134 lines
4.3 KiB
Go
134 lines
4.3 KiB
Go
// Copyright 2020-2022 the Pinniped contributors. All Rights Reserved.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package oidc
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
|
|
"github.com/ory/fosite"
|
|
"github.com/ory/fosite/compose"
|
|
"github.com/ory/fosite/handler/oauth2"
|
|
errorsx "github.com/pkg/errors"
|
|
)
|
|
|
|
const (
|
|
accessTokenPrefix = "pin_at_" // "Pinniped access token" abbreviated.
|
|
refreshTokenPrefix = "pin_rt_" // "Pinniped refresh token" abbreviated.
|
|
authcodePrefix = "pin_ac_" // "Pinniped authorization code" abbreviated.
|
|
)
|
|
|
|
// dynamicOauth2HMACStrategy is an oauth2.CoreStrategy that can dynamically load an HMAC key to sign
|
|
// stuff (access tokens, refresh tokens, and auth codes). We want this dynamic capability since our
|
|
// 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.
|
|
//
|
|
// 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.
|
|
//
|
|
// Tokens start with a custom prefix to make them identifiable as tokens when seen by a user
|
|
// out of context, such as when accidentally committed to a GitHub repo.
|
|
type dynamicOauth2HMACStrategy struct {
|
|
fositeConfig *compose.Config
|
|
keyFunc func() []byte
|
|
}
|
|
|
|
var _ oauth2.CoreStrategy = &dynamicOauth2HMACStrategy{}
|
|
|
|
func newDynamicOauth2HMACStrategy(
|
|
fositeConfig *compose.Config,
|
|
keyFunc func() []byte,
|
|
) *dynamicOauth2HMACStrategy {
|
|
return &dynamicOauth2HMACStrategy{
|
|
fositeConfig: fositeConfig,
|
|
keyFunc: keyFunc,
|
|
}
|
|
}
|
|
|
|
func (s *dynamicOauth2HMACStrategy) AccessTokenSignature(token string) string {
|
|
return s.delegate().AccessTokenSignature(token)
|
|
}
|
|
|
|
func (s *dynamicOauth2HMACStrategy) GenerateAccessToken(
|
|
ctx context.Context,
|
|
requester fosite.Requester,
|
|
) (token string, signature string, err error) {
|
|
token, sig, err := s.delegate().GenerateAccessToken(ctx, requester)
|
|
if err == nil {
|
|
token = accessTokenPrefix + token
|
|
}
|
|
return token, sig, err
|
|
}
|
|
|
|
func (s *dynamicOauth2HMACStrategy) ValidateAccessToken(
|
|
ctx context.Context,
|
|
requester fosite.Requester,
|
|
token string,
|
|
) (err error) {
|
|
if !strings.HasPrefix(token, accessTokenPrefix) {
|
|
return errorsx.WithStack(fosite.ErrInvalidTokenFormat.
|
|
WithDebugf("Access token did not have prefix %q", accessTokenPrefix))
|
|
}
|
|
return s.delegate().ValidateAccessToken(ctx, requester, token[len(accessTokenPrefix):])
|
|
}
|
|
|
|
func (s *dynamicOauth2HMACStrategy) RefreshTokenSignature(token string) string {
|
|
return s.delegate().RefreshTokenSignature(token)
|
|
}
|
|
|
|
func (s *dynamicOauth2HMACStrategy) GenerateRefreshToken(
|
|
ctx context.Context,
|
|
requester fosite.Requester,
|
|
) (token string, signature string, err error) {
|
|
token, sig, err := s.delegate().GenerateRefreshToken(ctx, requester)
|
|
if err == nil {
|
|
token = refreshTokenPrefix + token
|
|
}
|
|
return token, sig, err
|
|
}
|
|
|
|
func (s *dynamicOauth2HMACStrategy) ValidateRefreshToken(
|
|
ctx context.Context,
|
|
requester fosite.Requester,
|
|
token string,
|
|
) (err error) {
|
|
if !strings.HasPrefix(token, refreshTokenPrefix) {
|
|
return errorsx.WithStack(fosite.ErrInvalidTokenFormat.
|
|
WithDebugf("Refresh token did not have prefix %q", refreshTokenPrefix))
|
|
}
|
|
return s.delegate().ValidateRefreshToken(ctx, requester, token[len(refreshTokenPrefix):])
|
|
}
|
|
|
|
func (s *dynamicOauth2HMACStrategy) AuthorizeCodeSignature(token string) string {
|
|
return s.delegate().AuthorizeCodeSignature(token)
|
|
}
|
|
|
|
func (s *dynamicOauth2HMACStrategy) GenerateAuthorizeCode(
|
|
ctx context.Context,
|
|
requester fosite.Requester,
|
|
) (token string, signature string, err error) {
|
|
authcode, sig, err := s.delegate().GenerateAuthorizeCode(ctx, requester)
|
|
if err == nil {
|
|
authcode = authcodePrefix + authcode
|
|
}
|
|
return authcode, sig, err
|
|
}
|
|
|
|
func (s *dynamicOauth2HMACStrategy) ValidateAuthorizeCode(
|
|
ctx context.Context,
|
|
requester fosite.Requester,
|
|
token string,
|
|
) (err error) {
|
|
if !strings.HasPrefix(token, authcodePrefix) {
|
|
return errorsx.WithStack(fosite.ErrInvalidTokenFormat.
|
|
WithDebugf("Authorization code did not have prefix %q", authcodePrefix))
|
|
}
|
|
return s.delegate().ValidateAuthorizeCode(ctx, requester, token[len(authcodePrefix):])
|
|
}
|
|
|
|
func (s *dynamicOauth2HMACStrategy) delegate() *oauth2.HMACSHAStrategy {
|
|
return compose.NewOAuth2HMACStrategy(s.fositeConfig, s.keyFunc(), nil)
|
|
}
|