Skip to content

Commit

Permalink
GPP MSPA Control Module: add support for usnat version 2 (#12667)
Browse files Browse the repository at this point in the history
* MSPA: add support for usnat version 2

* Use 15 (array bound) for testing
  • Loading branch information
dgirardi authored Jan 21, 2025
1 parent 5b0024e commit 7eb00b5
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 16 deletions.
33 changes: 26 additions & 7 deletions libraries/mspa/activityControls.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) ||
Expand Down Expand Up @@ -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)) {
Expand Down
72 changes: 63 additions & 9 deletions test/spec/libraries/mspa/activityControls_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
})
});

Expand Down Expand Up @@ -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);
})
})
})
})
})
});

Expand Down Expand Up @@ -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);
})

Expand Down

0 comments on commit 7eb00b5

Please sign in to comment.