diff --git a/lib/services/role.go b/lib/services/role.go index 93f5b97902c68..511f9c218a091 100644 --- a/lib/services/role.go +++ b/lib/services/role.go @@ -1018,6 +1018,22 @@ func (result *EnumerationResult) WildcardDenied() bool { return result.wildcardDenied } +// ToEntities converts result back to allowed and denied entity slices. +// +// If wildcard is denied, only "*" is returned for the denied slice. +// If wildcard is allowed, allowed entities will be appended to the allowed +// slice after the "*" as a hint for users to select. +// Denied entities is only included if the wildcard is allowed. +func (result *EnumerationResult) ToEntities() (allowed, denied []string) { + if result.wildcardDenied { + return nil, []string{types.Wildcard} + } + if result.wildcardAllowed { + return append([]string{types.Wildcard}, result.Allowed()...), result.Denied() + } + return result.Allowed(), nil +} + // NewEnumerationResult returns new EnumerationResult. func NewEnumerationResult() EnumerationResult { return EnumerationResult{ @@ -1027,6 +1043,34 @@ func NewEnumerationResult() EnumerationResult { } } +// NewEnumerationResultFromEntities creates a new EnumerationResult and +// populates the result with provided allowed and denied entries. +func NewEnumerationResultFromEntities(allowed, denied []string) EnumerationResult { + var wildcardAllowed bool + var wildcardDenied bool + allowedDeniedMap := make(map[string]bool) + for _, allow := range allowed { + if allow == types.Wildcard { + wildcardAllowed = true + } else { + allowedDeniedMap[allow] = true + } + } + for _, deny := range denied { + if deny == types.Wildcard { + wildcardDenied = true + wildcardAllowed = false + break + } + allowedDeniedMap[deny] = false + } + return EnumerationResult{ + allowedDeniedMap: allowedDeniedMap, + wildcardAllowed: wildcardAllowed, + wildcardDenied: wildcardDenied, + } +} + // MatchNamespace returns true if given list of namespace matches // target namespace, wildcard matches everything. func MatchNamespace(selectors []string, namespace string) (bool, string) { diff --git a/lib/services/role_test.go b/lib/services/role_test.go index 86d49d093607a..8e582d08c9327 100644 --- a/lib/services/role_test.go +++ b/lib/services/role_test.go @@ -9739,3 +9739,46 @@ func TestCheckAccessToGitServer(t *testing.T) { }) } } + +func TestNewEnumerationResultFromEntities(t *testing.T) { + tests := []struct { + name string + inputAllowed []string + inputDenied []string + wantAllowed []string + wantDenied []string + }{ + { + name: "empty", + }, + { + name: "wildcard denied", + inputAllowed: []string{"allow_entry"}, + inputDenied: []string{"deny_entry", "*"}, + wantDenied: []string{"*"}, + }, + { + name: "wildcard allowed", + inputAllowed: []string{"allow_entry", "*", "deny_overwrite"}, + inputDenied: []string{"deny_overwrite"}, + wantAllowed: []string{"*", "allow_entry"}, + wantDenied: []string{"deny_overwrite"}, + }, + { + name: "no wildcard", + inputAllowed: []string{"allow_entry_1", "deny_overwrite", "allow_entry_2"}, + inputDenied: []string{"deny_overwrite", "deny_entry"}, + wantAllowed: []string{"allow_entry_1", "allow_entry_2"}, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + result := NewEnumerationResultFromEntities(test.inputAllowed, test.inputDenied) + + actualAllowed, actualDenied := result.ToEntities() + require.Equal(t, test.wantAllowed, actualAllowed) + require.Equal(t, test.wantDenied, actualDenied) + }) + } +} diff --git a/lib/web/ui/server.go b/lib/web/ui/server.go index 419874ee8dcc8..c0c190f3c7b44 100644 --- a/lib/web/ui/server.go +++ b/lib/web/ui/server.go @@ -339,19 +339,14 @@ type DatabaseInteractiveChecker interface { // MakeDatabase creates database objects. func MakeDatabase(database types.Database, accessChecker services.AccessChecker, interactiveChecker DatabaseInteractiveChecker, requiresRequest bool) Database { var ( - dbUsers []string - dbRoles []string + autoUserEnabled bool + dbUsers []string + dbRoles []string ) dbNamesResult := accessChecker.EnumerateDatabaseNames(database) - dbNames := dbNamesResult.Allowed() - if dbNamesResult.WildcardAllowed() { - dbNames = append(dbNames, types.Wildcard) - } + dbNames, _ := dbNamesResult.ToEntities() if res, err := accessChecker.EnumerateDatabaseUsers(database); err == nil { - dbUsers = res.Allowed() - if res.WildcardAllowed() { - dbUsers = append(dbUsers, types.Wildcard) - } + dbUsers, _ = res.ToEntities() } if roles, err := accessChecker.CheckDatabaseRoles(database, nil); err == nil { // Avoid assigning empty slice to keep the resulting roles nil. @@ -359,6 +354,9 @@ func MakeDatabase(database types.Database, accessChecker services.AccessChecker, dbRoles = roles } } + if autoUser, err := accessChecker.DatabaseAutoUserMode(database); err == nil { + autoUserEnabled = database.IsAutoUsersEnabled() && autoUser.IsEnabled() + } uiLabels := ui.MakeLabelsWithoutInternalPrefixes(database.GetAllLabels()) @@ -376,7 +374,7 @@ func MakeDatabase(database types.Database, accessChecker services.AccessChecker, URI: database.GetURI(), RequiresRequest: requiresRequest, SupportsInteractive: interactiveChecker.IsSupported(database.GetProtocol()), - AutoUsersEnabled: database.IsAutoUsersEnabled(), + AutoUsersEnabled: autoUserEnabled, } if database.IsAWSHosted() { diff --git a/lib/web/ui/server_test.go b/lib/web/ui/server_test.go index 6618a55766154..d6acbebd1bd5d 100644 --- a/lib/web/ui/server_test.go +++ b/lib/web/ui/server_test.go @@ -622,6 +622,7 @@ func TestMakeDatabaseConnectOptions(t *testing.T) { roles services.RoleSet db *types.DatabaseV3 assertResult require.ValueAssertionFunc + username string }{ "names wildcard": { db: makeTestDatabase(t, map[string]string{"env": "dev"}, false), @@ -698,12 +699,14 @@ func TestMakeDatabaseConnectOptions(t *testing.T) { }, }, "auto-user provisioning enabled": { - db: makeTestDatabase(t, map[string]string{"env": "dev"}, true), + db: makeTestDatabase(t, map[string]string{"env": "dev"}, true), + username: "alice", roles: services.NewRoleSet(&types.RoleV6{ Spec: types.RoleSpecV6{ Allow: types.RoleConditions{ Namespaces: []string{apidefaults.Namespace}, DatabaseLabels: types.Labels{"*": []string{"*"}}, + DatabaseUsers: []string{"otheruser"}, DatabaseRoles: []string{"myrole"}, }, Options: types.RoleOptions{ @@ -713,13 +716,35 @@ func TestMakeDatabaseConnectOptions(t *testing.T) { }), assertResult: func(t require.TestingT, v interface{}, _ ...interface{}) { db, _ := v.(Database) - require.ElementsMatch(t, []string{"myrole"}, db.DatabaseRoles) require.True(t, db.AutoUsersEnabled) + require.ElementsMatch(t, []string{"alice"}, db.DatabaseUsers) + require.ElementsMatch(t, []string{"myrole"}, db.DatabaseRoles) + }, + }, + "auto-user provisioning at database but disabled on role": { + db: makeTestDatabase(t, map[string]string{"env": "dev"}, true), + username: "alice", + roles: services.NewRoleSet(&types.RoleV6{ + Spec: types.RoleSpecV6{ + Allow: types.RoleConditions{ + Namespaces: []string{apidefaults.Namespace}, + DatabaseLabels: types.Labels{"*": []string{"*"}}, + DatabaseUsers: []string{"*", "myuser"}, + }, + Options: types.RoleOptions{ + CreateDatabaseUserMode: types.CreateDatabaseUserMode_DB_USER_MODE_OFF, + }, + }, + }), + assertResult: func(t require.TestingT, v interface{}, _ ...interface{}) { + db, _ := v.(Database) + require.False(t, db.AutoUsersEnabled) + require.ElementsMatch(t, []string{"*", "myuser"}, db.DatabaseUsers) }, }, } { t.Run(name, func(t *testing.T) { - accessChecker := services.NewAccessCheckerWithRoleSet(&services.AccessInfo{}, "clusterName", tc.roles) + accessChecker := services.NewAccessCheckerWithRoleSet(&services.AccessInfo{Username: tc.username}, "clusterName", tc.roles) single := MakeDatabase(tc.db, accessChecker, interactiveChecker, false) tc.assertResult(t, single)