196 lines
9.9 KiB
Go
196 lines
9.9 KiB
Go
package op
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"time"
|
|
|
|
"gopkg.in/square/go-jose.v2"
|
|
|
|
"github.com/zitadel/oidc/v3/pkg/oidc"
|
|
)
|
|
|
|
type AuthStorage interface {
|
|
CreateAuthRequest(context.Context, *oidc.AuthRequest, string) (AuthRequest, error)
|
|
AuthRequestByID(context.Context, string) (AuthRequest, error)
|
|
AuthRequestByCode(context.Context, string) (AuthRequest, error)
|
|
SaveAuthCode(context.Context, string, string) error
|
|
DeleteAuthRequest(context.Context, string) error
|
|
|
|
// The TokenRequest parameter of CreateAccessToken can be any of:
|
|
//
|
|
// * TokenRequest as returned by ClientCredentialsStorage.ClientCredentialsTokenRequest,
|
|
//
|
|
// * AuthRequest as returned by AuthRequestByID or AuthRequestByCode (above)
|
|
//
|
|
// * *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:
|
|
//
|
|
// * TokenRequest as returned by ClientCredentialsStorage.ClientCredentialsTokenRequest
|
|
//
|
|
// * RefreshTokenRequest as returned by AuthStorage.TokenRequestByRefreshToken
|
|
//
|
|
// * 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)
|
|
|
|
TerminateSession(ctx context.Context, userID string, clientID string) error
|
|
|
|
// 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. 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
|
|
|
|
// 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 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.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)
|
|
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
|
|
// token returned by CreateAccessToken should be a JWT.
|
|
// See https://datatracker.ietf.org/doc/html/rfc6749#section-1.3.4 for context.
|
|
type Storage interface {
|
|
AuthStorage
|
|
OPStorage
|
|
Health(context.Context) error
|
|
}
|
|
|
|
type StorageNotFoundError interface {
|
|
IsNotFound()
|
|
}
|
|
|
|
type EndSessionRequest struct {
|
|
UserID string
|
|
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
|
|
}
|