Skip to content

Commit

Permalink
cherrypick UI: Fix LDAP Mirage Handler (#28432)
Browse files Browse the repository at this point in the history
* update ldap mirage scenario to allow check-in/check-out action

* update libraries test to mount engine

* update mirage, fix tests

* update lease renew CLI command

* fix test

* update tests"
  • Loading branch information
hashishaw authored and hellobontempo committed Jan 22, 2025
1 parent 29e0f01 commit 6c09c62
Show file tree
Hide file tree
Showing 8 changed files with 189 additions and 49 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export default class LdapLibraryDetailsAccountsPageComponent extends Component<A
@tracked checkOutTtl: string | null = null;

get cliCommand() {
return `vault lease renew ad/library/${this.args.library.name}/check-out/:lease_id`;
return `vault lease renew ${this.args.library.backend}/library/${this.args.library.name}/check-out/:lease_id`;
}
@action
setTtl(data: TtlEvent) {
Expand Down
74 changes: 59 additions & 15 deletions ui/mirage/handlers/ldap.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export default function (server) {
const { name, backend } = req.params;
return name ? { name } : { backend };
};

const getRecord = (schema, req, dbKey) => {
const record = schema.db[dbKey].findBy(query(req));
if (record) {
Expand All @@ -21,13 +22,15 @@ export default function (server) {
}
return new Response(404, {}, { errors: [] });
};

const createOrUpdateRecord = (schema, req, dbKey) => {
const data = JSON.parse(req.requestBody);
const dbCollection = schema.db[dbKey];
dbCollection.firstOrCreate(query(req), data);
dbCollection.update(query(req), data);
return new Response(204);
};

const listRecords = (schema, dbKey, query = {}) => {
const records = schema.db[dbKey].where(query);
const keys = records.map(({ name }) => {
Expand Down Expand Up @@ -56,17 +59,6 @@ export default function (server) {
return getRecord(schema, req, 'ldapRoles', type);
};

// mount
server.post('/sys/mounts/:path', () => new Response(204));
server.get('/sys/internal/ui/mounts/:path', () => ({
data: {
accessor: 'ldap_ade94329',
type: 'ldap',
path: 'ldap-test/',
uuid: '35e9119d-5708-4b6b-58d2-f913e27f242d',
config: {},
},
}));
// config
server.post('/:backend/config', (schema, req) => createOrUpdateRecord(schema, req, 'ldapConfigs'));
server.get('/:backend/config', (schema, req) => getRecord(schema, req, 'ldapConfigs'));
Expand All @@ -90,8 +82,60 @@ export default function (server) {
server.post('/:backend/library/:name', (schema, req) => createOrUpdateRecord(schema, req, 'ldapLibraries'));
server.get('/:backend/library/:name', (schema, req) => getRecord(schema, req, 'ldapLibraries'));
server.get('/:backend/library', (schema) => listRecords(schema, 'ldapLibraries'));
server.get('/:backend/library/:name/status', () => ({
'bob.johnson': { available: false, borrower_client_token: '8b80c305eb3a7dbd161ef98f10ea60a116ce0910' },
'mary.smith': { available: true },
}));
server.get('/:backend/library/:name/status', (schema) => {
const data = schema.db['ldapAccountStatuses'].reduce((prev, curr) => {
prev[curr.account] = {
available: curr.available,
borrower_client_token: curr.borrower_client_token,
};
return prev;
}, {});
return { data };
});
// check-out / check-in
server.post('/:backend/library/:set_name/check-in', (schema, req) => {
// Check-in makes an unavailable account available again
const { service_account_names } = JSON.parse(req.requestBody);
const dbCollection = schema.db['ldapAccountStatuses'];
const updated = dbCollection.find(service_account_names).map((f) => ({
...f,
available: true,
borrower_client_token: undefined,
}));
updated.forEach((u) => {
dbCollection.update(u.id, u);
});
return {
data: {
check_ins: service_account_names,
},
};
});
server.post('/:backend/library/:set_name/check-out', (schema, req) => {
const { set_name, backend } = req.params;
const dbCollection = schema.db['ldapAccountStatuses'];
const available = dbCollection.where({ available: true });
if (available) {
return Response(404, {}, { errors: ['no accounts available to check out'] });
}
const checkOut = {
...available[0],
available: false,
borrower_client_token: crypto.randomUUID(),
};
dbCollection.update(checkOut.id, checkOut);
return {
request_id: '364a17d4-e5ab-998b-ceee-b49929229e0c',
lease_id: `${backend}/library/${set_name}/check-out/aoBsaBEI4PK96VnukubvYDlZ`,
renewable: true,
lease_duration: 36000,
data: {
password: crypto.randomUUID(),
service_account_name: checkOut.account,
},
wrap_info: null,
warnings: null,
auth: null,
};
});
}
13 changes: 13 additions & 0 deletions ui/mirage/models/ldap-account-status.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/

import { Model } from 'miragejs';

export default Model.extend({
account: '', // should match ID
library: '',
available: false,
borrower_client_token: undefined,
});
13 changes: 12 additions & 1 deletion ui/mirage/scenarios/ldap.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/

export default function (server) {
server.create('ldap-config', { path: 'kubernetes' });
server.create('ldap-config', { path: 'kubernetes', backend: 'ldap-test' });
server.create('ldap-role', 'static', { name: 'static-role' });
server.create('ldap-role', 'dynamic', { name: 'dynamic-role' });
// hierarchical roles
Expand All @@ -14,4 +14,15 @@ export default function (server) {
server.create('ldap-role', 'static', { name: 'my-role' });
server.create('ldap-role', 'dynamic', { name: 'my-role' });
server.create('ldap-library', { name: 'test-library' });
server.create('ldap-account-status', {
id: 'bob.johnson',
account: 'bob.johnson',
available: false,
borrower_client_token: '8b80c305eb3a7dbd161ef98f10ea60a116ce0910',
});
server.create('ldap-account-status', {
id: 'mary.smith',
account: 'mary.smith',
available: true,
});
}
39 changes: 32 additions & 7 deletions ui/tests/acceptance/secrets/backend/ldap/libraries-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@
import { module, test } from 'qunit';
import { setupApplicationTest } from 'ember-qunit';
import { setupMirage } from 'ember-cli-mirage/test-support';
import { v4 as uuidv4 } from 'uuid';
import ldapMirageScenario from 'vault/mirage/scenarios/ldap';
import ldapHandlers from 'vault/mirage/handlers/ldap';
import authPage from 'vault/tests/pages/auth';
import { click } from '@ember/test-helpers';
import { isURL, visitURL } from 'vault/tests/helpers/ldap/ldap-helpers';
import { deleteEngineCmd, mountEngineCmd, runCmd } from 'vault/tests/helpers/commands';

module('Acceptance | ldap | libraries', function (hooks) {
setupApplicationTest(hooks);
Expand All @@ -19,21 +21,41 @@ module('Acceptance | ldap | libraries', function (hooks) {
hooks.beforeEach(async function () {
ldapHandlers(this.server);
ldapMirageScenario(this.server);
this.backend = `ldap-test-${uuidv4()}`;
await authPage.login();
return visitURL('libraries');
// mount & configure
await runCmd([
mountEngineCmd('ldap', this.backend),
`write ${this.backend}/config binddn=foo bindpass=bar url=http://localhost:8208`,
]);
return visitURL('libraries', this.backend);
});

hooks.afterEach(async function () {
await runCmd(deleteEngineCmd(this.backend));
});

test('it should show libraries on overview page', async function (assert) {
await visitURL('overview', this.backend);
assert.dom('[data-test-libraries-count]').hasText('1');
});

test('it should transition to create library route on toolbar link click', async function (assert) {
await click('[data-test-toolbar-action="library"]');
assert.true(isURL('libraries/create'), 'Transitions to library create route on toolbar link click');
assert.true(
isURL('libraries/create', this.backend),
'Transitions to library create route on toolbar link click'
);
});

test('it should transition to library details route on list item click', async function (assert) {
await click('[data-test-list-item-link] a');
assert.true(
isURL('libraries/test-library/details/accounts'),
isURL('libraries/test-library/details/accounts', this.backend),
'Transitions to library details accounts route on list item click'
);
assert.dom('[data-test-account-name]').exists({ count: 2 }, 'lists the accounts');
assert.dom('[data-test-checked-out-account]').exists({ count: 1 }, 'lists the checked out accounts');
});

test('it should transition to routes from list item action menu', async function (assert) {
Expand All @@ -44,7 +66,7 @@ module('Acceptance | ldap | libraries', function (hooks) {
await click(`[data-test-${action}]`);
const uri = action === 'details' ? 'details/accounts' : action;
assert.true(
isURL(`libraries/test-library/${uri}`),
isURL(`libraries/test-library/${uri}`, this.backend),
`Transitions to ${action} route on list item action menu click`
);
await click('[data-test-breadcrumb="libraries"] a');
Expand All @@ -55,20 +77,23 @@ module('Acceptance | ldap | libraries', function (hooks) {
await click('[data-test-list-item-link] a');
await click('[data-test-tab="config"]');
assert.true(
isURL('libraries/test-library/details/configuration'),
isURL('libraries/test-library/details/configuration', this.backend),
'Transitions to configuration route on tab click'
);

await click('[data-test-tab="accounts"]');
assert.true(
isURL('libraries/test-library/details/accounts'),
isURL('libraries/test-library/details/accounts', this.backend),
'Transitions to accounts route on tab click'
);
});

test('it should transition to routes from library details toolbar links', async function (assert) {
await click('[data-test-list-item-link] a');
await click('[data-test-edit]');
assert.true(isURL('libraries/test-library/edit'), 'Transitions to credentials route from toolbar link');
assert.true(
isURL('libraries/test-library/edit', this.backend),
'Transitions to credentials route from toolbar link'
);
});
});
62 changes: 44 additions & 18 deletions ui/tests/acceptance/secrets/backend/ldap/overview-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,90 +6,116 @@
import { module, test } from 'qunit';
import { setupApplicationTest } from 'ember-qunit';
import { setupMirage } from 'ember-cli-mirage/test-support';
import { v4 as uuidv4 } from 'uuid';
import ldapMirageScenario from 'vault/mirage/scenarios/ldap';
import ldapHandlers from 'vault/mirage/handlers/ldap';
import authPage from 'vault/tests/pages/auth';
import { click, fillIn, visit } from '@ember/test-helpers';
import { selectChoose } from 'ember-power-select/test-support';
import { isURL, visitURL } from 'vault/tests/helpers/ldap/ldap-helpers';
import { deleteEngineCmd, mountEngineCmd, runCmd } from 'vault/tests/helpers/commands';

module('Acceptance | ldap | overview', function (hooks) {
setupApplicationTest(hooks);
setupMirage(hooks);

hooks.beforeEach(async function () {
ldapHandlers(this.server);
this.backend = `ldap-test-${uuidv4()}`;
this.mountAndConfig = (backend) => {
return runCmd([
mountEngineCmd('ldap', backend),
`write ${backend}/config binddn=foo bindpass=bar url=http://localhost:8208`,
]);
};
return authPage.login();
});

test('it should transition to ldap overview on mount success', async function (assert) {
const backend = 'ldap-test-mount';
await visit('/vault/secrets');
await click('[data-test-enable-engine]');
await click('[data-test-mount-type="ldap"]');
await fillIn('[data-test-input="path"]', 'ldap-test');
await fillIn('[data-test-input="path"]', backend);
await click('[data-test-mount-submit]');
assert.true(isURL('overview'), 'Transitions to ldap overview route on mount success');
assert.true(isURL('overview', backend), 'Transitions to ldap overview route on mount success');
assert.dom('[data-test-header-title]').hasText(backend);
// cleanup mounted engine
await visit('/vault/secrets');
await runCmd(deleteEngineCmd(backend));
});

test('it should transition to routes on tab link click', async function (assert) {
assert.expect(4);
await this.mountAndConfig(this.backend);

await visitURL('overview');
await visitURL('overview', this.backend);

for (const tab of ['roles', 'libraries', 'config', 'overview']) {
await click(`[data-test-tab="${tab}"]`);
const route = tab === 'config' ? 'configuration' : tab;
assert.true(isURL(route), `Transitions to ${route} route on tab link click`);
assert.true(isURL(route, this.backend), `Transitions to ${route} route on tab link click`);
}
});

test('it should transition to configuration route when engine is not configured', async function (assert) {
await visitURL('overview');
await runCmd(mountEngineCmd('ldap', this.backend));
await visitURL('overview', this.backend);
await click('[data-test-config-cta] a');
assert.true(isURL('configure'), 'Transitions to configure route on cta link click');
assert.true(isURL('configure', this.backend), 'Transitions to configure route on cta link click');

await click('[data-test-breadcrumb="ldap-test"] a');
await click(`[data-test-breadcrumb="${this.backend}"] a`);
await click('[data-test-toolbar-action="config"]');
assert.true(isURL('configure'), 'Transitions to configure route on toolbar link click');
assert.true(isURL('configure', this.backend), 'Transitions to configure route on toolbar link click');
});
// including a test for the configuration route here since it is the only one needed
test('it should transition to configuration edit on toolbar link click', async function (assert) {
ldapMirageScenario(this.server);
await visitURL('overview');
await this.mountAndConfig(this.backend);
await visitURL('overview', this.backend);
await click('[data-test-tab="config"]');
await click('[data-test-toolbar-config-action]');
assert.true(isURL('configure'), 'Transitions to configure route on toolbar link click');
assert.true(isURL('configure', this.backend), 'Transitions to configure route on toolbar link click');
});

test('it should transition to create role route on card action link click', async function (assert) {
ldapMirageScenario(this.server);
await visitURL('overview');
await this.mountAndConfig(this.backend);
await visitURL('overview', this.backend);
await click('[data-test-overview-card="Roles"] a');
assert.true(isURL('roles/create'), 'Transitions to role create route on card action link click');
assert.true(
isURL('roles/create', this.backend),
'Transitions to role create route on card action link click'
);
});

test('it should transition to create library route on card action link click', async function (assert) {
ldapMirageScenario(this.server);
await visitURL('overview');
await this.mountAndConfig(this.backend);
await visitURL('overview', this.backend);
await click('[data-test-overview-card="Libraries"] a');
assert.true(isURL('libraries/create'), 'Transitions to library create route on card action link click');
assert.true(
isURL('libraries/create', this.backend),
'Transitions to library create route on card action link click'
);
});

test('it should transition to role credentials route on generate credentials action', async function (assert) {
ldapMirageScenario(this.server);
await visitURL('overview');
await this.mountAndConfig(this.backend);
await visitURL('overview', this.backend);
await selectChoose('.search-select', 'static-role');
await click('[data-test-generate-credential-button]');
assert.true(
isURL('roles/static/static-role/credentials'),
isURL('roles/static/static-role/credentials', this.backend),
'Transitions to role credentials route on generate credentials action'
);

await click('[data-test-breadcrumb="ldap-test"] a');
await click(`[data-test-breadcrumb="${this.backend}"] a`);
await selectChoose('.search-select', 'dynamic-role');
await click('[data-test-generate-credential-button]');
assert.true(
isURL('roles/dynamic/dynamic-role/credentials'),
isURL('roles/dynamic/dynamic-role/credentials', this.backend),
'Transitions to role credentials route on generate credentials action'
);
});
Expand Down
Loading

0 comments on commit 6c09c62

Please sign in to comment.