Skip to content

Commit

Permalink
Add client metadata + API (#2488)
Browse files Browse the repository at this point in the history
* add known client metadata

* pr feedback

* revert error return
  • Loading branch information
gerardsn authored Sep 18, 2023
1 parent a6e2f5d commit 551c8a1
Show file tree
Hide file tree
Showing 9 changed files with 633 additions and 16 deletions.
39 changes: 39 additions & 0 deletions auth/api/iam/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"context"
"embed"
"errors"
"fmt"
"github.com/labstack/echo/v4"
"github.com/nuts-foundation/go-did/did"
"github.com/nuts-foundation/nuts-node/audit"
Expand All @@ -34,6 +35,7 @@ import (
vdr "github.com/nuts-foundation/nuts-node/vdr/types"
"html/template"
"net/http"
"strings"
"sync"
)

Expand Down Expand Up @@ -250,6 +252,43 @@ func (r Wrapper) GetWebDID(ctx context.Context, request GetWebDIDRequestObject)
return GetWebDID200JSONResponse(*document), nil
}

// GetOAuthClientMetadata returns the OAuth2 Client metadata for the request.Id if it is managed by this node.
func (r Wrapper) GetOAuthClientMetadata(ctx context.Context, request GetOAuthClientMetadataRequestObject) (GetOAuthClientMetadataResponseObject, error) {
if err := r.validateAsNutsFingerprint(ctx, request.Id); err != nil {
return nil, fmt.Errorf("client metadata: %w", err)
}

identity := r.auth.PublicURL().JoinPath("iam", request.Id)

return GetOAuthClientMetadata200JSONResponse(clientMetadata(*identity)), nil
}

func (r Wrapper) validateAsNutsFingerprint(ctx context.Context, fingerprint string) error {
// convert fingerprint to did:nuts
if strings.HasPrefix(fingerprint, "did:") {
return core.InvalidInputError("id contains full did")
}
nutsDID, err := did.ParseDID("did:nuts:" + fingerprint)
if err != nil {
return core.InvalidInputError(err.Error())
}

// assert ownership of did
owned, err := r.vdr.IsOwner(ctx, *nutsDID)
if err != nil {
if didservice.IsFunctionalResolveError(err) {
return core.NotFoundError(err.Error())
}
log.Logger().WithField("did", nutsDID.String()).Errorf("oauth metadata: failed to assert ownership of did: %s", err.Error())
return core.Error(500, err.Error())
}
if !owned {
return core.NotFoundError("did not owned")
}

return nil
}

func createSession(params map[string]string, ownDID did.DID) *Session {
session := &Session{
// TODO: Validate client ID
Expand Down
70 changes: 70 additions & 0 deletions auth/api/iam/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,76 @@ func TestWrapper_GetWebDID(t *testing.T) {
})
}

func TestWrapper_GetOAuthClientMetadata(t *testing.T) {
t.Run("ok", func(t *testing.T) {
// 200
ctx := newTestClient(t)
did := did.MustParseDID("did:nuts:123")
ctx.vdrInstance.EXPECT().IsOwner(nil, did).Return(true, nil)

res, err := ctx.client.GetOAuthClientMetadata(nil, GetOAuthClientMetadataRequestObject{Id: did.ID})

require.NoError(t, err)
assert.IsType(t, GetOAuthClientMetadata200JSONResponse{}, res)
})
t.Run("error - not a did", func(t *testing.T) {
//400
ctx := newTestClient(t)

res, err := ctx.client.GetOAuthClientMetadata(nil, GetOAuthClientMetadataRequestObject{})

assert.Equal(t, 400, statusCodeFrom(err))
assert.EqualError(t, err, "client metadata: invalid DID")
assert.Nil(t, res)
})
t.Run("error - contains full did:nuts", func(t *testing.T) {
//400
ctx := newTestClient(t)

res, err := ctx.client.GetOAuthClientMetadata(nil, GetOAuthClientMetadataRequestObject{Id: "did:nuts:123"})

assert.Equal(t, 400, statusCodeFrom(err))
assert.EqualError(t, err, "client metadata: id contains full did")
assert.Nil(t, res)
})
t.Run("error - did not managed by this node", func(t *testing.T) {
//404
ctx := newTestClient(t)
did := did.MustParseDID("did:nuts:123")
ctx.vdrInstance.EXPECT().IsOwner(nil, did)

res, err := ctx.client.GetOAuthClientMetadata(nil, GetOAuthClientMetadataRequestObject{Id: did.ID})

assert.Equal(t, 404, statusCodeFrom(err))
assert.EqualError(t, err, "client metadata: did not owned")
assert.Nil(t, res)
})
t.Run("error - did does not exist", func(t *testing.T) {
//404
ctx := newTestClient(t)
did := did.MustParseDID("did:nuts:123")
ctx.vdrInstance.EXPECT().IsOwner(nil, did).Return(false, vdr.ErrNotFound)

res, err := ctx.client.GetOAuthClientMetadata(nil, GetOAuthClientMetadataRequestObject{Id: did.ID})

assert.Equal(t, 404, statusCodeFrom(err))
assert.EqualError(t, err, "client metadata: unable to find the DID document")
assert.Nil(t, res)
})
t.Run("error - internal error 500", func(t *testing.T) {
//500
ctx := newTestClient(t)
did := did.MustParseDID("did:nuts:123")
ctx.vdrInstance.EXPECT().IsOwner(nil, did).Return(false, errors.New("unknown error"))

res, err := ctx.client.GetOAuthClientMetadata(nil, GetOAuthClientMetadataRequestObject{Id: did.ID})

assert.Equal(t, 500, statusCodeFrom(err))
assert.EqualError(t, err, "client metadata: unknown error")
assert.Nil(t, res)
})
}

// statusCodeFrom returns the statuscode if err is core.HTTPStatusCodeError, or 0 if it isn't
func statusCodeFrom(err error) int {
var SE core.HTTPStatusCodeError
Expand Down
86 changes: 86 additions & 0 deletions auth/api/iam/generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 551c8a1

Please sign in to comment.