Skip to content

Commit

Permalink
fix: failing tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Gozala committed Oct 4, 2023
1 parent 221eec1 commit fa061dd
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 48 deletions.
12 changes: 0 additions & 12 deletions packages/server/src/handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,18 +75,6 @@ export const provideAdvanced =
}
}

/**
*
* @param {API.Authorization} authorization
* @returns {Iterable<API.Link>}
*/
const iterateAuthorization = function* ({ delegation, proofs }) {
yield delegation.cid
for (const proof of proofs) {
yield* iterateAuthorization(proof)
}
}

/**
* @implements {API.InvalidAudience}
*/
Expand Down
38 changes: 37 additions & 1 deletion packages/server/test/handler.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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<string, any>} Service
* @param {Service} service
Expand Down
48 changes: 48 additions & 0 deletions packages/validator/src/authorization.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import * as API from '@ucanto/interface'

/**
* @template {API.ParsedCapability} C
* @implements {API.Authorization<C>}
*/
class Authorization {
/**
* @param {API.Match<C>} match
* @param {API.Authorization<API.ParsedCapability>[]} 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<C>} match
* @param {API.Authorization<API.ParsedCapability>[]} proofs
* @returns {API.Authorization<C>}
*/
export const create = (match, proofs = []) => new Authorization(match, proofs)

/**
*
* @param {API.Authorization} authorization
* @returns {Iterable<API.UCANLink>}
*/
export const iterate = function* ({ delegation, proofs }) {
yield delegation.cid
for (const proof of proofs) {
yield* iterate(proof)
}
}
54 changes: 20 additions & 34 deletions packages/validator/src/lib.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -23,6 +24,7 @@ export * from '@ucanto/core/schema'

export {
Schema,
Authorization,
Failure,
fail,
ok,
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -313,32 +315,6 @@ export const claim = async (
}
}

/**
* @template {API.ParsedCapability} C
* @implements {API.Authorization<C>}
*/
class Authorization {
/**
* @param {API.Match<C>} match
* @param {API.Authorization<API.ParsedCapability>[]} 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.
*
Expand All @@ -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]
),
}
}
}
}
Expand Down
7 changes: 6 additions & 1 deletion packages/validator/test/revocation.spec.js
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -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) }
},
})
Expand Down Expand Up @@ -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) }
},
})
Expand Down

0 comments on commit fa061dd

Please sign in to comment.