From d31b2d13ee60868a6d0822479549ef7c0e65c89f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Kr=C3=BCger?= Date: Mon, 13 Dec 2021 20:29:18 +0100 Subject: [PATCH] Use nesting instead of combining flat records --- src/attenuation.ts | 46 ++++++++------ src/capability/wnfs.ts | 42 +++++-------- tests/attenuation.test.ts | 84 ++++++++++++++++--------- tests/capabilitiy/wnfs.test.ts | 112 ++++++++++++++++++++------------- 4 files changed, 168 insertions(+), 116 deletions(-) diff --git a/src/attenuation.ts b/src/attenuation.ts index 828dfcd..fd9ace9 100644 --- a/src/attenuation.ts +++ b/src/attenuation.ts @@ -21,7 +21,7 @@ export interface CapabilitySemantics { * - `null`: The capabilities from `parentCap` and `childCap` are unrelated and can't be compared nor delegated. * - `CapabilityEscalation`: It's clear that `childCap` is meant to be delegated from `parentCap`, but there's a rights escalation. */ - tryDelegating(parentCap: T, childCap: T): T | null | CapabilityEscalation + tryDelegating(parentCap: A, childCap: A): A | null | CapabilityEscalation // TODO builders } @@ -33,6 +33,17 @@ export interface CapabilityInfo { } +export interface CapabilityWithInfo { + info: CapabilityInfo + capability: A +} + + +export type CapabilityResult + = CapabilityWithInfo + | CapabilityEscalation + + export interface CapabilityEscalation { escalation: string // reason capability: A // the capability that escalated rights @@ -45,21 +56,16 @@ function isCapabilityEscalation(obj: unknown): obj is CapabilityEscalation } -export type CapabilityResult - = A & CapabilityInfo - | CapabilityEscalation - - export function capabilities( ucan: Chained, capability: CapabilitySemantics, ): Iterable> { - function* findParsingCaps(ucan: Ucan): Iterable { + function* findParsingCaps(ucan: Ucan): Iterable> { const capInfo = parseCapabilityInfo(ucan) for (const cap of ucan.payload.att) { const parsedCap = capability.tryParsing(cap) - if (parsedCap != null) yield { ...parsedCap, ...capInfo } + if (parsedCap != null) yield { info: capInfo, capability: parsedCap } } } @@ -74,7 +80,7 @@ export function capabilities( yield parsedParentCap } else { // try figuring out whether we can delegate the capabilities from this to the parent - const delegated = capability.tryDelegating(parsedParentCap, parsedChildCap) + const delegated = capability.tryDelegating(parsedParentCap.capability, parsedChildCap.capability) // if the capabilities *are* related, then this will be non-null // otherwise we just continue looking if (delegated != null) { @@ -84,7 +90,10 @@ export function capabilities( if (isCapabilityEscalation(delegated)) { yield delegated // which is an escalation } else { - yield delegateCapabilityInfo({ ...parsedChildCap, ...delegated }, parsedParentCap) + yield { + info: delegateCapabilityInfo(parsedChildCap.info, parsedParentCap.info), + capability: delegated + } } } } @@ -103,19 +112,18 @@ export function capabilities( return ucan.reduce(delegate)() } -function delegateCapabilityInfo(childCap: A, parentCap: A): A { +function delegateCapabilityInfo(childInfo: CapabilityInfo, parentInfo: CapabilityInfo): CapabilityInfo { let notBefore = {} - if (childCap.notBefore != null && parentCap.notBefore != null) { - notBefore = { notBefore: Math.max(childCap.notBefore, parentCap.notBefore) } - } else if (parentCap.notBefore != null) { - notBefore = { notBefore: parentCap.notBefore } + if (childInfo.notBefore != null && parentInfo.notBefore != null) { + notBefore = { notBefore: Math.max(childInfo.notBefore, parentInfo.notBefore) } + } else if (parentInfo.notBefore != null) { + notBefore = { notBefore: parentInfo.notBefore } } else { - notBefore = { notBefore: childCap.notBefore } + notBefore = { notBefore: childInfo.notBefore } } return { - ...childCap, - originator: parentCap.originator, - expiresAt: Math.min(childCap.expiresAt, parentCap.expiresAt), + originator: parentInfo.originator, + expiresAt: Math.min(childInfo.expiresAt, parentInfo.expiresAt), ...notBefore, } } diff --git a/src/capability/wnfs.ts b/src/capability/wnfs.ts index 5714a45..eca89bc 100644 --- a/src/capability/wnfs.ts +++ b/src/capability/wnfs.ts @@ -57,23 +57,32 @@ export const wnfsPublicSemantics: CapabilitySemantics = { } }, - tryDelegating(parentCap: T, childCap: T): T | null | CapabilityEscalation { + tryDelegating(parentCap: WnfsPublicCapability, childCap: WnfsPublicCapability): WnfsPublicCapability | null | CapabilityEscalation { // need to delegate the same user's file system if (childCap.user !== parentCap.user) return null // must not escalate capability level if (wnfsCapLevels[childCap.cap] > wnfsCapLevels[parentCap.cap]) { - return escalationPublic("Capability level escalation", childCap) + return { + escalation: "Capability level escalation", + capability: childCap, + } } // parentCap path must be a prefix of childCap path if (childCap.publicPath.length < parentCap.publicPath.length) { - return escalationPublic("WNFS Public path access escalation", childCap) + return { + escalation: "WNFS Public path access escalation", + capability: childCap, + } } for (let i = 0; i < parentCap.publicPath.length; i++) { if (childCap.publicPath[i] !== parentCap.publicPath[i]) { - return escalationPublic("WNFS Public path access escalation", childCap) + return { + escalation: "WNFS Public path access escalation", + capability: childCap, + } } } @@ -134,7 +143,10 @@ const wnfsPrivateSemantics: CapabilitySemantics = { // This escalation *could* be wrong, but we shouldn't assume they're unrelated either. if (wnfsCapLevels[childCap.cap] > wnfsCapLevels[parentCap.cap]) { - return escalationPrivate("Capability level escalation", childCap) + return { + escalation: "Capability level escalation", + capability: childCap, + } } return { @@ -148,23 +160,3 @@ const wnfsPrivateSemantics: CapabilitySemantics = { export function wnfsPrivateCapabilities(ucan: Chained) { return capabilities(ucan, wnfsPrivateSemantics) } - - - -// ㊙️ - - -function escalationPublic(reason: string, cap: T): CapabilityEscalation { - return { - escalation: reason, - capability: { user: cap.user, publicPath: cap.publicPath, cap: cap.cap } - } -} - - -function escalationPrivate(reason: string, cap: T): CapabilityEscalation { - return { - escalation: reason, - capability: { user: cap.user, requiredINumbers: cap.requiredINumbers, cap: cap.cap } - } -} diff --git a/tests/attenuation.test.ts b/tests/attenuation.test.ts index 96637ce..cacbcc9 100644 --- a/tests/attenuation.test.ts +++ b/tests/attenuation.test.ts @@ -33,11 +33,15 @@ describe("attenuation.emailCapabilities", () => { const emailCaps = Array.from(emailCapabilities(await Chained.fromToken(token.encode(ucan)))) expect(emailCaps).toEqual([{ - originator: alice.did(), - expiresAt: Math.min(leafUcan.payload.exp, ucan.payload.exp), - notBefore: maxNbf(leafUcan.payload.nbf, ucan.payload.nbf), - email: "alice@email.com", - cap: "SEND" + info: { + originator: alice.did(), + expiresAt: Math.min(leafUcan.payload.exp, ucan.payload.exp), + notBefore: maxNbf(leafUcan.payload.nbf, ucan.payload.nbf), + }, + capability: { + email: "alice@email.com", + cap: "SEND" + } }]) }) @@ -62,11 +66,15 @@ describe("attenuation.emailCapabilities", () => { // we implicitly expect the originator to become bob expect(Array.from(emailCapabilities(await Chained.fromToken(token.encode(ucan))))).toEqual([{ - originator: bob.did(), - expiresAt: ucan.payload.exp, - notBefore: ucan.payload.nbf, - email: "bob@email.com", - cap: "SEND" + info: { + originator: bob.did(), + expiresAt: ucan.payload.exp, + notBefore: ucan.payload.nbf, + }, + capability: { + email: "bob@email.com", + cap: "SEND" + } }]) }) @@ -112,18 +120,26 @@ describe("attenuation.emailCapabilities", () => { expect(Array.from(emailCapabilities(chained))).toEqual([ { - originator: alice.did(), - expiresAt: Math.min(leafUcanAlice.payload.exp, ucan.payload.exp), - notBefore: maxNbf(leafUcanAlice.payload.nbf, ucan.payload.nbf), - email: "alice@email.com", - cap: "SEND", + info: { + originator: alice.did(), + expiresAt: Math.min(leafUcanAlice.payload.exp, ucan.payload.exp), + notBefore: maxNbf(leafUcanAlice.payload.nbf, ucan.payload.nbf), + }, + capability: { + email: "alice@email.com", + cap: "SEND", + } }, { - originator: bob.did(), - expiresAt: Math.min(leafUcanBob.payload.exp, ucan.payload.exp), - notBefore: maxNbf(leafUcanBob.payload.nbf, ucan.payload.nbf), - email: "bob@email.com", - cap: "SEND", + info: { + originator: bob.did(), + expiresAt: Math.min(leafUcanBob.payload.exp, ucan.payload.exp), + notBefore: maxNbf(leafUcanBob.payload.nbf, ucan.payload.nbf), + }, + capability: { + email: "bob@email.com", + cap: "SEND", + } } ]) }) @@ -162,18 +178,26 @@ describe("attenuation.emailCapabilities", () => { expect(Array.from(emailCapabilities(chained))).toEqual([ { - originator: alice.did(), - expiresAt: Math.min(leafUcanAlice.payload.exp, ucan.payload.exp), - notBefore: maxNbf(leafUcanAlice.payload.nbf, ucan.payload.nbf), - email: "alice@email.com", - cap: "SEND", + info: { + originator: alice.did(), + expiresAt: Math.min(leafUcanAlice.payload.exp, ucan.payload.exp), + notBefore: maxNbf(leafUcanAlice.payload.nbf, ucan.payload.nbf), + }, + capability: { + email: "alice@email.com", + cap: "SEND", + } }, { - originator: bob.did(), - expiresAt: Math.min(leafUcanBob.payload.exp, ucan.payload.exp), - notBefore: maxNbf(leafUcanBob.payload.nbf, ucan.payload.nbf), - email: "alice@email.com", - cap: "SEND", + info: { + originator: bob.did(), + expiresAt: Math.min(leafUcanBob.payload.exp, ucan.payload.exp), + notBefore: maxNbf(leafUcanBob.payload.nbf, ucan.payload.nbf), + }, + capability: { + email: "alice@email.com", + cap: "SEND", + } } ]) }) diff --git a/tests/capabilitiy/wnfs.test.ts b/tests/capabilitiy/wnfs.test.ts index 5bc8672..3bf19ed 100644 --- a/tests/capabilitiy/wnfs.test.ts +++ b/tests/capabilitiy/wnfs.test.ts @@ -24,12 +24,16 @@ describe("wnfs public capability", () => { expect(Array.from(wnfsPublicCapabilities(chain))).toEqual([ { - originator: alice.did(), - expiresAt: Math.min(leaf.payload.exp, ucan.payload.exp), - notBefore: maxNbf(leaf.payload.nbf, ucan.payload.nbf), - user: "boris.fission.name", - publicPath: ["Apps", "appinator"], - cap: "REVISE", + info: { + originator: alice.did(), + expiresAt: Math.min(leaf.payload.exp, ucan.payload.exp), + notBefore: maxNbf(leaf.payload.nbf, ucan.payload.nbf), + }, + capability: { + user: "boris.fission.name", + publicPath: ["Apps", "appinator"], + cap: "REVISE", + } } ]) }) @@ -81,12 +85,16 @@ describe("wnfs public capability", () => { } }, { - originator: alice.did(), - expiresAt: Math.min(leaf.payload.exp, ucan.payload.exp), - notBefore: maxNbf(leaf.payload.nbf, ucan.payload.nbf), - user: "boris.fission.name", - publicPath: ["Apps", "appinator"], - cap: "OVERWRITE", + info: { + originator: alice.did(), + expiresAt: Math.min(leaf.payload.exp, ucan.payload.exp), + notBefore: maxNbf(leaf.payload.nbf, ucan.payload.nbf), + }, + capability: { + user: "boris.fission.name", + publicPath: ["Apps", "appinator"], + cap: "OVERWRITE", + } } ]) }) @@ -109,12 +117,16 @@ describe("wnfs private capability", () => { expect(Array.from(wnfsPrivateCapabilities(chain))).toEqual([ { - originator: alice.did(), - expiresAt: Math.min(leaf.payload.exp, ucan.payload.exp), - notBefore: maxNbf(leaf.payload.nbf, ucan.payload.nbf), - user: "boris.fission.name", - requiredINumbers: new Set(["abc", "def"]), - cap: "REVISE", + info: { + originator: alice.did(), + expiresAt: Math.min(leaf.payload.exp, ucan.payload.exp), + notBefore: maxNbf(leaf.payload.nbf, ucan.payload.nbf), + }, + capability: { + user: "boris.fission.name", + requiredINumbers: new Set(["abc", "def"]), + cap: "REVISE", + } } ]) }) @@ -171,12 +183,16 @@ describe("wnfs private capability", () => { } }, { - originator: alice.did(), - expiresAt: Math.min(leaf.payload.exp, ucan.payload.exp), - notBefore: maxNbf(leaf.payload.nbf, ucan.payload.nbf), - user: "boris.fission.name", - requiredINumbers: new Set(["abc", "ghi"]), - cap: "CREATE", + info: { + originator: alice.did(), + expiresAt: Math.min(leaf.payload.exp, ucan.payload.exp), + notBefore: maxNbf(leaf.payload.nbf, ucan.payload.nbf), + }, + capability: { + user: "boris.fission.name", + requiredINumbers: new Set(["abc", "ghi"]), + cap: "CREATE", + } } ]) }) @@ -201,20 +217,28 @@ describe("wnfs private capability", () => { expect(Array.from(wnfsPrivateCapabilities(chain))).toEqual([ { - originator: alice.did(), - expiresAt: Math.min(leafAlice.payload.exp, ucan.payload.exp), - notBefore: maxNbf(leafAlice.payload.nbf, ucan.payload.nbf), - user: "boris.fission.name", - requiredINumbers: new Set(["inumalice", "subinum"]), - cap: "OVERWRITE", + info: { + originator: alice.did(), + expiresAt: Math.min(leafAlice.payload.exp, ucan.payload.exp), + notBefore: maxNbf(leafAlice.payload.nbf, ucan.payload.nbf), + }, + capability: { + user: "boris.fission.name", + requiredINumbers: new Set(["inumalice", "subinum"]), + cap: "OVERWRITE", + } }, { - originator: bob.did(), - expiresAt: Math.min(leafBob.payload.exp, ucan.payload.exp), - notBefore: maxNbf(leafBob.payload.nbf, ucan.payload.nbf), - user: "boris.fission.name", - requiredINumbers: new Set(["inumbob", "subinum"]), - cap: "OVERWRITE", + info: { + originator: bob.did(), + expiresAt: Math.min(leafBob.payload.exp, ucan.payload.exp), + notBefore: maxNbf(leafBob.payload.nbf, ucan.payload.nbf), + }, + capability: { + user: "boris.fission.name", + requiredINumbers: new Set(["inumbob", "subinum"]), + cap: "OVERWRITE", + } } ]) }) @@ -247,12 +271,16 @@ describe("wnfs private capability", () => { } }, { - originator: bob.did(), - expiresAt: Math.min(leafBob.payload.exp, ucan.payload.exp), - notBefore: maxNbf(leafBob.payload.nbf, ucan.payload.nbf), - user: "boris.fission.name", - requiredINumbers: new Set(["inumbob", "subinum"]), - cap: "OVERWRITE", + info: { + originator: bob.did(), + expiresAt: Math.min(leafBob.payload.exp, ucan.payload.exp), + notBefore: maxNbf(leafBob.payload.nbf, ucan.payload.nbf), + }, + capability: { + user: "boris.fission.name", + requiredINumbers: new Set(["inumbob", "subinum"]), + cap: "OVERWRITE", + } } ]) })