diff --git a/pkg/oidc/authorization.go b/pkg/oidc/authorization.go index ed75f58..2da734f 100644 --- a/pkg/oidc/authorization.go +++ b/pkg/oidc/authorization.go @@ -8,9 +8,9 @@ import ( ) const ( - ResponseTypeCode = "code" - ResponseTypeIDToken = "id_token token" - ResponseTypeIDTokenOnly = "id_token" + ResponseTypeCode ResponseType = "code" + ResponseTypeIDToken ResponseType = "id_token token" + ResponseTypeIDTokenOnly ResponseType = "id_token" DisplayPage Display = "page" DisplayPopup Display = "popup" @@ -53,6 +53,32 @@ type AuthRequest struct { ACRValues []string `schema:"acr_values"` } +// func (a *AuthRequest) UnmarshalText(text []byte) error { +// // var f formAuthRequest +// log.Println(string(text)) +// return nil +// } + +// type formAuthRequest struct { +// Scopes string `schema:"scope"` +// ResponseType string `schema:"response_type"` +// ClientID string `schema:"client_id"` +// RedirectURI string `schema:"redirect_uri"` //TODO: type + +// State string `schema:"state"` + +// // ResponseMode TODO: ? + +// Nonce string `schema:"nonce"` +// Display string `schema:"display"` +// Prompt string `schema:"prompt"` +// MaxAge uint32 `schema:"max_age"` +// UILocales string `schema:"ui_locales"` +// IDTokenHint string `schema:"id_token_hint"` +// LoginHint string `schema:"login_hint"` +// ACRValues []string `schema:"acr_values"` +// } + type Scopes []string func (s *Scopes) UnmarshalText(text []byte) error { diff --git a/pkg/op/authrequest.go b/pkg/op/authrequest.go index 9a53238..6c4ba51 100644 --- a/pkg/op/authrequest.go +++ b/pkg/op/authrequest.go @@ -1,4 +1,4 @@ -package server +package op import ( "errors" @@ -7,6 +7,7 @@ import ( "github.com/gorilla/schema" "github.com/caos/oidc/pkg/oidc" + str_utils "github.com/caos/utils/strings" ) func ParseAuthRequest(w http.ResponseWriter, r *http.Request) (*oidc.AuthRequest, error) { @@ -24,6 +25,31 @@ func ParseAuthRequest(w http.ResponseWriter, r *http.Request) (*oidc.AuthRequest return authReq, err } -func ValidateAuthRequest(authRequest *oidc.AuthRequest) error { +func ValidateAuthRequest(authRequest *oidc.AuthRequest, storage Storage) error { + + if err := ValidateRedirectURI(authRequest.RedirectURI, authRequest.ClientID, storage); err != nil { + return err + } return errors.New("Unimplemented") //TODO: impl https://openid.net/specs/openid-connect-core-1_0.html#rfc.section.3.1.2.2 + + // if NeedsExistingSession(authRequest) { + // session, err := storage.CheckSession(authRequest) + // if err != nil { + // //TODO: return err + // } + // } +} + +func ValidateRedirectURI(uri, client_id string, storage Storage) error { + if uri == "" { + return errors.New("redirect_uri must not be empty") //TODO: + } + client, err := storage.GetClientByClientID(client_id) + if err != nil { + return err + } + if !str_utils.Contains(client.RedirectURIs(), uri) { + return errors.New("redirect_uri not allowed") + } + return nil } diff --git a/pkg/op/authrequest_test.go b/pkg/op/authrequest_test.go new file mode 100644 index 0000000..72338b6 --- /dev/null +++ b/pkg/op/authrequest_test.go @@ -0,0 +1,110 @@ +package op_test + +import ( + "testing" + + "github.com/caos/oidc/pkg/op" + "github.com/caos/oidc/pkg/op/mock" + + "github.com/caos/oidc/pkg/oidc" +) + +func TestValidateAuthRequest(t *testing.T) { + type args struct { + authRequest *oidc.AuthRequest + storage op.Storage + } + tests := []struct { + name string + args args + wantErr bool + }{ + //TODO: + // { + // "oauth2 spec" + // } + { + "scope missing fails", + args{&oidc.AuthRequest{}, nil}, + true, + }, + { + "scope openid missing fails", + args{&oidc.AuthRequest{Scopes: []string{"profile"}}, nil}, + true, + }, + { + "response_type missing fails", + args{&oidc.AuthRequest{Scopes: []string{"openid"}}, nil}, + true, + }, + { + "client_id missing fails", + args{&oidc.AuthRequest{Scopes: []string{"openid"}, ResponseType: oidc.ResponseTypeCode}, nil}, + true, + }, + { + "redirect_uri missing fails", + args{&oidc.AuthRequest{Scopes: []string{"openid"}, ResponseType: oidc.ResponseTypeCode, ClientID: "client_id"}, nil}, + true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := op.ValidateAuthRequest(tt.args.authRequest, tt.args.storage); (err != nil) != tt.wantErr { + t.Errorf("ValidateAuthRequest() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestValidateRedirectURI(t *testing.T) { + type args struct { + uri string + clientID string + storage op.Storage + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + "empty fails", + args{"", "", nil}, + true, + }, + { + "unregistered fails", + args{"https://unregistered.com/callback", "client_id", mock.NewMockStorageExpectValidClientID(t)}, + true, + }, + { + "http not allowed fails", + args{"http://registered.com/callback", "client_id", mock.NewMockStorageExpectValidClientID(t)}, + true, + }, + { + "registered https ok", + args{"https://registered.com/callback", "client_id", mock.NewMockStorageExpectValidClientID(t)}, + false, + }, + { + "registered http allowed ok", + args{"http://localhost:9999/callback", "client_id", mock.NewMockStorageExpectValidClientID(t)}, + false, + }, + { + "registered scheme ok", + args{"custom://callback", "client_id", mock.NewMockStorageExpectValidClientID(t)}, + false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := op.ValidateRedirectURI(tt.args.uri, tt.args.clientID, tt.args.storage); (err != nil) != tt.wantErr { + t.Errorf("ValidateRedirectURI() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/pkg/op/config.go b/pkg/op/config.go index b6ebe08..d4ef8b4 100644 --- a/pkg/op/config.go +++ b/pkg/op/config.go @@ -1,4 +1,4 @@ -package server +package op type Configuration interface { Issuer() string diff --git a/pkg/op/default_op.go b/pkg/op/default_op.go index 2764196..8f98cf5 100644 --- a/pkg/op/default_op.go +++ b/pkg/op/default_op.go @@ -1,4 +1,4 @@ -package server +package op import ( "errors" @@ -184,16 +184,10 @@ func (p *DefaultOP) HandleAuthorize(w http.ResponseWriter, r *http.Request) { if err != nil { //TODO: return err } - err = ValidateAuthRequest(authRequest) + err = ValidateAuthRequest(authRequest, p.storage) if err != nil { //TODO: return err } - if NeedsExistingSession(authRequest) { - // session, err := p.storage.CheckSession(authRequest) - // if err != nil { - // //TODO: return err - // } - } // err = p.storage.CreateAuthRequest(authRequest) // if err != nil { // //TODO: return err diff --git a/pkg/op/default_op_test.go b/pkg/op/default_op_test.go index a0b0c85..ed359a5 100644 --- a/pkg/op/default_op_test.go +++ b/pkg/op/default_op_test.go @@ -1,4 +1,4 @@ -package server +package op import ( "net/http" diff --git a/pkg/op/go.mod b/pkg/op/go.mod index bb6297d..79c738e 100644 --- a/pkg/op/go.mod +++ b/pkg/op/go.mod @@ -8,11 +8,16 @@ replace github.com/caos/oidc/pkg/oidc => /Users/livio/workspaces/go/src/github.c replace github.com/caos/oidc/pkg/utils => /Users/livio/workspaces/go/src/github.com/caos/oidc/pkg/utils +replace github.com/caos/oidc/pkg/op => /Users/livio/workspaces/go/src/github.com/caos/oidc/pkg/op + require ( 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/op v0.0.0-00010101000000-000000000000 github.com/caos/oidc/pkg/utils v0.0.0-00010101000000-000000000000 + github.com/caos/utils v0.0.0-20191104132131-b318678afbef github.com/caos/utils/logging v0.0.0-20191104132131-b318678afbef + github.com/golang/mock v1.3.1 github.com/gorilla/mux v1.7.3 github.com/gorilla/schema v1.1.0 github.com/stretchr/testify v1.4.0 diff --git a/pkg/op/go.sum b/pkg/op/go.sum index f2e7c9d..95796ea 100644 --- a/pkg/op/go.sum +++ b/pkg/op/go.sum @@ -20,7 +20,10 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1 h1:G5FRp8JnTd7RQH5kemVNlMeyXQAztQ3mOWV95KxsXH8= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1 h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= @@ -57,6 +60,8 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191117063200-497ca9f6d64f h1:kz4KIr+xcPUsI3VMoqWfPMvtnJ6MGfiVwsWSVzphMO4= golang.org/x/crypto v0.0.0-20191117063200-497ca9f6d64f/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20191119213627-4f8c1d86b1ba h1:9bFeDpN3gTqNanMVqNcoR/pJQuP5uroC3t1D7eXozTE= +golang.org/x/crypto v0.0.0-20191119213627-4f8c1d86b1ba/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -88,6 +93,8 @@ golang.org/x/sys v0.0.0-20191002091554-b397fe3ad8ed h1:5TJcLJn2a55mJjzYk0yOoqN8X golang.org/x/sys v0.0.0-20191002091554-b397fe3ad8ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191119060738-e882bf8e40c2 h1:wAW1U21MfVN0sUipAD8952TBjGXMRHFKQugDlQ9RwwE= golang.org/x/sys v0.0.0-20191119060738-e882bf8e40c2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e h1:N7DeIrjYszNmSW409R3frPPwglRwMkXSBzwVbkOjLLA= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= @@ -95,9 +102,11 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= diff --git a/pkg/op/mock/generate.go b/pkg/op/mock/generate.go new file mode 100644 index 0000000..57b8351 --- /dev/null +++ b/pkg/op/mock/generate.go @@ -0,0 +1,3 @@ +package mock + +//go:generate mockgen -package mock -destination ./storage.mock.go github.com/caos/oidc/pkg/op Storage diff --git a/pkg/op/mock/sotrage.mock.impl.go b/pkg/op/mock/sotrage.mock.impl.go new file mode 100644 index 0000000..4611956 --- /dev/null +++ b/pkg/op/mock/sotrage.mock.impl.go @@ -0,0 +1,37 @@ +package mock + +import ( + "testing" + + "github.com/golang/mock/gomock" + + "github.com/caos/oidc/pkg/op" +) + +func NewStorage(t *testing.T) op.Storage { + return NewMockStorage(gomock.NewController(t)) +} + +func NewMockStorageExpectValidClientID(t *testing.T) op.Storage { + m := NewStorage(t) + ExpectValidClientID(m) + return m +} + +func ExpectValidClientID(s op.Storage) { + mockS := s.(*MockStorage) + mockS.EXPECT().GetClientByClientID(gomock.Any()).Return(&ConfClient{}, nil) +} + +type ConfClient struct{} + +func (c *ConfClient) Type() op.ClientType { + return op.ClientTypeConfidential +} +func (c *ConfClient) RedirectURIs() []string { + return []string{ + "https://registered.com/callback", + "http://localhost:9999/callback", + "custom://callback", + } +} diff --git a/pkg/op/mock/storage.mock.go b/pkg/op/mock/storage.mock.go new file mode 100644 index 0000000..0f803f6 --- /dev/null +++ b/pkg/op/mock/storage.mock.go @@ -0,0 +1,64 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/caos/oidc/pkg/op (interfaces: Storage) + +// Package mock is a generated GoMock package. +package mock + +import ( + oidc "github.com/caos/oidc/pkg/oidc" + op "github.com/caos/oidc/pkg/op" + gomock "github.com/golang/mock/gomock" + reflect "reflect" +) + +// MockStorage is a mock of Storage interface +type MockStorage struct { + ctrl *gomock.Controller + recorder *MockStorageMockRecorder +} + +// MockStorageMockRecorder is the mock recorder for MockStorage +type MockStorageMockRecorder struct { + mock *MockStorage +} + +// NewMockStorage creates a new mock instance +func NewMockStorage(ctrl *gomock.Controller) *MockStorage { + mock := &MockStorage{ctrl: ctrl} + mock.recorder = &MockStorageMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockStorage) EXPECT() *MockStorageMockRecorder { + return m.recorder +} + +// CreateAuthRequest mocks base method +func (m *MockStorage) CreateAuthRequest(arg0 *oidc.AuthRequest) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateAuthRequest", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateAuthRequest indicates an expected call of CreateAuthRequest +func (mr *MockStorageMockRecorder) CreateAuthRequest(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateAuthRequest", reflect.TypeOf((*MockStorage)(nil).CreateAuthRequest), arg0) +} + +// GetClientByClientID mocks base method +func (m *MockStorage) GetClientByClientID(arg0 string) (op.Client, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetClientByClientID", arg0) + ret0, _ := ret[0].(op.Client) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetClientByClientID indicates an expected call of GetClientByClientID +func (mr *MockStorageMockRecorder) GetClientByClientID(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetClientByClientID", reflect.TypeOf((*MockStorage)(nil).GetClientByClientID), arg0) +} diff --git a/pkg/op/op.go b/pkg/op/op.go index 7925006..5a439d5 100644 --- a/pkg/op/op.go +++ b/pkg/op/op.go @@ -1,4 +1,4 @@ -package server +package op import ( "context" diff --git a/pkg/op/session.go b/pkg/op/session.go index 1d4433c..4a589c0 100644 --- a/pkg/op/session.go +++ b/pkg/op/session.go @@ -1,4 +1,4 @@ -package server +package op import "github.com/caos/oidc/pkg/oidc" diff --git a/pkg/op/storage.go b/pkg/op/storage.go index e49ff5c..2872703 100644 --- a/pkg/op/storage.go +++ b/pkg/op/storage.go @@ -1,7 +1,24 @@ -package server +package op import "github.com/caos/oidc/pkg/oidc" type Storage interface { CreateAuthRequest(*oidc.AuthRequest) error + GetClientByClientID(string) (Client, error) } + +type Client interface { + RedirectURIs() []string + Type() ClientType +} + +type ClientType int + +func (c ClientType) IsConvidential() bool { + return c == ClientTypeConfidential +} + +const ( + ClientTypeConfidential ClientType = iota + ClientTypePublic +)