From 576230ace1a2a488d9f4d4af4272df0caae9eec9 Mon Sep 17 00:00:00 2001 From: hackerman <3372410+aeneasr@users.noreply.github.com> Date: Tue, 9 Jul 2024 12:31:51 +0200 Subject: [PATCH] refactor: split HMAC SHA strategy (#813) BREAKING CHANGES: Going forward, the `HMACSHAStrategy` requires a `BaseHMACSHAStrategy`. Here is how to upgrade it: ```patch var hmacshaStrategy = HMACSHAStrategy{ - Enigma: &hmac.HMACStrategy{Config: &fosite.Config{GlobalSecret: []byte("foobarfoobarfoobarfoobarfoobarfoobarfoobarfoobar")}}, - Config: &fosite.Config{ - AccessTokenLifespan: time.Hour * 24, - AuthorizeCodeLifespan: time.Hour * 24, + BaseHMACSHAStrategy: &BaseHMACSHAStrategy{ + Enigma: &hmac.HMACStrategy{Config: &fosite.Config{GlobalSecret: []byte("foobarfoobarfoobarfoobarfoobarfoobarfoobarfoobar")}}, + Config: &fosite.Config{ + AccessTokenLifespan: time.Hour * 24, + AuthorizeCodeLifespan: time.Hour * 24, + }, }, } ``` --- compose/compose_strategy.go | 6 ++- handler/oauth2/strategy_hmacsha.go | 60 ++++++++------------- handler/oauth2/strategy_hmacsha_prefixed.go | 60 +++++++++++++++++++++ handler/oauth2/strategy_hmacsha_test.go | 10 ++-- handler/openid/flow_hybrid_test.go | 8 +-- handler/pkce/handler_test.go | 8 +-- integration/helper_setup_test.go | 14 ++--- 7 files changed, 109 insertions(+), 57 deletions(-) create mode 100644 handler/oauth2/strategy_hmacsha_prefixed.go diff --git a/compose/compose_strategy.go b/compose/compose_strategy.go index 267e3295..f2ecbafa 100644 --- a/compose/compose_strategy.go +++ b/compose/compose_strategy.go @@ -31,8 +31,10 @@ type HMACSHAStrategyConfigurator interface { func NewOAuth2HMACStrategy(config HMACSHAStrategyConfigurator) *oauth2.HMACSHAStrategy { return &oauth2.HMACSHAStrategy{ - Enigma: &hmac.HMACStrategy{Config: config}, - Config: config, + BaseHMACSHAStrategy: &oauth2.BaseHMACSHAStrategy{ + Enigma: &hmac.HMACStrategy{Config: config}, + Config: config, + }, } } diff --git a/handler/oauth2/strategy_hmacsha.go b/handler/oauth2/strategy_hmacsha.go index 71193459..293407fa 100644 --- a/handler/oauth2/strategy_hmacsha.go +++ b/handler/oauth2/strategy_hmacsha.go @@ -5,8 +5,6 @@ package oauth2 import ( "context" - "fmt" - "strings" "time" "github.com/ory/x/errorsx" @@ -15,55 +13,39 @@ import ( enigma "github.com/ory/fosite/token/hmac" ) -type HMACSHAStrategy struct { +var _ CoreStrategy = (*BaseHMACSHAStrategy)(nil) + +type BaseHMACSHAStrategy struct { Enigma *enigma.HMACStrategy Config interface { fosite.AccessTokenLifespanProvider fosite.RefreshTokenLifespanProvider fosite.AuthorizeCodeLifespanProvider } - prefix *string } -func (h *HMACSHAStrategy) AccessTokenSignature(ctx context.Context, token string) string { - return h.Enigma.Signature(token) -} -func (h *HMACSHAStrategy) RefreshTokenSignature(ctx context.Context, token string) string { - return h.Enigma.Signature(token) -} -func (h *HMACSHAStrategy) AuthorizeCodeSignature(ctx context.Context, token string) string { +func (h *BaseHMACSHAStrategy) AccessTokenSignature(_ context.Context, token string) string { return h.Enigma.Signature(token) } -func (h *HMACSHAStrategy) getPrefix(part string) string { - if h.prefix == nil { - prefix := "ory_%s_" - h.prefix = &prefix - } else if len(*h.prefix) == 0 { - return "" - } - - return fmt.Sprintf(*h.prefix, part) -} - -func (h *HMACSHAStrategy) trimPrefix(token, part string) string { - return strings.TrimPrefix(token, h.getPrefix(part)) +func (h *BaseHMACSHAStrategy) RefreshTokenSignature(_ context.Context, token string) string { + return h.Enigma.Signature(token) } -func (h *HMACSHAStrategy) setPrefix(token, part string) string { - return h.getPrefix(part) + token +func (h *BaseHMACSHAStrategy) AuthorizeCodeSignature(_ context.Context, token string) string { + return h.Enigma.Signature(token) } -func (h *HMACSHAStrategy) GenerateAccessToken(ctx context.Context, _ fosite.Requester) (token string, signature string, err error) { +func (h *BaseHMACSHAStrategy) GenerateAccessToken(ctx context.Context, _ fosite.Requester) (token string, signature string, err error) { token, sig, err := h.Enigma.Generate(ctx) if err != nil { return "", "", err } - return h.setPrefix(token, "at"), sig, nil + return token, sig, nil } -func (h *HMACSHAStrategy) ValidateAccessToken(ctx context.Context, r fosite.Requester, token string) (err error) { +func (h *BaseHMACSHAStrategy) ValidateAccessToken(ctx context.Context, r fosite.Requester, token string) (err error) { var exp = r.GetSession().GetExpiresAt(fosite.AccessToken) if exp.IsZero() && r.GetRequestedAt().Add(h.Config.GetAccessTokenLifespan(ctx)).Before(time.Now().UTC()) { return errorsx.WithStack(fosite.ErrTokenExpired.WithHintf("Access token expired at '%s'.", r.GetRequestedAt().Add(h.Config.GetAccessTokenLifespan(ctx)))) @@ -73,42 +55,42 @@ func (h *HMACSHAStrategy) ValidateAccessToken(ctx context.Context, r fosite.Requ return errorsx.WithStack(fosite.ErrTokenExpired.WithHintf("Access token expired at '%s'.", exp)) } - return h.Enigma.Validate(ctx, h.trimPrefix(token, "at")) + return h.Enigma.Validate(ctx, token) } -func (h *HMACSHAStrategy) GenerateRefreshToken(ctx context.Context, _ fosite.Requester) (token string, signature string, err error) { +func (h *BaseHMACSHAStrategy) GenerateRefreshToken(ctx context.Context, _ fosite.Requester) (token string, signature string, err error) { token, sig, err := h.Enigma.Generate(ctx) if err != nil { return "", "", err } - return h.setPrefix(token, "rt"), sig, nil + return token, sig, nil } -func (h *HMACSHAStrategy) ValidateRefreshToken(ctx context.Context, r fosite.Requester, token string) (err error) { +func (h *BaseHMACSHAStrategy) ValidateRefreshToken(ctx context.Context, r fosite.Requester, token string) (err error) { var exp = r.GetSession().GetExpiresAt(fosite.RefreshToken) if exp.IsZero() { // Unlimited lifetime - return h.Enigma.Validate(ctx, h.trimPrefix(token, "rt")) + return h.Enigma.Validate(ctx, token) } if !exp.IsZero() && exp.Before(time.Now().UTC()) { return errorsx.WithStack(fosite.ErrTokenExpired.WithHintf("Refresh token expired at '%s'.", exp)) } - return h.Enigma.Validate(ctx, h.trimPrefix(token, "rt")) + return h.Enigma.Validate(ctx, token) } -func (h *HMACSHAStrategy) GenerateAuthorizeCode(ctx context.Context, _ fosite.Requester) (token string, signature string, err error) { +func (h *BaseHMACSHAStrategy) GenerateAuthorizeCode(ctx context.Context, _ fosite.Requester) (token string, signature string, err error) { token, sig, err := h.Enigma.Generate(ctx) if err != nil { return "", "", err } - return h.setPrefix(token, "ac"), sig, nil + return token, sig, nil } -func (h *HMACSHAStrategy) ValidateAuthorizeCode(ctx context.Context, r fosite.Requester, token string) (err error) { +func (h *BaseHMACSHAStrategy) ValidateAuthorizeCode(ctx context.Context, r fosite.Requester, token string) (err error) { var exp = r.GetSession().GetExpiresAt(fosite.AuthorizeCode) if exp.IsZero() && r.GetRequestedAt().Add(h.Config.GetAuthorizeCodeLifespan(ctx)).Before(time.Now().UTC()) { return errorsx.WithStack(fosite.ErrTokenExpired.WithHintf("Authorize code expired at '%s'.", r.GetRequestedAt().Add(h.Config.GetAuthorizeCodeLifespan(ctx)))) @@ -118,5 +100,5 @@ func (h *HMACSHAStrategy) ValidateAuthorizeCode(ctx context.Context, r fosite.Re return errorsx.WithStack(fosite.ErrTokenExpired.WithHintf("Authorize code expired at '%s'.", exp)) } - return h.Enigma.Validate(ctx, h.trimPrefix(token, "ac")) + return h.Enigma.Validate(ctx, token) } diff --git a/handler/oauth2/strategy_hmacsha_prefixed.go b/handler/oauth2/strategy_hmacsha_prefixed.go new file mode 100644 index 00000000..8849deb8 --- /dev/null +++ b/handler/oauth2/strategy_hmacsha_prefixed.go @@ -0,0 +1,60 @@ +// Copyright © 2024 Ory Corp +// SPDX-License-Identifier: Apache-2.0 + +package oauth2 + +import ( + "context" + "fmt" + "strings" + + "github.com/ory/fosite" +) + +var _ CoreStrategy = (*HMACSHAStrategy)(nil) + +type HMACSHAStrategy struct { + *BaseHMACSHAStrategy +} + +func (h *HMACSHAStrategy) getPrefix(part string) string { + return fmt.Sprintf("ory_%s_", part) +} + +func (h *HMACSHAStrategy) trimPrefix(token, part string) string { + return strings.TrimPrefix(token, h.getPrefix(part)) +} + +func (h *HMACSHAStrategy) setPrefix(token, part string) string { + if token == "" { + return "" + } + return h.getPrefix(part) + token +} + +func (h *HMACSHAStrategy) GenerateAccessToken(ctx context.Context, r fosite.Requester) (token string, signature string, err error) { + token, sig, err := h.BaseHMACSHAStrategy.GenerateAccessToken(ctx, r) + return h.setPrefix(token, "at"), sig, err +} + +func (h *HMACSHAStrategy) ValidateAccessToken(ctx context.Context, r fosite.Requester, token string) (err error) { + return h.BaseHMACSHAStrategy.ValidateAccessToken(ctx, r, h.trimPrefix(token, "at")) +} + +func (h *HMACSHAStrategy) GenerateRefreshToken(ctx context.Context, r fosite.Requester) (token string, signature string, err error) { + token, sig, err := h.BaseHMACSHAStrategy.GenerateRefreshToken(ctx, r) + return h.setPrefix(token, "rt"), sig, err +} + +func (h *HMACSHAStrategy) ValidateRefreshToken(ctx context.Context, r fosite.Requester, token string) (err error) { + return h.BaseHMACSHAStrategy.ValidateRefreshToken(ctx, r, h.trimPrefix(token, "rt")) +} + +func (h *HMACSHAStrategy) GenerateAuthorizeCode(ctx context.Context, r fosite.Requester) (token string, signature string, err error) { + token, sig, err := h.BaseHMACSHAStrategy.GenerateAuthorizeCode(ctx, r) + return h.setPrefix(token, "ac"), sig, err +} + +func (h *HMACSHAStrategy) ValidateAuthorizeCode(ctx context.Context, r fosite.Requester, token string) (err error) { + return h.BaseHMACSHAStrategy.ValidateAuthorizeCode(ctx, r, h.trimPrefix(token, "ac")) +} diff --git a/handler/oauth2/strategy_hmacsha_test.go b/handler/oauth2/strategy_hmacsha_test.go index 9b780d28..9096bdba 100644 --- a/handler/oauth2/strategy_hmacsha_test.go +++ b/handler/oauth2/strategy_hmacsha_test.go @@ -17,10 +17,12 @@ import ( ) var hmacshaStrategy = HMACSHAStrategy{ - Enigma: &hmac.HMACStrategy{Config: &fosite.Config{GlobalSecret: []byte("foobarfoobarfoobarfoobarfoobarfoobarfoobarfoobar")}}, - Config: &fosite.Config{ - AccessTokenLifespan: time.Hour * 24, - AuthorizeCodeLifespan: time.Hour * 24, + BaseHMACSHAStrategy: &BaseHMACSHAStrategy{ + Enigma: &hmac.HMACStrategy{Config: &fosite.Config{GlobalSecret: []byte("foobarfoobarfoobarfoobarfoobarfoobarfoobarfoobar")}}, + Config: &fosite.Config{ + AccessTokenLifespan: time.Hour * 24, + AuthorizeCodeLifespan: time.Hour * 24, + }, }, } diff --git a/handler/openid/flow_hybrid_test.go b/handler/openid/flow_hybrid_test.go index 3412675a..96803133 100644 --- a/handler/openid/flow_hybrid_test.go +++ b/handler/openid/flow_hybrid_test.go @@ -27,9 +27,11 @@ import ( ) var hmacStrategy = &oauth2.HMACSHAStrategy{ - Enigma: &hmac.HMACStrategy{ - Config: &fosite.Config{ - GlobalSecret: []byte("some-super-cool-secret-that-nobody-knows-nobody-knows"), + BaseHMACSHAStrategy: &oauth2.BaseHMACSHAStrategy{ + Enigma: &hmac.HMACStrategy{ + Config: &fosite.Config{ + GlobalSecret: []byte("some-super-cool-secret-that-nobody-knows-nobody-knows"), + }, }, }, } diff --git a/handler/pkce/handler_test.go b/handler/pkce/handler_test.go index bdd50650..393f0bf1 100644 --- a/handler/pkce/handler_test.go +++ b/handler/pkce/handler_test.go @@ -38,9 +38,11 @@ func (m *mockCodeStrategy) ValidateAuthorizeCode(ctx context.Context, requester func TestPKCEHandleAuthorizeEndpointRequest(t *testing.T) { var config fosite.Config h := &Handler{ - Storage: storage.NewMemoryStore(), - AuthorizeCodeStrategy: new(oauth2.HMACSHAStrategy), - Config: &config, + Storage: storage.NewMemoryStore(), + AuthorizeCodeStrategy: &oauth2.HMACSHAStrategy{ + BaseHMACSHAStrategy: new(oauth2.BaseHMACSHAStrategy), + }, + Config: &config, } w := fosite.NewAuthorizeResponse() r := fosite.NewAuthorizeRequest() diff --git a/integration/helper_setup_test.go b/integration/helper_setup_test.go index 62a99d4f..1381608e 100644 --- a/integration/helper_setup_test.go +++ b/integration/helper_setup_test.go @@ -173,15 +173,17 @@ func newJWTBearerAppClient(ts *httptest.Server) *clients.JWTBearer { } var hmacStrategy = &oauth2.HMACSHAStrategy{ - Enigma: &hmac.HMACStrategy{ + BaseHMACSHAStrategy: &oauth2.BaseHMACSHAStrategy{ + Enigma: &hmac.HMACStrategy{ + Config: &fosite.Config{ + GlobalSecret: []byte("some-super-cool-secret-that-nobody-knows"), + }, + }, Config: &fosite.Config{ - GlobalSecret: []byte("some-super-cool-secret-that-nobody-knows"), + AccessTokenLifespan: accessTokenLifespan, + AuthorizeCodeLifespan: authCodeLifespan, }, }, - Config: &fosite.Config{ - AccessTokenLifespan: accessTokenLifespan, - AuthorizeCodeLifespan: authCodeLifespan, - }, } var defaultRSAKey = gen.MustRSAKey()