From 81dcdf66f2b468d3bdccd6ba59d159401619606e Mon Sep 17 00:00:00 2001 From: Kushal Arora Date: Mon, 13 Feb 2023 17:39:07 -0800 Subject: [PATCH 01/18] [UTOPIA-769] [Backend] [pia-intake.collectionUseAndDisclosure] Updated pia-intake entity and associated jsonb classes and dtos for collectionUseAndDisclosure --- .../common/interfaces/form-field.interface.ts | 8 ++++ .../pia-intake/dto/create-pia-intake.dto.ts | 37 +++++++++++++- .../pia-intake/entities/pia-intake.entity.ts | 9 ++++ .../collection-notice.class.ts | 28 +++++++++++ .../index.class.ts | 23 +++++++++ .../steps-walkthrough.class.ts | 48 +++++++++++++++++++ .../test/util/mocks/data/pia-intake.mock.ts | 20 ++++++++ 7 files changed, 172 insertions(+), 1 deletion(-) create mode 100644 src/backend/src/common/interfaces/form-field.interface.ts create mode 100644 src/backend/src/modules/pia-intake/jsonb-classes/collection-use-and-disclosure/collection-notice.class.ts create mode 100644 src/backend/src/modules/pia-intake/jsonb-classes/collection-use-and-disclosure/index.class.ts create mode 100644 src/backend/src/modules/pia-intake/jsonb-classes/collection-use-and-disclosure/steps-walkthrough.class.ts diff --git a/src/backend/src/common/interfaces/form-field.interface.ts b/src/backend/src/common/interfaces/form-field.interface.ts new file mode 100644 index 000000000..75b5a20c5 --- /dev/null +++ b/src/backend/src/common/interfaces/form-field.interface.ts @@ -0,0 +1,8 @@ +import { UserTypesEnum } from '../enums/users.enum'; + +export interface IFormField { + key: keyof T; + type: 'text'; // add ORs for future support if needed + isRichText: boolean; + allowedUserTypesEdit: Array; // null if no role restrictions apply +} diff --git a/src/backend/src/modules/pia-intake/dto/create-pia-intake.dto.ts b/src/backend/src/modules/pia-intake/dto/create-pia-intake.dto.ts index 86b51689d..f87d04ae6 100644 --- a/src/backend/src/modules/pia-intake/dto/create-pia-intake.dto.ts +++ b/src/backend/src/modules/pia-intake/dto/create-pia-intake.dto.ts @@ -3,14 +3,18 @@ import { IsDateString, IsEmail, IsEnum, + IsObject, IsOptional, IsString, + ValidateNested, } from '@nestjs/class-validator'; import { ApiProperty } from '@nestjs/swagger'; +import { Type } from 'class-transformer'; import { GovMinistriesEnum } from '../../../common/enums/gov-ministries.enum'; import { PiaIntakeStatusEnum } from '../enums/pia-intake-status.enum'; +import { CollectionUseAndDisclosure } from '../jsonb-classes/collection-use-and-disclosure/index.class'; -export const piaIntakeEntityMock = { +export const piaIntakeEntityMock: CreatePiaIntakeDto = { title: 'Test PIA for screening King Richard', ministry: GovMinistriesEnum.TOURISM_ARTS_CULTURE_AND_SPORT, branch: 'Entertainment', @@ -38,6 +42,26 @@ export const piaIntakeEntityMock = { hasAddedPiToDataElements: false, submittedAt: new Date(), riskMitigation: `The film was released on [Blu-ray](https://en.wikipedia.org/wiki/Blu-ray) and [DVD](https://en.wikipedia.org/wiki/DVD) February 8, 2022 by [Warner Bros. Home Entertainment](https://en.wikipedia.org/wiki/Warner_Bros._Home_Entertainment), with the 4K Ultra HD release through [Warner Archive Collection](https://en.wikipedia.org/wiki/Warner_Archive_Collection) on the same date.`, + collectionUseAndDisclosure: { + steps: [ + { + drafterInput: 'Make a Checklist.', + mpoInput: 'Agreed', + foippaInput: 'Agreed', + OtherInput: 'Agreed', + }, + { + drafterInput: 'Set Your Budget.', + mpoInput: 'Set precise budget', + foippaInput: 'Agreed', + OtherInput: 'Agreed', + }, + ], + collectionNotice: { + drafterInput: 'Test Input', + mpoInput: 'Updated Input', + }, + }, }; export class CreatePiaIntakeDto { @@ -205,4 +229,15 @@ export class CreatePiaIntakeDto { example: new Date(), }) submittedAt: Date; + + @IsObject() + @IsOptional() + @ValidateNested() + @Type(() => CollectionUseAndDisclosure) + @ApiProperty({ + type: CollectionUseAndDisclosure, + required: false, + example: piaIntakeEntityMock.collectionUseAndDisclosure, + }) + collectionUseAndDisclosure: CollectionUseAndDisclosure; } diff --git a/src/backend/src/modules/pia-intake/entities/pia-intake.entity.ts b/src/backend/src/modules/pia-intake/entities/pia-intake.entity.ts index b6e06ce80..c6d06460a 100644 --- a/src/backend/src/modules/pia-intake/entities/pia-intake.entity.ts +++ b/src/backend/src/modules/pia-intake/entities/pia-intake.entity.ts @@ -2,6 +2,7 @@ import { GovMinistriesEnum } from '../../../common/enums/gov-ministries.enum'; import { Column, Entity } from 'typeorm'; import { BaseEntity } from '../../../common/entities/base.entity'; import { PiaIntakeStatusEnum } from '../enums/pia-intake-status.enum'; +import { CollectionUseAndDisclosure } from '../jsonb-classes/collection-use-and-disclosure/index.class'; @Entity('pia-intake') export class PiaIntakeEntity extends BaseEntity { @@ -129,4 +130,12 @@ export class PiaIntakeEntity extends BaseEntity { default: null, }) submittedAt: Date; + + @Column({ + name: 'collection_use_and_disclosure', + type: 'jsonb', + nullable: false, + default: () => "'{}'", + }) + collectionUseAndDisclosure: CollectionUseAndDisclosure; } diff --git a/src/backend/src/modules/pia-intake/jsonb-classes/collection-use-and-disclosure/collection-notice.class.ts b/src/backend/src/modules/pia-intake/jsonb-classes/collection-use-and-disclosure/collection-notice.class.ts new file mode 100644 index 000000000..322215eb6 --- /dev/null +++ b/src/backend/src/modules/pia-intake/jsonb-classes/collection-use-and-disclosure/collection-notice.class.ts @@ -0,0 +1,28 @@ +import { IsOptional, IsString } from '@nestjs/class-validator'; +import { UserTypesEnum } from 'src/common/enums/users.enum'; +import { IFormField } from 'src/common/interfaces/form-field.interface'; + +export class CollectionNotice { + @IsString() + @IsOptional() + drafterInput?: string; + + @IsString() + @IsOptional() + mpoInput?: string; +} + +export const stepWalkthroughDetails: Array> = [ + { + key: 'drafterInput', + type: 'text', + isRichText: true, + allowedUserTypesEdit: null, // any + }, + { + key: 'mpoInput', + type: 'text', + isRichText: true, + allowedUserTypesEdit: [UserTypesEnum.MPO], + }, +]; diff --git a/src/backend/src/modules/pia-intake/jsonb-classes/collection-use-and-disclosure/index.class.ts b/src/backend/src/modules/pia-intake/jsonb-classes/collection-use-and-disclosure/index.class.ts new file mode 100644 index 000000000..044b7b308 --- /dev/null +++ b/src/backend/src/modules/pia-intake/jsonb-classes/collection-use-and-disclosure/index.class.ts @@ -0,0 +1,23 @@ +import { + IsArray, + IsObject, + IsOptional, + ValidateNested, +} from '@nestjs/class-validator'; +import { Type } from 'class-transformer'; +import { CollectionNotice } from './collection-notice.class'; +import { StepWalkthrough } from './steps-walkthrough.class'; + +export class CollectionUseAndDisclosure { + @IsArray() + @IsOptional() + @ValidateNested({ each: true }) + @Type(() => StepWalkthrough) + steps: Array; + + @IsObject() + @IsOptional() + @ValidateNested() + @Type(() => CollectionNotice) + collectionNotice: CollectionNotice; +} diff --git a/src/backend/src/modules/pia-intake/jsonb-classes/collection-use-and-disclosure/steps-walkthrough.class.ts b/src/backend/src/modules/pia-intake/jsonb-classes/collection-use-and-disclosure/steps-walkthrough.class.ts new file mode 100644 index 000000000..d81b74e19 --- /dev/null +++ b/src/backend/src/modules/pia-intake/jsonb-classes/collection-use-and-disclosure/steps-walkthrough.class.ts @@ -0,0 +1,48 @@ +import { IsOptional, IsString } from '@nestjs/class-validator'; +import { UserTypesEnum } from 'src/common/enums/users.enum'; +import { IFormField } from 'src/common/interfaces/form-field.interface'; + +export class StepWalkthrough { + @IsString() + @IsOptional() + drafterInput?: string; + + @IsString() + @IsOptional() + mpoInput?: string; + + @IsString() + @IsOptional() + foippaInput?: string; + + @IsString() + @IsOptional() + OtherInput?: string; +} + +export const stepWalkthroughDetails: Array> = [ + { + key: 'drafterInput', + type: 'text', + isRichText: false, + allowedUserTypesEdit: null, // any + }, + { + key: 'mpoInput', + type: 'text', + isRichText: false, + allowedUserTypesEdit: [UserTypesEnum.MPO], + }, + { + key: 'foippaInput', + type: 'text', + isRichText: false, + allowedUserTypesEdit: [UserTypesEnum.MPO], + }, + { + key: 'OtherInput', + type: 'text', + isRichText: false, + allowedUserTypesEdit: [UserTypesEnum.MPO], + }, +]; diff --git a/src/backend/test/util/mocks/data/pia-intake.mock.ts b/src/backend/test/util/mocks/data/pia-intake.mock.ts index c97c67a3f..6a6c333be 100644 --- a/src/backend/test/util/mocks/data/pia-intake.mock.ts +++ b/src/backend/test/util/mocks/data/pia-intake.mock.ts @@ -34,6 +34,26 @@ const piaIntakeDataMock = { status: PiaIntakeStatusEnum.MPO_REVIEW, saveId: 1, submittedAt: new Date(), + collectionUseAndDisclosure: { + steps: [ + { + drafterInput: 'Make a Checklist.', + mpoInput: 'Agreed', + foippaInput: 'Agreed', + OtherInput: 'Agreed', + }, + { + drafterInput: 'Set Your Budget.', + mpoInput: 'Set precise budget', + foippaInput: 'Agreed', + OtherInput: 'Agreed', + }, + ], + collectionNotice: { + drafterInput: 'Test Input', + mpoInput: 'Updated Input', + }, + }, }; export const piaIntakeEntityMock: PiaIntakeEntity = { From 2e5b2b1c4b3872daadc5049b84d5ac355b1094ee Mon Sep 17 00:00:00 2001 From: Kushal Arora Date: Mon, 13 Feb 2023 17:39:37 -0800 Subject: [PATCH 02/18] [UTOPIA-769] [Backend] [pia-intake] Update Sort type fix to fix breaking test --- .../constants/pia-intake-allowed-sort-fields.ts | 11 ++++++----- .../pia-intake/dto/pia-intake-find-query.dto.ts | 8 +++++--- .../src/modules/pia-intake/pia-intake.service.ts | 5 ++++- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/backend/src/modules/pia-intake/constants/pia-intake-allowed-sort-fields.ts b/src/backend/src/modules/pia-intake/constants/pia-intake-allowed-sort-fields.ts index 16d38e19b..f23d5d0da 100644 --- a/src/backend/src/modules/pia-intake/constants/pia-intake-allowed-sort-fields.ts +++ b/src/backend/src/modules/pia-intake/constants/pia-intake-allowed-sort-fields.ts @@ -1,6 +1,7 @@ -import { PiaIntakeEntity } from '../entities/pia-intake.entity'; +export type PiaIntakeAllowedSortFieldsType = + | 'drafterName' + | 'updatedAt' + | 'createdAt'; -export const PiaIntakeAllowedSortFields: Array = [ - 'drafterName', - 'updatedAt', -]; +export const PiaIntakeAllowedSortFields: Array = + ['drafterName', 'updatedAt', 'createdAt']; diff --git a/src/backend/src/modules/pia-intake/dto/pia-intake-find-query.dto.ts b/src/backend/src/modules/pia-intake/dto/pia-intake-find-query.dto.ts index 96de8ec7c..1ac45f654 100644 --- a/src/backend/src/modules/pia-intake/dto/pia-intake-find-query.dto.ts +++ b/src/backend/src/modules/pia-intake/dto/pia-intake-find-query.dto.ts @@ -10,8 +10,10 @@ import { ApiProperty } from '@nestjs/swagger'; import { Transform } from 'class-transformer'; import { GovMinistriesEnum } from 'src/common/enums/gov-ministries.enum'; import { SortOrderEnum } from 'src/common/enums/sort-order.enum'; -import { PiaIntakeAllowedSortFields } from '../constants/pia-intake-allowed-sort-fields'; -import { PiaIntakeEntity } from '../entities/pia-intake.entity'; +import { + PiaIntakeAllowedSortFields, + PiaIntakeAllowedSortFieldsType, +} from '../constants/pia-intake-allowed-sort-fields'; import { PiaFilterDrafterByCurrentUserEnum } from '../enums/pia-filter-drafter-by-current-user.enum'; import { PiaIntakeStatusEnum } from '../enums/pia-intake-status.enum'; import { piaIntakeEntityMock } from './create-pia-intake.dto'; @@ -86,7 +88,7 @@ export class PiaIntakeFindQuery { @IsString() @IsIn(PiaIntakeAllowedSortFields) @IsOptional() - readonly sortBy?: keyof PiaIntakeEntity; + readonly sortBy?: PiaIntakeAllowedSortFieldsType; @ApiProperty({ required: false, diff --git a/src/backend/src/modules/pia-intake/pia-intake.service.ts b/src/backend/src/modules/pia-intake/pia-intake.service.ts index f2a92b262..95218c6e1 100644 --- a/src/backend/src/modules/pia-intake/pia-intake.service.ts +++ b/src/backend/src/modules/pia-intake/pia-intake.service.ts @@ -27,6 +27,7 @@ import { PaginatedRO } from 'src/common/paginated.ro'; import { SortOrderEnum } from 'src/common/enums/sort-order.enum'; import { PiaFilterDrafterByCurrentUserEnum } from './enums/pia-filter-drafter-by-current-user.enum'; import { PiaIntakeStatusEnum } from './enums/pia-intake-status.enum'; +import { PiaIntakeAllowedSortFieldsType } from './constants/pia-intake-allowed-sort-fields'; @Injectable() export class PiaIntakeService { @@ -271,7 +272,9 @@ export class PiaIntakeService { /* ********** CONDITIONAL WHERE CLAUSE ENDS ********** */ /* ********** SORT LOGIC BEGINS ********** */ - const orderBy: Partial> = {}; + const orderBy: Partial< + Record + > = {}; // if sortBy is provided, sort the filtered records by the provided field // sortOrder can be as provided or by default descending From 76148b564f832e27a9a748d5c1ac6142d7720968 Mon Sep 17 00:00:00 2001 From: Kushal Arora Date: Tue, 14 Feb 2023 09:27:03 -0800 Subject: [PATCH 03/18] [Backend] [pia-intake.storingPersonalInformation] Updated pia-intake entity and associated jsonb classes and dtos for storingPersonalInformation --- .../src/common/enums/yes-no-input.enum.ts | 4 ++ .../pia-intake/dto/create-pia-intake.dto.ts | 59 +++++++++++++++++++ .../disclosures-outside-canda/index.class.ts | 34 +++++++++++ .../disclosures-outside-canda/section-1.ts | 39 ++++++++++++ .../disclosures-outside-canda/section-2.ts | 12 ++++ .../disclosures-outside-canda/section-3.ts | 7 +++ .../disclosures-outside-canda/section-4.ts | 7 +++ .../disclosures-outside-canda/section-5.ts | 29 +++++++++ .../index.class.ts | 22 +++++++ .../personal-information.class.ts | 12 ++++ .../sensitive-personal-information.class.ts | 12 ++++ .../test/util/mocks/data/pia-intake.mock.ts | 47 +++++++++++++++ 12 files changed, 284 insertions(+) create mode 100644 src/backend/src/common/enums/yes-no-input.enum.ts create mode 100644 src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/index.class.ts create mode 100644 src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/section-1.ts create mode 100644 src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/section-2.ts create mode 100644 src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/section-3.ts create mode 100644 src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/section-4.ts create mode 100644 src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/section-5.ts create mode 100644 src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/index.class.ts create mode 100644 src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/personal-information.class.ts create mode 100644 src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/sensitive-personal-information.class.ts diff --git a/src/backend/src/common/enums/yes-no-input.enum.ts b/src/backend/src/common/enums/yes-no-input.enum.ts new file mode 100644 index 000000000..ffe2e777b --- /dev/null +++ b/src/backend/src/common/enums/yes-no-input.enum.ts @@ -0,0 +1,4 @@ +export enum YesNoInput { + YES = 'YES', + NO = 'NO', +} diff --git a/src/backend/src/modules/pia-intake/dto/create-pia-intake.dto.ts b/src/backend/src/modules/pia-intake/dto/create-pia-intake.dto.ts index f87d04ae6..f227b27c6 100644 --- a/src/backend/src/modules/pia-intake/dto/create-pia-intake.dto.ts +++ b/src/backend/src/modules/pia-intake/dto/create-pia-intake.dto.ts @@ -10,9 +10,11 @@ import { } from '@nestjs/class-validator'; import { ApiProperty } from '@nestjs/swagger'; import { Type } from 'class-transformer'; +import { YesNoInput } from 'src/common/enums/yes-no-input.enum'; import { GovMinistriesEnum } from '../../../common/enums/gov-ministries.enum'; import { PiaIntakeStatusEnum } from '../enums/pia-intake-status.enum'; import { CollectionUseAndDisclosure } from '../jsonb-classes/collection-use-and-disclosure/index.class'; +import { StoringPersonalInformation } from '../jsonb-classes/storing-personal-information/index.class'; export const piaIntakeEntityMock: CreatePiaIntakeDto = { title: 'Test PIA for screening King Richard', @@ -62,6 +64,52 @@ export const piaIntakeEntityMock: CreatePiaIntakeDto = { mpoInput: 'Updated Input', }, }, + storingPersonalInformation: { + personalInformation: { + storedOutsideCanada: YesNoInput.YES, + whereDetails: 'USA', + }, + sensitivePersonalInformation: { + doesInvolve: YesNoInput.YES, + disclosedOutsideCanada: YesNoInput.NO, + }, + disclosuresOutsideCanada: { + section1: { + sensitiveInfoStoredByServiceProvider: YesNoInput.YES, + serviceProviderList: [ + { + name: 'Amazon', + cloudInfraName: 'AWS', + details: 'Stored in cloud', + }, + ], + disclosureDetails: 'S3 storage in us-east-1: US East (N. Virginia)', + contractualTerms: 'None', + }, + section2: { + relyOnExistingContract: YesNoInput.YES, + enterpriseServiceAccessDetails: 'S3', + }, + section3: { + unauthorizedAccessMeasures: 'IAM rules are in effect', + }, + section4: { + trackAccessDetails: 'IAM', + }, + section5: { + privacyRisks: [ + { + risk: 'Leak of Creds', + impact: 'Access of instance', + likelihoodOfUnauthorizedAccess: 'Medium', + levelOfPrivacyRisk: 'Medium', + riskResponse: 'immediately revoke', + outstandingRisk: 'No', + }, + ], + }, + }, + }, }; export class CreatePiaIntakeDto { @@ -240,4 +288,15 @@ export class CreatePiaIntakeDto { example: piaIntakeEntityMock.collectionUseAndDisclosure, }) collectionUseAndDisclosure: CollectionUseAndDisclosure; + + @IsObject() + @IsOptional() + @ValidateNested() + @Type(() => StoringPersonalInformation) + @ApiProperty({ + type: StoringPersonalInformation, + required: false, + example: piaIntakeEntityMock.storingPersonalInformation, + }) + storingPersonalInformation: StoringPersonalInformation; } diff --git a/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/index.class.ts b/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/index.class.ts new file mode 100644 index 000000000..9e53a2ebe --- /dev/null +++ b/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/index.class.ts @@ -0,0 +1,34 @@ +import { IsObject, ValidateNested } from '@nestjs/class-validator'; +import { Type } from 'class-transformer'; +import { DisclosureSection1 } from './section-1'; +import { DisclosureSection2 } from './section-2'; +import { DisclosureSection3 } from './section-3'; +import { DisclosureSection4 } from './section-4'; +import { DisclosureSection5 } from './section-5'; + +export class DisclosuresOutsideCanada { + @IsObject() + @ValidateNested() + @Type(() => DisclosureSection1) + section1: DisclosureSection1; + + @IsObject() + @ValidateNested() + @Type(() => DisclosureSection2) + section2: DisclosureSection2; + + @IsObject() + @ValidateNested() + @Type(() => DisclosureSection3) + section3: DisclosureSection3; + + @IsObject() + @ValidateNested() + @Type(() => DisclosureSection4) + section4: DisclosureSection4; + + @IsObject() + @ValidateNested() + @Type(() => DisclosureSection5) + section5: DisclosureSection5; +} diff --git a/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/section-1.ts b/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/section-1.ts new file mode 100644 index 000000000..d9f4e8eb3 --- /dev/null +++ b/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/section-1.ts @@ -0,0 +1,39 @@ +import { + IsArray, + IsEnum, + IsOptional, + IsString, + ValidateNested, +} from '@nestjs/class-validator'; +import { Type } from 'class-transformer'; +import { YesNoInput } from 'src/common/enums/yes-no-input.enum'; + +class ServiceProviderDetails { + @IsString() + name: string; + + @IsString() + cloudInfraName: string; + + @IsString() + details: string; +} + +export class DisclosureSection1 { + @IsEnum(YesNoInput) + @IsOptional() + sensitiveInfoStoredByServiceProvider: YesNoInput; + + @IsArray() + @ValidateNested({ each: true }) + @Type(() => ServiceProviderDetails) + serviceProviderList: Array; + + @IsString() + @IsOptional() + disclosureDetails: string; + + @IsString() + @IsOptional() + contractualTerms: string; +} diff --git a/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/section-2.ts b/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/section-2.ts new file mode 100644 index 000000000..1cc407146 --- /dev/null +++ b/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/section-2.ts @@ -0,0 +1,12 @@ +import { IsEnum, IsOptional, IsString } from '@nestjs/class-validator'; +import { YesNoInput } from 'src/common/enums/yes-no-input.enum'; + +export class DisclosureSection2 { + @IsEnum(YesNoInput) + @IsOptional() + relyOnExistingContract: YesNoInput; + + @IsString() + @IsOptional() + enterpriseServiceAccessDetails: string; +} diff --git a/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/section-3.ts b/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/section-3.ts new file mode 100644 index 000000000..50332af37 --- /dev/null +++ b/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/section-3.ts @@ -0,0 +1,7 @@ +import { IsOptional, IsString } from '@nestjs/class-validator'; + +export class DisclosureSection3 { + @IsString() + @IsOptional() + unauthorizedAccessMeasures: string; +} diff --git a/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/section-4.ts b/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/section-4.ts new file mode 100644 index 000000000..1de7cc837 --- /dev/null +++ b/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/section-4.ts @@ -0,0 +1,7 @@ +import { IsOptional, IsString } from '@nestjs/class-validator'; + +export class DisclosureSection4 { + @IsString() + @IsOptional() + trackAccessDetails: string; +} diff --git a/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/section-5.ts b/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/section-5.ts new file mode 100644 index 000000000..884f8768f --- /dev/null +++ b/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/section-5.ts @@ -0,0 +1,29 @@ +import { IsArray, IsString, ValidateNested } from '@nestjs/class-validator'; +import { Type } from 'class-transformer'; + +class PrivacyRisk { + @IsString() + risk: string; + + @IsString() + impact: string; + + @IsString() + likelihoodOfUnauthorizedAccess: string; + + @IsString() + levelOfPrivacyRisk: string; + + @IsString() + riskResponse: string; + + @IsString() + outstandingRisk: string; +} + +export class DisclosureSection5 { + @IsArray() + @ValidateNested({ each: true }) + @Type(() => PrivacyRisk) + privacyRisks: Array; +} diff --git a/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/index.class.ts b/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/index.class.ts new file mode 100644 index 000000000..9598d2928 --- /dev/null +++ b/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/index.class.ts @@ -0,0 +1,22 @@ +import { IsObject, ValidateNested } from '@nestjs/class-validator'; +import { Type } from 'class-transformer'; +import { DisclosuresOutsideCanada } from './disclosures-outside-canda/index.class'; +import { PersonalInformation } from './personal-information.class'; +import { SensitivePersonalInformation } from './sensitive-personal-information.class'; + +export class StoringPersonalInformation { + @IsObject() + @ValidateNested() + @Type(() => PersonalInformation) + personalInformation: PersonalInformation; + + @IsObject() + @ValidateNested() + @Type(() => SensitivePersonalInformation) + sensitivePersonalInformation: SensitivePersonalInformation; + + @IsObject() + @ValidateNested() + @Type(() => DisclosuresOutsideCanada) + disclosuresOutsideCanada: DisclosuresOutsideCanada; +} diff --git a/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/personal-information.class.ts b/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/personal-information.class.ts new file mode 100644 index 000000000..1bd48ef63 --- /dev/null +++ b/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/personal-information.class.ts @@ -0,0 +1,12 @@ +import { IsEnum, IsOptional, IsString } from '@nestjs/class-validator'; +import { YesNoInput } from 'src/common/enums/yes-no-input.enum'; + +export class PersonalInformation { + @IsEnum(YesNoInput) + @IsOptional() + storedOutsideCanada: YesNoInput; + + @IsString() + @IsOptional() + whereDetails: string; +} diff --git a/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/sensitive-personal-information.class.ts b/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/sensitive-personal-information.class.ts new file mode 100644 index 000000000..233684066 --- /dev/null +++ b/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/sensitive-personal-information.class.ts @@ -0,0 +1,12 @@ +import { IsEnum, IsOptional } from '@nestjs/class-validator'; +import { YesNoInput } from 'src/common/enums/yes-no-input.enum'; + +export class SensitivePersonalInformation { + @IsEnum(YesNoInput) + @IsOptional() + doesInvolve: YesNoInput; + + @IsEnum(YesNoInput) + @IsOptional() + disclosedOutsideCanada: YesNoInput; +} diff --git a/src/backend/test/util/mocks/data/pia-intake.mock.ts b/src/backend/test/util/mocks/data/pia-intake.mock.ts index 6a6c333be..b70f41ad6 100644 --- a/src/backend/test/util/mocks/data/pia-intake.mock.ts +++ b/src/backend/test/util/mocks/data/pia-intake.mock.ts @@ -1,4 +1,5 @@ import { GovMinistriesEnum } from 'src/common/enums/gov-ministries.enum'; +import { YesNoInput } from 'src/common/enums/yes-no-input.enum'; import { CreatePiaIntakeDto } from 'src/modules/pia-intake/dto/create-pia-intake.dto'; import { PiaIntakeEntity } from 'src/modules/pia-intake/entities/pia-intake.entity'; import { PiaIntakeStatusEnum } from 'src/modules/pia-intake/enums/pia-intake-status.enum'; @@ -54,6 +55,52 @@ const piaIntakeDataMock = { mpoInput: 'Updated Input', }, }, + storingPersonalInformation: { + personalInformation: { + storedOutsideCanada: YesNoInput.YES, + whereDetails: 'USA', + }, + sensitivePersonalInformation: { + doesInvolve: YesNoInput.YES, + disclosedOutsideCanada: YesNoInput.NO, + }, + disclosuresOutsideCanada: { + section1: { + sensitiveInfoStoredByServiceProvider: YesNoInput.YES, + serviceProviderList: [ + { + name: 'Amazon', + cloudInfraName: 'AWS', + details: 'Stored in cloud', + }, + ], + disclosureDetails: 'S3 storage in us-east-1: US East (N. Virginia)', + contractualTerms: 'None', + }, + section2: { + relyOnExistingContract: YesNoInput.YES, + enterpriseServiceAccessDetails: 'S3', + }, + section3: { + unauthorizedAccessMeasures: 'IAM rules are in effect', + }, + section4: { + trackAccessDetails: 'IAM', + }, + section5: { + privacyRisks: [ + { + risk: 'Leak of Creds', + impact: 'Access of instance', + likelihoodOfUnauthorizedAccess: 'Medium', + levelOfPrivacyRisk: 'Medium', + riskResponse: 'immediately revoke', + outstandingRisk: 'No', + }, + ], + }, + }, + }, }; export const piaIntakeEntityMock: PiaIntakeEntity = { From 94252aa890b1fd5594919dfcc957d1e2c9806d0e Mon Sep 17 00:00:00 2001 From: Kushal Arora Date: Tue, 14 Feb 2023 09:42:44 -0800 Subject: [PATCH 04/18] [UTOPIA-769] [BE] pia-intake validations update --- .../modules/pia-intake/dto/create-pia-intake.dto.ts | 3 +++ .../collection-use-and-disclosure/index.class.ts | 5 ++--- .../steps-walkthrough.class.ts | 6 +----- .../disclosures-outside-canda/index.class.ts | 11 ++++++++++- .../storing-personal-information/index.class.ts | 9 ++++++++- 5 files changed, 24 insertions(+), 10 deletions(-) diff --git a/src/backend/src/modules/pia-intake/dto/create-pia-intake.dto.ts b/src/backend/src/modules/pia-intake/dto/create-pia-intake.dto.ts index f227b27c6..8bc83fc90 100644 --- a/src/backend/src/modules/pia-intake/dto/create-pia-intake.dto.ts +++ b/src/backend/src/modules/pia-intake/dto/create-pia-intake.dto.ts @@ -3,6 +3,7 @@ import { IsDateString, IsEmail, IsEnum, + IsNotEmptyObject, IsObject, IsOptional, IsString, @@ -280,6 +281,7 @@ export class CreatePiaIntakeDto { @IsObject() @IsOptional() + @IsNotEmptyObject() @ValidateNested() @Type(() => CollectionUseAndDisclosure) @ApiProperty({ @@ -291,6 +293,7 @@ export class CreatePiaIntakeDto { @IsObject() @IsOptional() + @IsNotEmptyObject() @ValidateNested() @Type(() => StoringPersonalInformation) @ApiProperty({ diff --git a/src/backend/src/modules/pia-intake/jsonb-classes/collection-use-and-disclosure/index.class.ts b/src/backend/src/modules/pia-intake/jsonb-classes/collection-use-and-disclosure/index.class.ts index 044b7b308..fa43fec44 100644 --- a/src/backend/src/modules/pia-intake/jsonb-classes/collection-use-and-disclosure/index.class.ts +++ b/src/backend/src/modules/pia-intake/jsonb-classes/collection-use-and-disclosure/index.class.ts @@ -1,7 +1,7 @@ import { IsArray, + IsNotEmptyObject, IsObject, - IsOptional, ValidateNested, } from '@nestjs/class-validator'; import { Type } from 'class-transformer'; @@ -10,13 +10,12 @@ import { StepWalkthrough } from './steps-walkthrough.class'; export class CollectionUseAndDisclosure { @IsArray() - @IsOptional() @ValidateNested({ each: true }) @Type(() => StepWalkthrough) steps: Array; @IsObject() - @IsOptional() + @IsNotEmptyObject() @ValidateNested() @Type(() => CollectionNotice) collectionNotice: CollectionNotice; diff --git a/src/backend/src/modules/pia-intake/jsonb-classes/collection-use-and-disclosure/steps-walkthrough.class.ts b/src/backend/src/modules/pia-intake/jsonb-classes/collection-use-and-disclosure/steps-walkthrough.class.ts index d81b74e19..b7c075cfe 100644 --- a/src/backend/src/modules/pia-intake/jsonb-classes/collection-use-and-disclosure/steps-walkthrough.class.ts +++ b/src/backend/src/modules/pia-intake/jsonb-classes/collection-use-and-disclosure/steps-walkthrough.class.ts @@ -1,22 +1,18 @@ -import { IsOptional, IsString } from '@nestjs/class-validator'; +import { IsString } from '@nestjs/class-validator'; import { UserTypesEnum } from 'src/common/enums/users.enum'; import { IFormField } from 'src/common/interfaces/form-field.interface'; export class StepWalkthrough { @IsString() - @IsOptional() drafterInput?: string; @IsString() - @IsOptional() mpoInput?: string; @IsString() - @IsOptional() foippaInput?: string; @IsString() - @IsOptional() OtherInput?: string; } diff --git a/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/index.class.ts b/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/index.class.ts index 9e53a2ebe..3bfe78c65 100644 --- a/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/index.class.ts +++ b/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/index.class.ts @@ -1,4 +1,8 @@ -import { IsObject, ValidateNested } from '@nestjs/class-validator'; +import { + IsNotEmptyObject, + IsObject, + ValidateNested, +} from '@nestjs/class-validator'; import { Type } from 'class-transformer'; import { DisclosureSection1 } from './section-1'; import { DisclosureSection2 } from './section-2'; @@ -8,26 +12,31 @@ import { DisclosureSection5 } from './section-5'; export class DisclosuresOutsideCanada { @IsObject() + @IsNotEmptyObject() @ValidateNested() @Type(() => DisclosureSection1) section1: DisclosureSection1; @IsObject() + @IsNotEmptyObject() @ValidateNested() @Type(() => DisclosureSection2) section2: DisclosureSection2; @IsObject() + @IsNotEmptyObject() @ValidateNested() @Type(() => DisclosureSection3) section3: DisclosureSection3; @IsObject() + @IsNotEmptyObject() @ValidateNested() @Type(() => DisclosureSection4) section4: DisclosureSection4; @IsObject() + @IsNotEmptyObject() @ValidateNested() @Type(() => DisclosureSection5) section5: DisclosureSection5; diff --git a/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/index.class.ts b/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/index.class.ts index 9598d2928..fafb04197 100644 --- a/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/index.class.ts +++ b/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/index.class.ts @@ -1,4 +1,8 @@ -import { IsObject, ValidateNested } from '@nestjs/class-validator'; +import { + IsNotEmptyObject, + IsObject, + ValidateNested, +} from '@nestjs/class-validator'; import { Type } from 'class-transformer'; import { DisclosuresOutsideCanada } from './disclosures-outside-canda/index.class'; import { PersonalInformation } from './personal-information.class'; @@ -6,16 +10,19 @@ import { SensitivePersonalInformation } from './sensitive-personal-information.c export class StoringPersonalInformation { @IsObject() + @IsNotEmptyObject() @ValidateNested() @Type(() => PersonalInformation) personalInformation: PersonalInformation; @IsObject() + @IsNotEmptyObject() @ValidateNested() @Type(() => SensitivePersonalInformation) sensitivePersonalInformation: SensitivePersonalInformation; @IsObject() + @IsNotEmptyObject() @ValidateNested() @Type(() => DisclosuresOutsideCanada) disclosuresOutsideCanada: DisclosuresOutsideCanada; From fe6ff4ed852e7c7d1e29db5fba0af9b327084136 Mon Sep 17 00:00:00 2001 From: Kushal Arora Date: Tue, 14 Feb 2023 09:45:08 -0800 Subject: [PATCH 05/18] [UTOPIA-769] [BE] pia-intake - added entity column storingPersonalInformation --- .../src/modules/pia-intake/entities/pia-intake.entity.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/backend/src/modules/pia-intake/entities/pia-intake.entity.ts b/src/backend/src/modules/pia-intake/entities/pia-intake.entity.ts index c6d06460a..deb544ec2 100644 --- a/src/backend/src/modules/pia-intake/entities/pia-intake.entity.ts +++ b/src/backend/src/modules/pia-intake/entities/pia-intake.entity.ts @@ -3,6 +3,7 @@ import { Column, Entity } from 'typeorm'; import { BaseEntity } from '../../../common/entities/base.entity'; import { PiaIntakeStatusEnum } from '../enums/pia-intake-status.enum'; import { CollectionUseAndDisclosure } from '../jsonb-classes/collection-use-and-disclosure/index.class'; +import { StoringPersonalInformation } from '../jsonb-classes/storing-personal-information/index.class'; @Entity('pia-intake') export class PiaIntakeEntity extends BaseEntity { @@ -138,4 +139,12 @@ export class PiaIntakeEntity extends BaseEntity { default: () => "'{}'", }) collectionUseAndDisclosure: CollectionUseAndDisclosure; + + @Column({ + name: 'storing_personal_information', + type: 'jsonb', + nullable: false, + default: () => "'{}'", + }) + storingPersonalInformation: StoringPersonalInformation; } From c20f7f418ba7caddb8ec74f45956a6b250a96c85 Mon Sep 17 00:00:00 2001 From: Kushal Arora Date: Tue, 14 Feb 2023 12:32:03 -0800 Subject: [PATCH 06/18] [UTOPIA-769] [Backend] [pia-intake.securityPersonalInformation] Updated pia-intake entity and associated jsonb classes and dtos for securityPersonalInformation --- .../pia-intake/dto/create-pia-intake.dto.ts | 31 +++++++++++++++++++ .../pia-intake/entities/pia-intake.entity.ts | 9 ++++++ .../access-to-personal-information.ts | 20 ++++++++++++ .../digital-tools-and-systems/index.ts | 22 +++++++++++++ .../digital-tools-and-systems/section-1.ts | 12 +++++++ .../digital-tools-and-systems/section-2.ts | 12 +++++++ .../index.class.ts | 22 +++++++++++++ .../test/util/mocks/data/pia-intake.mock.ts | 18 +++++++++++ 8 files changed, 146 insertions(+) create mode 100644 src/backend/src/modules/pia-intake/jsonb-classes/security-personal-information/access-to-personal-information.ts create mode 100644 src/backend/src/modules/pia-intake/jsonb-classes/security-personal-information/digital-tools-and-systems/index.ts create mode 100644 src/backend/src/modules/pia-intake/jsonb-classes/security-personal-information/digital-tools-and-systems/section-1.ts create mode 100644 src/backend/src/modules/pia-intake/jsonb-classes/security-personal-information/digital-tools-and-systems/section-2.ts create mode 100644 src/backend/src/modules/pia-intake/jsonb-classes/security-personal-information/index.class.ts diff --git a/src/backend/src/modules/pia-intake/dto/create-pia-intake.dto.ts b/src/backend/src/modules/pia-intake/dto/create-pia-intake.dto.ts index 8bc83fc90..d94eff0ca 100644 --- a/src/backend/src/modules/pia-intake/dto/create-pia-intake.dto.ts +++ b/src/backend/src/modules/pia-intake/dto/create-pia-intake.dto.ts @@ -15,6 +15,7 @@ import { YesNoInput } from 'src/common/enums/yes-no-input.enum'; import { GovMinistriesEnum } from '../../../common/enums/gov-ministries.enum'; import { PiaIntakeStatusEnum } from '../enums/pia-intake-status.enum'; import { CollectionUseAndDisclosure } from '../jsonb-classes/collection-use-and-disclosure/index.class'; +import { SecurityPersonalInformation } from '../jsonb-classes/security-personal-information/index.class'; import { StoringPersonalInformation } from '../jsonb-classes/storing-personal-information/index.class'; export const piaIntakeEntityMock: CreatePiaIntakeDto = { @@ -111,6 +112,24 @@ export const piaIntakeEntityMock: CreatePiaIntakeDto = { }, }, }, + securityPersonalInformation: { + digitalToolsAndSystems: { + section1: { + involveDigitalToolsAndSystems: YesNoInput.NO, + haveSecurityAssessment: YesNoInput.NO, + }, + section2: { + onGovServers: YesNoInput.NO, + whereDetails: 'on AWS Cloud', + }, + }, + accessToPersonalInformation: { + onlyCertainRolesAccessInformation: YesNoInput.YES, + accessApproved: YesNoInput.YES, + useAuditLogs: YesNoInput.NO, + additionalStrategies: 'PEM file access', + }, + }, }; export class CreatePiaIntakeDto { @@ -302,4 +321,16 @@ export class CreatePiaIntakeDto { example: piaIntakeEntityMock.storingPersonalInformation, }) storingPersonalInformation: StoringPersonalInformation; + + @IsObject() + @IsOptional() + @IsNotEmptyObject() + @ValidateNested() + @Type(() => SecurityPersonalInformation) + @ApiProperty({ + type: SecurityPersonalInformation, + required: false, + example: piaIntakeEntityMock.securityPersonalInformation, + }) + securityPersonalInformation: SecurityPersonalInformation; } diff --git a/src/backend/src/modules/pia-intake/entities/pia-intake.entity.ts b/src/backend/src/modules/pia-intake/entities/pia-intake.entity.ts index deb544ec2..e6ca37143 100644 --- a/src/backend/src/modules/pia-intake/entities/pia-intake.entity.ts +++ b/src/backend/src/modules/pia-intake/entities/pia-intake.entity.ts @@ -4,6 +4,7 @@ import { BaseEntity } from '../../../common/entities/base.entity'; import { PiaIntakeStatusEnum } from '../enums/pia-intake-status.enum'; import { CollectionUseAndDisclosure } from '../jsonb-classes/collection-use-and-disclosure/index.class'; import { StoringPersonalInformation } from '../jsonb-classes/storing-personal-information/index.class'; +import { SecurityPersonalInformation } from '../jsonb-classes/security-personal-information/index.class'; @Entity('pia-intake') export class PiaIntakeEntity extends BaseEntity { @@ -147,4 +148,12 @@ export class PiaIntakeEntity extends BaseEntity { default: () => "'{}'", }) storingPersonalInformation: StoringPersonalInformation; + + @Column({ + name: 'security_personal_information', + type: 'jsonb', + nullable: false, + default: () => "'{}'", + }) + securityPersonalInformation: SecurityPersonalInformation; } diff --git a/src/backend/src/modules/pia-intake/jsonb-classes/security-personal-information/access-to-personal-information.ts b/src/backend/src/modules/pia-intake/jsonb-classes/security-personal-information/access-to-personal-information.ts new file mode 100644 index 000000000..471256904 --- /dev/null +++ b/src/backend/src/modules/pia-intake/jsonb-classes/security-personal-information/access-to-personal-information.ts @@ -0,0 +1,20 @@ +import { IsEnum, IsOptional, IsString } from '@nestjs/class-validator'; +import { YesNoInput } from 'src/common/enums/yes-no-input.enum'; + +export class AccessToPersonalInformation { + @IsEnum(YesNoInput) + @IsOptional() + onlyCertainRolesAccessInformation: YesNoInput; + + @IsEnum(YesNoInput) + @IsOptional() + accessApproved: YesNoInput; + + @IsEnum(YesNoInput) + @IsOptional() + useAuditLogs: YesNoInput; + + @IsString() + @IsOptional() + additionalStrategies: string; +} diff --git a/src/backend/src/modules/pia-intake/jsonb-classes/security-personal-information/digital-tools-and-systems/index.ts b/src/backend/src/modules/pia-intake/jsonb-classes/security-personal-information/digital-tools-and-systems/index.ts new file mode 100644 index 000000000..d25ba919f --- /dev/null +++ b/src/backend/src/modules/pia-intake/jsonb-classes/security-personal-information/digital-tools-and-systems/index.ts @@ -0,0 +1,22 @@ +import { + IsNotEmptyObject, + IsObject, + ValidateNested, +} from '@nestjs/class-validator'; +import { Type } from 'class-transformer'; +import { DigitalToolsAndSystemsSection1 } from './section-1'; +import { DigitalToolsAndSystemsSection2 } from './section-2'; + +export class DigitalToolsAndSystems { + @IsObject() + @IsNotEmptyObject() + @ValidateNested() + @Type(() => DigitalToolsAndSystemsSection1) + section1: DigitalToolsAndSystemsSection1; + + @IsObject() + @IsNotEmptyObject() + @ValidateNested() + @Type(() => DigitalToolsAndSystemsSection2) + section2: DigitalToolsAndSystemsSection2; +} diff --git a/src/backend/src/modules/pia-intake/jsonb-classes/security-personal-information/digital-tools-and-systems/section-1.ts b/src/backend/src/modules/pia-intake/jsonb-classes/security-personal-information/digital-tools-and-systems/section-1.ts new file mode 100644 index 000000000..51b3fbbf4 --- /dev/null +++ b/src/backend/src/modules/pia-intake/jsonb-classes/security-personal-information/digital-tools-and-systems/section-1.ts @@ -0,0 +1,12 @@ +import { IsEnum, IsOptional } from '@nestjs/class-validator'; +import { YesNoInput } from 'src/common/enums/yes-no-input.enum'; + +export class DigitalToolsAndSystemsSection1 { + @IsEnum(YesNoInput) + @IsOptional() + involveDigitalToolsAndSystems: YesNoInput; + + @IsEnum(YesNoInput) + @IsOptional() + haveSecurityAssessment: YesNoInput; +} diff --git a/src/backend/src/modules/pia-intake/jsonb-classes/security-personal-information/digital-tools-and-systems/section-2.ts b/src/backend/src/modules/pia-intake/jsonb-classes/security-personal-information/digital-tools-and-systems/section-2.ts new file mode 100644 index 000000000..01b8c8752 --- /dev/null +++ b/src/backend/src/modules/pia-intake/jsonb-classes/security-personal-information/digital-tools-and-systems/section-2.ts @@ -0,0 +1,12 @@ +import { IsEnum, IsOptional, IsString } from '@nestjs/class-validator'; +import { YesNoInput } from 'src/common/enums/yes-no-input.enum'; + +export class DigitalToolsAndSystemsSection2 { + @IsEnum(YesNoInput) + @IsOptional() + onGovServers: YesNoInput; + + @IsString() + @IsOptional() + whereDetails: string; +} diff --git a/src/backend/src/modules/pia-intake/jsonb-classes/security-personal-information/index.class.ts b/src/backend/src/modules/pia-intake/jsonb-classes/security-personal-information/index.class.ts new file mode 100644 index 000000000..fedcdaf8d --- /dev/null +++ b/src/backend/src/modules/pia-intake/jsonb-classes/security-personal-information/index.class.ts @@ -0,0 +1,22 @@ +import { + IsNotEmptyObject, + IsObject, + ValidateNested, +} from '@nestjs/class-validator'; +import { Type } from 'class-transformer'; +import { AccessToPersonalInformation } from './access-to-personal-information'; +import { DigitalToolsAndSystems } from './digital-tools-and-systems/index'; + +export class SecurityPersonalInformation { + @IsObject() + @IsNotEmptyObject() + @ValidateNested() + @Type(() => DigitalToolsAndSystems) + digitalToolsAndSystems: DigitalToolsAndSystems; + + @IsObject() + @IsNotEmptyObject() + @ValidateNested() + @Type(() => AccessToPersonalInformation) + accessToPersonalInformation: AccessToPersonalInformation; +} diff --git a/src/backend/test/util/mocks/data/pia-intake.mock.ts b/src/backend/test/util/mocks/data/pia-intake.mock.ts index b70f41ad6..d4ce351ba 100644 --- a/src/backend/test/util/mocks/data/pia-intake.mock.ts +++ b/src/backend/test/util/mocks/data/pia-intake.mock.ts @@ -101,6 +101,24 @@ const piaIntakeDataMock = { }, }, }, + securityPersonalInformation: { + digitalToolsAndSystems: { + section1: { + involveDigitalToolsAndSystems: YesNoInput.NO, + haveSecurityAssessment: YesNoInput.NO, + }, + section2: { + onGovServers: YesNoInput.NO, + whereDetails: 'on AWS Cloud', + }, + }, + accessToPersonalInformation: { + onlyCertainRolesAccessInformation: YesNoInput.YES, + accessApproved: YesNoInput.YES, + useAuditLogs: YesNoInput.NO, + additionalStrategies: 'PEM file access', + }, + }, }; export const piaIntakeEntityMock: PiaIntakeEntity = { From 86fc576c888ba2dbb0bfdff8ea113f06f852dd24 Mon Sep 17 00:00:00 2001 From: Kushal Arora Date: Tue, 14 Feb 2023 12:34:49 -0800 Subject: [PATCH 07/18] [UTOPIA-769] [Backend] removed class suffix from jsonb-classes --- .../src/modules/pia-intake/dto/create-pia-intake.dto.ts | 6 +++--- .../src/modules/pia-intake/entities/pia-intake.entity.ts | 6 +++--- .../{collection-notice.class.ts => collection-notice.ts} | 0 .../{index.class.ts => index.ts} | 4 ++-- .../{steps-walkthrough.class.ts => steps-walkthrough.ts} | 0 .../{index.class.ts => index.ts} | 0 .../disclosures-outside-canda/{index.class.ts => index.ts} | 0 .../{index.class.ts => index.ts} | 6 +++--- ...ersonal-information.class.ts => personal-information.ts} | 0 ...formation.class.ts => sensitive-personal-information.ts} | 0 10 files changed, 11 insertions(+), 11 deletions(-) rename src/backend/src/modules/pia-intake/jsonb-classes/collection-use-and-disclosure/{collection-notice.class.ts => collection-notice.ts} (100%) rename src/backend/src/modules/pia-intake/jsonb-classes/collection-use-and-disclosure/{index.class.ts => index.ts} (77%) rename src/backend/src/modules/pia-intake/jsonb-classes/collection-use-and-disclosure/{steps-walkthrough.class.ts => steps-walkthrough.ts} (100%) rename src/backend/src/modules/pia-intake/jsonb-classes/security-personal-information/{index.class.ts => index.ts} (100%) rename src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/{index.class.ts => index.ts} (100%) rename src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/{index.class.ts => index.ts} (87%) rename src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/{personal-information.class.ts => personal-information.ts} (100%) rename src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/{sensitive-personal-information.class.ts => sensitive-personal-information.ts} (100%) diff --git a/src/backend/src/modules/pia-intake/dto/create-pia-intake.dto.ts b/src/backend/src/modules/pia-intake/dto/create-pia-intake.dto.ts index d94eff0ca..242883576 100644 --- a/src/backend/src/modules/pia-intake/dto/create-pia-intake.dto.ts +++ b/src/backend/src/modules/pia-intake/dto/create-pia-intake.dto.ts @@ -14,9 +14,9 @@ import { Type } from 'class-transformer'; import { YesNoInput } from 'src/common/enums/yes-no-input.enum'; import { GovMinistriesEnum } from '../../../common/enums/gov-ministries.enum'; import { PiaIntakeStatusEnum } from '../enums/pia-intake-status.enum'; -import { CollectionUseAndDisclosure } from '../jsonb-classes/collection-use-and-disclosure/index.class'; -import { SecurityPersonalInformation } from '../jsonb-classes/security-personal-information/index.class'; -import { StoringPersonalInformation } from '../jsonb-classes/storing-personal-information/index.class'; +import { CollectionUseAndDisclosure } from '../jsonb-classes/collection-use-and-disclosure'; +import { SecurityPersonalInformation } from '../jsonb-classes/security-personal-information'; +import { StoringPersonalInformation } from '../jsonb-classes/storing-personal-information'; export const piaIntakeEntityMock: CreatePiaIntakeDto = { title: 'Test PIA for screening King Richard', diff --git a/src/backend/src/modules/pia-intake/entities/pia-intake.entity.ts b/src/backend/src/modules/pia-intake/entities/pia-intake.entity.ts index e6ca37143..b2ecb5a83 100644 --- a/src/backend/src/modules/pia-intake/entities/pia-intake.entity.ts +++ b/src/backend/src/modules/pia-intake/entities/pia-intake.entity.ts @@ -2,9 +2,9 @@ import { GovMinistriesEnum } from '../../../common/enums/gov-ministries.enum'; import { Column, Entity } from 'typeorm'; import { BaseEntity } from '../../../common/entities/base.entity'; import { PiaIntakeStatusEnum } from '../enums/pia-intake-status.enum'; -import { CollectionUseAndDisclosure } from '../jsonb-classes/collection-use-and-disclosure/index.class'; -import { StoringPersonalInformation } from '../jsonb-classes/storing-personal-information/index.class'; -import { SecurityPersonalInformation } from '../jsonb-classes/security-personal-information/index.class'; +import { CollectionUseAndDisclosure } from '../jsonb-classes/collection-use-and-disclosure'; +import { StoringPersonalInformation } from '../jsonb-classes/storing-personal-information'; +import { SecurityPersonalInformation } from '../jsonb-classes/security-personal-information'; @Entity('pia-intake') export class PiaIntakeEntity extends BaseEntity { diff --git a/src/backend/src/modules/pia-intake/jsonb-classes/collection-use-and-disclosure/collection-notice.class.ts b/src/backend/src/modules/pia-intake/jsonb-classes/collection-use-and-disclosure/collection-notice.ts similarity index 100% rename from src/backend/src/modules/pia-intake/jsonb-classes/collection-use-and-disclosure/collection-notice.class.ts rename to src/backend/src/modules/pia-intake/jsonb-classes/collection-use-and-disclosure/collection-notice.ts diff --git a/src/backend/src/modules/pia-intake/jsonb-classes/collection-use-and-disclosure/index.class.ts b/src/backend/src/modules/pia-intake/jsonb-classes/collection-use-and-disclosure/index.ts similarity index 77% rename from src/backend/src/modules/pia-intake/jsonb-classes/collection-use-and-disclosure/index.class.ts rename to src/backend/src/modules/pia-intake/jsonb-classes/collection-use-and-disclosure/index.ts index fa43fec44..16f307fe6 100644 --- a/src/backend/src/modules/pia-intake/jsonb-classes/collection-use-and-disclosure/index.class.ts +++ b/src/backend/src/modules/pia-intake/jsonb-classes/collection-use-and-disclosure/index.ts @@ -5,8 +5,8 @@ import { ValidateNested, } from '@nestjs/class-validator'; import { Type } from 'class-transformer'; -import { CollectionNotice } from './collection-notice.class'; -import { StepWalkthrough } from './steps-walkthrough.class'; +import { CollectionNotice } from './collection-notice'; +import { StepWalkthrough } from './steps-walkthrough'; export class CollectionUseAndDisclosure { @IsArray() diff --git a/src/backend/src/modules/pia-intake/jsonb-classes/collection-use-and-disclosure/steps-walkthrough.class.ts b/src/backend/src/modules/pia-intake/jsonb-classes/collection-use-and-disclosure/steps-walkthrough.ts similarity index 100% rename from src/backend/src/modules/pia-intake/jsonb-classes/collection-use-and-disclosure/steps-walkthrough.class.ts rename to src/backend/src/modules/pia-intake/jsonb-classes/collection-use-and-disclosure/steps-walkthrough.ts diff --git a/src/backend/src/modules/pia-intake/jsonb-classes/security-personal-information/index.class.ts b/src/backend/src/modules/pia-intake/jsonb-classes/security-personal-information/index.ts similarity index 100% rename from src/backend/src/modules/pia-intake/jsonb-classes/security-personal-information/index.class.ts rename to src/backend/src/modules/pia-intake/jsonb-classes/security-personal-information/index.ts diff --git a/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/index.class.ts b/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/index.ts similarity index 100% rename from src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/index.class.ts rename to src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/index.ts diff --git a/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/index.class.ts b/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/index.ts similarity index 87% rename from src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/index.class.ts rename to src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/index.ts index fafb04197..e12e8a7ae 100644 --- a/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/index.class.ts +++ b/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/index.ts @@ -4,9 +4,9 @@ import { ValidateNested, } from '@nestjs/class-validator'; import { Type } from 'class-transformer'; -import { DisclosuresOutsideCanada } from './disclosures-outside-canda/index.class'; -import { PersonalInformation } from './personal-information.class'; -import { SensitivePersonalInformation } from './sensitive-personal-information.class'; +import { DisclosuresOutsideCanada } from './disclosures-outside-canda'; +import { PersonalInformation } from './personal-information'; +import { SensitivePersonalInformation } from './sensitive-personal-information'; export class StoringPersonalInformation { @IsObject() diff --git a/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/personal-information.class.ts b/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/personal-information.ts similarity index 100% rename from src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/personal-information.class.ts rename to src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/personal-information.ts diff --git a/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/sensitive-personal-information.class.ts b/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/sensitive-personal-information.ts similarity index 100% rename from src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/sensitive-personal-information.class.ts rename to src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/sensitive-personal-information.ts From 5f988a5722a4f2e0d03d12ca220427f9c65b7d00 Mon Sep 17 00:00:00 2001 From: Kushal Arora Date: Tue, 14 Feb 2023 13:51:18 -0800 Subject: [PATCH 08/18] [UTOPIA-769] [Backend] [pia-intake.accuracyCorrectionAndRetention] Updated pia-intake entity and associated jsonb classes and dtos for accuracyCorrectionAndRetention --- .../pia-intake/dto/create-pia-intake.dto.ts | 30 ++++++++++++++++++- .../pia-intake/entities/pia-intake.entity.ts | 9 ++++++ .../accuracy.ts | 7 +++++ .../correction.ts | 16 ++++++++++ .../index.ts | 29 ++++++++++++++++++ .../retention.ts | 16 ++++++++++ .../test/util/mocks/data/pia-intake.mock.ts | 17 ++++++++++- 7 files changed, 122 insertions(+), 2 deletions(-) create mode 100644 src/backend/src/modules/pia-intake/jsonb-classes/accuracy-correction-and-retention/accuracy.ts create mode 100644 src/backend/src/modules/pia-intake/jsonb-classes/accuracy-correction-and-retention/correction.ts create mode 100644 src/backend/src/modules/pia-intake/jsonb-classes/accuracy-correction-and-retention/index.ts create mode 100644 src/backend/src/modules/pia-intake/jsonb-classes/accuracy-correction-and-retention/retention.ts diff --git a/src/backend/src/modules/pia-intake/dto/create-pia-intake.dto.ts b/src/backend/src/modules/pia-intake/dto/create-pia-intake.dto.ts index 242883576..b3565234c 100644 --- a/src/backend/src/modules/pia-intake/dto/create-pia-intake.dto.ts +++ b/src/backend/src/modules/pia-intake/dto/create-pia-intake.dto.ts @@ -14,6 +14,7 @@ import { Type } from 'class-transformer'; import { YesNoInput } from 'src/common/enums/yes-no-input.enum'; import { GovMinistriesEnum } from '../../../common/enums/gov-ministries.enum'; import { PiaIntakeStatusEnum } from '../enums/pia-intake-status.enum'; +import { AccuracyCorrectionAndRetention } from '../jsonb-classes/accuracy-correction-and-retention'; import { CollectionUseAndDisclosure } from '../jsonb-classes/collection-use-and-disclosure'; import { SecurityPersonalInformation } from '../jsonb-classes/security-personal-information'; import { StoringPersonalInformation } from '../jsonb-classes/storing-personal-information'; @@ -106,7 +107,7 @@ export const piaIntakeEntityMock: CreatePiaIntakeDto = { likelihoodOfUnauthorizedAccess: 'Medium', levelOfPrivacyRisk: 'Medium', riskResponse: 'immediately revoke', - outstandingRisk: 'No', + outstandingRisk: 'None', }, ], }, @@ -130,6 +131,21 @@ export const piaIntakeEntityMock: CreatePiaIntakeDto = { additionalStrategies: 'PEM file access', }, }, + accuracyCorrectionAndRetention: { + accuracy: { + description: 'Integrate with 3rd party validators', + }, + correction: { + haveProcessInPlace: YesNoInput.YES, + willDocument: YesNoInput.YES, + willConductNotifications: YesNoInput.YES, + }, + retention: { + usePIForDecision: YesNoInput.YES, + haveApprovedInfoSchedule: YesNoInput.NO, + describeRetention: 'will store in S3 Glacier Deep Archive', + }, + }, }; export class CreatePiaIntakeDto { @@ -333,4 +349,16 @@ export class CreatePiaIntakeDto { example: piaIntakeEntityMock.securityPersonalInformation, }) securityPersonalInformation: SecurityPersonalInformation; + + @IsObject() + @IsOptional() + @IsNotEmptyObject() + @ValidateNested() + @Type(() => AccuracyCorrectionAndRetention) + @ApiProperty({ + type: AccuracyCorrectionAndRetention, + required: false, + example: piaIntakeEntityMock.accuracyCorrectionAndRetention, + }) + accuracyCorrectionAndRetention: AccuracyCorrectionAndRetention; } diff --git a/src/backend/src/modules/pia-intake/entities/pia-intake.entity.ts b/src/backend/src/modules/pia-intake/entities/pia-intake.entity.ts index b2ecb5a83..6751d2ce5 100644 --- a/src/backend/src/modules/pia-intake/entities/pia-intake.entity.ts +++ b/src/backend/src/modules/pia-intake/entities/pia-intake.entity.ts @@ -5,6 +5,7 @@ import { PiaIntakeStatusEnum } from '../enums/pia-intake-status.enum'; import { CollectionUseAndDisclosure } from '../jsonb-classes/collection-use-and-disclosure'; import { StoringPersonalInformation } from '../jsonb-classes/storing-personal-information'; import { SecurityPersonalInformation } from '../jsonb-classes/security-personal-information'; +import { AccuracyCorrectionAndRetention } from '../jsonb-classes/accuracy-correction-and-retention'; @Entity('pia-intake') export class PiaIntakeEntity extends BaseEntity { @@ -156,4 +157,12 @@ export class PiaIntakeEntity extends BaseEntity { default: () => "'{}'", }) securityPersonalInformation: SecurityPersonalInformation; + + @Column({ + name: 'accuracy_correction_and_retention', + type: 'jsonb', + nullable: false, + default: () => "'{}'", + }) + accuracyCorrectionAndRetention: AccuracyCorrectionAndRetention; } diff --git a/src/backend/src/modules/pia-intake/jsonb-classes/accuracy-correction-and-retention/accuracy.ts b/src/backend/src/modules/pia-intake/jsonb-classes/accuracy-correction-and-retention/accuracy.ts new file mode 100644 index 000000000..2d17f0802 --- /dev/null +++ b/src/backend/src/modules/pia-intake/jsonb-classes/accuracy-correction-and-retention/accuracy.ts @@ -0,0 +1,7 @@ +import { IsOptional, IsString } from '@nestjs/class-validator'; + +export class Accuracy { + @IsString() + @IsOptional() + description: string; +} diff --git a/src/backend/src/modules/pia-intake/jsonb-classes/accuracy-correction-and-retention/correction.ts b/src/backend/src/modules/pia-intake/jsonb-classes/accuracy-correction-and-retention/correction.ts new file mode 100644 index 000000000..dae41e7c7 --- /dev/null +++ b/src/backend/src/modules/pia-intake/jsonb-classes/accuracy-correction-and-retention/correction.ts @@ -0,0 +1,16 @@ +import { IsEnum, IsOptional } from '@nestjs/class-validator'; +import { YesNoInput } from 'src/common/enums/yes-no-input.enum'; + +export class Correction { + @IsEnum(YesNoInput) + @IsOptional() + haveProcessInPlace: YesNoInput; + + @IsEnum(YesNoInput) + @IsOptional() + willDocument: YesNoInput; + + @IsEnum(YesNoInput) + @IsOptional() + willConductNotifications: YesNoInput; +} diff --git a/src/backend/src/modules/pia-intake/jsonb-classes/accuracy-correction-and-retention/index.ts b/src/backend/src/modules/pia-intake/jsonb-classes/accuracy-correction-and-retention/index.ts new file mode 100644 index 000000000..ad674f863 --- /dev/null +++ b/src/backend/src/modules/pia-intake/jsonb-classes/accuracy-correction-and-retention/index.ts @@ -0,0 +1,29 @@ +import { + IsNotEmptyObject, + IsObject, + ValidateNested, +} from '@nestjs/class-validator'; +import { Type } from 'class-transformer'; +import { Accuracy } from './accuracy'; +import { Correction } from './correction'; +import { Retention } from './retention'; + +export class AccuracyCorrectionAndRetention { + @IsObject() + @IsNotEmptyObject() + @ValidateNested() + @Type(() => Accuracy) + accuracy: Accuracy; + + @IsObject() + @IsNotEmptyObject() + @ValidateNested() + @Type(() => Correction) + correction: Correction; + + @IsObject() + @IsNotEmptyObject() + @ValidateNested() + @Type(() => Retention) + retention: Retention; +} diff --git a/src/backend/src/modules/pia-intake/jsonb-classes/accuracy-correction-and-retention/retention.ts b/src/backend/src/modules/pia-intake/jsonb-classes/accuracy-correction-and-retention/retention.ts new file mode 100644 index 000000000..4b6ff01e3 --- /dev/null +++ b/src/backend/src/modules/pia-intake/jsonb-classes/accuracy-correction-and-retention/retention.ts @@ -0,0 +1,16 @@ +import { IsEnum, IsOptional, IsString } from '@nestjs/class-validator'; +import { YesNoInput } from 'src/common/enums/yes-no-input.enum'; + +export class Retention { + @IsEnum(YesNoInput) + @IsOptional() + usePIForDecision: YesNoInput; + + @IsEnum(YesNoInput) + @IsOptional() + haveApprovedInfoSchedule: YesNoInput; + + @IsString() + @IsOptional() + describeRetention: string; +} diff --git a/src/backend/test/util/mocks/data/pia-intake.mock.ts b/src/backend/test/util/mocks/data/pia-intake.mock.ts index d4ce351ba..2981be86e 100644 --- a/src/backend/test/util/mocks/data/pia-intake.mock.ts +++ b/src/backend/test/util/mocks/data/pia-intake.mock.ts @@ -95,7 +95,7 @@ const piaIntakeDataMock = { likelihoodOfUnauthorizedAccess: 'Medium', levelOfPrivacyRisk: 'Medium', riskResponse: 'immediately revoke', - outstandingRisk: 'No', + outstandingRisk: 'None', }, ], }, @@ -119,6 +119,21 @@ const piaIntakeDataMock = { additionalStrategies: 'PEM file access', }, }, + accuracyCorrectionAndRetention: { + accuracy: { + description: 'Integrate with 3rd party validators', + }, + correction: { + haveProcessInPlace: YesNoInput.YES, + willDocument: YesNoInput.YES, + willConductNotifications: YesNoInput.YES, + }, + retention: { + usePIForDecision: YesNoInput.YES, + haveApprovedInfoSchedule: YesNoInput.NO, + describeRetention: 'will store in S3 Glacier Deep Archive', + }, + }, }; export const piaIntakeEntityMock: PiaIntakeEntity = { From 1beb238cede9e540f2d0cf0f828f294ebcce3805 Mon Sep 17 00:00:00 2001 From: Kushal Arora Date: Tue, 14 Feb 2023 14:13:26 -0800 Subject: [PATCH 09/18] [UTOPIA-769] [Backend] [pia-intake.personalInformationBanks] Updated pia-intake entity and associated jsonb classes and dtos for personalInformationBanks --- .../pia-intake/dto/create-pia-intake.dto.ts | 22 +++++++++++++++ .../pia-intake/entities/pia-intake.entity.ts | 9 ++++++ .../personal-information-banks/index.ts | 15 ++++++++++ .../resulting-pib.ts | 28 +++++++++++++++++++ .../test/util/mocks/data/pia-intake.mock.ts | 9 ++++++ 5 files changed, 83 insertions(+) create mode 100644 src/backend/src/modules/pia-intake/jsonb-classes/personal-information-banks/index.ts create mode 100644 src/backend/src/modules/pia-intake/jsonb-classes/personal-information-banks/resulting-pib.ts diff --git a/src/backend/src/modules/pia-intake/dto/create-pia-intake.dto.ts b/src/backend/src/modules/pia-intake/dto/create-pia-intake.dto.ts index b3565234c..d60de0f65 100644 --- a/src/backend/src/modules/pia-intake/dto/create-pia-intake.dto.ts +++ b/src/backend/src/modules/pia-intake/dto/create-pia-intake.dto.ts @@ -16,6 +16,7 @@ import { GovMinistriesEnum } from '../../../common/enums/gov-ministries.enum'; import { PiaIntakeStatusEnum } from '../enums/pia-intake-status.enum'; import { AccuracyCorrectionAndRetention } from '../jsonb-classes/accuracy-correction-and-retention'; import { CollectionUseAndDisclosure } from '../jsonb-classes/collection-use-and-disclosure'; +import { PersonalInformationBanks } from '../jsonb-classes/personal-information-banks'; import { SecurityPersonalInformation } from '../jsonb-classes/security-personal-information'; import { StoringPersonalInformation } from '../jsonb-classes/storing-personal-information'; @@ -146,6 +147,15 @@ export const piaIntakeEntityMock: CreatePiaIntakeDto = { describeRetention: 'will store in S3 Glacier Deep Archive', }, }, + personalInformationBanks: { + resultingPIB: { + willResultInPIB: YesNoInput.YES, + descriptionInformationType: 'Name and address of the user', + mainMinistryInvolved: 'Citizen Services', + managingPersonName: 'John Doe', + managingPersonPhone: '(587-555-555)', + }, + }, }; export class CreatePiaIntakeDto { @@ -361,4 +371,16 @@ export class CreatePiaIntakeDto { example: piaIntakeEntityMock.accuracyCorrectionAndRetention, }) accuracyCorrectionAndRetention: AccuracyCorrectionAndRetention; + + @IsObject() + @IsOptional() + @IsNotEmptyObject() + @ValidateNested() + @Type(() => PersonalInformationBanks) + @ApiProperty({ + type: PersonalInformationBanks, + required: false, + example: piaIntakeEntityMock.personalInformationBanks, + }) + personalInformationBanks: PersonalInformationBanks; } diff --git a/src/backend/src/modules/pia-intake/entities/pia-intake.entity.ts b/src/backend/src/modules/pia-intake/entities/pia-intake.entity.ts index 6751d2ce5..e363d671f 100644 --- a/src/backend/src/modules/pia-intake/entities/pia-intake.entity.ts +++ b/src/backend/src/modules/pia-intake/entities/pia-intake.entity.ts @@ -6,6 +6,7 @@ import { CollectionUseAndDisclosure } from '../jsonb-classes/collection-use-and- import { StoringPersonalInformation } from '../jsonb-classes/storing-personal-information'; import { SecurityPersonalInformation } from '../jsonb-classes/security-personal-information'; import { AccuracyCorrectionAndRetention } from '../jsonb-classes/accuracy-correction-and-retention'; +import { PersonalInformationBanks } from '../jsonb-classes/personal-information-banks'; @Entity('pia-intake') export class PiaIntakeEntity extends BaseEntity { @@ -165,4 +166,12 @@ export class PiaIntakeEntity extends BaseEntity { default: () => "'{}'", }) accuracyCorrectionAndRetention: AccuracyCorrectionAndRetention; + + @Column({ + name: 'personal_information_banks', + type: 'jsonb', + nullable: false, + default: () => "'{}'", + }) + personalInformationBanks: PersonalInformationBanks; } diff --git a/src/backend/src/modules/pia-intake/jsonb-classes/personal-information-banks/index.ts b/src/backend/src/modules/pia-intake/jsonb-classes/personal-information-banks/index.ts new file mode 100644 index 000000000..9565c8162 --- /dev/null +++ b/src/backend/src/modules/pia-intake/jsonb-classes/personal-information-banks/index.ts @@ -0,0 +1,15 @@ +import { + IsNotEmptyObject, + IsObject, + ValidateNested, +} from '@nestjs/class-validator'; +import { Type } from 'class-transformer'; +import { ResultingPIB } from './resulting-pib'; + +export class PersonalInformationBanks { + @IsObject() + @IsNotEmptyObject() + @ValidateNested() + @Type(() => ResultingPIB) + resultingPIB: ResultingPIB; +} diff --git a/src/backend/src/modules/pia-intake/jsonb-classes/personal-information-banks/resulting-pib.ts b/src/backend/src/modules/pia-intake/jsonb-classes/personal-information-banks/resulting-pib.ts new file mode 100644 index 000000000..3fcbfea78 --- /dev/null +++ b/src/backend/src/modules/pia-intake/jsonb-classes/personal-information-banks/resulting-pib.ts @@ -0,0 +1,28 @@ +import { IsEnum, IsOptional, IsString } from '@nestjs/class-validator'; +import { YesNoInput } from 'src/common/enums/yes-no-input.enum'; + +export class ResultingPIB { + @IsEnum(YesNoInput) + @IsOptional() + willResultInPIB?: YesNoInput; + + @IsString() + @IsOptional() + descriptionInformationType?: string; + + @IsString() + @IsOptional() + mainMinistryInvolved?: string; + + @IsString() + @IsOptional() + otherMinistryInvolved?: string; + + @IsString() + @IsOptional() + managingPersonName?: string; + + @IsString() + @IsOptional() + managingPersonPhone?: string; +} diff --git a/src/backend/test/util/mocks/data/pia-intake.mock.ts b/src/backend/test/util/mocks/data/pia-intake.mock.ts index 2981be86e..2ca7da24a 100644 --- a/src/backend/test/util/mocks/data/pia-intake.mock.ts +++ b/src/backend/test/util/mocks/data/pia-intake.mock.ts @@ -134,6 +134,15 @@ const piaIntakeDataMock = { describeRetention: 'will store in S3 Glacier Deep Archive', }, }, + personalInformationBanks: { + resultingPIB: { + willResultInPIB: YesNoInput.YES, + descriptionInformationType: 'Name and address of the user', + mainMinistryInvolved: 'Citizen Services', + managingPersonName: 'John Doe', + managingPersonPhone: '(587-555-555)', + }, + }, }; export const piaIntakeEntityMock: PiaIntakeEntity = { From d9ef5b7d6a256591cafd7b2aa11a9ce2a7c2cb1b Mon Sep 17 00:00:00 2001 From: Kushal Arora Date: Tue, 14 Feb 2023 14:29:21 -0800 Subject: [PATCH 10/18] [UTOPIA-769] [Backend] [pia-intake.additionalRisks] Updated pia-intake entity and associated jsonb classes and dtos for additionalRisks --- .../pia-intake/dto/create-pia-intake.dto.ts | 21 +++++++++++++++++++ .../pia-intake/entities/pia-intake.entity.ts | 9 ++++++++ .../additional-risks/additionalRisk.ts | 9 ++++++++ .../jsonb-classes/additional-risks/index.ts | 10 +++++++++ .../test/util/mocks/data/pia-intake.mock.ts | 8 +++++++ 5 files changed, 57 insertions(+) create mode 100644 src/backend/src/modules/pia-intake/jsonb-classes/additional-risks/additionalRisk.ts create mode 100644 src/backend/src/modules/pia-intake/jsonb-classes/additional-risks/index.ts diff --git a/src/backend/src/modules/pia-intake/dto/create-pia-intake.dto.ts b/src/backend/src/modules/pia-intake/dto/create-pia-intake.dto.ts index d60de0f65..cdb841404 100644 --- a/src/backend/src/modules/pia-intake/dto/create-pia-intake.dto.ts +++ b/src/backend/src/modules/pia-intake/dto/create-pia-intake.dto.ts @@ -15,6 +15,7 @@ import { YesNoInput } from 'src/common/enums/yes-no-input.enum'; import { GovMinistriesEnum } from '../../../common/enums/gov-ministries.enum'; import { PiaIntakeStatusEnum } from '../enums/pia-intake-status.enum'; import { AccuracyCorrectionAndRetention } from '../jsonb-classes/accuracy-correction-and-retention'; +import { AdditionalRisks } from '../jsonb-classes/additional-risks'; import { CollectionUseAndDisclosure } from '../jsonb-classes/collection-use-and-disclosure'; import { PersonalInformationBanks } from '../jsonb-classes/personal-information-banks'; import { SecurityPersonalInformation } from '../jsonb-classes/security-personal-information'; @@ -156,6 +157,14 @@ export const piaIntakeEntityMock: CreatePiaIntakeDto = { managingPersonPhone: '(587-555-555)', }, }, + additionalRisks: { + risks: [ + { + risk: 'Leak 1', + response: 'Response 1', + }, + ], + }, }; export class CreatePiaIntakeDto { @@ -383,4 +392,16 @@ export class CreatePiaIntakeDto { example: piaIntakeEntityMock.personalInformationBanks, }) personalInformationBanks: PersonalInformationBanks; + + @IsObject() + @IsOptional() + @IsNotEmptyObject() + @ValidateNested() + @Type(() => AdditionalRisks) + @ApiProperty({ + type: AdditionalRisks, + required: false, + example: piaIntakeEntityMock.additionalRisks, + }) + additionalRisks: AdditionalRisks; } diff --git a/src/backend/src/modules/pia-intake/entities/pia-intake.entity.ts b/src/backend/src/modules/pia-intake/entities/pia-intake.entity.ts index e363d671f..a985cf79f 100644 --- a/src/backend/src/modules/pia-intake/entities/pia-intake.entity.ts +++ b/src/backend/src/modules/pia-intake/entities/pia-intake.entity.ts @@ -7,6 +7,7 @@ import { StoringPersonalInformation } from '../jsonb-classes/storing-personal-in import { SecurityPersonalInformation } from '../jsonb-classes/security-personal-information'; import { AccuracyCorrectionAndRetention } from '../jsonb-classes/accuracy-correction-and-retention'; import { PersonalInformationBanks } from '../jsonb-classes/personal-information-banks'; +import { AdditionalRisks } from '../jsonb-classes/additional-risks'; @Entity('pia-intake') export class PiaIntakeEntity extends BaseEntity { @@ -174,4 +175,12 @@ export class PiaIntakeEntity extends BaseEntity { default: () => "'{}'", }) personalInformationBanks: PersonalInformationBanks; + + @Column({ + name: 'additional_risks', + type: 'jsonb', + nullable: false, + default: () => "'{}'", + }) + additionalRisks: AdditionalRisks; } diff --git a/src/backend/src/modules/pia-intake/jsonb-classes/additional-risks/additionalRisk.ts b/src/backend/src/modules/pia-intake/jsonb-classes/additional-risks/additionalRisk.ts new file mode 100644 index 000000000..2a30e6cb4 --- /dev/null +++ b/src/backend/src/modules/pia-intake/jsonb-classes/additional-risks/additionalRisk.ts @@ -0,0 +1,9 @@ +import { IsString } from '@nestjs/class-validator'; + +export class AdditionalRisk { + @IsString() + risk: string; + + @IsString() + response: string; +} diff --git a/src/backend/src/modules/pia-intake/jsonb-classes/additional-risks/index.ts b/src/backend/src/modules/pia-intake/jsonb-classes/additional-risks/index.ts new file mode 100644 index 000000000..196528291 --- /dev/null +++ b/src/backend/src/modules/pia-intake/jsonb-classes/additional-risks/index.ts @@ -0,0 +1,10 @@ +import { IsArray, ValidateNested } from '@nestjs/class-validator'; +import { Type } from 'class-transformer'; +import { AdditionalRisk } from './additionalRisk'; + +export class AdditionalRisks { + @IsArray() + @ValidateNested({ each: true }) + @Type(() => AdditionalRisk) + risks: Array; +} diff --git a/src/backend/test/util/mocks/data/pia-intake.mock.ts b/src/backend/test/util/mocks/data/pia-intake.mock.ts index 2ca7da24a..ee792c42a 100644 --- a/src/backend/test/util/mocks/data/pia-intake.mock.ts +++ b/src/backend/test/util/mocks/data/pia-intake.mock.ts @@ -143,6 +143,14 @@ const piaIntakeDataMock = { managingPersonPhone: '(587-555-555)', }, }, + additionalRisks: { + risks: [ + { + risk: 'Leak 1', + response: 'Response 1', + }, + ], + }, }; export const piaIntakeEntityMock: PiaIntakeEntity = { From 78434f8844f9044187fd52b9d7db9827ba87f4cc Mon Sep 17 00:00:00 2001 From: Kushal Arora Date: Tue, 14 Feb 2023 14:37:37 -0800 Subject: [PATCH 11/18] [CodeClimate fix] [Backend] moved create-pia-mock to separate file --- .../pia-intake/dto/create-pia-intake.dto.ts | 148 +---------------- .../dto/pia-intake-find-query.dto.ts | 2 +- .../mocks/create-pia-intake.mock.ts | 150 ++++++++++++++++++ 3 files changed, 152 insertions(+), 148 deletions(-) create mode 100644 src/backend/src/modules/pia-intake/mocks/create-pia-intake.mock.ts diff --git a/src/backend/src/modules/pia-intake/dto/create-pia-intake.dto.ts b/src/backend/src/modules/pia-intake/dto/create-pia-intake.dto.ts index cdb841404..8c9a85d2c 100644 --- a/src/backend/src/modules/pia-intake/dto/create-pia-intake.dto.ts +++ b/src/backend/src/modules/pia-intake/dto/create-pia-intake.dto.ts @@ -11,7 +11,6 @@ import { } from '@nestjs/class-validator'; import { ApiProperty } from '@nestjs/swagger'; import { Type } from 'class-transformer'; -import { YesNoInput } from 'src/common/enums/yes-no-input.enum'; import { GovMinistriesEnum } from '../../../common/enums/gov-ministries.enum'; import { PiaIntakeStatusEnum } from '../enums/pia-intake-status.enum'; import { AccuracyCorrectionAndRetention } from '../jsonb-classes/accuracy-correction-and-retention'; @@ -20,152 +19,7 @@ import { CollectionUseAndDisclosure } from '../jsonb-classes/collection-use-and- import { PersonalInformationBanks } from '../jsonb-classes/personal-information-banks'; import { SecurityPersonalInformation } from '../jsonb-classes/security-personal-information'; import { StoringPersonalInformation } from '../jsonb-classes/storing-personal-information'; - -export const piaIntakeEntityMock: CreatePiaIntakeDto = { - title: 'Test PIA for screening King Richard', - ministry: GovMinistriesEnum.TOURISM_ARTS_CULTURE_AND_SPORT, - branch: 'Entertainment', - status: PiaIntakeStatusEnum.INCOMPLETE, - drafterName: 'Will Smith', - drafterTitle: 'Actor', - drafterEmail: 'will@test.bc.gov.in', - leadName: 'King Richard', - leadTitle: 'Chief Guiding Officer', - leadEmail: 'king@test.bc.gov.in', - mpoName: 'Reinaldo Marcus Green', - mpoEmail: 'reinaldo@test.bc.gov.in', - initiativeDescription: `*King Richard* is a 2021 American biographical sports drama film directed by [Reinaldo Marcus Green](https://en.wikipedia.org/wiki/Reinaldo_Marcus_Green) and written by [Zach Baylin](https://en.wikipedia.org/wiki/Zach_Baylin). The film stars [Will Smith](https://en.wikipedia.org/wiki/Will_Smith) as Richard Williams, the father and coach of famed tennis players [Venus](https://en.wikipedia.org/wiki/Venus_Williams) and [Serena Williams](https://en.wikipedia.org/wiki/Serena_Williams) (both of whom served as executive producers on the film), with [Aunjanue Ellis](https://en.wikipedia.org/wiki/Aunjanue_Ellis), [Saniyya Sidney](https://en.wikipedia.org/wiki/Saniyya_Sidney), [Demi Singleton](https://en.wikipedia.org/wiki/Demi_Singleton), [Tony Goldwyn](https://en.wikipedia.org/wiki/Tony_Goldwyn), and [Jon Bernthal](https://en.wikipedia.org/wiki/Jon_Bernthal) in supporting roles.`, - initiativeScope: `Richard Williams lives in [Compton, California](https://en.wikipedia.org/wiki/Compton,_California), with his wife Brandy, his three step-daughters, and his two daughters, Venus and Serena. Richard aspires to turn Venus and Serena into professional tennis players; he has prepared a plan for success since before they were born. Richard and Brandy coach Venus and Serena on a daily basis, while also working as a security guard and a nurse, respectively. Richard works tirelessly to find a professional coach for the girls, creating brochures and videotapes to advertise their skills, but has not had success.`, - dataElementsInvolved: `Cast Involved: - - 1. Will Smith as Richard Williams - 2. Aunjanue Ellis as Oracene "Brandy" Price - 3. Saniyya Sidney as Venus Williams - 4. Demi Singleton as Serena Williams - 5. Jon Bernthal as Rick Macci - 6. Tony Goldwyn as Paul Cohen - 7. Mikayla LaShae Bartholomew as Tunde Price - `, - hasAddedPiToDataElements: false, - submittedAt: new Date(), - riskMitigation: `The film was released on [Blu-ray](https://en.wikipedia.org/wiki/Blu-ray) and [DVD](https://en.wikipedia.org/wiki/DVD) February 8, 2022 by [Warner Bros. Home Entertainment](https://en.wikipedia.org/wiki/Warner_Bros._Home_Entertainment), with the 4K Ultra HD release through [Warner Archive Collection](https://en.wikipedia.org/wiki/Warner_Archive_Collection) on the same date.`, - collectionUseAndDisclosure: { - steps: [ - { - drafterInput: 'Make a Checklist.', - mpoInput: 'Agreed', - foippaInput: 'Agreed', - OtherInput: 'Agreed', - }, - { - drafterInput: 'Set Your Budget.', - mpoInput: 'Set precise budget', - foippaInput: 'Agreed', - OtherInput: 'Agreed', - }, - ], - collectionNotice: { - drafterInput: 'Test Input', - mpoInput: 'Updated Input', - }, - }, - storingPersonalInformation: { - personalInformation: { - storedOutsideCanada: YesNoInput.YES, - whereDetails: 'USA', - }, - sensitivePersonalInformation: { - doesInvolve: YesNoInput.YES, - disclosedOutsideCanada: YesNoInput.NO, - }, - disclosuresOutsideCanada: { - section1: { - sensitiveInfoStoredByServiceProvider: YesNoInput.YES, - serviceProviderList: [ - { - name: 'Amazon', - cloudInfraName: 'AWS', - details: 'Stored in cloud', - }, - ], - disclosureDetails: 'S3 storage in us-east-1: US East (N. Virginia)', - contractualTerms: 'None', - }, - section2: { - relyOnExistingContract: YesNoInput.YES, - enterpriseServiceAccessDetails: 'S3', - }, - section3: { - unauthorizedAccessMeasures: 'IAM rules are in effect', - }, - section4: { - trackAccessDetails: 'IAM', - }, - section5: { - privacyRisks: [ - { - risk: 'Leak of Creds', - impact: 'Access of instance', - likelihoodOfUnauthorizedAccess: 'Medium', - levelOfPrivacyRisk: 'Medium', - riskResponse: 'immediately revoke', - outstandingRisk: 'None', - }, - ], - }, - }, - }, - securityPersonalInformation: { - digitalToolsAndSystems: { - section1: { - involveDigitalToolsAndSystems: YesNoInput.NO, - haveSecurityAssessment: YesNoInput.NO, - }, - section2: { - onGovServers: YesNoInput.NO, - whereDetails: 'on AWS Cloud', - }, - }, - accessToPersonalInformation: { - onlyCertainRolesAccessInformation: YesNoInput.YES, - accessApproved: YesNoInput.YES, - useAuditLogs: YesNoInput.NO, - additionalStrategies: 'PEM file access', - }, - }, - accuracyCorrectionAndRetention: { - accuracy: { - description: 'Integrate with 3rd party validators', - }, - correction: { - haveProcessInPlace: YesNoInput.YES, - willDocument: YesNoInput.YES, - willConductNotifications: YesNoInput.YES, - }, - retention: { - usePIForDecision: YesNoInput.YES, - haveApprovedInfoSchedule: YesNoInput.NO, - describeRetention: 'will store in S3 Glacier Deep Archive', - }, - }, - personalInformationBanks: { - resultingPIB: { - willResultInPIB: YesNoInput.YES, - descriptionInformationType: 'Name and address of the user', - mainMinistryInvolved: 'Citizen Services', - managingPersonName: 'John Doe', - managingPersonPhone: '(587-555-555)', - }, - }, - additionalRisks: { - risks: [ - { - risk: 'Leak 1', - response: 'Response 1', - }, - ], - }, -}; +import { piaIntakeEntityMock } from '../mocks/create-pia-intake.mock'; export class CreatePiaIntakeDto { @IsString() diff --git a/src/backend/src/modules/pia-intake/dto/pia-intake-find-query.dto.ts b/src/backend/src/modules/pia-intake/dto/pia-intake-find-query.dto.ts index 1ac45f654..1006a1de2 100644 --- a/src/backend/src/modules/pia-intake/dto/pia-intake-find-query.dto.ts +++ b/src/backend/src/modules/pia-intake/dto/pia-intake-find-query.dto.ts @@ -16,7 +16,7 @@ import { } from '../constants/pia-intake-allowed-sort-fields'; import { PiaFilterDrafterByCurrentUserEnum } from '../enums/pia-filter-drafter-by-current-user.enum'; import { PiaIntakeStatusEnum } from '../enums/pia-intake-status.enum'; -import { piaIntakeEntityMock } from './create-pia-intake.dto'; +import { piaIntakeEntityMock } from '../mocks/create-pia-intake.mock'; export class PiaIntakeFindQuery { @ApiProperty({ diff --git a/src/backend/src/modules/pia-intake/mocks/create-pia-intake.mock.ts b/src/backend/src/modules/pia-intake/mocks/create-pia-intake.mock.ts new file mode 100644 index 000000000..54e9880e3 --- /dev/null +++ b/src/backend/src/modules/pia-intake/mocks/create-pia-intake.mock.ts @@ -0,0 +1,150 @@ +import { GovMinistriesEnum } from 'src/common/enums/gov-ministries.enum'; +import { YesNoInput } from 'src/common/enums/yes-no-input.enum'; +import { PiaIntakeStatusEnum } from '../enums/pia-intake-status.enum'; +import { CreatePiaIntakeDto } from '../dto/create-pia-intake.dto'; + +export const piaIntakeEntityMock: CreatePiaIntakeDto = { + title: 'Test PIA for screening King Richard', + ministry: GovMinistriesEnum.TOURISM_ARTS_CULTURE_AND_SPORT, + branch: 'Entertainment', + status: PiaIntakeStatusEnum.INCOMPLETE, + drafterName: 'Will Smith', + drafterTitle: 'Actor', + drafterEmail: 'will@test.bc.gov.in', + leadName: 'King Richard', + leadTitle: 'Chief Guiding Officer', + leadEmail: 'king@test.bc.gov.in', + mpoName: 'Reinaldo Marcus Green', + mpoEmail: 'reinaldo@test.bc.gov.in', + initiativeDescription: `*King Richard* is a 2021 American biographical sports drama film directed by [Reinaldo Marcus Green](https://en.wikipedia.org/wiki/Reinaldo_Marcus_Green) and written by [Zach Baylin](https://en.wikipedia.org/wiki/Zach_Baylin). The film stars [Will Smith](https://en.wikipedia.org/wiki/Will_Smith) as Richard Williams, the father and coach of famed tennis players [Venus](https://en.wikipedia.org/wiki/Venus_Williams) and [Serena Williams](https://en.wikipedia.org/wiki/Serena_Williams) (both of whom served as executive producers on the film), with [Aunjanue Ellis](https://en.wikipedia.org/wiki/Aunjanue_Ellis), [Saniyya Sidney](https://en.wikipedia.org/wiki/Saniyya_Sidney), [Demi Singleton](https://en.wikipedia.org/wiki/Demi_Singleton), [Tony Goldwyn](https://en.wikipedia.org/wiki/Tony_Goldwyn), and [Jon Bernthal](https://en.wikipedia.org/wiki/Jon_Bernthal) in supporting roles.`, + initiativeScope: `Richard Williams lives in [Compton, California](https://en.wikipedia.org/wiki/Compton,_California), with his wife Brandy, his three step-daughters, and his two daughters, Venus and Serena. Richard aspires to turn Venus and Serena into professional tennis players; he has prepared a plan for success since before they were born. Richard and Brandy coach Venus and Serena on a daily basis, while also working as a security guard and a nurse, respectively. Richard works tirelessly to find a professional coach for the girls, creating brochures and videotapes to advertise their skills, but has not had success.`, + dataElementsInvolved: `Cast Involved: + + 1. Will Smith as Richard Williams + 2. Aunjanue Ellis as Oracene "Brandy" Price + 3. Saniyya Sidney as Venus Williams + 4. Demi Singleton as Serena Williams + 5. Jon Bernthal as Rick Macci + 6. Tony Goldwyn as Paul Cohen + 7. Mikayla LaShae Bartholomew as Tunde Price + `, + hasAddedPiToDataElements: false, + submittedAt: new Date(), + riskMitigation: `The film was released on [Blu-ray](https://en.wikipedia.org/wiki/Blu-ray) and [DVD](https://en.wikipedia.org/wiki/DVD) February 8, 2022 by [Warner Bros. Home Entertainment](https://en.wikipedia.org/wiki/Warner_Bros._Home_Entertainment), with the 4K Ultra HD release through [Warner Archive Collection](https://en.wikipedia.org/wiki/Warner_Archive_Collection) on the same date.`, + collectionUseAndDisclosure: { + steps: [ + { + drafterInput: 'Make a Checklist.', + mpoInput: 'Agreed', + foippaInput: 'Agreed', + OtherInput: 'Agreed', + }, + { + drafterInput: 'Set Your Budget.', + mpoInput: 'Set precise budget', + foippaInput: 'Agreed', + OtherInput: 'Agreed', + }, + ], + collectionNotice: { + drafterInput: 'Test Input', + mpoInput: 'Updated Input', + }, + }, + storingPersonalInformation: { + personalInformation: { + storedOutsideCanada: YesNoInput.YES, + whereDetails: 'USA', + }, + sensitivePersonalInformation: { + doesInvolve: YesNoInput.YES, + disclosedOutsideCanada: YesNoInput.NO, + }, + disclosuresOutsideCanada: { + section1: { + sensitiveInfoStoredByServiceProvider: YesNoInput.YES, + serviceProviderList: [ + { + name: 'Amazon', + cloudInfraName: 'AWS', + details: 'Stored in cloud', + }, + ], + disclosureDetails: 'S3 storage in us-east-1: US East (N. Virginia)', + contractualTerms: 'None', + }, + section2: { + relyOnExistingContract: YesNoInput.YES, + enterpriseServiceAccessDetails: 'S3', + }, + section3: { + unauthorizedAccessMeasures: 'IAM rules are in effect', + }, + section4: { + trackAccessDetails: 'IAM', + }, + section5: { + privacyRisks: [ + { + risk: 'Leak of Creds', + impact: 'Access of instance', + likelihoodOfUnauthorizedAccess: 'Medium', + levelOfPrivacyRisk: 'Medium', + riskResponse: 'immediately revoke', + outstandingRisk: 'None', + }, + ], + }, + }, + }, + securityPersonalInformation: { + digitalToolsAndSystems: { + section1: { + involveDigitalToolsAndSystems: YesNoInput.NO, + haveSecurityAssessment: YesNoInput.NO, + }, + section2: { + onGovServers: YesNoInput.NO, + whereDetails: 'on AWS Cloud', + }, + }, + accessToPersonalInformation: { + onlyCertainRolesAccessInformation: YesNoInput.YES, + accessApproved: YesNoInput.YES, + useAuditLogs: YesNoInput.NO, + additionalStrategies: 'PEM file access', + }, + }, + accuracyCorrectionAndRetention: { + accuracy: { + description: 'Integrate with 3rd party validators', + }, + correction: { + haveProcessInPlace: YesNoInput.YES, + willDocument: YesNoInput.YES, + willConductNotifications: YesNoInput.YES, + }, + retention: { + usePIForDecision: YesNoInput.YES, + haveApprovedInfoSchedule: YesNoInput.NO, + describeRetention: 'will store in S3 Glacier Deep Archive', + }, + }, + personalInformationBanks: { + resultingPIB: { + willResultInPIB: YesNoInput.YES, + descriptionInformationType: 'Name and address of the user', + mainMinistryInvolved: 'Citizen Services', + managingPersonName: 'John Doe', + managingPersonPhone: '(587-555-555)', + }, + }, + additionalRisks: { + risks: [ + { + risk: 'Leak 1', + response: 'Response 1', + }, + ], + }, +}; From cb2608b11ad48456f025ea574288ef95c65c0130 Mon Sep 17 00:00:00 2001 From: Kushal Arora Date: Tue, 14 Feb 2023 15:16:36 -0800 Subject: [PATCH 12/18] [UTOPIA-769] [Backend] Migration to update pia-intake table with josnb columns --- .../1676414798877-piaIntakeAddJsonbColumns.ts | 49 +++++++++++++++++++ .../pia-intake/entities/pia-intake.entity.ts | 18 +++---- 2 files changed, 55 insertions(+), 12 deletions(-) create mode 100644 src/backend/src/migrations/1676414798877-piaIntakeAddJsonbColumns.ts diff --git a/src/backend/src/migrations/1676414798877-piaIntakeAddJsonbColumns.ts b/src/backend/src/migrations/1676414798877-piaIntakeAddJsonbColumns.ts new file mode 100644 index 000000000..5cf219da7 --- /dev/null +++ b/src/backend/src/migrations/1676414798877-piaIntakeAddJsonbColumns.ts @@ -0,0 +1,49 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class PiaIntakeAddJsonbColumns1676414798877 + implements MigrationInterface +{ + name = 'piaIntakeAddJsonbColumns1676414798877'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "pia-intake" ADD "collection_use_and_disclosure" jsonb`, + ); + await queryRunner.query( + `ALTER TABLE "pia-intake" ADD "storing_personal_information" jsonb`, + ); + await queryRunner.query( + `ALTER TABLE "pia-intake" ADD "security_personal_information" jsonb`, + ); + await queryRunner.query( + `ALTER TABLE "pia-intake" ADD "accuracy_correction_and_retention" jsonb`, + ); + await queryRunner.query( + `ALTER TABLE "pia-intake" ADD "personal_information_banks" jsonb`, + ); + await queryRunner.query( + `ALTER TABLE "pia-intake" ADD "additional_risks" jsonb`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "pia-intake" DROP COLUMN "additional_risks"`, + ); + await queryRunner.query( + `ALTER TABLE "pia-intake" DROP COLUMN "personal_information_banks"`, + ); + await queryRunner.query( + `ALTER TABLE "pia-intake" DROP COLUMN "accuracy_correction_and_retention"`, + ); + await queryRunner.query( + `ALTER TABLE "pia-intake" DROP COLUMN "security_personal_information"`, + ); + await queryRunner.query( + `ALTER TABLE "pia-intake" DROP COLUMN "storing_personal_information"`, + ); + await queryRunner.query( + `ALTER TABLE "pia-intake" DROP COLUMN "collection_use_and_disclosure"`, + ); + } +} diff --git a/src/backend/src/modules/pia-intake/entities/pia-intake.entity.ts b/src/backend/src/modules/pia-intake/entities/pia-intake.entity.ts index a985cf79f..7cd801c53 100644 --- a/src/backend/src/modules/pia-intake/entities/pia-intake.entity.ts +++ b/src/backend/src/modules/pia-intake/entities/pia-intake.entity.ts @@ -139,48 +139,42 @@ export class PiaIntakeEntity extends BaseEntity { @Column({ name: 'collection_use_and_disclosure', type: 'jsonb', - nullable: false, - default: () => "'{}'", + nullable: true, }) collectionUseAndDisclosure: CollectionUseAndDisclosure; @Column({ name: 'storing_personal_information', type: 'jsonb', - nullable: false, - default: () => "'{}'", + nullable: true, }) storingPersonalInformation: StoringPersonalInformation; @Column({ name: 'security_personal_information', type: 'jsonb', - nullable: false, - default: () => "'{}'", + nullable: true, }) securityPersonalInformation: SecurityPersonalInformation; @Column({ name: 'accuracy_correction_and_retention', type: 'jsonb', - nullable: false, - default: () => "'{}'", + nullable: true, }) accuracyCorrectionAndRetention: AccuracyCorrectionAndRetention; @Column({ name: 'personal_information_banks', type: 'jsonb', - nullable: false, - default: () => "'{}'", + nullable: true, }) personalInformationBanks: PersonalInformationBanks; @Column({ name: 'additional_risks', type: 'jsonb', - nullable: false, - default: () => "'{}'", + nullable: true, }) additionalRisks: AdditionalRisks; } From 4e45d8401bcbf47ffdafec61e97ecc69612d804a Mon Sep 17 00:00:00 2001 From: Kushal Arora Date: Tue, 14 Feb 2023 15:26:28 -0800 Subject: [PATCH 13/18] [UTOPIA-769] [Backend] [Unit Test] Added Test Case for empty Jsonb values - default state --- .../pia-intake/pia-intake.service.spec.ts | 45 ++++++++++ .../mocks/data/pia-empty-jsonb-values.mock.ts | 85 +++++++++++++++++++ 2 files changed, 130 insertions(+) create mode 100644 src/backend/test/util/mocks/data/pia-empty-jsonb-values.mock.ts diff --git a/src/backend/test/unit/pia-intake/pia-intake.service.spec.ts b/src/backend/test/unit/pia-intake/pia-intake.service.spec.ts index b4d73dad9..8d9521f0b 100644 --- a/src/backend/test/unit/pia-intake/pia-intake.service.spec.ts +++ b/src/backend/test/unit/pia-intake/pia-intake.service.spec.ts @@ -41,6 +41,7 @@ import { PiaFilterDrafterByCurrentUserEnum } from 'src/modules/pia-intake/enums/ import { Not } from 'typeorm/find-options/operator/Not'; import { SortOrderEnum } from 'src/common/enums/sort-order.enum'; import { UpdatePiaIntakeDto } from 'src/modules/pia-intake/dto/update-pia-intake.dto'; +import { emptyJsonbValues } from 'test/util/mocks/data/pia-empty-jsonb-values.mock'; /** * @Description @@ -164,6 +165,50 @@ describe('PiaIntakeService', () => { expect(createPiaIntakeDto.submittedAt).toBeDefined(); expect(createPiaIntakeDto.submittedAt).toBeInstanceOf(Date); }); + + it('succeeds for jsonb columns with empty values', async () => { + const createPiaIntakeDto: CreatePiaIntakeDto = { + ...createPiaIntakeMock, + ...emptyJsonbValues, + }; + + const piaIntakeEntity = { + ...piaIntakeEntityMock, + ...emptyJsonbValues, + }; + + const getPiaIntakeRO = { + ...getPiaIntakeROMock, + ...emptyJsonbValues, + }; + + const user: KeycloakUser = { ...keycloakUserMock }; + + piaIntakeRepository.save = jest.fn(async () => { + delay(10); + return piaIntakeEntity; + }); + + omitBaseKeysSpy.mockReturnValue(getPiaIntakeRO); + + const result = await service.create(createPiaIntakeDto, user); + + expect(piaIntakeRepository.save).toHaveBeenCalledWith({ + ...createPiaIntakeDto, + createdByGuid: user.idir_user_guid, + createdByUsername: user.idir_username, + updatedByGuid: user.idir_user_guid, + updatedByUsername: user.idir_username, + updatedByDisplayName: user.display_name, + drafterEmail: user.email, + }); + + expect(omitBaseKeysSpy).toHaveBeenCalledWith(piaIntakeEntity, [ + 'updatedByDisplayName', + ]); + + expect(result).toEqual(getPiaIntakeRO); + }); }); /** diff --git a/src/backend/test/util/mocks/data/pia-empty-jsonb-values.mock.ts b/src/backend/test/util/mocks/data/pia-empty-jsonb-values.mock.ts new file mode 100644 index 000000000..ae8db1dbf --- /dev/null +++ b/src/backend/test/util/mocks/data/pia-empty-jsonb-values.mock.ts @@ -0,0 +1,85 @@ +export const emptyJsonbValues = { + collectionUseAndDisclosure: { + steps: [], + collectionNotice: { + drafterInput: null, + mpoInput: null, + }, + }, + storingPersonalInformation: { + personalInformation: { + storedOutsideCanada: null, + whereDetails: null, + }, + sensitivePersonalInformation: { + doesInvolve: null, + disclosedOutsideCanada: null, + }, + disclosuresOutsideCanada: { + section1: { + sensitiveInfoStoredByServiceProvider: null, + serviceProviderList: [], + disclosureDetails: null, + contractualTerms: null, + }, + section2: { + relyOnExistingContract: null, + enterpriseServiceAccessDetails: null, + }, + section3: { + unauthorizedAccessMeasures: null, + }, + section4: { + trackAccessDetails: null, + }, + section5: { + privacyRisks: [], + }, + }, + }, + securityPersonalInformation: { + digitalToolsAndSystems: { + section1: { + involveDigitalToolsAndSystems: null, + haveSecurityAssessment: null, + }, + section2: { + onGovServers: null, + whereDetails: null, + }, + }, + accessToPersonalInformation: { + onlyCertainRolesAccessInformation: null, + accessApproved: null, + useAuditLogs: null, + additionalStrategies: null, + }, + }, + accuracyCorrectionAndRetention: { + accuracy: { + description: null, + }, + correction: { + haveProcessInPlace: null, + willDocument: null, + willConductNotifications: null, + }, + retention: { + usePIForDecision: null, + haveApprovedInfoSchedule: null, + describeRetention: null, + }, + }, + personalInformationBanks: { + resultingPIB: { + willResultInPIB: null, + descriptionInformationType: null, + mainMinistryInvolved: null, + managingPersonName: null, + managingPersonPhone: null, + }, + }, + additionalRisks: { + risks: [], + }, +}; From 8bef40f8d453e06433e00638e3dfca14372229a3 Mon Sep 17 00:00:00 2001 From: Kushal Arora Date: Tue, 14 Feb 2023 15:34:29 -0800 Subject: [PATCH 14/18] [UTOPIA-769] [Backend] Added conditional operator to IsOptional fields --- .../accuracy-correction-and-retention/accuracy.ts | 2 +- .../accuracy-correction-and-retention/correction.ts | 6 +++--- .../accuracy-correction-and-retention/retention.ts | 6 +++--- .../access-to-personal-information.ts | 8 ++++---- .../digital-tools-and-systems/section-1.ts | 4 ++-- .../digital-tools-and-systems/section-2.ts | 4 ++-- .../disclosures-outside-canda/section-1.ts | 6 +++--- .../disclosures-outside-canda/section-2.ts | 4 ++-- .../disclosures-outside-canda/section-3.ts | 2 +- .../disclosures-outside-canda/section-4.ts | 2 +- .../storing-personal-information/personal-information.ts | 4 ++-- .../sensitive-personal-information.ts | 4 ++-- 12 files changed, 26 insertions(+), 26 deletions(-) diff --git a/src/backend/src/modules/pia-intake/jsonb-classes/accuracy-correction-and-retention/accuracy.ts b/src/backend/src/modules/pia-intake/jsonb-classes/accuracy-correction-and-retention/accuracy.ts index 2d17f0802..57911ff90 100644 --- a/src/backend/src/modules/pia-intake/jsonb-classes/accuracy-correction-and-retention/accuracy.ts +++ b/src/backend/src/modules/pia-intake/jsonb-classes/accuracy-correction-and-retention/accuracy.ts @@ -3,5 +3,5 @@ import { IsOptional, IsString } from '@nestjs/class-validator'; export class Accuracy { @IsString() @IsOptional() - description: string; + description?: string; } diff --git a/src/backend/src/modules/pia-intake/jsonb-classes/accuracy-correction-and-retention/correction.ts b/src/backend/src/modules/pia-intake/jsonb-classes/accuracy-correction-and-retention/correction.ts index dae41e7c7..b7623cedd 100644 --- a/src/backend/src/modules/pia-intake/jsonb-classes/accuracy-correction-and-retention/correction.ts +++ b/src/backend/src/modules/pia-intake/jsonb-classes/accuracy-correction-and-retention/correction.ts @@ -4,13 +4,13 @@ import { YesNoInput } from 'src/common/enums/yes-no-input.enum'; export class Correction { @IsEnum(YesNoInput) @IsOptional() - haveProcessInPlace: YesNoInput; + haveProcessInPlace?: YesNoInput; @IsEnum(YesNoInput) @IsOptional() - willDocument: YesNoInput; + willDocument?: YesNoInput; @IsEnum(YesNoInput) @IsOptional() - willConductNotifications: YesNoInput; + willConductNotifications?: YesNoInput; } diff --git a/src/backend/src/modules/pia-intake/jsonb-classes/accuracy-correction-and-retention/retention.ts b/src/backend/src/modules/pia-intake/jsonb-classes/accuracy-correction-and-retention/retention.ts index 4b6ff01e3..ed03e07e7 100644 --- a/src/backend/src/modules/pia-intake/jsonb-classes/accuracy-correction-and-retention/retention.ts +++ b/src/backend/src/modules/pia-intake/jsonb-classes/accuracy-correction-and-retention/retention.ts @@ -4,13 +4,13 @@ import { YesNoInput } from 'src/common/enums/yes-no-input.enum'; export class Retention { @IsEnum(YesNoInput) @IsOptional() - usePIForDecision: YesNoInput; + usePIForDecision?: YesNoInput; @IsEnum(YesNoInput) @IsOptional() - haveApprovedInfoSchedule: YesNoInput; + haveApprovedInfoSchedule?: YesNoInput; @IsString() @IsOptional() - describeRetention: string; + describeRetention?: string; } diff --git a/src/backend/src/modules/pia-intake/jsonb-classes/security-personal-information/access-to-personal-information.ts b/src/backend/src/modules/pia-intake/jsonb-classes/security-personal-information/access-to-personal-information.ts index 471256904..4bc388837 100644 --- a/src/backend/src/modules/pia-intake/jsonb-classes/security-personal-information/access-to-personal-information.ts +++ b/src/backend/src/modules/pia-intake/jsonb-classes/security-personal-information/access-to-personal-information.ts @@ -4,17 +4,17 @@ import { YesNoInput } from 'src/common/enums/yes-no-input.enum'; export class AccessToPersonalInformation { @IsEnum(YesNoInput) @IsOptional() - onlyCertainRolesAccessInformation: YesNoInput; + onlyCertainRolesAccessInformation?: YesNoInput; @IsEnum(YesNoInput) @IsOptional() - accessApproved: YesNoInput; + accessApproved?: YesNoInput; @IsEnum(YesNoInput) @IsOptional() - useAuditLogs: YesNoInput; + useAuditLogs?: YesNoInput; @IsString() @IsOptional() - additionalStrategies: string; + additionalStrategies?: string; } diff --git a/src/backend/src/modules/pia-intake/jsonb-classes/security-personal-information/digital-tools-and-systems/section-1.ts b/src/backend/src/modules/pia-intake/jsonb-classes/security-personal-information/digital-tools-and-systems/section-1.ts index 51b3fbbf4..9c9411ad6 100644 --- a/src/backend/src/modules/pia-intake/jsonb-classes/security-personal-information/digital-tools-and-systems/section-1.ts +++ b/src/backend/src/modules/pia-intake/jsonb-classes/security-personal-information/digital-tools-and-systems/section-1.ts @@ -4,9 +4,9 @@ import { YesNoInput } from 'src/common/enums/yes-no-input.enum'; export class DigitalToolsAndSystemsSection1 { @IsEnum(YesNoInput) @IsOptional() - involveDigitalToolsAndSystems: YesNoInput; + involveDigitalToolsAndSystems?: YesNoInput; @IsEnum(YesNoInput) @IsOptional() - haveSecurityAssessment: YesNoInput; + haveSecurityAssessment?: YesNoInput; } diff --git a/src/backend/src/modules/pia-intake/jsonb-classes/security-personal-information/digital-tools-and-systems/section-2.ts b/src/backend/src/modules/pia-intake/jsonb-classes/security-personal-information/digital-tools-and-systems/section-2.ts index 01b8c8752..efafbb4f6 100644 --- a/src/backend/src/modules/pia-intake/jsonb-classes/security-personal-information/digital-tools-and-systems/section-2.ts +++ b/src/backend/src/modules/pia-intake/jsonb-classes/security-personal-information/digital-tools-and-systems/section-2.ts @@ -4,9 +4,9 @@ import { YesNoInput } from 'src/common/enums/yes-no-input.enum'; export class DigitalToolsAndSystemsSection2 { @IsEnum(YesNoInput) @IsOptional() - onGovServers: YesNoInput; + onGovServers?: YesNoInput; @IsString() @IsOptional() - whereDetails: string; + whereDetails?: string; } diff --git a/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/section-1.ts b/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/section-1.ts index d9f4e8eb3..83d10147c 100644 --- a/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/section-1.ts +++ b/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/section-1.ts @@ -22,7 +22,7 @@ class ServiceProviderDetails { export class DisclosureSection1 { @IsEnum(YesNoInput) @IsOptional() - sensitiveInfoStoredByServiceProvider: YesNoInput; + sensitiveInfoStoredByServiceProvider?: YesNoInput; @IsArray() @ValidateNested({ each: true }) @@ -31,9 +31,9 @@ export class DisclosureSection1 { @IsString() @IsOptional() - disclosureDetails: string; + disclosureDetails?: string; @IsString() @IsOptional() - contractualTerms: string; + contractualTerms?: string; } diff --git a/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/section-2.ts b/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/section-2.ts index 1cc407146..70a14134b 100644 --- a/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/section-2.ts +++ b/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/section-2.ts @@ -4,9 +4,9 @@ import { YesNoInput } from 'src/common/enums/yes-no-input.enum'; export class DisclosureSection2 { @IsEnum(YesNoInput) @IsOptional() - relyOnExistingContract: YesNoInput; + relyOnExistingContract?: YesNoInput; @IsString() @IsOptional() - enterpriseServiceAccessDetails: string; + enterpriseServiceAccessDetails?: string; } diff --git a/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/section-3.ts b/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/section-3.ts index 50332af37..7124d95f6 100644 --- a/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/section-3.ts +++ b/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/section-3.ts @@ -3,5 +3,5 @@ import { IsOptional, IsString } from '@nestjs/class-validator'; export class DisclosureSection3 { @IsString() @IsOptional() - unauthorizedAccessMeasures: string; + unauthorizedAccessMeasures?: string; } diff --git a/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/section-4.ts b/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/section-4.ts index 1de7cc837..72c42c8dd 100644 --- a/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/section-4.ts +++ b/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/section-4.ts @@ -3,5 +3,5 @@ import { IsOptional, IsString } from '@nestjs/class-validator'; export class DisclosureSection4 { @IsString() @IsOptional() - trackAccessDetails: string; + trackAccessDetails?: string; } diff --git a/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/personal-information.ts b/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/personal-information.ts index 1bd48ef63..ebd79ed0f 100644 --- a/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/personal-information.ts +++ b/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/personal-information.ts @@ -4,9 +4,9 @@ import { YesNoInput } from 'src/common/enums/yes-no-input.enum'; export class PersonalInformation { @IsEnum(YesNoInput) @IsOptional() - storedOutsideCanada: YesNoInput; + storedOutsideCanada?: YesNoInput; @IsString() @IsOptional() - whereDetails: string; + whereDetails?: string; } diff --git a/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/sensitive-personal-information.ts b/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/sensitive-personal-information.ts index 233684066..da55e4ab0 100644 --- a/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/sensitive-personal-information.ts +++ b/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/sensitive-personal-information.ts @@ -4,9 +4,9 @@ import { YesNoInput } from 'src/common/enums/yes-no-input.enum'; export class SensitivePersonalInformation { @IsEnum(YesNoInput) @IsOptional() - doesInvolve: YesNoInput; + doesInvolve?: YesNoInput; @IsEnum(YesNoInput) @IsOptional() - disclosedOutsideCanada: YesNoInput; + disclosedOutsideCanada?: YesNoInput; } From 73cfcf1641c6f0412da94d5aee7048bb3afcafe0 Mon Sep 17 00:00:00 2001 From: Kushal Arora Date: Tue, 14 Feb 2023 18:20:20 -0800 Subject: [PATCH 15/18] [UTOPIA-769] [Backend] applied pia jsonb fields role validations --- src/backend/src/common/enums/users.enum.ts | 1 + .../validators/form-field-role.validator.ts | 34 +++++ .../collection-notice.ts | 34 ++++- .../collection-use-and-disclosure/index.ts | 38 ++++- .../steps-walkthrough.ts | 33 +++- .../pia-intake/pia-intake.controller.ts | 3 + .../modules/pia-intake/pia-intake.service.ts | 45 +++++- .../pia-intake/pia-intake.service.spec.ts | 144 ++++++++++++++++-- .../test/util/mocks/data/pia-intake.mock.ts | 26 ++++ 9 files changed, 332 insertions(+), 26 deletions(-) create mode 100644 src/backend/src/common/validators/form-field-role.validator.ts diff --git a/src/backend/src/common/enums/users.enum.ts b/src/backend/src/common/enums/users.enum.ts index c452f1bf2..c49779449 100644 --- a/src/backend/src/common/enums/users.enum.ts +++ b/src/backend/src/common/enums/users.enum.ts @@ -1,3 +1,4 @@ export enum UserTypesEnum { + DRAFTER = 'DRAFTER', MPO = 'MPO', } diff --git a/src/backend/src/common/validators/form-field-role.validator.ts b/src/backend/src/common/validators/form-field-role.validator.ts new file mode 100644 index 000000000..52b403bdb --- /dev/null +++ b/src/backend/src/common/validators/form-field-role.validator.ts @@ -0,0 +1,34 @@ +import { ForbiddenException } from '@nestjs/common'; +import { UserTypesEnum } from '../enums/users.enum'; +import { IFormField } from '../interfaces/form-field.interface'; + +/** + * @method validateRoleForFormField + * + * @description + * This method validates role access to Form Fields values + */ +export const validateRoleForFormField = ( + metadata: IFormField, + value: any, + userType: UserTypesEnum, + path: string, +) => { + if (!value) return; // if value not edited - no need to validate permissions + + if (!metadata?.allowedUserTypesEdit) return; // if allowedUserTypesEdit is null, all roles can edit this field/key + + if ( + metadata.allowedUserTypesEdit.includes(UserTypesEnum.MPO) && + userType !== UserTypesEnum.MPO + ) { + // if allowed user types is MPO and the user is not an MPO user, throw error + throw new ForbiddenException({ + path: path, + message: `You do not have permissions to edit certain section of this document. Please reach out to your MPO to proceed.`, + }); + } + + // allow otherwise + return; +}; diff --git a/src/backend/src/modules/pia-intake/jsonb-classes/collection-use-and-disclosure/collection-notice.ts b/src/backend/src/modules/pia-intake/jsonb-classes/collection-use-and-disclosure/collection-notice.ts index 322215eb6..41e1e59ce 100644 --- a/src/backend/src/modules/pia-intake/jsonb-classes/collection-use-and-disclosure/collection-notice.ts +++ b/src/backend/src/modules/pia-intake/jsonb-classes/collection-use-and-disclosure/collection-notice.ts @@ -1,6 +1,7 @@ import { IsOptional, IsString } from '@nestjs/class-validator'; import { UserTypesEnum } from 'src/common/enums/users.enum'; import { IFormField } from 'src/common/interfaces/form-field.interface'; +import { validateRoleForFormField } from 'src/common/validators/form-field-role.validator'; export class CollectionNotice { @IsString() @@ -12,17 +13,44 @@ export class CollectionNotice { mpoInput?: string; } -export const stepWalkthroughDetails: Array> = [ +export const CollectionNoticeMetadata: Array> = [ { key: 'drafterInput', type: 'text', isRichText: true, - allowedUserTypesEdit: null, // any + allowedUserTypesEdit: null, // any user can edit this field }, { key: 'mpoInput', type: 'text', isRichText: true, - allowedUserTypesEdit: [UserTypesEnum.MPO], + allowedUserTypesEdit: [UserTypesEnum.MPO], // only MPO users can edit this field }, ]; + +/** + * @method validateRoleForCollectionNotice + * + * @description + * This method validates role access to CollectionNotice values + */ +export const validateRoleForCollectionNotice = ( + collectionNotice: CollectionNotice, + userType: UserTypesEnum, +) => { + if (!collectionNotice) return; + + const keys = Object.keys(collectionNotice) as Array; + + keys.forEach((key) => { + const value = collectionNotice?.[key]; + const metadata = CollectionNoticeMetadata.find((m) => m.key === key); + + validateRoleForFormField( + metadata, + value, + userType, + `collectionNotice.${key}`, + ); + }); +}; diff --git a/src/backend/src/modules/pia-intake/jsonb-classes/collection-use-and-disclosure/index.ts b/src/backend/src/modules/pia-intake/jsonb-classes/collection-use-and-disclosure/index.ts index 16f307fe6..1e65fd836 100644 --- a/src/backend/src/modules/pia-intake/jsonb-classes/collection-use-and-disclosure/index.ts +++ b/src/backend/src/modules/pia-intake/jsonb-classes/collection-use-and-disclosure/index.ts @@ -5,8 +5,15 @@ import { ValidateNested, } from '@nestjs/class-validator'; import { Type } from 'class-transformer'; -import { CollectionNotice } from './collection-notice'; -import { StepWalkthrough } from './steps-walkthrough'; +import { UserTypesEnum } from 'src/common/enums/users.enum'; +import { + CollectionNotice, + validateRoleForCollectionNotice, +} from './collection-notice'; +import { + StepWalkthrough, + validateRoleForStepWalkthrough, +} from './steps-walkthrough'; export class CollectionUseAndDisclosure { @IsArray() @@ -20,3 +27,30 @@ export class CollectionUseAndDisclosure { @Type(() => CollectionNotice) collectionNotice: CollectionNotice; } + +/** + * @method validateRoleForCollectionUseAndDisclosure + * + * @description + * This method validates role access to collectionUseAndDisclosure + */ +export const validateRoleForCollectionUseAndDisclosure = ( + piaCollectionUseAndDisclosure: CollectionUseAndDisclosure, + userType: UserTypesEnum, +) => { + if (!piaCollectionUseAndDisclosure) return; + + // steps walkthrough validations + const steps = piaCollectionUseAndDisclosure?.steps; + if (steps?.length) { + steps.forEach((step: StepWalkthrough) => { + validateRoleForStepWalkthrough(step, userType); + }); + } + + // collection notice validations + validateRoleForCollectionNotice( + piaCollectionUseAndDisclosure?.collectionNotice, + userType, + ); +}; diff --git a/src/backend/src/modules/pia-intake/jsonb-classes/collection-use-and-disclosure/steps-walkthrough.ts b/src/backend/src/modules/pia-intake/jsonb-classes/collection-use-and-disclosure/steps-walkthrough.ts index b7c075cfe..cf9d6b773 100644 --- a/src/backend/src/modules/pia-intake/jsonb-classes/collection-use-and-disclosure/steps-walkthrough.ts +++ b/src/backend/src/modules/pia-intake/jsonb-classes/collection-use-and-disclosure/steps-walkthrough.ts @@ -1,6 +1,7 @@ import { IsString } from '@nestjs/class-validator'; import { UserTypesEnum } from 'src/common/enums/users.enum'; import { IFormField } from 'src/common/interfaces/form-field.interface'; +import { validateRoleForFormField } from 'src/common/validators/form-field-role.validator'; export class StepWalkthrough { @IsString() @@ -16,29 +17,51 @@ export class StepWalkthrough { OtherInput?: string; } -export const stepWalkthroughDetails: Array> = [ +export const StepWalkthroughMetadata: Array> = [ { key: 'drafterInput', type: 'text', isRichText: false, - allowedUserTypesEdit: null, // any + allowedUserTypesEdit: null, // any user can edit this field }, { key: 'mpoInput', type: 'text', isRichText: false, - allowedUserTypesEdit: [UserTypesEnum.MPO], + allowedUserTypesEdit: [UserTypesEnum.MPO], // only MPO users can edit this field }, { key: 'foippaInput', type: 'text', isRichText: false, - allowedUserTypesEdit: [UserTypesEnum.MPO], + allowedUserTypesEdit: [UserTypesEnum.MPO], // only MPO users can edit this field }, { key: 'OtherInput', type: 'text', isRichText: false, - allowedUserTypesEdit: [UserTypesEnum.MPO], + allowedUserTypesEdit: [UserTypesEnum.MPO], // only MPO users can edit this field }, ]; + +/** + * @method validateRoleForStepWalkthrough + * + * @description + * This method validates role access to StepWalkthrough values + */ +export const validateRoleForStepWalkthrough = ( + step: StepWalkthrough, + userType: UserTypesEnum, +) => { + if (!step) return; + + const keys = Object.keys(step) as Array; + + keys.forEach((key) => { + const value = step?.[key]; + const metadata = StepWalkthroughMetadata.find((m) => m.key === key); + + validateRoleForFormField(metadata, value, userType, `steps.${key}`); + }); +}; diff --git a/src/backend/src/modules/pia-intake/pia-intake.controller.ts b/src/backend/src/modules/pia-intake/pia-intake.controller.ts index 7679c7555..51bc05f3f 100644 --- a/src/backend/src/modules/pia-intake/pia-intake.controller.ts +++ b/src/backend/src/modules/pia-intake/pia-intake.controller.ts @@ -47,6 +47,9 @@ export class PiaIntakeController { @ApiCreatedResponse({ description: 'Successfully submitted a PIA-intake form', }) + @ApiForbiddenResponse({ + description: `You do not have permissions to edit certain section of this document. Please reach out to your MPO to proceed.`, + }) async create( @Body() createPiaIntakeDto: CreatePiaIntakeDto, @Req() req: IRequest, diff --git a/src/backend/src/modules/pia-intake/pia-intake.service.ts b/src/backend/src/modules/pia-intake/pia-intake.service.ts index 95218c6e1..1dc24fa6a 100644 --- a/src/backend/src/modules/pia-intake/pia-intake.service.ts +++ b/src/backend/src/modules/pia-intake/pia-intake.service.ts @@ -28,6 +28,8 @@ import { SortOrderEnum } from 'src/common/enums/sort-order.enum'; import { PiaFilterDrafterByCurrentUserEnum } from './enums/pia-filter-drafter-by-current-user.enum'; import { PiaIntakeStatusEnum } from './enums/pia-intake-status.enum'; import { PiaIntakeAllowedSortFieldsType } from './constants/pia-intake-allowed-sort-fields'; +import { validateRoleForCollectionUseAndDisclosure } from './jsonb-classes/collection-use-and-disclosure'; +import { UserTypesEnum } from 'src/common/enums/users.enum'; @Injectable() export class PiaIntakeService { @@ -43,6 +45,10 @@ export class PiaIntakeService { // update submittedAt column if it is first time submit this.updateSubmittedAt(createPiaIntakeDto); + // sending DRAFTER to userType as only a drafter can create a new PIA; + // A user could have MPO privileges, however while creating a PIA he/she is acting as a drafter + this.validateJsonbFields(createPiaIntakeDto, UserTypesEnum.DRAFTER); + const piaInfoForm: PiaIntakeEntity = await this.piaIntakeRepository.save({ ...createPiaIntakeDto, createdByGuid: user.idir_user_guid, @@ -73,7 +79,7 @@ export class PiaIntakeService { const existingRecord = await this.findOneBy({ id }); // Validate if the user has access to the pia-intake form. Throw appropriate exceptions if not - this.validateUserAccess(user, userRoles, existingRecord); + const userType = this.validateUserAccess(user, userRoles, existingRecord); // check if the user is not acting on / updating a stale version if (existingRecord.saveId !== updatePiaIntakeDto.saveId) { @@ -83,6 +89,9 @@ export class PiaIntakeService { }); } + // validate jsonb fields for role access + this.validateJsonbFields(updatePiaIntakeDto, userType); + // remove the provided saveId delete updatePiaIntakeDto.saveId; @@ -390,20 +399,20 @@ export class PiaIntakeService { user: KeycloakUser, userRoles: RolesEnum[], piaIntake: PiaIntakeEntity, - ) { + ): UserTypesEnum { if (!piaIntake.isActive) { throw new GoneException(); } // Scenario 1: A self-submitted PIA if (user.idir_user_guid === piaIntake.createdByGuid) { - return true; + return UserTypesEnum.DRAFTER; } // Scenario 2: PIA is submitted to the ministry I'm an MPO of const { mpoMinistries } = this.getMpoMinistriesByRoles(userRoles); if (mpoMinistries.includes(piaIntake.ministry)) { - return true; + return UserTypesEnum.MPO; } // Throw Forbidden user access if none of the above scenarios are met @@ -420,4 +429,32 @@ export class PiaIntakeService { if (!dto.submittedAt && dto.status === PiaIntakeStatusEnum.MPO_REVIEW) dto.submittedAt = new Date(); }; + + /** + * @method validateJsonbFields + * + * @param userType - type of the logged in user + * It can be DRAFTER when initially creating the PIA or while editing it + * It shall also be DRAFTER when an MPO is drafting a PIA + * It can be MPO when MPO is reviewing someone else's PIA + * + * @description + * This method validates role access to the following fields, if needed: + * 1. collectionUseAndDisclosure + * 2. storingPersonalInformation + * 3. securityPersonalInformation + * 4. accuracyCorrectionAndRetention + * 5. personalInformationBanks + * 6. additionalRisks + */ + validateJsonbFields( + pia: CreatePiaIntakeDto | UpdatePiaIntakeDto, + userType: UserTypesEnum, + ) { + validateRoleForCollectionUseAndDisclosure( + pia?.collectionUseAndDisclosure, + userType, + ); + // space for future validators, as needed + } } diff --git a/src/backend/test/unit/pia-intake/pia-intake.service.spec.ts b/src/backend/test/unit/pia-intake/pia-intake.service.spec.ts index 8d9521f0b..e8387ec7c 100644 --- a/src/backend/test/unit/pia-intake/pia-intake.service.spec.ts +++ b/src/backend/test/unit/pia-intake/pia-intake.service.spec.ts @@ -42,6 +42,7 @@ import { Not } from 'typeorm/find-options/operator/Not'; import { SortOrderEnum } from 'src/common/enums/sort-order.enum'; import { UpdatePiaIntakeDto } from 'src/modules/pia-intake/dto/update-pia-intake.dto'; import { emptyJsonbValues } from 'test/util/mocks/data/pia-empty-jsonb-values.mock'; +import { UserTypesEnum } from 'src/common/enums/users.enum'; /** * @Description @@ -108,7 +109,8 @@ describe('PiaIntakeService', () => { omitBaseKeysSpy.mockClear(); }); - it('succeeds calling the database repository with correct data', async () => { + // Scenario 1 + it('succeeds calling the database when a drafter creates PIA with the correct data ', async () => { const createPiaIntakeDto: CreatePiaIntakeDto = { ...createPiaIntakeMock }; const piaIntakeEntity = { ...piaIntakeEntityMock }; const getPiaIntakeRO = { ...getPiaIntakeROMock }; @@ -141,6 +143,7 @@ describe('PiaIntakeService', () => { expect(result).toEqual(getPiaIntakeRO); }); + // Scenario 2 it('succeeds and update submittedAt with current value if status is changed to MPO_REVIEW', async () => { const createPiaIntakeDto: CreatePiaIntakeDto = { ...createPiaIntakeMock, @@ -166,6 +169,7 @@ describe('PiaIntakeService', () => { expect(createPiaIntakeDto.submittedAt).toBeInstanceOf(Date); }); + // Scenario 3 it('succeeds for jsonb columns with empty values', async () => { const createPiaIntakeDto: CreatePiaIntakeDto = { ...createPiaIntakeMock, @@ -209,6 +213,36 @@ describe('PiaIntakeService', () => { expect(result).toEqual(getPiaIntakeRO); }); + + // Scenario 4 + it('fails when a drafter tries to create fields they do not have permissions to ', async () => { + const createPiaIntakeDto: CreatePiaIntakeDto = { + ...createPiaIntakeMock, + collectionUseAndDisclosure: { + steps: [ + { + drafterInput: 'Make a Checklist.', + mpoInput: 'I do not have privilege to edit this', + foippaInput: 'I do not have privilege to edit this', + OtherInput: 'I do not have privilege to edit this', + }, + ], + collectionNotice: { + drafterInput: 'Test Input', + mpoInput: 'I do not have privilege to edit this', + }, + }, + }; + + const user: KeycloakUser = { ...keycloakUserMock }; + + await expect(service.create(createPiaIntakeDto, user)).rejects.toThrow( + new ForbiddenException({ + path: 'steps.mpoInput', + message: `You do not have permissions to edit certain section of this document. Please reach out to your MPO to proceed.`, + }), + ); + }); }); /** @@ -1254,7 +1288,7 @@ describe('PiaIntakeService', () => { return { ...piaIntakeEntityMock }; }); - service.validateUserAccess = jest.fn(() => true); + service.validateUserAccess = jest.fn(() => UserTypesEnum.DRAFTER); omitBaseKeysSpy.mockImplementation(() => getPiaIntakeROMock); @@ -1297,7 +1331,7 @@ describe('PiaIntakeService', () => { return piaIntakeMock; }); - service.validateUserAccess = jest.fn(() => true); + service.validateUserAccess = jest.fn(() => UserTypesEnum.DRAFTER); piaIntakeRepository.save = jest.fn(async () => { delay(10); @@ -1347,7 +1381,7 @@ describe('PiaIntakeService', () => { return piaIntakeROMock; }); - service.validateUserAccess = jest.fn(() => true); + service.validateUserAccess = jest.fn(() => UserTypesEnum.DRAFTER); piaIntakeRepository.save = jest.fn(async () => { delay(10); @@ -1398,7 +1432,7 @@ describe('PiaIntakeService', () => { return piaIntakeMock; }); - service.validateUserAccess = jest.fn(() => true); + service.validateUserAccess = jest.fn(() => UserTypesEnum.DRAFTER); await expect( service.update(id, updatePiaIntakeDto, user, userRoles), @@ -1442,7 +1476,7 @@ describe('PiaIntakeService', () => { return piaIntakeROMock; }); - service.validateUserAccess = jest.fn(() => true); + service.validateUserAccess = jest.fn(() => UserTypesEnum.DRAFTER); piaIntakeRepository.save = jest.fn(async () => { delay(10); @@ -1454,6 +1488,92 @@ describe('PiaIntakeService', () => { expect(updatePiaIntakeDto.submittedAt).toBeDefined(); expect(updatePiaIntakeDto.submittedAt).toBeInstanceOf(Date); }); + + // Scenario 5: fails with Forbidden if DRAFTER tries to update fields they don't have permissions to + it("fails with Forbidden if DRAFTER tries to update fields they don't have permissions to", async () => { + const piaIntakeMock = { ...piaIntakeEntityMock, saveId: 10 }; + + const updatePiaIntakeDto = { + collectionUseAndDisclosure: { + steps: [ + { + drafterInput: 'Make a Checklist.', + mpoInput: null, + foippaInput: null, + OtherInput: null, + }, + ], + collectionNotice: { + drafterInput: 'Test Input', + mpoInput: 'I do not have access to update this field', + }, + }, + saveId: 10, + }; + + const userType = UserTypesEnum.DRAFTER; + + const user: KeycloakUser = { ...keycloakUserMock }; + const userRoles = [RolesEnum.MPO_CITZ]; + const id = 1; + + service.findOneBy = jest.fn(async () => { + delay(10); + return piaIntakeMock; + }); + + service.validateUserAccess = jest.fn(() => userType); + + await expect( + service.update(id, updatePiaIntakeDto, user, userRoles), + ).rejects.toThrow( + new ForbiddenException({ + path: 'collectionNotice.mpoInput', + message: + 'You do not have permissions to edit certain section of this document. Please reach out to your MPO to proceed.', + }), + ); + }); + + // Scenario 6: succeeds if scenario 5 updates are requested by an MPO + it('succeeds if scenario 5 updates are requested by an MPO', async () => { + const piaIntakeMock = { ...piaIntakeEntityMock, saveId: 10 }; + + const updatePiaIntakeDto = { + collectionUseAndDisclosure: { + steps: [ + { + drafterInput: 'Make a Checklist.', + mpoInput: null, + foippaInput: null, + OtherInput: null, + }, + ], + collectionNotice: { + drafterInput: 'Test Input', + mpoInput: 'I now DO have access to update this field', + }, + }, + saveId: 10, + }; + + const userType = UserTypesEnum.MPO; + + const user: KeycloakUser = { ...keycloakUserMock }; + const userRoles = [RolesEnum.MPO_CITZ]; + const id = 1; + + service.findOneBy = jest.fn(async () => { + delay(10); + return piaIntakeMock; + }); + + service.validateUserAccess = jest.fn(() => userType); + + await expect( + service.update(id, updatePiaIntakeDto, user, userRoles), + ).resolves.not.toThrow(); + }); }); /** @@ -1564,8 +1684,8 @@ describe('PiaIntakeService', () => { } }); - // Scenario 2: Test succeeds when the record is self submitted irrespective of user role or ministry they belong - it('succeeds when the record is self submitted irrespective of user role or ministry they belong', () => { + // Scenario 2: Test succeeds and returns userType - drafter when the record is self submitted + it('succeeds and returns userType - drafter when the record is self submitted', () => { const user: KeycloakUser = { ...keycloakUserMock, idir_user_guid: 'TEST_USER', @@ -1578,11 +1698,11 @@ describe('PiaIntakeService', () => { const result = service.validateUserAccess(user, userRoles, piaIntake); - expect(result).toBe(true); + expect(result).toBe(UserTypesEnum.DRAFTER); }); - // Scenario 3: Test succeeds when PIA is not self-submitted, but submitted to the ministry I belong and MPO of - it('succeeds when PIA is not self-submitted, but submitted to the ministry I belong and MPO of', () => { + // Scenario 3: succeeds and returns userType - MPO when PIA is not self-submitted, but submitted to the ministry I belong and MPO of + it('succeeds and returns userType - MPO when PIA is not self-submitted, but submitted to the ministry I belong and MPO of', () => { const user: KeycloakUser = { ...keycloakUserMock, idir_user_guid: 'USER_1', @@ -1596,7 +1716,7 @@ describe('PiaIntakeService', () => { const result = service.validateUserAccess(user, userRoles, piaIntake); - expect(result).toBe(true); + expect(result).toBe(UserTypesEnum.MPO); }); // Scenario 4: Test fails when the record is not self-submitted, but submitted to the ministry I belong and NOT MPO of diff --git a/src/backend/test/util/mocks/data/pia-intake.mock.ts b/src/backend/test/util/mocks/data/pia-intake.mock.ts index ee792c42a..a8ab84232 100644 --- a/src/backend/test/util/mocks/data/pia-intake.mock.ts +++ b/src/backend/test/util/mocks/data/pia-intake.mock.ts @@ -153,14 +153,39 @@ const piaIntakeDataMock = { }, }; +const collectionUseAndDisclosureWithDrafterPermissions = { + collectionUseAndDisclosure: { + steps: [ + { + drafterInput: 'Make a Checklist.', + mpoInput: null, + foippaInput: null, + OtherInput: null, + }, + { + drafterInput: 'Set Your Budget.', + mpoInput: null, + foippaInput: null, + OtherInput: null, + }, + ], + collectionNotice: { + drafterInput: 'Test Input', + mpoInput: null, + }, + }, +}; + export const piaIntakeEntityMock: PiaIntakeEntity = { ...baseEntityMock, ...piaIntakeDataMock, ...{ updatedByDisplayName: 'Richard, King CITZ:EX' }, + ...collectionUseAndDisclosureWithDrafterPermissions, }; export const createPiaIntakeMock: CreatePiaIntakeDto = { ...piaIntakeDataMock, + ...collectionUseAndDisclosureWithDrafterPermissions, }; export const getPiaIntakeROMock: GetPiaIntakeRO = { @@ -170,4 +195,5 @@ export const getPiaIntakeROMock: GetPiaIntakeRO = { createdAt: baseEntityMock.createdAt, updatedAt: baseEntityMock.updatedAt, }, + ...collectionUseAndDisclosureWithDrafterPermissions, }; From a4a28bc6cfefa299099c4237233bfb03dff53322 Mon Sep 17 00:00:00 2001 From: Kushal Arora Date: Wed, 15 Feb 2023 12:36:20 -0800 Subject: [PATCH 16/18] [UTOPIA-769] [Backend] updated generic section names to specific --- .../digital-tools-and-systems/index.ts | 12 ++++---- .../{section-2.ts => section-storage.ts} | 2 +- ...n-1.ts => section-tools-and-assessment.ts} | 2 +- .../disclosures-outside-canda/index.ts | 30 +++++++++---------- .../{section-2.ts => section-contract.ts} | 2 +- .../{section-3.ts => section-controls.ts} | 2 +- .../{section-5.ts => section-risks.ts} | 2 +- .../{section-1.ts => section-storage.ts} | 2 +- .../{section-4.ts => section-track-access.ts} | 2 +- .../mocks/create-pia-intake.mock.ts | 14 ++++----- .../mocks/data/pia-empty-jsonb-values.mock.ts | 14 ++++----- .../test/util/mocks/data/pia-intake.mock.ts | 14 ++++----- 12 files changed, 49 insertions(+), 49 deletions(-) rename src/backend/src/modules/pia-intake/jsonb-classes/security-personal-information/digital-tools-and-systems/{section-2.ts => section-storage.ts} (85%) rename src/backend/src/modules/pia-intake/jsonb-classes/security-personal-information/digital-tools-and-systems/{section-1.ts => section-tools-and-assessment.ts} (83%) rename src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/{section-2.ts => section-contract.ts} (89%) rename src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/{section-3.ts => section-controls.ts} (80%) rename src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/{section-5.ts => section-risks.ts} (93%) rename src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/{section-1.ts => section-storage.ts} (95%) rename src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/{section-4.ts => section-track-access.ts} (77%) diff --git a/src/backend/src/modules/pia-intake/jsonb-classes/security-personal-information/digital-tools-and-systems/index.ts b/src/backend/src/modules/pia-intake/jsonb-classes/security-personal-information/digital-tools-and-systems/index.ts index d25ba919f..a08d23bab 100644 --- a/src/backend/src/modules/pia-intake/jsonb-classes/security-personal-information/digital-tools-and-systems/index.ts +++ b/src/backend/src/modules/pia-intake/jsonb-classes/security-personal-information/digital-tools-and-systems/index.ts @@ -4,19 +4,19 @@ import { ValidateNested, } from '@nestjs/class-validator'; import { Type } from 'class-transformer'; -import { DigitalToolsAndSystemsSection1 } from './section-1'; -import { DigitalToolsAndSystemsSection2 } from './section-2'; +import { DigitalToolsAndSystemsToolsAndAssessment } from './section-tools-and-assessment'; +import { DigitalToolsAndSystemsStorage } from './section-storage'; export class DigitalToolsAndSystems { @IsObject() @IsNotEmptyObject() @ValidateNested() - @Type(() => DigitalToolsAndSystemsSection1) - section1: DigitalToolsAndSystemsSection1; + @Type(() => DigitalToolsAndSystemsToolsAndAssessment) + toolsAndAssessment: DigitalToolsAndSystemsToolsAndAssessment; @IsObject() @IsNotEmptyObject() @ValidateNested() - @Type(() => DigitalToolsAndSystemsSection2) - section2: DigitalToolsAndSystemsSection2; + @Type(() => DigitalToolsAndSystemsStorage) + storage: DigitalToolsAndSystemsStorage; } diff --git a/src/backend/src/modules/pia-intake/jsonb-classes/security-personal-information/digital-tools-and-systems/section-2.ts b/src/backend/src/modules/pia-intake/jsonb-classes/security-personal-information/digital-tools-and-systems/section-storage.ts similarity index 85% rename from src/backend/src/modules/pia-intake/jsonb-classes/security-personal-information/digital-tools-and-systems/section-2.ts rename to src/backend/src/modules/pia-intake/jsonb-classes/security-personal-information/digital-tools-and-systems/section-storage.ts index efafbb4f6..4ebf41e50 100644 --- a/src/backend/src/modules/pia-intake/jsonb-classes/security-personal-information/digital-tools-and-systems/section-2.ts +++ b/src/backend/src/modules/pia-intake/jsonb-classes/security-personal-information/digital-tools-and-systems/section-storage.ts @@ -1,7 +1,7 @@ import { IsEnum, IsOptional, IsString } from '@nestjs/class-validator'; import { YesNoInput } from 'src/common/enums/yes-no-input.enum'; -export class DigitalToolsAndSystemsSection2 { +export class DigitalToolsAndSystemsStorage { @IsEnum(YesNoInput) @IsOptional() onGovServers?: YesNoInput; diff --git a/src/backend/src/modules/pia-intake/jsonb-classes/security-personal-information/digital-tools-and-systems/section-1.ts b/src/backend/src/modules/pia-intake/jsonb-classes/security-personal-information/digital-tools-and-systems/section-tools-and-assessment.ts similarity index 83% rename from src/backend/src/modules/pia-intake/jsonb-classes/security-personal-information/digital-tools-and-systems/section-1.ts rename to src/backend/src/modules/pia-intake/jsonb-classes/security-personal-information/digital-tools-and-systems/section-tools-and-assessment.ts index 9c9411ad6..a1dc7ac5b 100644 --- a/src/backend/src/modules/pia-intake/jsonb-classes/security-personal-information/digital-tools-and-systems/section-1.ts +++ b/src/backend/src/modules/pia-intake/jsonb-classes/security-personal-information/digital-tools-and-systems/section-tools-and-assessment.ts @@ -1,7 +1,7 @@ import { IsEnum, IsOptional } from '@nestjs/class-validator'; import { YesNoInput } from 'src/common/enums/yes-no-input.enum'; -export class DigitalToolsAndSystemsSection1 { +export class DigitalToolsAndSystemsToolsAndAssessment { @IsEnum(YesNoInput) @IsOptional() involveDigitalToolsAndSystems?: YesNoInput; diff --git a/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/index.ts b/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/index.ts index 3bfe78c65..a13a40fb7 100644 --- a/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/index.ts +++ b/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/index.ts @@ -4,40 +4,40 @@ import { ValidateNested, } from '@nestjs/class-validator'; import { Type } from 'class-transformer'; -import { DisclosureSection1 } from './section-1'; -import { DisclosureSection2 } from './section-2'; -import { DisclosureSection3 } from './section-3'; -import { DisclosureSection4 } from './section-4'; -import { DisclosureSection5 } from './section-5'; +import { DisclosureStorage } from './section-storage'; +import { DisclosureContract } from './section-contract'; +import { DisclosureControls } from './section-controls'; +import { DisclosureTrackAccess } from './section-track-access'; +import { DisclosureRisks } from './section-risks'; export class DisclosuresOutsideCanada { @IsObject() @IsNotEmptyObject() @ValidateNested() - @Type(() => DisclosureSection1) - section1: DisclosureSection1; + @Type(() => DisclosureStorage) + storage: DisclosureStorage; @IsObject() @IsNotEmptyObject() @ValidateNested() - @Type(() => DisclosureSection2) - section2: DisclosureSection2; + @Type(() => DisclosureContract) + contract: DisclosureContract; @IsObject() @IsNotEmptyObject() @ValidateNested() - @Type(() => DisclosureSection3) - section3: DisclosureSection3; + @Type(() => DisclosureControls) + controls: DisclosureControls; @IsObject() @IsNotEmptyObject() @ValidateNested() - @Type(() => DisclosureSection4) - section4: DisclosureSection4; + @Type(() => DisclosureTrackAccess) + trackAccess: DisclosureTrackAccess; @IsObject() @IsNotEmptyObject() @ValidateNested() - @Type(() => DisclosureSection5) - section5: DisclosureSection5; + @Type(() => DisclosureRisks) + risks: DisclosureRisks; } diff --git a/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/section-2.ts b/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/section-contract.ts similarity index 89% rename from src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/section-2.ts rename to src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/section-contract.ts index 70a14134b..5273966c0 100644 --- a/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/section-2.ts +++ b/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/section-contract.ts @@ -1,7 +1,7 @@ import { IsEnum, IsOptional, IsString } from '@nestjs/class-validator'; import { YesNoInput } from 'src/common/enums/yes-no-input.enum'; -export class DisclosureSection2 { +export class DisclosureContract { @IsEnum(YesNoInput) @IsOptional() relyOnExistingContract?: YesNoInput; diff --git a/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/section-3.ts b/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/section-controls.ts similarity index 80% rename from src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/section-3.ts rename to src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/section-controls.ts index 7124d95f6..95a9a49f8 100644 --- a/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/section-3.ts +++ b/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/section-controls.ts @@ -1,6 +1,6 @@ import { IsOptional, IsString } from '@nestjs/class-validator'; -export class DisclosureSection3 { +export class DisclosureControls { @IsString() @IsOptional() unauthorizedAccessMeasures?: string; diff --git a/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/section-5.ts b/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/section-risks.ts similarity index 93% rename from src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/section-5.ts rename to src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/section-risks.ts index 884f8768f..fdd7729ef 100644 --- a/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/section-5.ts +++ b/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/section-risks.ts @@ -21,7 +21,7 @@ class PrivacyRisk { outstandingRisk: string; } -export class DisclosureSection5 { +export class DisclosureRisks { @IsArray() @ValidateNested({ each: true }) @Type(() => PrivacyRisk) diff --git a/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/section-1.ts b/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/section-storage.ts similarity index 95% rename from src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/section-1.ts rename to src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/section-storage.ts index 83d10147c..bbd3207ff 100644 --- a/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/section-1.ts +++ b/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/section-storage.ts @@ -19,7 +19,7 @@ class ServiceProviderDetails { details: string; } -export class DisclosureSection1 { +export class DisclosureStorage { @IsEnum(YesNoInput) @IsOptional() sensitiveInfoStoredByServiceProvider?: YesNoInput; diff --git a/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/section-4.ts b/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/section-track-access.ts similarity index 77% rename from src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/section-4.ts rename to src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/section-track-access.ts index 72c42c8dd..362c3ad28 100644 --- a/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/section-4.ts +++ b/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/section-track-access.ts @@ -1,6 +1,6 @@ import { IsOptional, IsString } from '@nestjs/class-validator'; -export class DisclosureSection4 { +export class DisclosureTrackAccess { @IsString() @IsOptional() trackAccessDetails?: string; diff --git a/src/backend/src/modules/pia-intake/mocks/create-pia-intake.mock.ts b/src/backend/src/modules/pia-intake/mocks/create-pia-intake.mock.ts index 54e9880e3..c2cecb17a 100644 --- a/src/backend/src/modules/pia-intake/mocks/create-pia-intake.mock.ts +++ b/src/backend/src/modules/pia-intake/mocks/create-pia-intake.mock.ts @@ -61,7 +61,7 @@ export const piaIntakeEntityMock: CreatePiaIntakeDto = { disclosedOutsideCanada: YesNoInput.NO, }, disclosuresOutsideCanada: { - section1: { + storage: { sensitiveInfoStoredByServiceProvider: YesNoInput.YES, serviceProviderList: [ { @@ -73,17 +73,17 @@ export const piaIntakeEntityMock: CreatePiaIntakeDto = { disclosureDetails: 'S3 storage in us-east-1: US East (N. Virginia)', contractualTerms: 'None', }, - section2: { + contract: { relyOnExistingContract: YesNoInput.YES, enterpriseServiceAccessDetails: 'S3', }, - section3: { + controls: { unauthorizedAccessMeasures: 'IAM rules are in effect', }, - section4: { + trackAccess: { trackAccessDetails: 'IAM', }, - section5: { + risks: { privacyRisks: [ { risk: 'Leak of Creds', @@ -99,11 +99,11 @@ export const piaIntakeEntityMock: CreatePiaIntakeDto = { }, securityPersonalInformation: { digitalToolsAndSystems: { - section1: { + toolsAndAssessment: { involveDigitalToolsAndSystems: YesNoInput.NO, haveSecurityAssessment: YesNoInput.NO, }, - section2: { + storage: { onGovServers: YesNoInput.NO, whereDetails: 'on AWS Cloud', }, diff --git a/src/backend/test/util/mocks/data/pia-empty-jsonb-values.mock.ts b/src/backend/test/util/mocks/data/pia-empty-jsonb-values.mock.ts index ae8db1dbf..995b909d4 100644 --- a/src/backend/test/util/mocks/data/pia-empty-jsonb-values.mock.ts +++ b/src/backend/test/util/mocks/data/pia-empty-jsonb-values.mock.ts @@ -16,34 +16,34 @@ export const emptyJsonbValues = { disclosedOutsideCanada: null, }, disclosuresOutsideCanada: { - section1: { + storage: { sensitiveInfoStoredByServiceProvider: null, serviceProviderList: [], disclosureDetails: null, contractualTerms: null, }, - section2: { + contract: { relyOnExistingContract: null, enterpriseServiceAccessDetails: null, }, - section3: { + controls: { unauthorizedAccessMeasures: null, }, - section4: { + trackAccess: { trackAccessDetails: null, }, - section5: { + risks: { privacyRisks: [], }, }, }, securityPersonalInformation: { digitalToolsAndSystems: { - section1: { + toolsAndAssessment: { involveDigitalToolsAndSystems: null, haveSecurityAssessment: null, }, - section2: { + storage: { onGovServers: null, whereDetails: null, }, diff --git a/src/backend/test/util/mocks/data/pia-intake.mock.ts b/src/backend/test/util/mocks/data/pia-intake.mock.ts index a8ab84232..d57426be1 100644 --- a/src/backend/test/util/mocks/data/pia-intake.mock.ts +++ b/src/backend/test/util/mocks/data/pia-intake.mock.ts @@ -65,7 +65,7 @@ const piaIntakeDataMock = { disclosedOutsideCanada: YesNoInput.NO, }, disclosuresOutsideCanada: { - section1: { + storage: { sensitiveInfoStoredByServiceProvider: YesNoInput.YES, serviceProviderList: [ { @@ -77,17 +77,17 @@ const piaIntakeDataMock = { disclosureDetails: 'S3 storage in us-east-1: US East (N. Virginia)', contractualTerms: 'None', }, - section2: { + contract: { relyOnExistingContract: YesNoInput.YES, enterpriseServiceAccessDetails: 'S3', }, - section3: { + controls: { unauthorizedAccessMeasures: 'IAM rules are in effect', }, - section4: { + trackAccess: { trackAccessDetails: 'IAM', }, - section5: { + risks: { privacyRisks: [ { risk: 'Leak of Creds', @@ -103,11 +103,11 @@ const piaIntakeDataMock = { }, securityPersonalInformation: { digitalToolsAndSystems: { - section1: { + toolsAndAssessment: { involveDigitalToolsAndSystems: YesNoInput.NO, haveSecurityAssessment: YesNoInput.NO, }, - section2: { + storage: { onGovServers: YesNoInput.NO, whereDetails: 'on AWS Cloud', }, From 42f0b93e7c2dd15add3b8abb520e448d502ab642 Mon Sep 17 00:00:00 2001 From: Kushal Arora Date: Wed, 15 Feb 2023 13:40:34 -0800 Subject: [PATCH 17/18] [UTOPIA-769] [Backend] Updating jsonb classes to add IsOptional for array fields --- .../additional-risks/additionalRisk.ts | 3 ++- .../steps-walkthrough.ts | 6 +++++- .../disclosures-outside-canda/section-risks.ts | 12 +++++++++++- .../disclosures-outside-canda/section-storage.ts | 2 ++ .../pia-intake/mocks/create-pia-intake.mock.ts | 14 +++++++------- 5 files changed, 27 insertions(+), 10 deletions(-) diff --git a/src/backend/src/modules/pia-intake/jsonb-classes/additional-risks/additionalRisk.ts b/src/backend/src/modules/pia-intake/jsonb-classes/additional-risks/additionalRisk.ts index 2a30e6cb4..c3380207e 100644 --- a/src/backend/src/modules/pia-intake/jsonb-classes/additional-risks/additionalRisk.ts +++ b/src/backend/src/modules/pia-intake/jsonb-classes/additional-risks/additionalRisk.ts @@ -1,9 +1,10 @@ -import { IsString } from '@nestjs/class-validator'; +import { IsOptional, IsString } from '@nestjs/class-validator'; export class AdditionalRisk { @IsString() risk: string; @IsString() + @IsOptional() response: string; } diff --git a/src/backend/src/modules/pia-intake/jsonb-classes/collection-use-and-disclosure/steps-walkthrough.ts b/src/backend/src/modules/pia-intake/jsonb-classes/collection-use-and-disclosure/steps-walkthrough.ts index cf9d6b773..3885bbb4a 100644 --- a/src/backend/src/modules/pia-intake/jsonb-classes/collection-use-and-disclosure/steps-walkthrough.ts +++ b/src/backend/src/modules/pia-intake/jsonb-classes/collection-use-and-disclosure/steps-walkthrough.ts @@ -1,19 +1,23 @@ -import { IsString } from '@nestjs/class-validator'; +import { IsOptional, IsString } from '@nestjs/class-validator'; import { UserTypesEnum } from 'src/common/enums/users.enum'; import { IFormField } from 'src/common/interfaces/form-field.interface'; import { validateRoleForFormField } from 'src/common/validators/form-field-role.validator'; export class StepWalkthrough { @IsString() + @IsOptional() drafterInput?: string; @IsString() + @IsOptional() mpoInput?: string; @IsString() + @IsOptional() foippaInput?: string; @IsString() + @IsOptional() OtherInput?: string; } diff --git a/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/section-risks.ts b/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/section-risks.ts index fdd7729ef..d438097a8 100644 --- a/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/section-risks.ts +++ b/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/section-risks.ts @@ -1,4 +1,9 @@ -import { IsArray, IsString, ValidateNested } from '@nestjs/class-validator'; +import { + IsArray, + IsOptional, + IsString, + ValidateNested, +} from '@nestjs/class-validator'; import { Type } from 'class-transformer'; class PrivacyRisk { @@ -6,18 +11,23 @@ class PrivacyRisk { risk: string; @IsString() + @IsOptional() impact: string; @IsString() + @IsOptional() likelihoodOfUnauthorizedAccess: string; @IsString() + @IsOptional() levelOfPrivacyRisk: string; @IsString() + @IsOptional() riskResponse: string; @IsString() + @IsOptional() outstandingRisk: string; } diff --git a/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/section-storage.ts b/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/section-storage.ts index bbd3207ff..356a93d0a 100644 --- a/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/section-storage.ts +++ b/src/backend/src/modules/pia-intake/jsonb-classes/storing-personal-information/disclosures-outside-canda/section-storage.ts @@ -13,9 +13,11 @@ class ServiceProviderDetails { name: string; @IsString() + @IsOptional() cloudInfraName: string; @IsString() + @IsOptional() details: string; } diff --git a/src/backend/src/modules/pia-intake/mocks/create-pia-intake.mock.ts b/src/backend/src/modules/pia-intake/mocks/create-pia-intake.mock.ts index c2cecb17a..7585a44e8 100644 --- a/src/backend/src/modules/pia-intake/mocks/create-pia-intake.mock.ts +++ b/src/backend/src/modules/pia-intake/mocks/create-pia-intake.mock.ts @@ -35,20 +35,20 @@ export const piaIntakeEntityMock: CreatePiaIntakeDto = { steps: [ { drafterInput: 'Make a Checklist.', - mpoInput: 'Agreed', - foippaInput: 'Agreed', - OtherInput: 'Agreed', + mpoInput: null, + foippaInput: null, + OtherInput: null, }, { drafterInput: 'Set Your Budget.', - mpoInput: 'Set precise budget', - foippaInput: 'Agreed', - OtherInput: 'Agreed', + mpoInput: null, + foippaInput: null, + OtherInput: null, }, ], collectionNotice: { drafterInput: 'Test Input', - mpoInput: 'Updated Input', + mpoInput: null, }, }, storingPersonalInformation: { From 52a61881ced59bf1068c4c84ff8b9783918b3e62 Mon Sep 17 00:00:00 2001 From: Kushal Arora Date: Wed, 15 Feb 2023 14:12:02 -0800 Subject: [PATCH 18/18] [UTOPIA-769] [Backend] [Bug fix] updated role validation logic when drafter saves after MPO has provided his inputs - with no change in data --- .../validators/form-field-role.validator.ts | 7 +++++-- .../collection-notice.ts | 13 ++++++++----- .../collection-use-and-disclosure/index.ts | 17 ++++++++++------- .../steps-walkthrough.ts | 18 +++++++++++++----- .../modules/pia-intake/pia-intake.service.ts | 10 ++++++---- 5 files changed, 42 insertions(+), 23 deletions(-) diff --git a/src/backend/src/common/validators/form-field-role.validator.ts b/src/backend/src/common/validators/form-field-role.validator.ts index 52b403bdb..d5fc0f207 100644 --- a/src/backend/src/common/validators/form-field-role.validator.ts +++ b/src/backend/src/common/validators/form-field-role.validator.ts @@ -10,11 +10,14 @@ import { IFormField } from '../interfaces/form-field.interface'; */ export const validateRoleForFormField = ( metadata: IFormField, - value: any, + updatedValue: any, + storedValue: any, userType: UserTypesEnum, path: string, ) => { - if (!value) return; // if value not edited - no need to validate permissions + if (!updatedValue) return; // if value not edited - no need to validate permissions + + if (typeof updatedValue === 'string' && updatedValue === storedValue) return; // if value is not updated by the current user; if (!metadata?.allowedUserTypesEdit) return; // if allowedUserTypesEdit is null, all roles can edit this field/key diff --git a/src/backend/src/modules/pia-intake/jsonb-classes/collection-use-and-disclosure/collection-notice.ts b/src/backend/src/modules/pia-intake/jsonb-classes/collection-use-and-disclosure/collection-notice.ts index 41e1e59ce..407e2af86 100644 --- a/src/backend/src/modules/pia-intake/jsonb-classes/collection-use-and-disclosure/collection-notice.ts +++ b/src/backend/src/modules/pia-intake/jsonb-classes/collection-use-and-disclosure/collection-notice.ts @@ -35,20 +35,23 @@ export const CollectionNoticeMetadata: Array> = [ * This method validates role access to CollectionNotice values */ export const validateRoleForCollectionNotice = ( - collectionNotice: CollectionNotice, + updatedValue: CollectionNotice, + storedValue: CollectionNotice, userType: UserTypesEnum, ) => { - if (!collectionNotice) return; + if (!updatedValue) return; - const keys = Object.keys(collectionNotice) as Array; + const keys = Object.keys(updatedValue) as Array; keys.forEach((key) => { - const value = collectionNotice?.[key]; + const updatedKeyValue = updatedValue?.[key]; + const storedKeyValue = storedValue?.[key]; const metadata = CollectionNoticeMetadata.find((m) => m.key === key); validateRoleForFormField( metadata, - value, + updatedKeyValue, + storedKeyValue, userType, `collectionNotice.${key}`, ); diff --git a/src/backend/src/modules/pia-intake/jsonb-classes/collection-use-and-disclosure/index.ts b/src/backend/src/modules/pia-intake/jsonb-classes/collection-use-and-disclosure/index.ts index 1e65fd836..470ed52bd 100644 --- a/src/backend/src/modules/pia-intake/jsonb-classes/collection-use-and-disclosure/index.ts +++ b/src/backend/src/modules/pia-intake/jsonb-classes/collection-use-and-disclosure/index.ts @@ -35,22 +35,25 @@ export class CollectionUseAndDisclosure { * This method validates role access to collectionUseAndDisclosure */ export const validateRoleForCollectionUseAndDisclosure = ( - piaCollectionUseAndDisclosure: CollectionUseAndDisclosure, + updatedValue: CollectionUseAndDisclosure, + storedValue: CollectionUseAndDisclosure, userType: UserTypesEnum, ) => { - if (!piaCollectionUseAndDisclosure) return; + if (!updatedValue) return; // steps walkthrough validations - const steps = piaCollectionUseAndDisclosure?.steps; - if (steps?.length) { - steps.forEach((step: StepWalkthrough) => { - validateRoleForStepWalkthrough(step, userType); + const updatedSteps = updatedValue?.steps; + const storedSteps = storedValue?.steps; + if (updatedSteps?.length) { + updatedSteps.forEach((step: StepWalkthrough, i: number) => { + validateRoleForStepWalkthrough(step, storedSteps?.[i], userType); }); } // collection notice validations validateRoleForCollectionNotice( - piaCollectionUseAndDisclosure?.collectionNotice, + updatedValue?.collectionNotice, + storedValue?.collectionNotice, userType, ); }; diff --git a/src/backend/src/modules/pia-intake/jsonb-classes/collection-use-and-disclosure/steps-walkthrough.ts b/src/backend/src/modules/pia-intake/jsonb-classes/collection-use-and-disclosure/steps-walkthrough.ts index 3885bbb4a..4fe6b86f7 100644 --- a/src/backend/src/modules/pia-intake/jsonb-classes/collection-use-and-disclosure/steps-walkthrough.ts +++ b/src/backend/src/modules/pia-intake/jsonb-classes/collection-use-and-disclosure/steps-walkthrough.ts @@ -55,17 +55,25 @@ export const StepWalkthroughMetadata: Array> = [ * This method validates role access to StepWalkthrough values */ export const validateRoleForStepWalkthrough = ( - step: StepWalkthrough, + updatedStep: StepWalkthrough, + storedStep: StepWalkthrough, userType: UserTypesEnum, ) => { - if (!step) return; + if (!updatedStep) return; - const keys = Object.keys(step) as Array; + const keys = Object.keys(updatedStep) as Array; keys.forEach((key) => { - const value = step?.[key]; + const updatedValue = updatedStep?.[key]; + const storedValue = storedStep?.[key]; const metadata = StepWalkthroughMetadata.find((m) => m.key === key); - validateRoleForFormField(metadata, value, userType, `steps.${key}`); + validateRoleForFormField( + metadata, + updatedValue, + storedValue, + userType, + `steps.${key}`, + ); }); }; diff --git a/src/backend/src/modules/pia-intake/pia-intake.service.ts b/src/backend/src/modules/pia-intake/pia-intake.service.ts index 1dc24fa6a..eff149828 100644 --- a/src/backend/src/modules/pia-intake/pia-intake.service.ts +++ b/src/backend/src/modules/pia-intake/pia-intake.service.ts @@ -47,7 +47,7 @@ export class PiaIntakeService { // sending DRAFTER to userType as only a drafter can create a new PIA; // A user could have MPO privileges, however while creating a PIA he/she is acting as a drafter - this.validateJsonbFields(createPiaIntakeDto, UserTypesEnum.DRAFTER); + this.validateJsonbFields(createPiaIntakeDto, null, UserTypesEnum.DRAFTER); const piaInfoForm: PiaIntakeEntity = await this.piaIntakeRepository.save({ ...createPiaIntakeDto, @@ -90,7 +90,7 @@ export class PiaIntakeService { } // validate jsonb fields for role access - this.validateJsonbFields(updatePiaIntakeDto, userType); + this.validateJsonbFields(updatePiaIntakeDto, existingRecord, userType); // remove the provided saveId delete updatePiaIntakeDto.saveId; @@ -448,11 +448,13 @@ export class PiaIntakeService { * 6. additionalRisks */ validateJsonbFields( - pia: CreatePiaIntakeDto | UpdatePiaIntakeDto, + updatedValue: CreatePiaIntakeDto | UpdatePiaIntakeDto, + storedValue: PiaIntakeEntity, userType: UserTypesEnum, ) { validateRoleForCollectionUseAndDisclosure( - pia?.collectionUseAndDisclosure, + updatedValue?.collectionUseAndDisclosure, + storedValue?.collectionUseAndDisclosure, userType, ); // space for future validators, as needed