Skip to content

Commit

Permalink
Handle authorization request from verifier by holder/wallet (#2680)
Browse files Browse the repository at this point in the history
  • Loading branch information
woutslakhorst authored Jan 10, 2024
1 parent 0be776e commit f3f66c6
Show file tree
Hide file tree
Showing 26 changed files with 1,382 additions and 278 deletions.
7 changes: 7 additions & 0 deletions auth/api/auth/v1/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ type mockAuthClient struct {
authzServer *oauth.MockAuthorizationServer
relyingParty *oauth.MockRelyingParty
verifier *oauth.MockVerifier
holder *oauth.MockHolder
}

func (m *mockAuthClient) V2APIEnabled() bool {
Expand All @@ -84,6 +85,10 @@ func (m *mockAuthClient) Verifier() oauth.Verifier {
return m.verifier
}

func (m *mockAuthClient) Holder() oauth.Holder {
return m.holder
}

func (m *mockAuthClient) ContractNotary() services.ContractNotary {
return m.contractNotary
}
Expand All @@ -100,13 +105,15 @@ func createContext(t *testing.T) *TestContext {
relyingParty := oauth.NewMockRelyingParty(ctrl)
verifier := oauth.NewMockVerifier(ctrl)
mockCredentialResolver := vcr.NewMockResolver(ctrl)
holder := oauth.NewMockHolder(ctrl)

authMock := &mockAuthClient{
ctrl: ctrl,
contractNotary: contractNotary,
authzServer: authzServer,
relyingParty: relyingParty,
verifier: verifier,
holder: holder,
}

requestCtx := audit.TestContext()
Expand Down
15 changes: 2 additions & 13 deletions auth/api/iam/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -265,17 +265,8 @@ func (r Wrapper) HandleAuthorizeRequest(ctx context.Context, request HandleAutho
for key, value := range httpRequest.URL.Query() {
params[key] = value[0]
}
// todo: store session in database? Isn't session specific for a particular flow?
session := createSession(params, *ownDID)
if session.RedirectURI == "" {
// TODO: Spec says that the redirect URI is optional, but it's not clear what to do if it's not provided.
// Threat models say it's unsafe to omit redirect_uri.
// See https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.1
return nil, oauth.OAuth2Error{
Code: oauth.InvalidRequest,
Description: "redirect_uri is required",
}
}
// todo: store session in database?

switch session.ResponseType {
case responseTypeCode:
Expand Down Expand Up @@ -303,9 +294,7 @@ func (r Wrapper) HandleAuthorizeRequest(ctx context.Context, request HandleAutho
case responseTypeVPToken:
// Options:
// - OpenID4VP flow, vp_token is sent in Authorization Response
// TODO: Check parameters for right flow
// TODO: Do we actually need this? (probably not)
panic("not implemented")
return r.handleAuthorizeRequestFromVerifier(ctx, *ownDID, params)
case responseTypeVPIDToken:
// Options:
// - OpenID4VP+SIOP flow, vp_token is sent in Authorization Response
Expand Down
60 changes: 47 additions & 13 deletions auth/api/iam/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,19 +223,23 @@ func TestWrapper_PresentationDefinition(t *testing.T) {
}

func TestWrapper_HandleAuthorizeRequest(t *testing.T) {
metadata := oauth.AuthorizationServerMetadata{
AuthorizationEndpoint: "https://example.com/holder/authorize",
serverMetadata := oauth.AuthorizationServerMetadata{
AuthorizationEndpoint: "https://example.com/holder/authorize",
ClientIdSchemesSupported: []string{"did"},
}
t.Run("ok - from holder", func(t *testing.T) {
clientMetadata := oauth.OAuthClientMetadata{
VPFormats: oauth.DefaultOpenIDSupportedFormats(),
}
t.Run("ok - code response type - from holder", func(t *testing.T) {
ctx := newTestClient(t)
ctx.vdr.EXPECT().IsOwner(gomock.Any(), verifierDID).Return(true, nil)
ctx.verifierRole.EXPECT().AuthorizationServerMetadata(gomock.Any(), holderDID).Return(&metadata, nil)
ctx.verifierRole.EXPECT().AuthorizationServerMetadata(gomock.Any(), holderDID).Return(&serverMetadata, nil)
ctx.verifierRole.EXPECT().ClientMetadataURL(verifierDID).Return(test.MustParseURL("https://example.com/.well-known/authorization-server/iam/verifier"), nil)

res, err := ctx.client.HandleAuthorizeRequest(requestContext(map[string]string{
clientIDParam: holderDID.String(),
redirectURIParam: "https://example.com",
responseTypeParam: "code",
responseTypeParam: responseTypeCode,
scopeParam: "test",
}), HandleAuthorizeRequestRequestObject{
Id: "verifier",
Expand All @@ -255,16 +259,42 @@ func TestWrapper_HandleAuthorizeRequest(t *testing.T) {
assert.Contains(t, location, "response_type=vp_token")

})
t.Run("missing redirect_uri", func(t *testing.T) {
t.Run("ok - vp_token response type - from verifier", func(t *testing.T) {
ctx := newTestClient(t)
ctx.vdr.EXPECT().IsOwner(gomock.Any(), webDID).Return(true, nil)
_ = ctx.client.storageEngine.GetSessionDatabase().GetStore(oAuthFlowTimeout, oauthClientStateKey...).Put("state", OAuthSession{
// this is the state from the holder that was stored at the creation of the first authorization request to the verifier
ClientID: holderDID.String(),
Scope: "test",
OwnDID: holderDID,
ClientState: "state",
RedirectURI: "https://example.com/iam/holder/cb",
ResponseType: "code",
})
ctx.vdr.EXPECT().IsOwner(gomock.Any(), holderDID).Return(true, nil)
ctx.holderRole.EXPECT().ClientMetadata(gomock.Any(), "https://example.com/.well-known/authorization-server/iam/verifier").Return(&clientMetadata, nil)
ctx.holderRole.EXPECT().PresentationDefinition(gomock.Any(), "https://example.com/iam/verifier/presentation_definition?scope=test").Return(&pe.PresentationDefinition{}, nil)
ctx.holderRole.EXPECT().BuildPresentation(gomock.Any(), holderDID, pe.PresentationDefinition{}, clientMetadata.VPFormats, "nonce").Return(&vc.VerifiablePresentation{}, &pe.PresentationSubmission{}, nil)
ctx.holderRole.EXPECT().PostAuthorizationResponse(gomock.Any(), vc.VerifiablePresentation{}, pe.PresentationSubmission{}, "https://example.com/iam/verifier/response", "state").Return("https://example.com/iam/holder/redirect", nil)

res, err := ctx.client.HandleAuthorizeRequest(requestContext(map[string]string{}), HandleAuthorizeRequestRequestObject{
Id: webIDPart,
res, err := ctx.client.HandleAuthorizeRequest(requestContext(map[string]string{
clientIDParam: verifierDID.String(),
clientIDSchemeParam: didScheme,
clientMetadataURIParam: "https://example.com/.well-known/authorization-server/iam/verifier",
nonceParam: "nonce",
presentationDefUriParam: "https://example.com/iam/verifier/presentation_definition?scope=test",
responseURIParam: "https://example.com/iam/verifier/response",
responseModeParam: responseModeDirectPost,
responseTypeParam: responseTypeVPToken,
scopeParam: "test",
stateParam: "state",
}), HandleAuthorizeRequestRequestObject{
Id: "holder",
})

requireOAuthError(t, err, oauth.InvalidRequest, "redirect_uri is required")
assert.Nil(t, res)
require.NoError(t, err)
assert.IsType(t, HandleAuthorizeRequest302Response{}, res)
location := res.(HandleAuthorizeRequest302Response).Headers.Location
assert.Equal(t, location, "https://example.com/iam/holder/redirect")
})
t.Run("unsupported response type", func(t *testing.T) {
ctx := newTestClient(t)
Expand Down Expand Up @@ -420,12 +450,13 @@ type testCtx struct {
client *Wrapper
authnServices *auth.MockAuthenticationServices
vdr *vdr.MockVDR
policy *policy.MockBackend
policy *policy.MockPDPBackend
resolver *resolver.MockDIDResolver
relyingParty *oauthServices.MockRelyingParty
vcVerifier *verifier.MockVerifier
vcr *vcr.MockVCR
verifierRole *oauthServices.MockVerifier
holderRole *oauthServices.MockHolder
}

func newTestClient(t testing.TB) *testCtx {
Expand All @@ -435,18 +466,20 @@ func newTestClient(t testing.TB) *testCtx {
storageEngine := storage.NewTestStorageEngine(t)
authnServices := auth.NewMockAuthenticationServices(ctrl)
authnServices.EXPECT().PublicURL().Return(publicURL).AnyTimes()
policyInstance := policy.NewMockBackend(ctrl)
policyInstance := policy.NewMockPDPBackend(ctrl)
mockResolver := resolver.NewMockDIDResolver(ctrl)
relyingPary := oauthServices.NewMockRelyingParty(ctrl)
vcVerifier := verifier.NewMockVerifier(ctrl)
verifierRole := oauthServices.NewMockVerifier(ctrl)
holderRole := oauthServices.NewMockHolder(ctrl)
mockVDR := vdr.NewMockVDR(ctrl)
mockVCR := vcr.NewMockVCR(ctrl)

authnServices.EXPECT().PublicURL().Return(publicURL).AnyTimes()
authnServices.EXPECT().RelyingParty().Return(relyingPary).AnyTimes()
mockVCR.EXPECT().Verifier().Return(vcVerifier).AnyTimes()
authnServices.EXPECT().Verifier().Return(verifierRole).AnyTimes()
authnServices.EXPECT().Holder().Return(holderRole).AnyTimes()
mockVDR.EXPECT().Resolver().Return(mockResolver).AnyTimes()

return &testCtx{
Expand All @@ -458,6 +491,7 @@ func newTestClient(t testing.TB) *testCtx {
resolver: mockResolver,
vdr: mockVDR,
verifierRole: verifierRole,
holderRole: holderRole,
vcr: mockVCR,
client: &Wrapper{
auth: authnServices,
Expand Down
Loading

0 comments on commit f3f66c6

Please sign in to comment.