feat: token exchange in op (rfc 6749)
This commit is contained in:
parent
205f2c4a30
commit
42a965796b
7 changed files with 513 additions and 16 deletions
|
@ -31,6 +31,7 @@ type AccessTokenClaims interface {
|
|||
GetSubject() string
|
||||
GetTokenID() string
|
||||
SetPrivateClaims(map[string]interface{})
|
||||
GetClaims() map[string]interface{}
|
||||
}
|
||||
|
||||
type IDTokenClaims interface {
|
||||
|
@ -151,6 +152,11 @@ func (a *accessTokenClaims) SetPrivateClaims(claims map[string]interface{}) {
|
|||
a.claims = claims
|
||||
}
|
||||
|
||||
// GetClaims implements the AccessTokenClaims interface
|
||||
func (a *accessTokenClaims) GetClaims() map[string]interface{} {
|
||||
return a.claims
|
||||
}
|
||||
|
||||
func (a *accessTokenClaims) MarshalJSON() ([]byte, error) {
|
||||
type Alias accessTokenClaims
|
||||
s := &struct {
|
||||
|
@ -612,3 +618,12 @@ func GenerateJWTProfileToken(assertion JWTProfileAssertionClaims) (string, error
|
|||
}
|
||||
return signedAssertion.CompactSerialize()
|
||||
}
|
||||
|
||||
type TokenExchangeResponse struct {
|
||||
AccessToken string `json:"access_token"` // Can be access token or ID token
|
||||
IssuedTokenType TokenType `json:"issued_token_type"`
|
||||
TokenType string `json:"token_type"`
|
||||
ExpiresIn uint64 `json:"expires_in,omitempty"`
|
||||
Scopes SpaceDelimitedArray `json:"scope,omitempty"`
|
||||
RefreshToken string `json:"refresh_token,omitempty"`
|
||||
}
|
||||
|
|
|
@ -40,6 +40,29 @@ var AllGrantTypes = []GrantType{
|
|||
|
||||
type GrantType string
|
||||
|
||||
const (
|
||||
AccessTokenType TokenType = "urn:ietf:params:oauth:token-type:access_token"
|
||||
RefreshTokenType TokenType = "urn:ietf:params:oauth:token-type:refresh_token"
|
||||
IDTokenType TokenType = "urn:ietf:params:oauth:token-type:id_token"
|
||||
JWTTokenType TokenType = "urn:ietf:params:oauth:token-type:jwt"
|
||||
)
|
||||
|
||||
var AllTokenTypes = []TokenType{
|
||||
AccessTokenType, RefreshTokenType, IDTokenType, JWTTokenType,
|
||||
}
|
||||
|
||||
type TokenType string
|
||||
|
||||
func (t TokenType) IsValid() bool {
|
||||
for _, tt := range AllTokenTypes {
|
||||
if t == tt {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
type TokenRequest interface {
|
||||
// GrantType GrantType `schema:"grant_type"`
|
||||
GrantType() GrantType
|
||||
|
@ -203,14 +226,15 @@ func (j *JWTTokenRequest) GetScopes() []string {
|
|||
}
|
||||
|
||||
type TokenExchangeRequest struct {
|
||||
subjectToken string `schema:"subject_token"`
|
||||
subjectTokenType string `schema:"subject_token_type"`
|
||||
actorToken string `schema:"actor_token"`
|
||||
actorTokenType string `schema:"actor_token_type"`
|
||||
resource []string `schema:"resource"`
|
||||
audience Audience `schema:"audience"`
|
||||
Scope SpaceDelimitedArray `schema:"scope"`
|
||||
requestedTokenType string `schema:"requested_token_type"`
|
||||
GrantType GrantType `schema:"grant_type"`
|
||||
SubjectToken string `schema:"subject_token"`
|
||||
SubjectTokenType TokenType `schema:"subject_token_type"`
|
||||
ActorToken string `schema:"actor_token"`
|
||||
ActorTokenType TokenType `schema:"actor_token_type"`
|
||||
Resource []string `schema:"resource"`
|
||||
Audience Audience `schema:"audience"`
|
||||
Scopes SpaceDelimitedArray `schema:"scope"`
|
||||
RequestedTokenType TokenType `schema:"requested_token_type"`
|
||||
}
|
||||
|
||||
type ClientCredentialsRequest struct {
|
||||
|
|
|
@ -190,7 +190,7 @@ type openidProvider struct {
|
|||
interceptors []HttpInterceptor
|
||||
timer <-chan time.Time
|
||||
accessTokenVerifierOpts []AccessTokenVerifierOpt
|
||||
idTokenHintVerifierOpts []IDTokenHintVerifierOpt
|
||||
idTokenHintVerifierOpts []IDTokenHintVerifierOpt
|
||||
}
|
||||
|
||||
func (o *openidProvider) Issuer() string {
|
||||
|
@ -246,7 +246,8 @@ func (o *openidProvider) GrantTypeRefreshTokenSupported() bool {
|
|||
}
|
||||
|
||||
func (o *openidProvider) GrantTypeTokenExchangeSupported() bool {
|
||||
return false
|
||||
_, ok := o.storage.(TokenExchangeStorage)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (o *openidProvider) GrantTypeJWTAuthorizationSupported() bool {
|
||||
|
|
|
@ -24,6 +24,8 @@ type AuthStorage interface {
|
|||
//
|
||||
// * *oidc.JWTTokenRequest from a JWT that is the assertion value of a JWT Profile
|
||||
// Grant: https://datatracker.ietf.org/doc/html/rfc7523#section-2.1
|
||||
//
|
||||
// * TokenExchangeRequest
|
||||
CreateAccessToken(context.Context, TokenRequest) (accessTokenID string, expiration time.Time, err error)
|
||||
|
||||
// The TokenRequest parameter of CreateAccessAndRefreshTokens can be any of:
|
||||
|
@ -35,6 +37,8 @@ type AuthStorage interface {
|
|||
// * AuthRequest as by returned by the AuthRequestByID or AuthRequestByCode (above).
|
||||
// Used for the authorization code flow which requested offline_access scope and
|
||||
// registered the refresh_token grant type in advance
|
||||
//
|
||||
// * TokenExchangeRequest
|
||||
CreateAccessAndRefreshTokens(ctx context.Context, request TokenRequest, currentRefreshToken string) (accessTokenID string, newRefreshTokenID string, expiration time.Time, err error)
|
||||
TokenRequestByRefreshToken(ctx context.Context, refreshTokenID string) (RefreshTokenRequest, error)
|
||||
|
||||
|
@ -54,6 +58,45 @@ type ClientCredentialsStorage interface {
|
|||
ClientCredentialsTokenRequest(ctx context.Context, clientID string, scopes []string) (TokenRequest, error)
|
||||
}
|
||||
|
||||
type TokenExchangeStorage interface {
|
||||
// ValidateTokenExchangeRequest will be called to validate parsed (including tokens) Token Exchange Grant request.
|
||||
//
|
||||
// Important validations can include:
|
||||
// - permissions
|
||||
// - set requested token type to some default value if it is empty (rfc 8693 allows it) using SetRequestedTokenType method.
|
||||
// Depending on RequestedTokenType - the following tokens will be issued:
|
||||
// - RefreshTokenType - both access and refresh tokens
|
||||
// - AccessTokenType - only access token
|
||||
// - IDTokenType - only id token
|
||||
// - validation of subject's token type on possibility to be exchanged to the requested token type (according to your requirements)
|
||||
// - scopes (and update them using SetCurrentScopes method)
|
||||
// - set new subject if it differs from exchange subject (impersonation flow)
|
||||
//
|
||||
// Request will include subject's and/or actor's token claims if correspinding tokens are access/id_token issued by op
|
||||
// or third party tokens parsed by TokenExchangeTokensVerifierStorage interface methods.
|
||||
ValidateTokenExchangeRequest(ctx context.Context, request TokenExchangeRequest) error
|
||||
|
||||
// CreateTokenExchangeRequest will be called after parsing and validating token exchange request.
|
||||
// Stored request is not accessed later by op - so it is up to implementer to decide
|
||||
// should this method actually store the request or not (common use case - store for it for audit purposes)
|
||||
CreateTokenExchangeRequest(ctx context.Context, request TokenExchangeRequest) error
|
||||
|
||||
// GetPrivateClaimsFromTokenExchangeRequest will be called during access token creation.
|
||||
// Claims evaluation can be based on all validated request data available, including: scopes, resource, audience, etc.
|
||||
GetPrivateClaimsFromTokenExchangeRequest(ctx context.Context, request TokenExchangeRequest) (claims map[string]interface{}, err error)
|
||||
|
||||
// SetUserinfoFromTokenExchangeRequest will be called during id token creation.
|
||||
// Claims evaluation can be based on all validated request data available, including: scopes, resource, audience, etc.
|
||||
SetUserinfoFromTokenExchangeRequest(ctx context.Context, userinfo oidc.UserInfoSetter, request TokenExchangeRequest) error
|
||||
}
|
||||
|
||||
// TokenExchangeTokensVerifierStorage is an optional interface used in token exchange process to verify tokens
|
||||
// issued by third-party applications. If interface is not implemented - only tokens issued by op will be exchanged.
|
||||
type TokenExchangeTokensVerifierStorage interface {
|
||||
VerifyExchangeSubjectToken(ctx context.Context, token string, tokenType oidc.TokenType) (tokenIDOrToken string, subject string, tokenClaims map[string]interface{}, err error)
|
||||
VerifyExchangeActorToken(ctx context.Context, token string, tokenType oidc.TokenType) (tokenIDOrToken string, actor string, tokenClaims map[string]interface{}, err error)
|
||||
}
|
||||
|
||||
type OPStorage interface {
|
||||
GetClientByClientID(ctx context.Context, clientID string) (Client, error)
|
||||
AuthorizeClientIDSecret(ctx context.Context, clientID, clientSecret string) error
|
||||
|
|
|
@ -69,6 +69,8 @@ func needsRefreshToken(tokenRequest TokenRequest, client Client) bool {
|
|||
switch req := tokenRequest.(type) {
|
||||
case AuthRequest:
|
||||
return strings.Contains(req.GetScopes(), oidc.ScopeOfflineAccess) && req.GetResponseType() == oidc.ResponseTypeCode && ValidateGrantType(client, oidc.GrantTypeRefreshToken)
|
||||
case TokenExchangeRequest:
|
||||
return req.GetRequestedTokenType() == oidc.RefreshTokenType
|
||||
case RefreshTokenRequest:
|
||||
return true
|
||||
default:
|
||||
|
@ -102,7 +104,20 @@ func CreateJWT(ctx context.Context, issuer string, tokenRequest TokenRequest, ex
|
|||
claims := oidc.NewAccessTokenClaims(issuer, tokenRequest.GetSubject(), tokenRequest.GetAudience(), exp, id, client.GetID(), client.ClockSkew())
|
||||
if client != nil {
|
||||
restrictedScopes := client.RestrictAdditionalAccessTokenScopes()(tokenRequest.GetScopes())
|
||||
privateClaims, err := storage.GetPrivateClaimsFromScopes(ctx, tokenRequest.GetSubject(), client.GetID(), removeUserinfoScopes(restrictedScopes))
|
||||
|
||||
var (
|
||||
privateClaims map[string]interface{}
|
||||
err error
|
||||
)
|
||||
if tokenExchangeRequest, ok := tokenRequest.(TokenExchangeRequest); ok {
|
||||
privateClaims, err = storage.(TokenExchangeStorage).GetPrivateClaimsFromTokenExchangeRequest(
|
||||
ctx,
|
||||
tokenExchangeRequest,
|
||||
)
|
||||
} else {
|
||||
privateClaims, err = storage.GetPrivateClaimsFromScopes(ctx, tokenRequest.GetSubject(), client.GetID(), removeUserinfoScopes(restrictedScopes))
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
@ -139,7 +154,16 @@ func CreateIDToken(ctx context.Context, issuer string, request IDTokenRequest, v
|
|||
scopes = removeUserinfoScopes(scopes)
|
||||
}
|
||||
}
|
||||
if len(scopes) > 0 {
|
||||
|
||||
if tokenExchangeRequest, ok := request.(TokenExchangeRequest); ok {
|
||||
userInfo := oidc.NewUserInfo()
|
||||
teStorage := storage.(TokenExchangeStorage)
|
||||
err := teStorage.SetUserinfoFromTokenExchangeRequest(ctx, userInfo, tokenExchangeRequest)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
claims.SetUserinfo(userInfo)
|
||||
} else if len(scopes) > 0 {
|
||||
userInfo := oidc.NewUserInfo()
|
||||
err := storage.SetUserinfoFromScopes(ctx, userInfo, request.GetSubject(), request.GetClientID(), scopes)
|
||||
if err != nil {
|
||||
|
|
|
@ -1,11 +1,399 @@
|
|||
package op
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"context"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
httphelper "github.com/zitadel/oidc/pkg/http"
|
||||
"github.com/zitadel/oidc/pkg/oidc"
|
||||
)
|
||||
|
||||
// TokenExchange will handle the OAuth 2.0 token exchange grant ("urn:ietf:params:oauth:grant-type:token-exchange")
|
||||
func TokenExchange(w http.ResponseWriter, r *http.Request, exchanger Exchanger) {
|
||||
RequestError(w, r, errors.New("unimplemented"))
|
||||
type TokenExchangeRequest interface {
|
||||
GetAMR() []string
|
||||
GetAudience() []string
|
||||
GetResourses() []string
|
||||
GetAuthTime() time.Time
|
||||
GetClientID() string
|
||||
GetScopes() []string
|
||||
GetSubject() string
|
||||
GetRequestedTokenType() oidc.TokenType
|
||||
|
||||
GetExchangeSubject() string
|
||||
GetExchangeSubjectTokenType() oidc.TokenType
|
||||
GetExchangeSubjectTokenIDOrToken() string
|
||||
GetExchangeSubjectTokenClaims() map[string]interface{}
|
||||
|
||||
GetExchangeActor() string
|
||||
GetExchangeActorTokenType() oidc.TokenType
|
||||
GetExchangeActorTokenIDOrToken() string
|
||||
GetExchangeActorTokenClaims() map[string]interface{}
|
||||
|
||||
SetCurrentScopes(scopes []string)
|
||||
SetRequestedTokenType(tt oidc.TokenType)
|
||||
SetSubject(subject string)
|
||||
}
|
||||
|
||||
type tokenExchangeRequest struct {
|
||||
exchangeSubjectTokenIDOrToken string
|
||||
exchangeSubjectTokenType oidc.TokenType
|
||||
exchangeSubject string
|
||||
exchangeSubjectTokenClaims map[string]interface{}
|
||||
|
||||
exchangeActorTokenIDOrToken string
|
||||
exchangeActorTokenType oidc.TokenType
|
||||
exchangeActor string
|
||||
exchangeActorTokenClaims map[string]interface{}
|
||||
|
||||
resource []string
|
||||
audience oidc.Audience
|
||||
scopes oidc.SpaceDelimitedArray
|
||||
requestedTokenType oidc.TokenType
|
||||
clientID string
|
||||
authTime time.Time
|
||||
subject string
|
||||
}
|
||||
|
||||
func (r *tokenExchangeRequest) GetAMR() []string {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
func (r *tokenExchangeRequest) GetAudience() []string {
|
||||
return r.audience
|
||||
}
|
||||
|
||||
func (r *tokenExchangeRequest) GetResourses() []string {
|
||||
return r.resource
|
||||
}
|
||||
|
||||
func (r *tokenExchangeRequest) GetAuthTime() time.Time {
|
||||
return r.authTime
|
||||
}
|
||||
|
||||
func (r *tokenExchangeRequest) GetClientID() string {
|
||||
return r.clientID
|
||||
}
|
||||
|
||||
func (r *tokenExchangeRequest) GetScopes() []string {
|
||||
return r.scopes
|
||||
}
|
||||
|
||||
func (r *tokenExchangeRequest) GetRequestedTokenType() oidc.TokenType {
|
||||
return r.requestedTokenType
|
||||
}
|
||||
|
||||
func (r *tokenExchangeRequest) GetExchangeSubject() string {
|
||||
return r.exchangeSubject
|
||||
}
|
||||
|
||||
func (r *tokenExchangeRequest) GetExchangeSubjectTokenType() oidc.TokenType {
|
||||
return r.exchangeSubjectTokenType
|
||||
}
|
||||
|
||||
func (r *tokenExchangeRequest) GetExchangeSubjectTokenIDOrToken() string {
|
||||
return r.exchangeSubjectTokenIDOrToken
|
||||
}
|
||||
|
||||
func (r *tokenExchangeRequest) GetExchangeSubjectTokenClaims() map[string]interface{} {
|
||||
return r.exchangeSubjectTokenClaims
|
||||
}
|
||||
|
||||
func (r *tokenExchangeRequest) GetExchangeActor() string {
|
||||
return r.exchangeActor
|
||||
}
|
||||
|
||||
func (r *tokenExchangeRequest) GetExchangeActorTokenType() oidc.TokenType {
|
||||
return r.exchangeActorTokenType
|
||||
}
|
||||
|
||||
func (r *tokenExchangeRequest) GetExchangeActorTokenIDOrToken() string {
|
||||
return r.exchangeActorTokenIDOrToken
|
||||
}
|
||||
|
||||
func (r *tokenExchangeRequest) GetExchangeActorTokenClaims() map[string]interface{} {
|
||||
return r.exchangeActorTokenClaims
|
||||
}
|
||||
|
||||
func (r *tokenExchangeRequest) GetSubject() string {
|
||||
return r.subject
|
||||
}
|
||||
|
||||
func (r *tokenExchangeRequest) SetCurrentScopes(scopes []string) {
|
||||
r.scopes = scopes
|
||||
}
|
||||
|
||||
func (r *tokenExchangeRequest) SetRequestedTokenType(tt oidc.TokenType) {
|
||||
r.requestedTokenType = tt
|
||||
}
|
||||
|
||||
func (r *tokenExchangeRequest) SetSubject(subject string) {
|
||||
r.subject = subject
|
||||
}
|
||||
|
||||
// TokenExchange handles the OAuth 2.0 token exchange grant ("urn:ietf:params:oauth:grant-type:token-exchange")
|
||||
func TokenExchange(w http.ResponseWriter, r *http.Request, exchanger Exchanger) {
|
||||
tokenExchangeReq, clientID, clientSecret, err := ParseTokenExchangeRequest(r, exchanger.Decoder())
|
||||
if err != nil {
|
||||
RequestError(w, r, err)
|
||||
}
|
||||
|
||||
tokenExchangeRequest, client, err := ValidateTokenExchangeRequest(r.Context(), tokenExchangeReq, clientID, clientSecret, exchanger)
|
||||
if err != nil {
|
||||
RequestError(w, r, err)
|
||||
return
|
||||
}
|
||||
resp, err := CreateTokenExchangeResponse(r.Context(), tokenExchangeRequest, client, exchanger)
|
||||
if err != nil {
|
||||
RequestError(w, r, err)
|
||||
return
|
||||
}
|
||||
httphelper.MarshalJSON(w, resp)
|
||||
}
|
||||
|
||||
// ParseTokenExchangeRequest parses the http request into oidc.TokenExchangeRequest
|
||||
func ParseTokenExchangeRequest(r *http.Request, decoder httphelper.Decoder) (_ *oidc.TokenExchangeRequest, clientID, clientSecret string, err error) {
|
||||
err = r.ParseForm()
|
||||
if err != nil {
|
||||
return nil, "", "", oidc.ErrInvalidRequest().WithDescription("error parsing form").WithParent(err)
|
||||
}
|
||||
|
||||
request := new(oidc.TokenExchangeRequest)
|
||||
err = decoder.Decode(request, r.Form)
|
||||
if err != nil {
|
||||
return nil, "", "", oidc.ErrInvalidRequest().WithDescription("error decoding form").WithParent(err)
|
||||
}
|
||||
|
||||
var ok bool
|
||||
if clientID, clientSecret, ok = r.BasicAuth(); ok {
|
||||
clientID, err = url.QueryUnescape(clientID)
|
||||
if err != nil {
|
||||
return nil, "", "", oidc.ErrInvalidClient().WithDescription("invalid basic auth header").WithParent(err)
|
||||
}
|
||||
|
||||
clientSecret, err = url.QueryUnescape(clientSecret)
|
||||
if err != nil {
|
||||
return nil, "", "", oidc.ErrInvalidClient().WithDescription("invalid basic auth header").WithParent(err)
|
||||
}
|
||||
}
|
||||
|
||||
return request, clientID, clientSecret, nil
|
||||
}
|
||||
|
||||
// ValidateTokenExchangeRequest validates the token exchange request parameters including authorization check of the client,
|
||||
// subject_token and actor_token
|
||||
func ValidateTokenExchangeRequest(
|
||||
ctx context.Context,
|
||||
oidcTokenExchangeRequest *oidc.TokenExchangeRequest,
|
||||
clientID, clientSecret string,
|
||||
exchanger Exchanger,
|
||||
) (TokenExchangeRequest, Client, error) {
|
||||
if oidcTokenExchangeRequest.SubjectToken == "" {
|
||||
return nil, nil, oidc.ErrInvalidRequest().WithDescription("subject_token missing")
|
||||
}
|
||||
|
||||
if oidcTokenExchangeRequest.SubjectTokenType == "" {
|
||||
return nil, nil, oidc.ErrInvalidRequest().WithDescription("subject_token_type missing")
|
||||
}
|
||||
|
||||
storage := exchanger.Storage()
|
||||
teStorage, ok := storage.(TokenExchangeStorage)
|
||||
if !ok {
|
||||
return nil, nil, oidc.ErrUnsupportedGrantType().WithDescription("token_exchange grant not supported")
|
||||
}
|
||||
|
||||
client, err := AuthorizeTokenExchangeClient(ctx, clientID, clientSecret, exchanger)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if oidcTokenExchangeRequest.RequestedTokenType != "" && !oidcTokenExchangeRequest.RequestedTokenType.IsValid() {
|
||||
return nil, nil, oidc.ErrInvalidRequest().WithDescription("requested_token_type is not supported")
|
||||
}
|
||||
|
||||
if !oidcTokenExchangeRequest.SubjectTokenType.IsValid() {
|
||||
return nil, nil, oidc.ErrInvalidRequest().WithDescription("subject_token_type is not supported")
|
||||
}
|
||||
|
||||
if oidcTokenExchangeRequest.ActorTokenType != "" && !oidcTokenExchangeRequest.ActorTokenType.IsValid() {
|
||||
return nil, nil, oidc.ErrInvalidRequest().WithDescription("actor_token_type is not supported")
|
||||
}
|
||||
|
||||
exchangeSubjectTokenIDOrToken, exchangeSubject, exchangeSubjectTokenClaims, ok := GetTokenIDAndSubjectFromToken(ctx, exchanger,
|
||||
oidcTokenExchangeRequest.SubjectToken, oidcTokenExchangeRequest.SubjectTokenType, false)
|
||||
if !ok {
|
||||
return nil, nil, oidc.ErrInvalidRequest().WithDescription("subject_token is invalid")
|
||||
}
|
||||
|
||||
var (
|
||||
exchangeActorTokenIDOrToken, exchangeActor string
|
||||
exchangeActorTokenClaims map[string]interface{}
|
||||
)
|
||||
if oidcTokenExchangeRequest.ActorToken != "" {
|
||||
exchangeActorTokenIDOrToken, exchangeActor, exchangeActorTokenClaims, ok = GetTokenIDAndSubjectFromToken(ctx, exchanger,
|
||||
oidcTokenExchangeRequest.ActorToken, oidcTokenExchangeRequest.ActorTokenType, true)
|
||||
if !ok {
|
||||
return nil, nil, oidc.ErrInvalidRequest().WithDescription("actor_token is invalid")
|
||||
}
|
||||
}
|
||||
|
||||
req := &tokenExchangeRequest{
|
||||
exchangeSubjectTokenIDOrToken: exchangeSubjectTokenIDOrToken,
|
||||
exchangeSubjectTokenType: oidcTokenExchangeRequest.SubjectTokenType,
|
||||
exchangeSubject: exchangeSubject,
|
||||
exchangeSubjectTokenClaims: exchangeSubjectTokenClaims,
|
||||
|
||||
exchangeActorTokenIDOrToken: exchangeActorTokenIDOrToken,
|
||||
exchangeActorTokenType: oidcTokenExchangeRequest.ActorTokenType,
|
||||
exchangeActor: exchangeActor,
|
||||
exchangeActorTokenClaims: exchangeActorTokenClaims,
|
||||
|
||||
subject: exchangeSubject,
|
||||
resource: oidcTokenExchangeRequest.Resource,
|
||||
audience: oidcTokenExchangeRequest.Audience,
|
||||
scopes: oidcTokenExchangeRequest.Scopes,
|
||||
requestedTokenType: oidcTokenExchangeRequest.RequestedTokenType,
|
||||
clientID: client.GetID(),
|
||||
authTime: time.Now(),
|
||||
}
|
||||
|
||||
err = teStorage.ValidateTokenExchangeRequest(ctx, req)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
err = teStorage.CreateTokenExchangeRequest(ctx, req)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return req, client, nil
|
||||
}
|
||||
|
||||
func GetTokenIDAndSubjectFromToken(
|
||||
ctx context.Context,
|
||||
exchanger Exchanger,
|
||||
token string,
|
||||
tokenType oidc.TokenType,
|
||||
isActor bool,
|
||||
) (tokenIDOrToken, subject string, claims map[string]interface{}, ok bool) {
|
||||
switch tokenType {
|
||||
case oidc.AccessTokenType:
|
||||
var accessTokenClaims oidc.AccessTokenClaims
|
||||
tokenIDOrToken, subject, accessTokenClaims, ok = getTokenIDAndClaims(ctx, exchanger, token)
|
||||
claims = accessTokenClaims.GetClaims()
|
||||
case oidc.RefreshTokenType:
|
||||
refreshTokenRequest, err := exchanger.Storage().TokenRequestByRefreshToken(ctx, token)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
tokenIDOrToken, subject, ok = token, refreshTokenRequest.GetSubject(), true
|
||||
case oidc.IDTokenType:
|
||||
idTokenClaims, err := VerifyIDTokenHint(ctx, token, exchanger.IDTokenHintVerifier())
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
tokenIDOrToken, subject, claims, ok = token, idTokenClaims.GetSubject(), idTokenClaims.GetClaims(), true
|
||||
}
|
||||
|
||||
if !ok {
|
||||
if verifier, ok := exchanger.Storage().(TokenExchangeTokensVerifierStorage); ok {
|
||||
var err error
|
||||
if isActor {
|
||||
tokenIDOrToken, subject, claims, err = verifier.VerifyExchangeActorToken(ctx, token, tokenType)
|
||||
} else {
|
||||
tokenIDOrToken, subject, claims, err = verifier.VerifyExchangeSubjectToken(ctx, token, tokenType)
|
||||
}
|
||||
if err != nil {
|
||||
return "", "", nil, false
|
||||
}
|
||||
|
||||
return tokenIDOrToken, subject, claims, true
|
||||
}
|
||||
|
||||
return "", "", nil, false
|
||||
}
|
||||
|
||||
return tokenIDOrToken, subject, claims, true
|
||||
}
|
||||
|
||||
// AuthorizeTokenExchangeClient authorizes a client by validating the client_id and client_secret
|
||||
func AuthorizeTokenExchangeClient(ctx context.Context, clientID, clientSecret string, exchanger Exchanger) (client Client, err error) {
|
||||
if err := AuthorizeClientIDSecret(ctx, clientID, clientSecret, exchanger.Storage()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client, err = exchanger.Storage().GetClientByClientID(ctx, clientID)
|
||||
if err != nil {
|
||||
return nil, oidc.ErrInvalidClient().WithParent(err)
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func CreateTokenExchangeResponse(
|
||||
ctx context.Context,
|
||||
tokenExchangeRequest TokenExchangeRequest,
|
||||
client Client,
|
||||
creator TokenCreator,
|
||||
) (_ *oidc.TokenExchangeResponse, err error) {
|
||||
|
||||
var (
|
||||
token, refreshToken, tokenType string
|
||||
validity time.Duration
|
||||
)
|
||||
|
||||
switch tokenExchangeRequest.GetRequestedTokenType() {
|
||||
case oidc.AccessTokenType, oidc.RefreshTokenType:
|
||||
token, refreshToken, validity, err = CreateAccessToken(ctx, tokenExchangeRequest, client.AccessTokenType(), creator, client, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tokenType = oidc.BearerToken
|
||||
case oidc.IDTokenType:
|
||||
token, err = CreateIDToken(ctx, creator.Issuer(), tokenExchangeRequest, client.IDTokenLifetime(), "", "", creator.Storage(), creator.Signer(), client)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// not applicable (see https://datatracker.ietf.org/doc/html/rfc8693#section-2-2-1-2-6)
|
||||
tokenType = "N_A"
|
||||
default:
|
||||
// oidc.JWTTokenType and other custom token types are not supported for issuing.
|
||||
// In the future it can be considered to have custom tokens generation logic injected via op configuration
|
||||
// or via expanding Storage interface
|
||||
oidc.ErrInvalidRequest().WithDescription("requested_token_type is invalid")
|
||||
}
|
||||
|
||||
exp := uint64(validity.Seconds())
|
||||
return &oidc.TokenExchangeResponse{
|
||||
AccessToken: token,
|
||||
IssuedTokenType: tokenExchangeRequest.GetRequestedTokenType(),
|
||||
TokenType: tokenType,
|
||||
ExpiresIn: exp,
|
||||
RefreshToken: refreshToken,
|
||||
Scopes: tokenExchangeRequest.GetScopes(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getTokenIDAndClaims(ctx context.Context, userinfoProvider UserinfoProvider, accessToken string) (string, string, oidc.AccessTokenClaims, bool) {
|
||||
tokenIDSubject, err := userinfoProvider.Crypto().Decrypt(accessToken)
|
||||
if err == nil {
|
||||
splitToken := strings.Split(tokenIDSubject, ":")
|
||||
if len(splitToken) != 2 {
|
||||
return "", "", nil, false
|
||||
}
|
||||
|
||||
return splitToken[0], splitToken[1], nil, true
|
||||
}
|
||||
accessTokenClaims, err := VerifyAccessToken(ctx, accessToken, userinfoProvider.AccessTokenVerifier())
|
||||
if err != nil {
|
||||
return "", "", nil, false
|
||||
}
|
||||
|
||||
return accessTokenClaims.GetTokenID(), accessTokenClaims.GetSubject(), accessTokenClaims, true
|
||||
}
|
||||
|
|
|
@ -21,6 +21,8 @@ type Exchanger interface {
|
|||
GrantTypeTokenExchangeSupported() bool
|
||||
GrantTypeJWTAuthorizationSupported() bool
|
||||
GrantTypeClientCredentialsSupported() bool
|
||||
AccessTokenVerifier() AccessTokenVerifier
|
||||
IDTokenHintVerifier() IDTokenHintVerifier
|
||||
}
|
||||
|
||||
func tokenHandler(exchanger Exchanger) func(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue