Create API keys with metadata (#100682)

This commit is contained in:
Thom Heymann 2021-06-01 19:55:11 +03:00 committed by GitHub
parent 77f0a2a7ad
commit 27f790c8f2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 106 additions and 33 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 369 KiB

After

Width:  |  Height:  |  Size: 305 KiB

View file

@ -15,6 +15,7 @@ export interface ApiKey {
creation: number;
expiration: number;
invalidated: boolean;
metadata: Record<string, any>;
}
export interface ApiKeyToInvalidate {

View file

@ -28,6 +28,7 @@ export interface CreateApiKeyRequest {
name: string;
expiration?: string;
role_descriptors?: ApiKeyRoleDescriptors;
metadata?: Record<string, any>;
}
export interface CreateApiKeyResponse {

View file

@ -45,7 +45,9 @@ export interface ApiKeyFormValues {
expiration: string;
customExpiration: boolean;
customPrivileges: boolean;
includeMetadata: boolean;
role_descriptors: string;
metadata: string;
}
export interface CreateApiKeyFlyoutProps {
@ -59,30 +61,9 @@ const defaultDefaultValues: ApiKeyFormValues = {
expiration: '',
customExpiration: false,
customPrivileges: false,
role_descriptors: JSON.stringify(
{
'role-a': {
cluster: ['all'],
indices: [
{
names: ['index-a*'],
privileges: ['read'],
},
],
},
'role-b': {
cluster: ['all'],
indices: [
{
names: ['index-b*'],
privileges: ['all'],
},
],
},
},
null,
2
),
includeMetadata: false,
role_descriptors: '{}',
metadata: '{}',
};
export const CreateApiKeyFlyout: FunctionComponent<CreateApiKeyFlyoutProps> = ({
@ -227,7 +208,6 @@ export const CreateApiKeyFlyout: FunctionComponent<CreateApiKeyFlyoutProps> = ({
<EuiSpacer />
<EuiFormFieldset>
<EuiSwitch
id="apiKeyCustom"
label={i18n.translate(
'xpack.security.accountManagement.createApiKey.customPrivilegesLabel',
{
@ -270,7 +250,6 @@ export const CreateApiKeyFlyout: FunctionComponent<CreateApiKeyFlyoutProps> = ({
<EuiSpacer />
<EuiFormFieldset>
<EuiSwitch
name="customExpiration"
label={i18n.translate(
'xpack.security.accountManagement.createApiKey.customExpirationLabel',
{
@ -312,6 +291,48 @@ export const CreateApiKeyFlyout: FunctionComponent<CreateApiKeyFlyoutProps> = ({
)}
</EuiFormFieldset>
<EuiSpacer />
<EuiFormFieldset>
<EuiSwitch
label={i18n.translate(
'xpack.security.accountManagement.createApiKey.includeMetadataLabel',
{
defaultMessage: 'Include metadata',
}
)}
checked={!!form.values.includeMetadata}
onChange={(e) => form.setValue('includeMetadata', e.target.checked)}
/>
{form.values.includeMetadata && (
<>
<EuiSpacer size="m" />
<EuiFormRow
helpText={
<DocLink
app="elasticsearch"
doc="security-api-create-api-key.html#security-api-create-api-key-request-body"
>
<FormattedMessage
id="xpack.security.accountManagement.createApiKey.metadataHelpText"
defaultMessage="Learn how to structure metadata."
/>
</DocLink>
}
error={form.errors.metadata}
isInvalid={form.touched.metadata && !!form.errors.metadata}
>
<CodeEditorField
value={form.values.metadata!}
onChange={(value) => form.setValue('metadata', value)}
languageId="xjson"
height={200}
/>
</EuiFormRow>
<EuiSpacer size="s" />
</>
)}
</EuiFormFieldset>
{/* Hidden submit button is required for enter key to trigger form submission */}
<input type="submit" hidden />
</EuiForm>
@ -363,6 +384,28 @@ export function validate(values: ApiKeyFormValues) {
}
}
if (values.includeMetadata) {
if (!values.metadata) {
errors.metadata = i18n.translate(
'xpack.security.management.apiKeys.createApiKey.metadataRequired',
{
defaultMessage: 'Enter metadata or disable this option.',
}
);
} else {
try {
JSON.parse(values.metadata);
} catch (e) {
errors.metadata = i18n.translate(
'xpack.security.management.apiKeys.createApiKey.invalidJsonError',
{
defaultMessage: 'Enter valid JSON.',
}
);
}
}
}
return errors;
}
@ -374,5 +417,6 @@ export function mapValues(values: ApiKeyFormValues): CreateApiKeyRequest {
values.customPrivileges && values.role_descriptors
? JSON.parse(values.role_descriptors)
: undefined,
metadata: values.includeMetadata && values.metadata ? JSON.parse(values.metadata) : undefined,
};
}

View file

@ -30,15 +30,21 @@ export interface CreateAPIKeyParams {
name: string;
role_descriptors: Record<string, any>;
expiration?: string;
metadata?: Record<string, any>;
}
interface GrantAPIKeyParams {
api_key: CreateAPIKeyParams;
grant_type: 'password' | 'access_token';
username?: string;
password?: string;
access_token?: string;
}
type GrantAPIKeyParams =
| {
api_key: CreateAPIKeyParams;
grant_type: 'password';
username: string;
password: string;
}
| {
api_key: CreateAPIKeyParams;
grant_type: 'access_token';
access_token: string;
};
/**
* Represents the params for invalidating multiple API keys

View file

@ -85,6 +85,9 @@ describe('Create API Key route', () => {
role_descriptors: {
role_1: {},
},
metadata: {
foo: 'bar',
},
};
const request = httpServerMock.createKibanaRequest({

View file

@ -29,6 +29,7 @@ export function defineCreateApiKeyRoutes({
defaultValue: {},
}
),
metadata: schema.maybe(schema.object({}, { unknowns: 'allow' })),
}),
},
},

View file

@ -46,6 +46,23 @@ export default function ({ getService }: FtrProviderContext) {
expect(name).to.eql('test_api_key');
});
});
it('should allow an API Key to be created with metadata', async () => {
await supertest
.post('/internal/security/api_key')
.set('kbn-xsrf', 'xxx')
.send({
name: 'test_api_key_with_metadata',
metadata: {
foo: 'bar',
},
})
.expect(200)
.then((response: Record<string, any>) => {
const { name } = response.body;
expect(name).to.eql('test_api_key_with_metadata');
});
});
});
});
}