diff --git a/src/oneTrust/endpoints/getOneTrustUser.ts b/src/oneTrust/endpoints/getOneTrustUser.ts new file mode 100644 index 00000000..aa3d3954 --- /dev/null +++ b/src/oneTrust/endpoints/getOneTrustUser.ts @@ -0,0 +1,117 @@ +import * as t from 'io-ts'; +import { Got } from 'got'; +import { decodeCodec } from '@transcend-io/type-utils'; + +/** + * FIXME: move to privacy-types + */ + +// Nested types first +const OneTrustUserMetadata = t.type({ + /** When the user was created */ + created: t.string, + /** When the user was modified */ + lastModified: t.string, + /** The URI for accessing information about the user */ + location: t.string, + /** The resource type */ + resourceType: t.literal('User'), +}); +/** Type override */ +export type OneTrustUserMetadata = t.TypeOf; + +const OneTrustUserName = t.type({ + /** The user's family name */ + familyName: t.string, + /** The user's given name */ + givenName: t.string, +}); +/** Type override */ +export type OneTrustUserName = t.TypeOf; + +const OneTrustUserGroup = t.type({ + /** The user group's identification */ + value: t.string, + /** The user group's display name */ + display: t.string, +}); +/** Type override */ +export type OneTrustUserGroup = t.TypeOf; + +const OneTrustUserGroups = t.array(OneTrustUserGroup); +/** Type override */ +export type OneTrustUserGroups = t.TypeOf; + +const OneTrustUserEmail = t.type({ + /** The email value */ + value: t.string, + /** The email display name */ + display: t.string, + /** Whether this is the primary email */ + primary: t.boolean, + /** The email type */ + type: t.union([t.string, t.literal('work')]), +}); +/** Type override */ +export type OneTrustUserEmail = t.TypeOf; + +const OneTrustUserEmails = t.array(OneTrustUserEmail); +/** Type override */ +export type OneTrustUserEmails = t.TypeOf; + +/** + * The response type of the OneTrust GetUser endpoint + * ref: https://developer.onetrust.com/onetrust/reference/getriskusingget + */ +export const OneTrustGetUserResponse = t.type({ + /** ID of the user. */ + id: t.string, + /** External ID of the user. */ + externalId: t.union([t.string, t.null]), + /** Metadata of the user. */ + meta: OneTrustUserMetadata, + /** Schemas of the user */ + schemas: t.array(t.string), + /** Name or email of the user */ + userName: t.string, + /** Full name of the user */ + name: OneTrustUserName, + /** Type of the user */ + userType: t.union([t.string, t.literal('Internal')]), + /** Flag to check if the user is an active user or not. */ + active: t.boolean, + /** The groups that the user belongs to */ + groups: OneTrustUserGroups, + /** The emails of the user */ + emails: OneTrustUserEmails, + /** The roles of the user */ + roles: t.array(t.string), + /** The title of the user */ + title: t.union([t.string, t.null]), +}); + +/** Type override */ + +/** Type override */ +export type OneTrustGetUserResponse = t.TypeOf; + +/** + * Retrieve details about a particular user. + * ref: https://developer.onetrust.com/onetrust/reference/getriskusingget + * + * @param param - the information about the OneTrust client and risk to retrieve + * @returns the OneTrust risk + */ +export const getOneTrustUser = async ({ + oneTrust, + creatorId, +}: { + /** The OneTrust client instance */ + oneTrust: Got; + /** The ID of the OneTrust user to retrieve */ + creatorId: string; +}): Promise => { + const { body } = await oneTrust.get(`api/scim/v2/Users/${creatorId}`); + + return decodeCodec(OneTrustGetUserResponse, body); +}; diff --git a/src/oneTrust/helpers/syncOneTrustAssessments.ts b/src/oneTrust/helpers/syncOneTrustAssessments.ts index b68d02fb..d9353bca 100644 --- a/src/oneTrust/helpers/syncOneTrustAssessments.ts +++ b/src/oneTrust/helpers/syncOneTrustAssessments.ts @@ -1,5 +1,6 @@ import { Got } from 'got/dist/source'; import { + OneTrustGetUserResponse, getListOfOneTrustAssessments, getOneTrustAssessment, getOneTrustRisk, @@ -48,6 +49,9 @@ export const syncOneTrustAssessments = async ({ logger.info('Getting list of all assessments from OneTrust...'); const assessments = await getListOfOneTrustAssessments({ oneTrust }); + // a cache of OneTrust users so we avoid sending requests for users already fetched + const allOneTrustUsers: Record = {}; + /** * fetch details about each assessment in series and write to transcend or to disk * (depending on the dryRun argument) right away to avoid running out of memory @@ -63,11 +67,20 @@ export const syncOneTrustAssessments = async ({ assessmentId: assessment.assessmentId, }); - // enrich assessments with user information - const creator = await getOneTrustUser({ - oneTrust, - creatorId: assessmentDetails.createdBy.id, - }); + // fetch assessment's creator information + logger.info( + `Fetching details about assessment ${index + 1} of ${ + assessments.length + } creator...`, + ); + const creatorId = assessmentDetails.createdBy.id; + const creator = + allOneTrustUsers[creatorId] ?? + (await getOneTrustUser({ + oneTrust, + creatorId: assessmentDetails.createdBy.id, + })); + allOneTrustUsers[creatorId] = creator; /** * FIXME: enrich rootRequestInformationIds @@ -75,7 +88,7 @@ export const syncOneTrustAssessments = async ({ // console.log({ creator }); - // enrich assessments with risk information + // fetch assessment risk information let riskDetails: OneTrustGetRiskResponse[] = []; const riskIds = uniq( assessmentDetails.sections.flatMap((s: OneTrustAssessmentSection) =>