Skip to content

Commit

Permalink
Use nesting instead of combining flat records
Browse files Browse the repository at this point in the history
  • Loading branch information
matheus23 committed Dec 13, 2021
1 parent 2ee2775 commit d31b2d1
Show file tree
Hide file tree
Showing 4 changed files with 168 additions and 116 deletions.
46 changes: 27 additions & 19 deletions src/attenuation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export interface CapabilitySemantics<A> {
* - `null`: The capabilities from `parentCap` and `childCap` are unrelated and can't be compared nor delegated.
* - `CapabilityEscalation<A>`: It's clear that `childCap` is meant to be delegated from `parentCap`, but there's a rights escalation.
*/
tryDelegating<T extends A>(parentCap: T, childCap: T): T | null | CapabilityEscalation<A>
tryDelegating(parentCap: A, childCap: A): A | null | CapabilityEscalation<A>
// TODO builders
}

Expand All @@ -33,6 +33,17 @@ export interface CapabilityInfo {
}


export interface CapabilityWithInfo<A> {
info: CapabilityInfo
capability: A
}


export type CapabilityResult<A>
= CapabilityWithInfo<A>
| CapabilityEscalation<A>


export interface CapabilityEscalation<A> {
escalation: string // reason
capability: A // the capability that escalated rights
Expand All @@ -45,21 +56,16 @@ function isCapabilityEscalation<A>(obj: unknown): obj is CapabilityEscalation<A>
}


export type CapabilityResult<A>
= A & CapabilityInfo
| CapabilityEscalation<A>


export function capabilities<A>(
ucan: Chained,
capability: CapabilitySemantics<A>,
): Iterable<CapabilityResult<A>> {

function* findParsingCaps(ucan: Ucan<never>): Iterable<A & CapabilityInfo> {
function* findParsingCaps(ucan: Ucan<never>): Iterable<CapabilityWithInfo<A>> {
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 }
}
}

Expand All @@ -74,7 +80,7 @@ export function capabilities<A>(
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) {
Expand All @@ -84,7 +90,10 @@ export function capabilities<A>(
if (isCapabilityEscalation(delegated)) {
yield delegated // which is an escalation
} else {
yield delegateCapabilityInfo({ ...parsedChildCap, ...delegated }, parsedParentCap)
yield {
info: delegateCapabilityInfo(parsedChildCap.info, parsedParentCap.info),
capability: delegated
}
}
}
}
Expand All @@ -103,19 +112,18 @@ export function capabilities<A>(
return ucan.reduce(delegate)()
}

function delegateCapabilityInfo<A extends CapabilityInfo>(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,
}
}
Expand Down
42 changes: 17 additions & 25 deletions src/capability/wnfs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,23 +57,32 @@ export const wnfsPublicSemantics: CapabilitySemantics<WnfsPublicCapability> = {
}
},

tryDelegating<T extends WnfsPublicCapability>(parentCap: T, childCap: T): T | null | CapabilityEscalation<WnfsPublicCapability> {
tryDelegating(parentCap: WnfsPublicCapability, childCap: WnfsPublicCapability): WnfsPublicCapability | null | CapabilityEscalation<WnfsPublicCapability> {
// 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,
}
}
}

Expand Down Expand Up @@ -134,7 +143,10 @@ const wnfsPrivateSemantics: CapabilitySemantics<WnfsPrivateCapability> = {

// 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 {
Expand All @@ -148,23 +160,3 @@ const wnfsPrivateSemantics: CapabilitySemantics<WnfsPrivateCapability> = {
export function wnfsPrivateCapabilities(ucan: Chained) {
return capabilities(ucan, wnfsPrivateSemantics)
}



// ㊙️


function escalationPublic<T extends WnfsPublicCapability>(reason: string, cap: T): CapabilityEscalation<WnfsPublicCapability> {
return {
escalation: reason,
capability: { user: cap.user, publicPath: cap.publicPath, cap: cap.cap }
}
}


function escalationPrivate<T extends WnfsPrivateCapability>(reason: string, cap: T): CapabilityEscalation<WnfsPrivateCapability> {
return {
escalation: reason,
capability: { user: cap.user, requiredINumbers: cap.requiredINumbers, cap: cap.cap }
}
}
84 changes: 54 additions & 30 deletions tests/attenuation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: "[email protected]",
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: "[email protected]",
cap: "SEND"
}
}])
})

Expand All @@ -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: "[email protected]",
cap: "SEND"
info: {
originator: bob.did(),
expiresAt: ucan.payload.exp,
notBefore: ucan.payload.nbf,
},
capability: {
email: "[email protected]",
cap: "SEND"
}
}])
})

Expand Down Expand Up @@ -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: "[email protected]",
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: "[email protected]",
cap: "SEND",
}
},
{
originator: bob.did(),
expiresAt: Math.min(leafUcanBob.payload.exp, ucan.payload.exp),
notBefore: maxNbf(leafUcanBob.payload.nbf, ucan.payload.nbf),
email: "[email protected]",
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: "[email protected]",
cap: "SEND",
}
}
])
})
Expand Down Expand Up @@ -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: "[email protected]",
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: "[email protected]",
cap: "SEND",
}
},
{
originator: bob.did(),
expiresAt: Math.min(leafUcanBob.payload.exp, ucan.payload.exp),
notBefore: maxNbf(leafUcanBob.payload.nbf, ucan.payload.nbf),
email: "[email protected]",
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: "[email protected]",
cap: "SEND",
}
}
])
})
Expand Down
Loading

0 comments on commit d31b2d1

Please sign in to comment.