From 91911f9a5c47269815adc65e440f4d83d2399561 Mon Sep 17 00:00:00 2001 From: Rein Krul Date: Tue, 26 Sep 2023 10:00:07 +0200 Subject: [PATCH] w --- auth/api/iam/api.go | 6 +- crypto/api/v1/api.go | 5 +- crypto/api/v1/api_test.go | 22 +- didman/didman.go | 7 +- golden_hammer/module.go | 7 +- network/dag/keys.go | 3 +- network/dag/pal.go | 3 +- network/dag/pal_test.go | 8 +- network/network.go | 7 +- network/network_test.go | 4 +- vcr/credential/validator.go | 4 +- vcr/issuer/issuer.go | 5 +- vcr/issuer/keyresolver.go | 3 +- vcr/issuer/openid.go | 3 +- vcr/openid4vci/identifiers.go | 3 +- vdr/cmd/cmd.go | 4 +- vdr/did_owner.go | 3 +- vdr/didnuts/creator.go | 4 +- vdr/didnuts/didstore/finder.go | 4 +- vdr/didnuts/didstore/finder_test.go | 9 +- vdr/didnuts/didstore/interface.go | 6 +- vdr/didnuts/didstore/mock.go | 6 +- vdr/didnuts/didstore/store.go | 6 +- vdr/didnuts/resolver.go | 16 +- vdr/didnuts/resolver_test.go | 64 ++-- vdr/didservice/resolvers.go | 63 ---- vdr/didservice/resolvers_test.go | 287 ------------------ vdr/didservice/test.go | 29 -- vdr/didservice/util.go | 36 --- vdr/didservice/util_test.go | 51 ---- vdr/{didservice => management}/finder.go | 41 ++- vdr/{didservice => management}/finder_test.go | 24 +- vdr/resolver/did.go | 53 ++++ vdr/resolver/did_test.go | 99 ++++++ vdr/resolver/key.go | 38 ++- vdr/resolver/key_test.go | 110 +++++-- vdr/resolver/service.go | 3 +- vdr/resolver/service_test.go | 112 +++++++ vdr/types/interface.go | 30 -- vdr/types/key.go | 1 - vdr/types/management_test.go | 38 +++ vdr/types/mock.go | 7 +- vdr/vdr.go | 10 +- vdr/vdr_test.go | 3 +- 44 files changed, 538 insertions(+), 709 deletions(-) delete mode 100644 vdr/didservice/resolvers.go delete mode 100644 vdr/didservice/resolvers_test.go delete mode 100644 vdr/didservice/test.go delete mode 100644 vdr/didservice/util.go delete mode 100644 vdr/didservice/util_test.go rename vdr/{didservice => management}/finder.go (62%) rename vdr/{didservice => management}/finder_test.go (72%) delete mode 100644 vdr/types/key.go create mode 100644 vdr/types/management_test.go diff --git a/auth/api/iam/api.go b/auth/api/iam/api.go index 816de10f7f..cc230072e4 100644 --- a/auth/api/iam/api.go +++ b/auth/api/iam/api.go @@ -30,7 +30,7 @@ import ( "github.com/nuts-foundation/nuts-node/core" "github.com/nuts-foundation/nuts-node/vcr" "github.com/nuts-foundation/nuts-node/vcr/openid4vci" - "github.com/nuts-foundation/nuts-node/vdr/didservice" + "github.com/nuts-foundation/nuts-node/vdr/resolver" vdr "github.com/nuts-foundation/nuts-node/vdr/types" "html/template" "net/http" @@ -206,7 +206,7 @@ func (r Wrapper) OAuthAuthorizationServerMetadata(ctx context.Context, request O owned, err := r.vdr.IsOwner(ctx, ownDID) if err != nil { - if didservice.IsFunctionalResolveError(err) { + if resolver.IsFunctionalResolveError(err) { return nil, core.NotFoundError("authz server metadata: %w", err) } log.Logger().WithField("did", ownDID.String()).Errorf("authz server metadata: failed to assert ownership of did: %s", err.Error()) @@ -227,7 +227,7 @@ func (r Wrapper) GetWebDID(ctx context.Context, request GetWebDIDRequestObject) document, err := r.vdr.DeriveWebDIDDocument(ctx, baseURL, ownDID) if err != nil { - if didservice.IsFunctionalResolveError(err) { + if resolver.IsFunctionalResolveError(err) { return GetWebDID404Response{}, nil } log.Logger().WithError(err).Errorf("Could not resolve Nuts DID: %s", ownDID.String()) diff --git a/crypto/api/v1/api.go b/crypto/api/v1/api.go index f105d4a53f..b81442ab28 100644 --- a/crypto/api/v1/api.go +++ b/crypto/api/v1/api.go @@ -35,7 +35,6 @@ import ( "github.com/nuts-foundation/nuts-node/audit" "github.com/nuts-foundation/nuts-node/core" "github.com/nuts-foundation/nuts-node/crypto" - "github.com/nuts-foundation/nuts-node/vdr/types" ) var _ StrictServerInterface = (*Wrapper)(nil) @@ -196,14 +195,14 @@ func (w *Wrapper) resolvePublicKey(id *did.DID) (key crypt.PublicKey, keyID ssi. if id.IsURL() { // Assume it is a keyId now := time.Now() - key, err = w.K.ResolveKeyByID(id.String(), &now, types.KeyAgreement) + key, err = w.K.ResolveKeyByID(id.String(), &now, resolver.KeyAgreement) if err != nil { return nil, ssi.URI{}, err } keyID = id.URI() } else { // Assume it is a DID - keyID, key, err = w.K.ResolveKey(*id, nil, types.KeyAgreement) + keyID, key, err = w.K.ResolveKey(*id, nil, resolver.KeyAgreement) if err != nil { return nil, ssi.URI{}, err } diff --git a/crypto/api/v1/api_test.go b/crypto/api/v1/api_test.go index f04558e40d..1d928ff49a 100644 --- a/crypto/api/v1/api_test.go +++ b/crypto/api/v1/api_test.go @@ -253,7 +253,7 @@ func TestWrapper_EncryptJwe(t *testing.T) { Payload: payload, Headers: headers, } - ctx.keyResolver.EXPECT().ResolveKey(gomock.Any(), nil, types.KeyAgreement).Return(ssi.URI{}, nil, errors.New("FAIL")) + ctx.keyResolver.EXPECT().ResolveKey(gomock.Any(), nil, resolver.KeyAgreement).Return(ssi.URI{}, nil, errors.New("FAIL")) jwe, err := ctx.client.EncryptJwe(nil, EncryptJweRequestObject{Body: &request}) assert.EqualError(t, err, "invalid receiver: FAIL") @@ -268,7 +268,7 @@ func TestWrapper_EncryptJwe(t *testing.T) { Payload: payload, Headers: headers, } - ctx.keyResolver.EXPECT().ResolveKey(gomock.Any(), nil, types.KeyAgreement).Return(ssi.URI{}, nil, errors.New("FAIL")) + ctx.keyResolver.EXPECT().ResolveKey(gomock.Any(), nil, resolver.KeyAgreement).Return(ssi.URI{}, nil, errors.New("FAIL")) jwe, err := ctx.client.EncryptJwe(nil, EncryptJweRequestObject{Body: &request}) assert.EqualError(t, err, "invalid receiver: FAIL") @@ -283,7 +283,7 @@ func TestWrapper_EncryptJwe(t *testing.T) { Payload: payload, Headers: headers, } - ctx.keyResolver.EXPECT().ResolveKeyByID(gomock.Any(), gomock.Any(), types.KeyAgreement).Return(ssi.URI{}, errors.New("FAIL")) + ctx.keyResolver.EXPECT().ResolveKeyByID(gomock.Any(), gomock.Any(), resolver.KeyAgreement).Return(ssi.URI{}, errors.New("FAIL")) jwe, err := ctx.client.EncryptJwe(nil, EncryptJweRequestObject{Body: &request}) assert.EqualError(t, err, "invalid receiver: FAIL") @@ -298,7 +298,7 @@ func TestWrapper_EncryptJwe(t *testing.T) { Payload: payload, Headers: headers, } - ctx.keyResolver.EXPECT().ResolveKey(gomock.Any(), nil, types.KeyAgreement).Return(ssi.URI{}, nil, resolver.ErrNotFound) + ctx.keyResolver.EXPECT().ResolveKey(gomock.Any(), nil, resolver.KeyAgreement).Return(ssi.URI{}, nil, resolver.ErrNotFound) jwe, err := ctx.client.EncryptJwe(nil, EncryptJweRequestObject{Body: &request}) assert.EqualError(t, err, "unable to locate receiver did:nuts:12345: unable to find the DID document") @@ -313,7 +313,7 @@ func TestWrapper_EncryptJwe(t *testing.T) { Payload: payload, Headers: headers, } - ctx.keyResolver.EXPECT().ResolveKey(gomock.Any(), nil, types.KeyAgreement).Return(ssi.URI{}, nil, resolver.ErrNotFound) + ctx.keyResolver.EXPECT().ResolveKey(gomock.Any(), nil, resolver.KeyAgreement).Return(ssi.URI{}, nil, resolver.ErrNotFound) jwe, err := ctx.client.EncryptJwe(nil, EncryptJweRequestObject{Body: &request}) assert.EqualError(t, err, "unable to locate receiver did:nuts:12345: unable to find the DID document") @@ -328,7 +328,7 @@ func TestWrapper_EncryptJwe(t *testing.T) { Payload: payload, Headers: headers, } - ctx.keyResolver.EXPECT().ResolveKeyByID(gomock.Any(), gomock.Any(), types.KeyAgreement).Return(ssi.URI{}, resolver.ErrNotFound) + ctx.keyResolver.EXPECT().ResolveKeyByID(gomock.Any(), gomock.Any(), resolver.KeyAgreement).Return(ssi.URI{}, resolver.ErrNotFound) jwe, err := ctx.client.EncryptJwe(nil, EncryptJweRequestObject{Body: &request}) assert.EqualError(t, err, "unable to locate receiver did:nuts:12345#key-1: unable to find the DID document") @@ -360,7 +360,7 @@ func TestWrapper_EncryptJwe(t *testing.T) { did, _ := ssi.ParseURI("did:nuts:12345") ctx.keyStore.EXPECT().EncryptJWE(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return("", errors.New("b00m!")) - ctx.keyResolver.EXPECT().ResolveKey(gomock.Any(), nil, types.KeyAgreement).Return(*did, nil, nil) + ctx.keyResolver.EXPECT().ResolveKey(gomock.Any(), nil, resolver.KeyAgreement).Return(*did, nil, nil) jwe, err := ctx.client.EncryptJwe(audit.TestContext(), EncryptJweRequestObject{Body: &request}) @@ -378,7 +378,7 @@ func TestWrapper_EncryptJwe(t *testing.T) { Payload: payload, } ctx.keyStore.EXPECT().EncryptJWE(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return("jwe", nil) - ctx.keyResolver.EXPECT().ResolveKey(gomock.Any(), nil, types.KeyAgreement).Return(*did, nil, nil) + ctx.keyResolver.EXPECT().ResolveKey(gomock.Any(), nil, resolver.KeyAgreement).Return(*did, nil, nil) resp, err := ctx.client.EncryptJwe(nil, EncryptJweRequestObject{Body: &request}) @@ -396,7 +396,7 @@ func TestWrapper_EncryptJwe(t *testing.T) { Payload: payload, } ctx.keyStore.EXPECT().EncryptJWE(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return("jwe", nil) - ctx.keyResolver.EXPECT().ResolveKeyByID(gomock.Any(), gomock.Any(), types.KeyAgreement).Return(*did, nil) + ctx.keyResolver.EXPECT().ResolveKeyByID(gomock.Any(), gomock.Any(), resolver.KeyAgreement).Return(*did, nil) resp, err := ctx.client.EncryptJwe(nil, EncryptJweRequestObject{Body: &request}) @@ -414,7 +414,7 @@ func TestWrapper_EncryptJwe(t *testing.T) { } did, _ := ssi.ParseURI(kid) ctx.keyStore.EXPECT().EncryptJWE(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return("jwe", nil) - ctx.keyResolver.EXPECT().ResolveKey(gomock.Any(), nil, types.KeyAgreement).Return(*did, nil, nil) + ctx.keyResolver.EXPECT().ResolveKey(gomock.Any(), nil, resolver.KeyAgreement).Return(*did, nil, nil) resp, err := ctx.client.EncryptJwe(nil, EncryptJweRequestObject{Body: &request}) @@ -430,7 +430,7 @@ func TestWrapper_EncryptJwe(t *testing.T) { Receiver: did.String(), } ctx.keyStore.EXPECT().EncryptJWE(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return("jwe", nil) - ctx.keyResolver.EXPECT().ResolveKey(gomock.Any(), nil, types.KeyAgreement).Return(*did, nil, nil) + ctx.keyResolver.EXPECT().ResolveKey(gomock.Any(), nil, resolver.KeyAgreement).Return(*did, nil, nil) resp, err := ctx.client.EncryptJwe(nil, EncryptJweRequestObject{Body: &request}) diff --git a/didman/didman.go b/didman/didman.go index d82e8e8abe..9482ca8494 100644 --- a/didman/didman.go +++ b/didman/didman.go @@ -25,7 +25,6 @@ import ( "encoding/json" "errors" "fmt" - "github.com/nuts-foundation/nuts-node/vdr/didservice" "github.com/nuts-foundation/nuts-node/vdr/resolver" "net/url" "sync" @@ -283,7 +282,7 @@ func (d *didman) GetCompoundServiceEndpoint(id did.DID, compoundServiceType stri } func (d *didman) DeleteService(ctx context.Context, serviceID ssi.URI) error { - id, err := didservice.GetDIDFromURL(serviceID.String()) + id, err := resolver.GetDIDFromURL(serviceID.String()) if err != nil { return err } @@ -299,7 +298,7 @@ func (d *didman) deleteService(ctx context.Context, serviceID ssi.URI) error { log.Logger(). WithField(core.LogFieldServiceID, serviceID.String()). Debug("Deleting service") - id, err := didservice.GetDIDFromURL(serviceID.String()) + id, err := resolver.GetDIDFromURL(serviceID.String()) if err != nil { return err } @@ -469,7 +468,7 @@ func (d *didman) resolveOrganizationDIDDocuments(organizations []vc.VerifiableCr j := 0 for i, organization := range organizations { document, organizationDID, err := d.resolveOrganizationDIDDocument(organization) - if didservice.IsFunctionalResolveError(err) { + if resolver.IsFunctionalResolveError(err) { // Just ignore deactivated DID documents or VCs that don't refer to an existing DID document. // Log it on debug, because it might be useful for finding VCs that need to be revoked (since they're invalid). log.Logger(). diff --git a/golden_hammer/module.go b/golden_hammer/module.go index 44cfda7b8c..5620c432bc 100644 --- a/golden_hammer/module.go +++ b/golden_hammer/module.go @@ -29,7 +29,6 @@ import ( "github.com/nuts-foundation/nuts-node/network/transport" "github.com/nuts-foundation/nuts-node/vcr" "github.com/nuts-foundation/nuts-node/vcr/openid4vci" - "github.com/nuts-foundation/nuts-node/vdr/didservice" "github.com/nuts-foundation/nuts-node/vdr/resolver" "github.com/nuts-foundation/nuts-node/vdr/types" "net/url" @@ -142,7 +141,7 @@ func (h *GoldenHammer) registerServiceBaseURLs() error { serviceEndpoint := getServiceEndpoint(document, transport.NutsCommServiceType) if resolver.IsServiceReference(serviceEndpoint) { // Care organization DID document, register service pointing to vendor DID. - parentDID, err := didservice.GetDIDFromURL(serviceEndpoint) + parentDID, err := resolver.GetDIDFromURL(serviceEndpoint) if err != nil { // Invalid NutsComm reference, skip log.Logger().WithError(err). @@ -194,7 +193,7 @@ func (h *GoldenHammer) listDocumentToFix() ([]did.Document, error) { } document, _, err := h.vdrInstance.Resolver().Resolve(id, nil) if err != nil { - if !didservice.IsFunctionalResolveError(err) { + if !resolver.IsFunctionalResolveError(err) { log.Logger().WithError(err).Infof("Can't resolve DID document, skipping fix (did=%s)", id) } continue @@ -238,7 +237,7 @@ func (h *GoldenHammer) tryResolveURL(id did.DID) (*url.URL, error) { // resolveContainsService returns whether 1. given DID document can be resolved, and 2. it contains the specified service. func (h *GoldenHammer) resolveContainsService(id did.DID, serviceType string) bool { document, _, err := h.vdrInstance.Resolver().Resolve(id, nil) - if didservice.IsFunctionalResolveError(err) { + if resolver.IsFunctionalResolveError(err) { // Unresolvable DID document, nothing to do return false } diff --git a/network/dag/keys.go b/network/dag/keys.go index a041149d54..37036ca021 100644 --- a/network/dag/keys.go +++ b/network/dag/keys.go @@ -22,7 +22,6 @@ import ( "fmt" "github.com/nuts-foundation/go-did/did" "github.com/nuts-foundation/nuts-node/crypto/hash" - "github.com/nuts-foundation/nuts-node/vdr/didservice" resolver2 "github.com/nuts-foundation/nuts-node/vdr/resolver" ) @@ -53,7 +52,7 @@ func resolvePublicKey(resolver resolver2.DIDResolver, kid string, metadata resol if err != nil { return nil, fmt.Errorf("invalid key ID (id=%s): %w", kid, err) } - holder, _ := didservice.GetDIDFromURL(kid) // can't fail, already parsed + holder, _ := resolver2.GetDIDFromURL(kid) // can't fail, already parsed doc, _, err := resolver.Resolve(holder, &metadata) if err != nil { return nil, err diff --git a/network/dag/pal.go b/network/dag/pal.go index 373f2394f3..dd04449d3a 100644 --- a/network/dag/pal.go +++ b/network/dag/pal.go @@ -31,7 +31,6 @@ import ( "github.com/nuts-foundation/go-did/did" "github.com/nuts-foundation/nuts-node/crypto" "github.com/nuts-foundation/nuts-node/network/log" - "github.com/nuts-foundation/nuts-node/vdr/types" ) // palHeaderDIDSeparator holds the character(s) that separate DID entries in the PAL header, before being encrypted. @@ -59,7 +58,7 @@ func (pal PAL) Encrypt(keyResolver resolver.KeyResolver) (EncryptedPAL, error) { var recipients [][]byte for _, recipient := range pal { recipients = append(recipients, []byte(recipient.String())) - _, rawKak, err := keyResolver.ResolveKey(recipient, nil, types.KeyAgreement) + _, rawKak, err := keyResolver.ResolveKey(recipient, nil, resolver.KeyAgreement) if err != nil { return nil, fmt.Errorf("unable to resolve keyAgreement key (recipient=%s): %w", recipient, err) } diff --git a/network/dag/pal_test.go b/network/dag/pal_test.go index c6ac09f015..c4a21da9b5 100644 --- a/network/dag/pal_test.go +++ b/network/dag/pal_test.go @@ -48,8 +48,8 @@ func TestEncryptPal(t *testing.T) { // Encrypt ctrl := gomock.NewController(t) keyResolver := types.NewMockKeyResolver(ctrl) - keyResolver.EXPECT().ResolveKey(*pA, nil, types.KeyAgreement).Return(ssi.URI{}, pkA.Public(), nil) - keyResolver.EXPECT().ResolveKey(*pB, nil, types.KeyAgreement).Return(ssi.URI{}, pkB.Public(), nil) + keyResolver.EXPECT().ResolveKey(*pA, nil, resolver.KeyAgreement).Return(ssi.URI{}, pkA.Public(), nil) + keyResolver.EXPECT().ResolveKey(*pB, nil, resolver.KeyAgreement).Return(ssi.URI{}, pkB.Public(), nil) expected := PAL{*pA, *pB} pal, err := expected.Encrypt(keyResolver) require.NoError(t, err) @@ -72,7 +72,7 @@ func TestEncryptPal(t *testing.T) { t.Run("error - keyAgreement key type is not supported", func(t *testing.T) { ctrl := gomock.NewController(t) keyResolver := types.NewMockKeyResolver(ctrl) - keyResolver.EXPECT().ResolveKey(*pA, nil, types.KeyAgreement).Return(ssi.URI{}, &rsa.PublicKey{}, nil) + keyResolver.EXPECT().ResolveKey(*pA, nil, resolver.KeyAgreement).Return(ssi.URI{}, &rsa.PublicKey{}, nil) pal, err := PAL{*pA}.Encrypt(keyResolver) assert.Nil(t, pal) assert.EqualError(t, err, "resolved keyAgreement key is not an elliptic curve key (recipient=did:nuts:A)") @@ -80,7 +80,7 @@ func TestEncryptPal(t *testing.T) { t.Run("error - no keyAgreements", func(t *testing.T) { ctrl := gomock.NewController(t) keyResolver := types.NewMockKeyResolver(ctrl) - keyResolver.EXPECT().ResolveKey(*pA, nil, types.KeyAgreement).Return(ssi.URI{}, nil, resolver.ErrKeyNotFound) + keyResolver.EXPECT().ResolveKey(*pA, nil, resolver.KeyAgreement).Return(ssi.URI{}, nil, resolver.ErrKeyNotFound) pal, err := PAL{*pA}.Encrypt(keyResolver) assert.Nil(t, pal) assert.EqualError(t, err, "unable to resolve keyAgreement key (recipient=did:nuts:A): key not found in DID document") diff --git a/network/network.go b/network/network.go index 59f139fe83..c5da1db41d 100644 --- a/network/network.go +++ b/network/network.go @@ -26,7 +26,7 @@ import ( "errors" "fmt" "github.com/nuts-foundation/nuts-node/vdr/didnuts/didstore" - "github.com/nuts-foundation/nuts-node/vdr/didservice" + "github.com/nuts-foundation/nuts-node/vdr/management" "github.com/nuts-foundation/nuts-node/vdr/resolver" "net" "strings" @@ -46,7 +46,6 @@ import ( "github.com/nuts-foundation/nuts-node/network/transport/v2" "github.com/nuts-foundation/nuts-node/pki" "github.com/nuts-foundation/nuts-node/storage" - "github.com/nuts-foundation/nuts-node/vdr/types" "go.etcd.io/bbolt" ) @@ -86,7 +85,7 @@ type Network struct { peerID transport.PeerID nodeDID did.DID didStore didstore.Store - didDocumentFinder types.DocFinder + didDocumentFinder management.DocFinder serviceResolver resolver.ServiceResolver eventPublisher events.Event storeProvider storage.Provider @@ -391,7 +390,7 @@ func (n *Network) connectToKnownNodes(nodeDID did.DID) error { } // start connecting to published NutsComm addresses - otherNodes, err := n.didDocumentFinder.Find(didservice.IsActive(), didservice.ValidAt(time.Now()), didservice.ByServiceType(transport.NutsCommServiceType)) + otherNodes, err := n.didDocumentFinder.Find(management.IsActive(), management.ValidAt(time.Now()), management.ByServiceType(transport.NutsCommServiceType)) if err != nil { return err } diff --git a/network/network_test.go b/network/network_test.go index 2a32514e77..5cbe50013f 100644 --- a/network/network_test.go +++ b/network/network_test.go @@ -464,8 +464,8 @@ func TestNetwork_CreateTransaction(t *testing.T) { cxt.state.EXPECT().Head(gomock.Any()) cxt.state.EXPECT().Add(gomock.Any(), gomock.Any(), payload) - cxt.keyResolver.EXPECT().ResolveKey(*sender, nil, vdrTypes.KeyAgreement).Return(ssi.MustParseURI("sender"), senderKey.Public(), nil) - cxt.keyResolver.EXPECT().ResolveKey(*receiver, nil, vdrTypes.KeyAgreement).Return(ssi.MustParseURI("receiver"), receiverKey.Public(), nil) + cxt.keyResolver.EXPECT().ResolveKey(*sender, nil, resolver.KeyAgreement).Return(ssi.MustParseURI("sender"), senderKey.Public(), nil) + cxt.keyResolver.EXPECT().ResolveKey(*receiver, nil, resolver.KeyAgreement).Return(ssi.MustParseURI("receiver"), receiverKey.Public(), nil) _, err = cxt.network.CreateTransaction(ctx, TransactionTemplate(payloadType, payload, key).WithPrivate([]did.DID{*sender, *receiver})) assert.NoError(t, err) diff --git a/vcr/credential/validator.go b/vcr/credential/validator.go index 1bfae34b06..7aa72d7266 100644 --- a/vcr/credential/validator.go +++ b/vcr/credential/validator.go @@ -24,7 +24,7 @@ import ( "errors" "fmt" "github.com/nuts-foundation/go-did/did" - "github.com/nuts-foundation/nuts-node/vdr/didservice" + "github.com/nuts-foundation/nuts-node/vdr/resolver" "github.com/piprate/json-gold/ld" "strings" @@ -238,7 +238,7 @@ func validOperation(operation string) bool { } func validateNutsCredentialID(credential vc.VerifiableCredential) error { - id, err := didservice.GetDIDFromURL(credential.ID.String()) + id, err := resolver.GetDIDFromURL(credential.ID.String()) if err != nil { return err } diff --git a/vcr/issuer/issuer.go b/vcr/issuer/issuer.go index 40d9a6d7f1..fa6ee6a74a 100644 --- a/vcr/issuer/issuer.go +++ b/vcr/issuer/issuer.go @@ -23,7 +23,6 @@ import ( "encoding/json" "fmt" "github.com/nuts-foundation/nuts-node/vcr/openid4vci" - "github.com/nuts-foundation/nuts-node/vdr/didservice" resolver2 "github.com/nuts-foundation/nuts-node/vdr/resolver" "time" @@ -194,7 +193,7 @@ func (i issuer) buildVC(ctx context.Context, credentialOptions vc.VerifiableCred if err != nil { const errString = "failed to sign credential: could not resolve an assertionKey for issuer: %w" // Differentiate between a DID document not found and some other error: - if didservice.IsFunctionalResolveError(err) { + if resolver2.IsFunctionalResolveError(err) { return nil, core.InvalidInputError(errString, err) } return nil, fmt.Errorf(errString, err) @@ -298,7 +297,7 @@ func (i issuer) buildRevocation(ctx context.Context, credentialID ssi.URI) (*cre if err != nil { const errString = "failed to revoke credential (%s): could not resolve an assertionKey for issuer: %w" // Differentiate between a DID document not found and some other error: - if didservice.IsFunctionalResolveError(err) { + if resolver2.IsFunctionalResolveError(err) { return nil, core.InvalidInputError(errString, credentialID, err) } return nil, fmt.Errorf(errString, credentialID, err) diff --git a/vcr/issuer/keyresolver.go b/vcr/issuer/keyresolver.go index bce16ffaf6..6256fb6045 100644 --- a/vcr/issuer/keyresolver.go +++ b/vcr/issuer/keyresolver.go @@ -24,7 +24,6 @@ import ( "github.com/nuts-foundation/go-did/did" "github.com/nuts-foundation/nuts-node/crypto" "github.com/nuts-foundation/nuts-node/vdr/resolver" - vdr "github.com/nuts-foundation/nuts-node/vdr/types" ) // vdrKeyResolver resolves private keys based upon the VDR document resolver @@ -35,7 +34,7 @@ type vdrKeyResolver struct { // ResolveAssertionKey is a convenience method which tries to find a assertionKey on in the VDR for a given issuerDID. func (r vdrKeyResolver) ResolveAssertionKey(ctx context.Context, issuerDID did.DID) (crypto.Key, error) { - kid, _, err := r.publicKeyResolver.ResolveKey(issuerDID, nil, vdr.AssertionMethod) + kid, _, err := r.publicKeyResolver.ResolveKey(issuerDID, nil, resolver.AssertionMethod) if err != nil { return nil, fmt.Errorf("invalid issuer: %w", err) } diff --git a/vcr/issuer/openid.go b/vcr/issuer/openid.go index 29f4b8ee48..a8ee1ec298 100644 --- a/vcr/issuer/openid.go +++ b/vcr/issuer/openid.go @@ -37,7 +37,6 @@ import ( "github.com/nuts-foundation/nuts-node/vcr/issuer/assets" "github.com/nuts-foundation/nuts-node/vcr/log" "github.com/nuts-foundation/nuts-node/vcr/openid4vci" - "github.com/nuts-foundation/nuts-node/vdr/didservice" "github.com/nuts-foundation/nuts-node/vdr/resolver" "io/fs" "net/http" @@ -332,7 +331,7 @@ func (i *openidHandler) validateProof(ctx context.Context, flow *Flow, request o } // Proof must be signed by wallet to which it was offered (proof signer == offer receiver) - if signerDID, err := didservice.GetDIDFromURL(signingKeyID); err != nil || signerDID.String() != wallet.String() { + if signerDID, err := resolver.GetDIDFromURL(signingKeyID); err != nil || signerDID.String() != wallet.String() { return generateProofError(openid4vci.Error{ Err: fmt.Errorf("credential offer was signed by other DID than intended wallet: %s", signingKeyID), Code: openid4vci.InvalidProof, diff --git a/vcr/openid4vci/identifiers.go b/vcr/openid4vci/identifiers.go index d52746c4a3..ffacf3cb3c 100644 --- a/vcr/openid4vci/identifiers.go +++ b/vcr/openid4vci/identifiers.go @@ -24,7 +24,6 @@ import ( "github.com/nuts-foundation/go-did/did" "github.com/nuts-foundation/nuts-node/core" "github.com/nuts-foundation/nuts-node/vcr/log" - "github.com/nuts-foundation/nuts-node/vdr/didservice" "github.com/nuts-foundation/nuts-node/vdr/resolver" "net/http" "net/url" @@ -58,7 +57,7 @@ type DIDIdentifierResolver struct { func (i DIDIdentifierResolver) Resolve(id did.DID) (string, error) { service, err := i.ServiceResolver.Resolve(resolver.MakeServiceReference(id, resolver.BaseURLServiceType), resolver.DefaultMaxServiceReferenceDepth) - if didservice.IsFunctionalResolveError(err) { + if resolver.IsFunctionalResolveError(err) { return "", nil } else if err != nil { return "", fmt.Errorf("unable to resolve %s service: %w", resolver.BaseURLServiceType, err) diff --git a/vdr/cmd/cmd.go b/vdr/cmd/cmd.go index a3d1888e37..f10a202c3d 100644 --- a/vdr/cmd/cmd.go +++ b/vdr/cmd/cmd.go @@ -24,7 +24,7 @@ import ( "errors" "fmt" "github.com/nuts-foundation/nuts-node/vdr/didnuts" - "github.com/nuts-foundation/nuts-node/vdr/didservice" + "github.com/nuts-foundation/nuts-node/vdr/resolver" "io" "os" "strings" @@ -286,7 +286,7 @@ func addKeyAgreementKeyCmd() *cobra.Command { if err != nil { return fmt.Errorf("invalid key ID '%s': %w", args[0], err) } - targetDID, _ := didservice.GetDIDFromURL(args[0]) // can't fail because we already parsed the key ID + targetDID, _ := resolver.GetDIDFromURL(args[0]) // can't fail because we already parsed the key ID clientConfig := core.NewClientConfigForCommand(cmd) client := httpClient(clientConfig) diff --git a/vdr/did_owner.go b/vdr/did_owner.go index d9fb9e3b6f..ae094f757e 100644 --- a/vdr/did_owner.go +++ b/vdr/did_owner.go @@ -23,7 +23,6 @@ import ( "fmt" "github.com/nuts-foundation/go-did/did" "github.com/nuts-foundation/nuts-node/crypto" - "github.com/nuts-foundation/nuts-node/vdr/didservice" "github.com/nuts-foundation/nuts-node/vdr/resolver" "github.com/nuts-foundation/nuts-node/vdr/types" "strings" @@ -77,7 +76,7 @@ func (t *cachingDocumentOwner) IsOwner(ctx context.Context, id did.DID) (bool, e // First perform a cheap DID existence check (subsequent checks are more expensive), // without caching it as negative match (would allow unbound number of negative matches). _, _, err := t.didResolver.Resolve(id, nil) - if didservice.IsFunctionalResolveError(err) { + if resolver.IsFunctionalResolveError(err) { return false, nil } else if err != nil { return false, fmt.Errorf("unable to check ownership of DID: %w", err) diff --git a/vdr/didnuts/creator.go b/vdr/didnuts/creator.go index 03ae71f4ab..6e2e2e3df2 100644 --- a/vdr/didnuts/creator.go +++ b/vdr/didnuts/creator.go @@ -22,7 +22,7 @@ import ( "crypto" "errors" "fmt" - "github.com/nuts-foundation/nuts-node/vdr/didservice" + "github.com/nuts-foundation/nuts-node/vdr/resolver" ssi "github.com/nuts-foundation/go-did" @@ -153,7 +153,7 @@ func (n Creator) Create(ctx context.Context, options vdr.DIDCreationOptions) (*d } // Create the bare document. The Document DID will be the keyIDStr without the fragment. - didID, _ := didservice.GetDIDFromURL(key.KID()) + didID, _ := resolver.GetDIDFromURL(key.KID()) doc := CreateDocument() doc.ID = didID doc.Controller = options.Controllers diff --git a/vdr/didnuts/didstore/finder.go b/vdr/didnuts/didstore/finder.go index ed839029c5..e6bc3c9c6d 100644 --- a/vdr/didnuts/didstore/finder.go +++ b/vdr/didnuts/didstore/finder.go @@ -19,8 +19,8 @@ package didstore import ( "github.com/nuts-foundation/go-did/did" + "github.com/nuts-foundation/nuts-node/vdr/management" "github.com/nuts-foundation/nuts-node/vdr/resolver" - "github.com/nuts-foundation/nuts-node/vdr/types" ) // Finder is a helper that implements the DocFinder interface @@ -28,7 +28,7 @@ type Finder struct { Store Store } -func (f Finder) Find(predicate ...types.Predicate) ([]did.Document, error) { +func (f Finder) Find(predicate ...management.Predicate) ([]did.Document, error) { matches := make([]did.Document, 0) err := f.Store.Iterate(func(doc did.Document, metadata resolver.DocumentMetadata) error { diff --git a/vdr/didnuts/didstore/finder_test.go b/vdr/didnuts/didstore/finder_test.go index 980f446b36..ef97bbd188 100644 --- a/vdr/didnuts/didstore/finder_test.go +++ b/vdr/didnuts/didstore/finder_test.go @@ -19,13 +19,12 @@ package didstore import ( "errors" - "github.com/nuts-foundation/nuts-node/vdr/didservice" + "github.com/nuts-foundation/nuts-node/vdr/management" "github.com/nuts-foundation/nuts-node/vdr/resolver" "github.com/stretchr/testify/require" "testing" "github.com/nuts-foundation/go-did/did" - "github.com/nuts-foundation/nuts-node/vdr/types" "github.com/stretchr/testify/assert" "go.uber.org/mock/gomock" ) @@ -36,11 +35,11 @@ func TestFinder_Find(t *testing.T) { didStore := NewMockStore(ctrl) finder := Finder{Store: didStore} didStore.EXPECT().Iterate(gomock.Any()).Do(func(arg interface{}) { - f := arg.(types.DocIterator) + f := arg.(management.DocIterator) f(did.Document{}, resolver.DocumentMetadata{}) }) - docs, err := finder.Find(didservice.IsActive()) + docs, err := finder.Find(management.IsActive()) require.NoError(t, err) assert.Len(t, docs, 1) @@ -52,7 +51,7 @@ func TestFinder_Find(t *testing.T) { finder := Finder{Store: didStore} didStore.EXPECT().Iterate(gomock.Any()).Return(errors.New("b00m!")) - _, err := finder.Find(didservice.IsActive()) + _, err := finder.Find(management.IsActive()) assert.Error(t, err) }) diff --git a/vdr/didnuts/didstore/interface.go b/vdr/didnuts/didstore/interface.go index 8b0418f38b..6b46c6a277 100644 --- a/vdr/didnuts/didstore/interface.go +++ b/vdr/didnuts/didstore/interface.go @@ -20,8 +20,8 @@ package didstore import ( "github.com/nuts-foundation/go-did/did" + "github.com/nuts-foundation/nuts-node/vdr/management" "github.com/nuts-foundation/nuts-node/vdr/resolver" - vdr "github.com/nuts-foundation/nuts-node/vdr/types" ) // Store is the interface that groups all low level VDR DID storage operations. @@ -29,14 +29,14 @@ type Store interface { // Add a DID Document to the store. The store will place it on the timeline and reprocess other versions if needed Add(didDocument did.Document, transaction Transaction) error // Conflicted iterates over all conflicted documents - Conflicted(fn vdr.DocIterator) error + Conflicted(fn management.DocIterator) error // ConflictedCount returns the number of conflicted DID Documents ConflictedCount() (uint, error) // DocumentCount returns the number of DID Documents DocumentCount() (uint, error) // Iterate loops over all the latest versions of the stored DID Documents and applies fn. // Calling any of the Store's functions from the given fn might cause a deadlock. - Iterate(fn vdr.DocIterator) error + Iterate(fn management.DocIterator) error // Resolve returns the DID Document for the provided DID. // If metadata is not provided the latest version is returned. // If metadata is provided then the result is filtered or scoped on that metadata. diff --git a/vdr/didnuts/didstore/mock.go b/vdr/didnuts/didstore/mock.go index 6ec865ffa6..d123833145 100644 --- a/vdr/didnuts/didstore/mock.go +++ b/vdr/didnuts/didstore/mock.go @@ -9,11 +9,11 @@ package didstore import ( + "github.com/nuts-foundation/nuts-node/vdr/management" "github.com/nuts-foundation/nuts-node/vdr/resolver" reflect "reflect" did "github.com/nuts-foundation/go-did/did" - types "github.com/nuts-foundation/nuts-node/vdr/types" gomock "go.uber.org/mock/gomock" ) @@ -55,7 +55,7 @@ func (mr *MockStoreMockRecorder) Add(didDocument, transaction any) *gomock.Call } // Conflicted mocks base method. -func (m *MockStore) Conflicted(fn types.DocIterator) error { +func (m *MockStore) Conflicted(fn management.DocIterator) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Conflicted", fn) ret0, _ := ret[0].(error) @@ -99,7 +99,7 @@ func (mr *MockStoreMockRecorder) DocumentCount() *gomock.Call { } // Iterate mocks base method. -func (m *MockStore) Iterate(fn types.DocIterator) error { +func (m *MockStore) Iterate(fn management.DocIterator) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Iterate", fn) ret0, _ := ret[0].(error) diff --git a/vdr/didnuts/didstore/store.go b/vdr/didnuts/didstore/store.go index 248fc5ea91..ddd4083f5c 100644 --- a/vdr/didnuts/didstore/store.go +++ b/vdr/didnuts/didstore/store.go @@ -27,8 +27,8 @@ import ( "github.com/nuts-foundation/go-stoabs" "github.com/nuts-foundation/nuts-node/core" "github.com/nuts-foundation/nuts-node/storage" + "github.com/nuts-foundation/nuts-node/vdr/management" "github.com/nuts-foundation/nuts-node/vdr/resolver" - vdr "github.com/nuts-foundation/nuts-node/vdr/types" ) var _ core.Configurable = (*store)(nil) @@ -178,7 +178,7 @@ func (tl *store) Resolve(id did.DID, resolveMetadata *resolver.ResolveMetadata) return } -func (tl *store) Iterate(fn vdr.DocIterator) error { +func (tl *store) Iterate(fn management.DocIterator) error { return tl.db.Read(context.Background(), func(tx stoabs.ReadTx) error { latestReader := tx.GetShelfReader(latestShelf) @@ -247,7 +247,7 @@ func (tl *store) removeCachedConflict(document did.Document) { delete(tl.conflictedDocuments, document.ID.String()) } -func (tl *store) Conflicted(fn vdr.DocIterator) error { +func (tl *store) Conflicted(fn management.DocIterator) error { for _, conflicted := range tl.conflictedDocuments { if err := fn(conflicted.didDocument, conflicted.metadata.asVDRMetadata()); err != nil { return err diff --git a/vdr/didnuts/resolver.go b/vdr/didnuts/resolver.go index 5bc9976e09..5be2bc14b8 100644 --- a/vdr/didnuts/resolver.go +++ b/vdr/didnuts/resolver.go @@ -23,9 +23,7 @@ import ( "fmt" "github.com/nuts-foundation/go-did/did" "github.com/nuts-foundation/nuts-node/vdr/didnuts/didstore" - "github.com/nuts-foundation/nuts-node/vdr/didservice" "github.com/nuts-foundation/nuts-node/vdr/resolver" - "github.com/nuts-foundation/nuts-node/vdr/types" ) const maxControllerDepth = 5 @@ -46,12 +44,12 @@ func (d Resolver) Resolve(id did.DID, metadata *resolver.ResolveMetadata) (*did. return resolve(d.Store, id, metadata, 0) } -func resolve(resolver resolver.DIDResolver, id did.DID, metadata *resolver.ResolveMetadata, depth int) (*did.Document, *resolver.DocumentMetadata, error) { +func resolve(didResolver resolver.DIDResolver, id did.DID, metadata *resolver.ResolveMetadata, depth int) (*did.Document, *resolver.DocumentMetadata, error) { if depth >= maxControllerDepth { return nil, nil, ErrNestedDocumentsTooDeep } - doc, meta, err := resolver.Resolve(id, metadata) + doc, meta, err := didResolver.Resolve(id, metadata) if err != nil { return nil, nil, err } @@ -60,7 +58,7 @@ func resolve(resolver resolver.DIDResolver, id did.DID, metadata *resolver.Resol if len(doc.Controller) > 0 && (metadata == nil || !metadata.AllowDeactivated) { // also check if the controller is not deactivated // since ResolveControllers calls Resolve and propagates the metadata - controllers, err := resolveControllers(resolver, *doc, metadata, depth+1) + controllers, err := resolveControllers(didResolver, *doc, metadata, depth+1) if err != nil { return nil, nil, err } @@ -78,7 +76,7 @@ func ResolveControllers(resolver resolver.DIDResolver, doc did.Document, metadat return resolveControllers(resolver, doc, metadata, 0) } -func resolveControllers(resolver resolver.DIDResolver, doc did.Document, metadata *resolver.ResolveMetadata, depth int) ([]did.Document, error) { +func resolveControllers(didResolver resolver.DIDResolver, doc did.Document, metadata *resolver.ResolveMetadata, depth int) ([]did.Document, error) { var leaves []did.Document var refsToResolve []did.DID @@ -101,8 +99,8 @@ func resolveControllers(resolver resolver.DIDResolver, doc did.Document, metadat // resolve all unresolved doc for _, ref := range refsToResolve { - node, _, err := resolve(resolver, ref, metadata, depth) - if errors.Is(err, resolver.ErrDeactivated) || errors.Is(err, resolver.ErrNoActiveController) || errors.Is(err, resolver.ErrNotFound) || errors.Is(err, types.ErrDIDMethodNotSupported) { + node, _, err := resolve(didResolver, ref, metadata, depth) + if errors.Is(err, resolver.ErrDeactivated) || errors.Is(err, resolver.ErrNoActiveController) || errors.Is(err, resolver.ErrNotFound) || errors.Is(err, resolver.ErrDIDMethodNotSupported) { continue } if errors.Is(err, ErrNestedDocumentsTooDeep) { @@ -117,7 +115,7 @@ func resolveControllers(resolver resolver.DIDResolver, doc did.Document, metadat // filter deactivated j := 0 for _, leaf := range leaves { - if !didservice.IsDeactivated(leaf) { + if !resolver.IsDeactivated(leaf) { leaves[j] = leaf j++ } diff --git a/vdr/didnuts/resolver_test.go b/vdr/didnuts/resolver_test.go index a6728ed544..45d98d8202 100644 --- a/vdr/didnuts/resolver_test.go +++ b/vdr/didnuts/resolver_test.go @@ -42,13 +42,13 @@ func TestNutsDIDResolver_Resolve(t *testing.T) { t.Run("ok", func(t *testing.T) { ctrl := gomock.NewController(t) didStore := didstore.NewMockStore(ctrl) - resolver := Resolver{Store: didStore} + nutsResolver := Resolver{Store: didStore} doc := did.Document{ID: *id123} id123Method1, _ := did.ParseDIDURL("did:nuts:123#method-1") doc.AddCapabilityInvocation(&did.VerificationMethod{ID: *id123Method1}) didStore.EXPECT().Resolve(*id123, resolveMD).Return(&doc, &resolver.DocumentMetadata{}, nil) - resultDoc, _, err := resolver.Resolve(*id123, resolveMD) + resultDoc, _, err := nutsResolver.Resolve(*id123, resolveMD) require.NoError(t, err) @@ -59,11 +59,11 @@ func TestNutsDIDResolver_Resolve(t *testing.T) { t.Run("err - with resolver metadata", func(t *testing.T) { ctrl := gomock.NewController(t) didStore := didstore.NewMockStore(ctrl) - resolver := Resolver{Store: didStore} + nutsResolver := Resolver{Store: didStore} didStore.EXPECT().Resolve(*id456, resolveMD).Return(&docB, &resolver.DocumentMetadata{}, nil) didStore.EXPECT().Resolve(*id123, resolveMD).Return(&docA, &resolver.DocumentMetadata{}, nil) - doc, _, err := resolver.Resolve(*id456, resolveMD) + doc, _, err := nutsResolver.Resolve(*id456, resolveMD) assert.Error(t, err) assert.Equal(t, resolver.ErrNoActiveController, err) @@ -73,11 +73,11 @@ func TestNutsDIDResolver_Resolve(t *testing.T) { t.Run("err - without resolve metadata", func(t *testing.T) { ctrl := gomock.NewController(t) didStore := didstore.NewMockStore(ctrl) - resolver := Resolver{Store: didStore} + nutsResolver := Resolver{Store: didStore} didStore.EXPECT().Resolve(*id456, nil).Return(&docB, &resolver.DocumentMetadata{}, nil) didStore.EXPECT().Resolve(*id123, nil).Return(&docA, &resolver.DocumentMetadata{}, nil) - doc, _, err := resolver.Resolve(*id456, nil) + doc, _, err := nutsResolver.Resolve(*id456, nil) assert.Error(t, err) assert.Equal(t, resolver.ErrNoActiveController, err) @@ -87,12 +87,12 @@ func TestNutsDIDResolver_Resolve(t *testing.T) { t.Run("ok - allowed deactivated", func(t *testing.T) { ctrl := gomock.NewController(t) didStore := didstore.NewMockStore(ctrl) - resolver := Resolver{Store: didStore} + nutsResolver := Resolver{Store: didStore} resolveMD := &resolver.ResolveMetadata{ResolveTime: &resolveTime, AllowDeactivated: true} didStore.EXPECT().Resolve(*id456, resolveMD).Return(&docB, &resolver.DocumentMetadata{}, nil) - doc, _, err := resolver.Resolve(*id456, resolveMD) + doc, _, err := nutsResolver.Resolve(*id456, resolveMD) assert.NoError(t, err) assert.Equal(t, docB, *doc) }) @@ -108,7 +108,7 @@ func TestNutsDIDResolver_Resolve(t *testing.T) { prevID := rootID prevDoc := rootDoc didStore := didstore.NewMockStore(ctrl) - resolver := Resolver{Store: didStore} + nutsResolver := Resolver{Store: didStore} for i := 0; i < depth; i++ { id, _ := did.ParseDID(fmt.Sprintf("did:nuts:%d", i)) d := did.Document{ID: *id, Controller: []did.DID{*prevID}} @@ -120,7 +120,7 @@ func TestNutsDIDResolver_Resolve(t *testing.T) { } didStore.EXPECT().Resolve(*dids[depth-1], resolveMD).Return(&docs[depth-1], &resolver.DocumentMetadata{}, nil) - _, _, err := resolver.Resolve(*dids[depth-1], resolveMD) + _, _, err := nutsResolver.Resolve(*dids[depth-1], resolveMD) assert.Error(t, err) assert.Equal(t, ErrNestedDocumentsTooDeep, err) @@ -164,18 +164,18 @@ func TestResolveControllers(t *testing.T) { t.Run("docA is controller of docB", func(t *testing.T) { ctrl := gomock.NewController(t) - resolver := types.NewMockDIDResolver(ctrl) + nutsResolver := types.NewMockDIDResolver(ctrl) docA := did.Document{ID: *id123} docA.AddCapabilityInvocation(&did.VerificationMethod{ID: *id123Method1}) resolveTime := time.Date(2010, 1, 1, 1, 1, 1, 0, time.UTC) resolveMD := &resolver.ResolveMetadata{ResolveTime: &resolveTime} - resolver.EXPECT().Resolve(*id123, resolveMD).Return(&docA, &resolver.DocumentMetadata{}, nil) + nutsResolver.EXPECT().Resolve(*id123, resolveMD).Return(&docA, &resolver.DocumentMetadata{}, nil) docB := did.Document{ID: *id456, Controller: []did.DID{*id123}} - docs, err := ResolveControllers(resolver, docB, resolveMD) + docs, err := ResolveControllers(nutsResolver, docB, resolveMD) assert.NoError(t, err) assert.Len(t, docs, 1) assert.Equal(t, docA, docs[0], @@ -184,32 +184,32 @@ func TestResolveControllers(t *testing.T) { t.Run("docA is controller of docB and docA is deactivated", func(t *testing.T) { ctrl := gomock.NewController(t) - resolver := types.NewMockDIDResolver(ctrl) + nutsResolver := types.NewMockDIDResolver(ctrl) docA := did.Document{ID: *id123} resolveTime := time.Date(2010, 1, 1, 1, 1, 1, 0, time.UTC) resolveMD := &resolver.ResolveMetadata{ResolveTime: &resolveTime} - resolver.EXPECT().Resolve(*id123, resolveMD).Return(&docA, &resolver.DocumentMetadata{}, nil) + nutsResolver.EXPECT().Resolve(*id123, resolveMD).Return(&docA, &resolver.DocumentMetadata{}, nil) docB := did.Document{ID: *id456, Controller: []did.DID{*id123}} - docs, err := ResolveControllers(resolver, docB, resolveMD) + docs, err := ResolveControllers(nutsResolver, docB, resolveMD) assert.NoError(t, err) assert.Len(t, docs, 0) }) t.Run("docA and docB are both the controllers of docB", func(t *testing.T) { ctrl := gomock.NewController(t) - resolver := types.NewMockDIDResolver(ctrl) + nutsResolver := types.NewMockDIDResolver(ctrl) docA := did.Document{ID: *id123} docA.AddCapabilityInvocation(&did.VerificationMethod{ID: *id123Method1}) - resolver.EXPECT().Resolve(*id123, gomock.Any()).Return(&docA, &resolver.DocumentMetadata{}, nil) + nutsResolver.EXPECT().Resolve(*id123, gomock.Any()).Return(&docA, &resolver.DocumentMetadata{}, nil) docB := did.Document{ID: *id456, Controller: []did.DID{*id123, *id456}} docB.AddCapabilityInvocation(&did.VerificationMethod{ID: *id456Method1}) - docs, err := ResolveControllers(resolver, docB, nil) + docs, err := ResolveControllers(nutsResolver, docB, nil) assert.NoError(t, err) assert.Len(t, docs, 2) assert.Equal(t, []did.Document{docB, docA}, docs, @@ -218,17 +218,17 @@ func TestResolveControllers(t *testing.T) { t.Run("docA and docB are both the controllers of docB, resolve by source transaction", func(t *testing.T) { ctrl := gomock.NewController(t) - resolver := types.NewMockDIDResolver(ctrl) + nutsResolver := types.NewMockDIDResolver(ctrl) docA := did.Document{ID: *id123} docA.AddCapabilityInvocation(&did.VerificationMethod{ID: *id123Method1}) // when we resolve by source TX, we will not find the other controller - resolver.EXPECT().Resolve(*id123, gomock.Any()).Return(nil, nil, resolver.ErrNotFound) + nutsResolver.EXPECT().Resolve(*id123, gomock.Any()).Return(nil, nil, resolver.ErrNotFound) docB := did.Document{ID: *id456, Controller: []did.DID{*id123, *id456}} docB.AddCapabilityInvocation(&did.VerificationMethod{ID: *id456Method1}) - docs, err := ResolveControllers(resolver, docB, nil) + docs, err := ResolveControllers(nutsResolver, docB, nil) assert.NoError(t, err) assert.Len(t, docs, 1) assert.Equal(t, []did.Document{docB}, docs, @@ -237,11 +237,11 @@ func TestResolveControllers(t *testing.T) { t.Run("docA, docB and docC are controllers of docA, docB is deactivated", func(t *testing.T) { ctrl := gomock.NewController(t) - resolver := types.NewMockDIDResolver(ctrl) + nutsResolver := types.NewMockDIDResolver(ctrl) // Doc B is deactivated docBID, _ := did.ParseDID("did:nuts:B") docB := did.Document{ID: *docBID} - resolver.EXPECT().Resolve(docB.ID, gomock.Any()).Return(&docB, &resolver.DocumentMetadata{}, nil) + nutsResolver.EXPECT().Resolve(docB.ID, gomock.Any()).Return(&docB, &resolver.DocumentMetadata{}, nil) // Doc C is active docCID, _ := did.ParseDID("did:nuts:C") @@ -249,7 +249,7 @@ func TestResolveControllers(t *testing.T) { docCIDCapInv.Fragment = "cap-inv" docC := did.Document{ID: *docCID} docC.AddCapabilityInvocation(&did.VerificationMethod{ID: docCIDCapInv}) - resolver.EXPECT().Resolve(docC.ID, gomock.Any()).Return(&docC, &resolver.DocumentMetadata{}, nil) + nutsResolver.EXPECT().Resolve(docC.ID, gomock.Any()).Return(&docC, &resolver.DocumentMetadata{}, nil) // Doc A is active docAID, _ := did.ParseDID("did:nuts:A") @@ -259,7 +259,7 @@ func TestResolveControllers(t *testing.T) { docA.Controller = []did.DID{docA.ID, docB.ID, docC.ID} docA.AddCapabilityInvocation(&did.VerificationMethod{ID: docAIDCapInv}) - docs, err := ResolveControllers(resolver, docA, nil) + docs, err := ResolveControllers(nutsResolver, docA, nil) assert.NoError(t, err) assert.Len(t, docs, 2) assert.Contains(t, docs, docA, "expected docA to be resolved as controller of docA") @@ -268,15 +268,15 @@ func TestResolveControllers(t *testing.T) { t.Run("docA is controller of docB, docA has explicit self link in Controllers", func(t *testing.T) { ctrl := gomock.NewController(t) - resolver := types.NewMockDIDResolver(ctrl) + nutsResolver := types.NewMockDIDResolver(ctrl) docA := did.Document{ID: *id123, Controller: []did.DID{*id123}} docA.AddCapabilityInvocation(&did.VerificationMethod{ID: *id123Method1}) - resolver.EXPECT().Resolve(*id123, gomock.Any()).Return(&docA, &resolver.DocumentMetadata{}, nil) + nutsResolver.EXPECT().Resolve(*id123, gomock.Any()).Return(&docA, &resolver.DocumentMetadata{}, nil) docB := did.Document{ID: *id456, Controller: []did.DID{*id123}} - docs, err := ResolveControllers(resolver, docB, nil) + docs, err := ResolveControllers(nutsResolver, docB, nil) assert.NoError(t, err) assert.Len(t, docs, 1) assert.Equal(t, docA, docs[0], @@ -285,12 +285,12 @@ func TestResolveControllers(t *testing.T) { t.Run("ok - Resolve can not find the document", func(t *testing.T) { ctrl := gomock.NewController(t) - resolver := types.NewMockDIDResolver(ctrl) - resolver.EXPECT().Resolve(*id123, gomock.Any()).Return(nil, nil, resolver.ErrNotFound) + nutsResolver := types.NewMockDIDResolver(ctrl) + nutsResolver.EXPECT().Resolve(*id123, gomock.Any()).Return(nil, nil, resolver.ErrNotFound) docB := did.Document{ID: *id456, Controller: []did.DID{*id123}} - docs, err := ResolveControllers(resolver, docB, nil) + docs, err := ResolveControllers(nutsResolver, docB, nil) require.NoError(t, err) assert.Len(t, docs, 0) }) diff --git a/vdr/didservice/resolvers.go b/vdr/didservice/resolvers.go deleted file mode 100644 index 79d9d28592..0000000000 --- a/vdr/didservice/resolvers.go +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2022 Nuts community - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -// Package service contains DID Document related functionality that only matters to the current node. -// All functionality here has zero relations to the network. -package didservice - -import ( - "errors" - "github.com/nuts-foundation/go-did/did" - "github.com/nuts-foundation/nuts-node/vdr/resolver" - "github.com/nuts-foundation/nuts-node/vdr/types" - "sync" -) - -var _ resolver.DIDResolver = &DIDResolverRouter{} - -// DIDResolverRouter is a DID resolver that can route to different DID resolvers based on the DID method -type DIDResolverRouter struct { - resolvers sync.Map -} - -// Resolve looks up the right resolver for the given DID and delegates the resolution to it. -// If no resolver is registered for the given DID method, ErrDIDMethodNotSupported is returned. -func (r *DIDResolverRouter) Resolve(id did.DID, metadata *resolver.ResolveMetadata) (*did.Document, *resolver.DocumentMetadata, error) { - method := id.Method - resolver, registered := r.resolvers.Load(method) - if !registered { - return nil, nil, types.ErrDIDMethodNotSupported - } - return resolver.(resolver.DIDResolver).Resolve(id, metadata) -} - -// Register registers a DID resolver for the given DID method. -func (r *DIDResolverRouter) Register(method string, resolver resolver.DIDResolver) { - r.resolvers.Store(method, resolver) -} - -// IsFunctionalResolveError returns true if the given error indicates the DID or service not being found or invalid, -// e.g. because it is deactivated, referenced too deeply, etc. -func IsFunctionalResolveError(target error) bool { - return errors.Is(target, resolver.ErrNotFound) || - errors.Is(target, resolver.ErrDeactivated) || - errors.Is(target, resolver.ErrServiceNotFound) || - errors.Is(target, resolver.ErrNoActiveController) || - errors.Is(target, resolver.ErrServiceReferenceToDeep) || - errors.Is(target, did.InvalidDIDErr) || - errors.As(target, new(resolver.ServiceQueryError)) -} diff --git a/vdr/didservice/resolvers_test.go b/vdr/didservice/resolvers_test.go deleted file mode 100644 index 87e067eb3f..0000000000 --- a/vdr/didservice/resolvers_test.go +++ /dev/null @@ -1,287 +0,0 @@ -/* - * Copyright (C) 2022 Nuts community - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package didservice - -import ( - "crypto/ecdsa" - "crypto/elliptic" - "crypto/rand" - "errors" - "fmt" - ssi "github.com/nuts-foundation/go-did" - "github.com/nuts-foundation/go-did/did" - "github.com/nuts-foundation/nuts-node/crypto/hash" - resolver2 "github.com/nuts-foundation/nuts-node/vdr/resolver" - "github.com/nuts-foundation/nuts-node/vdr/types" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "go.uber.org/mock/gomock" - "testing" -) - -func TestServiceResolver_Resolve(t *testing.T) { - meta := &resolver2.DocumentMetadata{Hash: hash.EmptyHash()} - - didA, _ := did.ParseDID("did:nuts:A") - didB, _ := did.ParseDID("did:nuts:B") - - serviceID := ssi.MustParseURI(fmt.Sprintf("%s#1", didA.String())) - docA := did.Document{ - Context: []ssi.URI{did.DIDContextV1URI()}, - ID: *didA, - Service: []did.Service{{ - ID: serviceID, - Type: "hello", - ServiceEndpoint: "http://hello", - }}, - } - docB := did.Document{ - Context: []ssi.URI{did.DIDContextV1URI()}, - ID: *didA, - Service: []did.Service{ - { - Type: "simple", - ServiceEndpoint: "http://world", - }, - { - Type: "cyclic-ref", - ServiceEndpoint: didB.String() + "/serviceEndpoint?type=cyclic-ref", - }, - { - Type: "invalid-ref", - ServiceEndpoint: didB.String() + "?type=invalid-ref", - }, - { - Type: "external", - ServiceEndpoint: resolver2.MakeServiceReference(docA.ID, "hello").String(), - }, - }, - } - - t.Run("ok", func(t *testing.T) { - ctrl := gomock.NewController(t) - resolver := types.NewMockDIDResolver(ctrl) - - resolver.EXPECT().Resolve(*didB, nil).MinTimes(1).Return(&docB, meta, nil) - - actual, err := resolver2.DIDServiceResolver{Resolver: resolver}.Resolve(resolver2.MakeServiceReference(*didB, "simple"), resolver2.DefaultMaxServiceReferenceDepth) - - assert.NoError(t, err) - assert.Equal(t, docB.Service[0], actual) - }) - t.Run("ok - external", func(t *testing.T) { - ctrl := gomock.NewController(t) - resolver := types.NewMockDIDResolver(ctrl) - - resolver.EXPECT().Resolve(*didB, nil).MinTimes(1).Return(&docB, meta, nil) - resolver.EXPECT().Resolve(*didA, nil).MinTimes(1).Return(&docA, meta, nil) - - actual, err := resolver2.DIDServiceResolver{Resolver: resolver}.Resolve(resolver2.MakeServiceReference(*didB, "external"), resolver2.DefaultMaxServiceReferenceDepth) - - assert.NoError(t, err) - assert.Equal(t, docA.Service[0], actual) - }) - t.Run("error - cyclic reference (yields refs too deep)", func(t *testing.T) { - ctrl := gomock.NewController(t) - resolver := types.NewMockDIDResolver(ctrl) - - resolver.EXPECT().Resolve(*didB, nil).MinTimes(1).Return(&docB, meta, nil) - - actual, err := resolver2.DIDServiceResolver{Resolver: resolver}.Resolve(resolver2.MakeServiceReference(*didB, "cyclic-ref"), resolver2.DefaultMaxServiceReferenceDepth) - - assert.EqualError(t, err, "service references are nested to deeply before resolving to a non-reference") - assert.Empty(t, actual) - }) - t.Run("error - invalid ref", func(t *testing.T) { - ctrl := gomock.NewController(t) - resolver := types.NewMockDIDResolver(ctrl) - - resolver.EXPECT().Resolve(*didB, nil).MinTimes(1).Return(&docB, meta, nil) - - actual, err := resolver2.DIDServiceResolver{Resolver: resolver}.Resolve(resolver2.MakeServiceReference(*didB, "invalid-ref"), resolver2.DefaultMaxServiceReferenceDepth) - - assert.EqualError(t, err, "DID service query invalid: endpoint URI path must be /serviceEndpoint") - assert.Empty(t, actual) - }) - t.Run("error - service not found", func(t *testing.T) { - ctrl := gomock.NewController(t) - resolver := types.NewMockDIDResolver(ctrl) - - resolver.EXPECT().Resolve(*didB, nil).MinTimes(1).Return(&docB, meta, nil) - - actual, err := resolver2.DIDServiceResolver{Resolver: resolver}.Resolve(resolver2.MakeServiceReference(*didB, "non-existent"), resolver2.DefaultMaxServiceReferenceDepth) - - assert.EqualError(t, err, "service not found in DID Document") - assert.Empty(t, actual) - }) - t.Run("error - DID not found", func(t *testing.T) { - ctrl := gomock.NewController(t) - resolver := types.NewMockDIDResolver(ctrl) - - resolver.EXPECT().Resolve(*didB, nil).MinTimes(1).Return(nil, nil, resolver2.ErrNotFound) - - actual, err := resolver2.DIDServiceResolver{Resolver: resolver}.Resolve(resolver2.MakeServiceReference(*didB, "non-existent"), resolver2.DefaultMaxServiceReferenceDepth) - - assert.EqualError(t, err, "unable to find the DID document") - assert.Empty(t, actual) - }) -} - -func TestKeyResolver_ResolveKey(t *testing.T) { - ctrl := gomock.NewController(t) - resolver := types.NewMockDIDResolver(ctrl) - keyResolver := resolver2.DIDKeyResolver{Resolver: resolver} - - doc := newDidDoc() - resolver.EXPECT().Resolve(doc.ID, gomock.Any()).AnyTimes().Return(&doc, nil, nil) - - t.Run("ok - it finds the key", func(t *testing.T) { - keyId, key, err := keyResolver.ResolveKey(doc.ID, nil, types.AssertionMethod) - require.NoError(t, err) - assert.Equal(t, doc.VerificationMethod[0].ID.URI(), keyId) - assert.NotNil(t, key) - }) - - t.Run("error - document not found", func(t *testing.T) { - unknownDID := did.MustParseDID("did:example:123") - resolver.EXPECT().Resolve(unknownDID, gomock.Any()).Return(nil, nil, resolver2.ErrNotFound) - keyId, key, err := keyResolver.ResolveKey(unknownDID, nil, types.AssertionMethod) - assert.EqualError(t, err, "unable to find the DID document") - assert.Empty(t, keyId) - assert.Nil(t, key) - }) - - t.Run("error - key not found", func(t *testing.T) { - keyId, key, err := keyResolver.ResolveKey(did.MustParseDIDURL(doc.ID.String()), nil, types.CapabilityDelegation) - assert.EqualError(t, err, "key not found in DID document") - assert.Empty(t, keyId) - assert.Nil(t, key) - }) - - t.Run("error - unknown relationship type", func(t *testing.T) { - keyId, key, err := keyResolver.ResolveKey(doc.ID, nil, 1000) - assert.EqualError(t, err, "unable to locate RelationType 1000") - assert.Empty(t, keyId) - assert.Nil(t, key) - }) -} - -func newDidDoc() did.Document { - privateKey, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - id := did.MustParseDID("did:example:sakjsakldjsakld") - keyID := id - keyID.Fragment = "key-1" - vm, _ := did.NewVerificationMethod(keyID, ssi.JsonWebKey2020, id, privateKey.Public()) - doc := did.Document{ - ID: id, - } - doc.AddAssertionMethod(vm) - return doc -} - -func TestKeyResolver_ResolveKeyByID(t *testing.T) { - ctrl := gomock.NewController(t) - resolver := types.NewMockDIDResolver(ctrl) - keyResolver := resolver2.DIDKeyResolver{Resolver: resolver} - doc := newDidDoc() - resolver.EXPECT().Resolve(doc.ID, gomock.Any()).AnyTimes().Return(&doc, nil, nil) - keyID := doc.VerificationMethod[0].ID - - t.Run("ok - it finds the key", func(t *testing.T) { - key, err := keyResolver.ResolveKeyByID(keyID.String(), nil, types.AssertionMethod) - assert.NoError(t, err) - assert.NotNil(t, key) - }) - - t.Run("error - invalid key ID", func(t *testing.T) { - key, err := keyResolver.ResolveKeyByID("abcdef", nil, types.AssertionMethod) - assert.EqualError(t, err, "invalid key ID (id=abcdef): invalid DID") - assert.Nil(t, key) - }) - - t.Run("error - document not found", func(t *testing.T) { - unknownDID := did.MustParseDIDURL("did:example:123") - resolver.EXPECT().Resolve(unknownDID, gomock.Any()).Return(nil, nil, resolver2.ErrNotFound) - key, err := keyResolver.ResolveKeyByID(unknownDID.String()+"#456", nil, types.AssertionMethod) - assert.EqualError(t, err, "unable to find the DID document") - assert.Nil(t, key) - }) - - t.Run("error - key not found", func(t *testing.T) { - key, err := keyResolver.ResolveKeyByID(did.MustParseDIDURL(doc.ID.String()+"#123").String(), nil, types.AssertionMethod) - assert.EqualError(t, err, "key not found in DID document") - assert.Nil(t, key) - }) - - t.Run("error - unknown relationship type", func(t *testing.T) { - key, err := keyResolver.ResolveKeyByID(keyID.String(), nil, 1000) - assert.EqualError(t, err, "unable to locate RelationType 1000") - assert.Nil(t, key) - }) -} - -func TestIsFunctionalResolveError(t *testing.T) { - assert.True(t, IsFunctionalResolveError(resolver2.ErrNotFound)) - assert.True(t, IsFunctionalResolveError(resolver2.ErrDeactivated)) - assert.True(t, IsFunctionalResolveError(resolver2.ErrServiceNotFound)) - assert.True(t, IsFunctionalResolveError(resolver2.ErrServiceReferenceToDeep)) - assert.True(t, IsFunctionalResolveError(resolver2.ErrNoActiveController)) - assert.True(t, IsFunctionalResolveError(did.InvalidDIDErr)) - assert.True(t, IsFunctionalResolveError(resolver2.ServiceQueryError{Err: errors.New("oops")})) - - assert.False(t, IsFunctionalResolveError(errors.New("some error"))) - assert.False(t, IsFunctionalResolveError(resolver2.ErrDuplicateService)) -} - -func TestDIDResolverRouter_Resolve(t *testing.T) { - doc := newDidDoc() - t.Run("ok, 1 resolver", func(t *testing.T) { - ctrl := gomock.NewController(t) - resolver := types.NewMockDIDResolver(ctrl) - resolver.EXPECT().Resolve(doc.ID, gomock.Any()).Return(&doc, nil, nil) - router := &DIDResolverRouter{} - router.Register(doc.ID.Method, resolver) - - actual, _, err := router.Resolve(doc.ID, nil) - assert.NoError(t, err) - assert.Equal(t, &doc, actual) - }) - t.Run("ok, 2 resolvers", func(t *testing.T) { - ctrl := gomock.NewController(t) - otherResolver := types.NewMockDIDResolver(ctrl) - resolver := types.NewMockDIDResolver(ctrl) - resolver.EXPECT().Resolve(doc.ID, gomock.Any()).Return(&doc, nil, nil) - router := &DIDResolverRouter{} - router.Register(doc.ID.Method, resolver) - router.Register("test2", otherResolver) - - actual, _, err := router.Resolve(doc.ID, nil) - assert.NoError(t, err) - assert.Equal(t, &doc, actual) - }) - t.Run("error - resolver not found", func(t *testing.T) { - ctrl := gomock.NewController(t) - otherResolver := types.NewMockDIDResolver(ctrl) - router := &DIDResolverRouter{} - router.Register("other", otherResolver) - - actual, _, err := router.Resolve(doc.ID, nil) - assert.EqualError(t, err, "DID method not supported") - assert.Nil(t, actual) - }) -} diff --git a/vdr/didservice/test.go b/vdr/didservice/test.go deleted file mode 100644 index 121faeab3b..0000000000 --- a/vdr/didservice/test.go +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Nuts node - * Copyright (C) 2021 Nuts community - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - -package didservice - -import ( - ssi "github.com/nuts-foundation/go-did" - "github.com/nuts-foundation/go-did/did" -) - -// marshal/unmarshal safe notation -var testServiceA = did.Service{ID: ssi.MustParseURI("did:nuts:service:a"), ServiceEndpoint: []interface{}{"http://a"}} -var testServiceB = did.Service{ID: ssi.MustParseURI("did:nuts:service:b"), ServiceEndpoint: []interface{}{"http://b"}} diff --git a/vdr/didservice/util.go b/vdr/didservice/util.go deleted file mode 100644 index 39cfd980e9..0000000000 --- a/vdr/didservice/util.go +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (C) 2022 Nuts community - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package didservice - -import ( - "github.com/nuts-foundation/go-did/did" -) - -// GetDIDFromURL returns the DID from the given URL, stripping any query parameters, path segments and fragments. -func GetDIDFromURL(didURL string) (did.DID, error) { - parsed, err := did.ParseDIDURL(didURL) - if err != nil { - return did.DID{}, err - } - return parsed.WithoutURL(), nil -} - -// IsDeactivated returns true if the DID.Document has already been deactivated -func IsDeactivated(document did.Document) bool { - return len(document.Controller) == 0 && len(document.CapabilityInvocation) == 0 -} diff --git a/vdr/didservice/util_test.go b/vdr/didservice/util_test.go deleted file mode 100644 index 3d6907496a..0000000000 --- a/vdr/didservice/util_test.go +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2022 Nuts community - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package didservice - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestGetDIDFromURL(t *testing.T) { - t.Run("just it", func(t *testing.T) { - actual, err := GetDIDFromURL("did:nuts:abc") - assert.NoError(t, err) - assert.Equal(t, "did:nuts:abc", actual.String()) - }) - t.Run("with path", func(t *testing.T) { - actual, err := GetDIDFromURL("did:nuts:abc/serviceEndpoint") - assert.NoError(t, err) - assert.Equal(t, "did:nuts:abc", actual.String()) - }) - t.Run("with fragment", func(t *testing.T) { - actual, err := GetDIDFromURL("did:nuts:abc#key-1") - assert.NoError(t, err) - assert.Equal(t, "did:nuts:abc", actual.String()) - }) - t.Run("with params", func(t *testing.T) { - actual, err := GetDIDFromURL("did:nuts:abc?foo=bar") - assert.NoError(t, err) - assert.Equal(t, "did:nuts:abc", actual.String()) - }) - t.Run("invalid DID", func(t *testing.T) { - _, err := GetDIDFromURL("https://example.com") - assert.Error(t, err) - }) -} diff --git a/vdr/didservice/finder.go b/vdr/management/finder.go similarity index 62% rename from vdr/didservice/finder.go rename to vdr/management/finder.go index 46c9cdf7d0..3cc9b12d1d 100644 --- a/vdr/didservice/finder.go +++ b/vdr/management/finder.go @@ -1,34 +1,29 @@ -/* - * Copyright (C) 2022 Nuts community - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - -package didservice +package management import ( "github.com/nuts-foundation/go-did/did" "github.com/nuts-foundation/nuts-node/vdr/resolver" - "github.com/nuts-foundation/nuts-node/vdr/types" "strings" "time" ) +// DocFinder is the interface that groups all methods for finding DID documents based on search conditions +type DocFinder interface { + Find(...Predicate) ([]did.Document, error) +} + +// Predicate is an interface for abstracting search options on DID documents +type Predicate interface { + // Match returns true if the given DID Document passes the predicate condition + Match(did.Document, resolver.DocumentMetadata) bool +} + +// DocIterator is the function type for iterating over the all current DID Documents in the store +type DocIterator func(doc did.Document, metadata resolver.DocumentMetadata) error + // ByServiceType returns a predicate that matches on service type // it only matches on DID Documents with a concrete endpoint (not starting with "did") -func ByServiceType(serviceType string) types.Predicate { +func ByServiceType(serviceType string) Predicate { return servicePredicate{serviceType: serviceType} } @@ -49,7 +44,7 @@ func (s servicePredicate) Match(document did.Document, _ resolver.DocumentMetada } // ValidAt returns a predicate that matches on validity period. -func ValidAt(at time.Time) types.Predicate { +func ValidAt(at time.Time) Predicate { return validAtPredicate{validAt: at} } @@ -72,7 +67,7 @@ func (v validAtPredicate) Match(_ did.Document, metadata resolver.DocumentMetada } // IsActive returns a predicate that matches DID Documents that are not deactivated. -func IsActive() types.Predicate { +func IsActive() Predicate { return deactivatedPredicate{deactivated: false} } diff --git a/vdr/didservice/finder_test.go b/vdr/management/finder_test.go similarity index 72% rename from vdr/didservice/finder_test.go rename to vdr/management/finder_test.go index 5899aa62bb..d365451177 100644 --- a/vdr/didservice/finder_test.go +++ b/vdr/management/finder_test.go @@ -1,29 +1,11 @@ -/* - * Copyright (C) 2022 Nuts community - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package didservice +package management import ( + "github.com/nuts-foundation/go-did/did" "github.com/nuts-foundation/nuts-node/vdr/resolver" + "github.com/stretchr/testify/assert" "testing" "time" - - "github.com/nuts-foundation/go-did/did" - "github.com/stretchr/testify/assert" ) func TestIsActive(t *testing.T) { diff --git a/vdr/resolver/did.go b/vdr/resolver/did.go index cc1ccf0d01..eec2a7025c 100644 --- a/vdr/resolver/did.go +++ b/vdr/resolver/did.go @@ -4,6 +4,7 @@ import ( "errors" "github.com/nuts-foundation/go-did/did" "github.com/nuts-foundation/nuts-node/crypto/hash" + "sync" "time" ) @@ -18,6 +19,9 @@ type DIDResolver interface { Resolve(id did.DID, metadata *ResolveMetadata) (*did.Document, *DocumentMetadata, error) } +// ErrDIDMethodNotSupported is returned when a DID method is not supported by the DID resolver +var ErrDIDMethodNotSupported = errors.New("DID method not supported") + // ErrDIDNotManagedByThisNode is returned when an operation needs the private key and if is not found on this host var ErrDIDNotManagedByThisNode = errors.New("DID document not managed by this node") @@ -94,3 +98,52 @@ type ResolveMetadata struct { // Allow DIDs which are deactivated AllowDeactivated bool } + +var _ DIDResolver = &DIDResolverRouter{} + +// DIDResolverRouter is a DID resolver that can route to different DID resolvers based on the DID method +type DIDResolverRouter struct { + resolvers sync.Map +} + +// Resolve looks up the right resolver for the given DID and delegates the resolution to it. +// If no resolver is registered for the given DID method, ErrDIDMethodNotSupported is returned. +func (r *DIDResolverRouter) Resolve(id did.DID, metadata *ResolveMetadata) (*did.Document, *DocumentMetadata, error) { + method := id.Method + didResolver, registered := r.resolvers.Load(method) + if !registered { + return nil, nil, ErrDIDMethodNotSupported + } + return didResolver.(DIDResolver).Resolve(id, metadata) +} + +// Register registers a DID resolver for the given DID method. +func (r *DIDResolverRouter) Register(method string, resolver DIDResolver) { + r.resolvers.Store(method, resolver) +} + +// IsFunctionalResolveError returns true if the given error indicates the DID or service not being found or invalid, +// e.g. because it is deactivated, referenced too deeply, etc. +func IsFunctionalResolveError(target error) bool { + return errors.Is(target, ErrNotFound) || + errors.Is(target, ErrDeactivated) || + errors.Is(target, ErrServiceNotFound) || + errors.Is(target, ErrNoActiveController) || + errors.Is(target, ErrServiceReferenceToDeep) || + errors.Is(target, did.InvalidDIDErr) || + errors.As(target, new(ServiceQueryError)) +} + +// GetDIDFromURL returns the DID from the given URL, stripping any query parameters, path segments and fragments. +func GetDIDFromURL(didURL string) (did.DID, error) { + parsed, err := did.ParseDIDURL(didURL) + if err != nil { + return did.DID{}, err + } + return parsed.WithoutURL(), nil +} + +// IsDeactivated returns true if the DID.Document has already been deactivated +func IsDeactivated(document did.Document) bool { + return len(document.Controller) == 0 && len(document.CapabilityInvocation) == 0 +} diff --git a/vdr/resolver/did_test.go b/vdr/resolver/did_test.go index d5c96588ff..5e026971a0 100644 --- a/vdr/resolver/did_test.go +++ b/vdr/resolver/did_test.go @@ -1,8 +1,16 @@ package resolver import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "errors" + "github.com/nuts-foundation/go-did" + "github.com/nuts-foundation/go-did/did" "github.com/nuts-foundation/nuts-node/crypto/hash" + "github.com/nuts-foundation/nuts-node/vdr/types" "github.com/stretchr/testify/assert" + "go.uber.org/mock/gomock" "io" "reflect" "testing" @@ -68,3 +76,94 @@ func Test_deactivatedError_Is(t *testing.T) { assert.ErrorIs(t, ErrNoActiveController, ErrDeactivated) assert.NotErrorIs(t, io.EOF, ErrDeactivated) } + +func newDidDoc() did.Document { + privateKey, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + id := did.MustParseDID("did:example:sakjsakldjsakld") + keyID := id + keyID.Fragment = "key-1" + vm, _ := did.NewVerificationMethod(keyID, ssi.JsonWebKey2020, id, privateKey.Public()) + doc := did.Document{ + ID: id, + } + doc.AddAssertionMethod(vm) + return doc +} + +func TestDIDResolverRouter_Resolve(t *testing.T) { + doc := newDidDoc() + t.Run("ok, 1 resolver", func(t *testing.T) { + ctrl := gomock.NewController(t) + resolver := types.NewMockDIDResolver(ctrl) + resolver.EXPECT().Resolve(doc.ID, gomock.Any()).Return(&doc, nil, nil) + router := &DIDResolverRouter{} + router.Register(doc.ID.Method, resolver) + + actual, _, err := router.Resolve(doc.ID, nil) + assert.NoError(t, err) + assert.Equal(t, &doc, actual) + }) + t.Run("ok, 2 resolvers", func(t *testing.T) { + ctrl := gomock.NewController(t) + otherResolver := types.NewMockDIDResolver(ctrl) + resolver := types.NewMockDIDResolver(ctrl) + resolver.EXPECT().Resolve(doc.ID, gomock.Any()).Return(&doc, nil, nil) + router := &DIDResolverRouter{} + router.Register(doc.ID.Method, resolver) + router.Register("test2", otherResolver) + + actual, _, err := router.Resolve(doc.ID, nil) + assert.NoError(t, err) + assert.Equal(t, &doc, actual) + }) + t.Run("error - resolver not found", func(t *testing.T) { + ctrl := gomock.NewController(t) + otherResolver := types.NewMockDIDResolver(ctrl) + router := &DIDResolverRouter{} + router.Register("other", otherResolver) + + actual, _, err := router.Resolve(doc.ID, nil) + assert.EqualError(t, err, "DID method not supported") + assert.Nil(t, actual) + }) +} + +func TestIsFunctionalResolveError(t *testing.T) { + assert.True(t, IsFunctionalResolveError(ErrNotFound)) + assert.True(t, IsFunctionalResolveError(ErrDeactivated)) + assert.True(t, IsFunctionalResolveError(ErrServiceNotFound)) + assert.True(t, IsFunctionalResolveError(ErrServiceReferenceToDeep)) + assert.True(t, IsFunctionalResolveError(ErrNoActiveController)) + assert.True(t, IsFunctionalResolveError(did.InvalidDIDErr)) + assert.True(t, IsFunctionalResolveError(ServiceQueryError{Err: errors.New("oops")})) + + assert.False(t, IsFunctionalResolveError(errors.New("some error"))) + assert.False(t, IsFunctionalResolveError(ErrDuplicateService)) +} + +func TestGetDIDFromURL(t *testing.T) { + t.Run("just it", func(t *testing.T) { + actual, err := GetDIDFromURL("did:nuts:abc") + assert.NoError(t, err) + assert.Equal(t, "did:nuts:abc", actual.String()) + }) + t.Run("with path", func(t *testing.T) { + actual, err := GetDIDFromURL("did:nuts:abc/serviceEndpoint") + assert.NoError(t, err) + assert.Equal(t, "did:nuts:abc", actual.String()) + }) + t.Run("with fragment", func(t *testing.T) { + actual, err := GetDIDFromURL("did:nuts:abc#key-1") + assert.NoError(t, err) + assert.Equal(t, "did:nuts:abc", actual.String()) + }) + t.Run("with params", func(t *testing.T) { + actual, err := GetDIDFromURL("did:nuts:abc?foo=bar") + assert.NoError(t, err) + assert.Equal(t, "did:nuts:abc", actual.String()) + }) + t.Run("invalid DID", func(t *testing.T) { + _, err := GetDIDFromURL("https://example.com") + assert.Error(t, err) + }) +} diff --git a/vdr/resolver/key.go b/vdr/resolver/key.go index 9fcaab40d7..dfed19d4cc 100644 --- a/vdr/resolver/key.go +++ b/vdr/resolver/key.go @@ -7,8 +7,6 @@ import ( "github.com/nuts-foundation/go-did" "github.com/nuts-foundation/go-did/did" "github.com/nuts-foundation/nuts-node/crypto/hash" - "github.com/nuts-foundation/nuts-node/vdr/didservice" - "github.com/nuts-foundation/nuts-node/vdr/types" "time" ) @@ -16,7 +14,7 @@ import ( var ErrKeyNotFound = errors.New("key not found in DID document") // NutsSigningKeyType defines the verification method relationship type for signing keys in Nuts DID Documents. -const NutsSigningKeyType = types.AssertionMethod +const NutsSigningKeyType = AssertionMethod // KeyResolver is the interface for resolving keys. // This can be used for checking if a signing key is valid at a point in time or to just find a valid key for signing. @@ -24,11 +22,11 @@ type KeyResolver interface { // ResolveKeyByID looks up a specific key of the given RelationType and returns it as crypto.PublicKey. // If multiple keys are valid, the first one is returned. // An ErrKeyNotFound is returned when no key (of the specified type) is found. - ResolveKeyByID(keyID string, validAt *time.Time, relationType types.RelationType) (crypto.PublicKey, error) + ResolveKeyByID(keyID string, validAt *time.Time, relationType RelationType) (crypto.PublicKey, error) // ResolveKey looks for a valid key of the given RelationType for the given DID, and returns its ID and the key itself. // If multiple keys are valid, the first one is returned. // An ErrKeyNotFound is returned when no key (of the specified type) is found. - ResolveKey(id did.DID, validAt *time.Time, relationType types.RelationType) (ssi.URI, crypto.PublicKey, error) + ResolveKey(id did.DID, validAt *time.Time, relationType RelationType) (ssi.URI, crypto.PublicKey, error) } // NutsKeyResolver is the interface for resolving keys from Nuts DID Documents, @@ -48,8 +46,8 @@ type DIDKeyResolver struct { Resolver DIDResolver } -func (r DIDKeyResolver) ResolveKeyByID(keyID string, validAt *time.Time, relationType types.RelationType) (crypto.PublicKey, error) { - holder, err := didservice.GetDIDFromURL(keyID) +func (r DIDKeyResolver) ResolveKeyByID(keyID string, validAt *time.Time, relationType RelationType) (crypto.PublicKey, error) { + holder, err := GetDIDFromURL(keyID) if err != nil { return nil, fmt.Errorf("invalid key ID (id=%s): %w", keyID, err) } @@ -71,7 +69,7 @@ func (r DIDKeyResolver) ResolveKeyByID(keyID string, validAt *time.Time, relatio return nil, ErrKeyNotFound } -func (r DIDKeyResolver) ResolveKey(id did.DID, validAt *time.Time, relationType types.RelationType) (ssi.URI, crypto.PublicKey, error) { +func (r DIDKeyResolver) ResolveKey(id did.DID, validAt *time.Time, relationType RelationType) (ssi.URI, crypto.PublicKey, error) { doc, _, err := r.Resolver.Resolve(id, &ResolveMetadata{ ResolveTime: validAt, }) @@ -92,19 +90,31 @@ func (r DIDKeyResolver) ResolveKey(id did.DID, validAt *time.Time, relationType return keys[0].ID.URI(), publicKey, nil } -func resolveRelationships(doc *did.Document, relationType types.RelationType) (relationships did.VerificationRelationships, err error) { +func resolveRelationships(doc *did.Document, relationType RelationType) (relationships did.VerificationRelationships, err error) { switch relationType { - case types.Authentication: + case Authentication: return doc.Authentication, nil - case types.AssertionMethod: + case AssertionMethod: return doc.AssertionMethod, nil - case types.KeyAgreement: + case KeyAgreement: return doc.KeyAgreement, nil - case types.CapabilityInvocation: + case CapabilityInvocation: return doc.CapabilityInvocation, nil - case types.CapabilityDelegation: + case CapabilityDelegation: return doc.CapabilityDelegation, nil default: return nil, fmt.Errorf("unable to locate RelationType %v", relationType) } } + +// RelationType is the type that contains the different possible relationships between a DID Document and a VerificationMethod +// They are defined in the DID spec: https://www.w3.org/TR/did-core/#verification-relationships +type RelationType uint + +const ( + Authentication RelationType = iota + AssertionMethod RelationType = iota + KeyAgreement RelationType = iota + CapabilityInvocation RelationType = iota + CapabilityDelegation RelationType = iota +) diff --git a/vdr/resolver/key_test.go b/vdr/resolver/key_test.go index 66dd69b0d3..b9293feb98 100644 --- a/vdr/resolver/key_test.go +++ b/vdr/resolver/key_test.go @@ -1,38 +1,90 @@ package resolver import ( + "github.com/nuts-foundation/go-did/did" + "github.com/nuts-foundation/nuts-node/vdr/types" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" "testing" ) -func TestKeyUsage_Is(t *testing.T) { - types := []DIDKeyFlags{AssertionMethodUsage, AuthenticationUsage, CapabilityDelegationUsage, CapabilityInvocationUsage, KeyAgreementUsage} - t.Run("one usage", func(t *testing.T) { - for _, usage := range types { - for _, other := range types { - if usage == other { - assert.True(t, usage.Is(other)) - assert.True(t, other.Is(usage)) // assert symmetry - } else { - assert.False(t, usage.Is(other)) - assert.False(t, other.Is(usage)) // assert symmetry - } - } - } - }) - t.Run("multiple usage", func(t *testing.T) { - value := AssertionMethodUsage | CapabilityDelegationUsage | KeyAgreementUsage - for _, other := range types { - switch other { - case AssertionMethodUsage: - fallthrough - case CapabilityDelegationUsage: - fallthrough - case KeyAgreementUsage: - assert.True(t, value.Is(other)) - default: - assert.False(t, value.Is(other)) - } - } +func TestKeyResolver_ResolveKey(t *testing.T) { + ctrl := gomock.NewController(t) + resolver := types.NewMockDIDResolver(ctrl) + keyResolver := DIDKeyResolver{Resolver: resolver} + + doc := newDidDoc() + resolver.EXPECT().Resolve(doc.ID, gomock.Any()).AnyTimes().Return(&doc, nil, nil) + + t.Run("ok - it finds the key", func(t *testing.T) { + keyId, key, err := keyResolver.ResolveKey(doc.ID, nil, AssertionMethod) + require.NoError(t, err) + assert.Equal(t, doc.VerificationMethod[0].ID.URI(), keyId) + assert.NotNil(t, key) + }) + + t.Run("error - document not found", func(t *testing.T) { + unknownDID := did.MustParseDID("did:example:123") + resolver.EXPECT().Resolve(unknownDID, gomock.Any()).Return(nil, nil, ErrNotFound) + keyId, key, err := keyResolver.ResolveKey(unknownDID, nil, AssertionMethod) + assert.EqualError(t, err, "unable to find the DID document") + assert.Empty(t, keyId) + assert.Nil(t, key) + }) + + t.Run("error - key not found", func(t *testing.T) { + keyId, key, err := keyResolver.ResolveKey(did.MustParseDIDURL(doc.ID.String()), nil, CapabilityDelegation) + assert.EqualError(t, err, "key not found in DID document") + assert.Empty(t, keyId) + assert.Nil(t, key) + }) + + t.Run("error - unknown relationship type", func(t *testing.T) { + keyId, key, err := keyResolver.ResolveKey(doc.ID, nil, 1000) + assert.EqualError(t, err, "unable to locate RelationType 1000") + assert.Empty(t, keyId) + assert.Nil(t, key) + }) +} + +func TestKeyResolver_ResolveKeyByID(t *testing.T) { + ctrl := gomock.NewController(t) + resolver := types.NewMockDIDResolver(ctrl) + keyResolver := DIDKeyResolver{Resolver: resolver} + doc := newDidDoc() + resolver.EXPECT().Resolve(doc.ID, gomock.Any()).AnyTimes().Return(&doc, nil, nil) + keyID := doc.VerificationMethod[0].ID + + t.Run("ok - it finds the key", func(t *testing.T) { + key, err := keyResolver.ResolveKeyByID(keyID.String(), nil, AssertionMethod) + assert.NoError(t, err) + assert.NotNil(t, key) + }) + + t.Run("error - invalid key ID", func(t *testing.T) { + key, err := keyResolver.ResolveKeyByID("abcdef", nil, AssertionMethod) + assert.EqualError(t, err, "invalid key ID (id=abcdef): invalid DID") + assert.Nil(t, key) + }) + + t.Run("error - document not found", func(t *testing.T) { + unknownDID := did.MustParseDIDURL("did:example:123") + resolver.EXPECT().Resolve(unknownDID, gomock.Any()).Return(nil, nil, ErrNotFound) + key, err := keyResolver.ResolveKeyByID(unknownDID.String()+"#456", nil, AssertionMethod) + assert.EqualError(t, err, "unable to find the DID document") + assert.Nil(t, key) + }) + + t.Run("error - key not found", func(t *testing.T) { + key, err := keyResolver.ResolveKeyByID(did.MustParseDIDURL(doc.ID.String()+"#123").String(), nil, AssertionMethod) + assert.EqualError(t, err, "key not found in DID document") + assert.Nil(t, key) + }) + + t.Run("error - unknown relationship type", func(t *testing.T) { + key, err := keyResolver.ResolveKeyByID(keyID.String(), nil, 1000) + assert.EqualError(t, err, "unable to locate RelationType 1000") + assert.Nil(t, key) }) } diff --git a/vdr/resolver/service.go b/vdr/resolver/service.go index 2ca809de5c..f3e386b38f 100644 --- a/vdr/resolver/service.go +++ b/vdr/resolver/service.go @@ -5,7 +5,6 @@ import ( "fmt" "github.com/nuts-foundation/go-did" "github.com/nuts-foundation/go-did/did" - "github.com/nuts-foundation/nuts-node/vdr/didservice" "strings" ) @@ -49,7 +48,7 @@ func (s DIDServiceResolver) ResolveEx(endpoint ssi.URI, depth int, maxDepth int, return did.Service{}, ErrServiceReferenceToDeep } - referencedDID, err := didservice.GetDIDFromURL(endpoint.String()) + referencedDID, err := GetDIDFromURL(endpoint.String()) if err != nil { // Shouldn't happen, because only DID URLs are passed? return did.Service{}, err diff --git a/vdr/resolver/service_test.go b/vdr/resolver/service_test.go index 7e9a487b98..8fb7d424b4 100644 --- a/vdr/resolver/service_test.go +++ b/vdr/resolver/service_test.go @@ -1,9 +1,13 @@ package resolver import ( + "fmt" ssi "github.com/nuts-foundation/go-did" "github.com/nuts-foundation/go-did/did" + "github.com/nuts-foundation/nuts-node/crypto/hash" + "github.com/nuts-foundation/nuts-node/vdr/types" "github.com/stretchr/testify/assert" + "go.uber.org/mock/gomock" "testing" ) @@ -52,3 +56,111 @@ func Test_ValidateServiceReference(t *testing.T) { assert.ErrorContains(t, err, "endpoint URI with query parameter other than type") }) } + +func TestServiceResolver_Resolve(t *testing.T) { + meta := &DocumentMetadata{Hash: hash.EmptyHash()} + + didA, _ := did.ParseDID("did:nuts:A") + didB, _ := did.ParseDID("did:nuts:B") + + serviceID := ssi.MustParseURI(fmt.Sprintf("%s#1", didA.String())) + docA := did.Document{ + Context: []ssi.URI{did.DIDContextV1URI()}, + ID: *didA, + Service: []did.Service{{ + ID: serviceID, + Type: "hello", + ServiceEndpoint: "http://hello", + }}, + } + docB := did.Document{ + Context: []ssi.URI{did.DIDContextV1URI()}, + ID: *didA, + Service: []did.Service{ + { + Type: "simple", + ServiceEndpoint: "http://world", + }, + { + Type: "cyclic-ref", + ServiceEndpoint: didB.String() + "/serviceEndpoint?type=cyclic-ref", + }, + { + Type: "invalid-ref", + ServiceEndpoint: didB.String() + "?type=invalid-ref", + }, + { + Type: "external", + ServiceEndpoint: MakeServiceReference(docA.ID, "hello").String(), + }, + }, + } + + t.Run("ok", func(t *testing.T) { + ctrl := gomock.NewController(t) + resolver := types.NewMockDIDResolver(ctrl) + + resolver.EXPECT().Resolve(*didB, nil).MinTimes(1).Return(&docB, meta, nil) + + actual, err := DIDServiceResolver{Resolver: resolver}.Resolve(MakeServiceReference(*didB, "simple"), DefaultMaxServiceReferenceDepth) + + assert.NoError(t, err) + assert.Equal(t, docB.Service[0], actual) + }) + t.Run("ok - external", func(t *testing.T) { + ctrl := gomock.NewController(t) + resolver := types.NewMockDIDResolver(ctrl) + + resolver.EXPECT().Resolve(*didB, nil).MinTimes(1).Return(&docB, meta, nil) + resolver.EXPECT().Resolve(*didA, nil).MinTimes(1).Return(&docA, meta, nil) + + actual, err := DIDServiceResolver{Resolver: resolver}.Resolve(MakeServiceReference(*didB, "external"), DefaultMaxServiceReferenceDepth) + + assert.NoError(t, err) + assert.Equal(t, docA.Service[0], actual) + }) + t.Run("error - cyclic reference (yields refs too deep)", func(t *testing.T) { + ctrl := gomock.NewController(t) + resolver := types.NewMockDIDResolver(ctrl) + + resolver.EXPECT().Resolve(*didB, nil).MinTimes(1).Return(&docB, meta, nil) + + actual, err := DIDServiceResolver{Resolver: resolver}.Resolve(MakeServiceReference(*didB, "cyclic-ref"), DefaultMaxServiceReferenceDepth) + + assert.EqualError(t, err, "service references are nested to deeply before resolving to a non-reference") + assert.Empty(t, actual) + }) + t.Run("error - invalid ref", func(t *testing.T) { + ctrl := gomock.NewController(t) + resolver := types.NewMockDIDResolver(ctrl) + + resolver.EXPECT().Resolve(*didB, nil).MinTimes(1).Return(&docB, meta, nil) + + actual, err := DIDServiceResolver{Resolver: resolver}.Resolve(MakeServiceReference(*didB, "invalid-ref"), DefaultMaxServiceReferenceDepth) + + assert.EqualError(t, err, "DID service query invalid: endpoint URI path must be /serviceEndpoint") + assert.Empty(t, actual) + }) + t.Run("error - service not found", func(t *testing.T) { + ctrl := gomock.NewController(t) + resolver := types.NewMockDIDResolver(ctrl) + + resolver.EXPECT().Resolve(*didB, nil).MinTimes(1).Return(&docB, meta, nil) + + actual, err := DIDServiceResolver{Resolver: resolver}.Resolve(MakeServiceReference(*didB, "non-existent"), DefaultMaxServiceReferenceDepth) + + assert.EqualError(t, err, "service not found in DID Document") + assert.Empty(t, actual) + }) + t.Run("error - DID not found", func(t *testing.T) { + ctrl := gomock.NewController(t) + resolver := types.NewMockDIDResolver(ctrl) + + resolver.EXPECT().Resolve(*didB, nil).MinTimes(1).Return(nil, nil, ErrNotFound) + + actual, err := DIDServiceResolver{Resolver: resolver}.Resolve(MakeServiceReference(*didB, "non-existent"), DefaultMaxServiceReferenceDepth) + + assert.EqualError(t, err, "unable to find the DID document") + assert.Empty(t, actual) + }) +} diff --git a/vdr/types/interface.go b/vdr/types/interface.go index 516734b9b6..2b35cb7e63 100644 --- a/vdr/types/interface.go +++ b/vdr/types/interface.go @@ -20,24 +20,12 @@ package types import ( "context" - "errors" "github.com/nuts-foundation/go-did/did" crypto2 "github.com/nuts-foundation/nuts-node/crypto" "github.com/nuts-foundation/nuts-node/vdr/resolver" "net/url" ) -// Predicate is an interface for abstracting search options on DID documents -type Predicate interface { - // Match returns true if the given DID Document passes the predicate condition - Match(did.Document, resolver.DocumentMetadata) bool -} - -// DocFinder is the interface that groups all methods for finding DID documents based on search conditions -type DocFinder interface { - Find(...Predicate) ([]did.Document, error) -} - // DocCreator is the interface that wraps the Create method type DocCreator interface { // Create creates a new DID document and returns it. @@ -62,9 +50,6 @@ type DocUpdater interface { Update(ctx context.Context, id did.DID, next did.Document) error } -// DocIterator is the function type for iterating over the all current DID Documents in the store -type DocIterator func(doc did.Document, metadata resolver.DocumentMetadata) error - // VDR defines the public end facing methods for the Verifiable Data Registry. type VDR interface { DocumentOwner @@ -115,18 +100,3 @@ type DocManipulator interface { // It returns an ErrDIDNotManagedByThisNode if the DID document is not managed by this node. AddVerificationMethod(ctx context.Context, id did.DID, keyUsage DIDKeyFlags) (*did.VerificationMethod, error) } - -// ErrDIDMethodNotSupported is returned when a DID method is not supported by the DID resolver -var ErrDIDMethodNotSupported = errors.New("DID method not supported") - -// RelationType is the type that contains the different possible relationships between a DID Document and a VerificationMethod -// They are defined in the DID spec: https://www.w3.org/TR/did-core/#verification-relationships -type RelationType uint - -const ( - Authentication RelationType = iota - AssertionMethod RelationType = iota - KeyAgreement RelationType = iota - CapabilityInvocation RelationType = iota - CapabilityDelegation RelationType = iota -) diff --git a/vdr/types/key.go b/vdr/types/key.go deleted file mode 100644 index ab1254f4c2..0000000000 --- a/vdr/types/key.go +++ /dev/null @@ -1 +0,0 @@ -package types diff --git a/vdr/types/management_test.go b/vdr/types/management_test.go new file mode 100644 index 0000000000..21b7b8e37e --- /dev/null +++ b/vdr/types/management_test.go @@ -0,0 +1,38 @@ +package types + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestKeyUsage_Is(t *testing.T) { + types := []DIDKeyFlags{AssertionMethodUsage, AuthenticationUsage, CapabilityDelegationUsage, CapabilityInvocationUsage, KeyAgreementUsage} + t.Run("one usage", func(t *testing.T) { + for _, usage := range types { + for _, other := range types { + if usage == other { + assert.True(t, usage.Is(other)) + assert.True(t, other.Is(usage)) // assert symmetry + } else { + assert.False(t, usage.Is(other)) + assert.False(t, other.Is(usage)) // assert symmetry + } + } + } + }) + t.Run("multiple usage", func(t *testing.T) { + value := AssertionMethodUsage | CapabilityDelegationUsage | KeyAgreementUsage + for _, other := range types { + switch other { + case AssertionMethodUsage: + fallthrough + case CapabilityDelegationUsage: + fallthrough + case KeyAgreementUsage: + assert.True(t, value.Is(other)) + default: + assert.False(t, value.Is(other)) + } + } + }) +} diff --git a/vdr/types/mock.go b/vdr/types/mock.go index af42fbe0be..d9ed3dd7b2 100644 --- a/vdr/types/mock.go +++ b/vdr/types/mock.go @@ -11,6 +11,7 @@ package types import ( context "context" crypto "crypto" + "github.com/nuts-foundation/nuts-node/vdr/management" "github.com/nuts-foundation/nuts-node/vdr/resolver" url "net/url" reflect "reflect" @@ -123,7 +124,7 @@ func (m *MockDocFinder) EXPECT() *MockDocFinderMockRecorder { } // Find mocks base method. -func (m *MockDocFinder) Find(arg0 ...Predicate) ([]did.Document, error) { +func (m *MockDocFinder) Find(arg0 ...management.Predicate) ([]did.Document, error) { m.ctrl.T.Helper() varargs := []any{} for _, a := range arg0 { @@ -278,7 +279,7 @@ func (m *MockKeyResolver) EXPECT() *MockKeyResolverMockRecorder { } // ResolveKey mocks base method. -func (m *MockKeyResolver) ResolveKey(id did.DID, validAt *time.Time, relationType RelationType) (ssi.URI, crypto.PublicKey, error) { +func (m *MockKeyResolver) ResolveKey(id did.DID, validAt *time.Time, relationType resolver.RelationType) (ssi.URI, crypto.PublicKey, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ResolveKey", id, validAt, relationType) ret0, _ := ret[0].(ssi.URI) @@ -294,7 +295,7 @@ func (mr *MockKeyResolverMockRecorder) ResolveKey(id, validAt, relationType any) } // ResolveKeyByID mocks base method. -func (m *MockKeyResolver) ResolveKeyByID(keyID string, validAt *time.Time, relationType RelationType) (crypto.PublicKey, error) { +func (m *MockKeyResolver) ResolveKeyByID(keyID string, validAt *time.Time, relationType resolver.RelationType) (crypto.PublicKey, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ResolveKeyByID", keyID, validAt, relationType) ret0, _ := ret[0].(crypto.PublicKey) diff --git a/vdr/vdr.go b/vdr/vdr.go index adcf0a3517..a64dde54ad 100644 --- a/vdr/vdr.go +++ b/vdr/vdr.go @@ -40,9 +40,9 @@ import ( "github.com/nuts-foundation/nuts-node/vdr/didjwk" "github.com/nuts-foundation/nuts-node/vdr/didnuts" didnutsStore "github.com/nuts-foundation/nuts-node/vdr/didnuts/didstore" - "github.com/nuts-foundation/nuts-node/vdr/didservice" "github.com/nuts-foundation/nuts-node/vdr/didweb" "github.com/nuts-foundation/nuts-node/vdr/log" + "github.com/nuts-foundation/nuts-node/vdr/management" "github.com/nuts-foundation/nuts-node/vdr/resolver" "github.com/nuts-foundation/nuts-node/vdr/types" "net/url" @@ -63,7 +63,7 @@ type VDR struct { network network.Transactions networkAmbassador didnuts.Ambassador didDocCreator types.DocCreator - didResolver *didservice.DIDResolverRouter + didResolver *resolver.DIDResolverRouter serviceResolver resolver.ServiceResolver documentOwner types.DocumentOwner keyStore crypto.KeyStore @@ -115,7 +115,7 @@ func (r *VDR) Resolver() resolver.DIDResolver { // NewVDR creates a new VDR with provided params func NewVDR(cryptoClient crypto.KeyStore, networkClient network.Transactions, didStore didnutsStore.Store, eventManager events.Event) *VDR { - didResolver := &didservice.DIDResolverRouter{} + didResolver := &resolver.DIDResolverRouter{} return &VDR{ network: networkClient, eventManager: eventManager, @@ -191,7 +191,7 @@ func (r *VDR) ListOwned(ctx context.Context) ([]did.DID, error) { // newOwnConflictedDocIterator accepts two counters and returns a new DocIterator that counts the total number of // conflicted documents, both total and owned by this node. -func (r *VDR) newOwnConflictedDocIterator(totalCount, ownedCount *int) types.DocIterator { +func (r *VDR) newOwnConflictedDocIterator(totalCount, ownedCount *int) management.DocIterator { return func(doc did.Document, metadata resolver.DocumentMetadata) error { *totalCount++ controllers, err := didnuts.ResolveControllers(r.store, doc, nil) @@ -329,7 +329,7 @@ func (r *VDR) Update(ctx context.Context, id did.DID, next did.Document) error { if err != nil { return fmt.Errorf("update DID document: %w", err) } - if didservice.IsDeactivated(*currentDIDDocument) { + if resolver.IsDeactivated(*currentDIDDocument) { return fmt.Errorf("update DID document: %w", resolver.ErrDeactivated) } diff --git a/vdr/vdr_test.go b/vdr/vdr_test.go index 62253b765b..a200073f49 100644 --- a/vdr/vdr_test.go +++ b/vdr/vdr_test.go @@ -31,7 +31,6 @@ import ( "github.com/nuts-foundation/nuts-node/core" "github.com/nuts-foundation/nuts-node/vdr/didnuts" "github.com/nuts-foundation/nuts-node/vdr/didnuts/didstore" - "github.com/nuts-foundation/nuts-node/vdr/didservice" "github.com/nuts-foundation/nuts-node/vdr/resolver" "io" "net/http" @@ -72,7 +71,7 @@ func newVDRTestCtx(t *testing.T) vdrTestCtx { mockNetwork := network.NewMockTransactions(ctrl) mockKeyStore := crypto.NewMockKeyStore(ctrl) mockDocumentOwner := types.NewMockDocumentOwner(ctrl) - resolverRouter := &didservice.DIDResolverRouter{} + resolverRouter := &resolver.DIDResolverRouter{} vdr := VDR{ store: mockStore, network: mockNetwork,