Skip to content

Commit

Permalink
VDR: Produce did:web documents (#2631)
Browse files Browse the repository at this point in the history
  • Loading branch information
reinkrul authored Dec 6, 2023
1 parent 47df193 commit 199fbc6
Show file tree
Hide file tree
Showing 40 changed files with 1,380 additions and 330 deletions.
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ The following options can be configured on the server:
http.default.auth.type Whether to enable authentication for the default interface, specify 'token_v2' for bearer token mode or 'token' for legacy bearer token mode.
http.default.cors.origin [] When set, enables CORS from the specified origins on the default HTTP interface.
**JSONLD**
jsonld.contexts.localmapping [https://w3c-ccg.github.io/lds-jws2020/contexts/lds-jws2020-v1.json=assets/contexts/lds-jws2020-v1.ldjson,https://schema.org=assets/contexts/schema-org-v13.ldjson,https://nuts.nl/credentials/v1=assets/contexts/nuts.ldjson,https://www.w3.org/2018/credentials/v1=assets/contexts/w3c-credentials-v1.ldjson] This setting allows mapping external URLs to local files for e.g. preventing external dependencies. These mappings have precedence over those in remoteallowlist.
jsonld.contexts.localmapping [https://nuts.nl/credentials/v1=assets/contexts/nuts.ldjson,https://www.w3.org/2018/credentials/v1=assets/contexts/w3c-credentials-v1.ldjson,https://w3c-ccg.github.io/lds-jws2020/contexts/lds-jws2020-v1.json=assets/contexts/lds-jws2020-v1.ldjson,https://schema.org=assets/contexts/schema-org-v13.ldjson] This setting allows mapping external URLs to local files for e.g. preventing external dependencies. These mappings have precedence over those in remoteallowlist.
jsonld.contexts.remoteallowlist [https://schema.org,https://www.w3.org/2018/credentials/v1,https://w3c-ccg.github.io/lds-jws2020/contexts/lds-jws2020-v1.json] In strict mode, fetching external JSON-LD contexts is not allowed except for context-URLs listed here.
**Network**
network.bootstrapnodes [] List of bootstrap nodes ('<host>:<port>') which the node initially connect to.
Expand Down
13 changes: 5 additions & 8 deletions auth/api/iam/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -283,8 +283,7 @@ func (r Wrapper) HandleAuthorizeRequest(ctx context.Context, request HandleAutho

// OAuthAuthorizationServerMetadata returns the Authorization Server's metadata
func (r Wrapper) OAuthAuthorizationServerMetadata(ctx context.Context, request OAuthAuthorizationServerMetadataRequestObject) (OAuthAuthorizationServerMetadataResponseObject, error) {
// TODO: must be web DID once web DID creation and DB are implemented
ownDID := idToNutsDID(request.Id)
ownDID := r.idToDID(request.Id)
owned, err := r.vdr.IsOwner(ctx, ownDID)
if err != nil {
if resolver.IsFunctionalResolveError(err) {
Expand All @@ -302,17 +301,15 @@ func (r Wrapper) OAuthAuthorizationServerMetadata(ctx context.Context, request O
return OAuthAuthorizationServerMetadata200JSONResponse(authorizationServerMetadata(*identity)), nil
}

func (r Wrapper) GetWebDID(ctx context.Context, request GetWebDIDRequestObject) (GetWebDIDResponseObject, error) {
baseURL := *(r.auth.PublicURL().JoinPath(apiPath))
// TODO: must be web DID once web DID creation and DB are implemented
ownDID := idToNutsDID(request.Id)
func (r Wrapper) GetWebDID(_ context.Context, request GetWebDIDRequestObject) (GetWebDIDResponseObject, error) {
ownDID := r.idToDID(request.Id)

document, err := r.vdr.DeriveWebDIDDocument(ctx, baseURL, ownDID)
document, err := r.vdr.ResolveManaged(ownDID)
if err != nil {
if resolver.IsFunctionalResolveError(err) {
return GetWebDID404Response{}, nil
}
log.Logger().WithError(err).Errorf("Could not resolve Nuts DID: %s", ownDID.String())
log.Logger().WithError(err).Errorf("Could not resolve Web DID: %s", ownDID.String())
return nil, errors.New("unable to resolve DID")
}
return GetWebDID200JSONResponse(*document), nil
Expand Down
33 changes: 16 additions & 17 deletions auth/api/iam/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ import (
"testing"

"github.com/labstack/echo/v4"
ssi "github.com/nuts-foundation/go-did"
"github.com/nuts-foundation/go-did/did"
"github.com/nuts-foundation/go-did/vc"
"github.com/nuts-foundation/nuts-node/audit"
Expand All @@ -49,14 +48,15 @@ import (

var nutsDID = did.MustParseDID("did:nuts:123")
var webDID = did.MustParseDID("did:web:example.com:iam:123")
var webIDPart = "123"

func TestWrapper_OAuthAuthorizationServerMetadata(t *testing.T) {
t.Run("ok", func(t *testing.T) {
// 200
ctx := newTestClient(t)
ctx.vdr.EXPECT().IsOwner(nil, nutsDID).Return(true, nil)
ctx.vdr.EXPECT().IsOwner(nil, webDID).Return(true, nil)

res, err := ctx.client.OAuthAuthorizationServerMetadata(nil, OAuthAuthorizationServerMetadataRequestObject{Id: nutsDID.ID})
res, err := ctx.client.OAuthAuthorizationServerMetadata(nil, OAuthAuthorizationServerMetadataRequestObject{Id: webIDPart})

require.NoError(t, err)
assert.IsType(t, OAuthAuthorizationServerMetadata200JSONResponse{}, res)
Expand All @@ -65,9 +65,9 @@ func TestWrapper_OAuthAuthorizationServerMetadata(t *testing.T) {
t.Run("error - did not managed by this node", func(t *testing.T) {
//404
ctx := newTestClient(t)
ctx.vdr.EXPECT().IsOwner(nil, nutsDID)
ctx.vdr.EXPECT().IsOwner(nil, webDID)

res, err := ctx.client.OAuthAuthorizationServerMetadata(nil, OAuthAuthorizationServerMetadataRequestObject{Id: nutsDID.ID})
res, err := ctx.client.OAuthAuthorizationServerMetadata(nil, OAuthAuthorizationServerMetadataRequestObject{Id: webIDPart})

assert.Equal(t, 404, statusCodeFrom(err))
assert.EqualError(t, err, "authz server metadata: did not owned")
Expand All @@ -76,9 +76,9 @@ func TestWrapper_OAuthAuthorizationServerMetadata(t *testing.T) {
t.Run("error - did does not exist", func(t *testing.T) {
//404
ctx := newTestClient(t)
ctx.vdr.EXPECT().IsOwner(nil, nutsDID).Return(false, resolver.ErrNotFound)
ctx.vdr.EXPECT().IsOwner(nil, webDID).Return(false, resolver.ErrNotFound)

res, err := ctx.client.OAuthAuthorizationServerMetadata(nil, OAuthAuthorizationServerMetadataRequestObject{Id: nutsDID.ID})
res, err := ctx.client.OAuthAuthorizationServerMetadata(nil, OAuthAuthorizationServerMetadataRequestObject{Id: webIDPart})

assert.Equal(t, 404, statusCodeFrom(err))
assert.EqualError(t, err, "authz server metadata: unable to find the DID document")
Expand All @@ -87,9 +87,9 @@ func TestWrapper_OAuthAuthorizationServerMetadata(t *testing.T) {
t.Run("error - internal error 500", func(t *testing.T) {
//500
ctx := newTestClient(t)
ctx.vdr.EXPECT().IsOwner(nil, nutsDID).Return(false, errors.New("unknown error"))
ctx.vdr.EXPECT().IsOwner(nil, webDID).Return(false, errors.New("unknown error"))

res, err := ctx.client.OAuthAuthorizationServerMetadata(nil, OAuthAuthorizationServerMetadataRequestObject{Id: nutsDID.ID})
res, err := ctx.client.OAuthAuthorizationServerMetadata(nil, OAuthAuthorizationServerMetadataRequestObject{Id: webIDPart})

assert.Equal(t, 500, statusCodeFrom(err))
assert.EqualError(t, err, "authz server metadata: unknown error")
Expand All @@ -99,8 +99,7 @@ func TestWrapper_OAuthAuthorizationServerMetadata(t *testing.T) {

func TestWrapper_GetWebDID(t *testing.T) {
webDID := did.MustParseDID("did:web:example.com:iam:123")
publicURL := ssi.MustParseURI("https://example.com").URL
webDIDBaseURL := publicURL.JoinPath("/iam")
id := "123"
ctx := audit.TestContext()
expectedWebDIDDoc := did.Document{
ID: webDID,
Expand All @@ -111,27 +110,27 @@ func TestWrapper_GetWebDID(t *testing.T) {

t.Run("ok", func(t *testing.T) {
test := newTestClient(t)
test.vdr.EXPECT().DeriveWebDIDDocument(gomock.Any(), *webDIDBaseURL, nutsDID).Return(&expectedWebDIDDoc, nil)
test.vdr.EXPECT().ResolveManaged(webDID).Return(&expectedWebDIDDoc, nil)

response, err := test.client.GetWebDID(ctx, GetWebDIDRequestObject{nutsDID.ID})
response, err := test.client.GetWebDID(ctx, GetWebDIDRequestObject{id})

assert.NoError(t, err)
assert.Equal(t, expectedWebDIDDoc, did.Document(response.(GetWebDID200JSONResponse)))
})
t.Run("unknown DID", func(t *testing.T) {
test := newTestClient(t)
test.vdr.EXPECT().DeriveWebDIDDocument(ctx, *webDIDBaseURL, nutsDID).Return(nil, resolver.ErrNotFound)
test.vdr.EXPECT().ResolveManaged(webDID).Return(nil, resolver.ErrNotFound)

response, err := test.client.GetWebDID(ctx, GetWebDIDRequestObject{nutsDID.ID})
response, err := test.client.GetWebDID(ctx, GetWebDIDRequestObject{id})

assert.NoError(t, err)
assert.IsType(t, GetWebDID404Response{}, response)
})
t.Run("other error", func(t *testing.T) {
test := newTestClient(t)
test.vdr.EXPECT().DeriveWebDIDDocument(gomock.Any(), *webDIDBaseURL, nutsDID).Return(nil, errors.New("failed"))
test.vdr.EXPECT().ResolveManaged(webDID).Return(nil, errors.New("failed"))

response, err := test.client.GetWebDID(ctx, GetWebDIDRequestObject{nutsDID.ID})
response, err := test.client.GetWebDID(ctx, GetWebDIDRequestObject{id})

assert.EqualError(t, err, "unable to resolve DID")
assert.Nil(t, response)
Expand Down
23 changes: 12 additions & 11 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,9 @@ import (
"context"
"errors"
"fmt"
"github.com/nuts-foundation/nuts-node/discovery"
"github.com/nuts-foundation/nuts-node/vdr/resolver"

"github.com/nuts-foundation/nuts-node/golden_hammer"
goldenHammerCmd "github.com/nuts-foundation/nuts-node/golden_hammer/cmd"
"github.com/nuts-foundation/nuts-node/vdr/didnuts"
"github.com/nuts-foundation/nuts-node/vdr/didnuts/didstore"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"io"
"os"
"runtime/pprof"
Expand All @@ -47,9 +43,12 @@ import (
"github.com/nuts-foundation/nuts-node/didman"
didmanAPI "github.com/nuts-foundation/nuts-node/didman/api/v1"
didmanCmd "github.com/nuts-foundation/nuts-node/didman/cmd"
"github.com/nuts-foundation/nuts-node/discovery"
discoveryCmd "github.com/nuts-foundation/nuts-node/discovery/cmd"
"github.com/nuts-foundation/nuts-node/events"
eventsCmd "github.com/nuts-foundation/nuts-node/events/cmd"
"github.com/nuts-foundation/nuts-node/golden_hammer"
goldenHammerCmd "github.com/nuts-foundation/nuts-node/golden_hammer/cmd"
httpEngine "github.com/nuts-foundation/nuts-node/http"
httpCmd "github.com/nuts-foundation/nuts-node/http/cmd"
"github.com/nuts-foundation/nuts-node/jsonld"
Expand All @@ -65,10 +64,11 @@ import (
vcrCmd "github.com/nuts-foundation/nuts-node/vcr/cmd"
"github.com/nuts-foundation/nuts-node/vdr"
vdrAPI "github.com/nuts-foundation/nuts-node/vdr/api/v1"
vdrAPIv2 "github.com/nuts-foundation/nuts-node/vdr/api/v2"
vdrCmd "github.com/nuts-foundation/nuts-node/vdr/cmd"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/nuts-foundation/nuts-node/vdr/didnuts"
"github.com/nuts-foundation/nuts-node/vdr/didnuts/didstore"
"github.com/nuts-foundation/nuts-node/vdr/resolver"
)

var stdOutWriter io.Writer = os.Stdout
Expand Down Expand Up @@ -191,7 +191,7 @@ func CreateSystem(shutdownCallback context.CancelFunc) *core.System {
didStore := didstore.New(storageInstance.GetProvider(vdr.ModuleName))
eventManager := events.NewManager()
networkInstance := network.NewNetworkInstance(network.DefaultConfig(), didStore, cryptoInstance, eventManager, storageInstance.GetProvider(network.ModuleName), pkiInstance)
vdrInstance := vdr.NewVDR(cryptoInstance, networkInstance, didStore, eventManager)
vdrInstance := vdr.NewVDR(cryptoInstance, networkInstance, didStore, eventManager, storageInstance)
credentialInstance := vcr.NewVCRInstance(cryptoInstance, vdrInstance, networkInstance, jsonld, eventManager, storageInstance, pkiInstance)
didmanInstance := didman.NewDidmanInstance(vdrInstance, credentialInstance, jsonld)
discoveryInstance := discovery.New(storageInstance, credentialInstance)
Expand All @@ -209,6 +209,7 @@ func CreateSystem(shutdownCallback context.CancelFunc) *core.System {
Updater: vdrInstance,
Resolver: vdrInstance.Resolver(),
}})
system.RegisterRoutes(&vdrAPIv2.Wrapper{VDR: vdrInstance})
system.RegisterRoutes(&vcrAPI.Wrapper{VCR: credentialInstance, ContextManager: jsonld})
system.RegisterRoutes(&openid4vciAPI.Wrapper{
VCR: credentialInstance,
Expand Down
1 change: 1 addition & 0 deletions core/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ func TestServerConfig(template ServerConfig) ServerConfig {
config.Datadir = template.Datadir
config.Strictmode = template.Strictmode
config.InternalRateLimiter = template.InternalRateLimiter
config.URL = template.URL
return *config
}

Expand Down
22 changes: 22 additions & 0 deletions crypto/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,3 +125,25 @@ func (t TestKey) Public() crypto.PublicKey {
func (t TestKey) Private() crypto.PrivateKey {
return t.PrivateKey
}

// TestPublicKey is a Key impl for testing purposes that only contains a public key. It can't be used for signing.
type TestPublicKey struct {
Kid string
PublicKey crypto.PublicKey
}

func (t TestPublicKey) Signer() crypto.Signer {
panic("test public key is not for signing")
}

func (t TestPublicKey) KID() string {
return t.Kid
}

func (t TestPublicKey) Public() crypto.PublicKey {
return t.PublicKey
}

func (t TestPublicKey) Private() crypto.PrivateKey {
panic("test public key is not for signing")
}
3 changes: 2 additions & 1 deletion docs/pages/deployment/cli-reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,7 @@ Print conflicted documents and their metadata
nuts vdr create-did
^^^^^^^^^^^^^^^^^^^

Registers a new DID
When using the V2 API, a web:did will be created. All the other options are ignored for a web:did.

::

Expand All @@ -430,6 +430,7 @@ Registers a new DID
--timeout duration Client time-out when performing remote operations, such as '500ms' or '10s'. Refer to Golang's 'time.Duration' syntax for a more elaborate description of the syntax. (default 10s)
--token string Token to be used for authenticating on the remote node. Takes precedence over 'token-file'.
--token-file string File from which the authentication token will be read. If not specified it will try to read the token from the '.nuts-client.cfg' file in the user's home dir.
--v2 Pass 'true' to use the V2 API and create a web:did.
--verbosity string Log level (trace, debug, info, warn, error) (default "info")

nuts vdr deactivate
Expand Down
2 changes: 1 addition & 1 deletion docs/pages/deployment/server_options.rst
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
http.default.auth.type Whether to enable authentication for the default interface, specify 'token_v2' for bearer token mode or 'token' for legacy bearer token mode.
http.default.cors.origin [] When set, enables CORS from the specified origins on the default HTTP interface.
**JSONLD**
jsonld.contexts.localmapping [https://w3c-ccg.github.io/lds-jws2020/contexts/lds-jws2020-v1.json=assets/contexts/lds-jws2020-v1.ldjson,https://schema.org=assets/contexts/schema-org-v13.ldjson,https://nuts.nl/credentials/v1=assets/contexts/nuts.ldjson,https://www.w3.org/2018/credentials/v1=assets/contexts/w3c-credentials-v1.ldjson] This setting allows mapping external URLs to local files for e.g. preventing external dependencies. These mappings have precedence over those in remoteallowlist.
jsonld.contexts.localmapping [https://nuts.nl/credentials/v1=assets/contexts/nuts.ldjson,https://www.w3.org/2018/credentials/v1=assets/contexts/w3c-credentials-v1.ldjson,https://w3c-ccg.github.io/lds-jws2020/contexts/lds-jws2020-v1.json=assets/contexts/lds-jws2020-v1.ldjson,https://schema.org=assets/contexts/schema-org-v13.ldjson] This setting allows mapping external URLs to local files for e.g. preventing external dependencies. These mappings have precedence over those in remoteallowlist.
jsonld.contexts.remoteallowlist [https://schema.org,https://www.w3.org/2018/credentials/v1,https://w3c-ccg.github.io/lds-jws2020/contexts/lds-jws2020-v1.json] In strict mode, fetching external JSON-LD contexts is not allowed except for context-URLs listed here.
**Network**
network.bootstrapnodes [] List of bootstrap nodes ('<host>:<port>') which the node initially connect to.
Expand Down
Loading

0 comments on commit 199fbc6

Please sign in to comment.