-
Notifications
You must be signed in to change notification settings - Fork 23
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: #418 #410
feat: #418 #410
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,66 @@ | ||||||
/** | ||||||
* Provider Capabilities | ||||||
* | ||||||
* These can be imported directly with: | ||||||
* ```js | ||||||
* import * as Consumer from '@web3-storage/capabilities/consumer' | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see |
||||||
* ``` | ||||||
* | ||||||
* @module | ||||||
*/ | ||||||
import { capability, DID, URI, Link } from '@ucanto/validator' | ||||||
// @ts-ignore | ||||||
// eslint-disable-next-line no-unused-vars | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. hopefully not needed now that this is in |
||||||
import * as Types from '@ucanto/interface' | ||||||
import { equalWith, fail, equal, equalCID } from './utils.js' | ||||||
import { top } from './top.js' | ||||||
|
||||||
export { top } | ||||||
|
||||||
/** | ||||||
* Capability can only be delegated (but not invoked) allowing audience to | ||||||
* derived any `consumer/` prefixed capability for the agent identified | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
* by did:key in the `with` field. | ||||||
*/ | ||||||
export const consumer = top.derive({ | ||||||
to: capability({ | ||||||
can: 'consumer/*', | ||||||
with: DID, | ||||||
derives: equalWith, | ||||||
}), | ||||||
derives: equalWith, | ||||||
}) | ||||||
|
||||||
const base = top.or(consumer) | ||||||
|
||||||
/** | ||||||
* Capability can be invoked by an agent to request a `consumer/add` for an account. | ||||||
*/ | ||||||
export const add = base.derive({ | ||||||
to: capability({ | ||||||
can: 'consumer/add', | ||||||
/** | ||||||
* Must be an provider DID | ||||||
*/ | ||||||
with: DID, | ||||||
nb: { | ||||||
/** | ||||||
* CID for the provider/get invocation | ||||||
*/ | ||||||
request: Link, | ||||||
/** | ||||||
* Support specific space DIDs or undefined to request a provider for multiple spaces | ||||||
*/ | ||||||
consumer: URI.match({ protocol: 'did:' }).optional(), | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. and maybe add an There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. but basically the point is - we can use the schemas to never allow ambiguous values like |
||||||
}, | ||||||
derives: (child, parent) => { | ||||||
return ( | ||||||
fail(equalWith(child, parent)) || | ||||||
fail(equalCID(child.nb.request, parent.nb.request, 'request')) || | ||||||
fail(equal(child.nb.consumer, parent.nb.consumer, 'consumer')) || | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seems like to accomodate this from spec, this check might need to adjust to authorize deriving from |
||||||
true | ||||||
) | ||||||
}, | ||||||
}), | ||||||
derives: equalWith, | ||||||
}) |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,83 @@ | ||||||
/** | ||||||
* Provider Capabilities | ||||||
* | ||||||
* These can be imported directly with: | ||||||
* ```js | ||||||
* import * as Provider from '@web3-storage/capabilities/provider' | ||||||
* ``` | ||||||
* | ||||||
* @module | ||||||
*/ | ||||||
import { capability, DID, URI } from '@ucanto/validator' | ||||||
// @ts-ignore | ||||||
// eslint-disable-next-line no-unused-vars | ||||||
import * as Types from '@ucanto/interface' | ||||||
import { equalWith, fail, equal } from './utils.js' | ||||||
import { top } from './top.js' | ||||||
|
||||||
export { top } | ||||||
|
||||||
/** | ||||||
* Capability can only be delegated (but not invoked) allowing audience to | ||||||
* derived any `provider/` prefixed capability for the agent identified | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
* by did:key in the `with` field. | ||||||
*/ | ||||||
export const provider = top.derive({ | ||||||
to: capability({ | ||||||
can: 'provider/*', | ||||||
with: DID, | ||||||
derives: equalWith, | ||||||
}), | ||||||
derives: equalWith, | ||||||
}) | ||||||
|
||||||
const base = top.or(provider) | ||||||
|
||||||
/** | ||||||
* Capability can be invoked by an agent to request a `consumer/add` for a space. | ||||||
*/ | ||||||
export const get = base.derive({ | ||||||
to: capability({ | ||||||
can: 'provider/get', | ||||||
with: DID, | ||||||
nb: { | ||||||
provider: DID, | ||||||
/** | ||||||
* Support specific space DIDs or undefined to request a provider for multiple spaces | ||||||
*/ | ||||||
consumer: URI.match({ protocol: 'did:' }).optional(), | ||||||
}, | ||||||
derives: (child, parent) => { | ||||||
return ( | ||||||
fail(equalWith(child, parent)) || | ||||||
fail(equal(child.nb.provider, parent.nb.provider, 'provider')) || | ||||||
fail(equal(child.nb.consumer, parent.nb.consumer, 'consumer')) || | ||||||
true | ||||||
) | ||||||
}, | ||||||
}), | ||||||
derives: equalWith, | ||||||
}) | ||||||
|
||||||
/** | ||||||
* Capability can be invoked by an agent to add a provider to a space. | ||||||
*/ | ||||||
export const add = base.derive({ | ||||||
to: capability({ | ||||||
can: 'provider/add', | ||||||
with: DID, | ||||||
nb: { | ||||||
provider: DID, | ||||||
consumer: URI.match({ protocol: 'did:' }), | ||||||
}, | ||||||
derives: (child, parent) => { | ||||||
return ( | ||||||
fail(equalWith(child, parent)) || | ||||||
fail(equal(child.nb.provider, parent.nb.provider, 'provider')) || | ||||||
fail(equal(child.nb.consumer, parent.nb.consumer, 'consumer')) || | ||||||
true | ||||||
) | ||||||
}, | ||||||
}), | ||||||
derives: equalWith, | ||||||
}) |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -57,6 +57,24 @@ export function equal(child, parent, constraint) { | |||||
} | ||||||
} | ||||||
|
||||||
/** | ||||||
* @param {Types.Link<unknown, number, number, 0 | 1> | undefined} child | ||||||
* @param {Types.Link<unknown, number, number, 0 | 1> | undefined} parent | ||||||
* @param {string} constraint | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. would be helpful to explain this parameter. It's used to build the error message? |
||||||
*/ | ||||||
|
||||||
export function equalCID(child, parent, constraint) { | ||||||
if (parent === undefined) { | ||||||
return true | ||||||
} else if (child && !child.equals(parent)) { | ||||||
return new Failure( | ||||||
`Constrain violation: ${child} violates imposed ${constraint} constraint ${parent}` | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
) | ||||||
} else { | ||||||
return true | ||||||
} | ||||||
} | ||||||
|
||||||
/** | ||||||
* @template {Types.ParsedCapability<"store/add"|"store/remove", Types.URI<'did:'>, {link?: Types.Link<unknown, number, number, 0|1>}>} T | ||||||
* @param {T} claimed | ||||||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,165 @@ | ||||||
import assert from 'assert' | ||||||
import { access } from '@ucanto/validator' | ||||||
import { Verifier } from '@ucanto/principal/ed25519' | ||||||
import * as Consumer from '../../src/consumer.js' | ||||||
import { alice, bob, service, mallory } from '../helpers/fixtures.js' | ||||||
import { parseLink } from '@ucanto/core' | ||||||
import { createCborCid } from '../helpers/utils.js' | ||||||
|
||||||
describe('consumer capabilities', function () { | ||||||
describe('consumer/add', function () { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this could use a test ensuring that this example would be authorized, whose proof includes |
||||||
it('should not self issue', async function () { | ||||||
const space = bob | ||||||
const provider = mallory.withDID( | ||||||
'did:web:ucan.web3.storage:providers:free' | ||||||
) | ||||||
const invocation = Consumer.add.invoke({ | ||||||
issuer: alice, | ||||||
audience: service, | ||||||
with: provider.did(), | ||||||
nb: { | ||||||
request: parseLink('bafkqaaa'), | ||||||
consumer: space.did(), | ||||||
}, | ||||||
}) | ||||||
|
||||||
const result = await access(await invocation.delegate(), { | ||||||
capability: Consumer.add, | ||||||
principal: Verifier, | ||||||
authority: service, | ||||||
}) | ||||||
if (result.error) { | ||||||
assert.ok( | ||||||
result.message.includes(`Capability can not be (self) issued`) | ||||||
) | ||||||
} else { | ||||||
assert.fail('should return error') | ||||||
} | ||||||
}) | ||||||
|
||||||
it('should fail different nb.request', async function () { | ||||||
const space = bob | ||||||
const provider = alice | ||||||
const consume = Consumer.add.invoke({ | ||||||
issuer: mallory, | ||||||
audience: service, | ||||||
with: provider.did(), | ||||||
nb: { | ||||||
request: parseLink('bafkqaaa'), | ||||||
consumer: space.did(), | ||||||
}, | ||||||
proofs: [ | ||||||
await Consumer.add.delegate({ | ||||||
issuer: provider, | ||||||
audience: mallory, | ||||||
with: provider.did(), | ||||||
nb: { | ||||||
request: await createCborCid('root'), | ||||||
consumer: space.did(), | ||||||
}, | ||||||
}), | ||||||
], | ||||||
}) | ||||||
|
||||||
const result = await access(await consume.delegate(), { | ||||||
capability: Consumer.add, | ||||||
principal: Verifier, | ||||||
authority: service, | ||||||
}) | ||||||
if (result.error) { | ||||||
assert.ok( | ||||||
result.message.includes( | ||||||
`Constrain violation: ${parseLink( | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
'bafkqaaa' | ||||||
)} violates imposed request constraint ${await createCborCid( | ||||||
'root' | ||||||
)}` | ||||||
) | ||||||
) | ||||||
} else { | ||||||
assert.fail('should return error') | ||||||
} | ||||||
}) | ||||||
|
||||||
it('should fail different nb.consumer', async function () { | ||||||
const space = bob | ||||||
const provider = alice | ||||||
const consume = Consumer.add.invoke({ | ||||||
issuer: mallory, | ||||||
audience: service, | ||||||
with: provider.did(), | ||||||
nb: { | ||||||
request: parseLink('bafkqaaa'), | ||||||
consumer: space.did(), | ||||||
}, | ||||||
proofs: [ | ||||||
await Consumer.add.delegate({ | ||||||
issuer: provider, | ||||||
audience: mallory, | ||||||
with: provider.did(), | ||||||
nb: { | ||||||
request: parseLink('bafkqaaa'), | ||||||
consumer: mallory.did(), | ||||||
}, | ||||||
}), | ||||||
], | ||||||
}) | ||||||
|
||||||
const result = await access(await consume.delegate(), { | ||||||
capability: Consumer.add, | ||||||
principal: Verifier, | ||||||
authority: service, | ||||||
}) | ||||||
if (result.error) { | ||||||
assert.ok( | ||||||
result.message.includes( | ||||||
`${space.did()} violates imposed consumer constraint ${mallory.did()}` | ||||||
) | ||||||
) | ||||||
} else { | ||||||
assert.fail('should return error') | ||||||
} | ||||||
}) | ||||||
|
||||||
it('should invoke with good proof', async function () { | ||||||
const space = bob | ||||||
const provider = alice | ||||||
const consume = Consumer.add.invoke({ | ||||||
issuer: mallory, | ||||||
audience: service, | ||||||
with: provider.did(), | ||||||
nb: { | ||||||
request: parseLink('bafkqaaa'), | ||||||
consumer: space.did(), | ||||||
}, | ||||||
proofs: [ | ||||||
await Consumer.add.delegate({ | ||||||
issuer: provider, | ||||||
audience: mallory, | ||||||
with: provider.did(), | ||||||
nb: { | ||||||
request: parseLink('bafkqaaa'), | ||||||
consumer: space.did(), | ||||||
}, | ||||||
}), | ||||||
], | ||||||
}) | ||||||
|
||||||
const result = await access(await consume.delegate(), { | ||||||
capability: Consumer.add, | ||||||
principal: Verifier, | ||||||
authority: service, | ||||||
}) | ||||||
if (result.error) { | ||||||
assert.fail('should return error') | ||||||
} else { | ||||||
assert.deepEqual(result.audience.did(), service.did()) | ||||||
assert.equal(result.capability.can, 'consumer/add') | ||||||
assert.deepEqual(result.capability.nb, { | ||||||
request: parseLink('bafkqaaa'), | ||||||
consumer: space.did(), | ||||||
}) | ||||||
} | ||||||
}) | ||||||
}) | ||||||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO it could be helpful to others grokking this to link to the spec it's trying to implement?