From 7eb00b5608a2ddc3d2e7ecd6bd8f5d870bbee686 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Tue, 21 Jan 2025 08:10:46 -0800 Subject: [PATCH] GPP MSPA Control Module: add support for usnat version 2 (#12667) * MSPA: add support for usnat version 2 * Use 15 (array bound) for testing --- libraries/mspa/activityControls.js | 33 +++++++-- .../libraries/mspa/activityControls_spec.js | 72 ++++++++++++++++--- 2 files changed, 89 insertions(+), 16 deletions(-) diff --git a/libraries/mspa/activityControls.js b/libraries/mspa/activityControls.js index eb68259d585..c93748f73c7 100644 --- a/libraries/mspa/activityControls.js +++ b/libraries/mspa/activityControls.js @@ -24,6 +24,8 @@ export function isBasicConsentDenied(cd) { cd.PersonalDataConsents === 2 || // minors 13+ who have not given consent cd.KnownChildSensitiveDataConsents[0] === 1 || + // minors 16+ who have not given consent (added in usnat version 2) + cd.KnownChildSensitiveDataConsents[2] === 1 || // minors under 13 cannot consent isApplicable(cd.KnownChildSensitiveDataConsents[1]) || // covered cannot be zero @@ -53,14 +55,31 @@ export function isConsentDenied(cd) { } export const isTransmitUfpdConsentDenied = (() => { - // deny anything that smells like: genetic, biometric, state/national ID, financial, union membership, - // or personal communication data - const cannotBeInScope = [6, 7, 9, 10, 12].map(el => --el); - // require consent for everything else (except geo, which is treated separately) - const allExceptGeo = Array.from(Array(12).keys()).filter((el) => el !== SENSITIVE_DATA_GEO) - const mustHaveConsent = allExceptGeo.filter(el => !cannotBeInScope.includes(el)); + const sensitiveFlags = (() => { + // deny anything that smells like: genetic, biometric, state/national ID, financial, union membership, + // personal communication data, status as victim of crime (version 2), status as transgender/nonbinary (version 2) + const cannotBeInScope = [6, 7, 9, 10, 12, 14, 16].map(el => --el); + // require consent for everything else (except geo, which is treated separately) + const allExceptGeo = Array.from(Array(16).keys()).filter((el) => el !== SENSITIVE_DATA_GEO) + const mustHaveConsent = allExceptGeo.filter(el => !cannotBeInScope.includes(el)); + + return Object.fromEntries( + Object.entries({ + 1: 12, + 2: 16 + }).map(([version, cardinality]) => { + const isInVersion = (el) => el < cardinality + return [version, { + cannotBeInScope: cannotBeInScope.filter(isInVersion), + allExceptGeo: allExceptGeo.filter(isInVersion), + mustHaveConsent: mustHaveConsent.filter(isInVersion) + }] + }) + ) + })() return function (cd) { + const {cannotBeInScope, mustHaveConsent, allExceptGeo} = sensitiveFlags[cd.Version]; return isConsentDenied(cd) || // no notice about sensitive data was given sensitiveNoticeIs(cd, 2) || @@ -97,7 +116,7 @@ export function mspaRule(sids, getConsent, denies, applicableSids = () => gppDat if (consent == null) { return {allow: false, reason: 'consent data not available'}; } - if (consent.Version !== 1) { + if (![1, 2].includes(consent.Version)) { return {allow: false, reason: `unsupported consent specification version "${consent.Version}"`} } if (denies(consent)) { diff --git a/test/spec/libraries/mspa/activityControls_spec.js b/test/spec/libraries/mspa/activityControls_spec.js index 80d9fc500b1..dcbebf9974c 100644 --- a/test/spec/libraries/mspa/activityControls_spec.js +++ b/test/spec/libraries/mspa/activityControls_spec.js @@ -45,6 +45,13 @@ describe('Consent interpretation', () => { expect(isBasicConsentDenied(mkConsent({ KnownChildSensitiveDataConsents: [0, null] }))).to.be.false; + }); + + it('should deny when Version = 2 & childconsent[3] is 1', () => { + expect(isBasicConsentDenied(mkConsent({ + Version: 2, + KnownChildSensitiveDataConsents: [null, null, 1] + }))).to.be.true; }) }); @@ -84,13 +91,60 @@ describe('Consent interpretation', () => { expect(result).to.equal(false); }); Object.entries({ - 'health information': 2, - 'biometric data': 6, - }).forEach(([t, flagNo]) => { - it(`'should be true (consent denied to add ufpd) if no consent to process ${t}'`, () => { - const consent = mkConsent(); - consent.SensitiveDataProcessing[flagNo] = 1; - expect(isTransmitUfpdConsentDenied(consent)).to.be.true; + 'health information': { + flagNo: 2, + consents: { + 1: true, + 2: false + }, + versions: [1, 2] + }, + 'biometric data': { + flagNo: 6, + consents: { + 1: true, + 2: true + }, + versions: [1, 2] + }, + 'consumer health data': { + flagNo: 12, + consents: { + 1: true, + 2: false + }, + versions: [2] + }, + 'status as transgender': { + flagNo: 15, + consents: { + 1: true, + 2: true, + }, + versions: [2] + } + + }).forEach(([t, {flagNo, consents, versions}]) => { + describe(t, () => { + Object.entries(consents).forEach(([flagValue, shouldBeDenied]) => { + const flagDescription = ({ + 1: 'denied', + 2: 'given' + })[flagValue] + describe(`consent is ${flagValue} (${flagDescription})`, () => { + let consent; + beforeEach(() => { + consent = mkConsent(); + consent.SensitiveDataProcessing[flagNo] = parseInt(flagValue, 10); + }); + versions.forEach(version => { + it(`should ${shouldBeDenied ? 'deny' : 'allow'} (version ${version})`, () => { + consent.Version = version; + expect(isTransmitUfpdConsentDenied(consent)).to.eql(shouldBeDenied); + }) + }) + }) + }) }) }); @@ -181,8 +235,8 @@ describe('mspaRule', () => { expect(mkRule()().allow).to.equal(false); }); - it('should deny when consent is using version != 1', () => { - consent = {Version: 2}; + it('should deny when consent is using version other than 1/2', () => { + consent = {Version: 3}; expect(mkRule()().allow).to.equal(false); })