diff --git a/api/package-lock.json b/api/package-lock.json index 35ff7ff65d..0cab9949f9 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -2965,7 +2965,7 @@ "encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" }, "end-of-stream": { "version": "1.4.4", @@ -3097,7 +3097,7 @@ "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" }, "escape-string-regexp": { "version": "1.0.5", @@ -3371,7 +3371,7 @@ "etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" }, "events": { "version": "1.1.1", @@ -4003,7 +4003,7 @@ "fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" }, "fromentries": { "version": "1.3.2", @@ -5030,7 +5030,7 @@ "is-dir": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-dir/-/is-dir-1.0.0.tgz", - "integrity": "sha512-vLwCNpTNkFC5k7SBRxPubhOCryeulkOsSkjbGyZ8eOzZmzMS+hSEO/Kn9ZOVhFNAlRZTFc4ZKql48hESuYUPIQ==" + "integrity": "sha1-QdN/SV/MrMBaR3jWboMCTCkro/8=" }, "is-extendable": { "version": "0.1.1", @@ -9094,7 +9094,7 @@ "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" }, "unset-value": { "version": "1.0.0", diff --git a/api/src/database/db-utils.test.ts b/api/src/database/db-utils.test.ts index a73408bf4c..65a8f12d75 100644 --- a/api/src/database/db-utils.test.ts +++ b/api/src/database/db-utils.test.ts @@ -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. @@ -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().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 + }); }); }); diff --git a/api/src/database/db-utils.ts b/api/src/database/db-utils.ts index d010e8b70d..f6f1030d4e 100644 --- a/api/src/database/db-utils.ts +++ b/api/src/database/db-utils.ts @@ -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 @@ -81,3 +102,59 @@ export const getZodQueryResult = (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; +}; diff --git a/api/src/database/db.test.ts b/api/src/database/db.test.ts index 8199a8282a..12412602c8 100644 --- a/api/src/database/db.test.ts +++ b/api/src/database/db.test.ts @@ -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(() => { @@ -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}` + }); }); }); diff --git a/api/src/database/db.ts b/api/src/database/db.ts index 8b0dc8a200..27b5d38a54 100644 --- a/api/src/database/db.ts +++ b/api/src/database/db.ts @@ -1,34 +1,45 @@ import knex, { Knex } from 'knex'; import * as pg from 'pg'; -import { SQLStatement } from 'sql-template-strings'; +import SQL, { SQLStatement } from 'sql-template-strings'; import { z } from 'zod'; import { SOURCE_SYSTEM, SYSTEM_IDENTITY_SOURCE } from '../constants/database'; import { ApiExecuteSQLError, ApiGeneralError } from '../errors/api-error'; -import * as UserQueries from '../queries/database/user-context-queries'; -import { getUserGuid, getUserIdentitySource } from '../utils/keycloak-utils'; +import { + DatabaseUserInformation, + getKeycloakUserInformationFromKeycloakToken, + getUserGuid, + getUserIdentitySource, + KeycloakUserInformation, + ServiceClientUserInformation +} from '../utils/keycloak-utils'; import { getLogger } from '../utils/logger'; -import { asyncErrorWrapper, getZodQueryResult, syncErrorWrapper } from './db-utils'; - -export const DB_CLIENT = 'pg'; +import { + asyncErrorWrapper, + getGenericizedKeycloakUserInformation, + getZodQueryResult, + syncErrorWrapper +} from './db-utils'; const defaultLog = getLogger('database/db'); -const DB_HOST = process.env.DB_HOST; -const DB_PORT = Number(process.env.DB_PORT); -const DB_USERNAME = process.env.DB_USER_API; -const DB_PASSWORD = process.env.DB_USER_API_PASS; -const DB_DATABASE = process.env.DB_DATABASE; +const getDbHost = () => process.env.DB_HOST; +const getDbPort = () => Number(process.env.DB_PORT); +const getDbUsername = () => process.env.DB_USER_API; +const getDbPassword = () => process.env.DB_USER_API_PASS; +const getDbDatabase = () => process.env.DB_DATABASE; const DB_POOL_SIZE: number = Number(process.env.DB_POOL_SIZE) || 20; const DB_CONNECTION_TIMEOUT: number = Number(process.env.DB_CONNECTION_TIMEOUT) || 0; const DB_IDLE_TIMEOUT: number = Number(process.env.DB_IDLE_TIMEOUT) || 10000; +export const DB_CLIENT = 'pg'; + export const defaultPoolConfig: pg.PoolConfig = { - user: DB_USERNAME, - password: DB_PASSWORD, - database: DB_DATABASE, - port: DB_PORT, - host: DB_HOST, + user: getDbUsername(), + password: getDbPassword(), + database: getDbDatabase(), + port: getDbPort(), + host: getDbHost(), max: DB_POOL_SIZE, connectionTimeoutMillis: DB_CONNECTION_TIMEOUT, idleTimeoutMillis: DB_IDLE_TIMEOUT @@ -129,15 +140,21 @@ export interface IDBConnection { /** * Performs a query against this connection, returning the results. * - * @param {SQLStatement} sqlStatement SQL statement object + * @param {string} text SQL text + * @param {any[]} [values] SQL values array (optional) * @return {*} {(Promise>)} * @throws If the connection is not open. + * @deprecated Prefer using `.sql` (pass entire statement object) or `.knex` (pass knex query builder object) * @memberof IDBConnection */ query: (text: string, values?: any[]) => Promise>; /** * Performs a query against this connection, returning the results. * + * @example + * // Create a basic SQLStatement object + * const sqlStatement = SQL`select * from table where name = ${name}`; + * * @param {SQLStatement} sqlStatement SQL statement object * @param {z.Schema} zodSchema An optional zod schema * @return {*} {(Promise>)} @@ -151,6 +168,8 @@ export interface IDBConnection { /** * Performs a query against this connection, returning the results. * + * @see {@link getKnex} to get a knex instance. + * * @param {Knex.QueryBuilder} queryBuilder Knex query builder object * @param {z.Schema} zodSchema An optional zod schema * @return {*} {(Promise>)} @@ -199,12 +218,9 @@ export const getDBConnection = function (keycloakToken: object): IDBConnection { } let _client: pg.PoolClient; - let _isOpen = false; let _isReleased = false; - let _systemUserId: number | null = null; - const _token = keycloakToken; /** @@ -226,12 +242,10 @@ export const getDBConnection = function (keycloakToken: object): IDBConnection { } _client = await pool.connect(); - _isOpen = true; _isReleased = false; await _setUserContext(); - await _client.query('BEGIN'); }; @@ -250,7 +264,6 @@ export const getDBConnection = function (keycloakToken: object): IDBConnection { } _client.release(); - _isOpen = false; _isReleased = true; }; @@ -360,36 +373,87 @@ export const getDBConnection = function (keycloakToken: object): IDBConnection { /** * Set the user context. * - * Sets the _systemUserId if successful. + * Sets the `_systemUserId` if successful. + * + * @return {*} {Promise} */ - const _setUserContext = async () => { - const userGuid = getUserGuid(_token); - - const userIdentitySource = getUserIdentitySource(_token); + const _setUserContext = async (): Promise => { + const keycloakUserInformation = getKeycloakUserInformationFromKeycloakToken(_token); - if (!userGuid || !userIdentitySource) { + if (!keycloakUserInformation) { throw new ApiGeneralError('Failed to identify authenticated user'); } - // Set the user context for all queries made using this connection - const setSystemUserContextSQLStatement = UserQueries.setSystemUserContextSQL(userGuid, userIdentitySource); + defaultLog.debug({ label: '_setUserContext', keycloakUserInformation }); - if (!setSystemUserContextSQLStatement) { - throw new ApiExecuteSQLError('Failed to build SQL user context statement'); - } + // Update the logged in user with their latest information from Keycloak (if it has changed) + await _updateSystemUserInformation(keycloakUserInformation); try { - const response = await _client.query( - setSystemUserContextSQLStatement.text, - setSystemUserContextSQLStatement.values + // Set the user context in the database, so database queries are aware of the calling user when writing to audit + // tables, etc. + _systemUserId = await _setSystemUserContext( + getUserGuid(keycloakUserInformation), + getUserIdentitySource(keycloakUserInformation) ); - - _systemUserId = response?.rows?.[0].api_set_context; } catch (error) { throw new ApiExecuteSQLError('Failed to set user context', [error as object]); } }; + /** + * Update a system user's record with the latest information from a verified Keycloak token. + * + * Note: Does nothing if the user is an internal database user. + * + * @param {KeycloakUserInformation} keycloakUserInformation + * @return {*} {Promise} + */ + const _updateSystemUserInformation = async (keycloakUserInformation: KeycloakUserInformation): Promise => { + const data = getGenericizedKeycloakUserInformation(keycloakUserInformation); + + if (!data) { + return; + } + + const patchSystemUserSQLStatement = SQL` + SELECT api_patch_system_user( + ${data.user_guid}, + ${data.user_identifier}, + ${data.user_identity_source}, + ${data.email}, + ${data.display_name}, + ${data.given_name || null}, + ${data.family_name || null}, + ${data.agency || null} + ) + `; + + await _client.query(patchSystemUserSQLStatement.text, patchSystemUserSQLStatement.values); + }; + + /** + * Set the user context for all queries made using this connection. + * + * This is necessary in order for the database audit triggers to function properly. + * + * @param {string} userGuid + * @param {SYSTEM_IDENTITY_SOURCE} userIdentitySource + * @return {*} + */ + const _setSystemUserContext = async (userGuid: string, userIdentitySource: SYSTEM_IDENTITY_SOURCE) => { + const setSystemUserContextSQLStatement = SQL` + SELECT api_set_context(${userGuid}, ${userIdentitySource}); + `; + + const response = await _client.query( + setSystemUserContextSQLStatement.text, + setSystemUserContextSQLStatement.values + ); + + return response?.rows?.[0].api_set_context; + }; + return { open: asyncErrorWrapper(_open), query: asyncErrorWrapper(_query), @@ -403,48 +467,42 @@ export const getDBConnection = function (keycloakToken: object): IDBConnection { }; /** - * Returns an IDBConnection where the system user context is set to the API's system user. + * Returns an IDBConnection where the system user context is set to a service client user. * - * Note: Use of this should be limited to requests that are impossible to initiated under a real user context (ie: when - * an unknown user is requesting access to BioHub and therefore does not yet have a user in the system). + * Note: Spoofs a keycloak token in order to leverage the same keycloak/database code that would normally be + * called when queries are executed on behalf of a real human user. * + * Future enhancement: Service client users do have real keycloak tokens, and so this/related code could be enhanced to + * process a service client token in a similar fashion to a regular token, rather than spoofing the token. + * + * @param {SOURCE_SYSTEM} sourceSystem * @return {*} {IDBConnection} */ -export const getAPIUserDBConnection = (): IDBConnection => { +export const getServiceClientDBConnection = (sourceSystem: SOURCE_SYSTEM): IDBConnection => { return getDBConnection({ - preferred_username: `${DB_USERNAME}@${SYSTEM_IDENTITY_SOURCE.DATABASE}`, - identity_provider: SYSTEM_IDENTITY_SOURCE.DATABASE - }); + database_user_guid: sourceSystem, + identity_provider: SYSTEM_IDENTITY_SOURCE.SYSTEM.toLowerCase(), + username: `service-account-${sourceSystem}` + } as ServiceClientUserInformation); }; /** - * Returns an IDBConnection where the system user context is set to a service client user. + * Returns an IDBConnection where the system user context is set to the API's system user. * - * Note: Use of this should be limited to requests that are sent by an external system that is participating in BioHub - * by submitting data to the BioHub Platform Backbone. + * Note: Spoofs a keycloak token in order to leverage the same keycloak/database code that would normally be + * called when queries are executed on behalf of a real human user. + * + * Note: Use of this should be limited to requests that are impossible to initiated under a real user context (ie: when + * an unknown user is requesting access to BioHub and therefore does not yet have a user in the system). * - * @param {SOURCE_SYSTEM} sourceSystem * @return {*} {IDBConnection} */ -export const getServiceAccountDBConnection = (sourceSystem: SOURCE_SYSTEM): IDBConnection => { +export const getAPIUserDBConnection = (): IDBConnection => { return getDBConnection({ - preferred_username: `${sourceSystem}@${SYSTEM_IDENTITY_SOURCE.SYSTEM}`, - identity_provider: SYSTEM_IDENTITY_SOURCE.SYSTEM - }); -}; - -/** - * Get a Knex queryBuilder instance. - * - * @template TRecord - * @template TResult - * @return {*} {Knex.QueryBuilder} - */ -export const getKnexQueryBuilder = < - TRecord extends Record = any, - TResult = Record[] ->(): Knex.QueryBuilder => { - return knex({ client: DB_CLIENT }).queryBuilder(); + database_user_guid: getDbUsername(), + identity_provider: SYSTEM_IDENTITY_SOURCE.DATABASE.toLowerCase(), + username: getDbUsername() + } as DatabaseUserInformation); }; /** diff --git a/api/src/paths/user/add.ts b/api/src/paths/user/add.ts index 29c9692812..67cb6ab09d 100644 --- a/api/src/paths/user/add.ts +++ b/api/src/paths/user/add.ts @@ -2,7 +2,7 @@ import { RequestHandler } from 'express'; import { Operation } from 'express-openapi'; import { SOURCE_SYSTEM, SYSTEM_IDENTITY_SOURCE } from '../../constants/database'; import { SYSTEM_ROLE } from '../../constants/roles'; -import { getDBConnection, getServiceAccountDBConnection } from '../../database/db'; +import { getDBConnection, getServiceClientDBConnection } from '../../database/db'; import { authorizeRequestHandler } from '../../request-handlers/security/authorization'; import { UserService } from '../../services/user-service'; import { getKeycloakSource } from '../../utils/keycloak-utils'; @@ -134,7 +134,7 @@ export function addSystemRoleUser(): RequestHandler { const sourceSystem = getKeycloakSource(req['keycloak_token']); const connection = sourceSystem - ? getServiceAccountDBConnection(sourceSystem) + ? getServiceClientDBConnection(sourceSystem) : getDBConnection(req['keycloak_token']); try { diff --git a/api/src/services/authorization-service.test.ts b/api/src/services/authorization-service.test.ts index 6f83dae177..70ed6f091a 100644 --- a/api/src/services/authorization-service.test.ts +++ b/api/src/services/authorization-service.test.ts @@ -21,831 +21,877 @@ import { ProjectParticipationService } from './project-participation-service'; chai.use(sinonChai); -describe('executeAuthorizationScheme', function () { - afterEach(() => { - sinon.restore(); - }); +describe('AuthorizationService', () => { + describe('executeAuthorizationScheme', function () { + afterEach(() => { + sinon.restore(); + }); - it('returns false if any AND authorizationScheme rules return false', async function () { - const mockAuthorizationScheme = ({ and: [] } as unknown) as AuthorizationScheme; - const mockDBConnection = getMockDBConnection(); + it('returns false if any AND authorizationScheme rules return false', async function () { + const mockAuthorizationScheme = ({ and: [] } as unknown) as AuthorizationScheme; + const mockDBConnection = getMockDBConnection(); - sinon.stub(AuthorizationService.prototype, 'executeAuthorizeConfig').resolves([true, false, true]); + sinon.stub(AuthorizationService.prototype, 'executeAuthorizeConfig').resolves([true, false, true]); - const authorizationService = new AuthorizationService(mockDBConnection); + const authorizationService = new AuthorizationService(mockDBConnection); - const isAuthorized = await authorizationService.executeAuthorizationScheme(mockAuthorizationScheme); + const isAuthorized = await authorizationService.executeAuthorizationScheme(mockAuthorizationScheme); - expect(isAuthorized).to.equal(false); - }); + expect(isAuthorized).to.equal(false); + }); - it('returns true if all AND authorizationScheme rules return true', async function () { - const mockAuthorizationScheme = ({ and: [] } as unknown) as AuthorizationScheme; - const mockDBConnection = getMockDBConnection(); + it('returns true if all AND authorizationScheme rules return true', async function () { + const mockAuthorizationScheme = ({ and: [] } as unknown) as AuthorizationScheme; + const mockDBConnection = getMockDBConnection(); - sinon.stub(AuthorizationService.prototype, 'executeAuthorizeConfig').resolves([true, true, true]); + sinon.stub(AuthorizationService.prototype, 'executeAuthorizeConfig').resolves([true, true, true]); - const authorizationService = new AuthorizationService(mockDBConnection); + const authorizationService = new AuthorizationService(mockDBConnection); - const isAuthorized = await authorizationService.executeAuthorizationScheme(mockAuthorizationScheme); + const isAuthorized = await authorizationService.executeAuthorizationScheme(mockAuthorizationScheme); - expect(isAuthorized).to.equal(true); - }); + expect(isAuthorized).to.equal(true); + }); - it('returns false if all OR authorizationScheme rules return false', async function () { - const mockAuthorizationScheme = ({ or: [] } as unknown) as AuthorizationScheme; - const mockDBConnection = getMockDBConnection(); + it('returns false if all OR authorizationScheme rules return false', async function () { + const mockAuthorizationScheme = ({ or: [] } as unknown) as AuthorizationScheme; + const mockDBConnection = getMockDBConnection(); - sinon.stub(AuthorizationService.prototype, 'executeAuthorizeConfig').resolves([false, false, false]); + sinon.stub(AuthorizationService.prototype, 'executeAuthorizeConfig').resolves([false, false, false]); - const authorizationService = new AuthorizationService(mockDBConnection); + const authorizationService = new AuthorizationService(mockDBConnection); - const isAuthorized = await authorizationService.executeAuthorizationScheme(mockAuthorizationScheme); + const isAuthorized = await authorizationService.executeAuthorizationScheme(mockAuthorizationScheme); - expect(isAuthorized).to.equal(false); - }); + expect(isAuthorized).to.equal(false); + }); - it('returns true if any OR authorizationScheme rules return true', async function () { - const mockAuthorizationScheme = ({ or: [] } as unknown) as AuthorizationScheme; - const mockDBConnection = getMockDBConnection(); + it('returns true if any OR authorizationScheme rules return true', async function () { + const mockAuthorizationScheme = ({ or: [] } as unknown) as AuthorizationScheme; + const mockDBConnection = getMockDBConnection(); - sinon.stub(AuthorizationService.prototype, 'executeAuthorizeConfig').resolves([false, true, false]); + sinon.stub(AuthorizationService.prototype, 'executeAuthorizeConfig').resolves([false, true, false]); - const authorizationService = new AuthorizationService(mockDBConnection); + const authorizationService = new AuthorizationService(mockDBConnection); - const isAuthorized = await authorizationService.executeAuthorizationScheme(mockAuthorizationScheme); + const isAuthorized = await authorizationService.executeAuthorizationScheme(mockAuthorizationScheme); - expect(isAuthorized).to.equal(true); + expect(isAuthorized).to.equal(true); + }); }); -}); -describe('executeAuthorizeConfig', function () { - afterEach(() => { - sinon.restore(); + describe('executeAuthorizeConfig', function () { + afterEach(() => { + sinon.restore(); + }); + + it('returns an array of authorizeRule results', async function () { + const mockAuthorizeRules: AuthorizeRule[] = [ + { + validSystemRoles: [SYSTEM_ROLE.SYSTEM_ADMIN], + discriminator: 'SystemRole' + }, + { + discriminator: 'SystemUser' + }, + { + validServiceClientIDs: [SOURCE_SYSTEM['SIMS-SVC-4464']], + discriminator: 'ServiceClient' + }, + { + validProjectPermissions: [PROJECT_PERMISSION.COLLABORATOR], + projectId: 1, + discriminator: 'ProjectPermission' + } + ]; + const mockDBConnection = getMockDBConnection(); + + sinon.stub(AuthorizationService.prototype, 'authorizeBySystemRole').resolves(false); + sinon.stub(AuthorizationService.prototype, 'authorizeBySystemUser').resolves(true); + sinon.stub(AuthorizationService.prototype, 'authorizeByServiceClient').resolves(true); + sinon.stub(AuthorizationService.prototype, 'authorizeByProjectPermission').resolves(false); + + const authorizationService = new AuthorizationService(mockDBConnection); + + const authorizeResults = await authorizationService.executeAuthorizeConfig(mockAuthorizeRules); + + expect(authorizeResults).to.eql([false, true, true, false]); + }); }); - it('returns an array of authorizeRule results', async function () { - const mockAuthorizeRules: AuthorizeRule[] = [ - { - validSystemRoles: [SYSTEM_ROLE.SYSTEM_ADMIN], - discriminator: 'SystemRole' - }, - { - discriminator: 'SystemUser' - }, - { - validServiceClientIDs: [SOURCE_SYSTEM['SIMS-SVC-4464']], - discriminator: 'ServiceClient' - }, - { - validProjectPermissions: [PROJECT_PERMISSION.COLLABORATOR], - projectId: 1, - discriminator: 'ProjectPermission' - } - ]; - const mockDBConnection = getMockDBConnection(); + describe('authorizeSystemAdministrator', function () { + afterEach(() => { + sinon.restore(); + }); - sinon.stub(AuthorizationService.prototype, 'authorizeBySystemRole').resolves(false); - sinon.stub(AuthorizationService.prototype, 'authorizeBySystemUser').resolves(true); - sinon.stub(AuthorizationService.prototype, 'authorizeByServiceClient').resolves(true); - sinon.stub(AuthorizationService.prototype, 'authorizeByProjectPermission').resolves(false); + it('returns false if `systemUserObject` is null', async function () { + const mockDBConnection = getMockDBConnection(); - const authorizationService = new AuthorizationService(mockDBConnection); + sinon.stub(AuthorizationService.prototype, 'getSystemUserObject').resolves(null); - const authorizeResults = await authorizationService.executeAuthorizeConfig(mockAuthorizeRules); + const authorizationService = new AuthorizationService(mockDBConnection); - expect(authorizeResults).to.eql([false, true, true, false]); - }); -}); + const isAuthorizedByServiceClient = await authorizationService.authorizeSystemAdministrator(); -describe('authorizeByServiceClient', function () { - afterEach(() => { - sinon.restore(); - }); + expect(isAuthorizedByServiceClient).to.equal(false); + }); + + it('returns true if `systemUserObject` is not null and includes admin role', async function () { + const mockDBConnection = getMockDBConnection(); - it('returns false if `systemUserObject` is null', async function () { - const mockDBConnection = getMockDBConnection(); + const mockGetSystemUsersObjectResponse = ({ + role_names: [SYSTEM_ROLE.SYSTEM_ADMIN] + } as unknown) as SystemUser; - sinon.stub(AuthorizationService.prototype, 'getSystemUserObject').resolves(null); + sinon.stub(AuthorizationService.prototype, 'getSystemUserObject').resolves(mockGetSystemUsersObjectResponse); - const authorizationService = new AuthorizationService(mockDBConnection); + const authorizationService = new AuthorizationService(mockDBConnection); - const isAuthorizedByServiceClient = await authorizationService.authorizeSystemAdministrator(); + const isAuthorizedByServiceClient = await authorizationService.authorizeSystemAdministrator(); - expect(isAuthorizedByServiceClient).to.equal(false); + expect(isAuthorizedByServiceClient).to.equal(true); + }); }); - it('returns true if `systemUserObject` is not null and includes admin role', async function () { - const mockDBConnection = getMockDBConnection(); + describe('authorizeBySystemRole', function () { + afterEach(() => { + sinon.restore(); + }); - const mockGetSystemUsersObjectResponse = ({ - role_names: [SYSTEM_ROLE.SYSTEM_ADMIN] - } as unknown) as SystemUser; + it('returns false if `authorizeSystemRoles` is null', async function () { + const mockAuthorizeSystemRoles = (null as unknown) as AuthorizeBySystemRoles; + const mockDBConnection = getMockDBConnection(); - sinon.stub(AuthorizationService.prototype, 'getSystemUserObject').resolves(mockGetSystemUsersObjectResponse); + const authorizationService = new AuthorizationService(mockDBConnection); - const authorizationService = new AuthorizationService(mockDBConnection); + const isAuthorizedBySystemRole = await authorizationService.authorizeBySystemRole(mockAuthorizeSystemRoles); - const isAuthorizedByServiceClient = await authorizationService.authorizeSystemAdministrator(); + expect(isAuthorizedBySystemRole).to.equal(false); + }); - expect(isAuthorizedByServiceClient).to.equal(true); - }); -}); + it('returns false if `systemUserObject` is null', async function () { + const mockAuthorizeSystemRoles: AuthorizeBySystemRoles = { + validSystemRoles: [SYSTEM_ROLE.SYSTEM_ADMIN], + discriminator: 'SystemRole' + }; + const mockDBConnection = getMockDBConnection(); -describe('authorizeBySystemRole', function () { - afterEach(() => { - sinon.restore(); - }); + const mockGetSystemUsersObjectResponse = (null as unknown) as SystemUser; + sinon.stub(AuthorizationService.prototype, 'getSystemUserObject').resolves(mockGetSystemUsersObjectResponse); - it('returns false if `authorizeSystemRoles` is null', async function () { - const mockAuthorizeSystemRoles = (null as unknown) as AuthorizeBySystemRoles; - const mockDBConnection = getMockDBConnection(); + const authorizationService = new AuthorizationService(mockDBConnection); - const authorizationService = new AuthorizationService(mockDBConnection); + const isAuthorizedBySystemRole = await authorizationService.authorizeBySystemRole(mockAuthorizeSystemRoles); - const isAuthorizedBySystemRole = await authorizationService.authorizeBySystemRole(mockAuthorizeSystemRoles); + expect(isAuthorizedBySystemRole).to.equal(false); + }); - expect(isAuthorizedBySystemRole).to.equal(false); - }); + it('returns false if `record_end_date` is null', async function () { + const mockAuthorizeSystemRoles: AuthorizeBySystemRoles = { + validSystemRoles: [SYSTEM_ROLE.SYSTEM_ADMIN], + discriminator: 'SystemRole' + }; + const mockDBConnection = getMockDBConnection(); - it('returns false if `systemUserObject` is null', async function () { - const mockAuthorizeSystemRoles: AuthorizeBySystemRoles = { - validSystemRoles: [SYSTEM_ROLE.SYSTEM_ADMIN], - discriminator: 'SystemRole' - }; - const mockDBConnection = getMockDBConnection(); + const mockGetSystemUsersObjectResponse = ({ record_end_date: 'datetime' } as unknown) as SystemUser; + sinon.stub(AuthorizationService.prototype, 'getSystemUserObject').resolves(mockGetSystemUsersObjectResponse); - const mockGetSystemUsersObjectResponse = (null as unknown) as SystemUser; - sinon.stub(AuthorizationService.prototype, 'getSystemUserObject').resolves(mockGetSystemUsersObjectResponse); + const authorizationService = new AuthorizationService(mockDBConnection); - const authorizationService = new AuthorizationService(mockDBConnection); + const isAuthorizedBySystemRole = await authorizationService.authorizeBySystemRole(mockAuthorizeSystemRoles); - const isAuthorizedBySystemRole = await authorizationService.authorizeBySystemRole(mockAuthorizeSystemRoles); + expect(isAuthorizedBySystemRole).to.equal(false); + }); - expect(isAuthorizedBySystemRole).to.equal(false); - }); + it('returns true if `authorizeSystemRoles` specifies no valid roles', async function () { + const mockAuthorizeSystemRoles: AuthorizeBySystemRoles = { + validSystemRoles: [], + discriminator: 'SystemRole' + }; + const mockDBConnection = getMockDBConnection(); - it('returns false if `record_end_date` is null', async function () { - const mockAuthorizeSystemRoles: AuthorizeBySystemRoles = { - validSystemRoles: [SYSTEM_ROLE.SYSTEM_ADMIN], - discriminator: 'SystemRole' - }; - const mockDBConnection = getMockDBConnection(); + const authorizationService = new AuthorizationService(mockDBConnection, { + systemUser: ({} as unknown) as SystemUser + }); - const mockGetSystemUsersObjectResponse = ({ record_end_date: 'datetime' } as unknown) as SystemUser; - sinon.stub(AuthorizationService.prototype, 'getSystemUserObject').resolves(mockGetSystemUsersObjectResponse); + const isAuthorizedBySystemRole = await authorizationService.authorizeBySystemRole(mockAuthorizeSystemRoles); - const authorizationService = new AuthorizationService(mockDBConnection); + expect(isAuthorizedBySystemRole).to.equal(true); + }); - const isAuthorizedBySystemRole = await authorizationService.authorizeBySystemRole(mockAuthorizeSystemRoles); + it('returns false if the user does not have any valid roles', async function () { + const mockAuthorizeSystemRoles: AuthorizeBySystemRoles = { + validSystemRoles: [SYSTEM_ROLE.SYSTEM_ADMIN], + discriminator: 'SystemRole' + }; + const mockDBConnection = getMockDBConnection(); - expect(isAuthorizedBySystemRole).to.equal(false); - }); + const authorizationService = new AuthorizationService(mockDBConnection, { + systemUser: ({ role_names: [] } as unknown) as SystemUser + }); - it('returns true if `authorizeSystemRoles` specifies no valid roles', async function () { - const mockAuthorizeSystemRoles: AuthorizeBySystemRoles = { - validSystemRoles: [], - discriminator: 'SystemRole' - }; - const mockDBConnection = getMockDBConnection(); + const isAuthorizedBySystemRole = await authorizationService.authorizeBySystemRole(mockAuthorizeSystemRoles); - const authorizationService = new AuthorizationService(mockDBConnection, { - systemUser: ({} as unknown) as SystemUser + expect(isAuthorizedBySystemRole).to.equal(false); }); - const isAuthorizedBySystemRole = await authorizationService.authorizeBySystemRole(mockAuthorizeSystemRoles); + it('returns true if the user has at least one of the valid roles', async function () { + const mockAuthorizeSystemRoles: AuthorizeBySystemRoles = { + validSystemRoles: [SYSTEM_ROLE.SYSTEM_ADMIN], + discriminator: 'SystemRole' + }; + const mockDBConnection = getMockDBConnection(); - expect(isAuthorizedBySystemRole).to.equal(true); - }); + const authorizationService = new AuthorizationService(mockDBConnection, { + systemUser: ({ role_names: [SYSTEM_ROLE.SYSTEM_ADMIN] } as unknown) as SystemUser + }); - it('returns false if the user does not have any valid roles', async function () { - const mockAuthorizeSystemRoles: AuthorizeBySystemRoles = { - validSystemRoles: [SYSTEM_ROLE.SYSTEM_ADMIN], - discriminator: 'SystemRole' - }; - const mockDBConnection = getMockDBConnection(); + const isAuthorizedBySystemRole = await authorizationService.authorizeBySystemRole(mockAuthorizeSystemRoles); - const authorizationService = new AuthorizationService(mockDBConnection, { - systemUser: ({ role_names: [] } as unknown) as SystemUser + expect(isAuthorizedBySystemRole).to.equal(true); }); - - const isAuthorizedBySystemRole = await authorizationService.authorizeBySystemRole(mockAuthorizeSystemRoles); - - expect(isAuthorizedBySystemRole).to.equal(false); }); - it('returns true if the user has at least one of the valid roles', async function () { - const mockAuthorizeSystemRoles: AuthorizeBySystemRoles = { - validSystemRoles: [SYSTEM_ROLE.SYSTEM_ADMIN], - discriminator: 'SystemRole' - }; - const mockDBConnection = getMockDBConnection(); - - const authorizationService = new AuthorizationService(mockDBConnection, { - systemUser: ({ role_names: [SYSTEM_ROLE.SYSTEM_ADMIN] } as unknown) as SystemUser + describe('authorizeBySystemUser', function () { + afterEach(() => { + sinon.restore(); }); - const isAuthorizedBySystemRole = await authorizationService.authorizeBySystemRole(mockAuthorizeSystemRoles); + it('returns false if `systemUserObject` is null', async function () { + const mockDBConnection = getMockDBConnection(); - expect(isAuthorizedBySystemRole).to.equal(true); - }); -}); + const mockGetSystemUsersObjectResponse = (null as unknown) as SystemUser; + sinon.stub(AuthorizationService.prototype, 'getSystemUserObject').resolves(mockGetSystemUsersObjectResponse); -describe('authorizeBySystemUser', function () { - afterEach(() => { - sinon.restore(); - }); + const authorizationService = new AuthorizationService(mockDBConnection); - it('returns false if `systemUserObject` is null', async function () { - const mockDBConnection = getMockDBConnection(); + const isAuthorizedBySystemRole = await authorizationService.authorizeBySystemUser(); - const mockGetSystemUsersObjectResponse = (null as unknown) as SystemUser; - sinon.stub(AuthorizationService.prototype, 'getSystemUserObject').resolves(mockGetSystemUsersObjectResponse); + expect(isAuthorizedBySystemRole).to.equal(false); + }); - const authorizationService = new AuthorizationService(mockDBConnection); + it('returns true if `systemUserObject` is not null', async function () { + const mockDBConnection = getMockDBConnection(); - const isAuthorizedBySystemRole = await authorizationService.authorizeBySystemUser(); + const mockGetSystemUsersObjectResponse = (null as unknown) as SystemUser; + sinon.stub(AuthorizationService.prototype, 'getSystemUserObject').resolves(mockGetSystemUsersObjectResponse); - expect(isAuthorizedBySystemRole).to.equal(false); - }); + const authorizationService = new AuthorizationService(mockDBConnection, { + systemUser: ({} as unknown) as SystemUser + }); - it('returns true if `systemUserObject` is not null', async function () { - const mockDBConnection = getMockDBConnection(); + const isAuthorizedBySystemRole = await authorizationService.authorizeBySystemUser(); - const mockGetSystemUsersObjectResponse = (null as unknown) as SystemUser; - sinon.stub(AuthorizationService.prototype, 'getSystemUserObject').resolves(mockGetSystemUsersObjectResponse); + expect(isAuthorizedBySystemRole).to.equal(true); + }); + }); - const authorizationService = new AuthorizationService(mockDBConnection, { - systemUser: ({} as unknown) as SystemUser + describe('authorizeByServiceClient', function () { + afterEach(() => { + sinon.restore(); }); - const isAuthorizedBySystemRole = await authorizationService.authorizeBySystemUser(); + it('returns false if the keycloak token is null', async function () { + const mockDBConnection = getMockDBConnection(); + sinon.stub(db, 'getDBConnection').returns(mockDBConnection); - expect(isAuthorizedBySystemRole).to.equal(true); - }); -}); + const authorizationService = new AuthorizationService(mockDBConnection); -describe('authorizeByServiceClient', function () { - afterEach(() => { - sinon.restore(); - }); + const authorizeByServiceClientData = ({ + validServiceClientIDs: SOURCE_SYSTEM['SIMS-SVC-4464'], + discriminator: 'ServiceClient' + } as unknown) as AuthorizeByServiceClient; - it('returns false if the keycloak token is null', async function () { - const mockDBConnection = getMockDBConnection(); - sinon.stub(db, 'getDBConnection').returns(mockDBConnection); + const result = await authorizationService.authorizeByServiceClient(authorizeByServiceClientData); - const authorizationService = new AuthorizationService(mockDBConnection); + expect(result).to.be.false; + }); - const authorizeByServiceClientData = ({ - validServiceClientIDs: SOURCE_SYSTEM['SIMS-SVC-4464'], - discriminator: 'ServiceClient' - } as unknown) as AuthorizeByServiceClient; + it('returns null if the system user identifier is null', async function () { + const mockDBConnection = getMockDBConnection(); + sinon.stub(db, 'getDBConnection').returns(mockDBConnection); - const result = await authorizationService.authorizeByServiceClient(authorizeByServiceClientData); + const authorizationService = new AuthorizationService(mockDBConnection, { + keycloakToken: { preferred_username: '' } + }); - expect(result).to.be.false; - }); + const authorizeByServiceClientData = ({ + validServiceClientIDs: SOURCE_SYSTEM['SIMS-SVC-4464'], + discriminator: 'ServiceClient' + } as unknown) as AuthorizeByServiceClient; - it('returns null if the system user identifier is null', async function () { - const mockDBConnection = getMockDBConnection(); - sinon.stub(db, 'getDBConnection').returns(mockDBConnection); + const result = await authorizationService.authorizeByServiceClient(authorizeByServiceClientData); - const authorizationService = new AuthorizationService(mockDBConnection, { - keycloakToken: { preferred_username: '' } + expect(result).to.be.false; }); - const authorizeByServiceClientData = ({ - validServiceClientIDs: SOURCE_SYSTEM['SIMS-SVC-4464'], - discriminator: 'ServiceClient' - } as unknown) as AuthorizeByServiceClient; + it('returns false if `systemUserObject` is null', async function () { + const mockDBConnection = getMockDBConnection(); - const result = await authorizationService.authorizeByServiceClient(authorizeByServiceClientData); + const authorizationService = new AuthorizationService(mockDBConnection); - expect(result).to.be.false; - }); + const authorizeByServiceClientData = ({ + validServiceClientIDs: SOURCE_SYSTEM['SIMS-SVC-4464'], + discriminator: 'ServiceClient' + } as unknown) as AuthorizeByServiceClient; - it('returns false if `systemUserObject` is null', async function () { - const mockDBConnection = getMockDBConnection(); + const isAuthorizedBySystemRole = await authorizationService.authorizeByServiceClient( + authorizeByServiceClientData + ); - const authorizationService = new AuthorizationService(mockDBConnection); + expect(isAuthorizedBySystemRole).to.equal(false); + }); - const authorizeByServiceClientData = ({ - validServiceClientIDs: SOURCE_SYSTEM['SIMS-SVC-4464'], - discriminator: 'ServiceClient' - } as unknown) as AuthorizeByServiceClient; + it('returns true if `systemUserObject` hasAtLeastOneValidValue', async function () { + const mockDBConnection = getMockDBConnection(); - const isAuthorizedBySystemRole = await authorizationService.authorizeByServiceClient(authorizeByServiceClientData); + const mockGetSystemUsersObjectResponse = (null as unknown) as SystemUser; + sinon.stub(AuthorizationService.prototype, 'getSystemUserObject').resolves(mockGetSystemUsersObjectResponse); - expect(isAuthorizedBySystemRole).to.equal(false); - }); + const authorizationService = new AuthorizationService(mockDBConnection, { + keycloakToken: { clientId: SOURCE_SYSTEM['SIMS-SVC-4464'] } + }); - it('returns true if `systemUserObject` hasAtLeastOneValidValue', async function () { - const mockDBConnection = getMockDBConnection(); + const authorizeByServiceClientData = ({ + validServiceClientIDs: SOURCE_SYSTEM['SIMS-SVC-4464'], + discriminator: 'ServiceClient' + } as unknown) as AuthorizeByServiceClient; - const mockGetSystemUsersObjectResponse = (null as unknown) as SystemUser; - sinon.stub(AuthorizationService.prototype, 'getSystemUserObject').resolves(mockGetSystemUsersObjectResponse); + const isAuthorizedBySystemRole = await authorizationService.authorizeByServiceClient( + authorizeByServiceClientData + ); - const authorizationService = new AuthorizationService(mockDBConnection, { - keycloakToken: { clientId: SOURCE_SYSTEM['SIMS-SVC-4464'] } + expect(isAuthorizedBySystemRole).to.equal(true); }); - - const authorizeByServiceClientData = ({ - validServiceClientIDs: SOURCE_SYSTEM['SIMS-SVC-4464'], - discriminator: 'ServiceClient' - } as unknown) as AuthorizeByServiceClient; - - const isAuthorizedBySystemRole = await authorizationService.authorizeByServiceClient(authorizeByServiceClientData); - - expect(isAuthorizedBySystemRole).to.equal(true); - }); -}); - -describe('authorizeByProjectPermission', function () { - afterEach(() => { - sinon.restore(); }); - it('returns false if `authorizeProjectPermission` is null', async function () { - const mockAuthorizeProjectPermission = (null as unknown) as AuthorizeByProjectPermission; - const mockDBConnection = getMockDBConnection(); - - const authorizationService = new AuthorizationService(mockDBConnection); + describe('authorizeByProjectPermission', function () { + afterEach(() => { + sinon.restore(); + }); - const isAuthorizedByProjectPermission = await authorizationService.authorizeByProjectPermission( - mockAuthorizeProjectPermission - ); + it('returns false if `authorizeProjectPermission` is null', async function () { + const mockAuthorizeProjectPermission = (null as unknown) as AuthorizeByProjectPermission; + const mockDBConnection = getMockDBConnection(); - expect(isAuthorizedByProjectPermission).to.equal(false); - }); + const authorizationService = new AuthorizationService(mockDBConnection); - it('returns false if `projectUserObject` is null', async function () { - const mockAuthorizeProjectPermission: AuthorizeByProjectPermission = { - validProjectPermissions: [PROJECT_PERMISSION.COORDINATOR], - projectId: 1, - discriminator: 'ProjectPermission' - }; - const mockDBConnection = getMockDBConnection(); + const isAuthorizedByProjectPermission = await authorizationService.authorizeByProjectPermission( + mockAuthorizeProjectPermission + ); - const mockGetSystemUsersObjectResponse = (null as unknown) as ProjectUser & SystemUser; - sinon.stub(AuthorizationService.prototype, 'getProjectUserObject').resolves(mockGetSystemUsersObjectResponse); + expect(isAuthorizedByProjectPermission).to.equal(false); + }); - const authorizationService = new AuthorizationService(mockDBConnection); + it('returns false if `projectUserObject` is null', async function () { + const mockAuthorizeProjectPermission: AuthorizeByProjectPermission = { + validProjectPermissions: [PROJECT_PERMISSION.COORDINATOR], + projectId: 1, + discriminator: 'ProjectPermission' + }; + const mockDBConnection = getMockDBConnection(); - const isAuthorizedByProjectPermission = await authorizationService.authorizeByProjectPermission( - mockAuthorizeProjectPermission - ); + const mockGetSystemUsersObjectResponse = (null as unknown) as ProjectUser & SystemUser; + sinon.stub(AuthorizationService.prototype, 'getProjectUserObject').resolves(mockGetSystemUsersObjectResponse); - expect(isAuthorizedByProjectPermission).to.equal(false); - }); + const authorizationService = new AuthorizationService(mockDBConnection); - it('returns false if `record_end_date` is null', async function () { - const mockAuthorizeProjectPermission: AuthorizeByProjectPermission = { - validProjectPermissions: [PROJECT_PERMISSION.COORDINATOR], - projectId: 1, - discriminator: 'ProjectPermission' - }; - const mockDBConnection = getMockDBConnection(); + const isAuthorizedByProjectPermission = await authorizationService.authorizeByProjectPermission( + mockAuthorizeProjectPermission + ); - const mockGetSystemUsersObjectResponse = ({ record_end_date: 'datetime' } as unknown) as ProjectUser & SystemUser; - sinon.stub(AuthorizationService.prototype, 'getProjectUserObject').resolves(mockGetSystemUsersObjectResponse); + expect(isAuthorizedByProjectPermission).to.equal(false); + }); - const authorizationService = new AuthorizationService(mockDBConnection); + it('returns false if `record_end_date` is null', async function () { + const mockAuthorizeProjectPermission: AuthorizeByProjectPermission = { + validProjectPermissions: [PROJECT_PERMISSION.COORDINATOR], + projectId: 1, + discriminator: 'ProjectPermission' + }; + const mockDBConnection = getMockDBConnection(); - const isAuthorizedByProjectPermission = await authorizationService.authorizeByProjectPermission( - mockAuthorizeProjectPermission - ); + const mockGetSystemUsersObjectResponse = ({ record_end_date: 'datetime' } as unknown) as ProjectUser & SystemUser; + sinon.stub(AuthorizationService.prototype, 'getProjectUserObject').resolves(mockGetSystemUsersObjectResponse); - expect(isAuthorizedByProjectPermission).to.equal(false); - }); + const authorizationService = new AuthorizationService(mockDBConnection); - it('returns true if `authorizeProjectPermission` specifies no valid permissions', async function () { - const mockAuthorizeProjectPermission: AuthorizeByProjectPermission = { - validProjectPermissions: [], - projectId: 1, - discriminator: 'ProjectPermission' - }; - const mockDBConnection = getMockDBConnection(); + const isAuthorizedByProjectPermission = await authorizationService.authorizeByProjectPermission( + mockAuthorizeProjectPermission + ); - const authorizationService = new AuthorizationService(mockDBConnection, { - projectUser: ({ project_id: 1 } as unknown) as ProjectUser & SystemUser + expect(isAuthorizedByProjectPermission).to.equal(false); }); - const isAuthorizedByProjectPermission = await authorizationService.authorizeByProjectPermission( - mockAuthorizeProjectPermission - ); + it('returns true if `authorizeProjectPermission` specifies no valid permissions', async function () { + const mockAuthorizeProjectPermission: AuthorizeByProjectPermission = { + validProjectPermissions: [], + projectId: 1, + discriminator: 'ProjectPermission' + }; + const mockDBConnection = getMockDBConnection(); - expect(isAuthorizedByProjectPermission).to.equal(true); - }); + const authorizationService = new AuthorizationService(mockDBConnection, { + projectUser: ({ project_id: 1 } as unknown) as ProjectUser & SystemUser + }); - it('returns false if the user does not have any valid permissions', async function () { - const mockAuthorizeProjectPermission: AuthorizeByProjectPermission = { - validProjectPermissions: [PROJECT_PERMISSION.COORDINATOR], - projectId: 1, - discriminator: 'ProjectPermission' - }; - const mockDBConnection = getMockDBConnection(); + const isAuthorizedByProjectPermission = await authorizationService.authorizeByProjectPermission( + mockAuthorizeProjectPermission + ); - const authorizationService = new AuthorizationService(mockDBConnection, { - projectUser: ({ project_id: 1, project_role_permissions: [] } as unknown) as ProjectUser & SystemUser + expect(isAuthorizedByProjectPermission).to.equal(true); }); - const isAuthorizedByProjectPermission = await authorizationService.authorizeByProjectPermission( - mockAuthorizeProjectPermission - ); + it('returns false if the user does not have any valid permissions', async function () { + const mockAuthorizeProjectPermission: AuthorizeByProjectPermission = { + validProjectPermissions: [PROJECT_PERMISSION.COORDINATOR], + projectId: 1, + discriminator: 'ProjectPermission' + }; + const mockDBConnection = getMockDBConnection(); - expect(isAuthorizedByProjectPermission).to.equal(false); - }); + const authorizationService = new AuthorizationService(mockDBConnection, { + projectUser: ({ project_id: 1, project_role_permissions: [] } as unknown) as ProjectUser & SystemUser + }); - it('returns true if the user has at least one of the valid permissions', async function () { - const mockAuthorizeProjectPermission: AuthorizeByProjectPermission = { - validProjectPermissions: [PROJECT_PERMISSION.COORDINATOR], - projectId: 1, - discriminator: 'ProjectPermission' - }; - const mockDBConnection = getMockDBConnection(); + const isAuthorizedByProjectPermission = await authorizationService.authorizeByProjectPermission( + mockAuthorizeProjectPermission + ); - const authorizationService = new AuthorizationService(mockDBConnection, { - projectUser: ({ - project_id: 1, - project_role_permissions: [PROJECT_PERMISSION.COORDINATOR] - } as unknown) as ProjectUser & SystemUser + expect(isAuthorizedByProjectPermission).to.equal(false); }); - const isAuthorizedByProjectPermission = await authorizationService.authorizeByProjectPermission( - mockAuthorizeProjectPermission - ); + it('returns true if the user has at least one of the valid permissions', async function () { + const mockAuthorizeProjectPermission: AuthorizeByProjectPermission = { + validProjectPermissions: [PROJECT_PERMISSION.COORDINATOR], + projectId: 1, + discriminator: 'ProjectPermission' + }; + const mockDBConnection = getMockDBConnection(); + + const authorizationService = new AuthorizationService(mockDBConnection, { + projectUser: ({ + project_id: 1, + project_role_permissions: [PROJECT_PERMISSION.COORDINATOR] + } as unknown) as ProjectUser & SystemUser + }); + + const isAuthorizedByProjectPermission = await authorizationService.authorizeByProjectPermission( + mockAuthorizeProjectPermission + ); - expect(isAuthorizedByProjectPermission).to.equal(true); + expect(isAuthorizedByProjectPermission).to.equal(true); + }); }); -}); -describe('hasAtLeastOneValidValue', () => { - describe('validValues is a string', () => { - describe('incomingValues is a string', () => { - it('returns true if the valid roles is empty', () => { - const response = AuthorizationService.hasAtLeastOneValidValue('', ''); + describe('hasAtLeastOneValidValue', () => { + describe('validValues is a string', () => { + describe('incomingValues is a string', () => { + it('returns true if the valid roles is empty', () => { + const response = AuthorizationService.hasAtLeastOneValidValue('', ''); - expect(response).to.be.true; - }); + expect(response).to.be.true; + }); - it('returns false if the user has no roles', () => { - const response = AuthorizationService.hasAtLeastOneValidValue('admin', ''); + it('returns false if the user has no roles', () => { + const response = AuthorizationService.hasAtLeastOneValidValue('admin', ''); - expect(response).to.be.false; - }); + expect(response).to.be.false; + }); - it('returns false if the user has no matching roles', () => { - const response = AuthorizationService.hasAtLeastOneValidValue('admin', 'user'); + it('returns false if the user has no matching roles', () => { + const response = AuthorizationService.hasAtLeastOneValidValue('admin', 'user'); - expect(response).to.be.false; - }); + expect(response).to.be.false; + }); - it('returns true if the user has a matching role', () => { - const response = AuthorizationService.hasAtLeastOneValidValue('admin', 'admin'); + it('returns true if the user has a matching role', () => { + const response = AuthorizationService.hasAtLeastOneValidValue('admin', 'admin'); - expect(response).to.be.true; + expect(response).to.be.true; + }); }); - }); - describe('incomingValues is an array', () => { - it('returns true if the valid roles is empty', () => { - const response = AuthorizationService.hasAtLeastOneValidValue('', []); + describe('incomingValues is an array', () => { + it('returns true if the valid roles is empty', () => { + const response = AuthorizationService.hasAtLeastOneValidValue('', []); - expect(response).to.be.true; - }); + expect(response).to.be.true; + }); - it('returns false if the user has no matching roles', () => { - const response = AuthorizationService.hasAtLeastOneValidValue('admin', []); + it('returns false if the user has no matching roles', () => { + const response = AuthorizationService.hasAtLeastOneValidValue('admin', []); - expect(response).to.be.false; - }); + expect(response).to.be.false; + }); - it('returns false if the user has no matching roles', () => { - const response = AuthorizationService.hasAtLeastOneValidValue('admin', ['user']); + it('returns false if the user has no matching roles', () => { + const response = AuthorizationService.hasAtLeastOneValidValue('admin', ['user']); - expect(response).to.be.false; - }); + expect(response).to.be.false; + }); - it('returns true if the user has a matching role', () => { - const response = AuthorizationService.hasAtLeastOneValidValue('admin', ['admin']); + it('returns true if the user has a matching role', () => { + const response = AuthorizationService.hasAtLeastOneValidValue('admin', ['admin']); - expect(response).to.be.true; + expect(response).to.be.true; + }); }); }); - }); - describe('validValues is an array', () => { - describe('incomingValues is a string', () => { - it('returns true if the valid roles is empty', () => { - const response = AuthorizationService.hasAtLeastOneValidValue([], ''); + describe('validValues is an array', () => { + describe('incomingValues is a string', () => { + it('returns true if the valid roles is empty', () => { + const response = AuthorizationService.hasAtLeastOneValidValue([], ''); - expect(response).to.be.true; - }); + expect(response).to.be.true; + }); - it('returns false if the user has no roles', () => { - const response = AuthorizationService.hasAtLeastOneValidValue(['admin'], ''); + it('returns false if the user has no roles', () => { + const response = AuthorizationService.hasAtLeastOneValidValue(['admin'], ''); - expect(response).to.be.false; - }); + expect(response).to.be.false; + }); - it('returns false if the user has no matching roles', () => { - const response = AuthorizationService.hasAtLeastOneValidValue(['admin'], 'user'); + it('returns false if the user has no matching roles', () => { + const response = AuthorizationService.hasAtLeastOneValidValue(['admin'], 'user'); - expect(response).to.be.false; - }); + expect(response).to.be.false; + }); - it('returns true if the user has a matching role', () => { - const response = AuthorizationService.hasAtLeastOneValidValue(['admin'], 'admin'); + it('returns true if the user has a matching role', () => { + const response = AuthorizationService.hasAtLeastOneValidValue(['admin'], 'admin'); - expect(response).to.be.true; + expect(response).to.be.true; + }); }); - }); - describe('incomingValues is an array', () => { - it('returns true if the valid roles is empty', () => { - const response = AuthorizationService.hasAtLeastOneValidValue([], []); + describe('incomingValues is an array', () => { + it('returns true if the valid roles is empty', () => { + const response = AuthorizationService.hasAtLeastOneValidValue([], []); - expect(response).to.be.true; - }); + expect(response).to.be.true; + }); - it('returns false if the user has no matching roles', () => { - const response = AuthorizationService.hasAtLeastOneValidValue(['admin'], []); + it('returns false if the user has no matching roles', () => { + const response = AuthorizationService.hasAtLeastOneValidValue(['admin'], []); - expect(response).to.be.false; - }); + expect(response).to.be.false; + }); - it('returns false if the user has no matching roles', () => { - const response = AuthorizationService.hasAtLeastOneValidValue(['admin'], ['user']); + it('returns false if the user has no matching roles', () => { + const response = AuthorizationService.hasAtLeastOneValidValue(['admin'], ['user']); - expect(response).to.be.false; - }); + expect(response).to.be.false; + }); - it('returns true if the user has a matching role', () => { - const response = AuthorizationService.hasAtLeastOneValidValue(['admin'], ['admin']); + it('returns true if the user has a matching role', () => { + const response = AuthorizationService.hasAtLeastOneValidValue(['admin'], ['admin']); - expect(response).to.be.true; + expect(response).to.be.true; + }); }); }); }); -}); -describe('getSystemUserObject', function () { - afterEach(() => { - sinon.restore(); - }); + describe('getSystemUserObject', function () { + afterEach(() => { + sinon.restore(); + }); - it('returns null if fetching the system user throws an error', async function () { - const mockDBConnection = getMockDBConnection(); + it('returns null if fetching the system user throws an error', async function () { + const mockDBConnection = getMockDBConnection(); - sinon.stub(AuthorizationService.prototype, 'getSystemUserWithRoles').callsFake(() => { - throw new Error('Test Error'); - }); + sinon.stub(AuthorizationService.prototype, 'getSystemUserWithRoles').callsFake(() => { + throw new Error('Test Error'); + }); - const authorizationService = new AuthorizationService(mockDBConnection); + const authorizationService = new AuthorizationService(mockDBConnection); - const systemUserObject = await authorizationService.getSystemUserObject(); + const systemUserObject = await authorizationService.getSystemUserObject(); - expect(systemUserObject).to.equal(null); - }); + expect(systemUserObject).to.equal(null); + }); - it('returns null if the system user is null or undefined', async function () { - const mockDBConnection = getMockDBConnection(); + it('returns null if the system user is null or undefined', async function () { + const mockDBConnection = getMockDBConnection(); - const mockSystemUserWithRolesResponse = null; - sinon.stub(AuthorizationService.prototype, 'getSystemUserWithRoles').resolves(mockSystemUserWithRolesResponse); + const mockSystemUserWithRolesResponse = null; + sinon.stub(AuthorizationService.prototype, 'getSystemUserWithRoles').resolves(mockSystemUserWithRolesResponse); - const authorizationService = new AuthorizationService(mockDBConnection); + const authorizationService = new AuthorizationService(mockDBConnection); - const systemUserObject = await authorizationService.getSystemUserObject(); + const systemUserObject = await authorizationService.getSystemUserObject(); - expect(systemUserObject).to.equal(null); - }); + expect(systemUserObject).to.equal(null); + }); - it('returns a system user', async function () { - const mockDBConnection = getMockDBConnection(); - - const mockSystemUserWithRolesResponse: SystemUser = { - system_user_id: 2, - user_identifier: 'username', - identity_source: SYSTEM_IDENTITY_SOURCE.IDIR, - user_guid: '123-456-789', - record_end_date: null, - role_ids: [1], - role_names: [SYSTEM_ROLE.SYSTEM_ADMIN], - email: 'email@email.com', - family_name: 'lname', - given_name: 'fname', - display_name: 'test user', - agency: null - }; - sinon.stub(AuthorizationService.prototype, 'getSystemUserWithRoles').resolves(mockSystemUserWithRolesResponse); - - const authorizationService = new AuthorizationService(mockDBConnection); - - const systemUserObject = await authorizationService.getSystemUserObject(); - - expect(systemUserObject).to.equal(mockSystemUserWithRolesResponse); + it('returns a system user', async function () { + const mockDBConnection = getMockDBConnection(); + + const mockSystemUserWithRolesResponse: SystemUser = { + system_user_id: 2, + user_identifier: 'username', + identity_source: SYSTEM_IDENTITY_SOURCE.IDIR, + user_guid: '123-456-789', + record_end_date: null, + role_ids: [1], + role_names: [SYSTEM_ROLE.SYSTEM_ADMIN], + email: 'email@email.com', + family_name: 'lname', + given_name: 'fname', + display_name: 'test user', + agency: null + }; + sinon.stub(AuthorizationService.prototype, 'getSystemUserWithRoles').resolves(mockSystemUserWithRolesResponse); + + const authorizationService = new AuthorizationService(mockDBConnection); + + const systemUserObject = await authorizationService.getSystemUserObject(); + + expect(systemUserObject).to.equal(mockSystemUserWithRolesResponse); + }); }); -}); -describe('getSystemUserWithRoles', function () { - afterEach(() => { - sinon.restore(); - }); + describe('getSystemUserWithRoles', function () { + afterEach(() => { + sinon.restore(); + }); - it('returns null if the keycloak token is null', async function () { - const mockDBConnection = getMockDBConnection(); - sinon.stub(db, 'getDBConnection').returns(mockDBConnection); + it('returns null if the keycloak token is null', async function () { + const mockDBConnection = getMockDBConnection(); + sinon.stub(db, 'getDBConnection').returns(mockDBConnection); - const authorizationService = new AuthorizationService(mockDBConnection); + const authorizationService = new AuthorizationService(mockDBConnection, { + keycloakToken: undefined + }); - const result = await authorizationService.getSystemUserWithRoles(); + const result = await authorizationService.getSystemUserWithRoles(); - expect(result).to.be.null; - }); + expect(result).to.be.null; + }); - it('returns null if the system user guid is null', async function () { - const mockDBConnection = getMockDBConnection(); - sinon.stub(db, 'getDBConnection').returns(mockDBConnection); + it('returns null if the keycloak token is not a valid format (fails the parser)', async function () { + const mockDBConnection = getMockDBConnection(); + sinon.stub(db, 'getDBConnection').returns(mockDBConnection); + + const authorizationService = new AuthorizationService(mockDBConnection, { + keycloakToken: { not: '', valid: '' } + }); - const authorizationService = new AuthorizationService(mockDBConnection, { - keycloakToken: { preferred_username: '' } + const result = await authorizationService.getSystemUserWithRoles(); + + expect(result).to.be.null; }); - const result = await authorizationService.getSystemUserWithRoles(); + it('returns a UserObject', async function () { + const mockDBConnection = getMockDBConnection(); + sinon.stub(db, 'getDBConnection').returns(mockDBConnection); + + const userObjectMock: SystemUser = { + system_user_id: 2, + user_identifier: 'username', + identity_source: SYSTEM_IDENTITY_SOURCE.IDIR, + user_guid: '123-456-789', + record_end_date: null, + role_ids: [1], + role_names: ['Collaborator'], + email: 'email@email.com', + family_name: 'lname', + given_name: 'fname', + display_name: 'test user', + agency: null + }; + + sinon.stub(UserService.prototype, 'getUserByGuid').resolves((userObjectMock as unknown) as any); + + const authorizationService = new AuthorizationService(mockDBConnection, { + keycloakToken: { + idir_user_guid: '123-456-789', + 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' + } + }); - expect(result).to.be.null; - }); + const result = await authorizationService.getSystemUserWithRoles(); - it('returns a UserObject', async function () { - const mockDBConnection = getMockDBConnection(); - sinon.stub(db, 'getDBConnection').returns(mockDBConnection); - - const userObjectMock: SystemUser = { - system_user_id: 2, - user_identifier: 'username', - identity_source: SYSTEM_IDENTITY_SOURCE.IDIR, - user_guid: '123-456-789', - record_end_date: null, - role_ids: [1], - role_names: ['Collaborator'], - email: 'email@email.com', - family_name: 'lname', - given_name: 'fname', - display_name: 'test user', - agency: null - }; - sinon.stub(UserService.prototype, 'getUserByGuid').resolves((userObjectMock as unknown) as any); - - const authorizationService = new AuthorizationService(mockDBConnection, { - keycloakToken: { preferred_username: '123-456-789@idir' } - }); - - const result = await authorizationService.getSystemUserWithRoles(); - - expect(result).to.equal(userObjectMock); + expect(result).to.equal(userObjectMock); + }); }); -}); -describe('getProjectUserObject', function () { - afterEach(() => { - sinon.restore(); - }); + describe('getProjectUserObject', function () { + afterEach(() => { + sinon.restore(); + }); + + it('returns null if fetching the project user throws an error', async function () { + const mockDBConnection = getMockDBConnection(); + + sinon.stub(AuthorizationService.prototype, 'getSystemUserWithRoles').callsFake(() => { + throw new Error('Test Error'); + }); - it('returns null if fetching the project user throws an error', async function () { - const mockDBConnection = getMockDBConnection(); + const authorizationService = new AuthorizationService(mockDBConnection); - sinon.stub(AuthorizationService.prototype, 'getSystemUserWithRoles').callsFake(() => { - throw new Error('Test Error'); + const projectId = 1; + + const projectUser = await authorizationService.getProjectUserObject(projectId); + + expect(projectUser).to.equal(null); }); - const authorizationService = new AuthorizationService(mockDBConnection); + it('returns null if the project user is null or undefined', async function () { + const mockDBConnection = getMockDBConnection(); - const projectId = 1; + const projectUserMock = null; + sinon.stub(AuthorizationService.prototype, 'getProjectUserWithRoles').resolves(projectUserMock); - const projectUser = await authorizationService.getProjectUserObject(projectId); + const authorizationService = new AuthorizationService(mockDBConnection); - expect(projectUser).to.equal(null); - }); + const projectId = 1; - it('returns null if the project user is null or undefined', async function () { - const mockDBConnection = getMockDBConnection(); + const projectUser = await authorizationService.getProjectUserObject(projectId); - const projectUserMock = null; - sinon.stub(AuthorizationService.prototype, 'getProjectUserWithRoles').resolves(projectUserMock); + expect(projectUser).to.equal(null); + }); - const authorizationService = new AuthorizationService(mockDBConnection); + it('returns a project user when keycloak token is valid', async function () { + const mockDBConnection = getMockDBConnection(); + + const projectUserMock: ProjectUser & SystemUser = { + system_user_id: 2, + user_identifier: 'username', + identity_source: SYSTEM_IDENTITY_SOURCE.IDIR, + user_guid: '123-456-789', + record_end_date: null, + role_ids: [1], + role_names: ['Collaborator'], + email: 'email@email.com', + family_name: 'lname', + given_name: 'fname', + display_name: 'test user', + agency: null, + project_participation_id: 3, + project_id: 1, + project_role_ids: [1], + project_role_names: [PROJECT_ROLE.COLLABORATOR], + project_role_permissions: [PROJECT_ROLE.COLLABORATOR] + }; + + sinon.stub(AuthorizationService.prototype, 'getProjectUserWithRoles').resolves(projectUserMock); + + const authorizationService = new AuthorizationService(mockDBConnection, { + keycloakToken: { + idir_user_guid: '123-456-789', + identity_provider: 'idir', + idir_username: 'username', + email_verified: false, + name: 'test user', + preferred_username: '123-456-789@idir', + display_name: 'test user', + given_name: 'test', + family_name: 'user', + email: 'email@email.com' + } + }); - const projectId = 1; + const projectId = 1; - const projectUser = await authorizationService.getProjectUserObject(projectId); + const projectUser = await authorizationService.getProjectUserObject(projectId); - expect(projectUser).to.equal(null); + expect(projectUser).to.equal(projectUserMock); + }); }); - it('returns a project user', async function () { - const mockDBConnection = getMockDBConnection(); - - const projectUserMock: ProjectUser & SystemUser = { - system_user_id: 2, - user_identifier: 'username', - identity_source: SYSTEM_IDENTITY_SOURCE.IDIR, - user_guid: '123-456-789', - record_end_date: null, - role_ids: [1], - role_names: ['Collaborator'], - email: 'email@email.com', - family_name: 'lname', - given_name: 'fname', - display_name: 'test user', - agency: null, - project_participation_id: 3, - project_id: 1, - project_role_ids: [1], - project_role_names: [PROJECT_ROLE.COLLABORATOR], - project_role_permissions: [PROJECT_ROLE.COLLABORATOR] - }; - - sinon.stub(AuthorizationService.prototype, 'getProjectUserWithRoles').resolves(projectUserMock); - - const authorizationService = new AuthorizationService(mockDBConnection); - - const projectId = 1; - - const projectUser = await authorizationService.getProjectUserObject(projectId); - - expect(projectUser).to.equal(projectUserMock); - }); -}); + describe('getProjectUserWithRoles', function () { + afterEach(() => { + sinon.restore(); + }); -describe('getProjectUserWithRoles', function () { - afterEach(() => { - sinon.restore(); - }); + it('returns null if the keycloak token is null', async function () { + const mockDBConnection = getMockDBConnection(); + sinon.stub(db, 'getDBConnection').returns(mockDBConnection); - it('returns null if the keycloak token is null', async function () { - const mockDBConnection = getMockDBConnection(); - sinon.stub(db, 'getDBConnection').returns(mockDBConnection); + const authorizationService = new AuthorizationService(mockDBConnection, { + keycloakToken: undefined + }); - const authorizationService = new AuthorizationService(mockDBConnection); + const projectId = 1; - const projectId = 1; + const result = await authorizationService.getProjectUserWithRoles(projectId); - const result = await authorizationService.getProjectUserWithRoles(projectId); + expect(result).to.be.null; + }); - expect(result).to.be.null; - }); + it('returns null if the keycloak token is not a valid format (fails the parser)', async function () { + const mockDBConnection = getMockDBConnection(); + sinon.stub(db, 'getDBConnection').returns(mockDBConnection); + + const authorizationService = new AuthorizationService(mockDBConnection, { + keycloakToken: { not: '', valid: '' } + }); - it('returns null if the project user guid is null', async function () { - const mockDBConnection = getMockDBConnection(); - sinon.stub(db, 'getDBConnection').returns(mockDBConnection); + const projectId = 1; - const authorizationService = new AuthorizationService(mockDBConnection, { - keycloakToken: { preferred_username: '' } + const result = await authorizationService.getProjectUserWithRoles(projectId); + + expect(result).to.be.null; }); - const projectId = 1; + it('returns a project user when keycloak token is valid', async function () { + const mockDBConnection = getMockDBConnection(); + sinon.stub(db, 'getDBConnection').returns(mockDBConnection); + + const projectUserMock: ProjectUser & SystemUser = { + system_user_id: 2, + user_identifier: 'username', + identity_source: SYSTEM_IDENTITY_SOURCE.IDIR, + user_guid: '123-456-789', + record_end_date: null, + role_ids: [1], + role_names: ['Collaborator'], + email: 'email@email.com', + family_name: 'lname', + given_name: 'fname', + display_name: 'test user', + agency: null, + project_participation_id: 3, + project_id: 1, + project_role_ids: [1], + project_role_names: [PROJECT_ROLE.COLLABORATOR], + project_role_permissions: [PROJECT_ROLE.COLLABORATOR] + }; + sinon + .stub(ProjectParticipationService.prototype, 'getProjectParticipantByUserGuid') + .resolves((projectUserMock as unknown) as any); + + const authorizationService = new AuthorizationService(mockDBConnection, { + keycloakToken: { + idir_user_guid: '123-456-789', + identity_provider: 'idir', + idir_username: 'username', + name: 'test user', + preferred_username: '123-456-789@idir', + display_name: 'test user', + email: 'email@email.com', + email_verified: false, + given_name: 'fname', + family_name: 'lname' + } + }); - const result = await authorizationService.getProjectUserWithRoles(projectId); + const projectId = 1; - expect(result).to.be.null; - }); + const result = await authorizationService.getProjectUserWithRoles(projectId); - it('returns a project user', async function () { - const mockDBConnection = getMockDBConnection(); - sinon.stub(db, 'getDBConnection').returns(mockDBConnection); - - const projectUserMock: ProjectUser & SystemUser = { - system_user_id: 2, - user_identifier: 'username', - identity_source: SYSTEM_IDENTITY_SOURCE.IDIR, - user_guid: '123-456-789', - record_end_date: null, - role_ids: [1], - role_names: ['Collaborator'], - email: 'email@email.com', - family_name: 'lname', - given_name: 'fname', - display_name: 'test user', - agency: null, - project_participation_id: 3, - project_id: 1, - project_role_ids: [1], - project_role_names: [PROJECT_ROLE.COLLABORATOR], - project_role_permissions: [PROJECT_ROLE.COLLABORATOR] - }; - sinon - .stub(ProjectParticipationService.prototype, 'getProjectParticipantByUserGuid') - .resolves((projectUserMock as unknown) as any); - - const authorizationService = new AuthorizationService(mockDBConnection, { - keycloakToken: { preferred_username: '123-456-789@idir' } - }); - - const projectId = 1; - - const result = await authorizationService.getProjectUserWithRoles(projectId); - - expect(result).to.equal(projectUserMock); + expect(result).to.equal(projectUserMock); + }); }); }); diff --git a/api/src/services/authorization-service.ts b/api/src/services/authorization-service.ts index e53d2e7c73..4ed0666189 100644 --- a/api/src/services/authorization-service.ts +++ b/api/src/services/authorization-service.ts @@ -3,7 +3,7 @@ import { PROJECT_PERMISSION, SYSTEM_ROLE } from '../constants/roles'; import { IDBConnection } from '../database/db'; import { ProjectUser } from '../repositories/project-participation-repository'; import { SystemUser } from '../repositories/user-repository'; -import { getKeycloakSource, getUserGuid } from '../utils/keycloak-utils'; +import { getKeycloakSource, getKeycloakUserInformationFromKeycloakToken, getUserGuid } from '../utils/keycloak-utils'; import { DBService } from './db-service'; import { ProjectParticipationService } from './project-participation-service'; import { UserService } from './user-service'; @@ -324,12 +324,14 @@ export class AuthorizationService extends DBService { return null; } - const userGuid = getUserGuid(this._keycloakToken); + const keycloakUserInformation = getKeycloakUserInformationFromKeycloakToken(this._keycloakToken); - if (!userGuid) { + if (!keycloakUserInformation) { return null; } + const userGuid = getUserGuid(keycloakUserInformation); + return this._userService.getUserByGuid(userGuid); } @@ -366,12 +368,14 @@ export class AuthorizationService extends DBService { return null; } - const userGuid = getUserGuid(this._keycloakToken); + const keycloakUserInformation = getKeycloakUserInformationFromKeycloakToken(this._keycloakToken); - if (!userGuid) { + if (!keycloakUserInformation) { return null; } + const userGuid = getUserGuid(keycloakUserInformation); + return this._projectParticipationService.getProjectParticipantByUserGuid(projectId, userGuid); } } diff --git a/api/src/utils/keycloak-utils.test.ts b/api/src/utils/keycloak-utils.test.ts index c1f235db41..4f561642dc 100644 --- a/api/src/utils/keycloak-utils.test.ts +++ b/api/src/utils/keycloak-utils.test.ts @@ -1,108 +1,239 @@ import { expect } from 'chai'; import { describe } from 'mocha'; import { SYSTEM_IDENTITY_SOURCE } from '../constants/database'; -import { coerceUserIdentitySource, getUserGuid, getUserIdentifier, getUserIdentitySource } from './keycloak-utils'; +import { + BceidBasicUserInformation, + BceidBusinessUserInformation, + coerceUserIdentitySource, + DatabaseUserInformation, + getUserGuid, + getUserIdentifier, + getUserIdentitySource, + IdirUserInformation, + isBceidBasicUserInformation, + isBceidBusinessUserInformation, + isDatabaseUserInformation, + isIdirUserInformation, + ServiceClientUserInformation +} from './keycloak-utils'; describe('keycloakUtils', () => { describe('getUserGuid', () => { - it('returns null response when null keycloakToken provided', () => { - const response = getUserGuid((null as unknown) as object); - - expect(response).to.be.null; + it('returns idir guid', () => { + const keycloakUserInformation: IdirUserInformation = { + idir_user_guid: '123456789', + identity_provider: 'idir', + idir_username: 'tname', + name: 'Test Name', + preferred_username: '123456789@idir', + display_name: 'Test Name', + email: 'email@email.com', + email_verified: false, + given_name: 'Test', + family_name: '' + }; + + const response = getUserGuid(keycloakUserInformation); + + expect(response).to.equal('123456789'); }); - it('returns null response when a keycloakToken is provided with a missing preferred_username field', () => { - const response = getUserGuid({ idir_username: 'username' }); + it('returns bceid basic guid', () => { + 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 response = getUserGuid(keycloakUserInformation); + + expect(response).to.equal('123456789'); + }); - expect(response).to.be.null; + it('returns bceid business guid', () => { + 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 response = getUserGuid(keycloakUserInformation); + + expect(response).to.equal('123456789'); }); - it('returns their guid', () => { - const response = getUserGuid({ preferred_username: 'aaaaa@idir' }); + it('returns database guid', () => { + const keycloakUserInformation: DatabaseUserInformation = { + database_user_guid: '123456789', + identity_provider: 'database', + username: 'biohub_dapi_v1' + }; + + const response = getUserGuid(keycloakUserInformation); - expect(response).to.equal('aaaaa'); + expect(response).to.equal('123456789'); }); }); describe('getUserIdentifier', () => { - it('returns null response when null keycloakToken provided', () => { - const response = getUserIdentifier((null as unknown) as object); - - expect(response).to.be.null; + it('returns idir username', () => { + const keycloakUserInformation: IdirUserInformation = { + idir_user_guid: '123456789', + identity_provider: 'idir', + idir_username: 'tname', + name: 'Test Name', + preferred_username: '123456789@idir', + display_name: 'Test Name', + email: 'email@email.com', + email_verified: false, + given_name: 'Test', + family_name: '' + }; + + const response = getUserIdentifier(keycloakUserInformation); + + expect(response).to.equal('tname'); }); - it('returns null response when a keycloakToken is provided with a missing username field', () => { - const response = getUserIdentifier({ preferred_username: 'aaaaa@idir' }); - - expect(response).to.be.null; + it('returns bceid basic username', () => { + 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 response = getUserIdentifier(keycloakUserInformation); + + expect(response).to.equal('tname'); }); - it('returns the identifier from their IDIR username', () => { - const response = getUserIdentifier({ preferred_username: 'aaaaa@idir', idir_username: 'idiruser' }); - - expect(response).to.equal('idiruser'); + it('returns bceid business username', () => { + 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 response = getUserIdentifier(keycloakUserInformation); + + expect(response).to.equal('tname'); }); - it('returns the identifier from their BCeID username', () => { - const response = getUserIdentifier({ preferred_username: 'aaaaa@idir', bceid_username: 'bceiduser' }); + it('returns database username', () => { + const keycloakUserInformation: DatabaseUserInformation = { + database_user_guid: '123456789', + identity_provider: 'database', + username: 'biohub_dapi_v1' + }; - expect(response).to.equal('bceiduser'); + const response = getUserIdentifier(keycloakUserInformation); + + expect(response).to.equal('biohub_dapi_v1'); }); }); describe('getUserIdentitySource', () => { - it('returns non null response when null keycloakToken provided', () => { - const response = getUserIdentitySource((null as unknown) as object); - - expect(response).to.equal(SYSTEM_IDENTITY_SOURCE.DATABASE); - }); - - it('returns non null response when valid keycloakToken provided with no preferred_username', () => { - const response = getUserIdentitySource({}); - - expect(response).to.equal(SYSTEM_IDENTITY_SOURCE.DATABASE); - }); - - it('returns non null response when valid keycloakToken provided with null preferred_username', () => { - const response = getUserIdentitySource({ preferred_username: null }); - - expect(response).to.equal(SYSTEM_IDENTITY_SOURCE.DATABASE); - }); - - it('returns non null response when valid keycloakToken provided with no source', () => { - const response = getUserIdentitySource({ preferred_username: 'username' }); - - expect(response).to.equal(SYSTEM_IDENTITY_SOURCE.DATABASE); - }); - - it('returns non null response when valid keycloakToken provided with idir source', () => { - const response = getUserIdentitySource({ preferred_username: 'username@idir' }); + it('returns idir source', () => { + const keycloakUserInformation: IdirUserInformation = { + idir_user_guid: '123456789', + identity_provider: 'idir', + idir_username: 'tname', + name: 'Test Name', + preferred_username: '123456789@idir', + display_name: 'Test Name', + email: 'email@email.com', + email_verified: false, + given_name: 'Test', + family_name: '' + }; + + const response = getUserIdentitySource(keycloakUserInformation); expect(response).to.equal(SYSTEM_IDENTITY_SOURCE.IDIR); }); - it('returns non null response when valid keycloakToken provided with bceid basic source', () => { - const response = getUserIdentitySource({ preferred_username: 'username@bceidbasic' }); + it('returns bceid basic source', () => { + 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 response = getUserIdentitySource(keycloakUserInformation); expect(response).to.equal(SYSTEM_IDENTITY_SOURCE.BCEID_BASIC); }); - it('returns non null response when valid keycloakToken provided with bceid business source', () => { - const response = getUserIdentitySource({ preferred_username: 'username@bceidbusiness' }); + it('returns bceid business source', () => { + 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 response = getUserIdentitySource(keycloakUserInformation); expect(response).to.equal(SYSTEM_IDENTITY_SOURCE.BCEID_BUSINESS); }); - it('returns non null response when valid keycloakToken provided with database source', () => { - const response = getUserIdentitySource({ preferred_username: 'username@database' }); - - expect(response).to.equal(SYSTEM_IDENTITY_SOURCE.DATABASE); - }); + it('returns database source', () => { + const keycloakUserInformation: DatabaseUserInformation = { + database_user_guid: '123456789', + identity_provider: 'database', + username: 'biohub_dapi_v1' + }; - it('returns non null response when valid keycloakToken provided with system source', () => { - const response = getUserIdentitySource({ preferred_username: 'username@system' }); + const response = getUserIdentitySource(keycloakUserInformation); - expect(response).to.equal(SYSTEM_IDENTITY_SOURCE.SYSTEM); + expect(response).to.equal(SYSTEM_IDENTITY_SOURCE.DATABASE); }); }); @@ -111,38 +242,402 @@ describe('keycloakUtils', () => { const response = coerceUserIdentitySource(''); expect(response).to.equal(SYSTEM_IDENTITY_SOURCE.DATABASE); }); - it('should coerce null string user identity to DATABASE', () => { const response = coerceUserIdentitySource((null as unknown) as string); expect(response).to.equal(SYSTEM_IDENTITY_SOURCE.DATABASE); }); - it('should coerce bceid basic user identity to BCEIDBASIC', () => { const response = coerceUserIdentitySource('bceidbasic'); expect(response).to.equal(SYSTEM_IDENTITY_SOURCE.BCEID_BASIC); }); - it('should coerce bceid business user identity to BCEIDBUSINESS', () => { const response = coerceUserIdentitySource('bceidbusiness'); expect(response).to.equal(SYSTEM_IDENTITY_SOURCE.BCEID_BUSINESS); }); - it('should coerce idir user identity to IDIR', () => { const response = coerceUserIdentitySource('idir'); expect(response).to.equal(SYSTEM_IDENTITY_SOURCE.IDIR); }); - it('should coerce database user identity to DATABASE', () => { const response = coerceUserIdentitySource('database'); expect(response).to.equal(SYSTEM_IDENTITY_SOURCE.DATABASE); }); + }); + + describe('getKeycloakUserInformationFromKeycloakToken', () => { + it('returns valid idir token information', () => { + const keycloakUserInformation: IdirUserInformation = { + idir_user_guid: '123456789', + identity_provider: 'idir', + idir_username: 'tname', + name: 'Test Name', + preferred_username: '123456789@idir', + display_name: 'Test Name', + email: 'email@email.com', + email_verified: false, + given_name: 'Test', + family_name: '' + }; + + const response = getUserIdentitySource(keycloakUserInformation); + + expect(response).not.to.be.null; + }); + + it('returns valid bceid basic token information', () => { + 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 response = getUserIdentitySource(keycloakUserInformation); + + expect(response).not.to.be.null; + }); + + it('returns valid bceid business token information', () => { + 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 response = getUserIdentitySource(keycloakUserInformation); + + expect(response).not.to.be.null; + }); + + it('returns valid database token information', () => { + const keycloakUserInformation: DatabaseUserInformation = { + database_user_guid: '123456789', + identity_provider: 'database', + username: 'biohub_dapi_v1' + }; + + const response = getUserIdentitySource(keycloakUserInformation); + + expect(response).not.to.be.null; + }); + + it('returns valid system token information', () => { + const keycloakUserInformation: ServiceClientUserInformation = { + database_user_guid: '123456789', + identity_provider: 'database', + username: 'biohub_dapi_v1' + }; + + const response = getUserIdentitySource(keycloakUserInformation); + + expect(response).not.to.be.null; + }); + }); + + describe('isIdirUserInformation', () => { + it('returns true when idir token information provided', () => { + const keycloakUserInformation: IdirUserInformation = { + idir_user_guid: '123456789', + identity_provider: 'idir', + idir_username: 'tname', + name: 'Test Name', + preferred_username: '123456789@idir', + display_name: 'Test Name', + email: 'email@email.com', + email_verified: false, + given_name: 'Test', + family_name: '' + }; + + const response = isIdirUserInformation(keycloakUserInformation); + + expect(response).to.be.true; + }); + + it('returns false when bceid basic token information provided', () => { + 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 response = isIdirUserInformation(keycloakUserInformation); + + expect(response).to.be.false; + }); + + it('returns false when bceid business token information provided', () => { + 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 response = isIdirUserInformation(keycloakUserInformation); + + expect(response).to.be.false; + }); + + it('returns false when database token information provided', () => { + const keycloakUserInformation: DatabaseUserInformation = { + database_user_guid: '123456789', + identity_provider: 'database', + username: 'biohub_dapi_v1' + }; + + const response = isIdirUserInformation(keycloakUserInformation); + + expect(response).to.be.false; + }); + }); + + describe('isBceidBasicUserInformation', () => { + it('returns false when idir token information provided', () => { + const keycloakUserInformation: IdirUserInformation = { + idir_user_guid: '123456789', + identity_provider: 'idir', + idir_username: 'tname', + name: 'Test Name', + preferred_username: '123456789@idir', + display_name: 'Test Name', + email: 'email@email.com', + email_verified: false, + given_name: 'Test', + family_name: '' + }; + + const response = isBceidBasicUserInformation(keycloakUserInformation); + + expect(response).to.be.false; + }); + + it('returns true when bceid basic token information provided', () => { + 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 response = isBceidBasicUserInformation(keycloakUserInformation); + + expect(response).to.be.true; + }); + + it('returns false when bceid business token information provided', () => { + 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 response = isBceidBasicUserInformation(keycloakUserInformation); + + expect(response).to.be.false; + }); + + it('returns false when database token information provided', () => { + const keycloakUserInformation: DatabaseUserInformation = { + database_user_guid: '123456789', + identity_provider: 'database', + username: 'biohub_dapi_v1' + }; + + const response = isBceidBasicUserInformation(keycloakUserInformation); + + expect(response).to.be.false; + }); + }); + + describe('isBceidBusinessUserInformation', () => { + it('returns false when idir token information provided', () => { + const keycloakUserInformation: IdirUserInformation = { + idir_user_guid: '123456789', + identity_provider: 'idir', + idir_username: 'tname', + name: 'Test Name', + preferred_username: '123456789@idir', + display_name: 'Test Name', + email: 'email@email.com', + email_verified: false, + given_name: 'Test', + family_name: '' + }; + + const response = isBceidBusinessUserInformation(keycloakUserInformation); + + expect(response).to.be.false; + }); + + it('returns false when bceid basic token information provided', () => { + 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 response = isBceidBusinessUserInformation(keycloakUserInformation); + + expect(response).to.be.false; + }); + + it('returns true when bceid business token information provided', () => { + 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 response = isBceidBusinessUserInformation(keycloakUserInformation); + + expect(response).to.be.true; + }); + + it('returns false when database token information provided', () => { + const keycloakUserInformation: DatabaseUserInformation = { + database_user_guid: '123456789', + identity_provider: 'database', + username: 'biohub_dapi_v1' + }; - it('should coerce system user identity to SYSTEM', () => { - const response = coerceUserIdentitySource('system'); - expect(response).to.equal(SYSTEM_IDENTITY_SOURCE.SYSTEM); + const response = isBceidBusinessUserInformation(keycloakUserInformation); + + expect(response).to.be.false; }); }); - describe('getKeycloakSource', () => { - //TODO + + describe('isDatabaseUserInformation', () => { + it('returns false when idir token information provided', () => { + const keycloakUserInformation: IdirUserInformation = { + idir_user_guid: '123456789', + identity_provider: 'idir', + idir_username: 'tname', + name: 'Test Name', + preferred_username: '123456789@idir', + display_name: 'Test Name', + email: 'email@email.com', + email_verified: false, + given_name: 'Test', + family_name: '' + }; + + const response = isDatabaseUserInformation(keycloakUserInformation); + + expect(response).to.be.false; + }); + + it('returns false when bceid basic token information provided', () => { + 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 response = isDatabaseUserInformation(keycloakUserInformation); + + expect(response).to.be.false; + }); + + it('returns false when bceid business token information provided', () => { + 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 response = isDatabaseUserInformation(keycloakUserInformation); + + expect(response).to.be.false; + }); + + it('returns true when database token information provided', () => { + const keycloakUserInformation: DatabaseUserInformation = { + database_user_guid: '123456789', + identity_provider: 'database', + username: 'biohub_dapi_v1' + }; + + const response = isDatabaseUserInformation(keycloakUserInformation); + + expect(response).to.be.true; + }); }); }); diff --git a/api/src/utils/keycloak-utils.ts b/api/src/utils/keycloak-utils.ts index 8453c158b0..193f7a6b04 100644 --- a/api/src/utils/keycloak-utils.ts +++ b/api/src/utils/keycloak-utils.ts @@ -1,40 +1,123 @@ +import { z } from 'zod'; import { SOURCE_SYSTEM, SYSTEM_IDENTITY_SOURCE } from '../constants/database'; /** - * Parses out the user's GUID from a keycloak token, which is extracted from the - * `preferred_username` property. - * - * @example getUserGuid({ preferred_username: 'aaabbaaa@idir' }) // => 'aaabbaaa' + * User information from a verified IDIR Keycloak token. + */ +export const IdirUserInformation = z.object({ + idir_user_guid: z.string().toLowerCase(), + identity_provider: z.literal(SYSTEM_IDENTITY_SOURCE.IDIR.toLowerCase()), + idir_username: z.string().toLowerCase(), + email_verified: z.boolean(), + name: z.string(), + preferred_username: z.string(), + display_name: z.string(), + given_name: z.string(), + family_name: z.string(), + email: z.string() +}); +export type IdirUserInformation = z.infer; + +/** + * User information from a verified BCeID Basic Keycloak token. + */ +export const BceidBasicUserInformation = z.object({ + bceid_user_guid: z.string().toLowerCase(), + identity_provider: z.literal(SYSTEM_IDENTITY_SOURCE.BCEID_BASIC.toLowerCase()), + bceid_username: z.string().toLowerCase(), + email_verified: z.boolean(), + name: z.string(), + preferred_username: z.string(), + display_name: z.string(), + given_name: z.string(), + family_name: z.string(), + email: z.string() +}); +export type BceidBasicUserInformation = z.infer; + +/** + * User information from a verified BCeID Keycloak token. + */ +export const BceidBusinessUserInformation = z.object({ + bceid_business_guid: z.string().toLowerCase(), + bceid_business_name: z.string(), + bceid_user_guid: z.string().toLowerCase(), + identity_provider: z.literal(SYSTEM_IDENTITY_SOURCE.BCEID_BUSINESS.toLowerCase()), + bceid_username: z.string().toLowerCase(), + email_verified: z.boolean(), + name: z.string(), + preferred_username: z.string(), + display_name: z.string(), + given_name: z.string(), + family_name: z.string(), + email: z.string() +}); +export type BceidBusinessUserInformation = z.infer; + +/** + * User information for a keycloak service client userF. + */ +export const ServiceClientUserInformation = z.object({ + database_user_guid: z.string(), + identity_provider: z.literal(SYSTEM_IDENTITY_SOURCE.SYSTEM.toLowerCase()), + username: z.string() +}); + +/** + * User information for an internal database user. + */ +export const DatabaseUserInformation = z.object({ + database_user_guid: z.string(), + identity_provider: z.literal(SYSTEM_IDENTITY_SOURCE.DATABASE.toLowerCase()), + username: z.string() +}); + +export type DatabaseUserInformation = z.infer; + +export type ServiceClientUserInformation = z.infer; + +/** + * User information from either an IDIR or BCeID Basic or BCeID Business Keycloak token. + */ +export const KeycloakUserInformation = z.discriminatedUnion('identity_provider', [ + IdirUserInformation, + BceidBasicUserInformation, + BceidBusinessUserInformation, + ServiceClientUserInformation, + DatabaseUserInformation +]); + +export type KeycloakUserInformation = z.infer; + +/** + * Returns the user information guid. * - * @param {object} keycloakToken - * @return {*} {(string | null)} + * @param {KeycloakUserInformation} keycloakUserInformation + * @return {*} {(string | null)} */ -export const getUserGuid = (keycloakToken: object): string | null => { - const userIdentifier = keycloakToken?.['preferred_username']?.split('@')?.[0]; +export const getUserGuid = (keycloakUserInformation: KeycloakUserInformation): string => { + if (isIdirUserInformation(keycloakUserInformation)) { + return keycloakUserInformation.idir_user_guid; + } - if (!userIdentifier) { - return null; + if (isBceidBusinessUserInformation(keycloakUserInformation) || isBceidBasicUserInformation(keycloakUserInformation)) { + return keycloakUserInformation.bceid_user_guid; } - return userIdentifier; + return keycloakUserInformation.database_user_guid; }; /** - * Parses out the preferred_username identity source ('idir', 'bceidbasic', etc.) from the token and maps it to a known - * `SYSTEM_IDENTITY_SOURCE`. If the `identity_provider` field in the keycloak token object is undefined, then the - * identity source is inferred from the `preferred_username` field as a contingency. + * Returns the user information identity source ('idir', 'bceidbasic', 'database, etc) and maps it to a known + * `SYSTEM_IDENTITY_SOURCE`. * - * @example getUserIdentitySource({ ...token, identity_provider: 'bceidbasic' }) => SYSTEM_IDENTITY_SOURCE.BCEID_BASIC - * @example getUserIdentitySource({ preferred_username: 'aaaa@idir' }) => SYSTEM_IDENTITY_SOURCE.IDIR + * @example getUserIdentitySource({ identity_provider: 'idir' }) => SYSTEM_IDENTITY_SOURCE.IDIR * - * @param {object} keycloakToken - * @return {*} {SYSTEM_IDENTITY_SOURCE} + * @param {Record} keycloakToken + * @return {*} {SYSTEM_IDENTITY_SOURCE} the identity source belonging to type SYSTEM_IDENTITY_SOURCE */ -export const getUserIdentitySource = (keycloakToken: object): SYSTEM_IDENTITY_SOURCE => { - const userIdentitySource: string = - keycloakToken?.['identity_provider'] || keycloakToken?.['preferred_username']?.split('@')?.[1]; - - return coerceUserIdentitySource(userIdentitySource); +export const getUserIdentitySource = (keycloakUserInformation: KeycloakUserInformation): SYSTEM_IDENTITY_SOURCE => { + return coerceUserIdentitySource(keycloakUserInformation.identity_provider); }; /** @@ -44,26 +127,26 @@ export const getUserIdentitySource = (keycloakToken: object): SYSTEM_IDENTITY_SO * * @example coerceUserIdentitySource('idir') => 'idir' satisfies SYSTEM_IDENTITY_SOURCE.IDIR * - * @param userIdentitySource the identity source string - * @returns {*} {SYSTEM_IDENTITY_SOURCE} the identity source belonging to type SYSTEM_IDENTITY_SOURCE + * @param {string} userIdentitySource + * @return {*} {SYSTEM_IDENTITY_SOURCE} the identity source belonging to type SYSTEM_IDENTITY_SOURCE */ -export const coerceUserIdentitySource = (userIdentitySource: string | null): SYSTEM_IDENTITY_SOURCE => { +export const coerceUserIdentitySource = (userIdentitySource: string): SYSTEM_IDENTITY_SOURCE => { switch (userIdentitySource?.toUpperCase()) { + case SYSTEM_IDENTITY_SOURCE.IDIR: + return SYSTEM_IDENTITY_SOURCE.IDIR; + case SYSTEM_IDENTITY_SOURCE.BCEID_BASIC: return SYSTEM_IDENTITY_SOURCE.BCEID_BASIC; case SYSTEM_IDENTITY_SOURCE.BCEID_BUSINESS: return SYSTEM_IDENTITY_SOURCE.BCEID_BUSINESS; - case SYSTEM_IDENTITY_SOURCE.IDIR: - return SYSTEM_IDENTITY_SOURCE.IDIR; + case SYSTEM_IDENTITY_SOURCE.DATABASE: + return SYSTEM_IDENTITY_SOURCE.DATABASE; case SYSTEM_IDENTITY_SOURCE.SYSTEM: return SYSTEM_IDENTITY_SOURCE.SYSTEM; - case SYSTEM_IDENTITY_SOURCE.DATABASE: - return SYSTEM_IDENTITY_SOURCE.DATABASE; - default: // Covers a user created directly in keycloak which wouldn't have an identity source return SYSTEM_IDENTITY_SOURCE.DATABASE; @@ -71,21 +154,65 @@ export const coerceUserIdentitySource = (userIdentitySource: string | null): SYS }; /** - * Parses out the user's identifier from a keycloak token. + * Returns the user information identifier (aka: username). * - * @example getUserIdentifier({ ....token, bceid_username: 'jsmith@idir' }) => 'jsmith' + * @example getUserIdentifier({ idir_username: 'jsmith' }) => 'jsmith' * - * @param {object} keycloakToken - * @return {*} {(string | null)} + * @param {KeycloakUserInformation} keycloakUserInformation + * @return {*} {string} */ -export const getUserIdentifier = (keycloakToken: object): string | null => { - const userIdentifier = keycloakToken?.['idir_username'] || keycloakToken?.['bceid_username']; +export const getUserIdentifier = (keycloakUserInformation: KeycloakUserInformation): string => { + if (isIdirUserInformation(keycloakUserInformation)) { + return keycloakUserInformation.idir_username; + } - if (!userIdentifier) { + if (isBceidBusinessUserInformation(keycloakUserInformation) || isBceidBasicUserInformation(keycloakUserInformation)) { + return keycloakUserInformation.bceid_username; + } + + return keycloakUserInformation.username; +}; + +/** + * Get a `KeycloakUserInformation` object from a Keycloak Bearer Token (IDIR or BCeID Basic or BCeID Business token). + * + * @param {Record} keycloakToken + * @return {*} {(KeycloakUserInformation | null)} + */ +export const getKeycloakUserInformationFromKeycloakToken = ( + keycloakToken: Record +): KeycloakUserInformation | null => { + const result = KeycloakUserInformation.safeParse(keycloakToken); + + if (!result.success) { return null; } - return userIdentifier; + return result.data; +}; + +export const isIdirUserInformation = ( + keycloakUserInformation: KeycloakUserInformation +): keycloakUserInformation is IdirUserInformation => { + return keycloakUserInformation.identity_provider === SYSTEM_IDENTITY_SOURCE.IDIR.toLowerCase(); +}; + +export const isBceidBasicUserInformation = ( + keycloakUserInformation: KeycloakUserInformation +): keycloakUserInformation is BceidBasicUserInformation => { + return keycloakUserInformation.identity_provider === SYSTEM_IDENTITY_SOURCE.BCEID_BASIC.toLowerCase(); +}; + +export const isBceidBusinessUserInformation = ( + keycloakUserInformation: KeycloakUserInformation +): keycloakUserInformation is BceidBusinessUserInformation => { + return keycloakUserInformation.identity_provider === SYSTEM_IDENTITY_SOURCE.BCEID_BUSINESS.toLowerCase(); +}; + +export const isDatabaseUserInformation = ( + keycloakUserInformation: KeycloakUserInformation +): keycloakUserInformation is DatabaseUserInformation => { + return keycloakUserInformation.identity_provider === SYSTEM_IDENTITY_SOURCE.DATABASE.toLowerCase(); }; /** diff --git a/app/package-lock.json b/app/package-lock.json index 810a3a7177..320cefe5b6 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -4555,7 +4555,7 @@ "amdefine": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", - "integrity": "sha512-S2Hw0TtNkMJhIabBwIojKL9YHO5T0n5eNqWJ7Lrlel/zDbftQpxpapi8tZs3X1HWa+u+QeydGmzzNU0m09+Rcg==" + "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=" }, "ansi-escapes": { "version": "4.3.2", @@ -4657,12 +4657,12 @@ "array-find-index": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", - "integrity": "sha512-M1HQyIXcBGtVywBt8WVdim+lrNaK7VHp99Qt5pSNziXznKHViIBbXWtfRTpEFpF/c4FdfxNAsCCwPp5phBYJtw==" + "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=" }, "array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, "array-includes": { "version": "3.1.6", @@ -4777,7 +4777,7 @@ "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==" + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" }, "ast-types-flow": { "version": "0.0.7", @@ -4794,12 +4794,12 @@ "async-foreach": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/async-foreach/-/async-foreach-0.1.3.tgz", - "integrity": "sha512-VUeSMD8nEGBWaZK4lizI1sf3yEC7pnAQ/mrI7pC2fBz2s/tq5jWWEngTwaf0Gruu/OoXRGLGg1XFqpYBiGTYJA==" + "integrity": "sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI=" }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, "at-least-node": { "version": "1.0.0", @@ -4835,7 +4835,7 @@ "aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==" + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" }, "aws4": { "version": "1.12.0", @@ -5139,7 +5139,7 @@ "bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", "requires": { "tweetnacl": "^0.14.3" } @@ -5171,7 +5171,7 @@ "block-stream": { "version": "0.0.9", "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", - "integrity": "sha512-OorbnJVPII4DuUKbjARAe8u8EfqOmkEEaSFIyoQ7OjTHn6kafxWl0wLgoZ2rXaYd7MyLcDaU4TmhfxtwgcccMQ==", + "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=", "requires": { "inherits": "~2.0.0" } @@ -5338,7 +5338,7 @@ "camelcase": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", - "integrity": "sha512-DLIsRzJVBQu72meAKPkWQOLcujdXT32hwdfnkI1frSiSRMK1MofjKHf+MEx0SB6fjEFXL8fBDv1dKymBlOp4Qw==" + "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=" }, "camelcase-css": { "version": "2.0.1", @@ -5349,7 +5349,7 @@ "camelcase-keys": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", - "integrity": "sha512-bA/Z/DERHKqoEOrp+qeGKw1QlvEQkGZSc0XaY6VnTxZr+Kv1G5zFwttpjv8qxZ/sBPT4nthwZaAcsAZTJlSKXQ==", + "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", "requires": { "camelcase": "^2.0.0", "map-obj": "^1.0.0" @@ -5381,7 +5381,7 @@ "caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==" + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" }, "chalk": { "version": "2.4.2", @@ -5474,7 +5474,7 @@ "is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==" + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" }, "string-width": { "version": "3.1.0", @@ -5521,7 +5521,7 @@ "code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==" + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" }, "collect-v8-coverage": { "version": "1.0.2", @@ -5540,7 +5540,7 @@ "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, "colord": { "version": "2.9.3", @@ -5642,7 +5642,7 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "concaveman": { "version": "1.2.1", @@ -5670,7 +5670,7 @@ "console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" }, "content-disposition": { "version": "0.5.4", @@ -5698,7 +5698,7 @@ "cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" }, "core-js": { "version": "3.31.1", @@ -5741,7 +5741,7 @@ "cross-spawn": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-3.0.1.tgz", - "integrity": "sha512-eZ+m1WNhSZutOa/uRblAc9Ut5MQfukFrFMtPSm3bZCA888NmMd5AWXWdgRZ80zd+pTk1P2JrGjg9pUPTvl2PWQ==", + "integrity": "sha1-ElYDfsufDF9549bvE14wdwGEuYI=", "requires": { "lru-cache": "^4.0.1", "which": "^1.2.9" @@ -5759,7 +5759,7 @@ "yallist": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==" + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" } } }, @@ -6115,7 +6115,7 @@ "currently-unhandled": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", - "integrity": "sha512-/fITjgjGU50vjQ4FH6eUoYu+iUoUKIXws2hL15JJpIR+BbTxaXQsMuuyjtNh2WqsSBS5nsaZHFsFecyw5CCAng==", + "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", "requires": { "array-find-index": "^1.0.1" } @@ -6129,7 +6129,7 @@ "dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", "requires": { "assert-plus": "^1.0.0" } @@ -6156,7 +6156,7 @@ "decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==" + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" }, "decimal.js": { "version": "10.4.3", @@ -6221,17 +6221,17 @@ "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" }, "delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" }, "depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==" + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" }, "dequal": { "version": "2.0.3", @@ -6242,7 +6242,7 @@ "destroy": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha512-3NdhDuEXnfun/z7x9GOElY49LoqVHoGScmOKwmxhsS8N5Y+Z8KyPPDnaSzqWgYt/ji4mqwfTS34Htrk0zPIXVg==" + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" }, "detect-newline": { "version": "3.1.0", @@ -6457,7 +6457,7 @@ "ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", "requires": { "jsbn": "~0.1.0", "safer-buffer": "^2.1.0" @@ -6466,7 +6466,7 @@ "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, "ejs": { "version": "3.1.9", @@ -6502,7 +6502,7 @@ "encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" }, "enhanced-resolve": { "version": "5.15.0", @@ -6660,12 +6660,12 @@ "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==" + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, "escodegen": { "version": "2.1.0", @@ -7351,7 +7351,7 @@ "etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" }, "eventemitter3": { "version": "4.0.7", @@ -7484,7 +7484,7 @@ "extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==" + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" }, "fast-deep-equal": { "version": "3.1.3", @@ -7667,7 +7667,7 @@ "find-up": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha512-jvElSjyuo4EMQGoTwo1uJU5pQMwTW5lS1x05zzfJuTIyLR3zwO27LYrxNg+dlvKpGOuGy/MzBdXh80g0ve5+HA==", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", "requires": { "path-exists": "^2.0.0", "pinkie-promise": "^2.0.0" @@ -7717,7 +7717,7 @@ "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==" + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" }, "fork-ts-checker-webpack-plugin": { "version": "6.5.3", @@ -7908,7 +7908,7 @@ "fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" }, "fs-constants": { "version": "1.0.0", @@ -7936,7 +7936,7 @@ "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "fsevents": { "version": "2.3.2", @@ -7981,7 +7981,7 @@ "gauge": { "version": "2.7.4", "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", - "integrity": "sha512-14x4kjc6lkD3ltw589k0NrPD6cCNTD6CWoVUNpB85+DrtONoZn+Rug6xZU5RvSC4+TZPxA5AnBibQYAvZn41Hg==", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", "requires": { "aproba": "^1.0.3", "console-control-strings": "^1.0.0", @@ -8009,7 +8009,7 @@ "geojson-equality": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/geojson-equality/-/geojson-equality-0.1.6.tgz", - "integrity": "sha512-TqG8YbqizP3EfwP5Uw4aLu6pKkg6JQK9uq/XZ1lXQntvTHD1BBKJWhNpJ2M0ax6TuWMP3oyx6Oq7FCIfznrgpQ==", + "integrity": "sha1-oXE3TvBD5dR5eZWEC65GSOB1LXI=", "requires": { "deep-equal": "^1.0.0" } @@ -8045,7 +8045,7 @@ "get-stdin": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", - "integrity": "sha512-F5aQMywwJ2n85s4hJPTT9RPxGmubonuB10MNYo17/xph174n2MIR33HRguhzVag10O/npM7SPk73LMZNP+FaWw==" + "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=" }, "get-stream": { "version": "6.0.1", @@ -8066,7 +8066,7 @@ "getpass": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", "requires": { "assert-plus": "^1.0.0" } @@ -8220,7 +8220,7 @@ "har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==" + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" }, "har-validator": { "version": "5.1.5", @@ -8248,7 +8248,7 @@ "has-ansi": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", "requires": { "ansi-regex": "^2.0.0" } @@ -8262,7 +8262,7 @@ "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==" + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" }, "has-property-descriptors": { "version": "1.0.0", @@ -8293,7 +8293,7 @@ "has-unicode": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" }, "he": { "version": "1.2.0", @@ -8506,7 +8506,7 @@ "http-signature": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", "requires": { "assert-plus": "^1.0.0", "jsprim": "^1.2.2", @@ -8578,7 +8578,7 @@ "immediate": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", - "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" + "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=" }, "immer": { "version": "9.0.21", @@ -8627,7 +8627,7 @@ "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "requires": { "once": "^1.3.0", "wrappy": "1" @@ -8683,7 +8683,7 @@ "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" }, "is-bigint": { "version": "1.0.4", @@ -8761,7 +8761,7 @@ "is-fullwidth-code-point": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "requires": { "number-is-nan": "^1.0.0" } @@ -8793,7 +8793,7 @@ "is-in-browser": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/is-in-browser/-/is-in-browser-1.1.3.tgz", - "integrity": "sha512-FeXIBgG/CPGd/WUxuEyvgGTEfwiG9Z4EKGxjNMRqviiIIfsmgrpnHLffEDdwUHqNva1VEW91o3xBT/m8Elgl9g==" + "integrity": "sha1-Vv9NtoOgeMYILrldrX3GLh0E+DU=" }, "is-map": { "version": "2.0.2", @@ -8938,12 +8938,12 @@ "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" }, "is-utf8": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==" + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=" }, "is-weakmap": { "version": "2.0.1", @@ -8982,17 +8982,17 @@ "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" }, "isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==" + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" }, "istanbul-lib-coverage": { "version": "3.2.0", @@ -11531,7 +11531,7 @@ "jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==" + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" }, "jsdom": { "version": "16.7.0", @@ -11628,7 +11628,7 @@ "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==" + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" }, "json5": { "version": "2.2.3", @@ -11825,7 +11825,7 @@ "leaflet-fullscreen": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/leaflet-fullscreen/-/leaflet-fullscreen-1.0.2.tgz", - "integrity": "sha512-1Yxm8RZg6KlKX25+hbP2H/wnOAphH7hFcvuADJFb4QZTN7uOSN9Hsci5EZpow8vtNej9OGzu59Jxmn+0qKOO9Q==" + "integrity": "sha1-CcYcS6xF9jsu4Sav2H5c2XZQ/Bs=" }, "leaflet.locatecontrol": { "version": "0.76.1", @@ -11875,7 +11875,7 @@ "load-json-file": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha512-cy7ZdNRXdablkXYNI049pthVeXFurRyb9+hA/dZzerZ0pGTx42z+y+ssxBaVV2l70t1muq5IdKhn4UtcoGUY9A==", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", "requires": { "graceful-fs": "^4.1.2", "parse-json": "^2.2.0", @@ -11923,7 +11923,7 @@ "path-exists": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==" + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" } } }, @@ -11978,7 +11978,7 @@ "loud-rejection": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", - "integrity": "sha512-RPNliZOFkqFumDhvYqOaNY4Uz9oJM2K9tC6JWsJJsNdhuONW4LQHRBpb0qf4pJApVffI5N39SwzWZJuEhfd7eQ==", + "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", "requires": { "currently-unhandled": "^0.4.1", "signal-exit": "^3.0.0" @@ -12037,7 +12037,7 @@ "map-obj": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==" + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=" }, "mdn-data": { "version": "2.0.4", @@ -12048,7 +12048,7 @@ "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==" + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" }, "memfs": { "version": "3.5.3", @@ -12067,7 +12067,7 @@ "meow": { "version": "3.7.0", "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", - "integrity": "sha512-TNdwZs0skRlpPpCUK25StC4VH+tP5GgeY1HQOOGP+lQ2xtdkN2VtT/5tiX9k3IWpkBPV9b3LsAWXn4GGi/PrSA==", + "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", "requires": { "camelcase-keys": "^2.0.0", "decamelize": "^1.1.2", @@ -12084,7 +12084,7 @@ "merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" }, "merge-stream": { "version": "2.0.0", @@ -12101,7 +12101,7 @@ "methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==" + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" }, "mgrs": { "version": "1.0.0", @@ -12333,7 +12333,7 @@ "semver": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", - "integrity": "sha512-mfmm3/H9+67MCVix1h+IXTpDwL6710LyHuk7+cWC9T1mE0qz4iHhh6r4hU2wrIT9iTsAAC2XQRvfblL028cpLw==" + "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=" } } }, @@ -12375,12 +12375,12 @@ "ansi-styles": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==" + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" }, "chalk": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "requires": { "ansi-styles": "^2.2.1", "escape-string-regexp": "^1.0.2", @@ -12392,14 +12392,14 @@ "supports-color": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==" + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" } } }, "nopt": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", - "integrity": "sha512-4GUt3kSEYmk4ITxzB/b9vaIDfUVWN/Ml1Fwl11IlnIG2iaJ9O6WXZ9SrYM9NLI8OCBieN2Y8SWC2oJV0RQ7qYg==", + "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", "requires": { "abbrev": "1" } @@ -12472,7 +12472,7 @@ "number-is-nan": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==" + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" }, "nwsapi": { "version": "2.2.7", @@ -12488,7 +12488,7 @@ "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" }, "object-hash": { "version": "3.0.0", @@ -12593,7 +12593,7 @@ "on-finished": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", "requires": { "ee-first": "1.1.1" } @@ -12607,7 +12607,7 @@ "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "requires": { "wrappy": "1" } @@ -12649,12 +12649,12 @@ "os-homedir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==" + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" }, "os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==" + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" }, "osenv": { "version": "0.1.5", @@ -12769,7 +12769,7 @@ "path-exists": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha512-yTltuKuhtNeFJKa1PiRzfLAU5182q1y4Eb4XCJ3PBqyzEDkAZRzBrKKBct682ls9reBVHf9udYLN5Nd+K1B9BQ==", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", "requires": { "pinkie-promise": "^2.0.0" } @@ -12777,7 +12777,7 @@ "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==" + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, "path-key": { "version": "3.1.1", @@ -12793,7 +12793,7 @@ "path-to-regexp": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" }, "path-type": { "version": "4.0.0", @@ -12803,7 +12803,7 @@ "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==" + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" }, "picocolors": { "version": "1.0.0", @@ -12819,17 +12819,17 @@ "pify": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==" + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" }, "pinkie": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==" + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=" }, "pinkie-promise": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", "requires": { "pinkie": "^2.0.0" } @@ -13836,7 +13836,7 @@ "pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==" + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" }, "psl": { "version": "1.9.0", @@ -15300,7 +15300,7 @@ "read-pkg": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha512-7BGwRHqt4s/uVbuyoeejRn4YmFnYZiFl4AuaeXHlgZf3sONF0SOGlxs2Pw8g6hCKupo08RafIO5YXFNOKTfwsQ==", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", "requires": { "load-json-file": "^1.0.0", "normalize-package-data": "^2.3.2", @@ -15322,7 +15322,7 @@ "read-pkg-up": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", - "integrity": "sha512-WD9MTlNtI55IwYUS27iHh9tK3YoIVhxis8yKhLpTqWtml739uXc9NWTpxoHkfZf3+DkCCsXox94/VWZniuZm6A==", + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", "requires": { "find-up": "^1.0.0", "read-pkg": "^1.0.0" @@ -15547,7 +15547,7 @@ "repeating": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", - "integrity": "sha512-ZqtSMuVybkISo2OWvqvm7iHSWngvdaW3IpsT9/uP8v4gMi591LY6h35wdOfvQdWCKFWZWm2Y1Opp4kV7vQKT6A==", + "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", "requires": { "is-finite": "^1.0.0" } @@ -15594,7 +15594,7 @@ "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==" + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" }, "require-from-string": { "version": "2.0.2", @@ -15896,7 +15896,7 @@ "scss-tokenizer": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz", - "integrity": "sha512-dYE8LhncfBUar6POCxMTm0Ln+erjeczqEvCJib5/7XNkdw1FkUGgwMPY360FY0FgPWQxHWCx29Jl3oejyGLM9Q==", + "integrity": "sha1-jrBtualyMzOCTT9VMGQRSYR85dE=", "requires": { "js-base64": "^2.1.8", "source-map": "^0.4.2" @@ -15905,7 +15905,7 @@ "source-map": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", - "integrity": "sha512-Y8nIfcb1s/7DcobUz1yOO1GSp7gyL+D9zLHDehT7iRESqGSxjJ448Sg7rvfgsRJCnKLdSl11uGf0s9X80cH0/A==", + "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", "requires": { "amdefine": ">=0.0.4" } @@ -16053,7 +16053,7 @@ "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" }, "setprototypeof": { "version": "1.2.0", @@ -16096,7 +16096,7 @@ "lru-cache": { "version": "2.7.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", - "integrity": "sha512-WpibWJ60c3AgAz8a2iYErDrcT2C7OmKnsWhIcHOjkUHFjkXncJhtLxNSqUmxRxRunpb5I8Vprd7aNSd2NtksJQ==" + "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=" } } }, @@ -16148,7 +16148,7 @@ "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==" + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" }, "source-map-js": { "version": "1.0.2", @@ -16324,7 +16324,7 @@ "statuses": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==" + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" }, "stdout-stream": { "version": "1.4.1", @@ -16402,7 +16402,7 @@ "string-width": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -16487,7 +16487,7 @@ "strip-ansi": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "requires": { "ansi-regex": "^2.0.0" } @@ -16495,7 +16495,7 @@ "strip-bom": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha512-kwrX1y7czp1E69n2ajbG65mIo9dqvJ+8aBQXOGVxqwvNbsXdFM6Lq37dLAY3mknUwru8CfcCbfOLL/gMo+fi3g==", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", "requires": { "is-utf8": "^0.2.0" } @@ -16924,7 +16924,7 @@ "to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==" + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=" }, "to-regex-range": { "version": "5.0.1", @@ -16943,7 +16943,7 @@ "toposort": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", - "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==" + "integrity": "sha1-riF2gXXRVZ1IvvNUILL0li8JwzA=" }, "tough-cookie": { "version": "2.5.0", @@ -16966,7 +16966,7 @@ "trim-newlines": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", - "integrity": "sha512-Nm4cF79FhSTzrLKGDMi3I4utBtFv8qKy4sq1enftf2gMdpqI8oVQTAfySkTz5r49giVzDj88SVZXP4CeYQwjaw==" + "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=" }, "true-case-path": { "version": "1.0.3", @@ -17042,7 +17042,7 @@ "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", "requires": { "safe-buffer": "^5.0.1" } @@ -17050,7 +17050,7 @@ "tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==" + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" }, "type-check": { "version": "0.4.0", @@ -17201,7 +17201,7 @@ "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" }, "unquote": { "version": "1.1.1", @@ -17258,7 +17258,7 @@ "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "util.promisify": { "version": "1.0.1", @@ -17281,7 +17281,7 @@ "utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==" + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" }, "uuid": { "version": "8.3.2", @@ -17316,12 +17316,12 @@ "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" }, "verror": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", "requires": { "assert-plus": "^1.0.0", "core-util-is": "1.0.2", @@ -18025,7 +18025,7 @@ "is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==" + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" }, "string-width": { "version": "3.1.0", @@ -18050,7 +18050,7 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "write-file-atomic": { "version": "4.0.2", @@ -18071,7 +18071,7 @@ "xml": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", - "integrity": "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==", + "integrity": "sha1-eLpyAgApxbyHuKgaPPzXS0ovweU=", "dev": true }, "xml-name-validator": { @@ -18134,7 +18134,7 @@ "is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==" + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" }, "string-width": { "version": "3.1.0", diff --git a/app/src/hooks/useKeycloakWrapper.tsx b/app/src/hooks/useKeycloakWrapper.tsx index e3a9d10d71..02369ace20 100644 --- a/app/src/hooks/useKeycloakWrapper.tsx +++ b/app/src/hooks/useKeycloakWrapper.tsx @@ -108,16 +108,39 @@ export interface IKeycloakWrapper { * @memberof IKeycloakWrapper */ getIdentitySource: () => string | null; - /** * Get the user guid * * @memberof IKeycloakWrapper */ getUserGuid: () => string | null; + /** + * The user's auth username.F + * + * @type {(string | undefined)} + * @memberof IKeycloakWrapper + */ username: string | undefined; + /** + * The user's display name. + * + * @type {(string | undefined)} + * @memberof IKeycloakWrapper + */ displayName: string | undefined; + /** + * The user's email. + * + * @type {(string | undefined)} + * @memberof IKeycloakWrapper + */ email: string | undefined; + /** + * The user's system user id. + * + * @type {(number | undefined)} + * @memberof IKeycloakWrapper + */ systemUserId: number | undefined; /** * Force this keycloak wrapper to refresh its data. @@ -132,8 +155,18 @@ export interface IKeycloakWrapper { * @memberof IKeycloakWrapper */ getLoginUrl: (redirectUri?: string) => string; - + /** + * The logged in user's data. + * + * @type {(ISystemUser | undefined)} + * @memberof IKeycloakWrapper + */ user: ISystemUser | undefined; + /** + * The critterbase Uuid. + * + * @memberof IKeycloakWrapper + */ critterbaseUuid: () => string | undefined; } @@ -306,7 +339,7 @@ function useKeycloakWrapper(): IKeycloakWrapper { }; const user = (): ISystemUser | undefined => { - return userDataLoader?.data; + return userDataLoader.data; }; const critterbaseUuid = useCallback(() => {