Refactor previous COOPGO Identity service - Initial commit
This commit is contained in:
565
oidc-provider/fosite.go
Normal file
565
oidc-provider/fosite.go
Normal file
@@ -0,0 +1,565 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user