Skip to content

Commit

Permalink
SIMSBIOHUB-308: System API User Add Bug (#1127)
Browse files Browse the repository at this point in the history
* add back removed functionality

* Fixed 'Failed to identify authenticated user' test

* Added back numeric type parser to db

* Fixed remaining tests

---------

Co-authored-by: Curtis Upshall <[email protected]>
Co-authored-by: Nick Phura <[email protected]>
Co-authored-by: Nick Phura <[email protected]>
  • Loading branch information
4 people authored Oct 19, 2023
1 parent 2960d9b commit 69f4781
Show file tree
Hide file tree
Showing 12 changed files with 1,903 additions and 921 deletions.
12 changes: 6 additions & 6 deletions api/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

116 changes: 115 additions & 1 deletion api/src/database/db-utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
import { expect } from 'chai';
import { QueryResult } from 'pg';
import sinon from 'sinon';
import { z } from 'zod';
import { getZodQueryResult } from './db-utils';
import { SYSTEM_IDENTITY_SOURCE } from '../constants/database';
import {
BceidBasicUserInformation,
BceidBusinessUserInformation,
DatabaseUserInformation,
IdirUserInformation
} from '../utils/keycloak-utils';
import { getGenericizedKeycloakUserInformation, getZodQueryResult } from './db-utils';

/**
* Enforces that a zod schema satisfies an existing type definition.
Expand Down Expand Up @@ -49,5 +58,110 @@ 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 protected]'
};

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 protected]',
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 protected]',
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: 77 additions & 0 deletions api/src/database/db-utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,26 @@
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 @@ -81,3 +102,59 @@ 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;
};
44 changes: 36 additions & 8 deletions api/src/database/db.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,19 @@ import { describe } from 'mocha';
import * as pg from 'pg';
import Sinon from 'sinon';
import SQL from 'sql-template-strings';
import { SYSTEM_IDENTITY_SOURCE } from '../constants/database';
import { SOURCE_SYSTEM, SYSTEM_IDENTITY_SOURCE } from '../constants/database';
import { ApiExecuteSQLError } from '../errors/api-error';
import { HTTPError } from '../errors/http-error';
import * as db from './db';
import { getAPIUserDBConnection, getDBConnection, getDBPool, getKnex, IDBConnection, initDBPool } from './db';
import {
getAPIUserDBConnection,
getDBConnection,
getDBPool,
getKnex,
getServiceClientDBConnection,
IDBConnection,
initDBPool
} from './db';

describe('db', () => {
beforeEach(() => {
Expand Down Expand Up @@ -375,18 +383,38 @@ describe('db', () => {

getAPIUserDBConnection();

const DB_USERNAME = process.env.DB_USER_API;
expect(getDBConnectionStub).to.have.been.calledWith({
preferred_username: `undefined@${SYSTEM_IDENTITY_SOURCE.DATABASE}`,
identity_provider: SYSTEM_IDENTITY_SOURCE.DATABASE
database_user_guid: DB_USERNAME,
identity_provider: SYSTEM_IDENTITY_SOURCE.DATABASE.toLowerCase(),
username: DB_USERNAME
});
});
});

describe('getKnexQueryBuilder', () => {
it('returns a Knex query builder', () => {
const queryBuilder = db.getKnexQueryBuilder();
describe('getServiceClientDBConnection', () => {
beforeEach(() => {
process.env.DB_USER_API = 'example_db_username';
});

afterEach(() => {
Sinon.restore();
});

it('calls getDBConnection for the biohub_api user', () => {
const getDBConnectionStub = Sinon.stub(db, 'getDBConnection').returns(
('stubbed DBConnection object' as unknown) as IDBConnection
);

expect(queryBuilder.client.config).to.eql({ client: db.DB_CLIENT });
const sourceSystem = SOURCE_SYSTEM['SIMS-SVC-4464'];

getServiceClientDBConnection(sourceSystem);

expect(getDBConnectionStub).to.have.been.calledWith({
database_user_guid: sourceSystem,
identity_provider: SYSTEM_IDENTITY_SOURCE.SYSTEM.toLowerCase(),
username: `service-account-${sourceSystem}`
});
});
});

Expand Down
Loading

0 comments on commit 69f4781

Please sign in to comment.