566 lines
15 KiB
Go
Executable File
566 lines
15 KiB
Go
Executable File
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
|
|
}
|
|
}
|