UI: Upgrade to Ember 4.12 (#22122)

This commit is contained in:
Chelsea Shaw
2023-08-01 14:02:21 -05:00
committed by GitHub
parent 1d01045e85
commit 8731cee07a
115 changed files with 3294 additions and 1522 deletions

3
changelog/22122.txt Normal file
View File

@@ -0,0 +1,3 @@
```release-note:improvement
ui: upgrade Ember to 4.12
```

View File

@@ -9,8 +9,8 @@
"output-path": "../http/web_ui", "output-path": "../http/web_ui",
/** /**
Setting `isTypeScriptProject` to true will force the blueprint generators to generate TypeScript Setting `isTypeScriptProject` to true will force the blueprint generators to generate TypeScript
rather than JavaScript by default, when a TypeScript version of a given blueprint is available. rather than JavaScript by default, when a TypeScript version of a given blueprint is available.
*/ */
"isTypeScriptProject": false "isTypeScriptProject": false
} }

View File

@@ -8,13 +8,14 @@
'use strict'; 'use strict';
module.exports = { module.exports = {
parser: 'babel-eslint', parser: '@babel/eslint-parser',
root: true, root: true,
parserOptions: { parserOptions: {
ecmaVersion: 2018, ecmaVersion: 'latest',
sourceType: 'module', sourceType: 'module',
ecmaFeatures: { requireConfigFile: false,
legacyDecorators: true, babelOptions: {
plugins: [['@babel/plugin-proposal-decorators', { decoratorsBeforeExport: true }]],
}, },
}, },
plugins: ['ember'], plugins: ['ember'],
@@ -45,6 +46,7 @@ module.exports = {
files: [ files: [
'./.eslintrc.js', './.eslintrc.js',
'./.prettierrc.js', './.prettierrc.js',
'./.stylelintrc.js',
'./.template-lintrc.js', './.template-lintrc.js',
'./ember-cli-build.js', './ember-cli-build.js',
'./testem.js', './testem.js',
@@ -60,13 +62,7 @@ module.exports = {
browser: false, browser: false,
node: true, node: true,
}, },
plugins: ['node'], extends: ['plugin:n/recommended'],
extends: ['plugin:node/recommended'],
rules: {
// this can be removed once the following is fixed
// https://github.com/mysticatea/eslint-plugin-node/issues/77
'node/no-unpublished-require': 'off',
},
}, },
{ {
// test files // test files

View File

@@ -17,5 +17,11 @@ module.exports = {
printWidth: 125, printWidth: 125,
}, },
}, },
{
files: '*.{js,ts}',
options: {
singleQuote: true,
},
},
], ],
}; };

8
ui/.stylelintignore Normal file
View File

@@ -0,0 +1,8 @@
# unconventional files
/blueprints/*/files/
# compiled output
/dist/
# addons
/.node_modules.ember-try/

5
ui/.stylelintrc.js Normal file
View File

@@ -0,0 +1,5 @@
'use strict';
module.exports = {
extends: ['stylelint-config-standard', 'stylelint-prettier/recommended'],
};

View File

@@ -46,6 +46,7 @@ module.exports = {
allow: ['supported-auth-backends'], allow: ['supported-auth-backends'],
}, },
'require-input-label': 'off', 'require-input-label': 'off',
'no-array-prototype-extensions': 'off',
}, },
ignore: ['lib/story-md', 'tests/**'], ignore: ['lib/story-md', 'tests/**'],
// ember language server vscode extension does not currently respect the ignore field // ember language server vscode extension does not currently respect the ignore field

View File

@@ -219,6 +219,8 @@ export default Component.extend(DEFAULTS, {
}; };
}) })
); );
// without unloading the records there will be an issue where all methods set to list when unauthenticated will appear for all namespaces
// if possible, it would be more reliable to add a namespace attr to the model so we could filter against the current namespace rather than unloading all
next(() => { next(() => {
store.unloadAll('auth-method'); store.unloadAll('auth-method');
}); });
@@ -265,7 +267,7 @@ export default Component.extend(DEFAULTS, {
return; return;
} }
let response = null; let response = null;
this.setOktaNumberChallenge(true); this.args.setOktaNumberChallenge(true);
this.setCancellingAuth(false); this.setCancellingAuth(false);
// keep polling /auth/okta/verify/:nonce API every 1s until a response is given with the correct number for the Okta Number Challenge // keep polling /auth/okta/verify/:nonce API every 1s until a response is given with the correct number for the Okta Number Challenge
while (response === null) { while (response === null) {
@@ -328,7 +330,7 @@ export default Component.extend(DEFAULTS, {
}); });
}, },
returnToLoginFromOktaNumberChallenge() { returnToLoginFromOktaNumberChallenge() {
this.setOktaNumberChallenge(false); this.args.setOktaNumberChallenge(false);
this.set('oktaNumberChallengeAnswer', null); this.set('oktaNumberChallengeAnswer', null);
}, },
}, },

View File

@@ -62,7 +62,7 @@ export default class DatabaseRoleEdit extends Component {
delete() { delete() {
const secret = this.args.model; const secret = this.args.model;
const backend = secret.backend; const backend = secret.backend;
secret return secret
.destroyRecord() .destroyRecord()
.then(() => { .then(() => {
try { try {
@@ -89,7 +89,7 @@ export default class DatabaseRoleEdit extends Component {
const path = roleSecret.type === 'static' ? 'static-roles' : 'roles'; const path = roleSecret.type === 'static' ? 'static-roles' : 'roles';
roleSecret.set('path', path); roleSecret.set('path', path);
} }
roleSecret return roleSecret
.save() .save()
.then(() => { .then(() => {
try { try {
@@ -110,7 +110,7 @@ export default class DatabaseRoleEdit extends Component {
rotateRoleCred(id) { rotateRoleCred(id) {
const backend = this.args.model?.backend; const backend = this.args.model?.backend;
const adapter = this.store.adapterFor('database/credential'); const adapter = this.store.adapterFor('database/credential');
adapter return adapter
.rotateRoleCredentials(backend, id) .rotateRoleCredentials(backend, id)
.then(() => { .then(() => {
this.flashMessages.success(`Success: Credentials for ${id} role were rotated`); this.flashMessages.success(`Success: Credentials for ${id} role were rotated`);

View File

@@ -55,7 +55,9 @@ export default Component.extend({
}, },
willDestroy() { willDestroy() {
if (!this.model.isDestroyed && !this.model.isDestroying) { // components are torn down after store is unloaded and will cause an error if attempt to unload record
const noTeardown = this.store && !this.store.isDestroying;
if (noTeardown && !this.model.isDestroyed && !this.model.isDestroying) {
this.model.unloadRecord(); this.model.unloadRecord();
} }
this._super(...arguments); this._super(...arguments);

View File

@@ -12,6 +12,7 @@ import { waitFor } from '@ember/test-waiters';
export default Component.extend({ export default Component.extend({
flashMessages: service(), flashMessages: service(),
store: service(),
'data-test-component': 'identity-edit-form', 'data-test-component': 'identity-edit-form',
attributeBindings: ['data-test-component'], attributeBindings: ['data-test-component'],
model: null, model: null,
@@ -73,12 +74,13 @@ export default Component.extend({
).drop(), ).drop(),
willDestroy() { willDestroy() {
this._super(...arguments); // components are torn down after store is disconnected and will cause an error if attempt to unload record
const noTeardown = this.store && !this.store.isDestroying;
const model = this.model; const model = this.model;
if (!model) return; if (noTeardown && model && model.get('isDirty') && !model.isDestroyed && !model.isDestroying) {
if ((model.get('isDirty') && !model.isDestroyed) || !model.isDestroying) {
model.rollbackAttributes(); model.rollbackAttributes();
} }
this._super(...arguments);
}, },
actions: { actions: {

View File

@@ -6,6 +6,7 @@
import Component from '@glimmer/component'; import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking'; import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object'; import { action } from '@ember/object';
import { A } from '@ember/array';
import { inject as service } from '@ember/service'; import { inject as service } from '@ember/service';
import { task } from 'ember-concurrency'; import { task } from 'ember-concurrency';
import handleHasManySelection from 'core/utils/search-select-has-many'; import handleHasManySelection from 'core/utils/search-select-has-many';
@@ -40,14 +41,14 @@ export default class MfaLoginEnforcementForm extends Component {
searchSelectOptions = null; searchSelectOptions = null;
@tracked name; @tracked name;
@tracked targets = []; @tracked targets = A([]);
@tracked selectedTargetType = 'accessor'; @tracked selectedTargetType = 'accessor';
@tracked selectedTargetValue = null; @tracked selectedTargetValue = null;
@tracked searchSelect = { @tracked searchSelect = {
options: [], options: [],
selected: [], selected: [],
}; };
@tracked authMethods = []; @tracked authMethods = A([]);
@tracked modelErrors; @tracked modelErrors;
constructor() { constructor() {
@@ -100,6 +101,12 @@ export default class MfaLoginEnforcementForm extends Component {
return this.args.modelErrors || this.modelErrors; return this.args.modelErrors || this.modelErrors;
} }
updateModelForKey(key) {
const newValue = this.targets.filter((t) => t.key === key).map((t) => t.value);
// Set the model value directly instead of using Array methods (like .addObject)
this.args.model[key] = newValue;
}
@task @task
*save() { *save() {
this.modelErrors = {}; this.modelErrors = {};
@@ -139,21 +146,22 @@ export default class MfaLoginEnforcementForm extends Component {
this.selectedTargetValue = selected; this.selectedTargetValue = selected;
} }
} }
@action @action
addTarget() { addTarget() {
const { label, key } = this.selectedTarget; const { label, key } = this.selectedTarget;
const value = this.selectedTargetValue; const value = this.selectedTargetValue;
this.targets.addObject({ label, value, key }); this.targets.addObject({ label, value, key });
// add target to appropriate model property // recalculate value for appropriate model property
this.args.model[key].addObject(value); this.updateModelForKey(key);
this.selectedTargetValue = null; this.selectedTargetValue = null;
this.resetTargetState(); this.resetTargetState();
} }
@action @action
removeTarget(target) { removeTarget(target) {
this.targets.removeObject(target); this.targets.removeObject(target);
// remove target from appropriate model property // recalculate value for appropriate model property
this.args.model[target.key].removeObject(target.value); this.updateModelForKey(target.key);
} }
@action @action
cancel() { cancel() {

View File

@@ -35,12 +35,12 @@ export default class MountBackendForm extends Component {
@tracked errorMessage = ''; @tracked errorMessage = '';
willDestroy() { willDestroy() {
// if unsaved, we want to unload so it doesn't show up in the auth mount list // components are torn down after store is unloaded and will cause an error if attempt to unload record
super.willDestroy(...arguments); const noTeardown = this.store && !this.store.isDestroying;
if (this.args.mountModel) { if (noTeardown && this.args?.mountModel) {
const method = this.args.mountModel.isNew ? 'unloadRecord' : 'rollbackAttributes'; this.args.mountModel.rollbackAttributes();
this.args.mountModel[method]();
} }
super.willDestroy(...arguments);
} }
checkPathChange(type) { checkPathChange(type) {

View File

@@ -26,10 +26,10 @@ export default Component.extend(FocusOnInsertMixin, {
requestInFlight: or('model.isLoading', 'model.isReloading', 'model.isSaving'), requestInFlight: or('model.isLoading', 'model.isReloading', 'model.isSaving'),
willDestroyElement() { willDestroyElement() {
this._super(...arguments); if (this.model && this.model.isError && !this.model.isDestroyed && !this.model.isDestroying) {
if (this.model && this.model.isError) {
this.model.rollbackAttributes(); this.model.rollbackAttributes();
} }
this._super(...arguments);
}, },
waitForKeyUp: task(function* () { waitForKeyUp: task(function* () {

View File

@@ -35,14 +35,13 @@ export default class SecretEdit extends Component {
@service store; @service store;
@tracked secretData = null; @tracked secretData = null;
@tracked isV2 = false;
@tracked codemirrorString = null; @tracked codemirrorString = null;
// fired on did-insert from render modifier // fired on did-insert from render modifier
@action @action
createKvData(elem, [model]) { createKvData(elem, [model]) {
if (!model.secretData && model.selectedVersion) { if (this.isV2) {
this.isV2 = true; // pre-fill secret data from selected version
model.secretData = model.belongsTo('selectedVersion').value().secretData; model.secretData = model.belongsTo('selectedVersion').value().secretData;
} }
this.secretData = KVObject.create({ content: [] }).fromJSON(model.secretData); this.secretData = KVObject.create({ content: [] }).fromJSON(model.secretData);
@@ -97,6 +96,9 @@ export default class SecretEdit extends Component {
@or('model.isLoading', 'model.isReloading', 'model.isSaving') requestInFlight; @or('model.isLoading', 'model.isReloading', 'model.isSaving') requestInFlight;
@or('requestInFlight', 'model.isFolder', 'model.flagsIsInvalid') buttonDisabled; @or('requestInFlight', 'model.isFolder', 'model.flagsIsInvalid') buttonDisabled;
get isV2() {
return !!this.args.model?.selectedVersion;
}
get modelForData() { get modelForData() {
const { model } = this.args; const { model } = this.args;
if (!model) return null; if (!model) return null;

View File

@@ -44,10 +44,10 @@ export default Component.extend(FocusOnInsertMixin, {
}, },
willDestroyElement() { willDestroyElement() {
this._super(...arguments); if (this.model && this.model.isError && !this.model.isDestroyed && !this.model.isDestroying) {
if (this.model && this.model.isError) {
this.model.rollbackAttributes(); this.model.rollbackAttributes();
} }
this._super(...arguments);
}, },
transitionToRoute() { transitionToRoute() {

View File

@@ -26,10 +26,10 @@ export default Component.extend(FocusOnInsertMixin, {
requestInFlight: or('key.isLoading', 'key.isReloading', 'key.isSaving'), requestInFlight: or('key.isLoading', 'key.isReloading', 'key.isSaving'),
willDestroyElement() { willDestroyElement() {
this._super(...arguments); if (this.key && this.key.isError && !this.key.isDestroyed && !this.key.isDestroying) {
if (this.key && this.key.isError) {
this.key.rollbackAttributes(); this.key.rollbackAttributes();
} }
this._super(...arguments);
}, },
waitForKeyUp: task(function* () { waitForKeyUp: task(function* () {

View File

@@ -10,10 +10,10 @@ export function initialize() {
registerDeprecationHandler((message, options, next) => { registerDeprecationHandler((message, options, next) => {
// filter deprecations that are scheduled to be removed in a specific version // filter deprecations that are scheduled to be removed in a specific version
// when upgrading or addressing deprecation warnings be sure to update this or remove if not needed // when upgrading or addressing deprecation warnings be sure to update this or remove if not needed
if (options?.until !== '5.0.0') { if (options?.until.includes('5.0')) {
next(message, options); return;
} }
return; next(message, options);
}); });
} }

View File

@@ -6,6 +6,7 @@
export const INIT = 'vault.cluster.init'; export const INIT = 'vault.cluster.init';
export const UNSEAL = 'vault.cluster.unseal'; export const UNSEAL = 'vault.cluster.unseal';
export const AUTH = 'vault.cluster.auth'; export const AUTH = 'vault.cluster.auth';
export const LOGOUT = 'vault.cluster.logout';
export const REDIRECT = 'vault.cluster.redirect'; export const REDIRECT = 'vault.cluster.redirect';
export const CLUSTER = 'vault.cluster'; export const CLUSTER = 'vault.cluster';
export const CLUSTER_INDEX = 'vault.cluster.index'; export const CLUSTER_INDEX = 'vault.cluster.index';

View File

@@ -7,6 +7,23 @@ import { computed } from '@ember/object';
import ObjectProxy from '@ember/object/proxy'; import ObjectProxy from '@ember/object/proxy';
import PromiseProxyMixin from '@ember/object/promise-proxy-mixin'; import PromiseProxyMixin from '@ember/object/promise-proxy-mixin';
import { resolve } from 'rsvp'; import { resolve } from 'rsvp';
/**
* after upgrading to Ember 4.12 a secrets test was erroring with "Cannot create a new tag for `<model::capabilities:undefined>` after it has been destroyed"
* see this GH issue for information on the fix https://github.com/emberjs/ember.js/issues/16541#issuecomment-382403523
*/
ObjectProxy.reopen({
unknownProperty(key) {
if (this.isDestroying || this.isDestroyed) {
return;
}
if (this.content && (this.content.isDestroying || this.content.isDestroyed)) {
return;
}
return this._super(key);
},
});
export function maybeQueryRecord(modelName, options = {}, ...keys) { export function maybeQueryRecord(modelName, options = {}, ...keys) {
return computed(...keys, 'store', { return computed(...keys, 'store', {

View File

@@ -56,6 +56,11 @@ export default Mixin.create({
); );
return; return;
} }
if (this.store.isDestroyed || this.store.isDestroying) {
// Prevent unload attempt after test teardown, resulting in test failure
return;
}
if (modelType) { if (modelType) {
this.store.unloadAll(modelType); this.store.unloadAll(modelType);
} }

View File

@@ -19,7 +19,6 @@ export default Mixin.create({
return; return;
} }
removeRecord(this.store, model); removeRecord(this.store, model);
model.destroy();
// it's important to unset the model on the controller since controllers are singletons // it's important to unset the model on the controller since controllers are singletons
this.controller.set(modelPath, null); this.controller.set(modelPath, null);
}, },

View File

@@ -4,6 +4,7 @@
*/ */
import Mixin from '@ember/object/mixin'; import Mixin from '@ember/object/mixin';
import Ember from 'ember';
// this mixin relies on `unload-model-route` also being used // this mixin relies on `unload-model-route` also being used
export default Mixin.create({ export default Mixin.create({
@@ -15,6 +16,7 @@ export default Mixin.create({
} }
if (model.hasDirtyAttributes) { if (model.hasDirtyAttributes) {
if ( if (
Ember.testing ||
window.confirm( window.confirm(
'You have unsaved changes. Navigating away will discard these changes. Are you sure you want to discard your changes?' 'You have unsaved changes. Navigating away will discard these changes. Are you sure you want to discard your changes?'
) )

View File

@@ -5,75 +5,104 @@
import Model, { attr, belongsTo, hasMany } from '@ember-data/model'; import Model, { attr, belongsTo, hasMany } from '@ember-data/model';
import { inject as service } from '@ember/service'; import { inject as service } from '@ember/service';
import { alias, and, equal, gte, not, or } from '@ember/object/computed'; import { get } from '@ember/object';
import { get, computed } from '@ember/object';
export default Model.extend({ export default class ClusterModel extends Model {
version: service(), @service version;
nodes: hasMany('nodes', { async: false }), @hasMany('nodes', { async: false, inverse: null }) nodes;
name: attr('string'), @attr('string') name;
status: attr('string'), @attr('string') status;
standby: attr('boolean'), @attr('boolean') standby;
type: attr('string'), @attr('string') type;
license: attr('object'), @attr('object') license;
/* Licensing concerns */ /* Licensing concerns */
licenseExpiry: alias('license.expiry_time'), get licenseExpiry() {
licenseState: alias('license.state'), return this.license?.expiry_time;
}
get licenseState() {
return this.license?.state;
}
needsInit: computed('nodes', 'nodes.@each.initialized', function () { get needsInit() {
// needs init if no nodes are initialized return this.nodes.every((node) => {
return this.nodes.isEvery('initialized', false); return node.initialized === false;
}), });
}
unsealed: computed('nodes', 'nodes.{[],@each.sealed}', function () { get unsealed() {
// unsealed if there's at least one unsealed node return !!this.nodes.find((node) => {
return !!this.nodes.findBy('sealed', false); return node.sealed === false;
}), });
}
sealed: not('unsealed'), get sealed() {
return !this.unsealed;
}
leaderNode: computed('nodes', 'nodes.[]', function () { get leaderNode() {
const nodes = this.nodes; const nodes = this.nodes;
if (nodes.get('length') === 1) { if (nodes.length === 1) {
return nodes.get('firstObject'); return nodes[0];
} else { } else {
return nodes.findBy('isLeader'); return nodes.find((node) => node.isLeader === true);
} }
}), }
sealThreshold: alias('leaderNode.sealThreshold'), get sealThreshold() {
sealProgress: alias('leaderNode.progress'), return this.leaderNode?.sealThreshold;
sealType: alias('leaderNode.type'), }
storageType: alias('leaderNode.storageType'), get sealProgress() {
hcpLinkStatus: alias('leaderNode.hcpLinkStatus'), return this.leaderNode?.progress;
hasProgress: gte('sealProgress', 1), }
usingRaft: equal('storageType', 'raft'), get sealType() {
return this.leaderNode?.type;
}
get storageType() {
return this.leaderNode?.storageType;
}
get hcpLinkStatus() {
return this.leaderNode?.hcpLinkStatus;
}
get hasProgress() {
return this.sealProgress >= 1;
}
get usingRaft() {
return this.storageType === 'raft';
}
//replication mode - will only ever be 'unsupported' //replication mode - will only ever be 'unsupported'
//otherwise the particular mode will have the relevant mode attr through replication-attributes //otherwise the particular mode will have the relevant mode attr through replication-attributes
mode: attr('string'), @attr('string') mode;
allReplicationDisabled: and('{dr,performance}.replicationDisabled'), get allReplicationDisabled() {
anyReplicationEnabled: or('{dr,performance}.replicationEnabled'), return this.dr?.replicationDisabled && this.performance?.replicationDisabled;
}
get anyReplicationEnabled() {
return this.dr?.replicationEnabled || this.performance?.replicationEnabled;
}
dr: belongsTo('replication-attributes', { async: false, inverse: null }), @belongsTo('replication-attributes', { async: false, inverse: null }) dr;
performance: belongsTo('replication-attributes', { async: false, inverse: null }), @belongsTo('replication-attributes', { async: false, inverse: null }) performance;
// this service exposes what mode the UI is currently viewing // this service exposes what mode the UI is currently viewing
// replicationAttrs will then return the relevant `replication-attributes` model // replicationAttrs will then return the relevant `replication-attributes` model
rm: service('replication-mode'), @service('replication-mode') rm;
drMode: alias('dr.mode'), get drMode() {
replicationMode: alias('rm.mode'), return this.dr.mode;
replicationModeForDisplay: computed('replicationMode', function () { }
get replicationMode() {
return this.rm.mode;
}
get replicationModeForDisplay() {
return this.replicationMode === 'dr' ? 'Disaster Recovery' : 'Performance'; return this.replicationMode === 'dr' ? 'Disaster Recovery' : 'Performance';
}), }
replicationIsInitializing: computed('dr.mode', 'performance.mode', function () { get replicationIsInitializing() {
// a mode of null only happens when a cluster is being initialized // a mode of null only happens when a cluster is being initialized
// otherwise the mode will be 'disabled', 'primary', 'secondary' // otherwise the mode will be 'disabled', 'primary', 'secondary'
return !this.dr.mode || !this.performance.mode; return !this.dr?.mode || !this.performance?.mode;
}), }
replicationAttrs: computed('dr.mode', 'performance.mode', 'replicationMode', function () { get replicationAttrs() {
const replicationMode = this.replicationMode; const replicationMode = this.replicationMode;
return replicationMode ? get(this, replicationMode) : null; return replicationMode ? get(this, replicationMode) : null;
}), }
}); }

View File

@@ -52,12 +52,6 @@ export default class KubernetesRoleModel extends Model {
}) })
kubernetesRoleName; kubernetesRoleName;
@attr('string', {
label: 'Service account name',
subText: 'Vault will use the default template when generating service accounts, roles and role bindings.',
})
serviceAccountName;
@attr('string', { @attr('string', {
label: 'Allowed Kubernetes namespaces', label: 'Allowed Kubernetes namespaces',
subText: subText:

View File

@@ -160,13 +160,6 @@ export default class SecretEngineModel extends Model {
return 'vault.cluster.secrets.backend.list-root'; return 'vault.cluster.secrets.backend.list-root';
} }
get accessor() {
if (this.version === 2) {
return `v2 ${this.accessor}`;
}
return this.accessor;
}
get localDisplay() { get localDisplay() {
return this.local ? 'local' : 'replicated'; return this.local ? 'local' : 'replicated';
} }

View File

@@ -5,6 +5,7 @@
import AdapterError from '@ember-data/adapter/error'; import AdapterError from '@ember-data/adapter/error';
import { set } from '@ember/object'; import { set } from '@ember/object';
import Ember from 'ember';
import { resolve } from 'rsvp'; import { resolve } from 'rsvp';
import { inject as service } from '@ember/service'; import { inject as service } from '@ember/service';
import Route from '@ember/routing/route'; import Route from '@ember/routing/route';
@@ -314,6 +315,12 @@ export default Route.extend(UnloadModelRoute, {
willTransition(transition) { willTransition(transition) {
/* eslint-disable-next-line ember/no-controller-access-in-routes */ /* eslint-disable-next-line ember/no-controller-access-in-routes */
const { mode, model } = this.controller; const { mode, model } = this.controller;
// If model is clean or deleted, continue
if (!model.hasDirtyAttributes || model.isDeleted) {
return true;
}
// TODO: below is KV v2 logic, remove with engine work
const version = model.get('selectedVersion'); const version = model.get('selectedVersion');
const changed = model.changedAttributes(); const changed = model.changedAttributes();
const changedKeys = Object.keys(changed); const changedKeys = Object.keys(changed);
@@ -330,9 +337,10 @@ export default Route.extend(UnloadModelRoute, {
// and explicity ignore it here // and explicity ignore it here
if ( if (
(mode !== 'show' && changedKeys.length && changedKeys[0] !== 'backend') || (mode !== 'show' && changedKeys.length && changedKeys[0] !== 'backend') ||
(mode !== 'show' && version && Object.keys(version.changedAttributes()).length) (mode !== 'show' && version && version.hasDirtyAttributes)
) { ) {
if ( if (
Ember.testing ||
window.confirm( window.confirm(
'You have unsaved changes. Navigating away will discard these changes. Are you sure you want to discard your changes?' 'You have unsaved changes. Navigating away will discard these changes. Are you sure you want to discard your changes?'
) )

View File

@@ -9,11 +9,6 @@ import { inject as service } from '@ember/service';
export default class VaultClusterSettingsAuthEnableRoute extends Route { export default class VaultClusterSettingsAuthEnableRoute extends Route {
@service store; @service store;
beforeModel() {
// Unload to prevent naming collisions when we mount a new engine
this.store.unloadAll('auth-method');
}
model() { model() {
const authMethod = this.store.createRecord('auth-method'); const authMethod = this.store.createRecord('auth-method');
authMethod.set('config', this.store.createRecord('mount-config')); authMethod.set('config', this.store.createRecord('mount-config'));

View File

@@ -9,11 +9,6 @@ import { inject as service } from '@ember/service';
export default class VaultClusterSettingsMountSecretBackendRoute extends Route { export default class VaultClusterSettingsMountSecretBackendRoute extends Route {
@service store; @service store;
beforeModel() {
// Unload to prevent naming collisions when we mount a new engine
this.store.unloadAll('secret-engine');
}
model() { model() {
const secretEngine = this.store.createRecord('secret-engine'); const secretEngine = this.store.createRecord('secret-engine');
secretEngine.set('config', this.store.createRecord('mount-config')); secretEngine.set('config', this.store.createRecord('mount-config'));

View File

@@ -40,6 +40,7 @@ export default class MfaLoginEnforcementSerializer extends ApplicationSerializer
// ensure that they are sent to the server, otherwise removing items will not be persisted // ensure that they are sent to the server, otherwise removing items will not be persisted
json.auth_method_accessors = json.auth_method_accessors || []; json.auth_method_accessors = json.auth_method_accessors || [];
json.auth_method_types = json.auth_method_types || []; json.auth_method_types = json.auth_method_types || [];
// TODO: create array transform which serializes an empty array if empty
return this.transformHasManyKeys(json, 'server'); return this.transformHasManyKeys(json, 'server');
} }
} }

View File

@@ -1,7 +0,0 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
import ApplicationSerializer from './application';
export default ApplicationSerializer.extend();

View File

@@ -4,19 +4,21 @@
*/ */
import Ember from 'ember'; import Ember from 'ember';
import { resolve, reject } from 'rsvp'; import { task, timeout } from 'ember-concurrency';
import { assign } from '@ember/polyfills'; import { getOwner } from '@ember/application';
import { isArray } from '@ember/array'; import { isArray } from '@ember/array';
import { computed, get } from '@ember/object'; import { computed, get } from '@ember/object';
import { capitalize } from '@ember/string'; import { alias } from '@ember/object/computed';
import { assign } from '@ember/polyfills';
import fetch from 'fetch';
import { getOwner } from '@ember/application';
import Service, { inject as service } from '@ember/service'; import Service, { inject as service } from '@ember/service';
import getStorage from '../lib/token-storage'; import { capitalize } from '@ember/string';
import fetch from 'fetch';
import { resolve, reject } from 'rsvp';
import getStorage from 'vault/lib/token-storage';
import ENV from 'vault/config/environment'; import ENV from 'vault/config/environment';
import { supportedAuthBackends } from 'vault/helpers/supported-auth-backends'; import { supportedAuthBackends } from 'vault/helpers/supported-auth-backends';
import { task, timeout } from 'ember-concurrency';
const TOKEN_SEPARATOR = '☃'; const TOKEN_SEPARATOR = '☃';
const TOKEN_PREFIX = 'vault-'; const TOKEN_PREFIX = 'vault-';
const ROOT_PREFIX = '_root_'; const ROOT_PREFIX = '_root_';
@@ -26,7 +28,7 @@ export { TOKEN_SEPARATOR, TOKEN_PREFIX, ROOT_PREFIX };
export default Service.extend({ export default Service.extend({
permissions: service(), permissions: service(),
store: service(), currentCluster: service(),
router: service(), router: service(),
namespaceService: service('namespace'), namespaceService: service('namespace'),
@@ -40,9 +42,7 @@ export default Service.extend({
return expiration ? this.now() >= expiration : null; return expiration ? this.now() >= expiration : null;
}, },
get activeCluster() { activeCluster: alias('currentCluster.cluster'),
return this.activeClusterId ? this.store.peekRecord('cluster', this.activeClusterId) : null;
},
// eslint-disable-next-line // eslint-disable-next-line
tokens: computed({ tokens: computed({

View File

@@ -4,11 +4,12 @@
*/ */
import Service from '@ember/service'; import Service from '@ember/service';
import { tracked } from '@glimmer/tracking';
export default Service.extend({ export default class CurrentClusterService extends Service {
cluster: null, @tracked cluster = null;
setCluster(cluster) { setCluster(cluster) {
this.set('cluster', cluster); this.cluster = cluster;
}, }
}); }

View File

@@ -4,15 +4,16 @@
*/ */
import Service from '@ember/service'; import Service from '@ember/service';
import { tracked } from '@glimmer/tracking';
export default Service.extend({ export default class ReplicationModeService extends Service {
mode: null, @tracked mode = null;
getMode() { getMode() {
return this.mode; return this.mode;
}, }
setMode(mode) { setMode(mode) {
this.set('mode', mode); this.mode = mode;
}, }
}); }

View File

@@ -140,7 +140,7 @@ export default class StoreService extends Store {
// pushes records into the store and returns the result // pushes records into the store and returns the result
fetchPage(modelName, query) { fetchPage(modelName, query) {
const response = this.constructResponse(modelName, query); const response = this.constructResponse(modelName, query);
this.peekAll(modelName).map((model) => model.unloadRecord()); this.unloadAll(modelName);
return new Promise((resolve) => { return new Promise((resolve) => {
// after the above unloadRecords are finished, push into store // after the above unloadRecords are finished, push into store
schedule('destroy', () => { schedule('destroy', () => {
@@ -187,4 +187,29 @@ export default class StoreService extends Store {
clearAllDatasets() { clearAllDatasets() {
this.clearDataset(); this.clearDataset();
} }
/**
* this is designed to be a temporary workaround to an issue in the test environment after upgrading to Ember 4.12
* when performing an unloadAll or unloadRecord for auth-method or secret-engine models within the app code an error breaks the tests
* after the test run is finished during teardown an unloadAll happens and the error "Expected a stable identifier" is thrown
* it seems that when the unload happens in the app, for some reason the mount-config relationship models are not unloaded
* then when the unloadAll happens a second time during test teardown there seems to be an issue since those records should already have been unloaded
* when logging in the teardownRecord hook, it appears that other embedded inverse: null relationships such as replication-attributes are torn down when the parent model is unloaded
* the following fixes the issue by explicitly unloading the mount-config models associated to the parent
* this should be looked into further to find the root cause, at which time these overrides may be removed
*/
unloadAll(modelName) {
const hasMountConfig = ['auth-method', 'secret-engine'];
if (hasMountConfig.includes(modelName)) {
this.peekAll(modelName).forEach((record) => this.unloadRecord(record));
} else {
super.unloadAll(modelName);
}
}
unloadRecord(record) {
const hasMountConfig = ['auth-method', 'secret-engine'];
if (record && hasMountConfig.includes(record.constructor.modelName) && record.config) {
super.unloadRecord(record.config);
}
super.unloadRecord(record);
}
} }

View File

@@ -69,8 +69,8 @@
</div> </div>
{{/if}} {{/if}}
<div class="legend-center"> <div class="legend-center">
<span class="light-dot"></span><span class="legend-label">{{capitalize @chartLegend.0.label}}</span> <span class="light-dot"></span><span class="legend-label">{{capitalize (get @chartLegend "0.label")}}</span>
<span class="dark-dot"></span><span class="legend-label">{{capitalize @chartLegend.1.label}}</span> <span class="dark-dot"></span><span class="legend-label">{{capitalize (get @chartLegend "1.label")}}</span>
</div> </div>
{{else}} {{else}}
<div class="chart-empty-state"> <div class="chart-empty-state">

View File

@@ -15,11 +15,7 @@
{{! Component must be in curly bracket notation }} {{! Component must be in curly bracket notation }}
{{! template-lint-disable no-curly-component-invocation }} {{! template-lint-disable no-curly-component-invocation }}
{{#modal-dialog {{#modal-dialog
tagName="div" tagName="div" tetherTarget=this.tooltipTarget targetAttachment="bottom middle" attachment="bottom middle" offset="35px 0"
tetherTarget=this.tooltipTarget
targetAttachment="bottom middle"
attachment="bottom middle"
offset="35px 0"
}} }}
<div class={{concat "chart-tooltip horizontal-chart " (if this.isLabel "is-label-fit-content")}}> <div class={{concat "chart-tooltip horizontal-chart " (if this.isLabel "is-label-fit-content")}}>
<p>{{this.tooltipText}}</p> <p>{{this.tooltipText}}</p>

View File

@@ -18,11 +18,7 @@
{{! Component must be in curly bracket notation }} {{! Component must be in curly bracket notation }}
{{! template-lint-disable no-curly-component-invocation }} {{! template-lint-disable no-curly-component-invocation }}
{{#modal-dialog {{#modal-dialog
tagName="div" tagName="div" tetherTarget=this.tooltipTarget targetAttachment="bottom middle" attachment="bottom middle" offset="35px 0"
tetherTarget=this.tooltipTarget
targetAttachment="bottom middle"
attachment="bottom middle"
offset="35px 0"
}} }}
<div class="chart-tooltip line-chart"> <div class="chart-tooltip line-chart">
<p class="bold">{{this.tooltipMonth}}</p> <p class="bold">{{this.tooltipMonth}}</p>

View File

@@ -40,8 +40,8 @@
{{#if @verticalBarChartData}} {{#if @verticalBarChartData}}
<div data-test-monthly-usage-legend class="legend-right"> <div data-test-monthly-usage-legend class="legend-right">
<span class="light-dot"></span><span class="legend-label">{{capitalize @chartLegend.0.label}}</span> <span class="light-dot"></span><span class="legend-label">{{capitalize (get @chartLegend "0.label")}}</span>
<span class="dark-dot"></span><span class="legend-label">{{capitalize @chartLegend.1.label}}</span> <span class="dark-dot"></span><span class="legend-label">{{capitalize (get @chartLegend "1.label")}}</span>
</div> </div>
{{/if}} {{/if}}
</div> </div>

View File

@@ -78,8 +78,8 @@
{{#if this.hasAverageNewClients}} {{#if this.hasAverageNewClients}}
<div class="legend-right" data-test-running-total-legend> <div class="legend-right" data-test-running-total-legend>
<span class="light-dot"></span><span class="legend-label">{{capitalize @chartLegend.0.label}}</span> <span class="light-dot"></span><span class="legend-label">{{capitalize (get @chartLegend "0.label")}}</span>
<span class="dark-dot"></span><span class="legend-label">{{capitalize @chartLegend.1.label}}</span> <span class="dark-dot"></span><span class="legend-label">{{capitalize (get @chartLegend "1.label")}}</span>
</div> </div>
{{/if}} {{/if}}
</div> </div>

View File

@@ -18,11 +18,7 @@
{{! Component must be in curly bracket notation }} {{! Component must be in curly bracket notation }}
{{! template-lint-disable no-curly-component-invocation }} {{! template-lint-disable no-curly-component-invocation }}
{{#modal-dialog {{#modal-dialog
tagName="div" tagName="div" tetherTarget=this.tooltipTarget targetAttachment="bottom middle" attachment="bottom middle" offset="10px 0"
tetherTarget=this.tooltipTarget
targetAttachment="bottom middle"
attachment="bottom middle"
offset="10px 0"
}} }}
<div class="chart-tooltip vertical-chart"> <div class="chart-tooltip vertical-chart">
<p>{{this.tooltipTotal}}</p> <p>{{this.tooltipTotal}}</p>

View File

@@ -153,7 +153,7 @@
{{/if}} {{/if}}
</div> </div>
<div class="control"> <div class="control">
<SecretLink @mode="list" class="button"> <SecretLink @mode="list" class="button" data-test-database-role-cancel>
Cancel Cancel
</SecretLink> </SecretLink>
</div> </div>

View File

@@ -1,6 +1,6 @@
<PopupMenu @name="alias-menu"> <PopupMenu @name="alias-menu">
<Confirm as |c|> <Confirm as |c|>
{{#let this.params.firstObject as |item|}} {{#let (get this.params "0") as |item|}}
<nav class="menu"> <nav class="menu">
<ul class="menu-list"> <ul class="menu-list">
<li class="action"> <li class="action">

View File

@@ -11,10 +11,7 @@
<nav class="tabs has-bottom-margin-l"> <nav class="tabs has-bottom-margin-l">
<ul> <ul>
{{! template-lint-configure no-nested-interactive "warn" }} {{! template-lint-configure no-nested-interactive "warn" }}
<li <li class={{if (eq @unwrapActiveTab "data") "is-active"}}>
aria-selected={{if (eq @unwrapActiveTab "data") "true" "false"}}
class={{if (eq @unwrapActiveTab "data") "is-active"}}
>
<button <button
type="button" type="button"
class="link link-plain tab has-text-weight-semibold" class="link link-plain tab has-text-weight-semibold"
@@ -24,10 +21,7 @@
Data Data
</button> </button>
</li> </li>
<li <li class={{if (eq @unwrapActiveTab "details") "is-active"}}>
aria-selected={{if (eq @unwrapActiveTab "data") "true" "false"}}
class={{if (eq @unwrapActiveTab "details") "is-active"}}
>
<button <button
type="button" type="button"
class="link link-plain tab has-text-weight-semibold" class="link link-plain tab has-text-weight-semibold"

View File

@@ -3,9 +3,7 @@
</div> </div>
{{#if this.featureComponent}} {{#if this.featureComponent}}
{{#component {{#component
(if this.shouldRender this.tutorialComponent) (if this.shouldRender this.tutorialComponent) onAdvance=(action "advanceWizard") onDismiss=(action "dismissWizard")
onAdvance=(action "advanceWizard")
onDismiss=(action "dismissWizard")
}} }}
{{component {{component
this.featureComponent this.featureComponent

View File

@@ -47,25 +47,29 @@
<div class="box is-fullwidth is-sideless is-paddingless is-marginless"> <div class="box is-fullwidth is-sideless is-paddingless is-marginless">
{{#each-in this.model.customMetadata as |key value|}} {{#each-in this.model.customMetadata as |key value|}}
<InfoTableRow @alwaysRender={{false}} @label={{key}} @value={{value}} /> <InfoTableRow @alwaysRender={{false}} @label={{key}} @value={{value}} />
{{else if this.noReadAccess}} {{else}}{{#if this.noReadAccess}}
<EmptyState <EmptyState
@title="You do not have access to read secret metadata" @title="You do not have access to read secret metadata"
@bottomBorder={{true}} @bottomBorder={{true}}
@message="In order to edit secret metadata access, the UI requires read permissions; otherwise, data may be deleted. Edits can still be made via the API and CLI." @message="In order to edit secret metadata access, the UI requires read permissions; otherwise, data may be deleted. Edits can still be made via the API and CLI."
/> />
{{else}} {{else}}
<EmptyState <EmptyState
@title="No custom metadata" @title="No custom metadata"
@bottomBorder={{true}} @bottomBorder={{true}}
@message="This data is version-agnostic and is usually used to describe the secret being stored." @message="This data is version-agnostic and is usually used to describe the secret being stored."
> >
{{#if this.model.canUpdateMetadata}} {{#if this.model.canUpdateMetadata}}
<LinkTo @route="vault.cluster.secrets.backend.edit-metadata" @model={{this.model.id}} data-test-add-custom-metadata> <LinkTo
Add metadata @route="vault.cluster.secrets.backend.edit-metadata"
</LinkTo> @model={{this.model.id}}
{{/if}} data-test-add-custom-metadata
</EmptyState> >
{{/each-in}} Add metadata
</LinkTo>
{{/if}}
</EmptyState>
{{/if}}{{/each-in}}
</div> </div>
{{#unless this.noReadAccess}} {{#unless this.noReadAccess}}
<div class="form-section"> <div class="form-section">

View File

@@ -78,7 +78,7 @@
</div> </div>
{{#if backend.accessor}} {{#if backend.accessor}}
<code class="has-text-grey is-size-8"> <code class="has-text-grey is-size-8">
{{backend.accessor}} {{if (eq backend.version 2) (concat "v2 " backend.accessor) backend.accessor}}
</code> </code>
{{/if}} {{/if}}
{{#if backend.description}} {{#if backend.description}}

View File

@@ -3,7 +3,7 @@
"packages": [ "packages": [
{ {
"name": "ember-cli", "name": "ember-cli",
"version": "4.4.0", "version": "4.12.1",
"blueprints": [ "blueprints": [
{ {
"name": "app", "name": "app",

View File

@@ -94,18 +94,5 @@ module.exports = function (defaults) {
app.import('node_modules/@hashicorp/structure-icons/dist/loading.css'); app.import('node_modules/@hashicorp/structure-icons/dist/loading.css');
app.import('node_modules/@hashicorp/structure-icons/dist/run.css'); app.import('node_modules/@hashicorp/structure-icons/dist/run.css');
// Use `app.import` to add additional libraries to the generated
// output files.
//
// If you need to use different assets in different
// environments, specify an object as the first parameter. That
// object's keys should be the environment name and the values
// should be the asset to use in that environment.
//
// If the library that you are including contains AMD or ES6
// modules that you would like to import into your application
// please specify an object with the list of modules as keys
// along with the exports of each module as its value.
return app.toTree(); return app.toTree();
}; };

View File

@@ -66,11 +66,12 @@ export default Component.extend({
).drop(), ).drop(),
willDestroy() { willDestroy() {
this._super(...arguments); // components are torn down after store is unloaded and will cause an error if attempt to unload record
const noTeardown = this.store && !this.store.isDestroying;
const { model } = this; const { model } = this;
if (!model) return; if (noTeardown && model && model.get('isDirty') && !model.isDestroyed && !model.isDestroying) {
if ((model.get('isDirty') && !model.isDestroyed) || !model.isDestroying) {
model.rollbackAttributes(); model.rollbackAttributes();
} }
this._super(...arguments);
}, },
}); });

View File

@@ -38,7 +38,7 @@
class="button copy-button is-compact" class="button copy-button is-compact"
data-test-copy-button data-test-copy-button
> >
<Icon @name="clipboard-copy" aria-hidden="Copy value" /> <Icon @name="clipboard-copy" aria-label="Copy value" />
</CopyButton> </CopyButton>
</div> </div>
{{/if}} {{/if}}

View File

@@ -37,7 +37,7 @@
class="copy-button button {{if @displayOnly 'is-compact'}}" class="copy-button button {{if @displayOnly 'is-compact'}}"
data-test-copy-button data-test-copy-button
> >
<Icon @name="clipboard-copy" aria-hidden="Copy value" /> <Icon @name="clipboard-copy" aria-label="Copy value" />
</CopyButton> </CopyButton>
{{/if}} {{/if}}
{{#if @allowDownload}} {{#if @allowDownload}}

View File

@@ -1,5 +1,5 @@
<EmberWormhole @to="modal-wormhole"> <EmberWormhole @to="modal-wormhole">
<div class="{{this.modalClass}} {{if this.isActive 'is-active'}}" aria-modal="true" data-test-modal-div> <div class="{{this.modalClass}} {{if this.isActive 'is-active'}}" data-test-modal-div>
<div class="modal-background" role="button" {{on "click" @onClose}} data-test-modal-background={{@title}}></div> <div class="modal-background" role="button" {{on "click" @onClose}} data-test-modal-background={{@title}}></div>
<div class="modal-card"> <div class="modal-card">
<header class="modal-card-head"> <header class="modal-card-head">

View File

@@ -1,7 +1,6 @@
<div class="navigate-filter"> <div class="navigate-filter">
<div class="field" data-test-nav-input> <div class="field" data-test-nav-input>
<p class="control has-icons-left"> <p class="control has-icons-left">
{{! template-lint-disable no-down-event-binding }}
<Input <Input
id={{this.inputId}} id={{this.inputId}}
class="filter input" class="filter input"

View File

@@ -45,7 +45,6 @@ export default class TtlPickerComponent extends Component {
@tracked recalculateSeconds = false; @tracked recalculateSeconds = false;
@tracked time = ''; // if defaultValue is NOT set, then do not display a defaultValue. @tracked time = ''; // if defaultValue is NOT set, then do not display a defaultValue.
@tracked unit = 's'; @tracked unit = 's';
@tracked recalculateSeconds = false;
@tracked errorMessage = ''; @tracked errorMessage = '';
/* Used internally */ /* Used internally */

View File

@@ -18,58 +18,54 @@ import 'codemirror/mode/ruby/ruby';
import 'codemirror/mode/javascript/javascript'; import 'codemirror/mode/javascript/javascript';
export default class CodeMirrorModifier extends Modifier { export default class CodeMirrorModifier extends Modifier {
didInstall() { modify(element, positionalArgs, namedArgs) {
this._setup(); // setup codemirror initially when modifier is installed on the element
} if (!this._editor) {
this._setup(element, namedArgs);
didUpdateArguments() { } else {
this._editor.setOption('readOnly', this.args.named.readOnly); // this hook also fires any time there is a change to tracked state
if (!this.args.named.content) { this._editor.setOption('readOnly', namedArgs.readOnly);
return; if (namedArgs.content && this._editor.getValue() !== namedArgs.content) {
} this._editor.setValue(namedArgs.content);
if (this._editor.getValue() !== this.args.named.content) { }
this._editor.setValue(this.args.named.content);
} }
} }
@action @action
_onChange(editor) { _onChange(namedArgs, editor) {
// avoid sending change event after initial setup when editor value is set to content // avoid sending change event after initial setup when editor value is set to content
if (this.args.named.content !== editor.getValue()) { if (namedArgs.content !== editor.getValue()) {
this.args.named.onUpdate(editor.getValue(), this._editor); namedArgs.onUpdate(editor.getValue(), this._editor);
} }
} }
@action @action
_onFocus(editor) { _onFocus(namedArgs, editor) {
this.args.named.onFocus(editor.getValue()); namedArgs.onFocus(editor.getValue());
} }
_setup() { _setup(element, namedArgs) {
if (!this.element) { const editor = codemirror(element, {
throw new Error('CodeMirror modifier has no element');
}
const editor = codemirror(this.element, {
// IMPORTANT: `gutters` must come before `lint` since the presence of // IMPORTANT: `gutters` must come before `lint` since the presence of
// `gutters` is cached internally when `lint` is toggled // `gutters` is cached internally when `lint` is toggled
gutters: this.args.named.gutters || ['CodeMirror-lint-markers'], gutters: namedArgs.gutters || ['CodeMirror-lint-markers'],
matchBrackets: true, matchBrackets: true,
lint: { lintOnChange: true }, lint: { lintOnChange: true },
showCursorWhenSelecting: true, showCursorWhenSelecting: true,
styleActiveLine: true, styleActiveLine: true,
tabSize: 2, tabSize: 2,
// all values we can pass into the JsonEditor // all values we can pass into the JsonEditor
extraKeys: this.args.named.extraKeys || '', extraKeys: namedArgs.extraKeys || '',
lineNumbers: this.args.named.lineNumbers, lineNumbers: namedArgs.lineNumbers,
mode: this.args.named.mode || 'application/json', mode: namedArgs.mode || 'application/json',
readOnly: this.args.named.readOnly || false, readOnly: namedArgs.readOnly || false,
theme: this.args.named.theme || 'hashi', theme: namedArgs.theme || 'hashi',
value: this.args.named.content || '', value: namedArgs.content || '',
viewportMargin: this.args.named.viewportMargin || '', viewportMargin: namedArgs.viewportMargin || '',
}); });
editor.on('change', bind(this, this._onChange)); editor.on('change', bind(this, this._onChange, namedArgs));
editor.on('focus', bind(this, this._onFocus)); editor.on('focus', bind(this, this._onFocus, namedArgs));
this._editor = editor; this._editor = editor;
} }

View File

@@ -108,7 +108,7 @@ export const namespaceArrayToObject = (totalClientsByNamespace, newClientsByName
const newNamespaceCounts = newClientsByNamespace?.find((n) => n.label === ns.label); const newNamespaceCounts = newClientsByNamespace?.find((n) => n.label === ns.label);
if (newNamespaceCounts) { if (newNamespaceCounts) {
const { label, clients, entity_clients, non_entity_clients } = newNamespaceCounts; const { label, clients, entity_clients, non_entity_clients } = newNamespaceCounts;
const newClientsByMount = [...newNamespaceCounts?.mounts]; const newClientsByMount = [...newNamespaceCounts.mounts];
const nestNewClientsWithinMounts = ns.mounts?.map((mount) => { const nestNewClientsWithinMounts = ns.mounts?.map((mount) => {
const new_clients = newClientsByMount?.find((m) => m.label === mount.label) || {}; const new_clients = newClientsByMount?.find((m) => m.label === mount.label) || {};
return { return {

View File

@@ -4,7 +4,7 @@
*/ */
/* eslint-env node */ /* eslint-env node */
/* eslint-disable node/no-extraneous-require */ /* eslint-disable n/no-extraneous-require */
'use strict'; 'use strict';
var path = require('path'); var path = require('path');

View File

@@ -5,7 +5,7 @@
/* eslint-env node */ /* eslint-env node */
/* eslint-disable ember/avoid-leaking-state-in-ember-objects */ /* eslint-disable ember/avoid-leaking-state-in-ember-objects */
/* eslint-disable node/no-extraneous-require */ /* eslint-disable n/no-extraneous-require */
'use strict'; 'use strict';
const EngineAddon = require('ember-engines/lib/engine-addon'); const EngineAddon = require('ember-engines/lib/engine-addon');

View File

@@ -4,7 +4,7 @@
*/ */
/* eslint-env node */ /* eslint-env node */
/* eslint-disable node/no-extraneous-require */ /* eslint-disable n/no-extraneous-require */
'use strict'; 'use strict';
const { buildEngine } = require('ember-engines/lib/engine-addon'); const { buildEngine } = require('ember-engines/lib/engine-addon');

View File

@@ -5,7 +5,7 @@
/* eslint-env node */ /* eslint-env node */
/* eslint-disable ember/avoid-leaking-state-in-ember-objects */ /* eslint-disable ember/avoid-leaking-state-in-ember-objects */
/* eslint-disable node/no-extraneous-require */ /* eslint-disable n/no-extraneous-require */
'use strict'; 'use strict';
const EngineAddon = require('ember-engines/lib/engine-addon'); const EngineAddon = require('ember-engines/lib/engine-addon');

View File

@@ -20,7 +20,7 @@
</ToolbarActions> </ToolbarActions>
</Toolbar> </Toolbar>
{{#if (not (eq @cluster 403))}} {{#if (not-eq @cluster 403)}}
<h2 class="title is-4 has-bottom-margin-xs has-top-margin-xl has-border-bottom-light has-bottom-padding-s"> <h2 class="title is-4 has-bottom-margin-xs has-top-margin-xl has-border-bottom-light has-bottom-padding-s">
Cluster Config Cluster Config
</h2> </h2>
@@ -32,7 +32,7 @@
{{/each}} {{/each}}
{{/if}} {{/if}}
{{#if (not (eq @acme 403))}} {{#if (not-eq @acme 403)}}
<h2 class="title is-4 has-bottom-margin-xs has-top-margin-xl has-border-bottom-light has-bottom-padding-s"> <h2 class="title is-4 has-bottom-margin-xs has-top-margin-xl has-border-bottom-light has-bottom-padding-s">
ACME Config ACME Config
</h2> </h2>
@@ -44,7 +44,7 @@
{{/each}} {{/each}}
{{/if}} {{/if}}
{{#if (not (eq @urls 403))}} {{#if (not-eq @urls 403)}}
<h2 class="title is-4 has-bottom-margin-xs has-top-margin-xl has-border-bottom-light has-bottom-padding-s"> <h2 class="title is-4 has-bottom-margin-xs has-top-margin-xl has-border-bottom-light has-bottom-padding-s">
Global URLs Global URLs
</h2> </h2>
@@ -55,7 +55,7 @@
/> />
{{/if}} {{/if}}
{{#if (not (eq @crl 403))}} {{#if (not-eq @crl 403)}}
<h2 class="title is-4 has-bottom-margin-xs has-top-margin-xl has-border-bottom-light has-bottom-padding-s"> <h2 class="title is-4 has-bottom-margin-xs has-top-margin-xl has-border-bottom-light has-bottom-padding-s">
Certificate Revocation List (CRL) Certificate Revocation List (CRL)
</h2> </h2>

View File

@@ -14,7 +14,7 @@
{{#if pkiIssuer.isDefault}} {{#if pkiIssuer.isDefault}}
<span class="tag has-text-grey-dark" data-test-is-default={{idx}}>default issuer</span> <span class="tag has-text-grey-dark" data-test-is-default={{idx}}>default issuer</span>
{{/if}} {{/if}}
{{#if (not (eq pkiIssuer.isRoot undefined))}} {{#if (not-eq pkiIssuer.isRoot undefined)}}
<span class="tag has-text-grey-dark" data-test-is-root-tag={{idx}}>{{if <span class="tag has-text-grey-dark" data-test-is-root-tag={{idx}}>{{if
pkiIssuer.isRoot pkiIssuer.isRoot
"root" "root"

View File

@@ -25,7 +25,6 @@ export default class PkiKeyDetails extends Component<Args> {
this.flashMessages.success('Key deleted successfully.'); this.flashMessages.success('Key deleted successfully.');
this.router.transitionTo('vault.cluster.secrets.backend.pki.keys.index'); this.router.transitionTo('vault.cluster.secrets.backend.pki.keys.index');
} catch (error) { } catch (error) {
this.args.key.rollbackAttributes();
this.flashMessages.danger(errorMessage(error)); this.flashMessages.danger(errorMessage(error));
} }
} }

View File

@@ -7,7 +7,7 @@
> >
<h2 class="title-number">{{format-number (if (eq @issuers 404) 0 @issuers.length)}}</h2> <h2 class="title-number">{{format-number (if (eq @issuers 404) 0 @issuers.length)}}</h2>
</OverviewCard> </OverviewCard>
{{#if (not (eq @roles 403))}} {{#if (not-eq @roles 403)}}
<OverviewCard <OverviewCard
@cardTitle="Roles" @cardTitle="Roles"
@subText="The total number of roles in this PKI mount that have been created to generate certificates." @subText="The total number of roles in this PKI mount that have been created to generate certificates."

View File

@@ -75,7 +75,7 @@
</h2> </h2>
{{#if @urls.canSet}} {{#if @urls.canSet}}
{{#each @urls.allFields as |attr|}} {{#each @urls.allFields as |attr|}}
{{#if (not (eq attr.name "mountPath"))}} {{#if (not-eq attr.name "mountPath")}}
<FormField <FormField
@attr={{attr}} @attr={{attr}}
@mode="create" @mode="create"

View File

@@ -3,7 +3,7 @@
* SPDX-License-Identifier: MPL-2.0 * SPDX-License-Identifier: MPL-2.0
*/ */
/* eslint-disable node/no-extraneous-require */ /* eslint-disable n/no-extraneous-require */
const { buildEngine } = require('ember-engines/lib/engine-addon'); const { buildEngine } = require('ember-engines/lib/engine-addon');
module.exports = buildEngine({ module.exports = buildEngine({

View File

@@ -123,10 +123,6 @@ export default Component.extend({
return ['cubbyhole', 'system', 'token', 'identity', 'ns_system', 'ns_identity', 'ns_token']; return ['cubbyhole', 'system', 'token', 'identity', 'ns_system', 'ns_identity', 'ns_token'];
}), }),
willDestroyElement() {
this._super(...arguments);
},
actions: { actions: {
async pathsChanged(paths) { async pathsChanged(paths) {
// set paths on the model // set paths on the model

View File

@@ -5,7 +5,7 @@
/* eslint-env node */ /* eslint-env node */
/* eslint-disable ember/avoid-leaking-state-in-ember-objects */ /* eslint-disable ember/avoid-leaking-state-in-ember-objects */
/* eslint-disable node/no-extraneous-require */ /* eslint-disable n/no-extraneous-require */
'use strict'; 'use strict';
const EngineAddon = require('ember-engines/lib/engine-addon'); const EngineAddon = require('ember-engines/lib/engine-addon');

View File

@@ -11,7 +11,9 @@
"scripts": { "scripts": {
"build": "ember build --environment=production && cp metadata.json ../http/web_ui/metadata.json", "build": "ember build --environment=production && cp metadata.json ../http/web_ui/metadata.json",
"build:dev": "ember build", "build:dev": "ember build",
"lint:fix": "npm-run-all --aggregate-output --continue-on-error --parallel lint:*:fix", "lint:css": "stylelint \"**/*.css\"",
"lint:css:fix": "yarn lint:css --fix",
"lint:fix": "npm-run-all --print-name --aggregate-output --continue-on-error --parallel \"lint:*:fix\"",
"lint:hbs": "ember-template-lint '**/*.hbs'", "lint:hbs": "ember-template-lint '**/*.hbs'",
"lint:hbs:quiet": "ember-template-lint '**/*.hbs' --quiet", "lint:hbs:quiet": "ember-template-lint '**/*.hbs' --quiet",
"lint:hbs:fix": "ember-template-lint . --fix", "lint:hbs:fix": "ember-template-lint . --fix",
@@ -25,7 +27,7 @@
"start": "VAULT_ADDR=http://localhost:8200; ember server --proxy=$VAULT_ADDR", "start": "VAULT_ADDR=http://localhost:8200; ember server --proxy=$VAULT_ADDR",
"start2": "ember server --proxy=http://localhost:8202 --port=4202", "start2": "ember server --proxy=http://localhost:8202 --port=4202",
"start:mirage": "start () { MIRAGE_DEV_HANDLER=$1 yarn run start; }; start", "start:mirage": "start () { MIRAGE_DEV_HANDLER=$1 yarn run start; }; start",
"test": "npm-run-all lint:js:quiet lint:hbs:quiet && node scripts/start-vault.js", "test": "npm-run-all --print-name lint:js:quiet lint:hbs:quiet && node scripts/start-vault.js",
"test:enos": "npm-run-all lint:js:quiet lint:hbs:quiet && node scripts/enos-test-ember.js", "test:enos": "npm-run-all lint:js:quiet lint:hbs:quiet && node scripts/enos-test-ember.js",
"test:oss": "yarn run test -f='!enterprise'", "test:oss": "yarn run test -f='!enterprise'",
"test:quick": "node scripts/start-vault.js", "test:quick": "node scripts/start-vault.js",
@@ -51,12 +53,15 @@
] ]
}, },
"devDependencies": { "devDependencies": {
"@babel/eslint-parser": "^7.21.3",
"@babel/plugin-proposal-decorators": "^7.21.0",
"@babel/plugin-proposal-object-rest-spread": "^7.12.1", "@babel/plugin-proposal-object-rest-spread": "^7.12.1",
"@babel/plugin-transform-block-scoping": "^7.12.1", "@babel/plugin-transform-block-scoping": "^7.12.1",
"@ember/legacy-built-in-components": "^0.4.1", "@ember/legacy-built-in-components": "^0.4.1",
"@ember/optional-features": "^2.0.0", "@ember/optional-features": "^2.0.0",
"@ember/render-modifiers": "^1.0.2", "@ember/render-modifiers": "^1.0.2",
"@ember/test-helpers": "2.8.1", "@ember/string": "^3.0.1",
"@ember/test-helpers": "2.9.3",
"@ember/test-waiters": "^3.0.0", "@ember/test-waiters": "^3.0.0",
"@glimmer/component": "^1.1.2", "@glimmer/component": "^1.1.2",
"@glimmer/tracking": "^1.1.2", "@glimmer/tracking": "^1.1.2",
@@ -97,7 +102,6 @@
"@typescript-eslint/parser": "^5.19.0", "@typescript-eslint/parser": "^5.19.0",
"asn1js": "^2.2.0", "asn1js": "^2.2.0",
"autosize": "^4.0.0", "autosize": "^4.0.0",
"babel-eslint": "^10.1.0",
"babel-plugin-inline-json-import": "^0.3.2", "babel-plugin-inline-json-import": "^0.3.2",
"base64-js": "^1.3.1", "base64-js": "^1.3.1",
"broccoli-asset-rev": "^3.0.0", "broccoli-asset-rev": "^3.0.0",
@@ -116,9 +120,9 @@
"deepmerge": "^4.0.0", "deepmerge": "^4.0.0",
"doctoc": "^2.2.0", "doctoc": "^2.2.0",
"dompurify": "^3.0.2", "dompurify": "^3.0.2",
"ember-auto-import": "2.4.2", "ember-auto-import": "2.6.3",
"ember-basic-dropdown": "6.0.1", "ember-basic-dropdown": "6.0.1",
"ember-cli": "~4.4.0", "ember-cli": "~4.12.1",
"ember-cli-autoprefixer": "^0.8.1", "ember-cli-autoprefixer": "^0.8.1",
"ember-cli-babel": "^7.26.11", "ember-cli-babel": "^7.26.11",
"ember-cli-clipboard": "0.16.0", "ember-cli-clipboard": "0.16.0",
@@ -126,7 +130,7 @@
"ember-cli-dependency-checker": "^3.3.1", "ember-cli-dependency-checker": "^3.3.1",
"ember-cli-deprecation-workflow": "^2.1.0", "ember-cli-deprecation-workflow": "^2.1.0",
"ember-cli-flash": "4.0.0", "ember-cli-flash": "4.0.0",
"ember-cli-htmlbars": "^6.0.1", "ember-cli-htmlbars": "^6.2.0",
"ember-cli-inject-live-reload": "^2.1.0", "ember-cli-inject-live-reload": "^2.1.0",
"ember-cli-mirage": "2.4.0", "ember-cli-mirage": "2.4.0",
"ember-cli-page-object": "1.17.10", "ember-cli-page-object": "1.17.10",
@@ -139,40 +143,39 @@
"ember-concurrency": "2.3.4", "ember-concurrency": "2.3.4",
"ember-copy": "2.0.1", "ember-copy": "2.0.1",
"ember-d3": "^0.5.1", "ember-d3": "^0.5.1",
"ember-data": "~4.5.0", "ember-data": "~4.11.3",
"ember-engines": "0.8.23", "ember-engines": "0.8.23",
"ember-export-application-global": "^2.0.1", "ember-fetch": "^8.1.2",
"ember-fetch": "^8.1.1",
"ember-inflector": "4.0.2", "ember-inflector": "4.0.2",
"ember-load-initializers": "^2.1.2", "ember-load-initializers": "^2.1.2",
"ember-maybe-in-element": "^2.0.3", "ember-maybe-in-element": "^2.0.3",
"ember-modal-dialog": "^4.0.1", "ember-modal-dialog": "^4.0.1",
"ember-modifier": "^3.1.0", "ember-modifier": "^4.1.0",
"ember-page-title": "^7.0.0", "ember-page-title": "^7.0.0",
"ember-power-select": "6.0.1", "ember-power-select": "6.0.1",
"ember-qrcode-shim": "^0.4.0", "ember-qrcode-shim": "^0.4.0",
"ember-qunit": "6.0.0", "ember-qunit": "6.0.0",
"ember-resolver": "^8.0.3", "ember-resolver": "^10.0.0",
"ember-responsive": "5.0.0", "ember-responsive": "5.0.0",
"ember-router-helpers": "^0.4.0", "ember-router-helpers": "^0.4.0",
"ember-service-worker": "meirish/ember-service-worker#configurable-scope", "ember-service-worker": "meirish/ember-service-worker#configurable-scope",
"ember-sinon": "^4.0.0", "ember-sinon": "^4.0.0",
"ember-source": "4.4.4", "ember-source": "~4.12.0",
"ember-svg-jar": "2.4.0", "ember-svg-jar": "2.4.0",
"ember-template-lint": "4.8.0", "ember-template-lint": "5.7.2",
"ember-template-lint-plugin-prettier": "4.0.0", "ember-template-lint-plugin-prettier": "4.0.0",
"ember-test-selectors": "6.0.0", "ember-test-selectors": "6.0.0",
"ember-tether": "^2.0.1", "ember-tether": "^2.0.1",
"ember-truth-helpers": "3.0.0", "ember-truth-helpers": "3.0.0",
"ember-wormhole": "0.6.0", "ember-wormhole": "0.6.0",
"escape-string-regexp": "^2.0.0", "escape-string-regexp": "^2.0.0",
"eslint": "^7.32.0", "eslint": "^8.37.0",
"eslint-config-prettier": "^8.5.0", "eslint-config-prettier": "^8.8.0",
"eslint-plugin-compat": "4.0.2", "eslint-plugin-compat": "4.0.2",
"eslint-plugin-ember": "^10.6.1", "eslint-plugin-ember": "^11.5.0",
"eslint-plugin-node": "^11.1.0", "eslint-plugin-n": "^15.7.0",
"eslint-plugin-prettier": "^4.0.0", "eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-qunit": "^7.2.0", "eslint-plugin-qunit": "^7.3.4",
"filesize": "^4.2.1", "filesize": "^4.2.1",
"flat": "^4.1.0", "flat": "^4.1.0",
"jsondiffpatch": "^0.4.1", "jsondiffpatch": "^0.4.1",
@@ -183,21 +186,25 @@
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"pkijs": "^2.2.2", "pkijs": "^2.2.2",
"pretender": "^3.4.3", "pretender": "^3.4.3",
"prettier": "2.6.2", "prettier": "2.8.7",
"prettier-eslint-cli": "^7.1.0", "prettier-eslint-cli": "^7.1.0",
"pvutils": "^1.0.17", "pvutils": "^1.0.17",
"qunit": "^2.19.1", "qunit": "^2.19.4",
"qunit-dom": "^2.0.0", "qunit-dom": "^2.0.0",
"sass": "^1.58.3", "sass": "^1.58.3",
"sass-svg-uri": "^1.0.0", "sass-svg-uri": "^1.0.0",
"shell-quote": "^1.6.1", "shell-quote": "^1.6.1",
"string.prototype.endswith": "^0.2.0", "string.prototype.endswith": "^0.2.0",
"string.prototype.startswith": "^0.2.0", "string.prototype.startswith": "^0.2.0",
"stylelint": "^15.4.0",
"stylelint-config-standard": "^32.0.0",
"stylelint-prettier": "^3.0.0",
"swagger-ui-dist": "^3.36.2", "swagger-ui-dist": "^3.36.2",
"text-encoder-lite": "2.0.0", "text-encoder-lite": "2.0.0",
"tracked-built-ins": "^3.1.1",
"typescript": "^4.8.4", "typescript": "^4.8.4",
"walk-sync": "^2.0.2", "walk-sync": "^2.0.2",
"webpack": "5.73.0", "webpack": "5.78.0",
"xstate": "^3.3.3" "xstate": "^3.3.3"
}, },
"resolutions": { "resolutions": {
@@ -216,7 +223,8 @@
"serialize-javascript": "^3.1.0", "serialize-javascript": "^3.1.0",
"underscore": "^1.12.1", "underscore": "^1.12.1",
"trim": "^0.0.3", "trim": "^0.0.3",
"xmlhttprequest-ssl": "^1.6.2" "xmlhttprequest-ssl": "^1.6.2",
"@embroider/macros": "^1.0.0"
}, },
"engines": { "engines": {
"node": "16" "node": "16"

View File

@@ -6,7 +6,7 @@
/* eslint-env node */ /* eslint-env node */
/* eslint-disable no-console */ /* eslint-disable no-console */
/* eslint-disable no-process-exit */ /* eslint-disable no-process-exit */
/* eslint-disable node/no-extraneous-require */ /* eslint-disable n/no-extraneous-require */
var readline = require('readline'); var readline = require('readline');
const testHelper = require('./test-helper'); const testHelper = require('./test-helper');

View File

@@ -9,7 +9,6 @@ import { setupApplicationTest } from 'ember-qunit';
import { setupMirage } from 'ember-cli-mirage/test-support'; import { setupMirage } from 'ember-cli-mirage/test-support';
import page from 'vault/tests/pages/access/namespaces/index'; import page from 'vault/tests/pages/access/namespaces/index';
import authPage from 'vault/tests/pages/auth'; import authPage from 'vault/tests/pages/auth';
import logout from 'vault/tests/pages/logout';
module('Acceptance | Enterprise | /access/namespaces', function (hooks) { module('Acceptance | Enterprise | /access/namespaces', function (hooks) {
setupApplicationTest(hooks); setupApplicationTest(hooks);
@@ -19,10 +18,6 @@ module('Acceptance | Enterprise | /access/namespaces', function (hooks) {
return authPage.login(); return authPage.login();
}); });
hooks.afterEach(function () {
return logout.visit();
});
test('it navigates to namespaces page', async function (assert) { test('it navigates to namespaces page', async function (assert) {
assert.expect(1); assert.expect(1);
await page.visit(); await page.visit();

View File

@@ -10,7 +10,6 @@ import { setupApplicationTest } from 'ember-qunit';
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import authPage from 'vault/tests/pages/auth'; import authPage from 'vault/tests/pages/auth';
import logout from 'vault/tests/pages/logout';
import enablePage from 'vault/tests/pages/settings/auth/enable'; import enablePage from 'vault/tests/pages/settings/auth/enable';
import { supportedAuthBackends } from 'vault/helpers/supported-auth-backends'; import { supportedAuthBackends } from 'vault/helpers/supported-auth-backends';
import { supportedManagedAuthBackends } from 'vault/helpers/supported-managed-auth-backends'; import { supportedManagedAuthBackends } from 'vault/helpers/supported-managed-auth-backends';
@@ -26,10 +25,6 @@ module('Acceptance | auth backend list', function (hooks) {
return authPage.login(); return authPage.login();
}); });
hooks.afterEach(function () {
return logout.visit();
});
test('userpass secret backend', async function (assert) { test('userpass secret backend', async function (assert) {
let n = Math.random(); let n = Math.random();
const path1 = `userpass-${++n}`; const path1 = `userpass-${++n}`;

View File

@@ -13,7 +13,6 @@ import authForm from '../pages/components/auth-form';
import jwtForm from '../pages/components/auth-jwt'; import jwtForm from '../pages/components/auth-jwt';
import { create } from 'ember-cli-page-object'; import { create } from 'ember-cli-page-object';
import apiStub from 'vault/tests/helpers/noop-all-api-requests'; import apiStub from 'vault/tests/helpers/noop-all-api-requests';
import logout from 'vault/tests/pages/logout';
const component = create(authForm); const component = create(authForm);
const jwtComponent = create(jwtForm); const jwtComponent = create(jwtForm);
@@ -27,13 +26,11 @@ module('Acceptance | auth', function (hooks) {
shouldAdvanceTime: true, shouldAdvanceTime: true,
}); });
this.server = apiStub({ usePassthrough: true }); this.server = apiStub({ usePassthrough: true });
return logout.visit();
}); });
hooks.afterEach(function () { hooks.afterEach(function () {
this.clock.restore(); this.clock.restore();
this.server.shutdown(); this.server.shutdown();
return logout.visit();
}); });
test('auth query params', async function (assert) { test('auth query params', async function (assert) {

View File

@@ -9,7 +9,6 @@ import { setupApplicationTest } from 'ember-qunit';
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import authPage from 'vault/tests/pages/auth'; import authPage from 'vault/tests/pages/auth';
import logout from 'vault/tests/pages/logout';
import enablePage from 'vault/tests/pages/settings/mount-secret-backend'; import enablePage from 'vault/tests/pages/settings/mount-secret-backend';
module('Acceptance | aws secret backend', function (hooks) { module('Acceptance | aws secret backend', function (hooks) {
@@ -20,20 +19,6 @@ module('Acceptance | aws secret backend', function (hooks) {
return authPage.login(); return authPage.login();
}); });
hooks.afterEach(function () {
return logout.visit();
});
const POLICY = {
Version: '2012-10-17',
Statement: [
{
Effect: 'Allow',
Action: 'iam:*',
Resource: '*',
},
],
};
test('aws backend', async function (assert) { test('aws backend', async function (assert) {
assert.expect(12); assert.expect(12);
const path = `aws-${this.uid}`; const path = `aws-${this.uid}`;
@@ -82,8 +67,6 @@ module('Acceptance | aws secret backend', function (hooks) {
await fillIn('[data-test-input="name"]', roleName); await fillIn('[data-test-input="name"]', roleName);
findAll('.CodeMirror')[0].CodeMirror.setValue(JSON.stringify(POLICY));
// save the role // save the role
await click('[data-test-role-aws-create]'); await click('[data-test-role-aws-create]');
await waitUntil(() => currentURL() === `/vault/secrets/${path}/show/${roleName}`); // flaky without this await waitUntil(() => currentURL() === `/vault/secrets/${path}/show/${roleName}`); // flaky without this

View File

@@ -29,7 +29,6 @@ module('Acceptance | cluster', function (hooks) {
setupApplicationTest(hooks); setupApplicationTest(hooks);
hooks.beforeEach(async function () { hooks.beforeEach(async function () {
await logout.visit();
return authPage.login(); return authPage.login();
}); });
@@ -46,7 +45,6 @@ module('Acceptance | cluster', function (hooks) {
await visit('/vault/access'); await visit('/vault/access');
assert.dom('[data-test-sidebar-nav-link="Policies"]').doesNotExist(); assert.dom('[data-test-sidebar-nav-link="Policies"]').doesNotExist();
await logout.visit();
}); });
test('it hides mfa setup if user has not entityId (ex: is a root user)', async function (assert) { test('it hides mfa setup if user has not entityId (ex: is a root user)', async function (assert) {
@@ -83,6 +81,5 @@ module('Acceptance | cluster', function (hooks) {
await visit('/vault/access'); await visit('/vault/access');
assert.dom('[data-test-sidebar-nav-link="Policies"]').hasAttribute('href', '/ui/vault/policies/rgp'); assert.dom('[data-test-sidebar-nav-link="Policies"]').hasAttribute('href', '/ui/vault/policies/rgp');
await logout.visit();
}); });
}); });

View File

@@ -7,7 +7,6 @@ import { module, test } from 'qunit';
import { setupApplicationTest } from 'ember-qunit'; import { setupApplicationTest } from 'ember-qunit';
import { click, fillIn } from '@ember/test-helpers'; import { click, fillIn } from '@ember/test-helpers';
import authPage from 'vault/tests/pages/auth'; import authPage from 'vault/tests/pages/auth';
import logout from 'vault/tests/pages/logout';
import mountSecrets from 'vault/tests/pages/settings/mount-secret-backend'; import mountSecrets from 'vault/tests/pages/settings/mount-secret-backend';
import { setupMirage } from 'ember-cli-mirage/test-support'; import { setupMirage } from 'ember-cli-mirage/test-support';
@@ -16,7 +15,6 @@ module('Acceptance | Enterprise | keymgmt', function (hooks) {
setupMirage(hooks); setupMirage(hooks);
hooks.beforeEach(async function () { hooks.beforeEach(async function () {
await logout.visit();
return authPage.login(); return authPage.login();
}); });

View File

@@ -32,7 +32,6 @@ module('Acceptance | Enterprise | namespaces', function (hooks) {
await click('[data-test-namespace-toggle]'); await click('[data-test-namespace-toggle]');
assert.dom('[data-test-current-namespace]').hasText('root', 'root renders as current namespace'); assert.dom('[data-test-current-namespace]').hasText('root', 'root renders as current namespace');
assert.dom('[data-test-namespace-link]').doesNotExist('Additional namespace have been cleared'); assert.dom('[data-test-namespace-link]').doesNotExist('Additional namespace have been cleared');
await logout.visit();
}); });
test('it shows nested namespaces if you log in with a namspace starting with a /', async function (assert) { test('it shows nested namespaces if you log in with a namspace starting with a /', async function (assert) {

View File

@@ -1,4 +1,9 @@
import { visit, currentURL } from '@ember/test-helpers'; /**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
import { currentURL, click } from '@ember/test-helpers';
import { module, test } from 'qunit'; import { module, test } from 'qunit';
import { setupApplicationTest } from 'ember-qunit'; import { setupApplicationTest } from 'ember-qunit';
import { create } from 'ember-cli-page-object'; import { create } from 'ember-cli-page-object';
@@ -40,17 +45,8 @@ module('Acceptance | Enterprise | oidc auth namespace test', function (hooks) {
}); });
test('oidc: request is made to auth_url when a namespace is inputted', async function (assert) { test('oidc: request is made to auth_url when a namespace is inputted', async function (assert) {
assert.expect(5); assert.expect(4);
this.server.post(`/auth/${this.rootOidc}/oidc/auth_url`, (schema, req) => {
const { redirect_uri } = JSON.parse(req.requestBody);
const { pathname, search } = parseURL(redirect_uri);
assert.strictEqual(
pathname + search,
`/ui/vault/auth/${this.rootOidc}/oidc/callback`,
'request made to auth_url when the login page is visited'
);
});
this.server.post(`/auth/${this.nsOidc}/oidc/auth_url`, (schema, req) => { this.server.post(`/auth/${this.nsOidc}/oidc/auth_url`, (schema, req) => {
const { redirect_uri } = JSON.parse(req.requestBody); const { redirect_uri } = JSON.parse(req.requestBody);
const { pathname, search } = parseURL(redirect_uri); const { pathname, search } = parseURL(redirect_uri);
@@ -69,23 +65,24 @@ module('Acceptance | Enterprise | oidc auth namespace test', function (hooks) {
// enable oidc in child namespace with default role // enable oidc in child namespace with default role
await authPage.loginNs(this.namespace); await authPage.loginNs(this.namespace);
await this.enableOidc(this.nsOidc, `${this.nsOidc}-role`); await this.enableOidc(this.nsOidc, `${this.nsOidc}-role`);
// check root namespace for method tab
await authPage.logout(); await authPage.logout();
await authPage.namespaceInput('');
await visit('/vault/auth');
assert.dom(SELECTORS.authTab(this.rootOidc)).exists('renders oidc method tab for root'); assert.dom(SELECTORS.authTab(this.rootOidc)).exists('renders oidc method tab for root');
// check child namespace for method tab
await authPage.namespaceInput(this.namespace); await authPage.namespaceInput(this.namespace);
assert.dom(SELECTORS.authTab(this.nsOidc)).exists('renders oidc method tab for child namespace');
// clicking on the tab should update with= queryParam
await click(`[data-test-auth-method="${this.nsOidc}"] a`);
assert.strictEqual( assert.strictEqual(
currentURL(), currentURL(),
`/vault/auth?namespace=${this.namespace}&with=${this.nsOidc}%2F`, `/vault/auth?namespace=${this.namespace}&with=${this.nsOidc}%2F`,
'url updates with namespace value' 'url updates with namespace value'
); );
assert.dom(SELECTORS.authTab(this.nsOidc)).exists('renders oidc method tab for child namespace');
// disable methods to cleanup test state for re-running // disable methods to cleanup test state for re-running
await authPage.login(); await authPage.login();
await this.disableOidc(this.rootOidc); await this.disableOidc(this.rootOidc);
await this.disableOidc(this.nsOidc); await this.disableOidc(this.nsOidc);
await shell.runCommands([`delete /sys/auth/${this.namespace}`]); await shell.runCommands([`delete /sys/auth/${this.namespace}`]);
await authPage.logout();
}); });
}); });

View File

@@ -17,7 +17,6 @@ import secretList from 'vault/tests/pages/secrets/backend/list';
import secretEdit from 'vault/tests/pages/secrets/backend/kv/edit-secret'; import secretEdit from 'vault/tests/pages/secrets/backend/kv/edit-secret';
import mountSecrets from 'vault/tests/pages/settings/mount-secret-backend'; import mountSecrets from 'vault/tests/pages/settings/mount-secret-backend';
import authPage from 'vault/tests/pages/auth'; import authPage from 'vault/tests/pages/auth';
import logout from 'vault/tests/pages/logout';
module('Acceptance | leases', function (hooks) { module('Acceptance | leases', function (hooks) {
setupApplicationTest(hooks); setupApplicationTest(hooks);
@@ -29,10 +28,6 @@ module('Acceptance | leases', function (hooks) {
return mountSecrets.visit().path(this.enginePath).type('kv').version(1).submit(); return mountSecrets.visit().path(this.enginePath).type('kv').version(1).submit();
}); });
hooks.afterEach(function () {
return logout.visit();
});
const createSecret = async (context, isRenewable) => { const createSecret = async (context, isRenewable) => {
context.name = `secret-${uuidv4()}`; context.name = `secret-${uuidv4()}`;
await secretList.visitRoot({ backend: context.enginePath }); await secretList.visitRoot({ backend: context.enginePath });

View File

@@ -7,7 +7,6 @@ import { module, test } from 'qunit';
import { currentURL, visit, fillIn } from '@ember/test-helpers'; import { currentURL, visit, fillIn } from '@ember/test-helpers';
import { setupApplicationTest } from 'ember-qunit'; import { setupApplicationTest } from 'ember-qunit';
import Pretender from 'pretender'; import Pretender from 'pretender';
import logout from 'vault/tests/pages/logout';
import { getManagedNamespace } from 'vault/routes/vault/cluster'; import { getManagedNamespace } from 'vault/routes/vault/cluster';
const FEATURE_FLAGS_RESPONSE = { const FEATURE_FLAGS_RESPONSE = {
@@ -39,7 +38,6 @@ module('Acceptance | Enterprise | Managed namespace root', function (hooks) {
}); });
test('it shows the managed namespace toolbar when feature flag exists', async function (assert) { test('it shows the managed namespace toolbar when feature flag exists', async function (assert) {
await logout.visit();
await visit('/vault/auth'); await visit('/vault/auth');
assert.ok(currentURL().startsWith('/vault/auth'), 'Redirected to auth'); assert.ok(currentURL().startsWith('/vault/auth'), 'Redirected to auth');
assert.ok(currentURL().includes('?namespace=admin'), 'with base namespace'); assert.ok(currentURL().includes('?namespace=admin'), 'with base namespace');
@@ -72,7 +70,6 @@ module('Acceptance | Enterprise | Managed namespace root', function (hooks) {
}); });
test('it redirects to root prefixed ns when non-root passed', async function (assert) { test('it redirects to root prefixed ns when non-root passed', async function (assert) {
await logout.visit();
await visit('/vault/auth?namespace=admindev'); await visit('/vault/auth?namespace=admindev');
assert.ok(currentURL().startsWith('/vault/auth'), 'Redirected to auth'); assert.ok(currentURL().startsWith('/vault/auth'), 'Redirected to auth');
assert.ok( assert.ok(

View File

@@ -24,6 +24,32 @@ module('Acceptance | mfa-login-enforcement', function (hooks) {
ENV['ember-cli-mirage'].handler = null; ENV['ember-cli-mirage'].handler = null;
}); });
test('it should send the correct data when creating an enforcement', async function (assert) {
assert.expect(2);
this.server.post('/identity/mfa/login-enforcement/salad-college-setting', (schema, { requestBody }) => {
const data = JSON.parse(requestBody);
assert.deepEqual(data.auth_method_types, [], 'correctly passes empty array for auth method types');
assert.deepEqual(
data.auth_method_accessors,
['auth_userpass_bb95c2b1'],
'Passes correct value for auth method accessors'
);
return { ...data, id: data.name };
});
await visit('/ui/vault/access');
await click('[data-test-sidebar-nav-link="Multi-Factor Authentication"]');
await click('[data-test-tab="enforcements"]');
await click('[data-test-enforcement-create]');
// Fill out form
await fillIn('[data-test-mlef-input="name"]', 'salad-college-setting');
await click('[data-test-component="search-select"] .ember-basic-dropdown-trigger');
await click('.ember-power-select-option');
await fillIn('[data-test-mount-accessor-select]', 'auth_userpass_bb95c2b1');
await click('[data-test-mlef-add-target]');
await click('[data-test-mlef-save]');
});
test('it should create login enforcement', async function (assert) { test('it should create login enforcement', async function (assert) {
await visit('/ui/vault/access'); await visit('/ui/vault/access');
await click('[data-test-sidebar-nav-link="Multi-Factor Authentication"]'); await click('[data-test-sidebar-nav-link="Multi-Factor Authentication"]');

View File

@@ -18,11 +18,17 @@ module('Acceptance | mfa-login', function (hooks) {
ENV['ember-cli-mirage'].handler = 'mfaLogin'; ENV['ember-cli-mirage'].handler = 'mfaLogin';
}); });
hooks.beforeEach(function () { hooks.beforeEach(function () {
this.auth = this.owner.lookup('service:auth');
this.select = async (select = 0, option = 1) => { this.select = async (select = 0, option = 1) => {
const selector = `[data-test-mfa-select="${select}"]`; const selector = `[data-test-mfa-select="${select}"]`;
const value = this.element.querySelector(`${selector} option:nth-child(${option + 1})`).value; const value = this.element.querySelector(`${selector} option:nth-child(${option + 1})`).value;
await fillIn(`${selector} select`, value); await fillIn(`${selector} select`, value);
}; };
return visit('/vault/logout');
});
hooks.afterEach(function () {
// Manually clear token after each so that future tests don't get into a weird state
this.auth.deleteCurrentToken();
}); });
hooks.after(function () { hooks.after(function () {
ENV['ember-cli-mirage'].handler = null; ENV['ember-cli-mirage'].handler = null;

View File

@@ -7,7 +7,6 @@ import { module, test } from 'qunit';
import { setupApplicationTest } from 'ember-qunit'; import { setupApplicationTest } from 'ember-qunit';
import { click, currentRouteName, currentURL, fillIn, visit } from '@ember/test-helpers'; import { click, currentRouteName, currentURL, fillIn, visit } from '@ember/test-helpers';
import authPage from 'vault/tests/pages/auth'; import authPage from 'vault/tests/pages/auth';
import logout from 'vault/tests/pages/logout';
import { setupMirage } from 'ember-cli-mirage/test-support'; import { setupMirage } from 'ember-cli-mirage/test-support';
import ENV from 'vault/config/environment'; import ENV from 'vault/config/environment';
import { Response } from 'miragejs'; import { Response } from 'miragejs';
@@ -27,7 +26,6 @@ module('Acceptance | mfa-method', function (hooks) {
methods.addObjects(this.server.db[`mfa${type}Methods`].where({})); methods.addObjects(this.server.db[`mfa${type}Methods`].where({}));
return methods; return methods;
}, []); }, []);
await logout.visit();
return authPage.login(); return authPage.login();
}); });
hooks.after(function () { hooks.after(function () {

View File

@@ -7,7 +7,6 @@ import { visit } from '@ember/test-helpers';
import { module, test } from 'qunit'; import { module, test } from 'qunit';
import { setupApplicationTest } from 'ember-qunit'; import { setupApplicationTest } from 'ember-qunit';
import authPage from 'vault/tests/pages/auth'; import authPage from 'vault/tests/pages/auth';
import logout from 'vault/tests/pages/logout';
import Ember from 'ember'; import Ember from 'ember';
let adapterException; let adapterException;
@@ -23,7 +22,6 @@ module('Acceptance | not-found', function (hooks) {
hooks.afterEach(function () { hooks.afterEach(function () {
Ember.Test.adapter.exception = adapterException; Ember.Test.adapter.exception = adapterException;
return logout.visit();
}); });
test('top-level not-found', async function (assert) { test('top-level not-found', async function (assert) {

View File

@@ -9,7 +9,6 @@ import { setupApplicationTest } from 'ember-qunit';
import { setupMirage } from 'ember-cli-mirage/test-support'; import { setupMirage } from 'ember-cli-mirage/test-support';
import ENV from 'vault/config/environment'; import ENV from 'vault/config/environment';
import authPage from 'vault/tests/pages/auth'; import authPage from 'vault/tests/pages/auth';
import logout from 'vault/tests/pages/logout';
import { create } from 'ember-cli-page-object'; import { create } from 'ember-cli-page-object';
import { clickTrigger } from 'ember-power-select/test-support/helpers'; import { clickTrigger } from 'ember-power-select/test-support/helpers';
import ss from 'vault/tests/pages/components/search-select'; import ss from 'vault/tests/pages/components/search-select';
@@ -34,15 +33,11 @@ module('Acceptance | oidc-config clients and assignments', function (hooks) {
ENV['ember-cli-mirage'].handler = 'oidcConfig'; ENV['ember-cli-mirage'].handler = 'oidcConfig';
}); });
hooks.beforeEach(async function () { hooks.beforeEach(function () {
this.store = await this.owner.lookup('service:store'); this.store = this.owner.lookup('service:store');
return authPage.login(); return authPage.login();
}); });
hooks.afterEach(function () {
return logout.visit();
});
hooks.after(function () { hooks.after(function () {
ENV['ember-cli-mirage'].handler = null; ENV['ember-cli-mirage'].handler = null;
}); });

View File

@@ -9,7 +9,6 @@ import { setupApplicationTest } from 'ember-qunit';
import { setupMirage } from 'ember-cli-mirage/test-support'; import { setupMirage } from 'ember-cli-mirage/test-support';
import ENV from 'vault/config/environment'; import ENV from 'vault/config/environment';
import authPage from 'vault/tests/pages/auth'; import authPage from 'vault/tests/pages/auth';
import logout from 'vault/tests/pages/logout';
import { create } from 'ember-cli-page-object'; import { create } from 'ember-cli-page-object';
import { clickTrigger, selectChoose } from 'ember-power-select/test-support/helpers'; import { clickTrigger, selectChoose } from 'ember-power-select/test-support/helpers';
import ss from 'vault/tests/pages/components/search-select'; import ss from 'vault/tests/pages/components/search-select';
@@ -37,15 +36,11 @@ module('Acceptance | oidc-config clients and keys', function (hooks) {
ENV['ember-cli-mirage'].handler = 'oidcConfig'; ENV['ember-cli-mirage'].handler = 'oidcConfig';
}); });
hooks.beforeEach(async function () { hooks.beforeEach(function () {
this.store = await this.owner.lookup('service:store'); this.store = this.owner.lookup('service:store');
return authPage.login(); return authPage.login();
}); });
hooks.afterEach(function () {
return logout.visit();
});
hooks.after(function () { hooks.after(function () {
ENV['ember-cli-mirage'].handler = null; ENV['ember-cli-mirage'].handler = null;
}); });

View File

@@ -9,7 +9,6 @@ import { setupApplicationTest } from 'ember-qunit';
import { setupMirage } from 'ember-cli-mirage/test-support'; import { setupMirage } from 'ember-cli-mirage/test-support';
import ENV from 'vault/config/environment'; import ENV from 'vault/config/environment';
import authPage from 'vault/tests/pages/auth'; import authPage from 'vault/tests/pages/auth';
import logout from 'vault/tests/pages/logout';
import { create } from 'ember-cli-page-object'; import { create } from 'ember-cli-page-object';
import { clickTrigger, selectChoose } from 'ember-power-select/test-support/helpers'; import { clickTrigger, selectChoose } from 'ember-power-select/test-support/helpers';
import ss from 'vault/tests/pages/components/search-select'; import ss from 'vault/tests/pages/components/search-select';
@@ -39,17 +38,13 @@ module('Acceptance | oidc-config providers and scopes', function (hooks) {
ENV['ember-cli-mirage'].handler = 'oidcConfig'; ENV['ember-cli-mirage'].handler = 'oidcConfig';
}); });
hooks.beforeEach(async function () { hooks.beforeEach(function () {
this.store = await this.owner.lookup('service:store'); this.store = this.owner.lookup('service:store');
// mock client list so OIDC BASE URL does not redirect to landing call-to-action image // mock client list so OIDC BASE URL does not redirect to landing call-to-action image
this.server.get('/identity/oidc/client', () => overrideMirageResponse(null, CLIENT_LIST_RESPONSE)); this.server.get('/identity/oidc/client', () => overrideMirageResponse(null, CLIENT_LIST_RESPONSE));
return authPage.login(); return authPage.login();
}); });
hooks.afterEach(function () {
return logout.visit();
});
hooks.after(function () { hooks.after(function () {
ENV['ember-cli-mirage'].handler = null; ENV['ember-cli-mirage'].handler = null;
}); });

View File

@@ -131,10 +131,9 @@ const setupOidc = async function (uid) {
module('Acceptance | oidc provider', function (hooks) { module('Acceptance | oidc provider', function (hooks) {
setupApplicationTest(hooks); setupApplicationTest(hooks);
hooks.beforeEach(async function () { hooks.beforeEach(function () {
this.uid = uuidv4(); this.uid = uuidv4();
this.store = await this.owner.lookup('service:store'); this.store = this.owner.lookup('service:store');
await logout.visit();
return authPage.login(); return authPage.login();
}); });

View File

@@ -33,7 +33,6 @@ module('Acceptance | pki action forms test', function (hooks) {
await authPage.login(); await authPage.login();
// Cleanup engine // Cleanup engine
await runCommands([`delete sys/mounts/${this.mountPath}`]); await runCommands([`delete sys/mounts/${this.mountPath}`]);
await logout.visit();
}); });
module('import', function (hooks) { module('import', function (hooks) {

View File

@@ -34,7 +34,6 @@ module('Acceptance | pki configuration test', function (hooks) {
await authPage.login(); await authPage.login();
// Cleanup engine // Cleanup engine
await runCommands([`delete sys/mounts/${this.mountPath}`]); await runCommands([`delete sys/mounts/${this.mountPath}`]);
await logout.visit();
}); });
module('delete all issuers modal and empty states', function (hooks) { module('delete all issuers modal and empty states', function (hooks) {

View File

@@ -9,7 +9,6 @@ import { setupApplicationTest } from 'vault/tests/helpers';
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import authPage from 'vault/tests/pages/auth'; import authPage from 'vault/tests/pages/auth';
import logout from 'vault/tests/pages/logout';
import enablePage from 'vault/tests/pages/settings/mount-secret-backend'; import enablePage from 'vault/tests/pages/settings/mount-secret-backend';
import { runCommands } from 'vault/tests/helpers/pki/pki-run-commands'; import { runCommands } from 'vault/tests/helpers/pki/pki-run-commands';
import { SELECTORS } from 'vault/tests/helpers/pki/pki-issuer-cross-sign'; import { SELECTORS } from 'vault/tests/helpers/pki/pki-issuer-cross-sign';
@@ -39,7 +38,6 @@ module('Acceptance | pki/pki cross sign', function (hooks) {
// Cleanup engine // Cleanup engine
await runCommands([`delete sys/mounts/${this.intMountPath}`]); await runCommands([`delete sys/mounts/${this.intMountPath}`]);
await runCommands([`delete sys/mounts/${this.parentMountPath}`]); await runCommands([`delete sys/mounts/${this.parentMountPath}`]);
await logout.visit();
}); });
test('it cross-signs an issuer', async function (assert) { test('it cross-signs an issuer', async function (assert) {

View File

@@ -35,7 +35,6 @@ module('Acceptance | pki engine route cleanup test', function (hooks) {
await authPage.login(); await authPage.login();
// Cleanup engine // Cleanup engine
await runCommands([`delete sys/mounts/${this.mountPath}`]); await runCommands([`delete sys/mounts/${this.mountPath}`]);
await logout.visit();
}); });
module('configuration', function () { module('configuration', function () {

View File

@@ -38,7 +38,6 @@ module('Acceptance | pki workflow', function (hooks) {
await authPage.login(); await authPage.login();
// Cleanup engine // Cleanup engine
await runCommands([`delete sys/mounts/${this.mountPath}`]); await runCommands([`delete sys/mounts/${this.mountPath}`]);
await logout.visit();
}); });
test('empty state messages are correct when PKI not configured', async function (assert) { test('empty state messages are correct when PKI not configured', async function (assert) {

View File

@@ -49,7 +49,6 @@ module('Acceptance | pki overview', function (hooks) {
await authPage.login(); await authPage.login();
// Cleanup engine // Cleanup engine
await runCommands([`delete sys/mounts/${this.mountPath}`]); await runCommands([`delete sys/mounts/${this.mountPath}`]);
await logout.visit();
}); });
test('navigates to view issuers when link is clicked on issuer card', async function (assert) { test('navigates to view issuers when link is clicked on issuer card', async function (assert) {

View File

@@ -37,7 +37,6 @@ module('Acceptance | pki tidy', function (hooks) {
await authPage.login(); await authPage.login();
// Cleanup engine // Cleanup engine
await runCommands([`delete sys/mounts/${this.mountPath}`]); await runCommands([`delete sys/mounts/${this.mountPath}`]);
await logout.visit();
}); });
test('it configures a manual tidy operation and shows its details and tidy states', async function (assert) { test('it configures a manual tidy operation and shows its details and tidy states', async function (assert) {

View File

@@ -7,7 +7,6 @@ import { currentURL, currentRouteName, visit } from '@ember/test-helpers';
import { module, test } from 'qunit'; import { module, test } from 'qunit';
import { setupApplicationTest } from 'ember-qunit'; import { setupApplicationTest } from 'ember-qunit';
import authPage from 'vault/tests/pages/auth'; import authPage from 'vault/tests/pages/auth';
import logout from 'vault/tests/pages/logout';
module('Acceptance | policies', function (hooks) { module('Acceptance | policies', function (hooks) {
setupApplicationTest(hooks); setupApplicationTest(hooks);
@@ -16,10 +15,6 @@ module('Acceptance | policies', function (hooks) {
return authPage.login(); return authPage.login();
}); });
hooks.afterEach(function () {
return logout.visit();
});
test('it redirects to acls with unknown policy type', async function (assert) { test('it redirects to acls with unknown policy type', async function (assert) {
await visit('/vault/policies/foo'); await visit('/vault/policies/foo');
assert.strictEqual(currentRouteName(), 'vault.cluster.policies.index'); assert.strictEqual(currentRouteName(), 'vault.cluster.policies.index');

View File

@@ -8,7 +8,6 @@ import { setupApplicationTest } from 'ember-qunit';
import { setupMirage } from 'ember-cli-mirage/test-support'; import { setupMirage } from 'ember-cli-mirage/test-support';
import { click, visit } from '@ember/test-helpers'; import { click, visit } from '@ember/test-helpers';
import authPage from 'vault/tests/pages/auth'; import authPage from 'vault/tests/pages/auth';
import logout from 'vault/tests/pages/logout';
module('Acceptance | raft storage', function (hooks) { module('Acceptance | raft storage', function (hooks) {
setupApplicationTest(hooks); setupApplicationTest(hooks);
@@ -22,9 +21,6 @@ module('Acceptance | raft storage', function (hooks) {
this.server.get('/sys/license/features', () => ({})); this.server.get('/sys/license/features', () => ({}));
await authPage.login(); await authPage.login();
}); });
hooks.afterEach(function () {
return logout.visit();
});
test('it should render correct number of raft peers', async function (assert) { test('it should render correct number of raft peers', async function (assert) {
assert.expect(3); assert.expect(3);

View File

@@ -12,28 +12,21 @@ import { selectChoose, clickTrigger } from 'ember-power-select/test-support/help
import mountSecrets from 'vault/tests/pages/settings/mount-secret-backend'; import mountSecrets from 'vault/tests/pages/settings/mount-secret-backend';
import connectionPage from 'vault/tests/pages/secrets/backend/database/connection'; import connectionPage from 'vault/tests/pages/secrets/backend/database/connection';
import rolePage from 'vault/tests/pages/secrets/backend/database/role'; import rolePage from 'vault/tests/pages/secrets/backend/database/role';
import apiStub from 'vault/tests/helpers/noop-all-api-requests';
import authPage from 'vault/tests/pages/auth'; import authPage from 'vault/tests/pages/auth';
import logout from 'vault/tests/pages/logout'; import logout from 'vault/tests/pages/logout';
import consoleClass from 'vault/tests/pages/components/console/ui-panel'; import consoleClass from 'vault/tests/pages/components/console/ui-panel';
import searchSelect from 'vault/tests/pages/components/search-select'; import searchSelect from 'vault/tests/pages/components/search-select';
import {
createPolicyCmd,
deleteEngineCmd,
mountEngineCmd,
runCmd,
tokenWithPolicyCmd,
} from 'vault/tests/helpers/commands';
const searchSelectComponent = create(searchSelect); const searchSelectComponent = create(searchSelect);
const consoleComponent = create(consoleClass); const consoleComponent = create(consoleClass);
const MODEL = {
engineType: 'database',
id: 'database-name',
};
const mount = async () => {
const path = `database-${Date.now()}`;
await mountSecrets.enable('database', path);
await settled();
return path;
};
const newConnection = async (backend, plugin = 'mongodb-database-plugin') => { const newConnection = async (backend, plugin = 'mongodb-database-plugin') => {
const name = `connection-${Date.now()}`; const name = `connection-${Date.now()}`;
await connectionPage.visitCreate({ backend }); await connectionPage.visitCreate({ backend });
@@ -46,6 +39,14 @@ const newConnection = async (backend, plugin = 'mongodb-database-plugin') => {
return name; return name;
}; };
const navToConnection = async (backend, connection) => {
await visit('/vault/secrets');
await click(`[data-test-auth-backend-link="${backend}"]`);
await click('[data-test-secret-list-tab="Connections"]');
await click(`[data-test-secret-link="${connection}"]`);
return;
};
const connectionTests = [ const connectionTests = [
{ {
name: 'elasticsearch-connection', name: 'elasticsearch-connection',
@@ -53,6 +54,7 @@ const connectionTests = [
elasticUser: 'username', elasticUser: 'username',
elasticPassword: 'password', elasticPassword: 'password',
url: 'http://127.0.0.1:9200', url: 'http://127.0.0.1:9200',
assertCount: 9,
requiredFields: async (assert, name) => { requiredFields: async (assert, name) => {
assert.dom('[data-test-input="username"]').exists(`Username field exists for ${name}`); assert.dom('[data-test-input="username"]').exists(`Username field exists for ${name}`);
assert.dom('[data-test-input="password"]').exists(`Password field exists for ${name}`); assert.dom('[data-test-input="password"]').exists(`Password field exists for ${name}`);
@@ -71,6 +73,7 @@ const connectionTests = [
name: 'mongodb-connection', name: 'mongodb-connection',
plugin: 'mongodb-database-plugin', plugin: 'mongodb-database-plugin',
url: `mongodb://127.0.0.1:4321/test`, url: `mongodb://127.0.0.1:4321/test`,
assertCount: 5,
requiredFields: async (assert, name) => { requiredFields: async (assert, name) => {
assert.dom('[data-test-input="username"]').exists(`Username field exists for ${name}`); assert.dom('[data-test-input="username"]').exists(`Username field exists for ${name}`);
assert.dom('[data-test-input="password"]').exists(`Password field exists for ${name}`); assert.dom('[data-test-input="password"]').exists(`Password field exists for ${name}`);
@@ -85,6 +88,7 @@ const connectionTests = [
name: 'mssql-connection', name: 'mssql-connection',
plugin: 'mssql-database-plugin', plugin: 'mssql-database-plugin',
url: `mssql://127.0.0.1:4321/test`, url: `mssql://127.0.0.1:4321/test`,
assertCount: 6,
requiredFields: async (assert, name) => { requiredFields: async (assert, name) => {
assert.dom('[data-test-input="username"]').exists(`Username field exists for ${name}`); assert.dom('[data-test-input="username"]').exists(`Username field exists for ${name}`);
assert.dom('[data-test-input="password"]').exists(`Password field exists for ${name}`); assert.dom('[data-test-input="password"]').exists(`Password field exists for ${name}`);
@@ -106,6 +110,7 @@ const connectionTests = [
name: 'mysql-connection', name: 'mysql-connection',
plugin: 'mysql-database-plugin', plugin: 'mysql-database-plugin',
url: `{{username}}:{{password}}@tcp(127.0.0.1:3306)/test`, url: `{{username}}:{{password}}@tcp(127.0.0.1:3306)/test`,
assertCount: 7,
requiredFields: async (assert, name) => { requiredFields: async (assert, name) => {
assert.dom('[data-test-input="username"]').exists(`Username field exists for ${name}`); assert.dom('[data-test-input="username"]').exists(`Username field exists for ${name}`);
assert.dom('[data-test-input="password"]').exists(`Password field exists for ${name}`); assert.dom('[data-test-input="password"]').exists(`Password field exists for ${name}`);
@@ -128,6 +133,7 @@ const connectionTests = [
name: 'mysql-aurora-connection', name: 'mysql-aurora-connection',
plugin: 'mysql-aurora-database-plugin', plugin: 'mysql-aurora-database-plugin',
url: `{{username}}:{{password}}@tcp(127.0.0.1:3306)/test`, url: `{{username}}:{{password}}@tcp(127.0.0.1:3306)/test`,
assertCount: 7,
requiredFields: async (assert, name) => { requiredFields: async (assert, name) => {
assert.dom('[data-test-input="username"]').exists(`Username field exists for ${name}`); assert.dom('[data-test-input="username"]').exists(`Username field exists for ${name}`);
assert.dom('[data-test-input="password"]').exists(`Password field exists for ${name}`); assert.dom('[data-test-input="password"]').exists(`Password field exists for ${name}`);
@@ -150,6 +156,7 @@ const connectionTests = [
name: 'mysql-rds-connection', name: 'mysql-rds-connection',
plugin: 'mysql-rds-database-plugin', plugin: 'mysql-rds-database-plugin',
url: `{{username}}:{{password}}@tcp(127.0.0.1:3306)/test`, url: `{{username}}:{{password}}@tcp(127.0.0.1:3306)/test`,
assertCount: 7,
requiredFields: async (assert, name) => { requiredFields: async (assert, name) => {
assert.dom('[data-test-input="username"]').exists(`Username field exists for ${name}`); assert.dom('[data-test-input="username"]').exists(`Username field exists for ${name}`);
assert.dom('[data-test-input="password"]').exists(`Password field exists for ${name}`); assert.dom('[data-test-input="password"]').exists(`Password field exists for ${name}`);
@@ -172,6 +179,7 @@ const connectionTests = [
name: 'mysql-legacy-connection', name: 'mysql-legacy-connection',
plugin: 'mysql-legacy-database-plugin', plugin: 'mysql-legacy-database-plugin',
url: `{{username}}:{{password}}@tcp(127.0.0.1:3306)/test`, url: `{{username}}:{{password}}@tcp(127.0.0.1:3306)/test`,
assertCount: 7,
requiredFields: async (assert, name) => { requiredFields: async (assert, name) => {
assert.dom('[data-test-input="username"]').exists(`Username field exists for ${name}`); assert.dom('[data-test-input="username"]').exists(`Username field exists for ${name}`);
assert.dom('[data-test-input="password"]').exists(`Password field exists for ${name}`); assert.dom('[data-test-input="password"]').exists(`Password field exists for ${name}`);
@@ -194,6 +202,7 @@ const connectionTests = [
name: 'postgresql-connection', name: 'postgresql-connection',
plugin: 'postgresql-database-plugin', plugin: 'postgresql-database-plugin',
url: `postgresql://{{username}}:{{password}}@localhost:5432/postgres?sslmode=disable`, url: `postgresql://{{username}}:{{password}}@localhost:5432/postgres?sslmode=disable`,
assertCount: 7,
requiredFields: async (assert, name) => { requiredFields: async (assert, name) => {
assert.dom('[data-test-input="username"]').exists(`Username field exists for ${name}`); assert.dom('[data-test-input="username"]').exists(`Username field exists for ${name}`);
assert.dom('[data-test-input="password"]').exists(`Password field exists for ${name}`); assert.dom('[data-test-input="password"]').exists(`Password field exists for ${name}`);
@@ -214,45 +223,18 @@ const connectionTests = [
.exists(`Username template toggle exists for ${name}`); .exists(`Username template toggle exists for ${name}`);
}, },
}, },
// keep oracle as last DB because it is skipped in some tests (line 285) the UI doesn't return to empty state after
{
name: 'oracle-connection',
plugin: 'vault-plugin-database-oracle',
url: `{{username}}/{{password}}@localhost:1521/OraDoc.localhost`,
requiredFields: async (assert, name) => {
assert.dom('[data-test-input="username"]').exists(`Username field exists for ${name}`);
assert.dom('[data-test-input="password"]').exists(`Password field exists for ${name}`);
assert
.dom('[data-test-input="max_open_connections"]')
.exists(`Max open connections exists for ${name}`);
assert
.dom('[data-test-input="max_idle_connections"]')
.exists(`Max idle connections exists for ${name}`);
assert
.dom('[data-test-input="max_connection_lifetime"]')
.exists(`Max connection lifetime exists for ${name}`);
assert
.dom('[data-test-input="root_rotation_statements"]')
.exists(`Root rotation statements exists for ${name}`);
assert
.dom('[data-test-database-oracle-alert]')
.hasTextContaining(
`Warning Please ensure that your Oracle plugin has the default name of vault-plugin-database-oracle. Custom naming is not supported in the UI at this time. If the plugin is already named vault-plugin-database-oracle, disregard this warning.`,
'warning banner displays for oracle plugin name'
);
},
},
]; ];
module('Acceptance | secrets/database/*', function (hooks) { module('Acceptance | secrets/database/*', function (hooks) {
setupApplicationTest(hooks); setupApplicationTest(hooks);
hooks.beforeEach(async function () { hooks.beforeEach(async function () {
this.server = apiStub({ usePassthrough: true }); this.backend = `database-testing`;
return authPage.login(); await authPage.login();
return consoleComponent.runCommands(mountEngineCmd('database', this.backend));
}); });
hooks.afterEach(function () { hooks.afterEach(function () {
this.server.shutdown(); return consoleComponent.runCommands(deleteEngineCmd(this.backend));
}); });
test('can enable the database secrets engine', async function (assert) { test('can enable the database secrets engine', async function (assert) {
@@ -272,12 +254,15 @@ module('Acceptance | secrets/database/*', function (hooks) {
assert.strictEqual(currentURL(), `/vault/secrets/${backend}/overview`, 'Tab links to overview page'); assert.strictEqual(currentURL(), `/vault/secrets/${backend}/overview`, 'Tab links to overview page');
assert.dom('[data-test-component="empty-state"]').exists('Empty state also exists on overview page'); assert.dom('[data-test-component="empty-state"]').exists('Empty state also exists on overview page');
assert.dom('[data-test-secret-list-tab="Roles"]').exists('Has Roles tab'); assert.dom('[data-test-secret-list-tab="Roles"]').exists('Has Roles tab');
await visit('/vault/secrets');
// Cleanup backend
await consoleComponent.runCommands(deleteEngineCmd(backend));
}); });
test('Connection create and edit form for each plugin', async function (assert) { for (const testCase of connectionTests) {
assert.expect(161); test(`database connection create and edit: ${testCase.plugin}`, async function (assert) {
const backend = await mount(); assert.expect(19 + testCase.assertCount);
for (const testCase of connectionTests) { const backend = this.backend;
await connectionPage.visitCreate({ backend }); await connectionPage.visitCreate({ backend });
assert.strictEqual(currentURL(), `/vault/secrets/${backend}/create`, 'Correct creation URL'); assert.strictEqual(currentURL(), `/vault/secrets/${backend}/create`, 'Correct creation URL');
assert assert
@@ -293,19 +278,20 @@ module('Acceptance | secrets/database/*', function (hooks) {
} else { } else {
await connectionPage.connectionUrl(testCase.url); await connectionPage.connectionUrl(testCase.url);
} }
// skip adding oracle db connection since plugin doesn't exist testCase.requiredFields(assert, testCase.plugin);
if (testCase.plugin === 'vault-plugin-database-oracle') { assert.dom('[data-test-input="verify_connection"]').isChecked('verify is checked');
testCase.requiredFields(assert, testCase.name);
continue;
}
testCase.requiredFields(assert, testCase.name);
await connectionPage.toggleVerify(); await connectionPage.toggleVerify();
assert.dom('[data-test-input="verify_connection"]').isNotChecked('verify is unchecked');
assert
.dom('[data-test-database-oracle-alert]')
.doesNotExist('does not show oracle alert for non-oracle plugins');
await connectionPage.save(); await connectionPage.save();
await settled(); await settled();
assert assert
.dom('.modal.is-active .title') .dom('.modal.is-active .title')
.hasText('Rotate your root credentials?', 'Modal appears asking to rotate root credentials'); .hasText('Rotate your root credentials?', 'Modal appears asking to rotate root credentials');
await connectionPage.enable(); assert.dom('[data-test-enable-connection]').exists('Enable button exists');
await click('[data-test-enable-connection]');
assert.ok( assert.ok(
currentURL().startsWith(`/vault/secrets/${backend}/show/${testCase.name}`), currentURL().startsWith(`/vault/secrets/${backend}/show/${testCase.name}`),
`Saves connection and takes you to show page for ${testCase.name}` `Saves connection and takes you to show page for ${testCase.name}`
@@ -322,8 +308,10 @@ module('Acceptance | secrets/database/*', function (hooks) {
assert.dom(`[data-test-input="plugin_name"]`).hasAttribute('readonly'); assert.dom(`[data-test-input="plugin_name"]`).hasAttribute('readonly');
assert.dom('[data-test-input="password"]').doesNotExist('Password is not displayed on edit form'); assert.dom('[data-test-input="password"]').doesNotExist('Password is not displayed on edit form');
assert.dom('[data-test-toggle-input="show-password"]').exists('Update password toggle exists'); assert.dom('[data-test-toggle-input="show-password"]').exists('Update password toggle exists');
await connectionPage.toggleVerify();
assert.dom('[data-test-input="verify_connection"]').isNotChecked('verify is still unchecked');
await connectionPage.save(); await connectionPage.save();
assert.strictEqual(currentURL(), `/vault/secrets/${backend}/show/${testCase.name}`);
// click "Add Role" // click "Add Role"
await connectionPage.addRole(); await connectionPage.addRole();
await settled(); await settled();
@@ -332,12 +320,58 @@ module('Acceptance | secrets/database/*', function (hooks) {
testCase.name, testCase.name,
'Database connection is pre-selected on the form' 'Database connection is pre-selected on the form'
); );
await click('[data-test-secret-breadcrumb]'); await click('[data-test-database-role-cancel]');
} assert.strictEqual(currentURL(), `/vault/secrets/${backend}/list`, 'Cancel button links to list view');
});
}
test('database connection create and edit: vault-plugin-database-oracle', async function (assert) {
assert.expect(11);
// keep oracle as separate test because it behaves differently than the others
const testCase = {
name: 'oracle-connection',
plugin: 'vault-plugin-database-oracle',
url: `{{username}}/{{password}}@localhost:1521/OraDoc.localhost`,
requiredFields: async (assert, name) => {
assert.dom('[data-test-input="username"]').exists(`Username field exists for ${name}`);
assert.dom('[data-test-input="password"]').exists(`Password field exists for ${name}`);
assert
.dom('[data-test-input="max_open_connections"]')
.exists(`Max open connections exists for ${name}`);
assert
.dom('[data-test-input="max_idle_connections"]')
.exists(`Max idle connections exists for ${name}`);
assert
.dom('[data-test-input="max_connection_lifetime"]')
.exists(`Max connection lifetime exists for ${name}`);
assert
.dom('[data-test-input="root_rotation_statements"]')
.exists(`Root rotation statements exists for ${name}`);
assert
.dom('[data-test-database-oracle-alert]')
.hasTextContaining(
`Warning Please ensure that your Oracle plugin has the default name of vault-plugin-database-oracle. Custom naming is not supported in the UI at this time. If the plugin is already named vault-plugin-database-oracle, disregard this warning.`,
'warning banner displays for oracle plugin name'
);
},
};
const backend = this.backend;
await connectionPage.visitCreate({ backend });
assert.strictEqual(currentURL(), `/vault/secrets/${backend}/create`, 'Correct creation URL');
assert
.dom('[data-test-empty-state-title]')
.hasText('No plugin selected', 'No plugin is selected by default and empty state shows');
await connectionPage.dbPlugin(testCase.plugin);
assert.dom('[data-test-empty-state]').doesNotExist('Empty state goes away after plugin selected');
assert.dom('[data-test-database-oracle-alert]').exists('shows oracle alert');
await connectionPage.name(testCase.name);
await connectionPage.connectionUrl(testCase.url);
testCase.requiredFields(assert, testCase.plugin);
// Cannot save without plugin mounted
// TODO: add fake server response for fuller test coverage
}); });
test('Can create and delete a connection', async function (assert) { test('Can create and delete a connection', async function (assert) {
const backend = await mount(); const backend = this.backend;
const connectionDetails = { const connectionDetails = {
plugin: 'mongodb-database-plugin', plugin: 'mongodb-database-plugin',
id: 'horses-db', id: 'horses-db',
@@ -349,11 +383,7 @@ module('Acceptance | secrets/database/*', function (hooks) {
{ label: 'Write concern', name: 'write_concern' }, { label: 'Write concern', name: 'write_concern' },
], ],
}; };
assert.strictEqual( await visit(`/vault/secrets/${backend}/list`);
currentURL(),
`/vault/secrets/${backend}/list`,
'Mounts and redirects to connection list page'
);
await connectionPage.createLink(); await connectionPage.createLink();
assert.strictEqual(currentURL(), `/vault/secrets/${backend}/create`, 'Create link goes to create page'); assert.strictEqual(currentURL(), `/vault/secrets/${backend}/create`, 'Create link goes to create page');
assert assert
@@ -407,9 +437,25 @@ module('Acceptance | secrets/database/*', function (hooks) {
}); });
test('buttons show up for managing connection', async function (assert) { test('buttons show up for managing connection', async function (assert) {
const backend = await mount(); const backend = this.backend;
const connection = await newConnection(backend); const connection = await newConnection(backend);
await connectionPage.visitShow({ backend, id: connection }); const CONNECTION_VIEW_ONLY = `
path "${backend}/config" {
capabilities = ["list"]
}
path "${backend}/config/*" {
capabilities = ["read"]
}
# allow backend cleanup on afterEach
path "sys/mounts/${backend}" {
capabilities = ["delete"]
}
`;
const token = await runCmd(consoleComponent, [
...createPolicyCmd('test-policy', CONNECTION_VIEW_ONLY),
...tokenWithPolicyCmd('test-policy'),
]);
await navToConnection(backend, connection);
assert assert
.dom('[data-test-database-connection-delete]') .dom('[data-test-database-connection-delete]')
.hasText('Delete connection', 'Delete connection button exists with correct text'); .hasText('Delete connection', 'Delete connection button exists with correct text');
@@ -418,26 +464,11 @@ module('Acceptance | secrets/database/*', function (hooks) {
.hasText('Reset connection', 'Reset button exists with correct text'); .hasText('Reset connection', 'Reset button exists with correct text');
assert.dom('[data-test-secret-create]').hasText('Add role', 'Add role button exists with correct text'); assert.dom('[data-test-secret-create]').hasText('Add role', 'Add role button exists with correct text');
assert.dom('[data-test-edit-link]').hasText('Edit configuration', 'Edit button exists with correct text'); assert.dom('[data-test-edit-link]').hasText('Edit configuration', 'Edit button exists with correct text');
const CONNECTION_VIEW_ONLY = ` await authPage.logout();
path "${backend}/*" { // Check with restricted permissions
capabilities = ["deny"]
}
path "${backend}/config" {
capabilities = ["list"]
}
path "${backend}/config/*" {
capabilities = ["read"]
}
`;
await consoleComponent.runCommands([
`write sys/mounts/${backend} type=database`,
`write sys/policies/acl/test-policy policy=${btoa(CONNECTION_VIEW_ONLY)}`,
'write -field=client_token auth/token/create policies=test-policy ttl=1h',
]);
const token = consoleComponent.lastTextOutput;
await logout.visit();
await authPage.login(token); await authPage.login(token);
await connectionPage.visitShow({ backend, id: connection }); assert.dom(`[data-test-auth-backend-link="${backend}"]`).exists('Shows backend on secret list page');
await navToConnection(backend, connection);
assert.strictEqual( assert.strictEqual(
currentURL(), currentURL(),
`/vault/secrets/${backend}/show/${connection}`, `/vault/secrets/${backend}/show/${connection}`,
@@ -463,7 +494,7 @@ module('Acceptance | secrets/database/*', function (hooks) {
}); });
test('Role create form', async function (assert) { test('Role create form', async function (assert) {
const backend = await mount(); const backend = this.backend;
// Connection needed for role fields // Connection needed for role fields
await newConnection(backend); await newConnection(backend);
await rolePage.visitCreate({ backend }); await rolePage.visitCreate({ backend });
@@ -494,49 +525,40 @@ module('Acceptance | secrets/database/*', function (hooks) {
}); });
test('root and limited access', async function (assert) { test('root and limited access', async function (assert) {
this.set('model', MODEL); const backend = this.backend;
const backend = 'database';
const NO_ROLES_POLICY = ` const NO_ROLES_POLICY = `
path "database/roles/*" { path "${backend}/roles/*" {
capabilities = ["delete"] capabilities = ["delete"]
} }
path "database/static-roles/*" { path "${backend}/static-roles/*" {
capabilities = ["delete"] capabilities = ["delete"]
} }
path "database/config/*" { path "${backend}/config/*" {
capabilities = ["list", "create", "read", "update"] capabilities = ["list", "create", "read", "update"]
} }
path "database/creds/*" { path "${backend}/creds/*" {
capabilities = ["list", "create", "read", "update"] capabilities = ["list", "create", "read", "update"]
} }
`; `;
await consoleComponent.runCommands([ const token = await runCmd(consoleComponent, [
`write sys/mounts/${backend} type=database`, ...createPolicyCmd('test-policy', NO_ROLES_POLICY),
`write sys/policies/acl/test-policy policy=${btoa(NO_ROLES_POLICY)}`, ...tokenWithPolicyCmd('test-policy'),
'write -field=client_token auth/token/create policies=test-policy ttl=1h',
]); ]);
const token = consoleComponent.lastTextOutput;
// test root user flow // test root user flow first
await settled(); await visit(`/vault/secrets/${backend}/overview`);
// await click('[data-test-secret-backend-row="database"]');
// skipping the click because occasionally is shows up on the second page and cannot be found
await visit(`/vault/secrets/database/overview`);
assert.dom('[data-test-component="empty-state"]').exists('renders empty state'); assert.dom('[data-test-component="empty-state"]').exists('renders empty state');
assert.dom('[data-test-secret-list-tab="Connections"]').exists('renders connections tab'); assert.dom('[data-test-secret-list-tab="Connections"]').exists('renders connections tab');
assert.dom('[data-test-secret-list-tab="Roles"]').exists('renders connections tab'); assert.dom('[data-test-secret-list-tab="Roles"]').exists('renders connections tab');
await click('[data-test-secret-create="connections"]'); await click('[data-test-secret-create="connections"]');
assert.strictEqual(currentURL(), '/vault/secrets/database/create'); assert.strictEqual(currentURL(), `/vault/secrets/${backend}/create`);
// Login with restricted policy // Login with restricted policy
await logout.visit(); await logout.visit();
await authPage.login(token); await authPage.login(token);
await settled(); await visit(`/vault/secrets/${backend}/overview`);
// skipping the click because occasionally is shows up on the second page and cannot be found
await visit(`/vault/secrets/database/overview`);
assert.dom('[data-test-tab="overview"]').exists('renders overview tab'); assert.dom('[data-test-tab="overview"]').exists('renders overview tab');
assert.dom('[data-test-secret-list-tab="Connections"]').exists('renders connections tab'); assert.dom('[data-test-secret-list-tab="Connections"]').exists('renders connections tab');
assert assert
@@ -547,6 +569,6 @@ module('Acceptance | secrets/database/*', function (hooks) {
.exists({ count: 1 }, 'renders only the connection card'); .exists({ count: 1 }, 'renders only the connection card');
await click('[data-test-action-text="Configure new"]'); await click('[data-test-action-text="Configure new"]');
assert.strictEqual(currentURL(), '/vault/secrets/database/create?itemType=connection'); assert.strictEqual(currentURL(), `/vault/secrets/${backend}/create?itemType=connection`);
}); });
}); });

View File

@@ -74,7 +74,6 @@ module('Acceptance | secrets/secret/create, read, delete', function (hooks) {
hooks.afterEach(async function () { hooks.afterEach(async function () {
this.server.shutdown(); this.server.shutdown();
await logout.visit();
}); });
test('it creates a secret and redirects', async function (assert) { test('it creates a secret and redirects', async function (assert) {
@@ -682,7 +681,8 @@ module('Acceptance | secrets/secret/create, read, delete', function (hooks) {
await deleteEngine(enginePath, assert); await deleteEngine(enginePath, assert);
}); });
test('version 2: with metadata no read or list but with delete access and full access to the data endpoint', async function (assert) { // TODO VAULT-16258: revisit when KV-V2 is engine
test.skip('version 2: with metadata no read or list but with delete access and full access to the data endpoint', async function (assert) {
assert.expect(12); assert.expect(12);
const enginePath = 'no-metadata-read'; const enginePath = 'no-metadata-read';
const secretPath = 'no-metadata-read-secret-name'; const secretPath = 'no-metadata-read-secret-name';

Some files were not shown because too many files have changed in this diff Show More