Access control: Extend GetUserPermissions() to query permissions in org (#83392)

* Access control: Extend GetUserPermissions() to query permissions in specific org

* Use db query to fetch permissions in org

* refactor

* refactor

* use conditional join

* minor refactor

* Add test cases

* Search permissions correctly in OSS vs Enterprise

* Get permissions from memory

* Refactor

* remove unused func

* Add tests for GetUserPermissionsInOrg

* fix linter
This commit is contained in:
Alexander Zobnin
2024-03-04 15:29:13 +03:00
committed by GitHub
parent 67c062acc7
commit 82a88cc83f
9 changed files with 171 additions and 17 deletions

View File

@@ -23,6 +23,7 @@ import (
"github.com/grafana/grafana/pkg/services/accesscontrol/migrator"
"github.com/grafana/grafana/pkg/services/accesscontrol/pluginutils"
"github.com/grafana/grafana/pkg/services/auth/identity"
"github.com/grafana/grafana/pkg/services/authn"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/folder"
@@ -41,6 +42,8 @@ var SharedWithMeFolderPermission = accesscontrol.Permission{
Scope: dashboards.ScopeFoldersProvider.GetResourceScopeUID(folder.SharedWithMeFolderUID),
}
var OSSRolesPrefixes = []string{accesscontrol.ManagedRolePrefix, accesscontrol.ExternalServiceRolePrefix}
func ProvideService(cfg *setting.Cfg, db db.DB, routeRegister routing.RouteRegister, cache *localcache.CacheService,
accessControl accesscontrol.AccessControl, features featuremgmt.FeatureToggles) (*Service, error) {
service := ProvideOSSService(cfg, database.ProvideService(db), cache, features)
@@ -125,7 +128,7 @@ func (s *Service) getUserPermissions(ctx context.Context, user identity.Requeste
UserID: userID,
Roles: accesscontrol.GetOrgRoles(user),
TeamIDs: user.GetTeams(),
RolePrefixes: []string{accesscontrol.ManagedRolePrefix, accesscontrol.ExternalServiceRolePrefix},
RolePrefixes: OSSRolesPrefixes,
})
if err != nil {
return nil, err
@@ -158,6 +161,48 @@ func (s *Service) getCachedUserPermissions(ctx context.Context, user identity.Re
return permissions, nil
}
func (s *Service) GetUserPermissionsInOrg(ctx context.Context, user identity.Requester, orgID int64) ([]accesscontrol.Permission, error) {
permissions := make([]accesscontrol.Permission, 0)
if s.features.IsEnabled(ctx, featuremgmt.FlagNestedFolders) {
permissions = append(permissions, SharedWithMeFolderPermission)
}
namespace, id := user.GetNamespacedID()
userID, err := identity.UserIdentifier(namespace, id)
if err != nil {
return nil, err
}
// Get permissions for user's basic roles from RAM
roleList, err := s.store.GetUsersBasicRoles(ctx, []int64{userID}, orgID)
if err != nil {
return nil, fmt.Errorf("could not fetch basic roles for the user: %w", err)
}
var roles []string
var ok bool
if roles, ok = roleList[userID]; !ok {
return nil, fmt.Errorf("found no basic roles for user %d in organisation %d", userID, orgID)
}
for _, builtin := range roles {
if basicRole, ok := s.roles[builtin]; ok {
permissions = append(permissions, basicRole.Permissions...)
}
}
dbPermissions, err := s.store.SearchUsersPermissions(ctx, orgID, accesscontrol.SearchOptions{
NamespacedID: authn.NamespacedID(namespace, userID),
// Query only basic, managed and plugin roles in OSS
RolePrefixes: OSSRolesPrefixes,
})
if err != nil {
return nil, err
}
userPermissions := dbPermissions[userID]
return append(permissions, userPermissions...), nil
}
func (s *Service) ClearUserPermissionCache(user identity.Requester) {
s.cache.Delete(permissionCacheKey(user))
}
@@ -237,6 +282,8 @@ func (s *Service) DeclarePluginRoles(ctx context.Context, ID, name string, regs
// SearchUsersPermissions returns all users' permissions filtered by action prefixes
func (s *Service) SearchUsersPermissions(ctx context.Context, usr identity.Requester,
options accesscontrol.SearchOptions) (map[int64][]accesscontrol.Permission, error) {
// Limit roles to available in OSS
options.RolePrefixes = OSSRolesPrefixes
if options.NamespacedID != "" {
userID, err := options.ComputeUserID()
if err != nil {

View File

@@ -938,3 +938,59 @@ func TestService_DeleteExternalServiceRole(t *testing.T) {
})
}
}
func TestService_GetUserPermissionsInOrg(t *testing.T) {
tests := []struct {
name string
orgID int64
ramRoles map[string]*accesscontrol.RoleDTO // BasicRole => RBAC BasicRole
storedPerms map[int64][]accesscontrol.Permission // UserID => Permissions
storedRoles map[int64][]string // UserID => Roles
want []accesscontrol.Permission
}{
{
name: "should get correct permissions from another org",
orgID: 2,
ramRoles: map[string]*accesscontrol.RoleDTO{
string(roletype.RoleEditor): {Permissions: []accesscontrol.Permission{}},
string(roletype.RoleAdmin): {Permissions: []accesscontrol.Permission{
{Action: accesscontrol.ActionTeamsRead, Scope: "teams:*"},
}},
},
storedPerms: map[int64][]accesscontrol.Permission{
1: {
{Action: accesscontrol.ActionTeamsRead, Scope: "teams:id:1"},
{Action: accesscontrol.ActionTeamsPermissionsRead, Scope: "teams:id:1"},
},
2: {
{Action: accesscontrol.ActionTeamsRead, Scope: "teams:id:2"},
},
},
storedRoles: map[int64][]string{
1: {string(roletype.RoleAdmin)},
2: {string(roletype.RoleEditor)},
},
want: []accesscontrol.Permission{
{Action: accesscontrol.ActionTeamsRead, Scope: "teams:id:2"},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx := context.Background()
ac := setupTestEnv(t)
ac.roles = tt.ramRoles
ac.store = actest.FakeStore{
ExpectedUsersPermissions: tt.storedPerms,
ExpectedUsersRoles: tt.storedRoles,
}
user := &user.SignedInUser{OrgID: 1, UserID: 2}
got, err := ac.GetUserPermissionsInOrg(ctx, user, 2)
require.Nil(t, err)
assert.ElementsMatch(t, got, tt.want)
})
}
}