mobility-accounts/oidc-provider/fosite.go

566 lines
15 KiB
Go
Raw Permalink Normal View History

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
}
}