Skip to content

Commit

Permalink
SIMSBIOHUB-154: System user table updates (#1060)
Browse files Browse the repository at this point in the history
* Add migration to add system role permission tables.
* Remove unused `SYSTEM` user identity source type
* Update user seed
* Update database readme
* Add support for patching verified user information
* Add keycloak user information zod schemas
  • Loading branch information
NickPhura authored Aug 10, 2023
1 parent eb4d16d commit ae70b16
Show file tree
Hide file tree
Showing 41 changed files with 1,987 additions and 549 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.

2 changes: 1 addition & 1 deletion api/src/constants/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export enum SYSTEM_IDENTITY_SOURCE {
IDIR = 'IDIR',
BCEID_BASIC = 'BCEIDBASIC',
BCEID_BUSINESS = 'BCEIDBUSINESS',
SYSTEM = 'SYSTEM'
UNVERIFIED = 'UNVERIFIED'
}

export enum SCHEMAS {
Expand Down
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;
};
17 changes: 12 additions & 5 deletions api/src/database/db.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,16 @@ describe('db', () => {
const sinonSandbox = Sinon.createSandbox();

const mockKeycloakToken = {
preferred_username: 'testguid@idir',
idir_user_guid: 'testguid',
identity_provider: 'idir',
idir_username: 'testuser',
identity_provider: SYSTEM_IDENTITY_SOURCE.IDIR
email_verified: false,
name: 'test user',
preferred_username: 'testguid@idir',
display_name: 'test user',
given_name: 'test',
family_name: 'user',
email: '[email protected]'
};

const queryStub = sinonSandbox.stub().resolves();
Expand Down Expand Up @@ -371,9 +378,9 @@ describe('db', () => {
const DB_USERNAME = process.env.DB_USER_API;

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

0 comments on commit ae70b16

Please sign in to comment.