From fa061dd2be5058c4a35ac45e4f590186b0aee2fc Mon Sep 17 00:00:00 2001 From: Irakli Gozalishvili Date: Wed, 4 Oct 2023 16:14:41 -0700 Subject: [PATCH] fix: failing tests --- packages/server/src/handler.js | 12 ----- packages/server/test/handler.spec.js | 38 ++++++++++++++- packages/validator/src/authorization.js | 48 +++++++++++++++++++ packages/validator/src/lib.js | 54 ++++++++-------------- packages/validator/test/revocation.spec.js | 7 ++- 5 files changed, 111 insertions(+), 48 deletions(-) create mode 100644 packages/validator/src/authorization.js diff --git a/packages/server/src/handler.js b/packages/server/src/handler.js index 8a2061f1..5e3dc524 100644 --- a/packages/server/src/handler.js +++ b/packages/server/src/handler.js @@ -75,18 +75,6 @@ export const provideAdvanced = } } -/** - * - * @param {API.Authorization} authorization - * @returns {Iterable} - */ -const iterateAuthorization = function* ({ delegation, proofs }) { - yield delegation.cid - for (const proof of proofs) { - yield* iterateAuthorization(proof) - } -} - /** * @implements {API.InvalidAudience} */ diff --git a/packages/server/test/handler.spec.js b/packages/server/test/handler.spec.js index 1ab6e332..2a7b52b5 100644 --- a/packages/server/test/handler.spec.js +++ b/packages/server/test/handler.spec.js @@ -7,7 +7,12 @@ import { alice, bob, mallory, service } from './fixtures.js' import { test, assert } from './test.js' import * as Access from './service/access.js' import { Verifier } from '@ucanto/principal/ed25519' -import { Schema, UnavailableProof, Unauthorized } from '@ucanto/validator' +import { + Schema, + UnavailableProof, + Unauthorized, + Revoked, +} from '@ucanto/validator' import { Absentee } from '@ucanto/principal' import { capability } from '../src/server.js' import { isLink, parseLink, fail } from '../src/lib.js' @@ -667,6 +672,37 @@ test('fx.ok API', () => { ) }) +test('invocation fails if proof is revoked', async () => { + const proof = await Client.delegate({ + issuer: w3, + audience: alice, + capabilities: [ + { + can: 'identity/register', + with: 'mailto:alice@web.mail', + }, + ], + }) + + const invocation = await Client.delegate({ + issuer: alice, + audience: w3, + capabilities: proof.capabilities, + proofs: [proof], + }) + + const result = await Access.register(invocation, { + ...context, + validateAuthorization: auth => { + assert.deepEqual(auth.delegation.cid, invocation.cid) + assert.deepEqual(auth.delegation.proofs, [proof]) + return { error: new Revoked(proof) } + }, + }) + + assert.match(String(result.error), /Proof bafy.* has been revoked/) +}) + /** * @template {Record} Service * @param {Service} service diff --git a/packages/validator/src/authorization.js b/packages/validator/src/authorization.js new file mode 100644 index 00000000..98b946a7 --- /dev/null +++ b/packages/validator/src/authorization.js @@ -0,0 +1,48 @@ +import * as API from '@ucanto/interface' + +/** + * @template {API.ParsedCapability} C + * @implements {API.Authorization} + */ +class Authorization { + /** + * @param {API.Match} match + * @param {API.Authorization[]} proofs + */ + constructor(match, proofs) { + this.match = match + this.proofs = proofs + } + get capability() { + return this.match.value + } + get delegation() { + return this.match.source[0].delegation + } + get issuer() { + return this.delegation.issuer + } + get audience() { + return this.delegation.audience + } +} + +/** + * @template {API.ParsedCapability} C + * @param {API.Match} match + * @param {API.Authorization[]} proofs + * @returns {API.Authorization} + */ +export const create = (match, proofs = []) => new Authorization(match, proofs) + +/** + * + * @param {API.Authorization} authorization + * @returns {Iterable} + */ +export const iterate = function* ({ delegation, proofs }) { + yield delegation.cid + for (const proof of proofs) { + yield* iterate(proof) + } +} diff --git a/packages/validator/src/lib.js b/packages/validator/src/lib.js index 771f59be..d973ea48 100644 --- a/packages/validator/src/lib.js +++ b/packages/validator/src/lib.js @@ -2,6 +2,7 @@ import * as API from '@ucanto/interface' import { isDelegation, UCAN, ok, fail } from '@ucanto/core' import { capability } from './capability.js' import * as Schema from '@ucanto/core/schema' +import * as Authorization from './authorization.js' import { UnavailableProof, Unauthorized, @@ -23,6 +24,7 @@ export * from '@ucanto/core/schema' export { Schema, + Authorization, Failure, fail, ok, @@ -279,7 +281,7 @@ export const claim = async ( for (const matched of selection.matches) { const selector = matched.prune(config) if (selector == null) { - const authorization = new Authorization(matched, []) + const authorization = Authorization.create(matched, []) const result = await validateAuthorization(authorization) if (result.error) { invalidProofs.push(result.error) @@ -291,7 +293,7 @@ export const claim = async ( if (result.error) { failedProofs.push(result.error) } else { - const authorization = new Authorization(matched, [result.ok]) + const authorization = Authorization.create(matched, [result.ok]) const approval = await validateAuthorization(authorization) if (approval.error) { invalidProofs.push(approval.error) @@ -313,32 +315,6 @@ export const claim = async ( } } -/** - * @template {API.ParsedCapability} C - * @implements {API.Authorization} - */ -class Authorization { - /** - * @param {API.Match} match - * @param {API.Authorization[]} proofs - */ - constructor(match, proofs) { - this.match = match - this.proofs = proofs - } - get capability() { - return this.match.value - } - get delegation() { - return this.match.source[0].delegation - } - get issuer() { - return this.delegation.issuer - } - get audience() { - return this.delegation.audience - } -} /** * Verifies whether any of the delegated proofs grant give capability. * @@ -359,17 +335,27 @@ export const authorize = async (match, config) => { for (const matched of selection.matches) { const selector = matched.prune(config) if (selector == null) { - // @ts-expect-error - it may not be a parsed capability but rather a - // group of capabilities but we can deal with that in the future. - return { ok: new Authorization(matched, []) } + return { + ok: Authorization.create( + // @ts-expect-error - it may not be a parsed capability but rather a + // group of capabilities but we can deal with that in the future. + matched, + [] + ), + } } else { const result = await authorize(selector, config) if (result.error) { failedProofs.push(result.error) } else { - // @ts-expect-error - it may not be a parsed capability but rather a - // group of capabilities but we can deal with that in the future. - return { ok: new Authorization(matched, [result.ok]) } + return { + ok: Authorization.create( + // @ts-expect-error - it may not be a parsed capability but rather a + // group of capabilities but we can deal with that in the future. + matched, + [result.ok] + ), + } } } } diff --git a/packages/validator/test/revocation.spec.js b/packages/validator/test/revocation.spec.js index 98814c1c..1bd3a6b0 100644 --- a/packages/validator/test/revocation.spec.js +++ b/packages/validator/test/revocation.spec.js @@ -1,5 +1,5 @@ import { test, assert, matchError } from './test.js' -import { access, claim, DID, Revoked } from '../src/lib.js' +import { access, claim, DID, Revoked, Authorization } from '../src/lib.js' import { capability, fail, URI, Link, Schema } from '../src/lib.js' import { ed25519, Verifier } from '@ucanto/principal' import * as Client from '@ucanto/client' @@ -33,6 +33,7 @@ test('revoked capability does not validate', async () => { principal: Verifier, validateAuthorization: auth => { assert.deepEqual(auth.delegation.cid, invocation.cid) + assert.deepEqual([...Authorization.iterate(auth)], [invocation.cid]) return { error: new Revoked(auth.delegation) } }, }) @@ -67,6 +68,10 @@ test('revoked proof does not validate', async () => { validateAuthorization: auth => { assert.deepEqual(auth.delegation.cid, invocation.cid) assert.deepEqual(auth.delegation.proofs, [proof]) + assert.deepEqual( + [...Authorization.iterate(auth)], + [invocation.cid, proof.cid] + ) return { error: new Revoked(proof) } }, })