diff --git a/example/internal/mock/storage.go b/example/internal/mock/storage.go index f272f77..9b8ba5e 100644 --- a/example/internal/mock/storage.go +++ b/example/internal/mock/storage.go @@ -12,18 +12,20 @@ import ( "github.com/caos/oidc/pkg/op" ) -type Storage struct { +type AuthStorage struct { key *rsa.PrivateKey } -func NewStorage() op.Storage { +type OPStorage struct{} + +func NewAuthStorage() op.AuthStorage { reader := rand.Reader bitSize := 2048 key, err := rsa.GenerateKey(reader, bitSize) if err != nil { panic(err) } - return &Storage{ + return &AuthStorage{ key: key, } } @@ -80,10 +82,10 @@ func (a *AuthRequest) GetSubject() string { return "" } -func (s *Storage) CreateAuthRequest(authReq *oidc.AuthRequest) (op.AuthRequest, error) { +func (s *AuthStorage) CreateAuthRequest(authReq *oidc.AuthRequest) (op.AuthRequest, error) { return &AuthRequest{ID: "id"}, nil } -func (s *Storage) GetClientByClientID(id string) (op.Client, error) { +func (s *OPStorage) GetClientByClientID(id string) (op.Client, error) { if id == "none" { return nil, errors.New("not found") } @@ -97,19 +99,19 @@ func (s *Storage) GetClientByClientID(id string) (op.Client, error) { } return &ConfClient{applicationType: appType}, nil } -func (s *Storage) AuthRequestByCode(op.Client, string, string) (op.AuthRequest, error) { +func (s *AuthStorage) AuthRequestByCode(op.Client, string, string) (op.AuthRequest, error) { return &AuthRequest{ID: "native"}, nil } -func (s *Storage) AuthorizeClientIDSecret(string, string) (op.Client, error) { +func (s *OPStorage) AuthorizeClientIDSecret(string, string) (op.Client, error) { return &ConfClient{}, nil } -func (s *Storage) AuthorizeClientIDCodeVerifier(string, string) (op.Client, error) { +func (s *OPStorage) AuthorizeClientIDCodeVerifier(string, string) (op.Client, error) { return &ConfClient{}, nil } -func (s *Storage) DeleteAuthRequestAndCode(string, string) error { +func (s *AuthStorage) DeleteAuthRequestAndCode(string, string) error { return nil } -func (s *Storage) AuthRequestByID(id string) (op.AuthRequest, error) { +func (s *AuthStorage) AuthRequestByID(id string) (op.AuthRequest, error) { if id == "none" { return nil, errors.New("not found") } @@ -127,10 +129,10 @@ func (s *Storage) AuthRequestByID(id string) (op.AuthRequest, error) { }, nil } -func (s *Storage) GetSigningKey() (*jose.SigningKey, error) { +func (s *AuthStorage) GetSigningKey() (*jose.SigningKey, error) { return &jose.SigningKey{Algorithm: jose.RS256, Key: s.key}, nil } -func (s *Storage) GetKeySet() (jose.JSONWebKeySet, error) { +func (s *AuthStorage) GetKeySet() (jose.JSONWebKeySet, error) { pubkey := s.key.Public() return jose.JSONWebKeySet{ Keys: []jose.JSONWebKey{ diff --git a/example/server/default/default.go b/example/server/default/default.go index f7b97f3..af61a45 100644 --- a/example/server/default/default.go +++ b/example/server/default/default.go @@ -15,8 +15,9 @@ func main() { Port: "9998", } - storage := mock.NewStorage() - handler, err := op.NewDefaultOP(config, storage, op.WithCustomTokenEndpoint("test")) + authStorage := mock.NewAuthStorage() + opStorage := &mock.OPStorage{} + handler, err := op.NewDefaultOP(config, authStorage, opStorage, op.WithCustomTokenEndpoint("test")) if err != nil { log.Fatal(err) } diff --git a/pkg/op/config.go b/pkg/op/config.go index 7d7c853..ee9a107 100644 --- a/pkg/op/config.go +++ b/pkg/op/config.go @@ -12,6 +12,11 @@ type Configuration interface { TokenEndpoint() Endpoint UserinfoEndpoint() Endpoint KeysEndpoint() Endpoint + + // SupportedScopes() []string + AuthMethodBasicSupported() bool + AuthMethodPostSupported() bool + Port() string } diff --git a/pkg/op/default_op.go b/pkg/op/default_op.go index db39fe8..98affbc 100644 --- a/pkg/op/default_op.go +++ b/pkg/op/default_op.go @@ -15,6 +15,9 @@ const ( defaultIntrospectEndpoint = "introspect" defaultUserinfoEndpoint = "userinfo" defaultKeysEndpoint = "keys" + + authMethodBasic = "client_secret_basic" + authMethodPost = "client_secret_post" ) var ( @@ -94,19 +97,27 @@ func WithCustomUserinfoEndpoint(endpoint Endpoint) DefaultOPOpts { } } -func NewDefaultOP(config *Config, storage Storage, opOpts ...DefaultOPOpts) (OpenIDProvider, error) { +func NewDefaultOP(config *Config, authStorage AuthStorage, opStorage OPStorage, opOpts ...DefaultOPOpts) (OpenIDProvider, error) { err := ValidateIssuer(config.Issuer) if err != nil { return nil, err } + storage := struct { + AuthStorage + OPStorage + }{ + AuthStorage: authStorage, + OPStorage: opStorage, + } + p := &DefaultOP{ config: config, storage: storage, endpoints: DefaultEndpoints, } - p.signer, err = NewDefaultSigner(storage) + p.signer, err = NewDefaultSigner(authStorage) if err != nil { return nil, err } @@ -117,7 +128,7 @@ func NewDefaultOP(config *Config, storage Storage, opOpts ...DefaultOPOpts) (Ope } } - p.discoveryConfig = CreateDiscoveryConfig(p) + p.discoveryConfig = CreateDiscoveryConfig(p, p.signer) router := CreateRouter(p) p.http = &http.Server{ @@ -152,6 +163,14 @@ func (p *DefaultOP) KeysEndpoint() Endpoint { return Endpoint(p.endpoints.JwksURI) } +func (p *DefaultOP) AuthMethodBasicSupported() bool { + return true //TODO: config +} + +func (p *DefaultOP) AuthMethodPostSupported() bool { + return true //TODO: config +} + func (p *DefaultOP) Port() string { return p.config.Port } @@ -218,6 +237,8 @@ func (p *DefaultOP) HandleExchange(w http.ResponseWriter, r *http.Request) { } func (p *DefaultOP) handleTokenExchange(w http.ResponseWriter, r *http.Request) { + ExchangeRequestError(w, r, ErrServerError("not implemented")) + return tokenRequest, err := ParseTokenExchangeRequest(w, r) if err != nil { //TODO: return err diff --git a/pkg/op/discovery.go b/pkg/op/discovery.go index a3db9be..411d668 100644 --- a/pkg/op/discovery.go +++ b/pkg/op/discovery.go @@ -11,7 +11,7 @@ func Discover(w http.ResponseWriter, config *oidc.DiscoveryConfiguration) { utils.MarshalJSON(w, config) } -func CreateDiscoveryConfig(c Configuration) *oidc.DiscoveryConfiguration { +func CreateDiscoveryConfig(c Configuration, s Signer) *oidc.DiscoveryConfiguration { return &oidc.DiscoveryConfiguration{ Issuer: c.Issuer(), AuthorizationEndpoint: c.AuthorizationEndpoint().Absolute(c.Issuer()), @@ -20,14 +20,61 @@ func CreateDiscoveryConfig(c Configuration) *oidc.DiscoveryConfiguration { UserinfoEndpoint: c.UserinfoEndpoint().Absolute(c.Issuer()), // EndSessionEndpoint: c.TokenEndpoint().Absolute(c.Issuer())(c.EndSessionEndpoint), // CheckSessionIframe: c.TokenEndpoint().Absolute(c.Issuer())(c.CheckSessionIframe), - JwksURI: c.KeysEndpoint().Absolute(c.Issuer()), - // ScopesSupported: oidc.SupportedScopes, - // ResponseTypesSupported: responseTypes, - // GrantTypesSupported: oidc.SupportedGrantTypes, + JwksURI: c.KeysEndpoint().Absolute(c.Issuer()), + ScopesSupported: scopes(c), + ResponseTypesSupported: responseTypes(c), + GrantTypesSupported: grantTypes(c), // ClaimsSupported: oidc.SupportedClaims, - // IdTokenSigningAlgValuesSupported: []string{keys.SigningAlgorithm}, - // SubjectTypesSupported: []string{"public"}, - // TokenEndpointAuthMethodsSupported: - + IDTokenSigningAlgValuesSupported: sigAlgorithms(s), + SubjectTypesSupported: subjectTypes(c), + TokenEndpointAuthMethodsSupported: authMethods(c), } } + +func scopes(c Configuration) []string { + return []string{ + "openid", + "profile", + "email", + "phone", + } //TODO: config +} + +func responseTypes(c Configuration) []string { + return []string{ + "code", + "id_token", + // "code token", + // "code id_token", + "id_token token", + // "code id_token token" + } +} + +func grantTypes(c Configuration) []string { + return []string{ + "client_credentials", + "authorization_code", + // "password", + "urn:ietf:params:oauth:grant-type:token-exchange", + } +} + +func sigAlgorithms(s Signer) []string { + return []string{string(s.SignatureAlgorithm())} +} + +func subjectTypes(c Configuration) []string { + return []string{"public"} //TODO: config +} + +func authMethods(c Configuration) []string { + authMethods := make([]string, 0, 2) + if c.AuthMethodBasicSupported() { + authMethods = append(authMethods, authMethodBasic) + } + if c.AuthMethodPostSupported() { + authMethods = append(authMethods, authMethodPost) + } + return authMethods +} diff --git a/pkg/op/discovery_test.go b/pkg/op/discovery_test.go index 0df04a5..bfc2360 100644 --- a/pkg/op/discovery_test.go +++ b/pkg/op/discovery_test.go @@ -42,6 +42,7 @@ func TestDiscover(t *testing.T) { func TestCreateDiscoveryConfig(t *testing.T) { type args struct { c op.Configuration + s op.Signer } tests := []struct { name string @@ -52,7 +53,7 @@ func TestCreateDiscoveryConfig(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := op.CreateDiscoveryConfig(tt.args.c); !reflect.DeepEqual(got, tt.want) { + if got := op.CreateDiscoveryConfig(tt.args.c, tt.args.s); !reflect.DeepEqual(got, tt.want) { t.Errorf("CreateDiscoveryConfig() = %v, want %v", got, tt.want) } }) diff --git a/pkg/op/signer.go b/pkg/op/signer.go index 39dfdc6..9692211 100644 --- a/pkg/op/signer.go +++ b/pkg/op/signer.go @@ -15,11 +15,11 @@ type Signer interface { type idTokenSigner struct { signer jose.Signer - storage Storage + storage AuthStorage algorithm jose.SignatureAlgorithm } -func NewDefaultSigner(storage Storage) (Signer, error) { +func NewDefaultSigner(storage AuthStorage) (Signer, error) { s := &idTokenSigner{ storage: storage, } diff --git a/pkg/op/storage.go b/pkg/op/storage.go index 3dc06ad..f4fa274 100644 --- a/pkg/op/storage.go +++ b/pkg/op/storage.go @@ -8,18 +8,27 @@ import ( "github.com/caos/oidc/pkg/oidc" ) -type Storage interface { +type AuthStorage interface { CreateAuthRequest(*oidc.AuthRequest) (AuthRequest, error) - GetClientByClientID(string) (Client, error) AuthRequestByID(string) (AuthRequest, error) AuthRequestByCode(Client, string, string) (AuthRequest, error) - AuthorizeClientIDSecret(string, string) (Client, error) - AuthorizeClientIDCodeVerifier(string, string) (Client, error) DeleteAuthRequestAndCode(string, string) error + GetSigningKey() (*jose.SigningKey, error) GetKeySet() (jose.JSONWebKeySet, error) } +type OPStorage interface { + GetClientByClientID(string) (Client, error) + AuthorizeClientIDSecret(string, string) (Client, error) + AuthorizeClientIDCodeVerifier(string, string) (Client, error) +} + +type Storage interface { + AuthStorage + OPStorage +} + type AuthRequest interface { GetID() string GetACR() string diff --git a/pkg/op/tokenrequest.go b/pkg/op/tokenrequest.go index 83b3cde..db00746 100644 --- a/pkg/op/tokenrequest.go +++ b/pkg/op/tokenrequest.go @@ -17,6 +17,8 @@ type Exchanger interface { Storage() Storage Decoder() *schema.Decoder Signer() Signer + AuthMethodBasicSupported() bool + AuthMethodPostSupported() bool } func CodeExchange(w http.ResponseWriter, r *http.Request, exchanger Exchanger) { @@ -37,7 +39,7 @@ func CodeExchange(w http.ResponseWriter, r *http.Request, exchanger Exchanger) { return } - client, err := AuthorizeClient(r, tokenReq, exchanger.Storage()) + client, err := AuthorizeClient(r, tokenReq, exchanger) if err != nil { ExchangeRequestError(w, r, err) return @@ -99,19 +101,25 @@ func CreateIDToken(issuer string, authReq AuthRequest, validity time.Duration, a return signer.SignIDToken(claims) } -func AuthorizeClient(r *http.Request, tokenReq *oidc.AccessTokenRequest, storage Storage) (Client, error) { +func AuthorizeClient(r *http.Request, tokenReq *oidc.AccessTokenRequest, exchanger Exchanger) (Client, error) { if tokenReq.ClientID == "" { + if !exchanger.AuthMethodBasicSupported() { + return nil, errors.New("basic not supported") + } clientID, clientSecret, ok := r.BasicAuth() if ok { - return storage.AuthorizeClientIDSecret(clientID, clientSecret) + return exchanger.Storage().AuthorizeClientIDSecret(clientID, clientSecret) } } if tokenReq.ClientSecret != "" { - return storage.AuthorizeClientIDSecret(tokenReq.ClientID, tokenReq.ClientSecret) + if !exchanger.AuthMethodPostSupported() { + return nil, errors.New("post not supported") + } + return exchanger.Storage().AuthorizeClientIDSecret(tokenReq.ClientID, tokenReq.ClientSecret) } if tokenReq.CodeVerifier != "" { - return storage.AuthorizeClientIDCodeVerifier(tokenReq.ClientID, tokenReq.CodeVerifier) + return exchanger.Storage().AuthorizeClientIDCodeVerifier(tokenReq.ClientID, tokenReq.CodeVerifier) } return nil, errors.New("Unimplemented") //TODO: impl }