Skip to content

Commit f5a6d5e

Browse files
committed
updates auth method list and config views to use api service
1 parent d3a118c commit f5a6d5e

14 files changed

Lines changed: 252 additions & 108 deletions

File tree

ui/app/adapters/generated-item-list.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { service } from '@ember/service';
88
import { sanitizePath } from 'core/utils/sanitize-path';
99
import { encodePath } from 'vault/utils/path-encoding-helpers';
1010
import { tracked } from '@glimmer/tracking';
11+
import { getOwner } from '@ember/owner';
1112

1213
export default class GeneratedItemListAdapter extends ApplicationAdapter {
1314
@service store;
@@ -28,8 +29,10 @@ export default class GeneratedItemListAdapter extends ApplicationAdapter {
2829
return this.paths.deletePath || '';
2930
}
3031

31-
getDynamicApiPath(id) {
32-
const result = this.store.peekRecord('auth-method', id);
32+
getDynamicApiPath() {
33+
const result = getOwner(this)
34+
.lookup('route:vault.cluster.access.method')
35+
.modelFor('vault.cluster.access.method');
3336
this.apiPath = result.apiPath;
3437
return result.apiPath;
3538
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{{!
2+
Copyright (c) HashiCorp, Inc.
3+
SPDX-License-Identifier: BUSL-1.1
4+
}}
5+
6+
<div class="box is-fullwidth is-sideless is-paddingless is-marginless">
7+
{{#if @method.directLoginLink}}
8+
<InfoTableRow @alwaysRender={{true}} @label="UI login link">
9+
<Hds::Copy::Snippet @textToCopy={{@method.directLoginLink}} />
10+
</InfoTableRow>
11+
{{/if}}
12+
{{#each this.displayFields as |field|}}
13+
<InfoTableRow
14+
@alwaysRender={{not (is-empty-value (get @method field))}}
15+
@label={{this.label field}}
16+
@value={{this.value field}}
17+
@formatTtl={{this.isTtl field}}
18+
/>
19+
{{/each}}
20+
</div>
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/**
2+
* Copyright (c) HashiCorp, Inc.
3+
* SPDX-License-Identifier: BUSL-1.1
4+
*/
5+
6+
import Component from '@glimmer/component';
7+
import { toLabel } from 'core/helpers/to-label';
8+
import { get } from '@ember/object';
9+
10+
import type AuthMethodResource from 'vault/resources/auth/method';
11+
12+
interface Args {
13+
method: AuthMethodResource;
14+
}
15+
export default class AuthMethodConfigurationComponent extends Component<Args> {
16+
displayFields = [
17+
'type',
18+
'path',
19+
'description',
20+
'accessor',
21+
'local',
22+
'sealWrap',
23+
'config.listingVisibility',
24+
'config.defaultLeaseTtl',
25+
'config.maxLeaseTtl',
26+
'config.tokenType',
27+
'config.auditNonHmacRequestKeys',
28+
'config.auditNonHmacResponseKeys',
29+
'config.passthroughRequestHeaders',
30+
'config.allowedResponseHeaders',
31+
'config.pluginVersion',
32+
];
33+
34+
label = (field: string) => {
35+
const key = field.replace('config.', '');
36+
const label = toLabel([key]);
37+
// map specific fields to custom labels
38+
return (
39+
{
40+
listingVisibility: 'Use as preferred UI login method',
41+
defaultLeaseTtl: 'Default Lease TTL',
42+
maxLeaseTtl: 'Max Lease TTL',
43+
auditNonHmacRequestKeys: 'Request keys excluded from HMACing in audit',
44+
auditNonHmacResponseKeys: 'Response keys excluded from HMACing in audit',
45+
passthroughRequestHeaders: 'Allowed passthrough request headers',
46+
}[key] || label
47+
);
48+
};
49+
value = (field: string) => {
50+
const { method } = this.args;
51+
if (field === 'config.listingVisibility') {
52+
return method.config.listingVisibility === 'unauth';
53+
}
54+
return get(method, field);
55+
};
56+
57+
isTtl = (field: string) => {
58+
return ['config.defaultLeaseTtl', 'config.maxLeaseTtl'].includes(field);
59+
};
60+
}

ui/app/resources/auth/method.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/**
2+
* Copyright (c) HashiCorp, Inc.
3+
* SPDX-License-Identifier: BUSL-1.1
4+
*/
5+
6+
import { baseResourceFactory } from 'vault/resources/base-factory';
7+
import { service } from '@ember/service';
8+
import { supportedTypes } from 'vault/utils/supported-login-methods';
9+
import engineDisplayData from 'vault/helpers/engines-display-data';
10+
11+
import type { SecretsEngine } from 'vault/secrets/engine';
12+
import type VersionService from 'vault/services/version';
13+
import type NamespaceService from 'vault/services/namespace';
14+
import type { PathInfo } from 'vault/utils/openapi-helpers';
15+
16+
export default class AuthMethodResource extends baseResourceFactory<SecretsEngine>() {
17+
@service declare readonly version: VersionService;
18+
@service declare readonly namespace: NamespaceService;
19+
20+
id: string;
21+
declare paths: PathInfo;
22+
23+
constructor(data: SecretsEngine, context: unknown) {
24+
super(data, context);
25+
// strip trailing slash from path for id since it is used in routing
26+
this.id = data.path.replace(/\/$/, '');
27+
}
28+
29+
// namespaces introduced types with a `ns_` prefix for built-in engines
30+
// so we need to strip that to normalize the type
31+
get methodType() {
32+
return this.type.replace(/^ns_/, '');
33+
}
34+
35+
get icon() {
36+
// methodType refers to the backend type (e.g., "aws", "azure")
37+
const engineData = engineDisplayData(this.methodType);
38+
return engineData?.glyph || 'users';
39+
}
40+
41+
get directLoginLink() {
42+
const ns = this.namespace.path;
43+
const nsQueryParam = ns ? `namespace=${encodeURIComponent(ns)}&` : '';
44+
const isSupported = supportedTypes(this.version.isEnterprise).includes(this.methodType);
45+
return isSupported
46+
? `${window.origin}/ui/vault/auth?${nsQueryParam}with=${encodeURIComponent(this.path)}`
47+
: '';
48+
}
49+
50+
// used when the `auth` prefix is important,
51+
// currently only when setting perf mount filtering
52+
get apiPath() {
53+
return `auth/${this.path}`;
54+
}
55+
56+
get localDisplay() {
57+
return this.local ? 'local' : 'replicated';
58+
}
59+
60+
get supportsUserLockoutConfig() {
61+
return ['approle', 'ldap', 'userpass'].includes(this.methodType);
62+
}
63+
}

ui/app/resources/base-factory.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,27 @@
33
* SPDX-License-Identifier: BUSL-1.1
44
*/
55

6+
import { getOwner, setOwner } from '@ember/owner';
7+
8+
import type Owner from '@ember/owner';
9+
610
abstract class BaseResource<T> {
711
// pass data that the resource should represent (typically from an API response) to constructor
812
// object properties will be assigned to class instance
913
// extending classes can define getters and additional properties/methods that are required widely across the app
10-
constructor(readonly data: T) {
14+
constructor(data: T, context?: unknown) {
1115
Object.assign(this, data) as T;
16+
// pass in context (this) of Ember class (route, component etc.) where the resource is being constructed
17+
// this will be used to set the owner on the class so that services can be injected (if required)
18+
if (context) {
19+
setOwner(this, getOwner(context) as Owner);
20+
}
1221
}
1322
}
1423

15-
// factory that allows for the BaseResource class to be casted to the specific type provided
24+
// factory that allows for the BaseResource class to be cast to the specific type provided
1625
// without this the compiler is not aware of the properties set on the class via Object.assign
1726
// example usage -> export default class SecretsEngineResource extends baseResourceFactory<SecretsEngine>() { ... }
1827
export function baseResourceFactory<T>() {
19-
return BaseResource as new (data: T) => T;
28+
return BaseResource as new (data: T, context?: unknown) => T;
2029
}

ui/app/routes/vault/cluster/access/method.js

Lines changed: 0 additions & 40 deletions
This file was deleted.
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/**
2+
* Copyright (c) HashiCorp, Inc.
3+
* SPDX-License-Identifier: BUSL-1.1
4+
*/
5+
6+
import Route from '@ember/routing/route';
7+
import { service } from '@ember/service';
8+
import { supportedManagedAuthBackends } from 'vault/helpers/supported-managed-auth-backends';
9+
import AuthMethodResource from 'vault/resources/auth/method';
10+
11+
import type ApiService from 'vault/services/api';
12+
import type PathHelpService from 'vault/services/path-help';
13+
14+
export default class VaultClusterAccessMethodRoute extends Route {
15+
@service declare readonly api: ApiService;
16+
@service declare readonly pathHelp: PathHelpService;
17+
18+
async model(params: { path: string }) {
19+
const { path } = params;
20+
const { auth } = await this.api.sys.internalUiListEnabledVisibleMounts();
21+
const methods = this.api
22+
.responseObjectToArray(auth, 'path')
23+
.map((method) => new AuthMethodResource(method, this));
24+
const method = methods.find((m) => m.id === path);
25+
// the user could have entered a random path in the URL that doesn't correspond to an existing method
26+
if (method) {
27+
const supportManaged = supportedManagedAuthBackends();
28+
// do not fetch path-help for unmanaged auth types
29+
if (!supportManaged.includes(method.methodType)) {
30+
method.paths = { apiPath: method.apiPath, paths: [], itemTypes: [] };
31+
return method;
32+
}
33+
return this.pathHelp.getPaths(method.apiPath, path, '', '').then((pathInfo) => {
34+
method.paths = pathInfo;
35+
return method;
36+
});
37+
} else {
38+
// throw a 404 if the path doesn't match any of the fetched methods
39+
throw { httpStatus: 404, path };
40+
}
41+
}
42+
}

ui/app/routes/vault/cluster/access/method/section.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,13 @@ export default Route.extend({
1818
return this.modelFor('vault.cluster.access.method');
1919
},
2020

21-
setupController(controller) {
21+
setupController(controller, model) {
2222
const { section_name: section } = this.paramsFor(this.routeName);
2323
this._super(...arguments);
2424
controller.set('section', section);
25-
const method = this.modelFor('vault.cluster.access.method');
2625
controller.set(
2726
'paths',
28-
method.paths.paths.filter((path) => path.navigation)
27+
model.paths.paths.filter((path) => path.navigation)
2928
);
3029
},
3130
});

ui/app/routes/vault/cluster/access/methods.js

Lines changed: 0 additions & 24 deletions
This file was deleted.
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/**
2+
* Copyright (c) HashiCorp, Inc.
3+
* SPDX-License-Identifier: BUSL-1.1
4+
*/
5+
6+
import Route from '@ember/routing/route';
7+
import { service } from '@ember/service';
8+
import AuthMethodResource from 'vault/resources/auth/method';
9+
10+
import type ApiService from 'vault/services/api';
11+
12+
export default class VaultClusterAccessMethodsRoute extends Route {
13+
@service declare readonly api: ApiService;
14+
15+
queryParams = {
16+
page: {
17+
refreshModel: true,
18+
},
19+
pageFilter: {
20+
refreshModel: true,
21+
},
22+
};
23+
24+
async model() {
25+
const { auth } = await this.api.sys.internalUiListEnabledVisibleMounts();
26+
return this.api.responseObjectToArray(auth, 'path').map((method) => new AuthMethodResource(method, this));
27+
}
28+
}

0 commit comments

Comments
 (0)