(discovery) config and storage

This commit is contained in:
Livio Amstutz 2019-12-03 15:17:06 +01:00
parent ce6f3182a2
commit ecea7e3730
9 changed files with 132 additions and 38 deletions

View file

@ -12,18 +12,20 @@ import (
"github.com/caos/oidc/pkg/op" "github.com/caos/oidc/pkg/op"
) )
type Storage struct { type AuthStorage struct {
key *rsa.PrivateKey key *rsa.PrivateKey
} }
func NewStorage() op.Storage { type OPStorage struct{}
func NewAuthStorage() op.AuthStorage {
reader := rand.Reader reader := rand.Reader
bitSize := 2048 bitSize := 2048
key, err := rsa.GenerateKey(reader, bitSize) key, err := rsa.GenerateKey(reader, bitSize)
if err != nil { if err != nil {
panic(err) panic(err)
} }
return &Storage{ return &AuthStorage{
key: key, key: key,
} }
} }
@ -80,10 +82,10 @@ func (a *AuthRequest) GetSubject() string {
return "" 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 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" { if id == "none" {
return nil, errors.New("not found") return nil, errors.New("not found")
} }
@ -97,19 +99,19 @@ func (s *Storage) GetClientByClientID(id string) (op.Client, error) {
} }
return &ConfClient{applicationType: appType}, nil 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 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 return &ConfClient{}, nil
} }
func (s *Storage) AuthorizeClientIDCodeVerifier(string, string) (op.Client, error) { func (s *OPStorage) AuthorizeClientIDCodeVerifier(string, string) (op.Client, error) {
return &ConfClient{}, nil return &ConfClient{}, nil
} }
func (s *Storage) DeleteAuthRequestAndCode(string, string) error { func (s *AuthStorage) DeleteAuthRequestAndCode(string, string) error {
return nil return nil
} }
func (s *Storage) AuthRequestByID(id string) (op.AuthRequest, error) { func (s *AuthStorage) AuthRequestByID(id string) (op.AuthRequest, error) {
if id == "none" { if id == "none" {
return nil, errors.New("not found") return nil, errors.New("not found")
} }
@ -127,10 +129,10 @@ func (s *Storage) AuthRequestByID(id string) (op.AuthRequest, error) {
}, nil }, 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 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() pubkey := s.key.Public()
return jose.JSONWebKeySet{ return jose.JSONWebKeySet{
Keys: []jose.JSONWebKey{ Keys: []jose.JSONWebKey{

View file

@ -15,8 +15,9 @@ func main() {
Port: "9998", Port: "9998",
} }
storage := mock.NewStorage() authStorage := mock.NewAuthStorage()
handler, err := op.NewDefaultOP(config, storage, op.WithCustomTokenEndpoint("test")) opStorage := &mock.OPStorage{}
handler, err := op.NewDefaultOP(config, authStorage, opStorage, op.WithCustomTokenEndpoint("test"))
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }

View file

@ -12,6 +12,11 @@ type Configuration interface {
TokenEndpoint() Endpoint TokenEndpoint() Endpoint
UserinfoEndpoint() Endpoint UserinfoEndpoint() Endpoint
KeysEndpoint() Endpoint KeysEndpoint() Endpoint
// SupportedScopes() []string
AuthMethodBasicSupported() bool
AuthMethodPostSupported() bool
Port() string Port() string
} }

View file

@ -15,6 +15,9 @@ const (
defaultIntrospectEndpoint = "introspect" defaultIntrospectEndpoint = "introspect"
defaultUserinfoEndpoint = "userinfo" defaultUserinfoEndpoint = "userinfo"
defaultKeysEndpoint = "keys" defaultKeysEndpoint = "keys"
authMethodBasic = "client_secret_basic"
authMethodPost = "client_secret_post"
) )
var ( 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) err := ValidateIssuer(config.Issuer)
if err != nil { if err != nil {
return nil, err return nil, err
} }
storage := struct {
AuthStorage
OPStorage
}{
AuthStorage: authStorage,
OPStorage: opStorage,
}
p := &DefaultOP{ p := &DefaultOP{
config: config, config: config,
storage: storage, storage: storage,
endpoints: DefaultEndpoints, endpoints: DefaultEndpoints,
} }
p.signer, err = NewDefaultSigner(storage) p.signer, err = NewDefaultSigner(authStorage)
if err != nil { if err != nil {
return nil, err 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) router := CreateRouter(p)
p.http = &http.Server{ p.http = &http.Server{
@ -152,6 +163,14 @@ func (p *DefaultOP) KeysEndpoint() Endpoint {
return Endpoint(p.endpoints.JwksURI) 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 { func (p *DefaultOP) Port() string {
return p.config.Port 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) { func (p *DefaultOP) handleTokenExchange(w http.ResponseWriter, r *http.Request) {
ExchangeRequestError(w, r, ErrServerError("not implemented"))
return
tokenRequest, err := ParseTokenExchangeRequest(w, r) tokenRequest, err := ParseTokenExchangeRequest(w, r)
if err != nil { if err != nil {
//TODO: return err //TODO: return err

View file

@ -11,7 +11,7 @@ func Discover(w http.ResponseWriter, config *oidc.DiscoveryConfiguration) {
utils.MarshalJSON(w, config) utils.MarshalJSON(w, config)
} }
func CreateDiscoveryConfig(c Configuration) *oidc.DiscoveryConfiguration { func CreateDiscoveryConfig(c Configuration, s Signer) *oidc.DiscoveryConfiguration {
return &oidc.DiscoveryConfiguration{ return &oidc.DiscoveryConfiguration{
Issuer: c.Issuer(), Issuer: c.Issuer(),
AuthorizationEndpoint: c.AuthorizationEndpoint().Absolute(c.Issuer()), AuthorizationEndpoint: c.AuthorizationEndpoint().Absolute(c.Issuer()),
@ -20,14 +20,61 @@ func CreateDiscoveryConfig(c Configuration) *oidc.DiscoveryConfiguration {
UserinfoEndpoint: c.UserinfoEndpoint().Absolute(c.Issuer()), UserinfoEndpoint: c.UserinfoEndpoint().Absolute(c.Issuer()),
// EndSessionEndpoint: c.TokenEndpoint().Absolute(c.Issuer())(c.EndSessionEndpoint), // EndSessionEndpoint: c.TokenEndpoint().Absolute(c.Issuer())(c.EndSessionEndpoint),
// CheckSessionIframe: c.TokenEndpoint().Absolute(c.Issuer())(c.CheckSessionIframe), // CheckSessionIframe: c.TokenEndpoint().Absolute(c.Issuer())(c.CheckSessionIframe),
JwksURI: c.KeysEndpoint().Absolute(c.Issuer()), JwksURI: c.KeysEndpoint().Absolute(c.Issuer()),
// ScopesSupported: oidc.SupportedScopes, ScopesSupported: scopes(c),
// ResponseTypesSupported: responseTypes, ResponseTypesSupported: responseTypes(c),
// GrantTypesSupported: oidc.SupportedGrantTypes, GrantTypesSupported: grantTypes(c),
// ClaimsSupported: oidc.SupportedClaims, // ClaimsSupported: oidc.SupportedClaims,
// IdTokenSigningAlgValuesSupported: []string{keys.SigningAlgorithm}, IDTokenSigningAlgValuesSupported: sigAlgorithms(s),
// SubjectTypesSupported: []string{"public"}, SubjectTypesSupported: subjectTypes(c),
// TokenEndpointAuthMethodsSupported: 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
}

View file

@ -42,6 +42,7 @@ func TestDiscover(t *testing.T) {
func TestCreateDiscoveryConfig(t *testing.T) { func TestCreateDiscoveryConfig(t *testing.T) {
type args struct { type args struct {
c op.Configuration c op.Configuration
s op.Signer
} }
tests := []struct { tests := []struct {
name string name string
@ -52,7 +53,7 @@ func TestCreateDiscoveryConfig(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { 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) t.Errorf("CreateDiscoveryConfig() = %v, want %v", got, tt.want)
} }
}) })

View file

@ -15,11 +15,11 @@ type Signer interface {
type idTokenSigner struct { type idTokenSigner struct {
signer jose.Signer signer jose.Signer
storage Storage storage AuthStorage
algorithm jose.SignatureAlgorithm algorithm jose.SignatureAlgorithm
} }
func NewDefaultSigner(storage Storage) (Signer, error) { func NewDefaultSigner(storage AuthStorage) (Signer, error) {
s := &idTokenSigner{ s := &idTokenSigner{
storage: storage, storage: storage,
} }

View file

@ -8,18 +8,27 @@ import (
"github.com/caos/oidc/pkg/oidc" "github.com/caos/oidc/pkg/oidc"
) )
type Storage interface { type AuthStorage interface {
CreateAuthRequest(*oidc.AuthRequest) (AuthRequest, error) CreateAuthRequest(*oidc.AuthRequest) (AuthRequest, error)
GetClientByClientID(string) (Client, error)
AuthRequestByID(string) (AuthRequest, error) AuthRequestByID(string) (AuthRequest, error)
AuthRequestByCode(Client, string, string) (AuthRequest, error) AuthRequestByCode(Client, string, string) (AuthRequest, error)
AuthorizeClientIDSecret(string, string) (Client, error)
AuthorizeClientIDCodeVerifier(string, string) (Client, error)
DeleteAuthRequestAndCode(string, string) error DeleteAuthRequestAndCode(string, string) error
GetSigningKey() (*jose.SigningKey, error) GetSigningKey() (*jose.SigningKey, error)
GetKeySet() (jose.JSONWebKeySet, 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 { type AuthRequest interface {
GetID() string GetID() string
GetACR() string GetACR() string

View file

@ -17,6 +17,8 @@ type Exchanger interface {
Storage() Storage Storage() Storage
Decoder() *schema.Decoder Decoder() *schema.Decoder
Signer() Signer Signer() Signer
AuthMethodBasicSupported() bool
AuthMethodPostSupported() bool
} }
func CodeExchange(w http.ResponseWriter, r *http.Request, exchanger Exchanger) { 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 return
} }
client, err := AuthorizeClient(r, tokenReq, exchanger.Storage()) client, err := AuthorizeClient(r, tokenReq, exchanger)
if err != nil { if err != nil {
ExchangeRequestError(w, r, err) ExchangeRequestError(w, r, err)
return return
@ -99,19 +101,25 @@ func CreateIDToken(issuer string, authReq AuthRequest, validity time.Duration, a
return signer.SignIDToken(claims) 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 tokenReq.ClientID == "" {
if !exchanger.AuthMethodBasicSupported() {
return nil, errors.New("basic not supported")
}
clientID, clientSecret, ok := r.BasicAuth() clientID, clientSecret, ok := r.BasicAuth()
if ok { if ok {
return storage.AuthorizeClientIDSecret(clientID, clientSecret) return exchanger.Storage().AuthorizeClientIDSecret(clientID, clientSecret)
} }
} }
if tokenReq.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 != "" { 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 return nil, errors.New("Unimplemented") //TODO: impl
} }