Refactor previous COOPGO Identity service - Initial commit

This commit is contained in:
2022-08-02 12:26:28 +02:00
commit 3e93e6593d
41 changed files with 9026 additions and 0 deletions

View File

@@ -0,0 +1,101 @@
package op
import (
"fmt"
"html/template"
"net/http"
"time"
"github.com/gorilla/csrf"
"github.com/gorilla/mux"
"github.com/ory/fosite"
"github.com/ory/fosite/handler/openid"
"github.com/ory/fosite/token/jwt"
)
func (op *OIDCHandler) AuthEndpoint(w http.ResponseWriter, r *http.Request) {
namespace := mux.Vars(r)["namespace"]
oauth2Provider := op.NamespaceProviders[namespace]
templates_dir := op.config.Namespaces[namespace].TemplatesDir
t := template.New("auth")
t = template.Must(t.ParseFiles(
templates_dir + "/auth.html",
))
ctx := r.Context()
ar, err := oauth2Provider.NewAuthorizeRequest(ctx, r)
if err != nil {
oauth2Provider.WriteAuthorizeError(w, ar, err)
return
}
if r.Method == "POST" {
if r.Form.Get("username") == "" || r.Form.Get("password") == "" {
oauth2Provider.WriteAuthorizeError(w, ar, fosite.ErrAccessDenied)
return
}
account, err := op.handler.Login(r.Form.Get("username"), r.Form.Get("password"), namespace)
if err != nil {
if err = t.ExecuteTemplate(w, "auth", map[string]any{
csrf.TemplateTag: csrf.TemplateField(r),
"error": fmt.Sprintf("Wrong username (%v) or password (%v) in namespace \"%v\"", r.Form.Get("username"), r.Form.Get("password"), namespace),
"realError": err,
}); err != nil {
oauth2Provider.WriteAuthorizeError(w, ar, err)
}
return
}
sessionData := &openid.DefaultSession{
Claims: &jwt.IDTokenClaims{
Issuer: fmt.Sprintf("http://%s/%s", r.Host, namespace),
Subject: account.ID,
Audience: []string{},
ExpiresAt: time.Now().Add(time.Hour * 30),
IssuedAt: time.Now(),
RequestedAt: time.Now(),
AuthTime: time.Now(),
Extra: make(map[string]interface{}),
},
Username: r.Form.Get("username"),
Subject: account.ID,
Headers: &jwt.Headers{
Extra: make(map[string]interface{}),
},
}
// Manage claims
for _, v := range ar.GetRequestedScopes() {
ar.GrantScope(v)
if v != "openid" { // TODO handle standard claims like profile, email, ...
if mc, ok := op.config.Namespaces[namespace].MatchClaims[v]; ok {
if d, ok := account.Data[mc]; ok {
sessionData.Claims.Extra[v] = d
}
} else if d, ok := account.Data[v]; ok {
sessionData.Claims.Extra[v] = d
}
}
}
response, err := oauth2Provider.NewAuthorizeResponse(ctx, ar, sessionData)
if err != nil {
oauth2Provider.WriteAuthorizeError(w, ar, err)
return
}
oauth2Provider.WriteAuthorizeResponse(w, ar, response)
return
}
err = t.ExecuteTemplate(w, "auth", map[string]any{
// csrf.TemplateTag: csrf.TemplateField(r),
})
if err != nil {
panic(err)
}
}

View File

@@ -0,0 +1,26 @@
package op
import (
"log"
"net/http"
"github.com/gorilla/mux"
"github.com/ory/fosite"
)
func (op *OIDCHandler) IntrospectionEndpoint(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
namespace := mux.Vars(r)["namespace"]
oauth2Provider := op.NamespaceProviders[namespace]
ctx := r.Context()
mySessionData := new(fosite.DefaultSession)
ir, err := oauth2Provider.NewIntrospectionRequest(ctx, r, mySessionData)
if err != nil {
log.Printf("Error occurred in NewIntrospectionRequest: %+v", err)
oauth2Provider.WriteIntrospectionError(w, err)
return
}
oauth2Provider.WriteIntrospectionResponse(w, ir)
}

View File

@@ -0,0 +1,41 @@
package op
import (
"fmt"
"net/http"
"github.com/gorilla/mux"
"github.com/ory/fosite/handler/openid"
)
func (op *OIDCHandler) TokenEndpoint(w http.ResponseWriter, req *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
namespace := mux.Vars(req)["namespace"]
provider := op.NamespaceProviders[namespace]
ctx := req.Context()
mySessionData := openid.NewDefaultSession()
accessRequest, err := provider.NewAccessRequest(ctx, req, mySessionData)
if err != nil {
fmt.Printf("Error occurred in NewAccessRequest: %+v", err)
provider.WriteAccessError(w, accessRequest, err)
return
}
if accessRequest.GetGrantTypes().ExactOne("client_credentials") {
for _, scope := range accessRequest.GetRequestedScopes() {
accessRequest.GrantScope(scope)
}
}
response, err := provider.NewAccessResponse(ctx, accessRequest)
if err != nil {
fmt.Printf("Error occurred in NewAccessResponse: %+v", err)
provider.WriteAccessError(w, accessRequest, err)
return
}
provider.WriteAccessResponse(w, accessRequest, response)
}

View File

@@ -0,0 +1,8 @@
package op
import "net/http"
func (op *OIDCHandler) UserinfoEndpoint(w http.ResponseWriter, req *http.Request) {
// TODO
w.WriteHeader(http.StatusNotFound)
}

View File

@@ -0,0 +1,62 @@
package op
import (
"encoding/json"
"fmt"
"net/http"
"github.com/gorilla/mux"
"gopkg.in/square/go-jose.v2"
)
func (op *OIDCHandler) WellKnownOIDCEndpoint(w http.ResponseWriter, r *http.Request) {
var (
host = r.Host
namespace = mux.Vars(r)["namespace"]
issuer = fmt.Sprintf("http://%s/%s", host, namespace)
)
response := map[string]any{
"issuer": issuer,
"authorization_endpoint": issuer + "/auth",
"token_endpoint": issuer + "/token",
"userinfo_endpoint": issuer + "/userinfo",
"id_token_signing_alg_values_supported": []string{"RS256"},
"grant_types_supported": []string{"authorization_code", "implicit", "client_credentials", "refresh_token"},
"response_types": []string{"code", "code id_token", "id_token", "token id_token", "token", "token id_token code"},
"response_modes_supported": []string{"query", "fragment"},
"jwks_uri": issuer + "/.well-known/jwks.json",
}
json, err := json.Marshal(response)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
w.Write(json)
}
func (op *OIDCHandler) WellKnownJWKSEndpoint(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
jwks := &jose.JSONWebKeySet{
Keys: []jose.JSONWebKey{
{
KeyID: "kid-foo",
Use: "sig",
Key: &op.PrivateKey.PublicKey,
},
},
}
jsonJwks, err := json.Marshal(jwks)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
}
w.WriteHeader(http.StatusOK)
w.Write(jsonJwks)
}

565
oidc-provider/fosite.go Normal file
View 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
}
}

View File

@@ -0,0 +1,93 @@
package op
import (
"crypto/rand"
"crypto/rsa"
"fmt"
"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/spf13/viper"
)
type OIDCConfig struct {
Enable bool
CSRFKey bool `mapstructure:"csrf_key"`
Port bool
Namespaces map[string]OIDCNamespaceConfig
}
type OIDCNamespaceConfig struct {
Namespace string
SecretKey string `mapstructure:"secret_key"`
TemplatesDir string `mapstructure:"templates_dir"`
MatchClaims map[string]string `mapstructure:"match_claims"`
Clients []OIDCClient
}
type OIDCClient struct {
ID string
OIDC bool
Secret string
RedirectURIs []string `mapstructure:"redirect_uris"`
ResponseTypes []string `mapstructure:"response_types"`
GrantTypes []string `mapstructure:"grant_types"`
Scopes []string
Audience []string
Public bool
//OIDC specific
TokenEndpointAuthMethod string `mapstructure:"token_endpoint_auth_method"`
}
type OIDCHandler struct {
NamespaceProviders map[string]fosite.OAuth2Provider
config OIDCConfig
handler handlers.MobilityAccountsHandler
PrivateKey *rsa.PrivateKey
}
func NewOIDCHandler(h handlers.MobilityAccountsHandler, storage storage.Storage, config *viper.Viper) *OIDCHandler {
var oidc_config OIDCConfig
mapstructure.Decode(config.Get("services.oidc_provider").(map[string]any), &oidc_config)
providers := map[string]fosite.OAuth2Provider{}
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil
}
for _, c := range oidc_config.Namespaces {
np := NewProvider(c, h, storage, privateKey)
providers[c.Namespace] = np
}
return &OIDCHandler{
config: oidc_config,
handler: h,
NamespaceProviders: providers,
PrivateKey: privateKey,
}
}
func Run(done chan error, cfg *viper.Viper, handler handlers.MobilityAccountsHandler, storage storage.Storage) {
var (
address = "0.0.0.0:" + cfg.GetString("services.oidc_provider.port")
)
fmt.Println("-> OIDC provider endpoints on", address)
s := NewOIDCHandler(handler, storage, cfg)
err := NewOIDCServer(s, cfg)
if err != nil {
fmt.Println("OIDC server ended")
}
done <- err
}

40
oidc-provider/server.go Normal file
View File

@@ -0,0 +1,40 @@
package op
import (
"net/http"
"time"
"github.com/gorilla/csrf"
"github.com/gorilla/mux"
"github.com/spf13/viper"
)
func NewOIDCServer(oidc_handler *OIDCHandler, cfg *viper.Viper) error {
var (
dev_env = cfg.GetBool("dev_env")
address = "0.0.0.0:" + cfg.GetString("services.oidc_provider.port")
//csrf_key = cfg.GetString("services.oidc_provider.csrf_key")
)
router := mux.NewRouter()
router.HandleFunc("/{namespace}/auth", oidc_handler.AuthEndpoint)
router.HandleFunc("/{namespace}/token", oidc_handler.TokenEndpoint)
router.HandleFunc("/{namespace}/introspect", oidc_handler.IntrospectionEndpoint)
router.HandleFunc("/{namespace}/userinfo", oidc_handler.UserinfoEndpoint)
router.HandleFunc("/{namespace}/.well-known/openid-configuration", oidc_handler.WellKnownOIDCEndpoint)
router.HandleFunc("/{namespace}/.well-known/jwks.json", oidc_handler.WellKnownJWKSEndpoint)
if dev_env {
csrf.Secure(false)
}
srv := &http.Server{
Handler: router,
Addr: address,
WriteTimeout: 15 * time.Second,
ReadTimeout: 15 * time.Second,
}
err := srv.ListenAndServe()
return err
}

View File

@@ -0,0 +1,16 @@
{{define "auth"}}
<html>
<head></head>
<body>
<h1>Login page</h1>
<p>{{.error}}</p>
<p>{{.realError}}</p>
<form method="post">
{{ .csrfField }}
Username : <input type="text" name="username" />
Password : <input type="text" name="password" />
<input type="submit" value="Submit">
</form>
</body>
</html>
{{end}}