diff --git a/internal/service/access_controls_service.go b/internal/service/access_controls_service.go index 6e9a3bb8..6f58fa7f 100644 --- a/internal/service/access_controls_service.go +++ b/internal/service/access_controls_service.go @@ -19,6 +19,7 @@ type LabelProvider interface { type AccessControlsService struct { log *logger.Logger config *model.Config + runtime *model.RuntimeConfig labelProvider LabelProvider } @@ -27,6 +28,7 @@ type AccessControlServiceInput struct { Log *logger.Logger Config *model.Config + Runtime *model.RuntimeConfig LabelProvider LabelProvider `optional:"true"` } @@ -35,6 +37,7 @@ func NewAccessControlsService(i AccessControlServiceInput) *AccessControlsServic return &AccessControlsService{ log: i.Log, config: i.Config, + runtime: i.Runtime, labelProvider: i.LabelProvider, } } @@ -68,6 +71,10 @@ func (service *AccessControlsService) getACLs(domain string, lookup func(locator var nameMatchedApps []string locatorFunc := func(name string, app *model.App) bool { + if !strings.HasSuffix(normalizedDomain, "."+service.runtime.CookieDomain) && normalizedDomain != service.runtime.CookieDomain { + service.log.App.Debug().Str("name", name).Msg("Domain does not match runtime cookie domain, skipping") + return false + } if app.Config.Domain != "" { if !service.ensureAscii(app.Config.Domain) { service.log.App.Warn().Str("name", name).Str("domain", app.Config.Domain).Msg("Domain contains non-ascii characters, skipping") @@ -104,7 +111,7 @@ func (service *AccessControlsService) getACLs(domain string, lookup func(locator } if len(nameMatchedApps) > 1 { - return nil, fmt.Errorf("multiple apps matched domain by name, app names must be unique") + return nil, fmt.Errorf("domain matched multiple apps by name prefix, use explicit domain config") } service.log.App.Debug().Str("domain", domain).Msg("Found matching app by app name") diff --git a/internal/service/access_controls_service_test.go b/internal/service/access_controls_service_test.go index 846d6a9f..1e773a15 100644 --- a/internal/service/access_controls_service_test.go +++ b/internal/service/access_controls_service_test.go @@ -7,6 +7,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/tinyauthapp/tinyauth/internal/model" + "github.com/tinyauthapp/tinyauth/internal/test" "github.com/tinyauthapp/tinyauth/internal/utils/logger" ) @@ -35,6 +36,8 @@ func TestAccessControlsService(t *testing.T) { log := logger.NewLogger().WithTestConfig() log.Init() + _, runtime := test.CreateTestConfigs(t) + tests := []struct { name string domain string @@ -44,6 +47,14 @@ func TestAccessControlsService(t *testing.T) { }{ { name: "returns ACLs for domain", + domain: "app.example.com", + acls: map[string]model.App{ + "foo": {Config: model.AppConfig{Domain: "app.example.com"}}, + }, + want: &model.App{Config: model.AppConfig{Domain: "app.example.com"}}, + }, + { + name: "returns ACLs for root domain", domain: "example.com", acls: map[string]model.App{ "foo": {Config: model.AppConfig{Domain: "example.com"}}, @@ -103,6 +114,27 @@ func TestAccessControlsService(t *testing.T) { acls: map[string]model.App{}, want: nil, }, + { + name: "App in domain not matching with the cookie domain should return nothing with name matching", + domain: "foo.bad_example.com", + acls: map[string]model.App{ + "foo": { + Path: model.AppPath{Allow: "/foo"}, + }, + }, + want: nil, + }, + { + name: "App in domain not matching with the cookie domain should return nothing with domain matching", + domain: "foo.bad_example.com", + acls: map[string]model.App{ + "foo": { + Path: model.AppPath{Allow: "/foo"}, + Config: model.AppConfig{Domain: "foo.bad_example.com"}, + }, + }, + want: nil, + }, } // run once for a mock provider @@ -111,6 +143,7 @@ func TestAccessControlsService(t *testing.T) { mock := newMockProvider(test.acls, false) acls := NewAccessControlsService(AccessControlServiceInput{ Log: log, + Runtime: &runtime, Config: &model.Config{}, LabelProvider: mock, }) @@ -128,7 +161,8 @@ func TestAccessControlsService(t *testing.T) { for _, test := range tests { t.Run(test.name+"(staticACLs)", func(t *testing.T) { acls := NewAccessControlsService(AccessControlServiceInput{ - Log: log, + Log: log, + Runtime: &runtime, Config: &model.Config{ Apps: test.acls, }, @@ -146,16 +180,32 @@ func TestAccessControlsService(t *testing.T) { // get acls should return an error when the provider fails mock := newMockProvider(map[string]model.App{}, true) acls := NewAccessControlsService(AccessControlServiceInput{ - Log: log, - Config: &model.Config{}, + Log: log, + Runtime: &runtime, + Config: &model.Config{}, }) _, err := acls.getACLs("example.com", mock.Lookup) - require.Error(t, err) + assert.Error(t, err) + + // get acls should return an error when multiple apps with the same domain exist + acls = NewAccessControlsService(AccessControlServiceInput{ + Log: log, + Runtime: &runtime, + Config: &model.Config{ + Apps: map[string]model.App{ + "foo": {Path: model.AppPath{Allow: "/foo"}}, + "foo.bar": {Path: model.AppPath{Allow: "/bar"}}, + }, + }, + }) + _, err = acls.GetAccessControls("foo.bar.example.com") + assert.ErrorContains(t, err, "domain matched multiple apps by name prefix, use explicit domain config") // get access controls should get acls from // static when static acls are configured acls = NewAccessControlsService(AccessControlServiceInput{ - Log: log, + Log: log, + Runtime: &runtime, Config: &model.Config{ Apps: map[string]model.App{ "foo": {Config: model.AppConfig{Domain: "foo.example.com"}}, @@ -164,12 +214,12 @@ func TestAccessControlsService(t *testing.T) { }) app, err := acls.GetAccessControls("foo.example.com") require.NoError(t, err) - require.Equal(t, &model.App{Config: model.AppConfig{Domain: "foo.example.com"}}, app) + assert.Equal(t, &model.App{Config: model.AppConfig{Domain: "foo.example.com"}}, app) // should return nil for no apps app, err = acls.GetAccessControls("bar.example.com") require.NoError(t, err) - require.Nil(t, app) + assert.Nil(t, app) // Should use label provider if available mock = newMockProvider(map[string]model.App{ @@ -179,10 +229,11 @@ func TestAccessControlsService(t *testing.T) { }, false) acls = NewAccessControlsService(AccessControlServiceInput{ Log: log, + Runtime: &runtime, Config: &model.Config{}, LabelProvider: mock, }) app, err = acls.GetAccessControls("bar.example.com") require.NoError(t, err) - require.Equal(t, &model.App{Config: model.AppConfig{Domain: "bar.example.com"}}, app) + assert.Equal(t, &model.App{Config: model.AppConfig{Domain: "bar.example.com"}}, app) }