claire bontempo 6e3ae793f5
UI: LDAP Hierarchical Library names (#29293)
* refactor crumbs

* add subdirectory library route and hierarchical nav

* update library breadcrumbs;

* fix role popup menus

* add getter to library model for full path

* cleanup model getters

* add changelog

* add bug fix note

* add transition after deleting

* fix function definition

* update adapter test

* add test coverage

* fix crumb typo
2025-01-07 12:54:36 -06:00

141 lines
5.4 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) => {
const dbKey = type ? 'ldapRoles' : 'ldapLibraries';
const query = type ? { type, name: `admin/child-${type}-role` } : { name: 'admin/test-library' };
if (req.queryParams.list) {
// the mirage database has setup all hierarchical names to be prefixed with "admin/"
// while passing a query with specific name is not flexible, for simplicity
// we only seeded the mirage db with one hierarchical resource for each role and a library
return listRecords(schema, dbKey, query);
}
// otherwise we want to view details for a specific resource
return getRecord(schema, req, dbKey);
};
// 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) => listOrGetRecord(schema, req));
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,
};
});
}