claire bontempo 30d4e21e88
UI: LDAP Hierarchical roles (#28824)
* remove named path adapter extension, add subdirectory query logic to adapter

* add subdirectory route and logic to page::roles component

* fix overview page search select

* breadcrumbs

* update tests and mirage

* revert ss changes

* oops

* cleanup adapter, add _ for private methods

* add acceptance test

* remove type

* add changelog

* add ldap breadcrumb test

* VAULT-31905 link jira

* update breadcrumbs in Edit route

* rename type interfaces
2024-11-06 00:52:29 +00:00

139 lines
5.2 KiB
JavaScript

/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import { Response } from 'miragejs';
export default function (server) {
const query = (req) => {
const { name, backend } = req.params;
return name ? { name } : { backend };
};
const getRecord = (schema, req, dbKey) => {
const record = schema.db[dbKey].findBy(query(req));
if (record) {
delete record.id;
delete record.name;
delete record.backend;
delete record.type;
return { data: record };
}
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 }) => {
if (name.includes('/')) {
const [parent, child] = name.split('/');
// query.name is only passed by listOrGetRecord and means we want to list children of admin/
// otherwise this is the request for all roles in an engine so we return the top-level paths
return query?.name ? child : `${parent}/`;
}
return name;
});
return {
data: { keys },
};
};
const listOrGetRecord = (schema, req, type) => {
// if the param name is admin, we want to LIST admin/ roles
if (req.queryParams.list) {
// passing a query with specific name is not flexible
// but we only seeded the mirage db with one hierarchical role for each type
return listRecords(schema, 'ldapRoles', { type, name: `admin/child-${type}-role` });
}
// otherwise we want to view details for a specific role
return getRecord(schema, req, 'ldapRoles', type);
};
// config
server.post('/:backend/config', (schema, req) => createOrUpdateRecord(schema, req, 'ldapConfigs'));
server.get('/:backend/config', (schema, req) => getRecord(schema, req, 'ldapConfigs'));
// roles
server.post('/:backend/static-role/:name', (schema, req) => createOrUpdateRecord(schema, req, 'ldapRoles'));
server.post('/:backend/role/:name', (schema, req) => createOrUpdateRecord(schema, req, 'ldapRoles'));
// if the role is hierarchical the name ends in a forward slash so we make a list request
server.get('/:backend/static-role/*name', (schema, req) => listOrGetRecord(schema, req, 'static'));
server.get('/:backend/role/*name', (schema, req) => listOrGetRecord(schema, req, 'dynamic'));
server.get('/:backend/static-role', (schema) => listRecords(schema, 'ldapRoles', { type: 'static' }));
server.get('/:backend/role', (schema) => listRecords(schema, 'ldapRoles', { type: 'dynamic' }));
// role credentials
server.get('/:backend/static-cred/:name', (schema) => ({
data: schema.db.ldapCredentials.firstOrCreate({ type: 'static' }),
}));
server.get('/:backend/creds/:name', (schema) => ({
data: schema.db.ldapCredentials.firstOrCreate({ type: 'dynamic' }),
}));
// libraries
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', (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,
};
});
}