Access control: endpoint for searching single user permissions (#59669)

* initial commit

* clean up

* fix a bug and add tests

* more tests

* undo some unintended changes

* undo some unintended changes

* linting

* PR feedback - add user ID to search options

* simplify the query

* Apply suggestions from code review

Co-authored-by: Gabriel MABILLE <gamab@users.noreply.github.com>

* remove unneeded formatting changes

Co-authored-by: Gabriel MABILLE <gamab@users.noreply.github.com>
This commit is contained in:
Ieva
2022-12-14 10:53:25 +00:00
committed by GitHub
co-authored by Gabriel MABILLE
parent d9d94ebc56
commit 6aa5a79cad
8 changed files with 363 additions and 21 deletions
@@ -86,11 +86,12 @@ func (s *AccessControlStore) SearchUsersPermissions(ctx context.Context, orgID i
INNER JOIN (
SELECT u.id AS user_id
FROM ` + s.sql.GetDialect().Quote("user") + ` AS u WHERE u.is_admin
) AS sa ON 1 = 1
) AS sa ON 1 = 1
WHERE br.role = ?
) AS up
WHERE (org_id = ? OR org_id = ?)
`
params := []interface{}{accesscontrol.RoleGrafanaAdmin, accesscontrol.GlobalOrgID, orgID}
if options.ActionPrefix != "" {
@@ -106,6 +107,11 @@ func (s *AccessControlStore) SearchUsersPermissions(ctx context.Context, orgID i
params = append(params, options.Scope)
}
if options.UserID != 0 {
q += ` AND user_id = ?`
params = append(params, options.UserID)
}
return sess.SQL(q, params...).
Find(&dbPerms)
}); err != nil {
@@ -121,7 +127,7 @@ func (s *AccessControlStore) SearchUsersPermissions(ctx context.Context, orgID i
}
// GetUsersBasicRoles returns the list of user basic roles (Admin, Editor, Viewer, Grafana Admin) indexed by UserID
func (s *AccessControlStore) GetUsersBasicRoles(ctx context.Context, orgID int64) (map[int64][]string, error) {
func (s *AccessControlStore) GetUsersBasicRoles(ctx context.Context, userFilter []int64, orgID int64) (map[int64][]string, error) {
type UserOrgRole struct {
UserID int64 `xorm:"id"`
OrgRole string `xorm:"role"`
@@ -132,12 +138,19 @@ func (s *AccessControlStore) GetUsersBasicRoles(ctx context.Context, orgID int64
// Find roles
q := `
SELECT u.id, ou.role, u.is_admin
FROM ` + s.sql.GetDialect().Quote("user") + ` AS u
FROM ` + s.sql.GetDialect().Quote("user") + ` AS u
LEFT JOIN org_user AS ou ON u.id = ou.user_id
WHERE u.is_admin OR ou.org_id = ?
WHERE (u.is_admin OR ou.org_id = ?)
`
params := []interface{}{orgID}
if len(userFilter) > 0 {
q += "AND u.id IN (?" + strings.Repeat(",?", len(userFilter)-1) + ")"
for _, u := range userFilter {
params = append(params, u)
}
}
return sess.SQL(q, orgID).Find(&dbRoles)
return sess.SQL(q, params...).Find(&dbRoles)
}); err != nil {
return nil, err
}
@@ -412,6 +412,34 @@ func TestIntegrationAccessControlStore_SearchUsersPermissions(t *testing.T) {
{Action: "teams:read", Scope: "teams:id:200"}},
},
},
{
name: "all assignments for one user by actionPrefix",
users: []testUser{
{orgRole: org.RoleAdmin, isAdmin: true},
{orgRole: org.RoleEditor, isAdmin: false},
},
permCmds: []rs.SetResourcePermissionsCommand{
// User assignments
{User: accesscontrol.User{ID: 1, IsExternal: false}, SetResourcePermissionCommand: readTeamPerm("1")},
{User: accesscontrol.User{ID: 2, IsExternal: false}, SetResourcePermissionCommand: readTeamPerm("2")},
// Team assignments
{TeamID: 1, SetResourcePermissionCommand: readTeamPerm("10")},
{TeamID: 2, SetResourcePermissionCommand: readTeamPerm("20")},
// Basic Assignments
{BuiltinRole: string(org.RoleAdmin), SetResourcePermissionCommand: readTeamPerm("100")},
{BuiltinRole: string(org.RoleEditor), SetResourcePermissionCommand: readTeamPerm("200")},
// Server Admin Assignment
{BuiltinRole: accesscontrol.RoleGrafanaAdmin, SetResourcePermissionCommand: readTeamPerm("1000")},
},
options: accesscontrol.SearchOptions{
ActionPrefix: "teams:",
UserID: 1,
},
wantPerm: map[int64][]accesscontrol.Permission{
1: {{Action: "teams:read", Scope: "teams:id:1"}, {Action: "teams:read", Scope: "teams:id:10"},
{Action: "teams:read", Scope: "teams:id:100"}, {Action: "teams:read", Scope: "teams:id:1000"}},
},
},
{
name: "filter permissions by action prefix",
users: []testUser{{orgRole: org.RoleAdmin, isAdmin: true}},
@@ -519,10 +547,11 @@ func TestIntegrationAccessControlStore_SearchUsersPermissions(t *testing.T) {
func TestAccessControlStore_GetUsersBasicRoles(t *testing.T) {
ctx := context.Background()
tests := []struct {
name string
users []testUser
wantRoles map[int64][]string
wantErr bool
name string
users []testUser
userFilter []int64
wantRoles map[int64][]string
wantErr bool
}{
{
name: "user with basic role",
@@ -548,6 +577,17 @@ func TestAccessControlStore_GetUsersBasicRoles(t *testing.T) {
2: {accesscontrol.RoleGrafanaAdmin},
},
},
{
name: "when filtered to one user, returns results only for that user",
userFilter: []int64{2},
users: []testUser{
{orgRole: org.RoleAdmin, isAdmin: false},
{orgRole: org.RoleEditor, isAdmin: true},
},
wantRoles: map[int64][]string{
2: {string(org.RoleEditor), accesscontrol.RoleGrafanaAdmin},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@@ -555,7 +595,7 @@ func TestAccessControlStore_GetUsersBasicRoles(t *testing.T) {
dbUsers := createUsersAndTeams(t, helperServices{userSvc, teamSvc, orgSvc}, 1, tt.users)
// Test
dbRoles, err := acStore.GetUsersBasicRoles(ctx, 1)
dbRoles, err := acStore.GetUsersBasicRoles(ctx, tt.userFilter, 1)
if tt.wantErr {
require.NotNil(t, err)
return