[UI] Ember Data Migration - Auth Method/Config Cleanup (#9033) (#9058)

* removes remaining instances of auth-method model/adapter use

* removes auth method and config models/adapters/serializers

* fixes field to attrs tests

* fixes mfa tests

* fixes password reset

Co-authored-by: Jordan Reimer <zofskeez@gmail.com>
Co-authored-by: Jordan Reimer <jordan.reimer@hashicorp.com>
This commit is contained in:
Vault Automation 2025-09-04 11:00:29 -06:00 committed by GitHub
parent d8ecd066b8
commit b79dcd715a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
45 changed files with 95 additions and 1133 deletions

View File

@ -1,43 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import ApplicationAdapter from '../application';
export default ApplicationAdapter.extend({
namespace: '/v1/auth',
pathForType(modelType) {
// we want the last part of the path
const type = modelType.split('/').pop();
if (type === 'identity-accesslist' || type === 'roletag-denylist') {
return `tidy/${type}`;
}
return type;
},
buildURL(modelName, id, snapshot) {
const backendId = id ? id : snapshot.belongsTo('backend').id;
let url = `${this.namespace}/${backendId}/config`;
// aws has a lot more config endpoints
if (modelName.includes('aws')) {
url = `${url}/${this.pathForType(modelName)}`;
}
return url;
},
createRecord(store, type, snapshot) {
const id = snapshot.belongsTo('backend').id;
return this._super(...arguments).then(() => {
return { id };
});
},
updateRecord(store, type, snapshot) {
const id = snapshot.belongsTo('backend').id;
return this._super(...arguments).then(() => {
return { id };
});
},
});

View File

@ -1,7 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import AuthConfig from '../_base';
export default AuthConfig.extend();

View File

@ -1,7 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import AuthConfig from '../_base';
export default AuthConfig.extend();

View File

@ -1,7 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import AuthConfig from '../_base';
export default AuthConfig.extend();

View File

@ -1,8 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import AuthConfig from './_base';
export default AuthConfig.extend();

View File

@ -1,8 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import AuthConfig from './_base';
export default AuthConfig.extend();

View File

@ -1,8 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import AuthConfig from './_base';
export default AuthConfig.extend();

View File

@ -1,7 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import AuthConfig from './_base';
export default AuthConfig.extend();

View File

@ -1,8 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import AuthConfig from './_base';
export default AuthConfig.extend();

View File

@ -1,8 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import AuthConfig from './_base';
export default AuthConfig.extend();

View File

@ -1,7 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import AuthConfig from './_base';
export default AuthConfig.extend();

View File

@ -1,8 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import AuthConfig from './_base';
export default AuthConfig.extend();

View File

@ -1,8 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import AuthConfig from './_base';
export default AuthConfig.extend();

View File

@ -1,96 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import AdapterError from '@ember-data/adapter/error';
import { set } from '@ember/object';
import ApplicationAdapter from './application';
import { encodePath } from 'vault/utils/path-encoding-helpers';
export default ApplicationAdapter.extend({
url(path) {
const url = `${this.buildURL()}/auth`;
return path ? url + '/' + encodePath(path) : url;
},
// used in updateRecord
pathForType() {
return 'mounts/auth';
},
findAll(store, type, sinceToken, snapshotRecordArray) {
const isUnauthenticated = snapshotRecordArray?.adapterOptions?.unauthenticated;
// sys/internal/ui/mounts returns the actual value of the system TTL
// instead of '0' which just indicates the mount is using system defaults
if (isUnauthenticated) {
const url = `/${this.urlPrefix()}/internal/ui/mounts`;
return this.ajax(url, 'GET', {
unauthenticated: true,
})
.then((result) => {
return {
data: result.data.auth,
};
})
.catch(() => {
return { data: {} };
});
}
// if authenticated, findAll will use GET sys/auth instead
return this.ajax(this.url(), 'GET').catch((e) => {
if (e instanceof AdapterError) {
set(e, 'policyPath', 'sys/auth');
}
throw e;
});
},
// findAll makes a network request and supplements the ember-data store with what the API returns.
// after upgrading to ember-data 5.3.2 the store was becoming cluttered with outdated records, so
// use query to refresh the store with each request. this is ideal for list views
query() {
const url = `/${this.urlPrefix()}/internal/ui/mounts`;
return this.ajax(url, 'GET')
.then((result) => {
return {
data: result.data.auth,
};
})
.catch((e) => {
if (e instanceof AdapterError) {
set(e, 'policyPath', 'sys/internal/ui/mounts');
}
throw e;
});
},
createRecord(store, type, snapshot) {
const serializer = store.serializerFor(type.modelName);
const data = serializer.serialize(snapshot);
const path = snapshot.attr('path');
return this.ajax(this.url(path), 'POST', { data }).then(() => {
// ember data doesn't like 204s if it's not a DELETE
data.config.id = path; // config relationship needs an id so use path for now
return {
data: { ...data, path: path + '/', id: path },
};
});
},
urlForDeleteRecord(id, modelName, snapshot) {
return this.url(snapshot.id);
},
tune(path, data) {
const url = `${this.buildURL()}/${this.pathForType()}/${encodePath(path)}tune`;
return this.ajax(url, 'POST', { data });
},
resetPassword(backend, username, password) {
// For userpass auth types only
const url = `/v1/auth/${encodePath(backend)}/users/${encodePath(username)}/password`;
return this.ajax(url, 'POST', { data: { password } });
},
});

View File

@ -7,11 +7,13 @@ import { service } from '@ember/service';
import Component from '@ember/component';
import { task } from 'ember-concurrency';
import { underscore } from 'vault/helpers/underscore';
import AuthMethodResource from 'vault/resources/auth/method';
export default Component.extend({
store: service(),
flashMessages: service(),
router: service(),
api: service(),
// Public API - either 'entity' or 'group'
// this will determine which adapter is used to make the lookup call
@ -25,7 +27,10 @@ export default Component.extend({
init() {
this._super(...arguments);
this.store.findAll('auth-method').then((methods) => {
this.api.sys.authListEnabledMethods().then(({ data }) => {
const methods = this.api
.responseObjectToArray(data, 'path')
.map((method) => new AuthMethodResource(method, this));
this.set('authMethods', methods);
this.set('aliasMountAccessor', methods[0].accessor);
});

View File

@ -10,6 +10,7 @@ import { service } from '@ember/service';
import { task } from 'ember-concurrency';
import { addManyToArray, addToArray } from 'vault/helpers/add-to-array';
import { removeFromArray } from 'vault/helpers/remove-from-array';
import AuthMethodResource from 'vault/resources/auth/method';
/**
* @module MfaLoginEnforcementForm
@ -31,6 +32,7 @@ import { removeFromArray } from 'vault/helpers/remove-from-array';
export default class MfaLoginEnforcementForm extends Component {
@service store;
@service flashMessages;
@service api;
targetTypes = [
{ label: 'Authentication mount', type: 'accessor', key: 'auth_method_accessors' },
@ -93,8 +95,11 @@ export default class MfaLoginEnforcementForm extends Component {
}
}
async fetchAuthMethods() {
const mounts = await this.store.findAll('auth-method');
this.authMethods = mounts.map((auth) => auth.type).uniq();
const { data } = await this.api.sys.authListEnabledMethods();
this.authMethods = this.api
.responseObjectToArray(data, 'path')
.map((method) => new AuthMethodResource(method, this).methodType)
.uniq();
}
async fetchMfaMethods() {

View File

@ -7,6 +7,7 @@ import Component from '@glimmer/component';
import { service } from '@ember/service';
import { task } from 'ember-concurrency';
import { action } from '@ember/object';
import AuthMethodResource from 'vault/resources/auth/method';
/**
* @module MountAccessorSelect
@ -27,7 +28,7 @@ import { action } from '@ember/object';
*/
export default class MountAccessorSelect extends Component {
@service store;
@service api;
get filterToken() {
return this.args.filterToken || false;
@ -43,7 +44,11 @@ export default class MountAccessorSelect extends Component {
}
@task *authMethods() {
const methods = yield this.store.findAll('auth-method');
const { data } = yield this.api.sys.authListEnabledMethods();
const methods = this.api
.responseObjectToArray(data, 'path')
.map((method) => new AuthMethodResource(method, this));
if (!this.args.value && !this.args.noDefault) {
const getValue = methods[0].accessor;
this.args.onChange(getValue);

View File

@ -7,11 +7,10 @@ import { service } from '@ember/service';
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { task } from 'ember-concurrency';
import errorMessage from 'vault/utils/error-message';
export default class PageUserpassResetPasswordComponent extends Component {
@service store;
@service flashMessages;
@service api;
@tracked newPassword = '';
@tracked error = '';
@ -26,7 +25,6 @@ export default class PageUserpassResetPasswordComponent extends Component {
*updatePassword(evt) {
evt.preventDefault();
this.error = '';
const adapter = this.store.adapterFor('auth-method');
const { backend, username } = this.args;
if (!backend || !username) return;
if (!this.newPassword) {
@ -34,10 +32,11 @@ export default class PageUserpassResetPasswordComponent extends Component {
return;
}
try {
yield adapter.resetPassword(backend, username, this.newPassword);
yield this.api.auth.userpassResetPassword(username, backend, { password: this.newPassword });
this.onSuccess();
} catch (e) {
this.error = errorMessage(e, 'Check Vault logs for details');
const { message } = yield this.api.parseError(e, 'Check Vault logs for details');
this.error = message;
}
}
}

View File

@ -1,15 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import Model, { belongsTo } from '@ember-data/model';
export default Model.extend({
backend: belongsTo('auth-method', {
inverse: 'authConfigs',
readOnly: true,
async: false,
as: 'auth-config',
}),
});

View File

@ -1,7 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import AuthConfig from '../auth-config';
export default AuthConfig.extend({});

View File

@ -1,35 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import { attr } from '@ember-data/model';
import { computed } from '@ember/object';
import AuthConfig from '../../auth-config';
import fieldToAttrs from 'vault/utils/field-to-attrs';
export default AuthConfig.extend({
secretKey: attr('string'),
accessKey: attr('string'),
endpoint: attr('string', {
label: 'EC2 Endpoint',
}),
iamEndpoint: attr('string', {
label: 'IAM Endpoint',
}),
stsEndpoint: attr('string', {
label: 'STS Endpoint',
}),
iamServerIdHeaderValue: attr('string', {
label: 'IAM Server ID Header Value',
}),
fieldGroups: computed(function () {
const groups = [
{ default: ['accessKey', 'secretKey'] },
{ 'AWS Options': ['endpoint', 'iamEndpoint', 'stsEndpoint', 'iamServerIdHeaderValue'] },
];
return fieldToAttrs(this, groups);
}),
});

View File

@ -1,7 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import Tidy from './tidy';
export default Tidy.extend();

View File

@ -1,7 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import Tidy from './tidy';
export default Tidy.extend();

View File

@ -1,24 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import { attr } from '@ember-data/model';
import { computed } from '@ember/object';
import { expandAttributeMeta } from 'vault/utils/field-to-attrs';
import AuthConfig from '../../auth-config';
export default AuthConfig.extend({
safetyBuffer: attr({
defaultValue: '72h',
editType: 'ttl',
}),
disablePeriodicTidy: attr('boolean', {
defaultValue: false,
}),
attrs: computed(function () {
return expandAttributeMeta(this, ['safetyBuffer', 'disablePeriodicTidy']);
}),
});

View File

@ -1,44 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import { attr } from '@ember-data/model';
import { computed } from '@ember/object';
import AuthConfig from '../auth-config';
import { combineFieldGroups } from 'vault/utils/openapi-to-attrs';
import fieldToAttrs from 'vault/utils/field-to-attrs';
export default AuthConfig.extend({
tenantId: attr('string', {
label: 'Tenant ID',
helpText: 'The tenant ID for the Azure Active Directory organization',
}),
resource: attr('string', {
helpText: 'The configured URL for the application registered in Azure Active Directory',
}),
clientId: attr('string', {
label: 'Client ID',
helpText:
'The client ID for credentials to query the Azure APIs. Currently read permissions to query compute resources are required.',
}),
clientSecret: attr('string', {
helpText: 'The client secret for credentials to query the Azure APIs',
}),
googleCertsEndpoint: attr('string'),
fieldGroups: computed('newFields', function () {
let groups = [
{ default: ['tenantId', 'resource'] },
{
'Azure Options': ['clientId', 'clientSecret'],
},
];
if (this.newFields) {
groups = combineFieldGroups(groups, this.newFields, []);
}
return fieldToAttrs(this, groups);
}),
});

View File

@ -1,7 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import AuthConfig from '../auth-config';
export default AuthConfig.extend({});

View File

@ -1,32 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import { attr } from '@ember-data/model';
import { computed } from '@ember/object';
import AuthConfig from '../auth-config';
import { combineFieldGroups } from 'vault/utils/openapi-to-attrs';
import fieldToAttrs from 'vault/utils/field-to-attrs';
export default AuthConfig.extend({
// We have to leave this here because the backend doesn't support the file type yet.
credentials: attr('string', {
editType: 'file',
}),
googleCertsEndpoint: attr('string'),
fieldGroups: computed('newFields', function () {
let groups = [
{ default: ['credentials'] },
{
'Google Cloud Options': ['googleCertsEndpoint'],
},
];
if (this.newFields) {
groups = combineFieldGroups(groups, this.newFields, []);
}
return fieldToAttrs(this, groups);
}),
});

View File

@ -1,31 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import { attr } from '@ember-data/model';
import { computed } from '@ember/object';
import AuthConfig from '../auth-config';
import fieldToAttrs from 'vault/utils/field-to-attrs';
import { combineFieldGroups } from 'vault/utils/openapi-to-attrs';
export default AuthConfig.extend({
organization: attr('string'),
baseUrl: attr('string', {
label: 'Base URL',
}),
fieldGroups: computed('newFields', function () {
let groups = [
{ default: ['organization'] },
{
'GitHub Options': ['baseUrl'],
},
];
if (this.newFields) {
groups = combineFieldGroups(groups, this.newFields, []);
}
return fieldToAttrs(this, groups);
}),
});

View File

@ -1,99 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import { attr } from '@ember-data/model';
import { computed } from '@ember/object';
import AuthConfig from '../auth-config';
import fieldToAttrs from 'vault/utils/field-to-attrs';
import { combineFieldGroups } from 'vault/utils/openapi-to-attrs';
export default AuthConfig.extend({
oidcDiscoveryUrl: attr('string', {
label: 'OIDC discovery URL',
helpText:
'The OIDC discovery URL, without any .well-known component (base path). Cannot be used with jwt_validation_pubkeys',
}),
oidcClientId: attr('string', {
label: 'OIDC client ID',
}),
oidcClientSecret: attr('string', {
label: 'OIDC client secret',
}),
oidcDiscoveryCaPem: attr('string', {
label: 'OIDC discovery CA PEM',
editType: 'file',
helpText:
'The CA certificate or chain of certificates, in PEM format, to use to validate connections to the OIDC Discovery URL. If not set, system certificates are used',
}),
jwksCaPem: attr('string', {
label: 'JWKS CA PEM',
editType: 'file',
}),
jwksUrl: attr('string', {
label: 'JWKS URL',
}),
jwksPairs: attr({
label: 'JWKS pairs',
// This attribute is not shown in the UI
}),
oidcResponseMode: attr('string', {
label: 'OIDC response mode',
}),
oidcResponseTypes: attr('string', {
label: 'OIDC response types',
}),
jwtValidationPubkeys: attr({
label: 'JWT validation public keys',
editType: 'stringArray',
}),
jwtSupportedAlgs: attr({
label: 'JWT supported algorithms',
}),
boundIssuer: attr('string', {
helpText: 'The value against which to match the iss claim in a JWT',
}),
fieldGroups: computed('constructor.modelName', 'newFields', function () {
const type = this.constructor.modelName.split('/')[1].toUpperCase();
let groups = [
{
default: [
'oidcDiscoveryUrl',
'defaultRole',
'jwksCaPem',
'jwksUrl',
'oidcResponseMode',
'oidcResponseTypes',
],
},
{
[`${type} Options`]: [
'oidcClientId',
'oidcClientSecret',
'oidcDiscoveryCaPem',
'jwtValidationPubkeys',
'jwtSupportedAlgs',
'boundIssuer',
],
},
];
if (this.newFields) {
groups = combineFieldGroups(groups, this.newFields, []);
}
return fieldToAttrs(this, groups);
}),
});

View File

@ -1,50 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import { attr } from '@ember-data/model';
import { computed } from '@ember/object';
import AuthConfig from '../auth-config';
import { combineFieldGroups } from 'vault/utils/openapi-to-attrs';
import fieldToAttrs from 'vault/utils/field-to-attrs';
export default AuthConfig.extend({
kubernetesHost: attr('string', {
helpText:
'Host must be a host string, a host:port pair, or a URL to the base of the Kubernetes API server.',
}),
kubernetesCaCert: attr('string', {
editType: 'file',
helpText:
"Optional PEM encoded CA cert for use by the TLS client used to talk with the Kubernetes API. If it is not set and disable_local_ca_jwt is true, the system's trusted CA certificate pool will be used.",
}),
tokenReviewerJwt: attr('string', {
helpText:
'A service account JWT used to access the TokenReview API to validate other JWTs during login. If not set the JWT used for login will be used to access the API',
}),
useAnnotationsAsAliasMetadata: attr('boolean'),
pemKeys: attr({
editType: 'stringArray',
}),
fieldGroups: computed('newFields', function () {
let groups = [
{
default: ['kubernetesHost', 'kubernetesCaCert'],
},
{
'Kubernetes Options': ['tokenReviewerJwt', 'pemKeys', 'useAnnotationsAsAliasMetadata'],
},
];
if (this.newFields) {
groups = combineFieldGroups(groups, this.newFields, []);
}
return fieldToAttrs(this, groups);
}),
});

View File

@ -1,51 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import { attr } from '@ember-data/model';
import { computed } from '@ember/object';
import AuthConfig from '../auth-config';
import fieldToAttrs from 'vault/utils/field-to-attrs';
import { combineFieldGroups } from 'vault/utils/openapi-to-attrs';
export default AuthConfig.extend({
certificate: attr({
label: 'Certificate',
editType: 'file',
}),
fieldGroups: computed('newFields', function () {
let groups = [
{
default: ['url'],
},
{
'LDAP Options': [
'starttls',
'insecureTls',
'discoverdn',
'denyNullBind',
'tlsMinVersion',
'tlsMaxVersion',
'certificate',
'clientTlsCert',
'clientTlsKey',
'userattr',
'upndomain',
'anonymousGroupSearch',
],
},
{
'Customize User Search': ['binddn', 'userdn', 'bindpass', 'userfilter'],
},
{
'Customize Group Membership Search': ['groupfilter', 'groupattr', 'groupdn', 'useTokenGroups'],
},
];
if (this.newFields) {
groups = combineFieldGroups(groups, this.newFields, []);
}
return fieldToAttrs(this, groups);
}),
});

View File

@ -1,6 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
export { default } from './jwt';

View File

@ -1,45 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import { attr } from '@ember-data/model';
import { computed } from '@ember/object';
import AuthConfig from '../auth-config';
import fieldToAttrs from 'vault/utils/field-to-attrs';
import { combineFieldGroups } from 'vault/utils/openapi-to-attrs';
export default AuthConfig.extend({
orgName: attr('string', {
helpText: 'Name of the organization to be used in the Okta API',
}),
apiToken: attr('string', {
helpText:
'Okta API token. This is required to query Okta for user group membership. If this is not supplied only locally configured groups will be enabled.',
}),
baseUrl: attr('string', {
helpText:
'If set, will be used as the base domain for API requests. Examples are okta.com, oktapreview.com, and okta-emea.com',
}),
bypassOktaMfa: attr('boolean', {
defaultValue: false,
helpText:
"Useful if using Vault's built-in MFA mechanisms. Will also cause certain other statuses to be ignored, such as PASSWORD_EXPIRED",
}),
fieldGroups: computed('newFields', function () {
let groups = [
{
default: ['orgName'],
},
{
Options: ['apiToken', 'baseUrl', 'bypassOktaMfa'],
},
];
if (this.newFields) {
groups = combineFieldGroups(groups, this.newFields, []);
}
return fieldToAttrs(this, groups);
}),
});

View File

@ -1,31 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import { attr } from '@ember-data/model';
import { computed } from '@ember/object';
import AuthConfig from '../auth-config';
import { combineFieldGroups } from 'vault/utils/openapi-to-attrs';
import fieldToAttrs from 'vault/utils/field-to-attrs';
export default AuthConfig.extend({
host: attr('string'),
secret: attr('string'),
fieldGroups: computed('newFields', function () {
let groups = [
{
default: ['host', 'secret'],
},
{
'RADIUS Options': ['port', 'nasPort', 'nasIdentifier', 'dialTimeout', 'unregisteredUserPolicies'],
},
];
if (this.newFields) {
groups = combineFieldGroups(groups, this.newFields, []);
}
return fieldToAttrs(this, groups);
}),
});

View File

@ -1,7 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import AuthConfig from '../auth-config';
export default AuthConfig.extend({});

View File

@ -1,169 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import Model, { belongsTo, hasMany, attr } from '@ember-data/model';
import { service } from '@ember/service';
import fieldToAttrs, { expandAttributeMeta } from 'vault/utils/field-to-attrs';
import apiPath from 'vault/utils/api-path';
import { withModelValidations } from 'vault/decorators/model-validations';
import lazyCapabilities from 'vault/macros/lazy-capabilities';
import { action } from '@ember/object';
import { camelize } from '@ember/string';
import { WHITESPACE_WARNING } from 'vault/utils/forms/validators';
import { supportedTypes } from 'vault/utils/auth-form-helpers';
import engineDisplayData from 'vault/helpers/engines-display-data';
const validations = {
path: [
{ type: 'presence', message: "Path can't be blank." },
{
type: 'containsWhiteSpace',
message: WHITESPACE_WARNING('path'),
level: 'warn',
},
],
};
@withModelValidations(validations)
export default class AuthMethodModel extends Model {
@service namespace;
@service store;
@service version;
@belongsTo('mount-config', { async: false, inverse: null }) config; // one-to-none that replaces former fragment
@hasMany('auth-config', { polymorphic: true, inverse: 'backend', async: false }) authConfigs;
@attr('string') path;
@attr('string') accessor;
@attr('string') name;
@attr('string') type;
// namespaces introduced types with a `ns_` prefix for built-in engines
// so we need to strip that to normalize the type
get methodType() {
return this.type.replace(/^ns_/, '');
}
get icon() {
// methodType refers to the backend type (e.g., "aws", "azure") and is set on a getter.
const engineData = engineDisplayData(this.methodType);
return engineData?.glyph || 'users';
}
get directLoginLink() {
const ns = this.namespace.path;
const nsQueryParam = ns ? `namespace=${encodeURIComponent(ns)}&` : '';
const isSupported = supportedTypes(this.version.isEnterprise).includes(this.methodType);
return isSupported
? `${window.origin}/ui/vault/auth?${nsQueryParam}with=${encodeURIComponent(this.path)}`
: '';
}
@attr('string', {
editType: 'textarea',
})
description;
@attr('boolean', {
helpText:
'When Replication is enabled, a local mount will not be replicated across clusters. This can only be specified at mount time.',
})
local;
@attr('boolean', {
helpText:
'When enabled - if a seal supporting seal wrapping is specified in the configuration, all critical security parameters (CSPs) in this backend will be seal wrapped. (For KV mounts, all values will be seal wrapped.) This can only be specified at mount time.',
})
sealWrap;
// used when the `auth` prefix is important,
// currently only when setting perf mount filtering
get apiPath() {
return `auth/${this.path}`;
}
get localDisplay() {
return this.local ? 'local' : 'replicated';
}
get supportsUserLockoutConfig() {
return ['approle', 'ldap', 'userpass'].includes(this.methodType);
}
userLockoutConfig = {
modelAttrs: [
'config.lockoutThreshold',
'config.lockoutDuration',
'config.lockoutCounterReset',
'config.lockoutDisable',
],
apiParams: ['lockout_threshold', 'lockout_duration', 'lockout_counter_reset', 'lockout_disable'],
};
get tuneAttrs() {
// order here determines order tune fields render
const tuneAttrs = [
'listingVisibility',
'defaultLeaseTtl',
'maxLeaseTtl',
...(this.methodType === 'token' ? [] : ['tokenType']),
'auditNonHmacRequestKeys',
'auditNonHmacResponseKeys',
'passthroughRequestHeaders',
'allowedResponseHeaders',
'pluginVersion',
...(this.supportsUserLockoutConfig ? this.userLockoutConfig.apiParams.map((a) => camelize(a)) : []),
];
return expandAttributeMeta(this, ['description', `config.{${tuneAttrs.join(',')}}`]);
}
get formFields() {
return [
'type',
'path',
'description',
'accessor',
'local',
'sealWrap',
'config.{listingVisibility,defaultLeaseTtl,maxLeaseTtl,tokenType,auditNonHmacRequestKeys,auditNonHmacResponseKeys,passthroughRequestHeaders,allowedResponseHeaders,pluginVersion}',
];
}
get formFieldGroups() {
return [
{ default: ['path'] },
{
'Method Options': [
'description',
'config.listingVisibility',
'local',
'sealWrap',
'config.{defaultLeaseTtl,maxLeaseTtl,tokenType,auditNonHmacRequestKeys,auditNonHmacResponseKeys,passthroughRequestHeaders,allowedResponseHeaders,pluginVersion}',
],
},
];
}
get attrs() {
return expandAttributeMeta(this, this.formFields);
}
get fieldGroups() {
return fieldToAttrs(this, this.formFieldGroups);
}
@lazyCapabilities(apiPath`sys/auth/${'id'}`, 'id') deletePath;
@lazyCapabilities(apiPath`auth/${'id'}/config`, 'id') configPath;
@lazyCapabilities(apiPath`auth/${'id'}/config/client`, 'id') awsConfigPath;
get canDisable() {
return this.deletePath.get('canDelete') !== false;
}
get canEdit() {
return this.configPath.get('canUpdate') !== false;
}
get canEditAws() {
return this.awsConfigPath.get('canUpdate') !== false;
}
@action
tune(data) {
return this.store.adapterFor('auth-method').tune(this.path, data);
}
}

View File

@ -36,7 +36,8 @@ const validations = {
@withModelValidations(validations)
export default class MfaLoginEnforcementModel extends Model {
@service store;
@service api;
@attr('string') name;
@hasMany('mfa-method', { async: true, inverse: null }) mfa_methods;
@attr('string') namespace_id;
@ -58,8 +59,8 @@ export default class MfaLoginEnforcementModel extends Model {
if (this.auth_method_accessors.length || this.auth_method_types.length) {
// fetch all auth methods and lookup by accessor to get mount path and type
try {
const { data } = await this.store.adapterFor('auth-method').findAll();
authMethods = Object.keys(data).map((key) => ({ path: key, ...data[key] }));
const { data } = await this.api.sys.authListEnabledMethods();
authMethods = this.api.responseObjectToArray(data, 'path');
} catch (error) {
// swallow this error
}

View File

@ -6,9 +6,12 @@
// this model is just used for integration tests
//
import AuthMethodModel from './auth-method';
import { belongsTo } from '@ember-data/model';
import Model, { belongsTo, attr } from '@ember-data/model';
export default AuthMethodModel.extend({
otherConfig: belongsTo('mount-config', { async: false, inverse: null }),
});
export default class TestFormModel extends Model {
@belongsTo('mount-config', { async: false, inverse: null }) config;
@belongsTo('mount-config', { async: false, inverse: null }) otherConfig;
@attr('string') path;
@attr('string', { editType: 'textarea' }) description;
}

View File

@ -1,38 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import ApplicationSerializer from './application';
import { EmbeddedRecordsMixin } from '@ember-data/serializer/rest';
export default ApplicationSerializer.extend(EmbeddedRecordsMixin, {
attrs: {
config: { embedded: 'always' },
},
normalize(modelClass, data) {
// embedded records need a unique value to be stored
// use the uuid from the auth-method as the unique id for mount-config
if (data.config && !data.config.id) {
data.config.id = data.uuid;
}
return this._super(modelClass, data);
},
normalizeBackend(path, backend) {
const struct = { ...backend };
// strip the trailing slash off of the path so we
// can navigate to it without getting `//` in the url
struct.id = path.slice(0, -1);
struct.path = path;
return struct;
},
normalizeResponse(store, primaryModelClass, payload, id, requestType) {
const isCreate = requestType === 'createRecord';
const backends = isCreate
? payload.data
: Object.keys(payload.data).map((path) => this.normalizeBackend(path, payload.data[path]));
return this._super(store, primaryModelClass, backends, id, requestType);
},
});

View File

@ -208,16 +208,6 @@ export function filterPathsByItemType(pathInfo: PathInfo, itemType: string): Pat
* This object maps model names to the openAPI path that hydrates the model, given the backend path.
*/
const OPENAPI_POWERED_MODELS = {
'auth-config/azure': (backend: string) => `/v1/auth/${backend}/config?help=1`,
'auth-config/cert': (backend: string) => `/v1/auth/${backend}/config?help=1`,
'auth-config/gcp': (backend: string) => `/v1/auth/${backend}/config?help=1`,
'auth-config/github': (backend: string) => `/v1/auth/${backend}/config?help=1`,
'auth-config/jwt': (backend: string) => `/v1/auth/${backend}/config?help=1`,
'auth-config/kubernetes': (backend: string) => `/v1/auth/${backend}/config?help=1`,
'auth-config/ldap': (backend: string) => `/v1/auth/${backend}/config?help=1`,
'auth-config/oidc': (backend: string) => `/v1/auth/${backend}/config?help=1`,
'auth-config/okta': (backend: string) => `/v1/auth/${backend}/config?help=1`,
'auth-config/radius': (backend: string) => `/v1/auth/${backend}/config?help=1`,
'kmip/config': (backend: string) => `/v1/${backend}/config?help=1`,
'kmip/role': (backend: string) => `/v1/${backend}/scope/example/role/example?help=1`,
'pki/certificate/generate': (backend: string) => `/v1/${backend}/issue/example?help=1`,

View File

@ -14,6 +14,7 @@ import formFields from '../../pages/components/form-field';
import { format, startOfDay } from 'date-fns';
import { GENERAL } from 'vault/tests/helpers/general-selectors';
import AuthMethodForm from 'vault/forms/auth/method';
const component = create(formFields);
@ -301,14 +302,10 @@ module('Integration | Component | form field', function (hooks) {
});
test('it should show validation warning', async function (assert) {
const model = this.owner.lookup('service:store').createRecord('auth-method');
model.path = 'foo bar';
this.validations = model.validate().state;
this.setProperties({
model,
attr: createAttr('path', 'string'),
onChange: () => {},
});
this.model = new AuthMethodForm({ path: 'foo bar' });
this.attr = createAttr('path', 'string');
this.onChange = () => {};
this.validations = this.model.toJSON().state;
await render(
hbs`<FormField @attr={{this.attr}} @model={{this.model}} @modelValidations={{this.validations}} @onChange={{this.onChange}} />`

View File

@ -3,7 +3,6 @@
* SPDX-License-Identifier: BUSL-1.1
*/
import { run } from '@ember/runloop';
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
import fieldToAttrs, { expandAttributeMeta } from 'vault/utils/field-to-attrs';
@ -35,129 +34,104 @@ module('Integration | Util | field to attrs', function (hooks) {
options: { label: 'Max Lease TTL', editType: 'ttl' },
};
hooks.beforeEach(function () {
this.model = this.owner.lookup('service:store').createRecord('test-form-model');
});
test('it extracts attrs', function (assert) {
assert.expect(1);
const model = run(() => this.owner.lookup('service:store').createRecord('test-form-model'));
run(() => {
const [attr] = expandAttributeMeta(model, ['path']);
assert.deepEqual(attr, PATH_ATTR, 'returns attribute meta');
});
const [attr] = expandAttributeMeta(this.model, ['path']);
assert.deepEqual(attr, PATH_ATTR, 'returns attribute meta');
});
test('it extracts more than one attr', function (assert) {
assert.expect(2);
const model = run(() => this.owner.lookup('service:store').createRecord('test-form-model'));
run(() => {
const [path, desc] = expandAttributeMeta(model, ['path', 'description']);
assert.deepEqual(path, PATH_ATTR, 'returns attribute meta');
assert.deepEqual(desc, DESCRIPTION_ATTR, 'returns attribute meta');
});
const [path, desc] = expandAttributeMeta(this.model, ['path', 'description']);
assert.deepEqual(path, PATH_ATTR, 'returns attribute meta');
assert.deepEqual(desc, DESCRIPTION_ATTR, 'returns attribute meta');
});
test('it extracts fieldGroups', function (assert) {
assert.expect(1);
const model = run(() => this.owner.lookup('service:store').createRecord('test-form-model'));
run(() => {
const groups = fieldToAttrs(model, [{ default: ['path'] }, { Options: ['description'] }]);
const expected = [{ default: [PATH_ATTR] }, { Options: [DESCRIPTION_ATTR] }];
assert.deepEqual(groups, expected, 'expands all given groups');
});
const groups = fieldToAttrs(this.model, [{ default: ['path'] }, { Options: ['description'] }]);
const expected = [{ default: [PATH_ATTR] }, { Options: [DESCRIPTION_ATTR] }];
assert.deepEqual(groups, expected, 'expands all given groups');
});
test('it extracts arrays as fieldGroups', function (assert) {
assert.expect(1);
const model = run(() => this.owner.lookup('service:store').createRecord('test-form-model'));
run(() => {
const groups = fieldToAttrs(model, [
{ default: ['path', 'description'] },
{ Options: ['description'] },
]);
const expected = [{ default: [PATH_ATTR, DESCRIPTION_ATTR] }, { Options: [DESCRIPTION_ATTR] }];
assert.deepEqual(groups, expected, 'expands all given groups');
});
const groups = fieldToAttrs(this.model, [
{ default: ['path', 'description'] },
{ Options: ['description'] },
]);
const expected = [{ default: [PATH_ATTR, DESCRIPTION_ATTR] }, { Options: [DESCRIPTION_ATTR] }];
assert.deepEqual(groups, expected, 'expands all given groups');
});
test('it extracts model-fragment attributes with brace expansion', function (assert) {
assert.expect(3);
const model = run(() => this.owner.lookup('service:store').createRecord('test-form-model'));
run(() => {
const [attr] = expandAttributeMeta(model, ['config.{defaultLeaseTtl}']);
assert.deepEqual(attr, DEFAULT_LEASE_ATTR, 'properly extracts model fragment attr');
});
const [attr] = expandAttributeMeta(this.model, ['config.{defaultLeaseTtl}']);
assert.deepEqual(attr, DEFAULT_LEASE_ATTR, 'properly extracts model fragment attr');
run(() => {
const [defaultLease, maxLease] = expandAttributeMeta(model, ['config.{defaultLeaseTtl,maxLeaseTtl}']);
assert.deepEqual(defaultLease, DEFAULT_LEASE_ATTR, 'properly extracts default lease');
assert.deepEqual(maxLease, MAX_LEASE_ATTR, 'properly extracts max lease');
});
const [defaultLease, maxLease] = expandAttributeMeta(this.model, [
'config.{defaultLeaseTtl,maxLeaseTtl}',
]);
assert.deepEqual(defaultLease, DEFAULT_LEASE_ATTR, 'properly extracts default lease');
assert.deepEqual(maxLease, MAX_LEASE_ATTR, 'properly extracts max lease');
});
test('it extracts model-fragment attributes with double brace expansion', function (assert) {
assert.expect(4);
const model = run(() => this.owner.lookup('service:store').createRecord('test-form-model'));
run(() => {
const [configDefault, configMax, otherConfigDefault, otherConfigMax] = expandAttributeMeta(model, [
'{config,otherConfig}.{defaultLeaseTtl,maxLeaseTtl}',
]);
assert.deepEqual(configDefault, DEFAULT_LEASE_ATTR, 'properly extracts config.defaultLeaseTTL');
assert.deepEqual(
otherConfigDefault,
OTHER_DEFAULT_LEASE_ATTR,
'properly extracts otherConfig.defaultLeaseTTL'
);
const [configDefault, configMax, otherConfigDefault, otherConfigMax] = expandAttributeMeta(this.model, [
'{config,otherConfig}.{defaultLeaseTtl,maxLeaseTtl}',
]);
assert.deepEqual(configDefault, DEFAULT_LEASE_ATTR, 'properly extracts config.defaultLeaseTTL');
assert.deepEqual(
otherConfigDefault,
OTHER_DEFAULT_LEASE_ATTR,
'properly extracts otherConfig.defaultLeaseTTL'
);
assert.deepEqual(configMax, MAX_LEASE_ATTR, 'properly extracts config.maxLeaseTTL');
assert.deepEqual(otherConfigMax, OTHER_MAX_LEASE_ATTR, 'properly extracts otherConfig.maxLeaseTTL');
});
assert.deepEqual(configMax, MAX_LEASE_ATTR, 'properly extracts config.maxLeaseTTL');
assert.deepEqual(otherConfigMax, OTHER_MAX_LEASE_ATTR, 'properly extracts otherConfig.maxLeaseTTL');
});
test('it extracts model-fragment attributes with dot notation', function (assert) {
assert.expect(3);
const model = run(() => this.owner.lookup('service:store').createRecord('test-form-model'));
run(() => {
const [attr] = expandAttributeMeta(model, ['config.defaultLeaseTtl']);
assert.deepEqual(attr, DEFAULT_LEASE_ATTR, 'properly extracts model fragment attr');
});
const [attr] = expandAttributeMeta(this.model, ['config.defaultLeaseTtl']);
assert.deepEqual(attr, DEFAULT_LEASE_ATTR, 'properly extracts model fragment attr');
run(() => {
const [defaultLease, maxLease] = expandAttributeMeta(model, [
'config.defaultLeaseTtl',
'config.maxLeaseTtl',
]);
assert.deepEqual(defaultLease, DEFAULT_LEASE_ATTR, 'properly extracts model fragment attr');
assert.deepEqual(maxLease, MAX_LEASE_ATTR, 'properly extracts model fragment attr');
});
const [defaultLease, maxLease] = expandAttributeMeta(this.model, [
'config.defaultLeaseTtl',
'config.maxLeaseTtl',
]);
assert.deepEqual(defaultLease, DEFAULT_LEASE_ATTR, 'properly extracts model fragment attr');
assert.deepEqual(maxLease, MAX_LEASE_ATTR, 'properly extracts model fragment attr');
});
test('it extracts fieldGroups from model-fragment attributes with brace expansion', function (assert) {
assert.expect(1);
const model = run(() => this.owner.lookup('service:store').createRecord('test-form-model'));
const expected = [
{ default: [PATH_ATTR, DEFAULT_LEASE_ATTR, MAX_LEASE_ATTR] },
{ Options: [DESCRIPTION_ATTR] },
];
run(() => {
const groups = fieldToAttrs(model, [
{ default: ['path', 'config.{defaultLeaseTtl,maxLeaseTtl}'] },
{ Options: ['description'] },
]);
assert.deepEqual(groups, expected, 'properly extracts fieldGroups with brace expansion');
});
const groups = fieldToAttrs(this.model, [
{ default: ['path', 'config.{defaultLeaseTtl,maxLeaseTtl}'] },
{ Options: ['description'] },
]);
assert.deepEqual(groups, expected, 'properly extracts fieldGroups with brace expansion');
});
test('it extracts fieldGroups from model-fragment attributes with dot notation', function (assert) {
assert.expect(1);
const model = run(() => this.owner.lookup('service:store').createRecord('test-form-model'));
const expected = [
{ default: [DEFAULT_LEASE_ATTR, PATH_ATTR, MAX_LEASE_ATTR] },
{ Options: [DESCRIPTION_ATTR] },
];
run(() => {
const groups = fieldToAttrs(model, [
{ default: ['config.defaultLeaseTtl', 'path', 'config.maxLeaseTtl'] },
{ Options: ['description'] },
]);
assert.deepEqual(groups, expected, 'properly extracts fieldGroups with dot notation');
});
const groups = fieldToAttrs(this.model, [
{ default: ['config.defaultLeaseTtl', 'path', 'config.maxLeaseTtl'] },
{ Options: ['description'] },
]);
assert.deepEqual(groups, expected, 'properly extracts fieldGroups with dot notation');
});
});

View File

@ -1,67 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
import { setupMirage } from 'ember-cli-mirage/test-support';
module('Unit | Adapter | auth method', function (hooks) {
setupTest(hooks);
setupMirage(hooks);
hooks.beforeEach(async function () {
this.store = this.owner.lookup('service:store');
this.mockResponse = {
data: {
auth: {
'approle/': {
accessor: 'auth_approle_43e5a627',
config: {
default_lease_ttl: 2764800,
force_no_cache: false,
listing_visibility: 'hidden',
max_lease_ttl: 2764800,
token_type: 'default-service',
},
uuid: '7a8bc146-76d0-3a9c-9feb-47a6713a85b3',
},
},
},
};
});
test('findAll makes request to correct endpoint with no adapterOptions', async function (assert) {
assert.expect(1);
this.server.get('sys/auth', () => {
assert.ok(true, 'request made to sys/auth when no options are passed to findAll');
return { data: this.mockResponse.data.auth };
});
await this.store.findAll('auth-method');
});
test('findAll makes request to correct endpoint when unauthenticated is true', async function (assert) {
assert.expect(1);
this.server.get('sys/internal/ui/mounts', () => {
assert.ok(true, 'request made to correct endpoint when unauthenticated');
return this.mockResponse;
});
await this.store.findAll('auth-method', { adapterOptions: { unauthenticated: true } });
});
test('query makes request to correct endpoint ', async function (assert) {
assert.expect(1);
this.server.get('sys/internal/ui/mounts', () => {
assert.ok(true, 'request made to correct endpoint when useMountsEndpoint');
return this.mockResponse;
});
await this.store.query('auth-method', {});
});
});

View File

@ -7,7 +7,6 @@ import Application from 'vault/adapters/application';
import Adapter from 'ember-data/adapter';
import ModelRegistry from 'ember-data/types/registries/model';
import AuthMethodAdapter from 'vault/vault/adapters/auth-method';
import ClientsActivityAdapter from 'vault/vault/adapters/clients/activity';
import KvDataAdapter from 'vault/adapters/kv/data';
import KvMetadataAdapter from 'vault/adapters/kv/metadata';
@ -22,7 +21,6 @@ import SyncDestinationAdapter from 'vault/adapters/sync/destination';
* Catch-all for ember-data.
*/
export default interface AdapterRegistry {
'auth-method': AuthMethodAdapter;
'clients/activity': ClientsActivityAdapter;
'ldap/library': LdapLibraryAdapter;
'ldap/role': LdapRoleAdapter;

View File

@ -1,13 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import Store from '@ember-data/store';
import { AdapterRegistry } from 'ember-data/adapter';
import { OidcApiResponse, SamlApiResponse } from 'vault/auth/methods';
export default interface AuthMethodAdapter extends AdapterRegistry {
exchangeOIDC: (path: string, state: string, code: string) => Promise<OidcApiResponse>;
pollSAMLToken: (path: string, tokenPollID: string, clientVerifier: string) => Promise<SamlApiResponse>;
}