Skip to content

Commit

Permalink
Merge branch 'dev' into UserRoleTesting
Browse files Browse the repository at this point in the history
  • Loading branch information
curtisupshall authored Sep 21, 2023
2 parents 1300f4c + c62c52b commit 5b1591a
Show file tree
Hide file tree
Showing 136 changed files with 7,682 additions and 3,130 deletions.
15 changes: 15 additions & 0 deletions api/src/__mocks__/db.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,23 @@
import { Request, Response } from 'express';
import { QueryResult } from 'pg';
import sinon from 'sinon';
import * as db from '../database/db';
import { IDBConnection } from '../database/db';

/**
* Registers and returns a mock `IDBConnection` with empty methods.
*
* @param {Partial<IDBConnection>} [config] Initial method overrides
* @return {*} {IDBConnection}
*/
export const registerMockDBConnection = (config?: Partial<IDBConnection>): IDBConnection => {
const mockDBConnection = getMockDBConnection(config);

sinon.stub(db, 'getDBConnection').returns(mockDBConnection);

return mockDBConnection;
};

/**
* Returns a mock `IDBConnection` with empty methods.
*
Expand Down
16 changes: 10 additions & 6 deletions api/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import swaggerUIExperss from 'swagger-ui-express';
import { defaultPoolConfig, initDBPool } from './database/db';
import { ensureHTTPError, HTTPErrorType } from './errors/http-error';
import { rootAPIDoc } from './openapi/root-api-doc';
import { authenticateRequest } from './request-handlers/security/authentication';
import { authenticateRequest, authenticateRequestOptional } from './request-handlers/security/authentication';
import { getLogger } from './utils/logger';

const defaultLog = getLogger('app');
Expand All @@ -26,7 +26,7 @@ const app: express.Express = express();

// Enable CORS
app.use(function (req: Request, res: Response, next: NextFunction) {
defaultLog.info({ label: 'req', message: `${req.method} ${req.url}` });
defaultLog.info(`${req.method} ${req.url}`);

res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With, Content-Type, Authorization, responseType');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE, HEAD');
Expand Down Expand Up @@ -75,9 +75,13 @@ const openAPIFramework = initialize({
'application/x-www-form-urlencoded': express.urlencoded({ limit: MAX_REQ_BODY_SIZE, extended: true })
},
securityHandlers: {
// authenticates the request bearer token, for endpoints that specify `Bearer` security
Bearer: async function (req: any) {
// authenticates the request bearer token, for endpoints that specify `Bearer` security
return authenticateRequest(req);
},
OptionalBearer: async function (req: any) {
// authenticates the request bearer token, if one exists, for endpoints that specify `OptionalBearer` security
return authenticateRequestOptional(req);
}
},
errorTransformer: function (openapiError: object, ajvError: object): object {
Expand All @@ -88,14 +92,14 @@ const openAPIFramework = initialize({
// If `next` is not included express will silently skip calling the `errorMiddleware` entirely.
// eslint-disable-next-line @typescript-eslint/no-unused-vars
errorMiddleware: function (error, req, res, next) {
// Ensure all errors (intentionally thrown or not) are in the same format as specified by the schema
const httpError = ensureHTTPError(error);

if (res.headersSent) {
// response has already been sent
return;
}

// Ensure all errors (intentionally thrown or not) are in the same format as specified by the schema
const httpError = ensureHTTPError(error);

res
.status(httpError.status)
.json({ name: httpError.name, status: httpError.status, message: httpError.message, errors: httpError.errors });
Expand Down
18 changes: 17 additions & 1 deletion api/src/constants/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,26 @@ export enum SYSTEM_IDENTITY_SOURCE {
IDIR = 'IDIR',
BCEID_BASIC = 'BCEIDBASIC',
BCEID_BUSINESS = 'BCEIDBUSINESS',
UNVERIFIED = 'UNVERIFIED'
UNVERIFIED = 'UNVERIFIED',
SYSTEM = 'SYSTEM'
}

export enum SCHEMAS {
API = 'BIOHUB_DAPI_V1',
DATA = 'BIOHUB'
}

/**
* The source system of a DwCA data set submission.
*
* Typically an external system that is participating in BioHub by submitting data to the BioHub Platform Backbone.
*
* Sources are based on the client id of the keycloak service account the participating system uses to authenticate with
* the BioHub Platform Backbone.
*
* @export
* @enum {number}
*/
export enum SOURCE_SYSTEM {
'SIMS-SVC-4464' = 'SIMS-SVC-4464'
}
116 changes: 1 addition & 115 deletions api/src/database/db-utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,6 @@
import { expect } from 'chai';
import { QueryResult } from 'pg';
import sinon from 'sinon';
import { z } from 'zod';
import { SYSTEM_IDENTITY_SOURCE } from '../constants/database';
import {
BceidBasicUserInformation,
BceidBusinessUserInformation,
DatabaseUserInformation,
IdirUserInformation
} from '../utils/keycloak-utils';
import { getGenericizedKeycloakUserInformation, getZodQueryResult } from './db-utils';
import { getZodQueryResult } from './db-utils';

/**
* Enforces that a zod schema satisfies an existing type definition.
Expand Down Expand Up @@ -58,110 +49,5 @@ describe('getZodQueryResult', () => {

// Not a traditional test: will just cause a compile error if the zod schema doesn't satisfy the `QueryResult` type
zodImplements<QueryResult>().with(zodQueryResult.shape);

// Dummy assertion to satisfy linter
expect(true).to.be.true;
});
});

describe('getGenericizedKeycloakUserInformation', () => {
afterEach(() => {
sinon.restore();
});

it('identifies a database user information object and returns null', () => {
const keycloakUserInformation: DatabaseUserInformation = {
database_user_guid: '123456789',
identity_provider: 'database',
username: 'biohub_dapi_v1'
};

const result = getGenericizedKeycloakUserInformation(keycloakUserInformation);

expect(result).to.be.null;
});

it('identifies an idir user information object and returns a genericized object', () => {
const keycloakUserInformation: IdirUserInformation = {
idir_user_guid: '123456789',
identity_provider: 'idir',
idir_username: 'testuser',
email_verified: false,
name: 'test user',
preferred_username: 'testguid@idir',
display_name: 'test user',
given_name: 'test',
family_name: 'user',
email: 'email@email.com'
};

const result = getGenericizedKeycloakUserInformation(keycloakUserInformation);

expect(result).to.eql({
user_guid: keycloakUserInformation.idir_user_guid,
user_identifier: keycloakUserInformation.idir_username,
user_identity_source: SYSTEM_IDENTITY_SOURCE.IDIR,
display_name: keycloakUserInformation.display_name,
email: keycloakUserInformation.email,
given_name: keycloakUserInformation.given_name,
family_name: keycloakUserInformation.family_name
});
});

it('identifies a bceid business user information object and returns a genericized object', () => {
const keycloakUserInformation: BceidBusinessUserInformation = {
bceid_business_guid: '1122334455',
bceid_business_name: 'Business Name',
bceid_user_guid: '123456789',
identity_provider: 'bceidbusiness',
bceid_username: 'tname',
name: 'Test Name',
preferred_username: '123456789@bceidbusiness',
display_name: 'Test Name',
email: 'email@email.com',
email_verified: false,
given_name: 'Test',
family_name: ''
};

const result = getGenericizedKeycloakUserInformation(keycloakUserInformation);

expect(result).to.eql({
user_guid: keycloakUserInformation.bceid_user_guid,
user_identifier: keycloakUserInformation.bceid_username,
user_identity_source: SYSTEM_IDENTITY_SOURCE.BCEID_BUSINESS,
display_name: keycloakUserInformation.display_name,
email: keycloakUserInformation.email,
given_name: keycloakUserInformation.given_name,
family_name: keycloakUserInformation.family_name,
agency: keycloakUserInformation.bceid_business_name
});
});

it('identifies a bceid basic user information object and returns a genericized object', () => {
const keycloakUserInformation: BceidBasicUserInformation = {
bceid_user_guid: '123456789',
identity_provider: 'bceidbasic',
bceid_username: 'tname',
name: 'Test Name',
preferred_username: '123456789@bceidbasic',
display_name: 'Test Name',
email: 'email@email.com',
email_verified: false,
given_name: 'Test',
family_name: ''
};

const result = getGenericizedKeycloakUserInformation(keycloakUserInformation);

expect(result).to.eql({
user_guid: keycloakUserInformation.bceid_user_guid,
user_identifier: keycloakUserInformation.bceid_username,
user_identity_source: SYSTEM_IDENTITY_SOURCE.BCEID_BASIC,
display_name: keycloakUserInformation.display_name,
email: keycloakUserInformation.email,
given_name: keycloakUserInformation.given_name,
family_name: keycloakUserInformation.family_name
});
});
});
77 changes: 0 additions & 77 deletions api/src/database/db-utils.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,5 @@
import { z } from 'zod';
import { SYSTEM_IDENTITY_SOURCE } from '../constants/database';
import { ApiExecuteSQLError } from '../errors/api-error';
import {
isBceidBusinessUserInformation,
isDatabaseUserInformation,
isIdirUserInformation,
KeycloakUserInformation
} from '../utils/keycloak-utils';

/**
* A type for a set of generic keycloak user information properties.
*/
type GenericizedKeycloakUserInformation = {
user_guid: string;
user_identifier: string;
user_identity_source: SYSTEM_IDENTITY_SOURCE;
display_name: string;
email: string;
given_name: string;
family_name: string;
agency?: string;
};

/**
* An asynchronous wrapper function that will catch any exceptions thrown by the wrapped function
Expand Down Expand Up @@ -102,59 +81,3 @@ export const getZodQueryResult = <T extends z.Schema>(zodQueryResultRow: T) =>
})
)
});

/**
* Converts a type specific keycloak user information object with type specific properties into a new object with
* generic properties.
*
* @param {KeycloakUserInformation} keycloakUserInformation
* @return {*} {(GenericizedKeycloakUserInformation | null)}
*/
export const getGenericizedKeycloakUserInformation = (
keycloakUserInformation: KeycloakUserInformation
): GenericizedKeycloakUserInformation | null => {
let data: GenericizedKeycloakUserInformation | null;

if (isDatabaseUserInformation(keycloakUserInformation)) {
// Don't patch internal database user records
return null;
}

// We don't yet know at this point what kind of token was used (idir vs bceid basic, etc).
// Determine which type it is, and parse the information into a generic structure that is supported by the
// database patch function
if (isIdirUserInformation(keycloakUserInformation)) {
data = {
user_guid: keycloakUserInformation.idir_user_guid,
user_identifier: keycloakUserInformation.idir_username,
user_identity_source: SYSTEM_IDENTITY_SOURCE.IDIR,
display_name: keycloakUserInformation.display_name,
email: keycloakUserInformation.email,
given_name: keycloakUserInformation.given_name,
family_name: keycloakUserInformation.family_name
};
} else if (isBceidBusinessUserInformation(keycloakUserInformation)) {
data = {
user_guid: keycloakUserInformation.bceid_user_guid,
user_identifier: keycloakUserInformation.bceid_username,
user_identity_source: SYSTEM_IDENTITY_SOURCE.BCEID_BUSINESS,
display_name: keycloakUserInformation.display_name,
email: keycloakUserInformation.email,
given_name: keycloakUserInformation.given_name,
family_name: keycloakUserInformation.family_name,
agency: keycloakUserInformation.bceid_business_name
};
} else {
data = {
user_guid: keycloakUserInformation.bceid_user_guid,
user_identifier: keycloakUserInformation.bceid_username,
user_identity_source: SYSTEM_IDENTITY_SOURCE.BCEID_BASIC,
display_name: keycloakUserInformation.display_name,
email: keycloakUserInformation.email,
given_name: keycloakUserInformation.given_name,
family_name: keycloakUserInformation.family_name
};
}

return data;
};
7 changes: 2 additions & 5 deletions api/src/database/db.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -375,12 +375,9 @@ describe('db', () => {

getAPIUserDBConnection();

const DB_USERNAME = process.env.DB_USER_API;

expect(getDBConnectionStub).to.have.been.calledWith({
database_user_guid: DB_USERNAME,
identity_provider: SYSTEM_IDENTITY_SOURCE.DATABASE.toLowerCase(),
username: DB_USERNAME
preferred_username: `undefined@${SYSTEM_IDENTITY_SOURCE.DATABASE}`,
identity_provider: SYSTEM_IDENTITY_SOURCE.DATABASE
});
});
});
Expand Down
Loading

0 comments on commit 5b1591a

Please sign in to comment.