Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BHBC-993: Review access requests + misc user/role management endpoints + misc cleanup #255

Merged
merged 15 commits into from
Apr 26, 2021
Merged
12 changes: 10 additions & 2 deletions api/src/models/user.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ describe('UserObject', () => {
describe('valid values provided, no roles', () => {
let data: UserObject;

const userObject = { id: 1, user_identifier: 'test name', role_names: [] };
const userObject = { id: 1, user_identifier: 'test name', role_ids: [], role_names: [] };

before(() => {
data = new UserObject(userObject);
Expand All @@ -40,6 +40,10 @@ describe('UserObject', () => {
expect(data.user_identifier).to.equal('test name');
});

it('sets role_ids', function () {
expect(data.role_ids).to.eql([]);
});

it('sets role_names', function () {
expect(data.role_names).to.eql([]);
});
Expand All @@ -48,7 +52,7 @@ describe('UserObject', () => {
describe('valid values provided', () => {
let data: UserObject;

const userObject = { id: 1, user_identifier: 'test name', role_names: ['role 1', 'role 2'] };
const userObject = { id: 1, user_identifier: 'test name', role_ids: [1, 2], role_names: ['role 1', 'role 2'] };

before(() => {
data = new UserObject(userObject);
Expand All @@ -62,6 +66,10 @@ describe('UserObject', () => {
expect(data.user_identifier).to.equal('test name');
});

it('sets role_ids', function () {
expect(data.role_ids).to.eql([1, 2]);
});

it('sets role_names', function () {
expect(data.role_names).to.eql(['role 1', 'role 2']);
});
Expand Down
2 changes: 2 additions & 0 deletions api/src/models/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ const defaultLog = getLogger('models/user');
export class UserObject {
id: number;
user_identifier: string;
role_ids: number[];
role_names: string[];

constructor(obj?: any) {
defaultLog.debug({ label: 'UserObject', message: 'params', obj });

this.id = obj?.id || null;
this.user_identifier = obj?.user_identifier || null;
this.role_ids = (obj?.role_ids?.length && obj.role_ids) || [];
this.role_names = (obj?.role_names?.length && obj.role_names) || [];
}
}
160 changes: 160 additions & 0 deletions api/src/paths/access-request.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
'use strict';

import { RequestHandler } from 'express';
import { Operation } from 'express-openapi';
import { WRITE_ROLES } from '../constants/roles';
import { getDBConnection } from '../database/db';
import { HTTP400, HTTP500 } from '../errors/CustomError';
import { UserObject } from '../models/user';
import { getUserByUserIdentifierSQL } from '../queries/users/user-queries';
import { getLogger } from '../utils/logger';
import { logRequest } from '../utils/path-utils';
import { updateAdministrativeActivity } from './administrative-activity';
import { addSystemUser } from './user';
import { addSystemRoles } from './user/{userId}/system-roles';

const defaultLog = getLogger('paths/access-request');

export const PUT: Operation = [logRequest('paths/access-request', 'POST'), updateAccessRequest()];

PUT.apiDoc = {
description: "Update a user's system access request and add any specified system roles to the user.",
tags: ['user'],
security: [
{
Bearer: WRITE_ROLES
}
],
requestBody: {
content: {
'application/json': {
schema: {
type: 'object',
required: ['userIdentifier', 'identitySource', 'requestId', 'requestStatusTypeId'],
properties: {
userIdentifier: {
type: 'string',
description: 'The user identifier for the user.'
},
identitySource: {
type: 'string',
description: 'The identity source for the user.'
},
requestId: {
type: 'number',
description: 'The id of the access request to update.'
},
requestStatusTypeId: {
type: 'number',
description: 'The status type id to set for the access request.'
},
roleIds: {
type: 'array',
items: {
type: 'number'
},
description:
'An array of role ids to add, if the access-request was approved. Ignored if the access-request was denied.'
}
}
}
}
}
},
responses: {
200: {
description: 'Add system user roles to user OK.'
},
400: {
$ref: '#/components/responses/400'
},
401: {
$ref: '#/components/responses/401'
},
403: {
$ref: '#/components/responses/401'
},
500: {
$ref: '#/components/responses/500'
},
default: {
$ref: '#/components/responses/default'
}
}
};

function updateAccessRequest(): RequestHandler {
return async (req, res) => {
defaultLog.debug({ label: 'updateAccessRequest', message: 'params', req_body: req.body });

const userIdentifier = req.body?.userIdentifier || null;
const identitySource = req.body?.identitySource || null;
const administrativeActivityId = Number(req.body?.requestId) || null;
const administrativeActivityStatusTypeId = Number(req.body?.requestStatusTypeId) || null;
const roleIds: number[] = req.body?.roleIds || [];

if (!userIdentifier) {
throw new HTTP400('Missing required body param: userIdentifier');
}

if (!identitySource) {
throw new HTTP400('Missing required body param: identitySource');
}

if (!administrativeActivityId) {
throw new HTTP400('Missing required body param: requestId');
}

if (!administrativeActivityStatusTypeId) {
NickPhura marked this conversation as resolved.
Show resolved Hide resolved
throw new HTTP400('Missing required body param: requestStatusTypeId');
}

const getUserSQLStatement = getUserByUserIdentifierSQL(userIdentifier);

if (!getUserSQLStatement) {
throw new HTTP400('Failed to build SQL get statement');
}

const connection = getDBConnection(req['keycloak_token']);

try {
await connection.open();

// Get the user by their user identifier (user may not exist)
const getUserResponse = await connection.query(getUserSQLStatement.text, getUserSQLStatement.values);

let userData = (getUserResponse && getUserResponse.rowCount && getUserResponse.rows[0]) || null;

if (!userData) {
// Found no existing user, add them
userData = await addSystemUser(userIdentifier, identitySource, connection);
}

const userObject = new UserObject(userData);

if (!userObject) {
throw new HTTP500('Failed to get or add system user');
}

// Filter out any system roles that have already been added to the user
const rolesToAdd = roleIds.filter((roleId) => !userObject.role_ids.includes(roleId));

if (rolesToAdd?.length) {
// Add any missing roles (if any)
await addSystemRoles(userObject.id, roleIds, connection);
}

// Update the access request record status
await updateAdministrativeActivity(administrativeActivityId, administrativeActivityStatusTypeId, connection);

await connection.commit();

return res.status(200).send();
} catch (error) {
defaultLog.debug({ label: 'updateAccessRequest', message: 'error', error });
throw error;
} finally {
connection.release();
}
};
}
26 changes: 22 additions & 4 deletions api/src/paths/administrative-activities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const GET: Operation = [logRequest('paths/administrative-activity', 'GET'

GET.apiDoc = {
description: 'Get a list of administrative activities based on the provided criteria.',
tags: ['project'],
tags: ['admin'],
security: [
{
Bearer: READ_ROLES
Expand All @@ -40,7 +40,24 @@ GET.apiDoc = {
type: 'object',
properties: {
id: {
type: 'number'
type: 'number',
description: 'Administrative activity row ID'
},
type: {
type: 'number',
description: 'Administrative activity type ID'
},
type_name: {
type: 'string',
description: 'Administrative activity type name'
},
status: {
type: 'number',
description: 'Administrative activity status type ID'
},
status_name: {
type: 'string',
description: 'Administrative activity status type name'
},
description: {
type: 'string'
Expand All @@ -50,6 +67,7 @@ GET.apiDoc = {
},
data: {
type: 'object',
description: 'JSON data blob containing additional information about the activity record',
properties: {
// Don't specify as this is a JSON blob column
}
Expand Down Expand Up @@ -82,7 +100,7 @@ GET.apiDoc = {
};

/**
* Get all projects.
* Get all administrative activities for the specified type, or all if no type is provided.
*
* @returns {RequestHandler}
*/
Expand All @@ -105,7 +123,7 @@ function getAdministrativeActivities(): RequestHandler {

await connection.commit();

const result = (response && response.rows && response.rows[0]) || [];
const result = (response && response.rowCount && response.rows) || [];

return res.status(200).json(result);
} catch (error) {
Expand Down
Loading