diff --git a/core/delegation/delegate.go b/core/delegation/delegate.go index 711a5c4..4df7dcc 100644 --- a/core/delegation/delegate.go +++ b/core/delegation/delegate.go @@ -69,26 +69,17 @@ func WithFacts(fct []ucan.FactBuilder) Option { } } -// WithProofs configures the proofs for the UCAN. If the `issuer` of this +// WithProof configures the proof(s) for the UCAN. If the `issuer` of this // `Delegation` is not the resource owner / service provider, for the delegated // capabilities, the `proofs` must contain valid `Proof`s containing // delegations to the `issuer`. -func WithProofs(prf Proofs) Option { +func WithProof(prf ...Proof) Option { return func(cfg *delegationConfig) error { cfg.prf = prf return nil } } -// WithProof configures the proofs for the UCAN in the case where there is only -// a single proof. -func WithProof(prf Proof) Option { - return func(cfg *delegationConfig) error { - cfg.prf = Proofs{prf} - return nil - } -} - // Delegate creates a new signed token with a given `options.issuer`. If // expiration is not set it defaults to 30 seconds from now. Returns UCAN in // primary IPLD representation. @@ -114,7 +105,7 @@ func Delegate[C ucan.CaveatBuilder](issuer ucan.Signer, audience ucan.Principal, ucan.WithFacts(cfg.fct), ucan.WithNonce(cfg.nnc), ucan.WithNotBefore(cfg.nbf), - ucan.WithProofs(links), + ucan.WithProof(links...), } if cfg.noexp { opts = append(opts, ucan.WithNoExpiration()) diff --git a/server/server_test.go b/server/server_test.go index 61ecb88..f55f8cb 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -192,7 +192,7 @@ func TestExecute(t *testing.T) { require.NoError(t, err) prfs := []delegation.Proof{delegation.FromDelegation(dgl)} - inv, err := invocation.Invoke(fixtures.Alice, fixtures.Service, cap, delegation.WithProofs(prfs)) + inv, err := invocation.Invoke(fixtures.Alice, fixtures.Service, cap, delegation.WithProof(prfs...)) require.NoError(t, err) resp, err := client.Execute([]invocation.Invocation{inv}, conn) diff --git a/ucan/lib.go b/ucan/lib.go index 1747ec3..91dc6d5 100644 --- a/ucan/lib.go +++ b/ucan/lib.go @@ -71,8 +71,8 @@ func WithFacts(fct []FactBuilder) Option { } } -// WithProofs configures the proofs for the UCAN. -func WithProofs(prf []Link) Option { +// WithProof configures the proofs for the UCAN. +func WithProof(prf ...Link) Option { return func(cfg *ucanConfig) error { cfg.prf = prf return nil diff --git a/validator/session_test.go b/validator/session_test.go index e170658..e74b804 100644 --- a/validator/session_test.go +++ b/validator/session_test.go @@ -120,10 +120,10 @@ func TestSession(t *testing.T) { service, account.DID().String(), nb, - delegation.WithProofs(delegation.Proofs{ + delegation.WithProof( delegation.FromDelegation(prf), delegation.FromDelegation(session), - }), + ), ) require.NoError(t, err) @@ -208,10 +208,10 @@ func TestSession(t *testing.T) { service, account.DID().String(), nb, - delegation.WithProofs(delegation.Proofs{ + delegation.WithProof( delegation.FromDelegation(session), delegation.FromDelegation(prf), - }), + ), ) require.NoError(t, err) @@ -351,11 +351,9 @@ func TestSession(t *testing.T) { service, account.DID().String(), nb, - delegation.WithProofs( - delegation.Proofs{ - delegation.FromDelegation(prf), - delegation.FromDelegation(session), - }, + delegation.WithProof( + delegation.FromDelegation(prf), + delegation.FromDelegation(session), ), ) require.NoError(t, err) @@ -409,4 +407,208 @@ func TestSession(t *testing.T) { require.Equal(t, account.DID().String(), a.Capability().With()) require.Equal(t, nb, a.Capability().Nb()) }) + + t.Run("service can not delegate access to account", func(t *testing.T) { + accountDID := helpers.Must(did.Parse("did:mailto:web.mail:alice")) + account := absentee.From(accountDID) + + // service should not be able to delegate access to account resource + auth, err := debugEcho.Delegate( + service, + fixtures.Alice, + account.DID().String(), + debugEchoCaveats{}, + ) + require.NoError(t, err) + + session, err := attest.Delegate( + service, + fixtures.Alice, + service.DID().String(), + attestCaveats{Proof: auth.Link()}, + delegation.WithProof(delegation.FromDelegation(auth)), + ) + require.NoError(t, err) + + msg := "Hello World" + nb := debugEchoCaveats{Message: &msg} + inv, err := debugEcho.Invoke( + fixtures.Alice, + service, + account.DID().String(), + nb, + delegation.WithProof( + delegation.FromDelegation(auth), + delegation.FromDelegation(session), + ), + ) + require.NoError(t, err) + + context := NewValidationContext( + service.Verifier(), + debugEcho, + IsSelfIssued, + validateAuthOk, + ProofUnavailable, + parseEdPrincipal, + FailDIDKeyResolution, + ) + + a, x := Access(inv, context) + require.Nil(t, a) + require.Error(t, x) + }) + + t.Run("attest with an account DID", func(t *testing.T) { + accountDID := helpers.Must(did.Parse("did:mailto:web.mail:alice")) + account := absentee.From(accountDID) + + // service should not be able to delegate access to account resource + auth, err := debugEcho.Delegate( + service, + fixtures.Alice, + account.DID().String(), + debugEchoCaveats{}, + ) + require.NoError(t, err) + + session, err := attest.Delegate( + service, + fixtures.Alice, + // this should be an service did instead + account.DID().String(), + attestCaveats{Proof: auth.Link()}, + delegation.WithProof(delegation.FromDelegation(auth)), + ) + require.NoError(t, err) + + msg := "Hello World" + nb := debugEchoCaveats{Message: &msg} + inv, err := debugEcho.Invoke( + fixtures.Alice, + service, + account.DID().String(), + nb, + delegation.WithProof( + delegation.FromDelegation(auth), + delegation.FromDelegation(session), + ), + ) + require.NoError(t, err) + + context := NewValidationContext( + service.Verifier(), + debugEcho, + IsSelfIssued, + validateAuthOk, + ProofUnavailable, + parseEdPrincipal, + FailDIDKeyResolution, + ) + + a, x := Access(inv, context) + require.Nil(t, a) + require.Error(t, x) + }) + + t.Run("service cannot delegate account resource", func(t *testing.T) { + accountDID := helpers.Must(did.Parse("did:mailto:web.mail:alice")) + account := absentee.From(accountDID) + + prf, err := debugEcho.Delegate( + service, + fixtures.Alice, + account.DID().String(), + debugEchoCaveats{}, + ) + require.NoError(t, err) + + msg := "Hello World" + nb := debugEchoCaveats{Message: &msg} + inv, err := debugEcho.Invoke( + fixtures.Alice, + service, + account.DID().String(), + nb, + delegation.WithProof(delegation.FromDelegation(prf)), + ) + require.NoError(t, err) + + context := NewValidationContext( + service.Verifier(), + debugEcho, + IsSelfIssued, + validateAuthOk, + ProofUnavailable, + parseEdPrincipal, + FailDIDKeyResolution, + ) + + a, x := Access(inv, context) + require.Nil(t, a) + require.Error(t, x) + }) + + t.Run("redundant proofs have no impact", func(t *testing.T) { + accountDID := helpers.Must(did.Parse("did:mailto:web.mail:alice")) + account := absentee.From(accountDID) + + var logins delegation.Proofs + for i := range 6 { + dlg, err := delegation.Delegate( + account, + fixtures.Alice, + []ucan.Capability[ucan.NoCaveats]{ + ucan.NewCapability("*", "ucan:*", ucan.NoCaveats{}), + }, + delegation.WithNoExpiration(), + delegation.WithNonce(fmt.Sprint(i)), + ) + require.NoError(t, err) + logins = append(logins, delegation.FromDelegation(dlg)) + } + + exp := ucan.Now() + 60*60*24*365 // 1 year + var attestations delegation.Proofs + for _, login := range logins { + dlg, err := attest.Delegate( + service, + fixtures.Alice, + service.DID().String(), + attestCaveats{Proof: login.Link()}, + delegation.WithExpiration(exp), + ) + require.NoError(t, err) + attestations = append(attestations, delegation.FromDelegation(dlg)) + } + + var prfs delegation.Proofs + prfs = append(prfs, logins...) + prfs = append(prfs, attestations...) + + msg := "Hello World" + nb := debugEchoCaveats{Message: &msg} + inv, err := debugEcho.Invoke( + fixtures.Alice, + service, + account.DID().String(), + nb, + delegation.WithProof(prfs...), + ) + require.NoError(t, err) + + context := NewValidationContext( + service.Verifier(), + debugEcho, + IsSelfIssued, + validateAuthOk, + ProofUnavailable, + parseEdPrincipal, + FailDIDKeyResolution, + ) + + a, x := Access(inv, context) + require.NotEmpty(t, a) + require.NoError(t, x) + }) }