userinfo and more

This commit is contained in:
Livio Amstutz 2019-12-10 14:50:39 +01:00
parent 7210be8e4b
commit 85814fb69a
12 changed files with 702 additions and 134 deletions

View file

@ -14,7 +14,6 @@ type Configuration interface {
KeysEndpoint() Endpoint
// SupportedScopes() []string
AuthMethodBasicSupported() bool
AuthMethodPostSupported() bool
Port() string

View file

@ -16,8 +16,8 @@ const (
defaultUserinfoEndpoint = "userinfo"
defaultKeysEndpoint = "keys"
authMethodBasic = "client_secret_basic"
authMethodPost = "client_secret_post"
AuthMethodBasic = "client_secret_basic"
AuthMethodPost = "client_secret_post"
DefaultIDTokenValidity = time.Duration(5 * time.Minute)
)
@ -251,5 +251,5 @@ func (p *DefaultOP) handleTokenExchange(w http.ResponseWriter, r *http.Request)
}
func (p *DefaultOP) HandleUserinfo(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("ok"))
Userinfo(w, r, p)
}

View file

@ -20,27 +20,38 @@ func CreateDiscoveryConfig(c Configuration, s Signer) *oidc.DiscoveryConfigurati
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: scopes(c),
ResponseTypesSupported: responseTypes(c),
GrantTypesSupported: grantTypes(c),
// ClaimsSupported: oidc.SupportedClaims,
IDTokenSigningAlgValuesSupported: sigAlgorithms(s),
SubjectTypesSupported: subjectTypes(c),
TokenEndpointAuthMethodsSupported: authMethods(c.AuthMethodBasicSupported(), c.AuthMethodPostSupported()),
JwksURI: c.KeysEndpoint().Absolute(c.Issuer()),
ScopesSupported: Scopes(c),
ResponseTypesSupported: ResponseTypes(c),
GrantTypesSupported: GrantTypes(c),
ClaimsSupported: SupportedClaims(c),
IDTokenSigningAlgValuesSupported: SigAlgorithms(s),
SubjectTypesSupported: SubjectTypes(c),
TokenEndpointAuthMethodsSupported: AuthMethods(c),
}
}
func scopes(c Configuration) []string {
return []string{
"openid",
"profile",
"email",
"phone",
} //TODO: config
const (
ScopeOpenID = "openid"
ScopeProfile = "profile"
ScopeEmail = "email"
ScopePhone = "phone"
ScopeAddress = "address"
)
var DefaultSupportedScopes = []string{
ScopeOpenID,
ScopeProfile,
ScopeEmail,
ScopePhone,
ScopeAddress,
}
func responseTypes(c Configuration) []string {
func Scopes(c Configuration) []string {
return DefaultSupportedScopes //TODO: config
}
func ResponseTypes(c Configuration) []string {
return []string{
"code",
"id_token",
@ -51,7 +62,7 @@ func responseTypes(c Configuration) []string {
}
}
func grantTypes(c Configuration) []string {
func GrantTypes(c Configuration) []string {
return []string{
"client_credentials",
"authorization_code",
@ -60,23 +71,49 @@ func grantTypes(c Configuration) []string {
}
}
func sigAlgorithms(s Signer) []string {
func SupportedClaims(c Configuration) []string {
return []string{ //TODO: config
"sub",
"aud",
"exp",
"iat",
"iss",
"auth_time",
"nonce",
"acr",
"amr",
"c_hash",
"at_hash",
"act",
"scopes",
"client_id",
"azp",
"preferred_username",
"name",
"family_name",
"given_name",
"locale",
"email",
"email_verified",
"phone_number",
"phone_number_verified",
}
}
func SigAlgorithms(s Signer) []string {
return []string{string(s.SignatureAlgorithm())}
}
func subjectTypes(c Configuration) []string {
func SubjectTypes(c Configuration) []string {
return []string{"public"} //TODO: config
}
func authMethods(basic, post bool) []string {
authMethods := make([]string, 0, 2)
if basic {
// if c.AuthMethodBasicSupported() {
authMethods = append(authMethods, authMethodBasic)
func AuthMethods(c Configuration) []string {
authMethods := []string{
AuthMethodBasic,
}
if post {
// if c.AuthMethodPostSupported() {
authMethods = append(authMethods, authMethodPost)
if c.AuthMethodPostSupported() {
authMethods = append(authMethods, AuthMethodPost)
}
return authMethods
}

View file

@ -1,4 +1,4 @@
package op
package op_test
import (
"net/http"
@ -6,9 +6,12 @@ import (
"reflect"
"testing"
"github.com/stretchr/testify/require"
"github.com/caos/oidc/pkg/oidc"
"github.com/caos/oidc/pkg/op"
"github.com/caos/oidc/pkg/op/mock"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/require"
"gopkg.in/square/go-jose.v2"
)
func TestDiscover(t *testing.T) {
@ -30,7 +33,7 @@ func TestDiscover(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Discover(tt.args.w, tt.args.config)
op.Discover(tt.args.w, tt.args.config)
rec := tt.args.w.(*httptest.ResponseRecorder)
require.Equal(t, http.StatusOK, rec.Code)
require.Equal(t, `{"issuer":"https://issuer.com"}`, rec.Body.String())
@ -40,8 +43,8 @@ func TestDiscover(t *testing.T) {
func TestCreateDiscoveryConfig(t *testing.T) {
type args struct {
c Configuration
s Signer
c op.Configuration
s op.Signer
}
tests := []struct {
name string
@ -52,7 +55,7 @@ func TestCreateDiscoveryConfig(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := CreateDiscoveryConfig(tt.args.c, tt.args.s); !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)
}
})
@ -61,27 +64,31 @@ func TestCreateDiscoveryConfig(t *testing.T) {
func Test_scopes(t *testing.T) {
type args struct {
c Configuration
c op.Configuration
}
tests := []struct {
name string
args args
want []string
}{
// TODO: Add test cases.
{
"default Scopes",
args{},
op.DefaultSupportedScopes,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := scopes(tt.args.c); !reflect.DeepEqual(got, tt.want) {
if got := op.Scopes(tt.args.c); !reflect.DeepEqual(got, tt.want) {
t.Errorf("scopes() = %v, want %v", got, tt.want)
}
})
}
}
func Test_responseTypes(t *testing.T) {
func Test_ResponseTypes(t *testing.T) {
type args struct {
c Configuration
c op.Configuration
}
tests := []struct {
name string
@ -92,16 +99,16 @@ func Test_responseTypes(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := responseTypes(tt.args.c); !reflect.DeepEqual(got, tt.want) {
if got := op.ResponseTypes(tt.args.c); !reflect.DeepEqual(got, tt.want) {
t.Errorf("responseTypes() = %v, want %v", got, tt.want)
}
})
}
}
func Test_grantTypes(t *testing.T) {
func Test_GrantTypes(t *testing.T) {
type args struct {
c Configuration
c op.Configuration
}
tests := []struct {
name string
@ -112,64 +119,64 @@ func Test_grantTypes(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := grantTypes(tt.args.c); !reflect.DeepEqual(got, tt.want) {
if got := op.GrantTypes(tt.args.c); !reflect.DeepEqual(got, tt.want) {
t.Errorf("grantTypes() = %v, want %v", got, tt.want)
}
})
}
}
// func Test_sigAlgorithms(t *testing.T) {
// type args struct {
// s Signer
// }
// tests := []struct {
// name string
// args args
// want []string
// }{
// {
// "",
// args{},
// []string{"RS256"},
// },
// }
// for _, tt := range tests {
// t.Run(tt.name, func(t *testing.T) {
// if got := sigAlgorithms(tt.args.s); !reflect.DeepEqual(got, tt.want) {
// t.Errorf("sigAlgorithms() = %v, want %v", got, tt.want)
// }
// })
// }
// }
// func Test_subjectTypes(t *testing.T) {
// type args struct {
// c Configuration
// }
// tests := []struct {
// name string
// args args
// want []string
// }{
// {
// "none",
// args{func()}
// }
// }
// for _, tt := range tests {
// t.Run(tt.name, func(t *testing.T) {
// if got := subjectTypes(tt.args.c); !reflect.DeepEqual(got, tt.want) {
// t.Errorf("subjectTypes() = %v, want %v", got, tt.want)
// }
// })
// }
// }
func Test_authMethods(t *testing.T) {
func TestSupportedClaims(t *testing.T) {
type args struct {
basic bool
post bool
c op.Configuration
}
tests := []struct {
name string
args args
want []string
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := op.SupportedClaims(tt.args.c); !reflect.DeepEqual(got, tt.want) {
t.Errorf("SupportedClaims() = %v, want %v", got, tt.want)
}
})
}
}
func Test_SigAlgorithms(t *testing.T) {
m := mock.NewMockSigner(gomock.NewController((t)))
type args struct {
s op.Signer
}
tests := []struct {
name string
args args
want []string
}{
{
"",
args{func() op.Signer {
m.EXPECT().SignatureAlgorithm().Return(jose.RS256)
return m
}()},
[]string{"RS256"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := op.SigAlgorithms(tt.args.s); !reflect.DeepEqual(got, tt.want) {
t.Errorf("sigAlgorithms() = %v, want %v", got, tt.want)
}
})
}
}
func Test_SubjectTypes(t *testing.T) {
type args struct {
c op.Configuration
}
tests := []struct {
name string
@ -178,28 +185,49 @@ func Test_authMethods(t *testing.T) {
}{
{
"none",
args{false, false},
[]string{},
},
{
"basic",
args{true, false},
[]string{authMethodBasic},
},
{
"post",
args{false, true},
[]string{authMethodPost},
},
{
"basic and post",
args{true, true},
[]string{authMethodBasic, authMethodPost},
args{},
[]string{"public"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := authMethods(tt.args.basic, tt.args.post); !reflect.DeepEqual(got, tt.want) {
if got := op.SubjectTypes(tt.args.c); !reflect.DeepEqual(got, tt.want) {
t.Errorf("subjectTypes() = %v, want %v", got, tt.want)
}
})
}
}
func Test_AuthMethods(t *testing.T) {
m := mock.NewMockConfiguration(gomock.NewController((t)))
type args struct {
c op.Configuration
}
tests := []struct {
name string
args args
want []string
}{
{
"imlicit basic",
args{func() op.Configuration {
m.EXPECT().AuthMethodPostSupported().Return(false)
return m
}()},
[]string{op.AuthMethodBasic},
},
{
"basic and post",
args{func() op.Configuration {
m.EXPECT().AuthMethodPostSupported().Return(true)
return m
}()},
[]string{op.AuthMethodBasic, op.AuthMethodPost},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := op.AuthMethods(tt.args.c); !reflect.DeepEqual(got, tt.want) {
t.Errorf("authMethods() = %v, want %v", got, tt.want)
}
})

View file

@ -0,0 +1,132 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/caos/oidc/pkg/op (interfaces: Configuration)
// Package mock is a generated GoMock package.
package mock
import (
op "github.com/caos/oidc/pkg/op"
gomock "github.com/golang/mock/gomock"
reflect "reflect"
)
// MockConfiguration is a mock of Configuration interface
type MockConfiguration struct {
ctrl *gomock.Controller
recorder *MockConfigurationMockRecorder
}
// MockConfigurationMockRecorder is the mock recorder for MockConfiguration
type MockConfigurationMockRecorder struct {
mock *MockConfiguration
}
// NewMockConfiguration creates a new mock instance
func NewMockConfiguration(ctrl *gomock.Controller) *MockConfiguration {
mock := &MockConfiguration{ctrl: ctrl}
mock.recorder = &MockConfigurationMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockConfiguration) EXPECT() *MockConfigurationMockRecorder {
return m.recorder
}
// AuthMethodPostSupported mocks base method
func (m *MockConfiguration) AuthMethodPostSupported() bool {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "AuthMethodPostSupported")
ret0, _ := ret[0].(bool)
return ret0
}
// AuthMethodPostSupported indicates an expected call of AuthMethodPostSupported
func (mr *MockConfigurationMockRecorder) AuthMethodPostSupported() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AuthMethodPostSupported", reflect.TypeOf((*MockConfiguration)(nil).AuthMethodPostSupported))
}
// AuthorizationEndpoint mocks base method
func (m *MockConfiguration) AuthorizationEndpoint() op.Endpoint {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "AuthorizationEndpoint")
ret0, _ := ret[0].(op.Endpoint)
return ret0
}
// AuthorizationEndpoint indicates an expected call of AuthorizationEndpoint
func (mr *MockConfigurationMockRecorder) AuthorizationEndpoint() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AuthorizationEndpoint", reflect.TypeOf((*MockConfiguration)(nil).AuthorizationEndpoint))
}
// Issuer mocks base method
func (m *MockConfiguration) Issuer() string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Issuer")
ret0, _ := ret[0].(string)
return ret0
}
// Issuer indicates an expected call of Issuer
func (mr *MockConfigurationMockRecorder) Issuer() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Issuer", reflect.TypeOf((*MockConfiguration)(nil).Issuer))
}
// KeysEndpoint mocks base method
func (m *MockConfiguration) KeysEndpoint() op.Endpoint {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "KeysEndpoint")
ret0, _ := ret[0].(op.Endpoint)
return ret0
}
// KeysEndpoint indicates an expected call of KeysEndpoint
func (mr *MockConfigurationMockRecorder) KeysEndpoint() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "KeysEndpoint", reflect.TypeOf((*MockConfiguration)(nil).KeysEndpoint))
}
// Port mocks base method
func (m *MockConfiguration) Port() string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Port")
ret0, _ := ret[0].(string)
return ret0
}
// Port indicates an expected call of Port
func (mr *MockConfigurationMockRecorder) Port() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Port", reflect.TypeOf((*MockConfiguration)(nil).Port))
}
// TokenEndpoint mocks base method
func (m *MockConfiguration) TokenEndpoint() op.Endpoint {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "TokenEndpoint")
ret0, _ := ret[0].(op.Endpoint)
return ret0
}
// TokenEndpoint indicates an expected call of TokenEndpoint
func (mr *MockConfigurationMockRecorder) TokenEndpoint() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TokenEndpoint", reflect.TypeOf((*MockConfiguration)(nil).TokenEndpoint))
}
// UserinfoEndpoint mocks base method
func (m *MockConfiguration) UserinfoEndpoint() op.Endpoint {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UserinfoEndpoint")
ret0, _ := ret[0].(op.Endpoint)
return ret0
}
// UserinfoEndpoint indicates an expected call of UserinfoEndpoint
func (mr *MockConfigurationMockRecorder) UserinfoEndpoint() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UserinfoEndpoint", reflect.TypeOf((*MockConfiguration)(nil).UserinfoEndpoint))
}

View file

@ -3,3 +3,5 @@ package mock
//go:generate mockgen -package mock -destination ./storage.mock.go github.com/caos/oidc/pkg/op Storage
//go:generate mockgen -package mock -destination ./authorizer.mock.go github.com/caos/oidc/pkg/op Authorizer
//go:generate mockgen -package mock -destination ./client.mock.go github.com/caos/oidc/pkg/op Client
//go:generate mockgen -package mock -destination ./configuration.mock.go github.com/caos/oidc/pkg/op Configuration
//go:generate mockgen -package mock -destination ./signer.mock.go github.com/caos/oidc/pkg/op Signer

View file

@ -0,0 +1,64 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/caos/oidc/pkg/op (interfaces: Signer)
// Package mock is a generated GoMock package.
package mock
import (
oidc "github.com/caos/oidc/pkg/oidc"
gomock "github.com/golang/mock/gomock"
go_jose_v2 "gopkg.in/square/go-jose.v2"
reflect "reflect"
)
// MockSigner is a mock of Signer interface
type MockSigner struct {
ctrl *gomock.Controller
recorder *MockSignerMockRecorder
}
// MockSignerMockRecorder is the mock recorder for MockSigner
type MockSignerMockRecorder struct {
mock *MockSigner
}
// NewMockSigner creates a new mock instance
func NewMockSigner(ctrl *gomock.Controller) *MockSigner {
mock := &MockSigner{ctrl: ctrl}
mock.recorder = &MockSignerMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockSigner) EXPECT() *MockSignerMockRecorder {
return m.recorder
}
// SignIDToken mocks base method
func (m *MockSigner) SignIDToken(arg0 *oidc.IDTokenClaims) (string, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SignIDToken", arg0)
ret0, _ := ret[0].(string)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// SignIDToken indicates an expected call of SignIDToken
func (mr *MockSignerMockRecorder) SignIDToken(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SignIDToken", reflect.TypeOf((*MockSigner)(nil).SignIDToken), arg0)
}
// SignatureAlgorithm mocks base method
func (m *MockSigner) SignatureAlgorithm() go_jose_v2.SignatureAlgorithm {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SignatureAlgorithm")
ret0, _ := ret[0].(go_jose_v2.SignatureAlgorithm)
return ret0
}
// SignatureAlgorithm indicates an expected call of SignatureAlgorithm
func (mr *MockSignerMockRecorder) SignatureAlgorithm() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SignatureAlgorithm", reflect.TypeOf((*MockSigner)(nil).SignatureAlgorithm))
}

View file

@ -168,3 +168,18 @@ func (mr *MockStorageMockRecorder) GetSigningKey() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSigningKey", reflect.TypeOf((*MockStorage)(nil).GetSigningKey))
}
// GetUserinfoFromScopes mocks base method
func (m *MockStorage) GetUserinfoFromScopes(arg0 []string) (*oidc.Userinfo, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetUserinfoFromScopes", arg0)
ret0, _ := ret[0].(*oidc.Userinfo)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetUserinfoFromScopes indicates an expected call of GetUserinfoFromScopes
func (mr *MockStorageMockRecorder) GetUserinfoFromScopes(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserinfoFromScopes", reflect.TypeOf((*MockStorage)(nil).GetUserinfoFromScopes), arg0)
}

View file

@ -22,6 +22,7 @@ type OPStorage interface {
GetClientByClientID(string) (Client, error)
AuthorizeClientIDSecret(string, string) (Client, error)
AuthorizeClientIDCodeVerifier(string, string) (Client, error)
GetUserinfoFromScopes([]string) (*oidc.Userinfo, error)
}
type Storage interface {

28
pkg/op/userinfo.go Normal file
View file

@ -0,0 +1,28 @@
package op
import (
"net/http"
"github.com/caos/oidc/pkg/utils"
)
type UserinfoProvider interface {
Storage() Storage
}
func Userinfo(w http.ResponseWriter, r *http.Request, userinfoProvider UserinfoProvider) {
scopes, err := ScopesFromAccessToken(w, r)
if err != nil {
return
}
info, err := userinfoProvider.Storage().GetUserinfoFromScopes(scopes)
if err != nil {
utils.MarshalJSON(w, err)
return
}
utils.MarshalJSON(w, info)
}
func ScopesFromAccessToken(w http.ResponseWriter, r *http.Request) ([]string, error) {
return []string{}, nil
}