Access control: Cache basic roles and teams permissions (#87043)
* RBAC: Cache basic roles permissions * Cache teams permissions * Set cache TTL to 1 minute * Add OSS implementation * Fetch basic role permissions correctly * fix conflict_user_command * Fix teams permissions query * Add traces for GetUserPermissions * Fix folders tests * Fix colflict user command * Update store mock * Fix linter error * Reuse GetUserPermissions for fetching basic roles * tests for GetTeamsPermissions * pre-allocate slice capacity * Fix linter
This commit is contained in:
@@ -17,7 +17,7 @@ const (
|
||||
|
||||
// teamAssignsSQL is a query to select all users' team assignments.
|
||||
teamAssignsSQL = `SELECT tm.user_id, tr.org_id, tr.role_id
|
||||
FROM team_role AS tr
|
||||
FROM team_role AS tr
|
||||
INNER JOIN team_member AS tm ON tm.team_id = tr.team_id`
|
||||
|
||||
// basicRoleAssignsSQL is a query to select all users basic role (Admin, Editor, Viewer, None) assignments.
|
||||
@@ -63,11 +63,9 @@ func (s *AccessControlStore) GetUserPermissions(ctx context.Context, query acces
|
||||
` + filter
|
||||
|
||||
if len(query.RolePrefixes) > 0 {
|
||||
q += " WHERE ( " + strings.Repeat("role.name LIKE ? OR ", len(query.RolePrefixes)-1)
|
||||
q += "role.name LIKE ? )"
|
||||
for i := range query.RolePrefixes {
|
||||
params = append(params, query.RolePrefixes[i]+"%")
|
||||
}
|
||||
rolePrefixesFilter, filterParams := accesscontrol.RolePrefixesFilter(query.RolePrefixes)
|
||||
q += rolePrefixesFilter
|
||||
params = append(params, filterParams...)
|
||||
}
|
||||
|
||||
if err := sess.SQL(q, params...).Find(&result); err != nil {
|
||||
@@ -80,6 +78,82 @@ func (s *AccessControlStore) GetUserPermissions(ctx context.Context, query acces
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *AccessControlStore) GetBasicRolesPermissions(ctx context.Context, query accesscontrol.GetUserPermissionsQuery) ([]accesscontrol.Permission, error) {
|
||||
return s.GetUserPermissions(ctx, accesscontrol.GetUserPermissionsQuery{
|
||||
Roles: query.Roles,
|
||||
OrgID: query.OrgID,
|
||||
RolePrefixes: query.RolePrefixes,
|
||||
})
|
||||
}
|
||||
|
||||
type teamPermission struct {
|
||||
TeamID int64 `xorm:"team_id"`
|
||||
Action string
|
||||
Scope string
|
||||
}
|
||||
|
||||
func (p teamPermission) Permission() accesscontrol.Permission {
|
||||
return accesscontrol.Permission{
|
||||
Action: p.Action,
|
||||
Scope: p.Scope,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *AccessControlStore) GetTeamsPermissions(ctx context.Context, query accesscontrol.GetUserPermissionsQuery) (map[int64][]accesscontrol.Permission, error) {
|
||||
teams := query.TeamIDs
|
||||
orgID := query.OrgID
|
||||
rolePrefixes := query.RolePrefixes
|
||||
result := make([]teamPermission, 0)
|
||||
err := s.sql.WithDbSession(ctx, func(sess *db.Session) error {
|
||||
if len(teams) == 0 {
|
||||
// no permission to fetch
|
||||
return nil
|
||||
}
|
||||
|
||||
q := `
|
||||
SELECT
|
||||
permission.action,
|
||||
permission.scope,
|
||||
all_role.team_id
|
||||
FROM permission
|
||||
INNER JOIN role ON role.id = permission.role_id
|
||||
INNER JOIN (
|
||||
SELECT tr.role_id, tr.team_id FROM team_role as tr
|
||||
WHERE tr.team_id IN(?` + strings.Repeat(", ?", len(teams)-1) + `)
|
||||
AND tr.org_id = ?
|
||||
) as all_role ON role.id = all_role.role_id
|
||||
`
|
||||
|
||||
params := make([]any, 0)
|
||||
for _, team := range teams {
|
||||
params = append(params, team)
|
||||
}
|
||||
params = append(params, orgID)
|
||||
|
||||
if len(rolePrefixes) > 0 {
|
||||
rolePrefixesFilter, filterParams := accesscontrol.RolePrefixesFilter(rolePrefixes)
|
||||
q += rolePrefixesFilter
|
||||
params = append(params, filterParams...)
|
||||
}
|
||||
|
||||
if err := sess.SQL(q, params...).Find(&result); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
teamPermissions := make(map[int64][]accesscontrol.Permission)
|
||||
for _, teamPermission := range result {
|
||||
tp := teamPermissions[teamPermission.TeamID]
|
||||
if tp == nil {
|
||||
tp = make([]accesscontrol.Permission, 0)
|
||||
}
|
||||
teamPermissions[teamPermission.TeamID] = append(tp, teamPermission.Permission())
|
||||
}
|
||||
return teamPermissions, err
|
||||
}
|
||||
|
||||
// SearchUsersPermissions returns the list of user permissions in specific organization indexed by UserID
|
||||
func (s *AccessControlStore) SearchUsersPermissions(ctx context.Context, orgID int64, options accesscontrol.SearchOptions) (map[int64][]accesscontrol.Permission, error) {
|
||||
type UserRBACPermission struct {
|
||||
|
||||
@@ -162,6 +162,82 @@ func TestAccessControlStore_GetUserPermissions(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
type getTeamsPermissionsTestCase struct {
|
||||
desc string
|
||||
orgID int64
|
||||
teamsPermissions [][]string
|
||||
teamsToQuery []int
|
||||
expected int
|
||||
}
|
||||
|
||||
func TestAccessControlStore_GetTeamsPermissions(t *testing.T) {
|
||||
tests := []getTeamsPermissionsTestCase{
|
||||
{
|
||||
desc: "should successfully get team permissions",
|
||||
orgID: 1,
|
||||
teamsPermissions: [][]string{
|
||||
{"100", "2"},
|
||||
{"101", "3"},
|
||||
},
|
||||
teamsToQuery: []int{0, 1},
|
||||
expected: 4,
|
||||
},
|
||||
{
|
||||
desc: "Should not get permissions for teams not listed in the query",
|
||||
orgID: 1,
|
||||
teamsPermissions: [][]string{
|
||||
{"100", "2"},
|
||||
{"101", "3"},
|
||||
},
|
||||
teamsToQuery: []int{0},
|
||||
expected: 2,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.desc, func(t *testing.T) {
|
||||
store, permissionStore, _, teamSvc, _ := setupTestEnv(t)
|
||||
|
||||
teams := make([]team.Team, 0)
|
||||
for i := 0; i < len(tt.teamsPermissions); i++ {
|
||||
team, err := teamSvc.CreateTeam(context.Background(), fmt.Sprintf("team-%v", i), "", tt.orgID)
|
||||
require.NoError(t, err)
|
||||
teams = append(teams, team)
|
||||
}
|
||||
|
||||
for teamIDx, teamPermissions := range tt.teamsPermissions {
|
||||
for _, id := range teamPermissions {
|
||||
team := teams[teamIDx]
|
||||
_, err := permissionStore.SetTeamResourcePermission(context.Background(), tt.orgID, team.ID, rs.SetResourcePermissionCommand{
|
||||
Actions: []string{"dashboards:read"},
|
||||
Resource: "dashboards",
|
||||
ResourceID: id,
|
||||
}, nil)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
teamIDs := make([]int64, 0)
|
||||
for _, teamIDx := range tt.teamsToQuery {
|
||||
if teamIDx < len(teams) {
|
||||
teamIDs = append(teamIDs, teams[teamIDx].ID)
|
||||
}
|
||||
}
|
||||
|
||||
teamsPermissions, err := store.GetTeamsPermissions(context.Background(), accesscontrol.GetUserPermissionsQuery{
|
||||
TeamIDs: teamIDs,
|
||||
OrgID: tt.orgID,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
permissions := make([]accesscontrol.Permission, 0)
|
||||
for _, teamPermissions := range teamsPermissions {
|
||||
permissions = append(permissions, teamPermissions...)
|
||||
}
|
||||
assert.Len(t, permissions, tt.expected)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccessControlStore_DeleteUserPermissions(t *testing.T) {
|
||||
t.Run("expect permissions in all orgs to be deleted", func(t *testing.T) {
|
||||
store, permissionsStore, usrSvc, teamSvc, _ := setupTestEnv(t)
|
||||
|
||||
Reference in New Issue
Block a user