Skip to content

Commit

Permalink
refactor: split HMAC SHA strategy (#813)
Browse files Browse the repository at this point in the history
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,
+		},
	},
}
```
  • Loading branch information
aeneasr committed Jul 9, 2024
1 parent 886248b commit 576230a
Show file tree
Hide file tree
Showing 7 changed files with 109 additions and 57 deletions.
6 changes: 4 additions & 2 deletions compose/compose_strategy.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
}
}

Expand Down
60 changes: 21 additions & 39 deletions handler/oauth2/strategy_hmacsha.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ package oauth2

import (
"context"
"fmt"
"strings"
"time"

"github.com/ory/x/errorsx"
Expand All @@ -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))))
Expand All @@ -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))))
Expand All @@ -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)
}
60 changes: 60 additions & 0 deletions handler/oauth2/strategy_hmacsha_prefixed.go
Original file line number Diff line number Diff line change
@@ -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"))
}
10 changes: 6 additions & 4 deletions handler/oauth2/strategy_hmacsha_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
},
}

Expand Down
8 changes: 5 additions & 3 deletions handler/openid/flow_hybrid_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
},
},
},
}
Expand Down
8 changes: 5 additions & 3 deletions handler/pkce/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
14 changes: 8 additions & 6 deletions integration/helper_setup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down

0 comments on commit 576230a

Please sign in to comment.