diff --git a/auth/api/iam/api.go b/auth/api/iam/api.go index 7bf86cd443..90e14223c3 100644 --- a/auth/api/iam/api.go +++ b/auth/api/iam/api.go @@ -130,7 +130,6 @@ func (r Wrapper) HandleTokenRequest(ctx context.Context, request HandleTokenRequ if err != nil { return nil, err } - switch request.Body.GrantType { case "authorization_code": // Options: @@ -140,13 +139,6 @@ func (r Wrapper) HandleTokenRequest(ctx context.Context, request HandleTokenRequ Code: oauth.UnsupportedGrantType, Description: "not implemented yet", } - case "vp_token-bearer": - // Options: - // - service-to-service vp_token flow - return nil, oauth.OAuth2Error{ - Code: oauth.UnsupportedGrantType, - Description: "not implemented yet", - } case "urn:ietf:params:oauth:grant-type:pre-authorized_code": // Options: // - OpenID4VCI @@ -324,7 +316,6 @@ func (r Wrapper) OAuthAuthorizationServerMetadata(ctx context.Context, request O func (r Wrapper) GetWebDID(_ context.Context, request GetWebDIDRequestObject) (GetWebDIDResponseObject, error) { ownDID := r.idToDID(request.Id) - document, err := r.vdr.ResolveManaged(ownDID) if err != nil { if resolver.IsFunctionalResolveError(err) { @@ -338,19 +329,12 @@ func (r Wrapper) GetWebDID(_ context.Context, request GetWebDIDRequestObject) (G // OAuthClientMetadata returns the OAuth2 Client metadata for the request.Id if it is managed by this node. func (r Wrapper) OAuthClientMetadata(ctx context.Context, request OAuthClientMetadataRequestObject) (OAuthClientMetadataResponseObject, error) { - ownDID := idToDID(request.Id) - owned, err := r.vdr.IsOwner(ctx, ownDID) + _, err := r.idToOwnedDID(ctx, request.Id) if err != nil { - log.Logger().WithField("did", ownDID.String()).Errorf("oauth metadata: failed to assert ownership of did: %s", err.Error()) - return nil, core.Error(500, err.Error()) - } - if !owned { - return nil, core.NotFoundError("did not owned") + return nil, err } - identity := r.auth.PublicURL().JoinPath("iam", request.Id) - - return OAuthClientMetadata200JSONResponse(clientMetadata(*identity)), nil + return OAuthClientMetadata200JSONResponse(clientMetadata(*r.identityURL(request.Id))), nil } func (r Wrapper) PresentationDefinition(_ context.Context, request PresentationDefinitionRequestObject) (PresentationDefinitionResponseObject, error) { if len(request.Params.Scope) == 0 { @@ -371,7 +355,7 @@ func (r Wrapper) PresentationDefinition(_ context.Context, request PresentationD } func (r Wrapper) idToOwnedDID(ctx context.Context, id string) (*did.DID, error) { - ownDID := idToDID(id) + ownDID := r.idToDID(id) owned, err := r.vdr.IsOwner(ctx, ownDID) if err != nil { if resolver.IsFunctionalResolveError(err) { @@ -406,19 +390,20 @@ func createSession(params map[string]string, ownDID did.DID) *Session { } return session } + +// idToDID converts the tenant-specific part of a did:web DID (e.g. 123) +// to a fully qualified did:web DID (e.g. did:web:example.com:123), using the configured Nuts node URL. func (r Wrapper) idToDID(id string) did.DID { - url := r.auth.PublicURL().JoinPath("iam", id) - did, _ := didweb.URLToDID(*url) - return *did + identityURL := r.identityURL(id) + result, _ := didweb.URLToDID(*identityURL) + return *result } -func idToNutsDID(id string) did.DID { - return did.DID{ - // should be changed to web when migrated to web DID - Method: "nuts", - ID: id, - DecodedID: id, - } +// identityURL the tenant-specific part of a did:web DID (e.g. 123) +// to an identity URL (e.g. did:web:example.com:123), which is used as base URL for resolving metadata and its did:web DID, +// using the configured Nuts node URL. +func (r Wrapper) identityURL(id string) *url.URL { + return r.auth.PublicURL().JoinPath("iam", id) } func (r *Wrapper) accessTokenStore() storage.SessionStore { diff --git a/auth/api/iam/api_test.go b/auth/api/iam/api_test.go index aa52f2b0f8..4055e479c2 100644 --- a/auth/api/iam/api_test.go +++ b/auth/api/iam/api_test.go @@ -48,7 +48,6 @@ import ( "time" ) -var nutsDID = did.MustParseDID("did:nuts:123") var webDID = did.MustParseDID("did:web:example.com:iam:123") var webIDPart = "123" @@ -142,32 +141,13 @@ func TestWrapper_GetWebDID(t *testing.T) { func TestWrapper_GetOAuthClientMetadata(t *testing.T) { t.Run("ok", func(t *testing.T) { 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.OAuthClientMetadata(nil, OAuthClientMetadataRequestObject{Id: nutsDID.ID}) + res, err := ctx.client.OAuthClientMetadata(nil, OAuthClientMetadataRequestObject{Id: webIDPart}) require.NoError(t, err) assert.IsType(t, OAuthClientMetadata200JSONResponse{}, res) }) - t.Run("error - did not managed by this node", func(t *testing.T) { - ctx := newTestClient(t) - ctx.vdr.EXPECT().IsOwner(nil, nutsDID) - - res, err := ctx.client.OAuthClientMetadata(nil, OAuthClientMetadataRequestObject{Id: nutsDID.ID}) - - assert.Equal(t, 404, statusCodeFrom(err)) - assert.Nil(t, res) - }) - t.Run("error - internal error 500", func(t *testing.T) { - ctx := newTestClient(t) - ctx.vdr.EXPECT().IsOwner(nil, nutsDID).Return(false, errors.New("unknown error")) - - res, err := ctx.client.OAuthClientMetadata(nil, OAuthClientMetadataRequestObject{Id: nutsDID.ID}) - - assert.Equal(t, 500, statusCodeFrom(err)) - assert.EqualError(t, err, "unknown error") - assert.Nil(t, res) - }) } func TestWrapper_PresentationDefinition(t *testing.T) { webDID := did.MustParseDID("did:web:example.com:iam:123") @@ -211,10 +191,10 @@ func TestWrapper_PresentationDefinition(t *testing.T) { func TestWrapper_HandleAuthorizeRequest(t *testing.T) { t.Run("missing redirect_uri", func(t *testing.T) { ctx := newTestClient(t) - ctx.vdr.EXPECT().IsOwner(gomock.Any(), nutsDID).Return(true, nil) + ctx.vdr.EXPECT().IsOwner(gomock.Any(), webDID).Return(true, nil) res, err := ctx.client.HandleAuthorizeRequest(requestContext(map[string]string{}), HandleAuthorizeRequestRequestObject{ - Id: nutsDID.ID, + Id: webIDPart, }) requireOAuthError(t, err, oauth.InvalidRequest, "redirect_uri is required") @@ -222,13 +202,13 @@ func TestWrapper_HandleAuthorizeRequest(t *testing.T) { }) t.Run("unsupported response type", func(t *testing.T) { ctx := newTestClient(t) - ctx.vdr.EXPECT().IsOwner(gomock.Any(), nutsDID).Return(true, nil) + ctx.vdr.EXPECT().IsOwner(gomock.Any(), webDID).Return(true, nil) res, err := ctx.client.HandleAuthorizeRequest(requestContext(map[string]string{ "redirect_uri": "https://example.com", "response_type": "unsupported", }), HandleAuthorizeRequestRequestObject{ - Id: nutsDID.ID, + Id: webIDPart, }) requireOAuthError(t, err, oauth.UnsupportedResponseType, "") @@ -239,10 +219,10 @@ func TestWrapper_HandleAuthorizeRequest(t *testing.T) { func TestWrapper_HandleTokenRequest(t *testing.T) { t.Run("unsupported grant type", func(t *testing.T) { ctx := newTestClient(t) - ctx.vdr.EXPECT().IsOwner(gomock.Any(), nutsDID).Return(true, nil) + ctx.vdr.EXPECT().IsOwner(gomock.Any(), webDID).Return(true, nil) res, err := ctx.client.HandleTokenRequest(nil, HandleTokenRequestRequestObject{ - Id: nutsDID.ID, + Id: webIDPart, Body: &HandleTokenRequestFormdataRequestBody{ GrantType: "unsupported", }, @@ -476,33 +456,33 @@ func TestWrapper_middleware(t *testing.T) { func TestWrapper_idToOwnedDID(t *testing.T) { t.Run("ok", func(t *testing.T) { ctx := newTestClient(t) - ctx.vdr.EXPECT().IsOwner(nil, nutsDID).Return(true, nil) + ctx.vdr.EXPECT().IsOwner(nil, webDID).Return(true, nil) - _, err := ctx.client.idToOwnedDID(nil, nutsDID.ID) + _, err := ctx.client.idToOwnedDID(nil, webIDPart) assert.NoError(t, err) }) t.Run("error - did not managed by this node", func(t *testing.T) { ctx := newTestClient(t) - ctx.vdr.EXPECT().IsOwner(nil, nutsDID) + ctx.vdr.EXPECT().IsOwner(nil, webDID) - _, err := ctx.client.idToOwnedDID(nil, nutsDID.ID) + _, err := ctx.client.idToOwnedDID(nil, webIDPart) assert.EqualError(t, err, "invalid_request - issuer DID not owned by the server") }) t.Run("DID does not exist (functional resolver error)", func(t *testing.T) { ctx := newTestClient(t) - ctx.vdr.EXPECT().IsOwner(nil, nutsDID).Return(false, resolver.ErrNotFound) + ctx.vdr.EXPECT().IsOwner(nil, webDID).Return(false, resolver.ErrNotFound) - _, err := ctx.client.idToOwnedDID(nil, nutsDID.ID) + _, err := ctx.client.idToOwnedDID(nil, webIDPart) assert.EqualError(t, err, "invalid_request - invalid issuer DID: unable to find the DID document") }) t.Run("other resolver error", func(t *testing.T) { 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")) - _, err := ctx.client.idToOwnedDID(nil, nutsDID.ID) + _, err := ctx.client.idToOwnedDID(nil, webIDPart) assert.EqualError(t, err, "DID resolution failed: unknown error") }) diff --git a/auth/api/iam/s2s_vptoken.go b/auth/api/iam/s2s_vptoken.go index 7cf5e85e38..b9e7355708 100644 --- a/auth/api/iam/s2s_vptoken.go +++ b/auth/api/iam/s2s_vptoken.go @@ -299,17 +299,15 @@ func (r *Wrapper) validatePresentationAudience(presentation vc.VerifiablePresent audience = []string{*proof.Domain} } } - // Callers use the did:web variant, so we need to check against that instead of did:nuts - webDID := nutsToWebDID(issuer, *r.auth.PublicURL()) for _, aud := range audience { - if aud == webDID.String() { + if aud == issuer.String() { return nil } } return oauth.OAuth2Error{ Code: oauth.InvalidRequest, - Description: "presentation audience is missing or does not match", - InternalError: fmt.Errorf("expected: %s, got: %v", webDID.String(), audience), + Description: "presentation audience/domain is missing or does not match", + InternalError: fmt.Errorf("expected: %s, got: %v", issuer, audience), } } diff --git a/auth/api/iam/s2s_vptoken_test.go b/auth/api/iam/s2s_vptoken_test.go index 811e2b7c3a..3d088690c9 100644 --- a/auth/api/iam/s2s_vptoken_test.go +++ b/auth/api/iam/s2s_vptoken_test.go @@ -310,7 +310,7 @@ func TestWrapper_handleS2SAccessTokenRequest(t *testing.T) { resp, err := ctx.client.handleS2SAccessTokenRequest(issuerDID, requestedScope, submissionJSON, presentation.Raw()) - assert.EqualError(t, err, "invalid_request - presentation audience is missing or does not match") + assert.EqualError(t, err, "invalid_request - expected: did:web:example.com:iam:123, got: [] - presentation audience/domain is missing or does not match") assert.Nil(t, resp) }) t.Run("not matching", func(t *testing.T) { @@ -321,7 +321,7 @@ func TestWrapper_handleS2SAccessTokenRequest(t *testing.T) { resp, err := ctx.client.handleS2SAccessTokenRequest(issuerDID, requestedScope, submissionJSON, presentation.Raw()) - assert.EqualError(t, err, "invalid_request - presentation audience is missing or does not match") + assert.EqualError(t, err, "invalid_request - expected: did:web:example.com:iam:123, got: [did:example:other] - presentation audience/domain is missing or does not match") assert.Nil(t, resp) }) }) diff --git a/vcr/credential/util_test.go b/vcr/credential/util_test.go index 9497bff317..e7eba1c518 100644 --- a/vcr/credential/util_test.go +++ b/vcr/credential/util_test.go @@ -70,7 +70,7 @@ func TestPresenterIsCredentialSubject(t *testing.T) { subjectDID := did.MustParseDID("did:test:123") keyID := ssi.MustParseURI("did:test:123#1") t.Run("ok", func(t *testing.T) { - vp := vc.VerifiablePresentation{ + vp := test.ParsePresentation(t, vc.VerifiablePresentation{ Proof: []interface{}{ proof.LDProof{ Type: "JsonWebSignature2020", @@ -82,7 +82,7 @@ func TestPresenterIsCredentialSubject(t *testing.T) { CredentialSubject: []interface{}{map[string]interface{}{"id": subjectDID}}, }, }, - } + }) is, err := PresenterIsCredentialSubject(vp) assert.NoError(t, err) assert.Equal(t, subjectDID, *is) @@ -94,7 +94,7 @@ func TestPresenterIsCredentialSubject(t *testing.T) { assert.Nil(t, actual) }) t.Run("no VC subject", func(t *testing.T) { - vp := vc.VerifiablePresentation{ + vp := test.ParsePresentation(t, vc.VerifiablePresentation{ Proof: []interface{}{ proof.LDProof{ Type: "JsonWebSignature2020", @@ -104,13 +104,13 @@ func TestPresenterIsCredentialSubject(t *testing.T) { VerifiableCredential: []vc.VerifiableCredential{ {}, }, - } + }) is, err := PresenterIsCredentialSubject(vp) assert.EqualError(t, err, "unable to get subject DID from VC: there must be at least 1 credentialSubject") assert.Nil(t, is) }) t.Run("no VC subject ID", func(t *testing.T) { - vp := vc.VerifiablePresentation{ + vp := test.ParsePresentation(t, vc.VerifiablePresentation{ Proof: []interface{}{ proof.LDProof{ Type: "JsonWebSignature2020", @@ -122,13 +122,13 @@ func TestPresenterIsCredentialSubject(t *testing.T) { CredentialSubject: []interface{}{map[string]interface{}{}}, }, }, - } + }) is, err := PresenterIsCredentialSubject(vp) assert.EqualError(t, err, "unable to get subject DID from VC: credential subjects have no ID") assert.Nil(t, is) }) t.Run("proof verification method does not equal VC subject ID", func(t *testing.T) { - vp := vc.VerifiablePresentation{ + vp := test.ParsePresentation(t, vc.VerifiablePresentation{ Proof: []interface{}{ proof.LDProof{ Type: "JsonWebSignature2020", @@ -140,13 +140,13 @@ func TestPresenterIsCredentialSubject(t *testing.T) { CredentialSubject: []interface{}{map[string]interface{}{"id": did.MustParseDID("did:test:456")}}, }, }, - } + }) is, err := PresenterIsCredentialSubject(vp) assert.NoError(t, err) assert.Nil(t, is) }) t.Run("proof type is unsupported", func(t *testing.T) { - vp := vc.VerifiablePresentation{ + vp := test.ParsePresentation(t, vc.VerifiablePresentation{ Proof: []interface{}{ true, }, @@ -155,13 +155,13 @@ func TestPresenterIsCredentialSubject(t *testing.T) { CredentialSubject: []interface{}{map[string]interface{}{"id": subjectDID}}, }, }, - } + }) is, err := PresenterIsCredentialSubject(vp) assert.EqualError(t, err, "invalid LD-proof for presentation: json: cannot unmarshal bool into Go value of type proof.LDProof") assert.Nil(t, is) }) t.Run("too many proofs", func(t *testing.T) { - vp := vc.VerifiablePresentation{ + vp := test.ParsePresentation(t, vc.VerifiablePresentation{ Proof: []interface{}{ proof.LDProof{}, proof.LDProof{}, @@ -171,7 +171,7 @@ func TestPresenterIsCredentialSubject(t *testing.T) { CredentialSubject: []interface{}{map[string]interface{}{"id": subjectDID}}, }, }, - } + }) is, err := PresenterIsCredentialSubject(vp) assert.EqualError(t, err, "presentation should have exactly 1 proof, got 2") assert.Nil(t, is) diff --git a/vcr/pe/presentation_definition_test.go b/vcr/pe/presentation_definition_test.go index 4dccd25d1a..e8e5515d37 100644 --- a/vcr/pe/presentation_definition_test.go +++ b/vcr/pe/presentation_definition_test.go @@ -672,8 +672,9 @@ func Test_matchFilter(t *testing.T) { } func TestPresentationDefinition_ResolveConstraintsFields(t *testing.T) { - jwtCredential := credential.JWTNutsOrganizationCredential(t) - jsonldCredential := credential.JWTNutsOrganizationCredential(t) + subjectDID := did.MustParseDID("did:web:example.com") + jwtCredential := credential.JWTNutsOrganizationCredential(t, subjectDID) + jsonldCredential := credential.JWTNutsOrganizationCredential(t, subjectDID) definition := definitions().JSONLDorJWT t.Run("match JWT", func(t *testing.T) { credentialMap := map[string]vc.VerifiableCredential{ diff --git a/vcr/test/test.go b/vcr/test/test.go index 219dac18d2..ceb1793be9 100644 --- a/vcr/test/test.go +++ b/vcr/test/test.go @@ -46,6 +46,7 @@ func CreateJWTPresentation(t *testing.T, subjectDID did.DID, tokenVisitor func(t jwt.JwtIDKey: subjectDID.String() + "#" + uuid.NewString(), jwt.NotBeforeKey: time.Now().Unix(), jwt.ExpirationKey: time.Now().Add(5 * time.Second).Unix(), + "nonce": crypto.GenerateNonce(), "vp": vc.VerifiablePresentation{ Type: []ssi.URI{vc.VerifiablePresentationTypeV1URI()}, VerifiableCredential: credentials,