package op import ( "context" "crypto/rsa" "errors" "reflect" "time" "git.coopgo.io/coopgo-platform/mobility-accounts/handlers" "git.coopgo.io/coopgo-platform/mobility-accounts/storage" "github.com/mitchellh/mapstructure" "github.com/ory/fosite" "github.com/ory/fosite/compose" "github.com/ory/fosite/handler/openid" "gopkg.in/square/go-jose.v2" ) func NewProvider(c OIDCNamespaceConfig, h handlers.MobilityAccountsHandler, s storage.Storage, privateKey *rsa.PrivateKey) fosite.OAuth2Provider { config := &compose.Config{} storage := NewOIDCProviderStore(c, h, s.KV) secret := []byte(c.SecretKey) return compose.ComposeAllEnabled(config, storage, secret, privateKey) } type OIDCProviderStore struct { Namespace string MobilityAccountsHandler handlers.MobilityAccountsHandler KV storage.KVStore Clients map[string]fosite.Client } func NewOIDCProviderStore(c OIDCNamespaceConfig, h handlers.MobilityAccountsHandler, storage storage.KVStore) *OIDCProviderStore { clients := map[string]fosite.Client{} for _, v := range c.Clients { client := &fosite.DefaultClient{ ID: v.ID, Secret: []byte(v.Secret), RedirectURIs: v.RedirectURIs, ResponseTypes: v.ResponseTypes, GrantTypes: v.GrantTypes, Scopes: v.Scopes, Audience: v.Audience, Public: v.Public, } if v.OIDC { oidc_client := &fosite.DefaultOpenIDConnectClient{ DefaultClient: client, TokenEndpointAuthMethod: v.TokenEndpointAuthMethod, } clients[v.ID] = oidc_client } else { clients[v.ID] = client } } return &OIDCProviderStore{ MobilityAccountsHandler: h, KV: storage, Clients: clients, } } func (s *OIDCProviderStore) GetClient(_ context.Context, id string) (fosite.Client, error) { cl, ok := s.Clients[id] if !ok { return nil, fosite.ErrNotFound } return cl, nil } func (s *OIDCProviderStore) Authenticate(_ context.Context, name string, secret string) error { _, err := s.MobilityAccountsHandler.Login(name, secret, s.Namespace) if err != nil { return fosite.ErrNotFound } return nil } func (s *OIDCProviderStore) CreateOpenIDConnectSession(_ context.Context, authorizeCode string, requester fosite.Requester) error { err := s.KV.Put("id_sessions/"+authorizeCode, requester) if err != nil { return err } return nil } func (s *OIDCProviderStore) GetOpenIDConnectSession(_ context.Context, authorizeCode string, requester fosite.Requester) (fosite.Requester, error) { d, err := s.KV.Get("id_sessions/" + authorizeCode) if err != nil { return nil, fosite.ErrNotFound } //return d.(fosite.Requester), nil return DecodeRequest(d) // req := fosite.NewRequest() // req.Session = new(openid.DefaultSession) // decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ // Metadata: nil, // DecodeHook: mapstructure.ComposeDecodeHookFunc( // ToTimeHookFunc()), // Result: &req, // }) // if err != nil { // return req, err // } // if err = decoder.Decode(d); err != nil { // return req, err // } // return req, nil } // DeleteOpenIDConnectSession is not really called from anywhere and it is deprecated. func (s *OIDCProviderStore) DeleteOpenIDConnectSession(_ context.Context, authorizeCode string) error { err := s.KV.Delete("id_sessions/" + authorizeCode) if err != nil { return err } return nil } func (s *OIDCProviderStore) ClientAssertionJWTValid(_ context.Context, jti string) error { if _, exists := s.KV.Get("blacklisted_jtis/" + jti); exists == nil { return fosite.ErrJTIKnown } return nil } func (s *OIDCProviderStore) SetClientAssertionJWT(_ context.Context, jti string, exp time.Time) error { if _, exists := s.KV.Get("blacklisted_jtis/" + jti); exists == nil { return fosite.ErrJTIKnown } duration := exp.Sub(time.Now()) if duration < 0 { return errors.New("already expired") } err := s.KV.PutWithTTL("blacklisted_jtis/"+jti, true, duration) if err != nil { return err } return nil } func (s *OIDCProviderStore) CreateAuthorizeCodeSession(_ context.Context, code string, req fosite.Requester) error { res := StoreAuthorizeCode{ Active: true, Requester: req.(*fosite.Request), } err := s.KV.Put("authorize_codes/"+code, res) if err != nil { return err } return nil } func (s *OIDCProviderStore) GetAuthorizeCodeSession(_ context.Context, code string, _ fosite.Session) (fosite.Requester, error) { rel, err := s.KV.Get("authorize_codes/" + code) if err != nil { return nil, fosite.ErrNotFound } sac, err := DecodeStoreAuthorizeCode(rel) if err != nil { return nil, err } if !sac.Active { return sac.Requester, fosite.ErrInvalidatedAuthorizeCode } return sac.Requester, nil } func (s *OIDCProviderStore) InvalidateAuthorizeCodeSession(ctx context.Context, code string) error { rel, err := s.KV.Get("authorize_codes/" + code) if err != nil { return fosite.ErrNotFound } sac, err := DecodeStoreAuthorizeCode(rel) if err != nil { return err } sac.Active = false err = s.KV.Put("authorize_codes/"+code, sac) if err != nil { return err } return nil } func (s *OIDCProviderStore) CreatePKCERequestSession(_ context.Context, code string, req fosite.Requester) error { err := s.KV.Put("pkce/"+code, req) if err != nil { return err } return nil } func (s *OIDCProviderStore) GetPKCERequestSession(_ context.Context, code string, _ fosite.Session) (fosite.Requester, error) { rel, err := s.KV.Get("pkce/" + code) if err != nil { return nil, fosite.ErrNotFound } req := fosite.NewRequest() req.Session = new(fosite.DefaultSession) decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ Metadata: nil, DecodeHook: mapstructure.ComposeDecodeHookFunc( ToTimeHookFunc()), Result: &req, }) if err != nil { return req, err } if err = decoder.Decode(rel); err != nil { return req, err } return req, nil } func (s *OIDCProviderStore) DeletePKCERequestSession(_ context.Context, code string) error { err := s.KV.Delete("pkce/" + code) if err != nil { return fosite.ErrNotFound } return nil } func (s *OIDCProviderStore) CreateAccessTokenSession(_ context.Context, signature string, req fosite.Requester) error { err := s.KV.Put("access_tokens/"+signature, req) if err != nil { return err } err = s.KV.Put("access_tokens_request_ids/"+req.GetID(), signature) if err != nil { return err } return nil } func (s *OIDCProviderStore) GetAccessTokenSession(_ context.Context, signature string, _ fosite.Session) (fosite.Requester, error) { rel, err := s.KV.Get("access_tokens/" + signature) if err != nil { return nil, fosite.ErrNotFound } //return rel.(fosite.Requester), nil req := fosite.NewRequest() req.Session = new(fosite.DefaultSession) decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ Metadata: nil, DecodeHook: mapstructure.ComposeDecodeHookFunc( ToTimeHookFunc()), Result: &req, }) if err != nil { return req, err } if err = decoder.Decode(rel); err != nil { return req, err } return req, nil } func (s *OIDCProviderStore) DeleteAccessTokenSession(_ context.Context, signature string) error { err := s.KV.Delete("access_tokens/" + signature) if err != nil { return fosite.ErrNotFound } return nil } func (s *OIDCProviderStore) CreateRefreshTokenSession(_ context.Context, signature string, req fosite.Requester) error { err := s.KV.Put("refresh_tokens/"+signature, StoreRefreshToken{Active: true, Requester: req}) if err != nil { return err } err = s.KV.Put("refresh_tokens_request_ids/"+req.GetID(), signature) if err != nil { return err } return nil } func (s *OIDCProviderStore) GetRefreshTokenSession(_ context.Context, signature string, _ fosite.Session) (fosite.Requester, error) { rel, err := s.KV.Get("refresh_tokens/" + signature) if err != nil { return nil, fosite.ErrNotFound } var srt StoreRefreshToken if err = mapstructure.Decode(rel.(map[string]any), &srt); err != nil { return nil, err } if !srt.Active { return nil, fosite.ErrInactiveToken } return srt.Requester, nil } func (s *OIDCProviderStore) DeleteRefreshTokenSession(_ context.Context, signature string) error { err := s.KV.Delete("refresh_tokens/" + signature) if err != nil { return fosite.ErrNotFound } return nil } func (s *OIDCProviderStore) RevokeRefreshToken(ctx context.Context, requestID string) error { if signature, err := s.KV.Get("refresh_tokens_request_ids" + requestID); err == nil { rel, err := s.KV.Get("refresh_tokens/" + signature.(string)) if err != nil { return fosite.ErrNotFound } var srt StoreRefreshToken if err = mapstructure.Decode(rel.(map[string]any), &srt); err != nil { return err } srt.Active = false err = s.KV.Put("refresh_tokens/"+signature.(string), srt) if err != nil { return err } } return nil } func (s *OIDCProviderStore) RevokeRefreshTokenMaybeGracePeriod(ctx context.Context, requestID string, signature string) error { // no configuration option is available; grace period is not available with memory store return s.RevokeRefreshToken(ctx, requestID) } func (s *OIDCProviderStore) RevokeAccessToken(ctx context.Context, requestID string) error { if signature, err := s.KV.Get("access_tokens_request_ids/" + requestID); err != nil { if err := s.DeleteAccessTokenSession(ctx, signature.(string)); err != nil { return err } } return nil } func (s *OIDCProviderStore) GetPublicKey(ctx context.Context, issuer string, subject string, keyId string) (*jose.JSONWebKey, error) { if issuerKeys, err := s.KV.Get("issuer_public_keys/" + issuer); err == nil { var ipk IssuerPublicKeys if err = mapstructure.Decode(issuerKeys.(map[string]any), &ipk); err != nil { return nil, err } if subKeys, ok := ipk.KeysBySub[subject]; ok { if keyScopes, ok := subKeys.Keys[keyId]; ok { return keyScopes.Key, nil } } } return nil, fosite.ErrNotFound } func (s *OIDCProviderStore) GetPublicKeys(ctx context.Context, issuer string, subject string) (*jose.JSONWebKeySet, error) { if issuerKeys, err := s.KV.Get("issuer_public_keys/" + issuer); err == nil { var ipk IssuerPublicKeys if err = mapstructure.Decode(issuerKeys.(map[string]any), &ipk); err != nil { return nil, err } if subKeys, ok := ipk.KeysBySub[subject]; ok { if len(subKeys.Keys) == 0 { return nil, fosite.ErrNotFound } keys := make([]jose.JSONWebKey, 0, len(subKeys.Keys)) for _, keyScopes := range subKeys.Keys { keys = append(keys, *keyScopes.Key) } return &jose.JSONWebKeySet{Keys: keys}, nil } } return nil, fosite.ErrNotFound } func (s *OIDCProviderStore) GetPublicKeyScopes(ctx context.Context, issuer string, subject string, keyId string) ([]string, error) { if issuerKeys, err := s.KV.Get("issuer_public_keys/" + issuer); err == nil { var ipk IssuerPublicKeys if err = mapstructure.Decode(issuerKeys.(map[string]any), &ipk); err != nil { return nil, err } if subKeys, ok := ipk.KeysBySub[subject]; ok { if keyScopes, ok := subKeys.Keys[keyId]; ok { return keyScopes.Scopes, nil } } } return nil, fosite.ErrNotFound } func (s *OIDCProviderStore) IsJWTUsed(ctx context.Context, jti string) (bool, error) { err := s.ClientAssertionJWTValid(ctx, jti) if err != nil { return true, nil } return false, nil } func (s *OIDCProviderStore) MarkJWTUsedForTime(ctx context.Context, jti string, exp time.Time) error { return s.SetClientAssertionJWT(ctx, jti, exp) } // CreatePARSession stores the pushed authorization request context. The requestURI is used to derive the key. func (s *OIDCProviderStore) CreatePARSession(ctx context.Context, requestURI string, request fosite.AuthorizeRequester) error { err := s.KV.Put("par_sessions/"+requestURI, request) if err != nil { return err } return nil } // GetPARSession gets the push authorization request context. If the request is nil, a new request object // is created. Otherwise, the same object is updated. func (s *OIDCProviderStore) GetPARSession(ctx context.Context, requestURI string) (fosite.AuthorizeRequester, error) { rel, err := s.KV.Get("par_sessions/" + requestURI) if err != nil { return nil, fosite.ErrNotFound } return rel.(fosite.AuthorizeRequester), nil } // DeletePARSession deletes the context. func (s *OIDCProviderStore) DeletePARSession(ctx context.Context, requestURI string) error { err := s.KV.Delete("par_sessions/" + requestURI) if err != nil { return fosite.ErrNotFound } return nil } type StoreAuthorizeCode struct { Active bool `json:"active"` Requester *fosite.Request `json:"requester"` } func DecodeStoreAuthorizeCode(rel interface{}) (StoreAuthorizeCode, error) { sac := StoreAuthorizeCode{ Active: false, Requester: fosite.NewRequest(), } // metadata := mapstructure.Metadata{} sac.Requester.Session = new(openid.DefaultSession) decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ // Metadata: &metadata, DecodeHook: mapstructure.ComposeDecodeHookFunc( ToTimeHookFunc(), ToBytes(), ), TagName: "json", Result: &sac, }) if err != nil { return sac, err } if err = decoder.Decode(rel); err != nil { return sac, err } return sac, nil } func DecodeRequest(rel interface{}) (*fosite.Request, error) { req := fosite.NewRequest() req.Session = new(openid.DefaultSession) decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ // Metadata: &metadata, DecodeHook: mapstructure.ComposeDecodeHookFunc( ToTimeHookFunc(), ToBytes(), ), TagName: "json", Result: &req, }) if err != nil { return req, err } if err = decoder.Decode(rel); err != nil { return req, err } return req, nil } type StoreRefreshToken struct { Active bool Requester fosite.Requester } type IssuerPublicKeys struct { Issuer string KeysBySub map[string]SubjectPublicKeys `mapstructure:"keys_by_sub"` } type SubjectPublicKeys struct { Subject string Keys map[string]PublicKeyScopes } type PublicKeyScopes struct { Key *jose.JSONWebKey Scopes []string } func ToTimeHookFunc() mapstructure.DecodeHookFunc { return func( f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { if t != reflect.TypeOf(time.Time{}) { return data, nil } switch f.Kind() { case reflect.String: return time.Parse(time.RFC3339, data.(string)) case reflect.Float64: return time.Unix(0, int64(data.(float64))*int64(time.Millisecond)), nil case reflect.Int64: return time.Unix(0, data.(int64)*int64(time.Millisecond)), nil default: return data, nil } // Convert it by parsing } } func ToBytes() mapstructure.DecodeHookFunc { return func( f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { if t == reflect.TypeOf([]byte("")) && f.Kind() == reflect.String { return []byte(data.(string)), nil } return data, nil } }