feat(op): allow double star globs (#507)

Related to https://github.com/zitadel/zitadel/issues/5110
This commit is contained in:
Tim Möhlmann 2024-01-05 17:30:17 +02:00 committed by GitHub
parent dce79a73fb
commit c37ca25220
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 374 additions and 2 deletions

1
go.mod
View file

@ -3,6 +3,7 @@ module github.com/zitadel/oidc/v3
go 1.19
require (
github.com/bmatcuk/doublestar/v4 v4.6.1
github.com/go-chi/chi/v5 v5.0.11
github.com/go-jose/go-jose/v3 v3.0.1
github.com/golang/mock v1.6.0

2
go.sum
View file

@ -1,3 +1,5 @@
github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I=
github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=

View file

@ -7,10 +7,10 @@ import (
"net"
"net/http"
"net/url"
"path"
"strings"
"time"
"github.com/bmatcuk/doublestar/v4"
httphelper "github.com/zitadel/oidc/v3/pkg/http"
"github.com/zitadel/oidc/v3/pkg/oidc"
str "github.com/zitadel/oidc/v3/pkg/strings"
@ -283,7 +283,7 @@ func checkURIAgainstRedirects(client Client, uri string) error {
}
if globClient, ok := client.(HasRedirectGlobs); ok {
for _, uriGlob := range globClient.RedirectURIGlobs() {
isMatch, err := path.Match(uriGlob, uri)
isMatch, err := doublestar.Match(uriGlob, uri)
if err != nil {
return oidc.ErrServerError().WithParent(err)
}

View file

@ -583,6 +583,60 @@ func TestValidateAuthReqRedirectURI(t *testing.T) {
},
false,
},
{
"code flow dev mode has redirect globs regular ok",
args{
"http://registered.com/callback",
mock.NewHasRedirectGlobsWithConfig(t, []string{"http://registered.com/*"}, op.ApplicationTypeUserAgent, nil, true),
oidc.ResponseTypeCode,
},
false,
},
{
"code flow dev mode has redirect globs wildcard ok",
args{
"http://registered.com/callback",
mock.NewHasRedirectGlobsWithConfig(t, []string{"http://registered.com/*"}, op.ApplicationTypeUserAgent, nil, true),
oidc.ResponseTypeCode,
},
false,
},
{
"code flow dev mode has redirect globs double star ok",
args{
"http://registered.com/callback",
mock.NewHasRedirectGlobsWithConfig(t, []string{"http://**/*"}, op.ApplicationTypeUserAgent, nil, true),
oidc.ResponseTypeCode,
},
false,
},
{
"code flow dev mode has redirect globs double star ok",
args{
"http://registered.com/callback",
mock.NewHasRedirectGlobsWithConfig(t, []string{"http://**/*"}, op.ApplicationTypeUserAgent, nil, true),
oidc.ResponseTypeCode,
},
false,
},
{
"code flow dev mode has redirect globs IPv6 ok",
args{
"http://[::1]:80/callback",
mock.NewHasRedirectGlobsWithConfig(t, []string{"http://\\[::1\\]:80/*"}, op.ApplicationTypeUserAgent, nil, true),
oidc.ResponseTypeCode,
},
false,
},
{
"code flow dev mode has redirect globs bad pattern",
args{
"http://registered.com/callback",
mock.NewHasRedirectGlobsWithConfig(t, []string{"http://**/\\"}, op.ApplicationTypeUserAgent, nil, true),
oidc.ResponseTypeCode,
},
true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {

View file

@ -63,6 +63,7 @@ type Client interface {
// such as DevMode for the client being enabled.
// https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest
type HasRedirectGlobs interface {
Client
RedirectURIGlobs() []string
PostLogoutRedirectURIGlobs() []string
}

View file

@ -4,6 +4,7 @@ package mock
//go:generate mockgen -package mock -destination ./storage.mock.go github.com/zitadel/oidc/v3/pkg/op Storage
//go:generate mockgen -package mock -destination ./authorizer.mock.go github.com/zitadel/oidc/v3/pkg/op Authorizer
//go:generate mockgen -package mock -destination ./client.mock.go github.com/zitadel/oidc/v3/pkg/op Client
//go:generate mockgen -package mock -destination ./glob.mock.go github.com/zitadel/oidc/v3/pkg/op HasRedirectGlobs
//go:generate mockgen -package mock -destination ./configuration.mock.go github.com/zitadel/oidc/v3/pkg/op Configuration
//go:generate mockgen -package mock -destination ./discovery.mock.go github.com/zitadel/oidc/v3/pkg/op DiscoverStorage
//go:generate mockgen -package mock -destination ./signer.mock.go github.com/zitadel/oidc/v3/pkg/op SigningKey,Key

24
pkg/op/mock/glob.go Normal file
View file

@ -0,0 +1,24 @@
package mock
import (
"testing"
gomock "github.com/golang/mock/gomock"
"github.com/zitadel/oidc/v3/pkg/oidc"
op "github.com/zitadel/oidc/v3/pkg/op"
)
func NewHasRedirectGlobs(t *testing.T) op.HasRedirectGlobs {
return NewMockHasRedirectGlobs(gomock.NewController(t))
}
func NewHasRedirectGlobsWithConfig(t *testing.T, uri []string, appType op.ApplicationType, responseTypes []oidc.ResponseType, devMode bool) op.HasRedirectGlobs {
c := NewHasRedirectGlobs(t)
m := c.(*MockHasRedirectGlobs)
m.EXPECT().RedirectURIs().AnyTimes().Return(uri)
m.EXPECT().RedirectURIGlobs().AnyTimes().Return(uri)
m.EXPECT().ApplicationType().AnyTimes().Return(appType)
m.EXPECT().ResponseTypes().AnyTimes().Return(responseTypes)
m.EXPECT().DevMode().AnyTimes().Return(devMode)
return c
}

289
pkg/op/mock/glob.mock.go Normal file
View file

@ -0,0 +1,289 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/zitadel/oidc/v3/pkg/op (interfaces: HasRedirectGlobs)
// Package mock is a generated GoMock package.
package mock
import (
reflect "reflect"
time "time"
gomock "github.com/golang/mock/gomock"
oidc "github.com/zitadel/oidc/v3/pkg/oidc"
op "github.com/zitadel/oidc/v3/pkg/op"
)
// MockHasRedirectGlobs is a mock of HasRedirectGlobs interface.
type MockHasRedirectGlobs struct {
ctrl *gomock.Controller
recorder *MockHasRedirectGlobsMockRecorder
}
// MockHasRedirectGlobsMockRecorder is the mock recorder for MockHasRedirectGlobs.
type MockHasRedirectGlobsMockRecorder struct {
mock *MockHasRedirectGlobs
}
// NewMockHasRedirectGlobs creates a new mock instance.
func NewMockHasRedirectGlobs(ctrl *gomock.Controller) *MockHasRedirectGlobs {
mock := &MockHasRedirectGlobs{ctrl: ctrl}
mock.recorder = &MockHasRedirectGlobsMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockHasRedirectGlobs) EXPECT() *MockHasRedirectGlobsMockRecorder {
return m.recorder
}
// AccessTokenType mocks base method.
func (m *MockHasRedirectGlobs) AccessTokenType() op.AccessTokenType {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "AccessTokenType")
ret0, _ := ret[0].(op.AccessTokenType)
return ret0
}
// AccessTokenType indicates an expected call of AccessTokenType.
func (mr *MockHasRedirectGlobsMockRecorder) AccessTokenType() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AccessTokenType", reflect.TypeOf((*MockHasRedirectGlobs)(nil).AccessTokenType))
}
// ApplicationType mocks base method.
func (m *MockHasRedirectGlobs) ApplicationType() op.ApplicationType {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ApplicationType")
ret0, _ := ret[0].(op.ApplicationType)
return ret0
}
// ApplicationType indicates an expected call of ApplicationType.
func (mr *MockHasRedirectGlobsMockRecorder) ApplicationType() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ApplicationType", reflect.TypeOf((*MockHasRedirectGlobs)(nil).ApplicationType))
}
// AuthMethod mocks base method.
func (m *MockHasRedirectGlobs) AuthMethod() oidc.AuthMethod {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "AuthMethod")
ret0, _ := ret[0].(oidc.AuthMethod)
return ret0
}
// AuthMethod indicates an expected call of AuthMethod.
func (mr *MockHasRedirectGlobsMockRecorder) AuthMethod() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AuthMethod", reflect.TypeOf((*MockHasRedirectGlobs)(nil).AuthMethod))
}
// ClockSkew mocks base method.
func (m *MockHasRedirectGlobs) ClockSkew() time.Duration {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ClockSkew")
ret0, _ := ret[0].(time.Duration)
return ret0
}
// ClockSkew indicates an expected call of ClockSkew.
func (mr *MockHasRedirectGlobsMockRecorder) ClockSkew() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClockSkew", reflect.TypeOf((*MockHasRedirectGlobs)(nil).ClockSkew))
}
// DevMode mocks base method.
func (m *MockHasRedirectGlobs) DevMode() bool {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DevMode")
ret0, _ := ret[0].(bool)
return ret0
}
// DevMode indicates an expected call of DevMode.
func (mr *MockHasRedirectGlobsMockRecorder) DevMode() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DevMode", reflect.TypeOf((*MockHasRedirectGlobs)(nil).DevMode))
}
// GetID mocks base method.
func (m *MockHasRedirectGlobs) GetID() string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetID")
ret0, _ := ret[0].(string)
return ret0
}
// GetID indicates an expected call of GetID.
func (mr *MockHasRedirectGlobsMockRecorder) GetID() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetID", reflect.TypeOf((*MockHasRedirectGlobs)(nil).GetID))
}
// GrantTypes mocks base method.
func (m *MockHasRedirectGlobs) GrantTypes() []oidc.GrantType {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GrantTypes")
ret0, _ := ret[0].([]oidc.GrantType)
return ret0
}
// GrantTypes indicates an expected call of GrantTypes.
func (mr *MockHasRedirectGlobsMockRecorder) GrantTypes() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GrantTypes", reflect.TypeOf((*MockHasRedirectGlobs)(nil).GrantTypes))
}
// IDTokenLifetime mocks base method.
func (m *MockHasRedirectGlobs) IDTokenLifetime() time.Duration {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "IDTokenLifetime")
ret0, _ := ret[0].(time.Duration)
return ret0
}
// IDTokenLifetime indicates an expected call of IDTokenLifetime.
func (mr *MockHasRedirectGlobsMockRecorder) IDTokenLifetime() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IDTokenLifetime", reflect.TypeOf((*MockHasRedirectGlobs)(nil).IDTokenLifetime))
}
// IDTokenUserinfoClaimsAssertion mocks base method.
func (m *MockHasRedirectGlobs) IDTokenUserinfoClaimsAssertion() bool {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "IDTokenUserinfoClaimsAssertion")
ret0, _ := ret[0].(bool)
return ret0
}
// IDTokenUserinfoClaimsAssertion indicates an expected call of IDTokenUserinfoClaimsAssertion.
func (mr *MockHasRedirectGlobsMockRecorder) IDTokenUserinfoClaimsAssertion() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IDTokenUserinfoClaimsAssertion", reflect.TypeOf((*MockHasRedirectGlobs)(nil).IDTokenUserinfoClaimsAssertion))
}
// IsScopeAllowed mocks base method.
func (m *MockHasRedirectGlobs) IsScopeAllowed(arg0 string) bool {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "IsScopeAllowed", arg0)
ret0, _ := ret[0].(bool)
return ret0
}
// IsScopeAllowed indicates an expected call of IsScopeAllowed.
func (mr *MockHasRedirectGlobsMockRecorder) IsScopeAllowed(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsScopeAllowed", reflect.TypeOf((*MockHasRedirectGlobs)(nil).IsScopeAllowed), arg0)
}
// LoginURL mocks base method.
func (m *MockHasRedirectGlobs) LoginURL(arg0 string) string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "LoginURL", arg0)
ret0, _ := ret[0].(string)
return ret0
}
// LoginURL indicates an expected call of LoginURL.
func (mr *MockHasRedirectGlobsMockRecorder) LoginURL(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LoginURL", reflect.TypeOf((*MockHasRedirectGlobs)(nil).LoginURL), arg0)
}
// PostLogoutRedirectURIGlobs mocks base method.
func (m *MockHasRedirectGlobs) PostLogoutRedirectURIGlobs() []string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "PostLogoutRedirectURIGlobs")
ret0, _ := ret[0].([]string)
return ret0
}
// PostLogoutRedirectURIGlobs indicates an expected call of PostLogoutRedirectURIGlobs.
func (mr *MockHasRedirectGlobsMockRecorder) PostLogoutRedirectURIGlobs() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PostLogoutRedirectURIGlobs", reflect.TypeOf((*MockHasRedirectGlobs)(nil).PostLogoutRedirectURIGlobs))
}
// PostLogoutRedirectURIs mocks base method.
func (m *MockHasRedirectGlobs) PostLogoutRedirectURIs() []string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "PostLogoutRedirectURIs")
ret0, _ := ret[0].([]string)
return ret0
}
// PostLogoutRedirectURIs indicates an expected call of PostLogoutRedirectURIs.
func (mr *MockHasRedirectGlobsMockRecorder) PostLogoutRedirectURIs() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PostLogoutRedirectURIs", reflect.TypeOf((*MockHasRedirectGlobs)(nil).PostLogoutRedirectURIs))
}
// RedirectURIGlobs mocks base method.
func (m *MockHasRedirectGlobs) RedirectURIGlobs() []string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "RedirectURIGlobs")
ret0, _ := ret[0].([]string)
return ret0
}
// RedirectURIGlobs indicates an expected call of RedirectURIGlobs.
func (mr *MockHasRedirectGlobsMockRecorder) RedirectURIGlobs() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RedirectURIGlobs", reflect.TypeOf((*MockHasRedirectGlobs)(nil).RedirectURIGlobs))
}
// RedirectURIs mocks base method.
func (m *MockHasRedirectGlobs) RedirectURIs() []string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "RedirectURIs")
ret0, _ := ret[0].([]string)
return ret0
}
// RedirectURIs indicates an expected call of RedirectURIs.
func (mr *MockHasRedirectGlobsMockRecorder) RedirectURIs() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RedirectURIs", reflect.TypeOf((*MockHasRedirectGlobs)(nil).RedirectURIs))
}
// ResponseTypes mocks base method.
func (m *MockHasRedirectGlobs) ResponseTypes() []oidc.ResponseType {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ResponseTypes")
ret0, _ := ret[0].([]oidc.ResponseType)
return ret0
}
// ResponseTypes indicates an expected call of ResponseTypes.
func (mr *MockHasRedirectGlobsMockRecorder) ResponseTypes() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ResponseTypes", reflect.TypeOf((*MockHasRedirectGlobs)(nil).ResponseTypes))
}
// RestrictAdditionalAccessTokenScopes mocks base method.
func (m *MockHasRedirectGlobs) RestrictAdditionalAccessTokenScopes() func([]string) []string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "RestrictAdditionalAccessTokenScopes")
ret0, _ := ret[0].(func([]string) []string)
return ret0
}
// RestrictAdditionalAccessTokenScopes indicates an expected call of RestrictAdditionalAccessTokenScopes.
func (mr *MockHasRedirectGlobsMockRecorder) RestrictAdditionalAccessTokenScopes() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RestrictAdditionalAccessTokenScopes", reflect.TypeOf((*MockHasRedirectGlobs)(nil).RestrictAdditionalAccessTokenScopes))
}
// RestrictAdditionalIdTokenScopes mocks base method.
func (m *MockHasRedirectGlobs) RestrictAdditionalIdTokenScopes() func([]string) []string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "RestrictAdditionalIdTokenScopes")
ret0, _ := ret[0].(func([]string) []string)
return ret0
}
// RestrictAdditionalIdTokenScopes indicates an expected call of RestrictAdditionalIdTokenScopes.
func (mr *MockHasRedirectGlobsMockRecorder) RestrictAdditionalIdTokenScopes() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RestrictAdditionalIdTokenScopes", reflect.TypeOf((*MockHasRedirectGlobs)(nil).RestrictAdditionalIdTokenScopes))
}