-
-
Notifications
You must be signed in to change notification settings - Fork 367
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add verifiable credentials handler (#758)
- Loading branch information
Showing
7 changed files
with
193 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
// Copyright © 2023 Ory Corp | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package compose | ||
|
||
import ( | ||
"github.com/ory/fosite" | ||
"github.com/ory/fosite/handler/verifiable" | ||
) | ||
|
||
// OIDCUserinfoVerifiableCredentialFactory creates a verifiable credentials | ||
// handler. | ||
func OIDCUserinfoVerifiableCredentialFactory(config fosite.Configurator, storage, strategy any) any { | ||
return &verifiable.Handler{ | ||
NonceManager: storage.(verifiable.NonceManager), | ||
Config: config, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
// Copyright © 2023 Ory Corp | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package verifiable | ||
|
||
import ( | ||
"context" | ||
"time" | ||
|
||
"github.com/ory/fosite" | ||
"github.com/ory/x/errorsx" | ||
) | ||
|
||
const ( | ||
draftScope = "userinfo_credential_draft_00" | ||
draftNonceField = "c_nonce_draft_00" | ||
draftNonceExpField = "c_nonce_expires_in_draft_00" | ||
) | ||
|
||
type Handler struct { | ||
Config interface { | ||
fosite.VerifiableCredentialsNonceLifespanProvider | ||
} | ||
NonceManager | ||
} | ||
|
||
var _ fosite.TokenEndpointHandler = (*Handler)(nil) | ||
|
||
func (c *Handler) HandleTokenEndpointRequest(ctx context.Context, request fosite.AccessRequester) error { | ||
if !c.CanHandleTokenEndpointRequest(ctx, request) { | ||
return errorsx.WithStack(fosite.ErrUnknownRequest) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (c *Handler) PopulateTokenEndpointResponse( | ||
ctx context.Context, | ||
request fosite.AccessRequester, | ||
response fosite.AccessResponder, | ||
) error { | ||
if !c.CanHandleTokenEndpointRequest(ctx, request) { | ||
return errorsx.WithStack(fosite.ErrUnknownRequest) | ||
} | ||
|
||
lifespan := c.Config.GetVerifiableCredentialsNonceLifespan(ctx) | ||
expiry := time.Now().UTC().Add(lifespan) | ||
nonce, err := c.NewNonce(ctx, response.GetAccessToken(), expiry) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
response.SetExtra(draftNonceField, nonce) | ||
response.SetExtra(draftNonceExpField, int64(lifespan.Seconds())) | ||
|
||
return nil | ||
} | ||
|
||
func (c *Handler) CanSkipClientAuth(context.Context, fosite.AccessRequester) bool { | ||
return false | ||
} | ||
|
||
func (c *Handler) CanHandleTokenEndpointRequest(_ context.Context, requester fosite.AccessRequester) bool { | ||
return requester.GetGrantedScopes().Has("openid", draftScope) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
// Copyright © 2023 Ory Corp | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package verifiable | ||
|
||
import ( | ||
"context" | ||
"testing" | ||
"time" | ||
|
||
"github.com/golang/mock/gomock" | ||
"github.com/stretchr/testify/assert" | ||
|
||
"github.com/ory/fosite" | ||
"github.com/ory/fosite/internal" | ||
) | ||
|
||
type mockNonceManager struct{ t *testing.T } | ||
|
||
func (m *mockNonceManager) NewNonce(ctx context.Context, accessToken string, expiresAt time.Time) (string, error) { | ||
assert.Equal(m.t, "fake access token", accessToken) | ||
assert.WithinDuration(m.t, time.Now().Add(time.Hour), expiresAt, 5*time.Second) | ||
return "mocked nonce", nil | ||
} | ||
|
||
func (m *mockNonceManager) IsNonceValid(context.Context, string, string) error { | ||
return nil | ||
} | ||
|
||
func TestHandler(t *testing.T) { | ||
t.Parallel() | ||
ctx := context.Background() | ||
|
||
t.Run("case=correct scopes", func(t *testing.T) { | ||
t.Parallel() | ||
handler := newHandler(t) | ||
ctrl := gomock.NewController(t) | ||
defer ctrl.Finish() | ||
|
||
req := internal.NewMockAccessRequester(ctrl) | ||
req.EXPECT().GetGrantedScopes().Return(fosite.Arguments{"openid", draftScope}).AnyTimes() | ||
|
||
resp := internal.NewMockAccessResponder(ctrl) | ||
resp.EXPECT().GetAccessToken().Return("fake access token") | ||
resp.EXPECT().SetExtra(gomock.Eq(draftNonceField), gomock.Eq("mocked nonce")) | ||
resp.EXPECT().SetExtra(gomock.Eq(draftNonceExpField), gomock.Any()) | ||
|
||
assert.NoError(t, handler.HandleTokenEndpointRequest(ctx, req)) | ||
assert.NoError(t, handler.PopulateTokenEndpointResponse(ctx, req, resp)) | ||
}) | ||
|
||
t.Run("case=incorrect scopes", func(t *testing.T) { | ||
t.Parallel() | ||
handler := newHandler(t) | ||
ctrl := gomock.NewController(t) | ||
defer ctrl.Finish() | ||
|
||
req := internal.NewMockAccessRequester(ctrl) | ||
req.EXPECT().GetGrantedScopes().Return(fosite.Arguments{"openid"}).AnyTimes() | ||
|
||
resp := internal.NewMockAccessResponder(ctrl) | ||
|
||
assert.ErrorIs(t, handler.HandleTokenEndpointRequest(ctx, req), fosite.ErrUnknownRequest) | ||
assert.ErrorIs(t, handler.PopulateTokenEndpointResponse(ctx, req, resp), fosite.ErrUnknownRequest) | ||
}) | ||
} | ||
|
||
func newHandler(t *testing.T) *Handler { | ||
return &Handler{ | ||
Config: new(fosite.Config), | ||
NonceManager: &mockNonceManager{t: t}, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
// Copyright © 2023 Ory Corp | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package verifiable | ||
|
||
import ( | ||
"context" | ||
"time" | ||
) | ||
|
||
type NonceManager interface { | ||
// NewNonce creates a new nonce bound to the access token valid until the given expiry time. | ||
NewNonce(ctx context.Context, accessToken string, expiresAt time.Time) (string, error) | ||
|
||
// IsNonceValid checks if the given nonce is valid for the given access token and not expired. | ||
IsNonceValid(ctx context.Context, accessToken string, nonce string) error | ||
} |