Merge branch 'next' into main-next

prepare the merge of next into main by resolving merge conflicts.
This commit is contained in:
Tim Möhlmann 2023-03-15 16:26:32 +02:00
commit 0476b5946e
122 changed files with 8195 additions and 2858 deletions

View file

@ -7,7 +7,7 @@ import (
"gopkg.in/square/go-jose.v2"
"github.com/zitadel/oidc/pkg/oidc"
"github.com/zitadel/oidc/v2/pkg/oidc"
)
type AuthStorage interface {
@ -25,6 +25,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 as returned by ValidateTokenExchangeRequest
CreateAccessToken(context.Context, TokenRequest) (accessTokenID string, expiration time.Time, err error)
// The TokenRequest parameter of CreateAccessAndRefreshTokens can be any of:
@ -36,6 +38,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 as returned by ValidateTokenExchangeRequest
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)
@ -44,44 +48,85 @@ type AuthStorage interface {
// RevokeToken should revoke a token. In the situation that the original request was to
// revoke an access token, then tokenOrTokenID will be a tokenID and userID will be set
// but if the original request was for a refresh token, then userID will be empty and
// tokenOrTokenID will be the refresh token, not its ID.
// tokenOrTokenID will be the refresh token, not its ID. RevokeToken depends upon GetRefreshTokenInfo
// to get information from refresh tokens that are not either "<tokenID>:<userID>" strings
// nor JWTs.
RevokeToken(ctx context.Context, tokenOrTokenID string, userID string, clientID string) *oidc.Error
GetSigningKey(context.Context, chan<- jose.SigningKey)
GetKeySet(context.Context) (*jose.JSONWebKeySet, error)
}
// CanRefreshTokenInfo is an optional additional interface that Storage can support.
// Supporting CanRefreshTokenInfo is required to be able to (revoke) a refresh token that
// is neither an encrypted string of <tokenID>:<userID> nor a JWT.
type CanRefreshTokenInfo interface {
// GetRefreshTokenInfo must return ErrInvalidRefreshToken when presented
// with a token that is not a refresh token.
GetRefreshTokenInfo(ctx context.Context, clientID string, token string) (userID string, tokenID string, err error)
SigningKey(context.Context) (SigningKey, error)
SignatureAlgorithms(context.Context) ([]jose.SignatureAlgorithm, error)
KeySet(context.Context) ([]Key, error)
}
type ClientCredentialsStorage interface {
ClientCredentials(ctx context.Context, clientID, clientSecret string) (Client, error)
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.UserInfo, 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)
}
var ErrInvalidRefreshToken = errors.New("invalid_refresh_token")
type ClientCredentialsStorage interface {
ClientCredentialsTokenRequest(ctx context.Context, clientID string, scopes []string) (TokenRequest, error)
}
type OPStorage interface {
// GetClientByClientID loads a Client. The returned Client is never cached and is only used to
// handle the current request.
GetClientByClientID(ctx context.Context, clientID string) (Client, error)
AuthorizeClientIDSecret(ctx context.Context, clientID, clientSecret string) error
SetUserinfoFromScopes(ctx context.Context, userinfo oidc.UserInfoSetter, userID, clientID string, scopes []string) error
SetUserinfoFromToken(ctx context.Context, userinfo oidc.UserInfoSetter, tokenID, subject, origin string) error
SetIntrospectionFromToken(ctx context.Context, userinfo oidc.IntrospectionResponse, tokenID, subject, clientID string) error
SetUserinfoFromScopes(ctx context.Context, userinfo *oidc.UserInfo, userID, clientID string, scopes []string) error
SetUserinfoFromToken(ctx context.Context, userinfo *oidc.UserInfo, tokenID, subject, origin string) error
SetIntrospectionFromToken(ctx context.Context, userinfo *oidc.IntrospectionResponse, tokenID, subject, clientID string) error
GetPrivateClaimsFromScopes(ctx context.Context, userID, clientID string, scopes []string) (map[string]interface{}, error)
// GetKeyByIDAndUserID is mis-named. It does not pass userID. Instead
// it passes the clientID.
GetKeyByIDAndUserID(ctx context.Context, keyID, clientID string) (*jose.JSONWebKey, error)
GetKeyByIDAndClientID(ctx context.Context, keyID, clientID string) (*jose.JSONWebKey, error)
ValidateJWTProfileScopes(ctx context.Context, userID string, scopes []string) ([]string, error)
}
// JWTProfileTokenStorage is an additional, optional storage to implement
// implementing it, allows specifying the [AccessTokenType] of the access_token returned form the JWT Profile TokenRequest
type JWTProfileTokenStorage interface {
JWTProfileTokenType(ctx context.Context, request TokenRequest) (AccessTokenType, error)
}
// Storage is a required parameter for NewOpenIDProvider(). In addition to the
// embedded interfaces below, if the passed Storage implements ClientCredentialsStorage
// then the grant type "client_credentials" will be supported. In that case, the access
@ -102,3 +147,50 @@ type EndSessionRequest struct {
ClientID string
RedirectURI string
}
var ErrDuplicateUserCode = errors.New("user code already exists")
type DeviceAuthorizationState struct {
ClientID string
Scopes []string
Expires time.Time
Done bool
Subject string
Denied bool
}
type DeviceAuthorizationStorage interface {
// StoreDeviceAuthorizationRequest stores a new device authorization request in the database.
// User code will be used by the user to complete the login flow and must be unique.
// ErrDuplicateUserCode signals the caller should try again with a new code.
//
// Note that user codes are low entropy keys and when many exist in the
// database, the change for collisions increases. Therefore implementers
// of this interface must make sure that user codes of expired authentication flows are purged,
// after some time.
StoreDeviceAuthorization(ctx context.Context, clientID, deviceCode, userCode string, expires time.Time, scopes []string) error
// GetDeviceAuthorizatonState returns the current state of the device authorization flow in the database.
// The method is polled untill the the authorization is eighter Completed, Expired or Denied.
GetDeviceAuthorizatonState(ctx context.Context, clientID, deviceCode string) (*DeviceAuthorizationState, error)
// GetDeviceAuthorizationByUserCode resturn the current state of the device authorization flow,
// identified by the user code.
GetDeviceAuthorizationByUserCode(ctx context.Context, userCode string) (*DeviceAuthorizationState, error)
// CompleteDeviceAuthorization marks a device authorization entry as Completed,
// identified by userCode. The Subject is added to the state, so that
// GetDeviceAuthorizatonState can use it to create a new Access Token.
CompleteDeviceAuthorization(ctx context.Context, userCode, subject string) error
// DenyDeviceAuthorization marks a device authorization entry as Denied.
DenyDeviceAuthorization(ctx context.Context, userCode string) error
}
func assertDeviceStorage(s Storage) (DeviceAuthorizationStorage, error) {
storage, ok := s.(DeviceAuthorizationStorage)
if !ok {
return nil, oidc.ErrUnsupportedGrantType().WithDescription("device_code grant not supported")
}
return storage, nil
}