mirror of
https://github.com/pocket-id/pocket-id.git
synced 2026-09-26 21:09:04 +02:00
819 lines
25 KiB
Go
819 lines
25 KiB
Go
package oidc
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"net/url"
|
|
"time"
|
|
|
|
"github.com/ory/fosite"
|
|
fositeoauth2 "github.com/ory/fosite/handler/oauth2"
|
|
"github.com/ory/fosite/handler/openid"
|
|
"github.com/ory/fosite/handler/pkce"
|
|
"github.com/ory/fosite/handler/rfc8628"
|
|
fositestorage "github.com/ory/fosite/storage"
|
|
"github.com/pocket-id/pocket-id/backend/internal/model"
|
|
datatype "github.com/pocket-id/pocket-id/backend/internal/model/types"
|
|
"gorm.io/gorm"
|
|
"gorm.io/gorm/clause"
|
|
)
|
|
|
|
const (
|
|
sessionKindAuthorizeCode = "authorize_code"
|
|
sessionKindAccessToken = "access_token"
|
|
sessionKindRefreshToken = "refresh_token"
|
|
sessionKindPKCE = "pkce"
|
|
sessionKindOpenID = "openid"
|
|
sessionKindPAR = "par"
|
|
sessionKindDeviceCode = "device_code"
|
|
sessionKindUserCode = "user_code"
|
|
)
|
|
|
|
var (
|
|
_ fosite.Storage = (*Store)(nil)
|
|
_ fosite.PARStorage = (*Store)(nil)
|
|
_ fositeoauth2.CoreStorage = (*Store)(nil)
|
|
_ fositeoauth2.TokenRevocationStorage = (*Store)(nil)
|
|
_ rfc8628.RFC8628CoreStorage = (*Store)(nil)
|
|
_ openid.OpenIDConnectRequestStorage = (*Store)(nil)
|
|
_ pkce.PKCERequestStorage = (*Store)(nil)
|
|
_ fositestorage.Transactional = (*Store)(nil)
|
|
)
|
|
|
|
// NewStore creates the fosite storage. Exported for packages that need to seed or
|
|
// revoke sessions (e.g. the e2e test service).
|
|
func NewStore(db *gorm.DB) *Store {
|
|
return &Store{db: db}
|
|
}
|
|
|
|
type Store struct {
|
|
db *gorm.DB
|
|
}
|
|
|
|
type storedRequester struct {
|
|
Authorize bool `json:"authorize,omitempty"`
|
|
|
|
ID string `json:"id"`
|
|
RequestedAt time.Time `json:"requested_at"`
|
|
ClientID string `json:"client_id"`
|
|
RequestedScope fosite.Arguments `json:"requested_scope,omitempty"`
|
|
GrantedScope fosite.Arguments `json:"granted_scope,omitempty"`
|
|
Form url.Values `json:"form,omitempty"`
|
|
Session *Session `json:"session,omitempty"`
|
|
RequestedAudience fosite.Arguments `json:"requested_audience,omitempty"`
|
|
GrantedAudience fosite.Arguments `json:"granted_audience,omitempty"`
|
|
Device bool `json:"device,omitempty"`
|
|
UserCodeState fosite.UserCodeState `json:"user_code_state,omitempty"`
|
|
|
|
ResponseTypes fosite.Arguments `json:"response_types,omitempty"`
|
|
RedirectURI string `json:"redirect_uri,omitempty"`
|
|
State string `json:"state,omitempty"`
|
|
HandledResponseTypes fosite.Arguments `json:"handled_response_types,omitempty"`
|
|
ResponseMode fosite.ResponseModeType `json:"response_mode,omitempty"`
|
|
DefaultResponseMode fosite.ResponseModeType `json:"default_response_mode,omitempty"`
|
|
}
|
|
|
|
// Satisfies fosite.Storage
|
|
|
|
func (s *Store) GetClient(ctx context.Context, id string) (fosite.Client, error) {
|
|
var client model.OidcClient
|
|
err := s.dbFor(ctx).
|
|
Preload("AllowedUserGroups").
|
|
First(&client, "id = ?", id).
|
|
Error
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, fosite.ErrNotFound
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return Client{OidcClient: client}, nil
|
|
}
|
|
|
|
func (s *Store) ClientAssertionJWTValid(ctx context.Context, jti string) error {
|
|
var count int64
|
|
err := s.dbFor(ctx).
|
|
Model(&clientAssertionJTI{}).
|
|
Where("jti = ? AND expires_at > ?", jti, datatype.DateTime(time.Now())).
|
|
Count(&count).
|
|
Error
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if count > 0 {
|
|
return fosite.ErrJTIKnown
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *Store) SetClientAssertionJWT(ctx context.Context, jti string, exp time.Time) error {
|
|
err := s.dbFor(ctx).Create(&clientAssertionJTI{
|
|
JTI: jti,
|
|
ExpiresAt: datatype.DateTime(exp),
|
|
}).Error
|
|
if errors.Is(err, gorm.ErrDuplicatedKey) {
|
|
return fosite.ErrJTIKnown
|
|
}
|
|
return err
|
|
}
|
|
|
|
// Satisfies fositeoauth2.CoreStorage
|
|
|
|
func (s *Store) CreateAuthorizeCodeSession(ctx context.Context, code string, request fosite.Requester) error {
|
|
return s.upsertSession(ctx, sessionKindAuthorizeCode, code, request, "", true, fosite.AuthorizeCode)
|
|
}
|
|
|
|
func (s *Store) GetAuthorizeCodeSession(ctx context.Context, code string, _ fosite.Session) (fosite.Requester, error) {
|
|
request, active, err := s.getRequesterSession(ctx, sessionKindAuthorizeCode, code)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !active {
|
|
return request, fosite.ErrInvalidatedAuthorizeCode
|
|
}
|
|
return request, nil
|
|
}
|
|
|
|
func (s *Store) InvalidateAuthorizeCodeSession(ctx context.Context, code string) error {
|
|
return s.deactivateSession(ctx, sessionKindAuthorizeCode, code)
|
|
}
|
|
|
|
func (s *Store) CreateAccessTokenSession(ctx context.Context, signature string, request fosite.Requester) error {
|
|
return s.upsertSession(ctx, sessionKindAccessToken, signature, request, "", true, fosite.AccessToken)
|
|
}
|
|
|
|
func (s *Store) GetAccessTokenSession(ctx context.Context, signature string, _ fosite.Session) (fosite.Requester, error) {
|
|
request, _, err := s.getRequesterSession(ctx, sessionKindAccessToken, signature)
|
|
return request, err
|
|
}
|
|
|
|
func (s *Store) DeleteAccessTokenSession(ctx context.Context, signature string) error {
|
|
return s.deleteSession(ctx, sessionKindAccessToken, signature)
|
|
}
|
|
|
|
func (s *Store) CreateRefreshTokenSession(ctx context.Context, signature string, accessSignature string, request fosite.Requester) error {
|
|
return s.upsertSession(ctx, sessionKindRefreshToken, signature, request, accessSignature, true, fosite.RefreshToken)
|
|
}
|
|
|
|
func (s *Store) GetRefreshTokenSession(ctx context.Context, signature string, _ fosite.Session) (fosite.Requester, error) {
|
|
request, active, err := s.getRequesterSession(ctx, sessionKindRefreshToken, signature)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !active {
|
|
return request, fosite.ErrInactiveToken
|
|
}
|
|
return request, nil
|
|
}
|
|
|
|
func (s *Store) DeleteRefreshTokenSession(ctx context.Context, signature string) error {
|
|
return s.deleteSession(ctx, sessionKindRefreshToken, signature)
|
|
}
|
|
|
|
func (s *Store) RotateRefreshToken(ctx context.Context, requestID string, refreshTokenSignature string) error {
|
|
if err := s.deactivateSession(ctx, sessionKindRefreshToken, refreshTokenSignature); err != nil {
|
|
return err
|
|
}
|
|
return s.RevokeAccessToken(ctx, requestID)
|
|
}
|
|
|
|
// Satisfies fositeoauth2.TokenRevocationStorage
|
|
|
|
func (s *Store) RevokeRefreshToken(ctx context.Context, requestID string) error {
|
|
return s.dbFor(ctx).
|
|
Model(&OAuth2Session{}).
|
|
Where("kind = ? AND request_id = ?", sessionKindRefreshToken, requestID).
|
|
Update("active", false).
|
|
Error
|
|
}
|
|
|
|
func (s *Store) RevokeAccessToken(ctx context.Context, requestID string) error {
|
|
return s.dbFor(ctx).
|
|
Where("kind = ? AND request_id = ?", sessionKindAccessToken, requestID).
|
|
Delete(&OAuth2Session{}).
|
|
Error
|
|
}
|
|
|
|
func (s *Store) RevokeSessionsByIDTokenHint(ctx context.Context, userID, clientID, idTokenJTI string) error {
|
|
_, jtiMatches, err := s.findUserClientRequestIDs(ctx, userID, clientID, idTokenJTI)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return s.revokeRequestIDs(ctx, jtiMatches)
|
|
}
|
|
|
|
func RevokeUserClientSessions(ctx context.Context, db *gorm.DB, userID, clientID string) error {
|
|
s := NewStore(db)
|
|
requestIDs, _, err := s.findUserClientRequestIDs(ctx, userID, clientID, "")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return s.revokeRequestIDs(ctx, requestIDs)
|
|
}
|
|
|
|
func (s *Store) findUserClientRequestIDs(ctx context.Context, userID, clientID, idTokenJTI string) (candidates []string, jtiMatches []string, err error) {
|
|
var sessions []OAuth2Session
|
|
err = s.dbFor(ctx).
|
|
Where("kind = ? AND active = ?", sessionKindRefreshToken, true).
|
|
Find(&sessions).
|
|
Error
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
candidateRequestIDs := map[string]struct{}{}
|
|
matchingRequestIDs := map[string]struct{}{}
|
|
for _, session := range sessions {
|
|
requester, err := s.decodeRequester(ctx, session.RequestData)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
requestSession := requester.GetSession()
|
|
if requestSession == nil || requester.GetClient().GetID() != clientID || requestSession.GetSubject() != userID {
|
|
continue
|
|
}
|
|
|
|
// Add all sessions that match the user and client
|
|
candidateRequestIDs[session.RequestID] = struct{}{}
|
|
// Only add sessions that also match the ID token hint JTI
|
|
if storedSession, ok := requestSession.(*Session); ok && idTokenJTI != "" && storedSession.IDTokenClaims().JTI == idTokenJTI {
|
|
matchingRequestIDs[session.RequestID] = struct{}{}
|
|
}
|
|
}
|
|
|
|
return mapKeys(candidateRequestIDs), mapKeys(matchingRequestIDs), nil
|
|
}
|
|
|
|
func (s *Store) revokeRequestIDs(ctx context.Context, requestIDs []string) error {
|
|
if len(requestIDs) == 0 {
|
|
return nil
|
|
}
|
|
|
|
if err := s.dbFor(ctx).
|
|
Model(&OAuth2Session{}).
|
|
Where("kind = ? AND request_id IN ?", sessionKindRefreshToken, requestIDs).
|
|
Update("active", false).
|
|
Error; err != nil {
|
|
return err
|
|
}
|
|
|
|
return s.dbFor(ctx).
|
|
Where("kind = ? AND request_id IN ?", sessionKindAccessToken, requestIDs).
|
|
Delete(&OAuth2Session{}).
|
|
Error
|
|
}
|
|
|
|
func mapKeys(m map[string]struct{}) []string {
|
|
keys := make([]string, 0, len(m))
|
|
for key := range m {
|
|
keys = append(keys, key)
|
|
}
|
|
return keys
|
|
}
|
|
|
|
// Satisfies pkce.PKCERequestStorage
|
|
|
|
func (s *Store) CreatePKCERequestSession(ctx context.Context, signature string, requester fosite.Requester) error {
|
|
// PKCE sessions share the authorize code lifespan so abandoned ones expire and get cleaned up
|
|
return s.upsertSession(ctx, sessionKindPKCE, signature, requester, "", true, fosite.AuthorizeCode)
|
|
}
|
|
|
|
func (s *Store) GetPKCERequestSession(ctx context.Context, signature string, _ fosite.Session) (fosite.Requester, error) {
|
|
request, _, err := s.getRequesterSession(ctx, sessionKindPKCE, signature)
|
|
return request, err
|
|
}
|
|
|
|
func (s *Store) DeletePKCERequestSession(ctx context.Context, signature string) error {
|
|
return s.deleteSession(ctx, sessionKindPKCE, signature)
|
|
}
|
|
|
|
// Satisfies openid.OpenIDConnectRequestStorage
|
|
|
|
func (s *Store) CreateOpenIDConnectSession(ctx context.Context, authorizeCode string, requester fosite.Requester) error {
|
|
return s.upsertSession(ctx, sessionKindOpenID, authorizeCode, requester, "", true, fosite.AuthorizeCode)
|
|
}
|
|
|
|
func (s *Store) GetOpenIDConnectSession(ctx context.Context, authorizeCode string, _ fosite.Requester) (fosite.Requester, error) {
|
|
request, _, err := s.getRequesterSession(ctx, sessionKindOpenID, authorizeCode)
|
|
if errors.Is(err, fosite.ErrNotFound) {
|
|
return nil, openid.ErrNoSessionFound
|
|
}
|
|
return request, err
|
|
}
|
|
|
|
func (s *Store) DeleteOpenIDConnectSession(ctx context.Context, authorizeCode string) error {
|
|
return s.deleteSession(ctx, sessionKindOpenID, authorizeCode)
|
|
}
|
|
|
|
// Satisfies fosite.PARStorage
|
|
|
|
func (s *Store) CreatePARSession(ctx context.Context, requestURI string, request fosite.AuthorizeRequester) error {
|
|
return s.upsertAuthorizeSession(ctx, sessionKindPAR, requestURI, request, true, fosite.PushedAuthorizeRequestContext)
|
|
}
|
|
|
|
func (s *Store) GetPARSession(ctx context.Context, requestURI string) (fosite.AuthorizeRequester, error) {
|
|
session, err := s.getSession(ctx, sessionKindPAR, requestURI)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !session.Active || session.ExpiresAt == nil || session.ExpiresAt.ToTime().Before(time.Now()) {
|
|
return nil, fosite.ErrNotFound
|
|
}
|
|
|
|
result := s.dbFor(ctx).
|
|
Model(&OAuth2Session{}).
|
|
Where("kind = ? AND key = ? AND active = ?", sessionKindPAR, requestURI, true).
|
|
Update("active", false)
|
|
if result.Error != nil {
|
|
return nil, result.Error
|
|
}
|
|
if result.RowsAffected == 0 {
|
|
return nil, fosite.ErrNotFound
|
|
}
|
|
|
|
return s.decodeAuthorizeRequester(ctx, session.RequestData)
|
|
}
|
|
|
|
func (s *Store) DeletePARSession(ctx context.Context, requestURI string) error {
|
|
return s.deleteSession(ctx, sessionKindPAR, requestURI)
|
|
}
|
|
|
|
// Satisfies rfc8628.RFC8628CoreStorage
|
|
|
|
func (s *Store) CreateDeviceAuthSession(ctx context.Context, deviceCodeSignature, userCodeSignature string, request fosite.DeviceRequester) error {
|
|
requestData, err := s.encodeDeviceRequester(request)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if _, err := s.getSession(ctx, sessionKindUserCode, userCodeSignature); err == nil {
|
|
return fosite.ErrExistingUserCodeSignature
|
|
} else if !errors.Is(err, fosite.ErrNotFound) {
|
|
return err
|
|
}
|
|
|
|
expDeviceCode := expiresAt(request.GetSession(), fosite.DeviceCode)
|
|
expUserCode := expiresAt(request.GetSession(), fosite.UserCode)
|
|
if err := s.storeSession(ctx, sessionKindDeviceCode, deviceCodeSignature, request.GetID(), "", true, requestData, expDeviceCode); err != nil {
|
|
return err
|
|
}
|
|
return s.storeSession(ctx, sessionKindUserCode, userCodeSignature, request.GetID(), "", true, requestData, expUserCode)
|
|
}
|
|
|
|
func (s *Store) GetDeviceCodeSession(ctx context.Context, signature string, _ fosite.Session) (fosite.DeviceRequester, error) {
|
|
request, active, err := s.getDeviceRequesterSession(ctx, sessionKindDeviceCode, signature)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !active {
|
|
return request, fosite.ErrInvalidatedDeviceCode
|
|
}
|
|
return request, nil
|
|
}
|
|
|
|
func (s *Store) InvalidateDeviceCodeSession(ctx context.Context, signature string) error {
|
|
session, err := s.getSession(ctx, sessionKindDeviceCode, signature)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Only flip rows that are still active so two concurrent token requests for the same
|
|
// device code cannot both pass the single-use check and each mint a token set.
|
|
result := s.dbFor(ctx).
|
|
Model(&OAuth2Session{}).
|
|
Where("kind IN ? AND request_id = ? AND active = ?", []string{sessionKindDeviceCode, sessionKindUserCode}, session.RequestID, true).
|
|
Update("active", false)
|
|
if result.Error != nil {
|
|
return result.Error
|
|
}
|
|
if result.RowsAffected == 0 {
|
|
return fosite.ErrNotFound
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *Store) GetDeviceCodeSessionByUserCodeSignature(ctx context.Context, signature string) (fosite.DeviceRequester, error) {
|
|
request, active, err := s.getDeviceRequesterSession(ctx, sessionKindUserCode, signature)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !active {
|
|
return nil, fosite.ErrNotFound
|
|
}
|
|
return request, nil
|
|
}
|
|
|
|
func (s *Store) AcceptDeviceCodeSessionByUserCodeSignature(ctx context.Context, signature string, request fosite.DeviceRequester) (string, error) {
|
|
userCodeSession, err := s.getSession(ctx, sessionKindUserCode, signature)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
request.SetUserCodeState(fosite.UserCodeAccepted)
|
|
|
|
requestData, err := s.encodeDeviceRequester(request)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
deviceCodeSession, err := s.getSessionByRequestID(ctx, sessionKindDeviceCode, userCodeSession.RequestID)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
err = s.dbFor(ctx).
|
|
Model(&OAuth2Session{}).
|
|
Where("kind IN ? AND request_id = ?", []string{sessionKindDeviceCode, sessionKindUserCode}, userCodeSession.RequestID).
|
|
Updates(map[string]any{
|
|
"request_data": requestData,
|
|
}).
|
|
Error
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return deviceCodeSession.Key, nil
|
|
}
|
|
|
|
// Satisfies fositestorage.Transactional
|
|
|
|
func (s *Store) BeginTX(ctx context.Context) (context.Context, error) {
|
|
tx := s.db.WithContext(ctx).Begin()
|
|
if tx.Error != nil {
|
|
return ctx, tx.Error
|
|
}
|
|
return contextWithTx(ctx, tx), nil
|
|
}
|
|
|
|
func (s *Store) Commit(ctx context.Context) error {
|
|
tx, ok := ctx.Value(txContextKey{}).(*gorm.DB)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
return tx.Commit().Error
|
|
}
|
|
|
|
func (s *Store) Rollback(ctx context.Context) error {
|
|
tx, ok := ctx.Value(txContextKey{}).(*gorm.DB)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
return tx.Rollback().Error
|
|
}
|
|
|
|
func (s *Store) upsertSession(ctx context.Context, kind string, key string, requester fosite.Requester, accessTokenSignature string, active bool, expiresAtKey fosite.TokenType) error {
|
|
requestData, err := s.encodeRequester(requester)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return s.storeSession(ctx, kind, key, requester.GetID(), accessTokenSignature, active, requestData, expiresAt(requester.GetSession(), expiresAtKey))
|
|
}
|
|
|
|
func (s *Store) upsertAuthorizeSession(ctx context.Context, kind string, key string, requester fosite.AuthorizeRequester, active bool, expiresAtKey fosite.TokenType) error {
|
|
requestData, err := s.encodeAuthorizeRequester(requester)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return s.storeSession(ctx, kind, key, requester.GetID(), "", active, requestData, expiresAt(requester.GetSession(), expiresAtKey))
|
|
}
|
|
|
|
func (s *Store) storeSession(ctx context.Context, kind string, key string, requestID string, accessTokenSignature string, active bool, requestData string, exp *datatype.DateTime) error {
|
|
session := OAuth2Session{
|
|
Kind: kind,
|
|
Key: key,
|
|
RequestID: requestID,
|
|
AccessTokenSignature: accessTokenSignature,
|
|
Active: active,
|
|
RequestData: requestData,
|
|
ExpiresAt: exp,
|
|
}
|
|
|
|
return s.dbFor(ctx).
|
|
Clauses(clause.OnConflict{
|
|
Columns: []clause.Column{{Name: "kind"}, {Name: "key"}},
|
|
DoUpdates: clause.AssignmentColumns([]string{
|
|
"request_id",
|
|
"access_token_signature",
|
|
"active",
|
|
"request_data",
|
|
"expires_at",
|
|
}),
|
|
}).
|
|
Create(&session).
|
|
Error
|
|
}
|
|
|
|
func (s *Store) getRequesterSession(ctx context.Context, kind string, key string) (fosite.Requester, bool, error) {
|
|
session, err := s.getSession(ctx, kind, key)
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
|
|
requester, err := s.decodeRequester(ctx, session.RequestData)
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
|
|
return requester, session.Active, nil
|
|
}
|
|
|
|
func (s *Store) getDeviceRequesterSession(ctx context.Context, kind string, key string) (fosite.DeviceRequester, bool, error) {
|
|
session, err := s.getSession(ctx, kind, key)
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
|
|
requester, err := s.decodeDeviceRequester(ctx, session.RequestData)
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
|
|
return requester, session.Active, nil
|
|
}
|
|
|
|
func (s *Store) getSession(ctx context.Context, kind string, key string) (session OAuth2Session, err error) {
|
|
err = s.dbFor(ctx).
|
|
Where("kind = ? AND key = ?", kind, key).
|
|
First(&session).
|
|
Error
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return session, fosite.ErrNotFound
|
|
}
|
|
if err != nil {
|
|
return session, err
|
|
}
|
|
|
|
return session, nil
|
|
}
|
|
|
|
func (s *Store) getSessionByRequestID(ctx context.Context, kind string, requestID string) (session OAuth2Session, err error) {
|
|
err = s.dbFor(ctx).
|
|
Where("kind = ? AND request_id = ?", kind, requestID).
|
|
First(&session).
|
|
Error
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return session, fosite.ErrNotFound
|
|
}
|
|
if err != nil {
|
|
return session, err
|
|
}
|
|
|
|
return session, nil
|
|
}
|
|
|
|
func (s *Store) deleteSession(ctx context.Context, kind string, key string) error {
|
|
return s.dbFor(ctx).
|
|
Where("kind = ? AND key = ?", kind, key).
|
|
Delete(&OAuth2Session{}).
|
|
Error
|
|
}
|
|
|
|
func (s *Store) deactivateSession(ctx context.Context, kind string, key string) error {
|
|
result := s.dbFor(ctx).
|
|
Model(&OAuth2Session{}).
|
|
Where("kind = ? AND key = ? AND active = ?", kind, key, true).
|
|
Update("active", false)
|
|
if result.Error != nil {
|
|
return result.Error
|
|
}
|
|
if result.RowsAffected == 0 {
|
|
return fosite.ErrNotFound
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *Store) encodeRequester(requester fosite.Requester) (string, error) {
|
|
stored, err := s.storedRequesterFromRequester(requester)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
data, err := json.Marshal(stored)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return string(data), nil
|
|
}
|
|
|
|
func (s *Store) encodeAuthorizeRequester(requester fosite.AuthorizeRequester) (string, error) {
|
|
stored, err := s.storedRequesterFromRequester(requester)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
stored.Authorize = true
|
|
stored.ResponseTypes = cloneArguments(requester.GetResponseTypes())
|
|
if redirectURI := requester.GetRedirectURI(); redirectURI != nil {
|
|
stored.RedirectURI = redirectURI.String()
|
|
}
|
|
stored.State = requester.GetState()
|
|
stored.ResponseMode = requester.GetResponseMode()
|
|
stored.DefaultResponseMode = requester.GetDefaultResponseMode()
|
|
|
|
if ar, ok := requester.(*fosite.AuthorizeRequest); ok {
|
|
stored.HandledResponseTypes = cloneArguments(ar.HandledResponseTypes)
|
|
}
|
|
|
|
data, err := json.Marshal(stored)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return string(data), nil
|
|
}
|
|
|
|
func (s *Store) encodeDeviceRequester(requester fosite.DeviceRequester) (string, error) {
|
|
stored, err := s.storedRequesterFromRequester(requester)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
stored.Device = true
|
|
stored.UserCodeState = requester.GetUserCodeState()
|
|
|
|
data, err := json.Marshal(stored)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return string(data), nil
|
|
}
|
|
|
|
func (s *Store) storedRequesterFromRequester(requester fosite.Requester) (storedRequester, error) {
|
|
if requester == nil {
|
|
return storedRequester{}, fosite.ErrServerError.WithHint("requester must not be nil")
|
|
}
|
|
|
|
return storedRequester{
|
|
ID: requester.GetID(),
|
|
RequestedAt: requester.GetRequestedAt(),
|
|
ClientID: requester.GetClient().GetID(),
|
|
RequestedScope: cloneArguments(requester.GetRequestedScopes()),
|
|
GrantedScope: cloneArguments(requester.GetGrantedScopes()),
|
|
Form: sanitizeStoredForm(requester.GetRequestForm()),
|
|
Session: cloneSession(requester.GetSession()),
|
|
RequestedAudience: cloneArguments(requester.GetRequestedAudience()),
|
|
GrantedAudience: cloneArguments(requester.GetGrantedAudience()),
|
|
}, nil
|
|
}
|
|
|
|
func (s *Store) decodeRequester(ctx context.Context, data string) (fosite.Requester, error) {
|
|
var stored storedRequester
|
|
if err := json.Unmarshal([]byte(data), &stored); err != nil {
|
|
return nil, err
|
|
}
|
|
if stored.Authorize {
|
|
return s.requesterFromStoredAuthorize(ctx, stored)
|
|
}
|
|
return s.requesterFromStored(ctx, stored)
|
|
}
|
|
|
|
func (s *Store) decodeAuthorizeRequester(ctx context.Context, data string) (fosite.AuthorizeRequester, error) {
|
|
var stored storedRequester
|
|
if err := json.Unmarshal([]byte(data), &stored); err != nil {
|
|
return nil, err
|
|
}
|
|
stored.Authorize = true
|
|
return s.requesterFromStoredAuthorize(ctx, stored)
|
|
}
|
|
|
|
func (s *Store) decodeDeviceRequester(ctx context.Context, data string) (fosite.DeviceRequester, error) {
|
|
var stored storedRequester
|
|
if err := json.Unmarshal([]byte(data), &stored); err != nil {
|
|
return nil, err
|
|
}
|
|
stored.Device = true
|
|
return s.requesterFromStoredDevice(ctx, stored)
|
|
}
|
|
|
|
func (s *Store) requesterFromStored(ctx context.Context, stored storedRequester) (fosite.Requester, error) {
|
|
client, err := s.GetClient(ctx, stored.ClientID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
request := fosite.NewRequest()
|
|
request.ID = stored.ID
|
|
request.RequestedAt = stored.RequestedAt
|
|
request.Client = client
|
|
request.RequestedScope = cloneArguments(stored.RequestedScope)
|
|
request.GrantedScope = cloneArguments(stored.GrantedScope)
|
|
request.Form = cloneValues(stored.Form)
|
|
request.Session = stored.Session
|
|
request.RequestedAudience = cloneArguments(stored.RequestedAudience)
|
|
request.GrantedAudience = cloneArguments(stored.GrantedAudience)
|
|
return request, nil
|
|
}
|
|
|
|
func (s *Store) requesterFromStoredDevice(ctx context.Context, stored storedRequester) (fosite.DeviceRequester, error) {
|
|
requester, err := s.requesterFromStored(ctx, stored)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
base := requester.(*fosite.Request)
|
|
request := fosite.NewDeviceRequest()
|
|
request.Request = *base
|
|
request.UserCodeState = stored.UserCodeState
|
|
return request, nil
|
|
}
|
|
|
|
func (s *Store) requesterFromStoredAuthorize(ctx context.Context, stored storedRequester) (fosite.AuthorizeRequester, error) {
|
|
requester, err := s.requesterFromStored(ctx, stored)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
base := requester.(*fosite.Request)
|
|
request := fosite.NewAuthorizeRequest()
|
|
request.Request = *base
|
|
request.ResponseTypes = cloneArguments(stored.ResponseTypes)
|
|
request.State = stored.State
|
|
request.HandledResponseTypes = cloneArguments(stored.HandledResponseTypes)
|
|
request.ResponseMode = stored.ResponseMode
|
|
request.DefaultResponseMode = stored.DefaultResponseMode
|
|
|
|
if stored.RedirectURI != "" {
|
|
redirectURI, err := url.Parse(stored.RedirectURI)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
request.RedirectURI = redirectURI
|
|
}
|
|
|
|
return request, nil
|
|
}
|
|
|
|
func cloneSession(session fosite.Session) *Session {
|
|
if session == nil {
|
|
return nil
|
|
}
|
|
|
|
if s, ok := session.(*Session); ok {
|
|
cloned := s.Clone()
|
|
if typed, ok := cloned.(*Session); ok {
|
|
return typed
|
|
}
|
|
}
|
|
|
|
cloned := NewEmptySession()
|
|
cloned.Subject = session.GetSubject()
|
|
for _, tokenType := range []fosite.TokenType{
|
|
fosite.AccessToken,
|
|
fosite.RefreshToken,
|
|
fosite.AuthorizeCode,
|
|
fosite.IDToken,
|
|
fosite.PushedAuthorizeRequestContext,
|
|
fosite.DeviceCode,
|
|
fosite.UserCode,
|
|
} {
|
|
if exp := session.GetExpiresAt(tokenType); !exp.IsZero() {
|
|
cloned.SetExpiresAt(tokenType, exp)
|
|
}
|
|
}
|
|
return cloned
|
|
}
|
|
|
|
func cloneArguments(arguments fosite.Arguments) fosite.Arguments {
|
|
if len(arguments) == 0 {
|
|
return fosite.Arguments{}
|
|
}
|
|
cloned := make(fosite.Arguments, len(arguments))
|
|
copy(cloned, arguments)
|
|
return cloned
|
|
}
|
|
|
|
func sanitizeStoredForm(values url.Values) url.Values {
|
|
cloned := cloneValues(values)
|
|
cloned.Del("client_secret")
|
|
cloned.Del("client_assertion")
|
|
return cloned
|
|
}
|
|
|
|
func cloneValues(values url.Values) url.Values {
|
|
if len(values) == 0 {
|
|
return url.Values{}
|
|
}
|
|
cloned := make(url.Values, len(values))
|
|
for key, value := range values {
|
|
cloned[key] = append([]string(nil), value...)
|
|
}
|
|
return cloned
|
|
}
|
|
|
|
func expiresAt(session fosite.Session, tokenType fosite.TokenType) *datatype.DateTime {
|
|
if session == nil || tokenType == "" {
|
|
return nil
|
|
}
|
|
exp := session.GetExpiresAt(tokenType)
|
|
if exp.IsZero() {
|
|
return nil
|
|
}
|
|
return new(datatype.DateTime(exp))
|
|
}
|
|
|
|
func (s *Store) dbFor(ctx context.Context) *gorm.DB {
|
|
return dbFromContext(ctx, s.db)
|
|
}
|