2023-06-22 22:12:33 +00:00
|
|
|
// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved.
|
2020-12-11 16:01:07 +00:00
|
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
|
2023-06-22 22:12:33 +00:00
|
|
|
package strategy
|
2020-12-11 16:01:07 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2022-04-13 17:13:27 +00:00
|
|
|
"strings"
|
2020-12-11 16:01:07 +00:00
|
|
|
|
|
|
|
"github.com/ory/fosite"
|
|
|
|
"github.com/ory/fosite/compose"
|
|
|
|
"github.com/ory/fosite/handler/oauth2"
|
2022-04-13 17:13:27 +00:00
|
|
|
errorsx "github.com/pkg/errors"
|
2023-06-22 22:12:33 +00:00
|
|
|
|
|
|
|
"go.pinniped.dev/internal/federationdomain/storage"
|
2022-04-13 17:13:27 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
2022-12-14 00:18:51 +00:00
|
|
|
pinAccessTokenPrefix = "pin_at_" // "Pinniped access token" abbreviated.
|
|
|
|
oryAccessTokenPrefix = "ory_at_"
|
|
|
|
|
|
|
|
pinRefreshTokenPrefix = "pin_rt_" // "Pinniped refresh token" abbreviated.
|
|
|
|
oryRefreshTokenPrefix = "ory_rt_"
|
|
|
|
|
|
|
|
pinAuthcodePrefix = "pin_ac_" // "Pinniped authorization code" abbreviated.
|
|
|
|
oryAuthcodePrefix = "ory_ac_"
|
2020-12-11 16:01:07 +00:00
|
|
|
)
|
|
|
|
|
2023-06-22 22:12:33 +00:00
|
|
|
// DynamicOauth2HMACStrategy is an oauth2.CoreStrategy that can dynamically load an HMAC key to sign
|
2020-12-11 16:01:07 +00:00
|
|
|
// stuff (access tokens, refresh tokens, and auth codes). We want this dynamic capability since our
|
2020-12-16 22:27:09 +00:00
|
|
|
// controllers for loading FederationDomain's and signing keys run in parallel, and thus the signing key
|
|
|
|
// might not be ready when an FederationDomain is otherwise ready.
|
2020-12-11 16:01:07 +00:00
|
|
|
//
|
2020-12-16 22:27:09 +00:00
|
|
|
// If we ever update FederationDomain's to hold their signing key, we might not need this type, since we
|
|
|
|
// could have an invariant that routes to an FederationDomain's endpoints are only wired up if an
|
|
|
|
// FederationDomain has a valid signing key.
|
2022-04-13 17:13:27 +00:00
|
|
|
//
|
|
|
|
// Tokens start with a custom prefix to make them identifiable as tokens when seen by a user
|
2022-12-14 00:18:51 +00:00
|
|
|
// out of context, such as when accidentally committed to a GitHub repo. After we implemented the
|
|
|
|
// custom prefix feature, fosite later added the same feature, but did not make the prefix customizable.
|
|
|
|
// Therefore, this code has been updated to replace the fosite prefix with our custom prefix.
|
2023-06-22 22:12:33 +00:00
|
|
|
type DynamicOauth2HMACStrategy struct {
|
2022-12-14 00:18:51 +00:00
|
|
|
fositeConfig *fosite.Config
|
2020-12-11 16:01:07 +00:00
|
|
|
keyFunc func() []byte
|
|
|
|
}
|
|
|
|
|
2023-06-22 22:12:33 +00:00
|
|
|
var _ oauth2.CoreStrategy = &DynamicOauth2HMACStrategy{}
|
2020-12-11 16:01:07 +00:00
|
|
|
|
2023-06-22 22:12:33 +00:00
|
|
|
func NewDynamicOauth2HMACStrategy(
|
2022-12-14 00:18:51 +00:00
|
|
|
fositeConfig *fosite.Config,
|
2020-12-11 16:01:07 +00:00
|
|
|
keyFunc func() []byte,
|
2023-06-22 22:12:33 +00:00
|
|
|
) *DynamicOauth2HMACStrategy {
|
|
|
|
return &DynamicOauth2HMACStrategy{
|
2020-12-11 16:01:07 +00:00
|
|
|
fositeConfig: fositeConfig,
|
|
|
|
keyFunc: keyFunc,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-14 00:18:51 +00:00
|
|
|
func replacePrefix(s, prefixToReplace, newPrefix string) string {
|
|
|
|
return newPrefix + strings.TrimPrefix(s, prefixToReplace)
|
|
|
|
}
|
|
|
|
|
2023-06-22 22:12:33 +00:00
|
|
|
func (s *DynamicOauth2HMACStrategy) AccessTokenSignature(ctx context.Context, token string) string {
|
2022-12-14 00:18:51 +00:00
|
|
|
return s.delegate().AccessTokenSignature(ctx, token)
|
2020-12-11 16:01:07 +00:00
|
|
|
}
|
|
|
|
|
2023-06-22 22:12:33 +00:00
|
|
|
func (s *DynamicOauth2HMACStrategy) GenerateAccessToken(
|
2020-12-11 16:01:07 +00:00
|
|
|
ctx context.Context,
|
|
|
|
requester fosite.Requester,
|
2022-12-14 00:18:51 +00:00
|
|
|
) (string, string, error) {
|
2022-04-13 17:13:27 +00:00
|
|
|
token, sig, err := s.delegate().GenerateAccessToken(ctx, requester)
|
|
|
|
if err == nil {
|
2022-12-14 00:18:51 +00:00
|
|
|
if !strings.HasPrefix(token, oryAccessTokenPrefix) {
|
|
|
|
// This would only happen if fosite changed how it generates tokens. Defensive programming here.
|
|
|
|
return "", "", errorsx.WithStack(fosite.ErrInvalidTokenFormat.
|
|
|
|
WithDebugf("Generated token does not have expected prefix"))
|
|
|
|
}
|
|
|
|
token = replacePrefix(token, oryAccessTokenPrefix, pinAccessTokenPrefix)
|
2022-04-13 17:13:27 +00:00
|
|
|
}
|
|
|
|
return token, sig, err
|
2020-12-11 16:01:07 +00:00
|
|
|
}
|
|
|
|
|
2023-06-22 22:12:33 +00:00
|
|
|
func (s *DynamicOauth2HMACStrategy) ValidateAccessToken(
|
2020-12-11 16:01:07 +00:00
|
|
|
ctx context.Context,
|
|
|
|
requester fosite.Requester,
|
|
|
|
token string,
|
2022-12-14 00:18:51 +00:00
|
|
|
) error {
|
|
|
|
if !strings.HasPrefix(token, pinAccessTokenPrefix) {
|
2022-04-13 17:13:27 +00:00
|
|
|
return errorsx.WithStack(fosite.ErrInvalidTokenFormat.
|
2022-12-14 00:18:51 +00:00
|
|
|
WithDebugf("Access token did not have prefix %q", pinAccessTokenPrefix))
|
2022-04-13 17:13:27 +00:00
|
|
|
}
|
2022-12-14 00:18:51 +00:00
|
|
|
return s.delegate().ValidateAccessToken(ctx, requester, replacePrefix(token, pinAccessTokenPrefix, oryAccessTokenPrefix))
|
2020-12-11 16:01:07 +00:00
|
|
|
}
|
|
|
|
|
2023-06-22 22:12:33 +00:00
|
|
|
func (s *DynamicOauth2HMACStrategy) RefreshTokenSignature(ctx context.Context, token string) string {
|
2022-12-14 00:18:51 +00:00
|
|
|
return s.delegate().RefreshTokenSignature(ctx, token)
|
2020-12-11 16:01:07 +00:00
|
|
|
}
|
|
|
|
|
2023-06-22 22:12:33 +00:00
|
|
|
func (s *DynamicOauth2HMACStrategy) GenerateRefreshToken(
|
2020-12-11 16:01:07 +00:00
|
|
|
ctx context.Context,
|
|
|
|
requester fosite.Requester,
|
2022-12-14 00:18:51 +00:00
|
|
|
) (string, string, error) {
|
2022-04-13 17:13:27 +00:00
|
|
|
token, sig, err := s.delegate().GenerateRefreshToken(ctx, requester)
|
|
|
|
if err == nil {
|
2022-12-14 00:18:51 +00:00
|
|
|
if !strings.HasPrefix(token, oryRefreshTokenPrefix) {
|
|
|
|
// This would only happen if fosite changed how it generates tokens. Defensive programming here.
|
|
|
|
return "", "", errorsx.WithStack(fosite.ErrInvalidTokenFormat.
|
|
|
|
WithDebugf("Generated token does not have expected prefix"))
|
|
|
|
}
|
|
|
|
token = replacePrefix(token, oryRefreshTokenPrefix, pinRefreshTokenPrefix)
|
2022-04-13 17:13:27 +00:00
|
|
|
}
|
|
|
|
return token, sig, err
|
2020-12-11 16:01:07 +00:00
|
|
|
}
|
|
|
|
|
2023-06-22 22:12:33 +00:00
|
|
|
func (s *DynamicOauth2HMACStrategy) ValidateRefreshToken(
|
2020-12-11 16:01:07 +00:00
|
|
|
ctx context.Context,
|
|
|
|
requester fosite.Requester,
|
|
|
|
token string,
|
2022-12-14 00:18:51 +00:00
|
|
|
) error {
|
|
|
|
if !strings.HasPrefix(token, pinRefreshTokenPrefix) {
|
2022-04-13 17:13:27 +00:00
|
|
|
return errorsx.WithStack(fosite.ErrInvalidTokenFormat.
|
2022-12-14 00:18:51 +00:00
|
|
|
WithDebugf("Refresh token did not have prefix %q", pinRefreshTokenPrefix))
|
2022-04-13 17:13:27 +00:00
|
|
|
}
|
2022-12-14 00:18:51 +00:00
|
|
|
return s.delegate().ValidateRefreshToken(ctx, requester, replacePrefix(token, pinRefreshTokenPrefix, oryRefreshTokenPrefix))
|
2020-12-11 16:01:07 +00:00
|
|
|
}
|
|
|
|
|
2023-06-22 22:12:33 +00:00
|
|
|
func (s *DynamicOauth2HMACStrategy) AuthorizeCodeSignature(ctx context.Context, token string) string {
|
2022-12-14 00:18:51 +00:00
|
|
|
return s.delegate().AuthorizeCodeSignature(ctx, token)
|
2020-12-11 16:01:07 +00:00
|
|
|
}
|
|
|
|
|
2023-06-22 22:12:33 +00:00
|
|
|
func (s *DynamicOauth2HMACStrategy) GenerateAuthorizeCode(
|
2020-12-11 16:01:07 +00:00
|
|
|
ctx context.Context,
|
|
|
|
requester fosite.Requester,
|
2022-12-14 00:18:51 +00:00
|
|
|
) (string, string, error) {
|
2022-04-13 17:13:27 +00:00
|
|
|
authcode, sig, err := s.delegate().GenerateAuthorizeCode(ctx, requester)
|
|
|
|
if err == nil {
|
2022-12-14 00:18:51 +00:00
|
|
|
if !strings.HasPrefix(authcode, oryAuthcodePrefix) {
|
|
|
|
// This would only happen if fosite changed how it generates tokens. Defensive programming here.
|
|
|
|
return "", "", errorsx.WithStack(fosite.ErrInvalidTokenFormat.
|
|
|
|
WithDebugf("Generated token does not have expected prefix"))
|
|
|
|
}
|
|
|
|
authcode = replacePrefix(authcode, oryAuthcodePrefix, pinAuthcodePrefix)
|
2022-04-13 17:13:27 +00:00
|
|
|
}
|
|
|
|
return authcode, sig, err
|
2020-12-11 16:01:07 +00:00
|
|
|
}
|
|
|
|
|
2023-06-22 22:12:33 +00:00
|
|
|
func (s *DynamicOauth2HMACStrategy) ValidateAuthorizeCode(
|
2020-12-11 16:01:07 +00:00
|
|
|
ctx context.Context,
|
|
|
|
requester fosite.Requester,
|
|
|
|
token string,
|
2022-12-14 00:18:51 +00:00
|
|
|
) error {
|
|
|
|
if !strings.HasPrefix(token, pinAuthcodePrefix) {
|
2022-04-13 17:13:27 +00:00
|
|
|
return errorsx.WithStack(fosite.ErrInvalidTokenFormat.
|
2022-12-14 00:18:51 +00:00
|
|
|
WithDebugf("Authorization code did not have prefix %q", pinAuthcodePrefix))
|
2022-04-13 17:13:27 +00:00
|
|
|
}
|
2022-12-14 00:18:51 +00:00
|
|
|
return s.delegate().ValidateAuthorizeCode(ctx, requester, replacePrefix(token, pinAuthcodePrefix, oryAuthcodePrefix))
|
2020-12-11 16:01:07 +00:00
|
|
|
}
|
|
|
|
|
2023-06-22 22:12:33 +00:00
|
|
|
func (s *DynamicOauth2HMACStrategy) delegate() *oauth2.HMACSHAStrategy {
|
|
|
|
return compose.NewOAuth2HMACStrategy(storage.NewDynamicGlobalSecretConfig(s.fositeConfig, s.keyFunc))
|
2020-12-11 16:01:07 +00:00
|
|
|
}
|