This commit is contained in:
Livio Amstutz 2019-11-28 12:14:14 +01:00
parent 10d671956a
commit 80eeee2de2
19 changed files with 422 additions and 157 deletions

View file

@ -6,6 +6,13 @@ import (
"github.com/caos/oidc/pkg/oidc" "github.com/caos/oidc/pkg/oidc"
) )
type Signer struct {
}
func (s *Signer) Sign(*oidc.IDTokenClaims) (string, error) {
return "sdsa", nil
}
type Storage struct { type Storage struct {
} }
@ -64,5 +71,5 @@ func (c *ConfClient) LoginURL(id string) string {
} }
func (c *ConfClient) ApplicationType() oidc.ApplicationType { func (c *ConfClient) ApplicationType() oidc.ApplicationType {
return oidc.ApplicationTypeWeb return oidc.ApplicationTypeNative
} }

View file

@ -16,7 +16,8 @@ func main() {
Port: "9998", Port: "9998",
} }
storage := &mock.Storage{} storage := &mock.Storage{}
handler, err := server.NewDefaultOP(config, storage, server.WithCustomTokenEndpoint("test")) signer := &mock.Signer{}
handler, err := server.NewDefaultOP(config, storage, signer, server.WithCustomTokenEndpoint("test"))
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }

View file

@ -10,25 +10,35 @@ import (
"github.com/gorilla/schema" "github.com/gorilla/schema"
"github.com/caos/oidc/pkg/oidc" "github.com/caos/oidc/pkg/oidc"
"github.com/caos/oidc/pkg/op/u"
str_utils "github.com/caos/utils/strings" str_utils "github.com/caos/utils/strings"
) )
type Authorizer interface { type Authorizer interface {
Storage() Storage Storage() u.Storage
Decoder() *schema.Decoder Decoder() *schema.Decoder
Encoder() *schema.Encoder Encoder() *schema.Encoder
Signer() Signer Signe() u.Signer
ErrorHandler() func(w http.ResponseWriter, r *http.Request, authReq *oidc.AuthRequest, err error)
} }
// type Signer interface {
// Sign(claims *oidc.IDTokenClaims) (string, error)
// }
type ValidationAuthorizer interface { type ValidationAuthorizer interface {
Authorizer Authorizer
ValidateAuthRequest(*oidc.AuthRequest, Storage) error ValidateAuthRequest(*oidc.AuthRequest, u.Storage) error
} }
// type errorHandler func(w http.ResponseWriter, r *http.Request, authReq *oidc.AuthRequest, err error)
type callbackHandler func(authReq *oidc.AuthRequest, client oidc.Client, w http.ResponseWriter, r *http.Request)
func Authorize(w http.ResponseWriter, r *http.Request, authorizer Authorizer) { func Authorize(w http.ResponseWriter, r *http.Request, authorizer Authorizer) {
err := r.ParseForm() err := r.ParseForm()
if err != nil { if err != nil {
AuthRequestError(w, r, nil, ErrInvalidRequest("cannot parse form: %v", err)) AuthRequestError(w, r, nil, ErrInvalidRequest("cannot parse form"))
// AuthRequestError(w, r, nil, )
return return
} }
authReq := new(oidc.AuthRequest) authReq := new(oidc.AuthRequest)
@ -62,7 +72,7 @@ func Authorize(w http.ResponseWriter, r *http.Request, authorizer Authorizer) {
RedirectToLogin(authReq, client, w, r) RedirectToLogin(authReq, client, w, r)
} }
func ValidateAuthRequest(authReq *oidc.AuthRequest, storage Storage) error { func ValidateAuthRequest(authReq *oidc.AuthRequest, storage u.Storage) error {
if err := ValidateAuthReqScopes(authReq.Scopes); err != nil { if err := ValidateAuthReqScopes(authReq.Scopes); err != nil {
return err return err
} }
@ -90,7 +100,7 @@ func ValidateAuthReqScopes(scopes []string) error {
return nil return nil
} }
func ValidateAuthReqRedirectURI(uri, client_id string, responseType oidc.ResponseType, storage Storage) error { func ValidateAuthReqRedirectURI(uri, client_id string, responseType oidc.ResponseType, storage u.Storage) error {
if uri == "" { if uri == "" {
return ErrInvalidRequest("redirect_uri must not be empty") return ErrInvalidRequest("redirect_uri must not be empty")
} }
@ -153,7 +163,7 @@ func AuthResponse(authReq *oidc.AuthRequest, authorizer Authorizer, w http.Respo
} }
} }
idToken, err := CreateIDToken(authReq, accessToken, authorizer.Signer()) idToken, err := CreateIDToken(authReq, accessToken, authorizer.Signe())
if err != nil { if err != nil {
} }

View file

@ -3,19 +3,18 @@ package op
import ( import (
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"strings"
"testing" "testing"
"github.com/gorilla/schema"
"github.com/caos/oidc/pkg/oidc" "github.com/caos/oidc/pkg/oidc"
"github.com/caos/oidc/pkg/op"
"github.com/caos/oidc/pkg/op/mock" "github.com/caos/oidc/pkg/op/mock"
"github.com/caos/oidc/pkg/op/u"
) )
func TestValidateAuthRequest(t *testing.T) { func TestValidateAuthRequest(t *testing.T) {
type args struct { type args struct {
authRequest *oidc.AuthRequest authRequest *oidc.AuthRequest
storage op.Storage storage u.Storage
} }
tests := []struct { tests := []struct {
name string name string
@ -66,7 +65,7 @@ func TestValidateAuthReqRedirectURI(t *testing.T) {
uri string uri string
clientID string clientID string
responseType oidc.ResponseType responseType oidc.ResponseType
storage op.Storage storage u.Storage
} }
tests := []struct { tests := []struct {
name string name string
@ -172,23 +171,54 @@ func TestValidateAuthReqScopes(t *testing.T) {
} }
func TestAuthorize(t *testing.T) { func TestAuthorize(t *testing.T) {
// testCallback := func(t *testing.T, clienID string) callbackHandler {
// return func(authReq *oidc.AuthRequest, client oidc.Client, w http.ResponseWriter, r *http.Request) {
// // require.Equal(t, clientID, client.)
// }
// }
// testErr := func(t *testing.T, expected error) errorHandler {
// return func(w http.ResponseWriter, r *http.Request, authReq *oidc.AuthRequest, err error) {
// require.Equal(t, expected, err)
// }
// }
type args struct { type args struct {
w http.ResponseWriter w http.ResponseWriter
r *http.Request r *http.Request
storage Storage authorizer Authorizer
decoder *schema.Decoder
} }
tests := []struct { tests := []struct {
name string name string
args args args args
}{ }{
{"parsing fails", args{httptest.NewRecorder(), &http.Request{Method: "POST", Body: nil}, nil, nil}}, {
{"decoding fails", args{httptest.NewRecorder(), &http.Request{}, nil, schema.NewDecoder()}}, "parsing fails",
{"decoding fails", args{httptest.NewRecorder(), &http.Request{}, nil, schema.NewDecoder()}}, args{
httptest.NewRecorder(),
&http.Request{Method: "POST", Body: nil},
mock.NewAuthorizerExpectValid(t, true),
// testCallback(t, ""),
// testErr(t, ErrInvalidRequest("cannot parse form")),
},
},
{
"decoding fails",
args{
httptest.NewRecorder(),
func() *http.Request {
r := httptest.NewRequest("POST", "/authorize", strings.NewReader("client_id=foo"))
r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
return r
}(),
mock.NewAuthorizerExpectValid(t, true),
// testCallback(t, ""),
// testErr(t, ErrInvalidRequest("cannot parse auth request")),
},
},
// {"decoding fails", args{httptest.NewRecorder(), &http.Request{}, mock.NewAuthorizerExpectValid(t), nil, testErr(t, nil)}},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
Authorize(tt.args.w, tt.args.r, tt.args.storage, tt.args.decoder) Authorize(tt.args.w, tt.args.r, tt.args.authorizer)
}) })
} }
} }

View file

@ -1,23 +1,36 @@
package op package op
import ( import (
"errors"
"net/http" "net/http"
"net/url"
"strings"
"github.com/gorilla/schema" "github.com/gorilla/schema"
"github.com/caos/oidc/pkg/utils"
"github.com/caos/oidc/pkg/oidc" "github.com/caos/oidc/pkg/oidc"
"github.com/caos/oidc/pkg/op/u"
)
const (
defaultAuthorizationEndpoint = "authorize"
defaulTokenEndpoint = "oauth/token"
defaultIntrospectEndpoint = "introspect"
defaultUserinfoEndpoint = "userinfo"
)
var (
DefaultEndpoints = &endpoints{
Authorization: defaultAuthorizationEndpoint,
Token: defaulTokenEndpoint,
IntrospectionEndpoint: defaultIntrospectEndpoint,
Userinfo: defaultUserinfoEndpoint,
}
) )
type DefaultOP struct { type DefaultOP struct {
config *Config config *Config
endpoints *endpoints endpoints *endpoints
discoveryConfig *oidc.DiscoveryConfiguration discoveryConfig *oidc.DiscoveryConfiguration
storage Storage storage u.Storage
signer u.Signer
http *http.Server http *http.Server
decoder *schema.Decoder decoder *schema.Decoder
encoder *schema.Encoder encoder *schema.Encoder
@ -77,42 +90,7 @@ func WithCustomUserinfoEndpoint(endpoint Endpoint) DefaultOPOpts {
} }
} }
const ( func NewDefaultOP(config *Config, storage u.Storage, signer u.Signer, opOpts ...DefaultOPOpts) (OpenIDProvider, error) {
defaultAuthorizationEndpoint = "authorize"
defaulTokenEndpoint = "oauth/token"
defaultIntrospectEndpoint = "introspect"
defaultUserinfoEndpoint = "userinfo"
)
func CreateDiscoveryConfig(c Configuration) *oidc.DiscoveryConfiguration {
return &oidc.DiscoveryConfiguration{
Issuer: c.Issuer(),
AuthorizationEndpoint: c.AuthorizationEndpoint().Absolute(c.Issuer()),
TokenEndpoint: c.TokenEndpoint().Absolute(c.Issuer()),
// IntrospectionEndpoint: c.Intro().Absolute(c.Issuer()),
UserinfoEndpoint: c.UserinfoEndpoint().Absolute(c.Issuer()),
// EndSessionEndpoint: c.TokenEndpoint().Absolute(c.Issuer())(c.EndSessionEndpoint),
// CheckSessionIframe: c.TokenEndpoint().Absolute(c.Issuer())(c.CheckSessionIframe),
// JwksURI: c.TokenEndpoint().Absolute(c.Issuer())(c.JwksURI),
// ScopesSupported: oidc.SupportedScopes,
// ResponseTypesSupported: responseTypes,
// GrantTypesSupported: oidc.SupportedGrantTypes,
// ClaimsSupported: oidc.SupportedClaims,
// IdTokenSigningAlgValuesSupported: []string{keys.SigningAlgorithm},
// SubjectTypesSupported: []string{"public"},
// TokenEndpointAuthMethodsSupported:
}
}
var DefaultEndpoints = &endpoints{
Authorization: defaultAuthorizationEndpoint,
Token: defaulTokenEndpoint,
IntrospectionEndpoint: defaultIntrospectEndpoint,
Userinfo: defaultUserinfoEndpoint,
}
func NewDefaultOP(config *Config, storage Storage, opOpts ...DefaultOPOpts) (OpenIDProvider, error) {
if err := ValidateIssuer(config.Issuer); err != nil { if err := ValidateIssuer(config.Issuer); err != nil {
return nil, err return nil, err
} }
@ -120,6 +98,7 @@ func NewDefaultOP(config *Config, storage Storage, opOpts ...DefaultOPOpts) (Ope
p := &DefaultOP{ p := &DefaultOP{
config: config, config: config,
storage: storage, storage: storage,
signer: signer,
endpoints: DefaultEndpoints, endpoints: DefaultEndpoints,
} }
@ -148,20 +127,6 @@ func (p *DefaultOP) Issuer() string {
return p.config.Issuer return p.config.Issuer
} }
type Endpoint string
func (e Endpoint) Relative() string {
return relativeEndpoint(string(e))
}
func (e Endpoint) Absolute(host string) string {
return absoluteEndpoint(host, string(e))
}
func (e Endpoint) Validate() error {
return nil //TODO:
}
func (p *DefaultOP) AuthorizationEndpoint() Endpoint { func (p *DefaultOP) AuthorizationEndpoint() Endpoint {
return p.endpoints.Authorization return p.endpoints.Authorization
} }
@ -183,7 +148,7 @@ func (p *DefaultOP) HttpHandler() *http.Server {
} }
func (p *DefaultOP) HandleDiscovery(w http.ResponseWriter, r *http.Request) { func (p *DefaultOP) HandleDiscovery(w http.ResponseWriter, r *http.Request) {
utils.MarshalJSON(w, p.discoveryConfig) Discover(w, p.discoveryConfig)
} }
func (p *DefaultOP) Decoder() *schema.Decoder { func (p *DefaultOP) Decoder() *schema.Decoder {
@ -194,13 +159,17 @@ func (p *DefaultOP) Encoder() *schema.Encoder {
return p.encoder return p.encoder
} }
func (p *DefaultOP) Storage() Storage { func (p *DefaultOP) Storage() u.Storage {
return p.storage return p.storage
} }
func (p *DefaultOP) Signer() Signer { func (p *DefaultOP) Signe() u.Signer {
// return p.signer return p.signer
return nil // return
}
func (p *DefaultOP) ErrorHandler() func(w http.ResponseWriter, r *http.Request, authReq *oidc.AuthRequest, err error) {
return AuthRequestError
} }
func (p *DefaultOP) HandleAuthorize(w http.ResponseWriter, r *http.Request) { func (p *DefaultOP) HandleAuthorize(w http.ResponseWriter, r *http.Request) {
@ -270,56 +239,3 @@ func (p *DefaultOP) handleTokenExchange(w http.ResponseWriter, r *http.Request)
func (p *DefaultOP) HandleUserinfo(w http.ResponseWriter, r *http.Request) { func (p *DefaultOP) HandleUserinfo(w http.ResponseWriter, r *http.Request) {
} }
// func (c *Config) DefaultAndValidate() error {
// if err := ValidateIssuer(c.Issuer); err != nil {
// return err
// }
// if c.AuthorizationEndpoint == "" {
// c.AuthorizationEndpoint = defaultAuthorizationEndpoint
// }
// if c.TokenEndpoint == "" {
// c.TokenEndpoint = defaulTokenEndpoint
// }
// if c.IntrospectionEndpoint == "" {
// c.IntrospectionEndpoint = defaultIntrospectEndpoint
// }
// if c.UserinfoEndpoint == "" {
// c.UserinfoEndpoint = defaultUserinfoEndpoint
// }
// return nil
// }
func ValidateIssuer(issuer string) error {
if issuer == "" {
return errors.New("missing issuer")
}
u, err := url.Parse(issuer)
if err != nil {
return errors.New("invalid url for issuer")
}
if u.Host == "" {
return errors.New("host for issuer missing")
}
if u.Scheme != "https" {
if !(u.Scheme == "http" && (u.Host == "localhost" || u.Host == "127.0.0.1" || u.Host == "::1" || strings.HasPrefix(u.Host, "localhost:"))) { //TODO: ?
return errors.New("scheme for issuer must be `https`")
}
}
if u.Fragment != "" || len(u.Query()) > 0 {
return errors.New("no fragments or query allowed for issuer")
}
return nil
}
func (c *Config) absoluteEndpoint(endpoint string) string {
return strings.TrimSuffix(c.Issuer, "/") + relativeEndpoint(endpoint)
}
func absoluteEndpoint(host, endpoint string) string {
return strings.TrimSuffix(host, "/") + relativeEndpoint(endpoint)
}
func relativeEndpoint(endpoint string) string {
return "/" + strings.TrimPrefix(endpoint, "/")
}

View file

@ -8,6 +8,7 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/caos/oidc/pkg/oidc" "github.com/caos/oidc/pkg/oidc"
"github.com/caos/oidc/pkg/op/u"
) )
func TestDefaultOP_HandleDiscovery(t *testing.T) { func TestDefaultOP_HandleDiscovery(t *testing.T) {
@ -15,7 +16,7 @@ func TestDefaultOP_HandleDiscovery(t *testing.T) {
config *Config config *Config
endpoints *endpoints endpoints *endpoints
discoveryConfig *oidc.DiscoveryConfiguration discoveryConfig *oidc.DiscoveryConfiguration
storage Storage storage u.Storage
http *http.Server http *http.Server
} }
type args struct { type args struct {

33
pkg/op/discovery.go Normal file
View file

@ -0,0 +1,33 @@
package op
import (
"net/http"
"github.com/caos/oidc/pkg/oidc"
"github.com/caos/oidc/pkg/utils"
)
func Discover(w http.ResponseWriter, config *oidc.DiscoveryConfiguration) {
utils.MarshalJSON(w, config)
}
func CreateDiscoveryConfig(c Configuration) *oidc.DiscoveryConfiguration {
return &oidc.DiscoveryConfiguration{
Issuer: c.Issuer(),
AuthorizationEndpoint: c.AuthorizationEndpoint().Absolute(c.Issuer()),
TokenEndpoint: c.TokenEndpoint().Absolute(c.Issuer()),
// IntrospectionEndpoint: c.Intro().Absolute(c.Issuer()),
UserinfoEndpoint: c.UserinfoEndpoint().Absolute(c.Issuer()),
// EndSessionEndpoint: c.TokenEndpoint().Absolute(c.Issuer())(c.EndSessionEndpoint),
// CheckSessionIframe: c.TokenEndpoint().Absolute(c.Issuer())(c.CheckSessionIframe),
// JwksURI: c.TokenEndpoint().Absolute(c.Issuer())(c.JwksURI),
// ScopesSupported: oidc.SupportedScopes,
// ResponseTypesSupported: responseTypes,
// GrantTypesSupported: oidc.SupportedGrantTypes,
// ClaimsSupported: oidc.SupportedClaims,
// IdTokenSigningAlgValuesSupported: []string{keys.SigningAlgorithm},
// SubjectTypesSupported: []string{"public"},
// TokenEndpointAuthMethodsSupported:
}
}

25
pkg/op/endpoint.go Normal file
View file

@ -0,0 +1,25 @@
package op
import "strings"
type Endpoint string
func (e Endpoint) Relative() string {
return relativeEndpoint(string(e))
}
func (e Endpoint) Absolute(host string) string {
return absoluteEndpoint(host, string(e))
}
func (e Endpoint) Validate() error {
return nil //TODO:
}
func absoluteEndpoint(host, endpoint string) string {
return strings.TrimSuffix(host, "/") + relativeEndpoint(endpoint)
}
func relativeEndpoint(endpoint string) string {
return "/" + strings.TrimPrefix(endpoint, "/")
}

View file

@ -10,6 +10,8 @@ replace github.com/caos/oidc/pkg/utils => /Users/livio/workspaces/go/src/github.
replace github.com/caos/oidc/pkg/op => /Users/livio/workspaces/go/src/github.com/caos/oidc/pkg/op replace github.com/caos/oidc/pkg/op => /Users/livio/workspaces/go/src/github.com/caos/oidc/pkg/op
replace github.com/caos/oidc/pkg/op/u => /Users/livio/workspaces/go/src/github.com/caos/oidc/pkg/op/u
require ( require (
github.com/caos/oidc v0.0.0-20191119072320-6412f213450c github.com/caos/oidc v0.0.0-20191119072320-6412f213450c
github.com/caos/oidc/pkg/oidc v0.0.0-00010101000000-000000000000 github.com/caos/oidc/pkg/oidc v0.0.0-00010101000000-000000000000

View file

@ -55,6 +55,7 @@ github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6L
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=

View file

@ -0,0 +1,107 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/caos/oidc/pkg/op (interfaces: Authorizer)
// Package mock is a generated GoMock package.
package mock
import (
oidc "github.com/caos/oidc/pkg/oidc"
u "github.com/caos/oidc/pkg/op/u"
gomock "github.com/golang/mock/gomock"
schema "github.com/gorilla/schema"
http "net/http"
reflect "reflect"
)
// MockAuthorizer is a mock of Authorizer interface
type MockAuthorizer struct {
ctrl *gomock.Controller
recorder *MockAuthorizerMockRecorder
}
// MockAuthorizerMockRecorder is the mock recorder for MockAuthorizer
type MockAuthorizerMockRecorder struct {
mock *MockAuthorizer
}
// NewMockAuthorizer creates a new mock instance
func NewMockAuthorizer(ctrl *gomock.Controller) *MockAuthorizer {
mock := &MockAuthorizer{ctrl: ctrl}
mock.recorder = &MockAuthorizerMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockAuthorizer) EXPECT() *MockAuthorizerMockRecorder {
return m.recorder
}
// Decoder mocks base method
func (m *MockAuthorizer) Decoder() *schema.Decoder {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Decoder")
ret0, _ := ret[0].(*schema.Decoder)
return ret0
}
// Decoder indicates an expected call of Decoder
func (mr *MockAuthorizerMockRecorder) Decoder() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Decoder", reflect.TypeOf((*MockAuthorizer)(nil).Decoder))
}
// Encoder mocks base method
func (m *MockAuthorizer) Encoder() *schema.Encoder {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Encoder")
ret0, _ := ret[0].(*schema.Encoder)
return ret0
}
// Encoder indicates an expected call of Encoder
func (mr *MockAuthorizerMockRecorder) Encoder() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Encoder", reflect.TypeOf((*MockAuthorizer)(nil).Encoder))
}
// ErrorHandler mocks base method
func (m *MockAuthorizer) ErrorHandler() func(http.ResponseWriter, *http.Request, *oidc.AuthRequest, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ErrorHandler")
ret0, _ := ret[0].(func(http.ResponseWriter, *http.Request, *oidc.AuthRequest, error))
return ret0
}
// ErrorHandler indicates an expected call of ErrorHandler
func (mr *MockAuthorizerMockRecorder) ErrorHandler() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ErrorHandler", reflect.TypeOf((*MockAuthorizer)(nil).ErrorHandler))
}
// Signe mocks base method
func (m *MockAuthorizer) Signe() u.Signer {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Signe")
ret0, _ := ret[0].(u.Signer)
return ret0
}
// Signe indicates an expected call of Signe
func (mr *MockAuthorizerMockRecorder) Signe() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Signe", reflect.TypeOf((*MockAuthorizer)(nil).Signe))
}
// Storage mocks base method
func (m *MockAuthorizer) Storage() u.Storage {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Storage")
ret0, _ := ret[0].(u.Storage)
return ret0
}
// Storage indicates an expected call of Storage
func (mr *MockAuthorizerMockRecorder) Storage() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Storage", reflect.TypeOf((*MockAuthorizer)(nil).Storage))
}

View file

@ -0,0 +1,86 @@
package mock
import (
http "net/http"
"testing"
"github.com/stretchr/testify/require"
"github.com/golang/mock/gomock"
"github.com/gorilla/schema"
oidc "github.com/caos/oidc/pkg/oidc"
"github.com/caos/oidc/pkg/op"
u "github.com/caos/oidc/pkg/op/u"
)
func NewAuthorizer(t *testing.T) op.Authorizer {
return NewMockAuthorizer(gomock.NewController(t))
}
func NewAuthorizerExpectValid(t *testing.T, wantErr bool) op.Authorizer {
m := NewAuthorizer(t)
ExpectDecoder(m)
ExpectEncoder(m)
ExpectSigner(m, t)
ExpectStorage(m, t)
ExpectErrorHandler(m, t, wantErr)
return m
}
// func NewAuthorizerExpectDecoderFails(t *testing.T) op.Authorizer {
// m := NewAuthorizer(t)
// ExpectDecoderFails(m)
// ExpectEncoder(m)
// ExpectSigner(m, t)
// ExpectStorage(m, t)
// ExpectErrorHandler(m, t)
// return m
// }
func ExpectDecoder(a op.Authorizer) {
mockA := a.(*MockAuthorizer)
mockA.EXPECT().Decoder().AnyTimes().Return(schema.NewDecoder())
}
func ExpectEncoder(a op.Authorizer) {
mockA := a.(*MockAuthorizer)
mockA.EXPECT().Encoder().AnyTimes().Return(schema.NewEncoder())
}
func ExpectSigner(a op.Authorizer, t *testing.T) {
mockA := a.(*MockAuthorizer)
mockA.EXPECT().Signe().DoAndReturn(
func() u.Signer {
return &Sig{}
})
}
func ExpectErrorHandler(a op.Authorizer, t *testing.T, wantErr bool) {
mockA := a.(*MockAuthorizer)
mockA.EXPECT().ErrorHandler().AnyTimes().
Return(func(w http.ResponseWriter, r *http.Request, authReq *oidc.AuthRequest, err error) {
if wantErr {
require.Error(t, err)
return
}
require.NoError(t, err)
})
}
type Sig struct{}
func (s *Sig) Sign(*oidc.IDTokenClaims) (string, error) {
return "", nil
}
func ExpectStorage(a op.Authorizer, t *testing.T) {
mockA := a.(*MockAuthorizer)
mockA.EXPECT().Storage().AnyTimes().Return(NewMockStorageAny(t))
}
// func NewMockSignerAny(t *testing.T) op.Signer {
// m := NewMockSigner(gomock.NewController(t))
// m.EXPECT().Sign(gomock.Any()).AnyTimes().Return("", nil)
// return m
// }

View file

@ -1,3 +1,4 @@
package mock package mock
//go:generate mockgen -package mock -destination ./storage.mock.go github.com/caos/oidc/pkg/op Storage //go:generate mockgen -package mock -destination ./storage.mock.go github.com/caos/oidc/pkg/op/u Storage
//go:generate mockgen -package mock -destination ./authorizer.mock.go github.com/caos/oidc/pkg/op Authorizer

View file

@ -1,5 +1,5 @@
// Code generated by MockGen. DO NOT EDIT. // Code generated by MockGen. DO NOT EDIT.
// Source: github.com/caos/oidc/pkg/op (interfaces: Storage) // Source: github.com/caos/oidc/pkg/op/u (interfaces: Storage)
// Package mock is a generated GoMock package. // Package mock is a generated GoMock package.
package mock package mock
@ -48,6 +48,21 @@ func (mr *MockStorageMockRecorder) AuthRequestByCode(arg0, arg1, arg2 interface{
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AuthRequestByCode", reflect.TypeOf((*MockStorage)(nil).AuthRequestByCode), arg0, arg1, arg2) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AuthRequestByCode", reflect.TypeOf((*MockStorage)(nil).AuthRequestByCode), arg0, arg1, arg2)
} }
// AuthRequestByID mocks base method
func (m *MockStorage) AuthRequestByID(arg0 string) (*oidc.AuthRequest, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "AuthRequestByID", arg0)
ret0, _ := ret[0].(*oidc.AuthRequest)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// AuthRequestByID indicates an expected call of AuthRequestByID
func (mr *MockStorageMockRecorder) AuthRequestByID(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AuthRequestByID", reflect.TypeOf((*MockStorage)(nil).AuthRequestByID), arg0)
}
// AuthorizeClientIDCodeVerifier mocks base method // AuthorizeClientIDCodeVerifier mocks base method
func (m *MockStorage) AuthorizeClientIDCodeVerifier(arg0, arg1 string) (oidc.Client, error) { func (m *MockStorage) AuthorizeClientIDCodeVerifier(arg0, arg1 string) (oidc.Client, error) {
m.ctrl.T.Helper() m.ctrl.T.Helper()

View file

@ -4,30 +4,29 @@ import (
"errors" "errors"
"testing" "testing"
"github.com/caos/oidc/pkg/oidc"
"github.com/golang/mock/gomock" "github.com/golang/mock/gomock"
"github.com/caos/oidc/pkg/op" "github.com/caos/oidc/pkg/oidc"
u "github.com/caos/oidc/pkg/op/u"
) )
func NewStorage(t *testing.T) op.Storage { func NewStorage(t *testing.T) u.Storage {
return NewMockStorage(gomock.NewController(t)) return NewMockStorage(gomock.NewController(t))
} }
func NewMockStorageExpectValidClientID(t *testing.T) op.Storage { func NewMockStorageExpectValidClientID(t *testing.T) u.Storage {
m := NewStorage(t) m := NewStorage(t)
ExpectValidClientID(m) ExpectValidClientID(m)
return m return m
} }
func NewMockStorageExpectInvalidClientID(t *testing.T) op.Storage { func NewMockStorageExpectInvalidClientID(t *testing.T) u.Storage {
m := NewStorage(t) m := NewStorage(t)
ExpectInvalidClientID(m) ExpectInvalidClientID(m)
return m return m
} }
func NewMockStorageAny(t *testing.T) op.Storage { func NewMockStorageAny(t *testing.T) u.Storage {
m := NewStorage(t) m := NewStorage(t)
mockS := m.(*MockStorage) mockS := m.(*MockStorage)
mockS.EXPECT().GetClientByClientID(gomock.Any()).AnyTimes().Return(&ConfClient{}, nil) mockS.EXPECT().GetClientByClientID(gomock.Any()).AnyTimes().Return(&ConfClient{}, nil)
@ -35,12 +34,12 @@ func NewMockStorageAny(t *testing.T) op.Storage {
return m return m
} }
func ExpectInvalidClientID(s op.Storage) { func ExpectInvalidClientID(s u.Storage) {
mockS := s.(*MockStorage) mockS := s.(*MockStorage)
mockS.EXPECT().GetClientByClientID(gomock.Any()).Return(nil, errors.New("client not found")) mockS.EXPECT().GetClientByClientID(gomock.Any()).Return(nil, errors.New("client not found"))
} }
func ExpectValidClientID(s op.Storage) { func ExpectValidClientID(s u.Storage) {
mockS := s.(*MockStorage) mockS := s.(*MockStorage)
mockS.EXPECT().GetClientByClientID(gomock.Any()).DoAndReturn( mockS.EXPECT().GetClientByClientID(gomock.Any()).DoAndReturn(
func(id string) (oidc.Client, error) { func(id string) (oidc.Client, error) {

View file

@ -2,7 +2,10 @@ package op
import ( import (
"context" "context"
"errors"
"net/http" "net/http"
"net/url"
"strings"
"github.com/gorilla/mux" "github.com/gorilla/mux"
@ -22,6 +25,28 @@ type OpenIDProvider interface {
HttpHandler() *http.Server HttpHandler() *http.Server
} }
func ValidateIssuer(issuer string) error {
if issuer == "" {
return errors.New("missing issuer")
}
u, err := url.Parse(issuer)
if err != nil {
return errors.New("invalid url for issuer")
}
if u.Host == "" {
return errors.New("host for issuer missing")
}
if u.Scheme != "https" {
if !(u.Scheme == "http" && (u.Host == "localhost" || u.Host == "127.0.0.1" || u.Host == "::1" || strings.HasPrefix(u.Host, "localhost:"))) { //TODO: ?
return errors.New("scheme for issuer must be `https`")
}
}
if u.Fragment != "" || len(u.Query()) > 0 {
return errors.New("no fragments or query allowed for issuer")
}
return nil
}
func CreateRouter(o OpenIDProvider) *mux.Router { func CreateRouter(o OpenIDProvider) *mux.Router {
router := mux.NewRouter() router := mux.NewRouter()
router.HandleFunc(oidc.DiscoveryEndpoint, o.HandleDiscovery) router.HandleFunc(oidc.DiscoveryEndpoint, o.HandleDiscovery)

View file

@ -5,6 +5,7 @@ import (
"net/http" "net/http"
"time" "time"
"github.com/caos/oidc/pkg/op/u"
"github.com/caos/oidc/pkg/utils" "github.com/caos/oidc/pkg/utils"
"github.com/gorilla/schema" "github.com/gorilla/schema"
@ -23,7 +24,7 @@ import (
// return ParseTokenExchangeRequest(w, r) // return ParseTokenExchangeRequest(w, r)
// } // }
func CodeExchange(w http.ResponseWriter, r *http.Request, storage Storage, decoder *schema.Decoder) { func CodeExchange(w http.ResponseWriter, r *http.Request, storage u.Storage, decoder *schema.Decoder) {
err := r.ParseForm() err := r.ParseForm()
if err != nil { if err != nil {
ExchangeRequestError(w, r, ErrInvalidRequest("error parsing form")) ExchangeRequestError(w, r, ErrInvalidRequest("error parsing form"))
@ -78,11 +79,7 @@ func CreateAccessToken() (string, error) {
return "accessToken", nil return "accessToken", nil
} }
type Signer interface { func CreateIDToken(authReq *oidc.AuthRequest, atHash string, signer u.Signer) (string, error) {
Sign(claims *oidc.IDTokenClaims) (string, error)
}
func CreateIDToken(authReq *oidc.AuthRequest, atHash string, signer Signer) (string, error) {
var issuer, sub, acr string var issuer, sub, acr string
var aud, amr []string var aud, amr []string
var exp, iat, authTime time.Time var exp, iat, authTime time.Time
@ -103,7 +100,7 @@ func CreateIDToken(authReq *oidc.AuthRequest, atHash string, signer Signer) (str
return signer.Sign(claims) return signer.Sign(claims)
} }
func AuthorizeClient(r *http.Request, tokenReq *oidc.AccessTokenRequest, storage Storage) (oidc.Client, error) { func AuthorizeClient(r *http.Request, tokenReq *oidc.AccessTokenRequest, storage u.Storage) (oidc.Client, error) {
if tokenReq.ClientID == "" { if tokenReq.ClientID == "" {
clientID, clientSecret, ok := r.BasicAuth() clientID, clientSecret, ok := r.BasicAuth()
if ok { if ok {
@ -124,7 +121,7 @@ func ParseTokenExchangeRequest(w http.ResponseWriter, r *http.Request) (oidc.Tok
return nil, errors.New("Unimplemented") //TODO: impl return nil, errors.New("Unimplemented") //TODO: impl
} }
func ValidateTokenExchangeRequest(tokenReq oidc.TokenRequest, storage Storage) error { func ValidateTokenExchangeRequest(tokenReq oidc.TokenRequest, storage u.Storage) error {
return errors.New("Unimplemented") //TODO: impl return errors.New("Unimplemented") //TODO: impl
} }

9
pkg/op/u/signer.go Normal file
View file

@ -0,0 +1,9 @@
package u
import (
"github.com/caos/oidc/pkg/oidc"
)
type Signer interface {
Sign(claims *oidc.IDTokenClaims) (string, error)
}

View file

@ -1,4 +1,4 @@
package op package u
import "github.com/caos/oidc/pkg/oidc" import "github.com/caos/oidc/pkg/oidc"