AccessControl: Use UIDs for Resource permissions frontend (#95552)

* frontend can use uids to set resource permissions

* lint

* add uids to folder acl

* Update public/app/core/components/Select/UserPicker.tsx

Co-authored-by: Ieva <ieva.vasiljeva@grafana.com>

* simplify conditions

---------

Co-authored-by: Ieva <ieva.vasiljeva@grafana.com>
pull/95665/head
Jo 7 months ago committed by GitHub
parent 76b43267c8
commit 9f43724b57
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 4
      pkg/api/dashboard_permission.go
  2. 4
      pkg/api/dashboard_permission_test.go
  3. 1
      pkg/api/dtos/user.go
  4. 6
      pkg/api/folder_permission.go
  5. 4
      pkg/api/folder_permission_test.go
  6. 1
      pkg/api/org_users.go
  7. 6
      pkg/services/accesscontrol/models.go
  8. 4
      pkg/services/accesscontrol/ossaccesscontrol/receivers.go
  9. 10
      pkg/services/accesscontrol/resourcepermissions/api.go
  10. 14
      pkg/services/accesscontrol/resourcepermissions/store.go
  11. 4
      pkg/services/accesscontrol/resourcepermissions/store_test.go
  12. 2
      pkg/services/dashboards/models.go
  13. 1
      pkg/services/org/model.go
  14. 1
      pkg/services/org/orgimpl/store.go
  15. 18
      public/api-enterprise-spec.json
  16. 18
      public/api-merged.json
  17. 18
      public/app/core/components/AccessControl/AddPermission.tsx
  18. 37
      public/app/core/components/AccessControl/Permissions.tsx
  19. 6
      public/app/core/components/AccessControl/types.ts
  20. 1
      public/app/core/components/RolePickerDrawer/RolePickerBadges.test.tsx
  21. 5
      public/app/core/components/Select/ServiceAccountPicker.tsx
  22. 5
      public/app/core/components/Select/UserPicker.tsx
  23. 1
      public/app/features/users/__mocks__/userMocks.ts
  24. 1
      public/app/types/user.ts
  25. 18
      public/openapi3.json

@ -212,10 +212,10 @@ func (hs *HTTPServer) getDashboardACL(ctx context.Context, user identity.Request
FolderID: dashboard.FolderID, // nolint:staticcheck
Created: p.Created,
Updated: p.Updated,
UserID: p.UserId,
UserID: p.UserID,
UserLogin: p.UserLogin,
UserEmail: p.UserEmail,
TeamID: p.TeamId,
TeamID: p.TeamID,
TeamEmail: p.TeamEmail,
Team: p.Team,
Role: role,

@ -58,8 +58,8 @@ func TestHTTPServer_GetDashboardPermissionList(t *testing.T) {
hs.DashboardService = svc
hs.dashboardPermissionsService = &actest.FakePermissionsService{
ExpectedPermissions: []accesscontrol.ResourcePermission{
{UserId: 1, UserLogin: "regular", IsManaged: true},
{UserId: 2, UserLogin: "hidden", IsManaged: true},
{UserID: 1, UserLogin: "regular", IsManaged: true},
{UserID: 2, UserLogin: "hidden", IsManaged: true},
},
}
})

@ -43,6 +43,7 @@ type ResetUserPasswordForm struct {
type UserLookupDTO struct {
UserID int64 `json:"userId"`
UID string `json:"uid"`
Login string `json:"login"`
AvatarURL string `json:"avatarUrl"`
}

@ -153,10 +153,12 @@ func (hs *HTTPServer) getFolderACL(ctx context.Context, user identity.Requester,
FolderUID: folder.ParentUID,
Created: p.Created,
Updated: p.Updated,
UserID: p.UserId,
UserID: p.UserID,
UserUID: p.UserUID,
UserLogin: p.UserLogin,
UserEmail: p.UserEmail,
TeamID: p.TeamId,
TeamID: p.TeamID,
TeamUID: p.TeamUID,
TeamEmail: p.TeamEmail,
Team: p.Team,
Role: role,

@ -54,8 +54,8 @@ func TestHTTPServer_GetFolderPermissionList(t *testing.T) {
hs.folderPermissionsService = &actest.FakePermissionsService{
ExpectedPermissions: []accesscontrol.ResourcePermission{
{UserId: 1, UserLogin: "regular", IsManaged: true},
{UserId: 2, UserLogin: "hidden", IsManaged: true},
{UserID: 1, UserLogin: "regular", IsManaged: true},
{UserID: 2, UserLogin: "hidden", IsManaged: true},
},
}
})

@ -161,6 +161,7 @@ func (hs *HTTPServer) GetOrgUsersForCurrentOrgLookup(c *contextmodel.ReqContext)
for _, u := range orgUsersResult.OrgUsers {
result = append(result, &dtos.UserLookupDTO{
UID: u.UID,
UserID: u.UserID,
Login: u.Login,
AvatarURL: u.AvatarURL,

@ -234,10 +234,12 @@ type ResourcePermission struct {
RoleName string
Actions []string
Scope string
UserId int64
UserID int64
UserUID string
UserLogin string
UserEmail string
TeamId int64
TeamID int64
TeamUID string
TeamEmail string
Team string
BuiltInRole string

@ -171,8 +171,8 @@ func (r ReceiverPermissionsService) toSetResourcePermissionCommands(permissions
cmds = append(cmds, accesscontrol.SetResourcePermissionCommand{
Permission: permission,
BuiltinRole: p.BuiltInRole,
TeamID: p.TeamId,
UserID: p.UserId,
TeamID: p.TeamID,
UserID: p.UserID,
})
}
return cmds

@ -134,10 +134,12 @@ type resourcePermissionDTO struct {
IsInherited bool `json:"isInherited"`
IsServiceAccount bool `json:"isServiceAccount"`
UserID int64 `json:"userId,omitempty"`
UserUID string `json:"userUid,omitempty"`
UserLogin string `json:"userLogin,omitempty"`
UserAvatarUrl string `json:"userAvatarUrl,omitempty"`
Team string `json:"team,omitempty"`
TeamID int64 `json:"teamId,omitempty"`
TeamUID string `json:"teamUid,omitempty"`
TeamAvatarUrl string `json:"teamAvatarUrl,omitempty"`
BuiltInRole string `json:"builtInRole,omitempty"`
Actions []string `json:"actions"`
@ -191,18 +193,20 @@ func (a *api) getPermissions(c *contextmodel.ReqContext) response.Response {
for _, p := range permissions {
if permission := a.service.MapActions(p); permission != "" {
teamAvatarUrl := ""
if p.TeamId != 0 {
if p.TeamID != 0 {
teamAvatarUrl = dtos.GetGravatarUrlWithDefault(a.cfg, p.TeamEmail, p.Team)
}
dto = append(dto, resourcePermissionDTO{
ID: p.ID,
RoleName: p.RoleName,
UserID: p.UserId,
UserID: p.UserID,
UserUID: p.UserUID,
UserLogin: p.UserLogin,
UserAvatarUrl: dtos.GetGravatarUrl(a.cfg, p.UserEmail),
Team: p.Team,
TeamID: p.TeamId,
TeamID: p.TeamID,
TeamUID: p.TeamUID,
TeamAvatarUrl: teamAvatarUrl,
BuiltInRole: p.BuiltInRole,
Actions: p.Actions,

@ -35,9 +35,11 @@ type flatResourcePermission struct {
Action string
Scope string
UserId int64
UserUid string
UserLogin string
UserEmail string
TeamId int64
TeamUid string
TeamEmail string
Team string
BuiltInRole string
@ -331,10 +333,12 @@ func (s *store) getResourcePermissions(sess *db.Session, orgID int64, query GetR
userSelect := rawSelect + `
ur.user_id AS user_id,
u.login AS user_login,
u.uid AS user_uid,
u.is_service_account AS is_service_account,
u.email AS user_email,
0 AS team_id,
'' AS team,
'' AS team_uid,
'' AS team_email,
'' AS built_in_role
`
@ -342,10 +346,12 @@ func (s *store) getResourcePermissions(sess *db.Session, orgID int64, query GetR
teamSelect := rawSelect + `
0 AS user_id,
'' AS user_login,
'' AS user_uid,
` + s.sql.GetDialect().BooleanStr(false) + ` AS is_service_account,
'' AS user_email,
tr.team_id AS team_id,
t.name AS team,
t.uid AS team_uid,
t.email AS team_email,
'' AS built_in_role
`
@ -353,10 +359,12 @@ func (s *store) getResourcePermissions(sess *db.Session, orgID int64, query GetR
builtinSelect := rawSelect + `
0 AS user_id,
'' AS user_login,
'' AS user_uid,
` + s.sql.GetDialect().BooleanStr(false) + ` AS is_service_account,
'' AS user_email,
0 as team_id,
'' AS team,
'' AS team_uid,
'' AS team_email,
br.role AS built_in_role
`
@ -522,10 +530,12 @@ func flatPermissionsToResourcePermission(scope string, permissions []flatResourc
RoleName: first.RoleName,
Actions: actions,
Scope: first.Scope,
UserId: first.UserId,
UserID: first.UserId,
UserUID: first.UserUid,
UserLogin: first.UserLogin,
UserEmail: first.UserEmail,
TeamId: first.TeamId,
TeamID: first.TeamId,
TeamUID: first.TeamUid,
TeamEmail: first.TeamEmail,
Team: first.Team,
BuiltInRole: first.BuiltInRole,

@ -351,8 +351,8 @@ func TestIntegrationStore_SetResourcePermissions(t *testing.T) {
assert.Equal(t, accesscontrol.ResourcePermission{}, permissions[i])
} else {
assert.Len(t, permissions[i].Actions, len(c.Actions))
assert.Equal(t, c.TeamID, permissions[i].TeamId)
assert.Equal(t, c.User.ID, permissions[i].UserId)
assert.Equal(t, c.TeamID, permissions[i].TeamID)
assert.Equal(t, c.User.ID, permissions[i].UserID)
assert.Equal(t, c.BuiltinRole, permissions[i].BuiltInRole)
assert.Equal(t, accesscontrol.Scope(c.Resource, tt.resourceAttribute, c.ResourceID), permissions[i].Scope)
}

@ -388,10 +388,12 @@ type DashboardACLInfoDTO struct {
Updated time.Time `json:"updated"`
UserID int64 `json:"userId" xorm:"user_id"`
UserUID string `json:"userUid"`
UserLogin string `json:"userLogin"`
UserEmail string `json:"userEmail"`
UserAvatarURL string `json:"userAvatarUrl" xorm:"user_avatar_url"`
TeamID int64 `json:"teamId" xorm:"team_id"`
TeamUID string `json:"teamUid"`
TeamEmail string `json:"teamEmail"`
TeamAvatarURL string `json:"teamAvatarUrl" xorm:"team_avatar_url"`
Team string `json:"team"`

@ -143,6 +143,7 @@ type UpdateOrgUserCommand struct {
type OrgUserDTO struct {
OrgID int64 `json:"orgId" xorm:"org_id"`
UserID int64 `json:"userId" xorm:"user_id"`
UID string `json:"uid" xorm:"uid"`
Email string `json:"email"`
Name string `json:"name"`
AvatarURL string `json:"avatarUrl" xorm:"avatar_url"`

@ -602,6 +602,7 @@ func (ss *sqlStore) SearchOrgUsers(ctx context.Context, query *org.SearchOrgUser
"org_user.org_id",
"org_user.user_id",
"u.email",
"u.uid",
"u.name",
"u.login",
"org_user.role",

@ -4080,6 +4080,9 @@
"type": "integer",
"format": "int64"
},
"teamUid": {
"type": "string"
},
"title": {
"type": "string"
},
@ -4105,6 +4108,9 @@
},
"userLogin": {
"type": "string"
},
"userUid": {
"type": "string"
}
}
},
@ -6036,6 +6042,9 @@
"role": {
"type": "string"
},
"uid": {
"type": "string"
},
"userId": {
"type": "integer",
"format": "int64"
@ -8603,6 +8612,9 @@
"login": {
"type": "string"
},
"uid": {
"type": "string"
},
"userId": {
"type": "integer",
"format": "int64"
@ -8975,6 +8987,9 @@
"type": "integer",
"format": "int64"
},
"teamUid": {
"type": "string"
},
"userAvatarUrl": {
"type": "string"
},
@ -8984,6 +8999,9 @@
},
"userLogin": {
"type": "string"
},
"userUid": {
"type": "string"
}
}
},

@ -14519,6 +14519,9 @@
"type": "integer",
"format": "int64"
},
"teamUid": {
"type": "string"
},
"title": {
"type": "string"
},
@ -14544,6 +14547,9 @@
},
"userLogin": {
"type": "string"
},
"userUid": {
"type": "string"
}
}
},
@ -17815,6 +17821,9 @@
"role": {
"type": "string"
},
"uid": {
"type": "string"
},
"userId": {
"type": "integer",
"format": "int64"
@ -22171,6 +22180,9 @@
"login": {
"type": "string"
},
"uid": {
"type": "string"
},
"userId": {
"type": "integer",
"format": "int64"
@ -23173,6 +23185,9 @@
"type": "integer",
"format": "int64"
},
"teamUid": {
"type": "string"
},
"userAvatarUrl": {
"type": "string"
},
@ -23182,6 +23197,9 @@
},
"userLogin": {
"type": "string"
},
"userUid": {
"type": "string"
}
}
},

@ -26,8 +26,8 @@ export const AddPermission = ({
onCancel,
}: Props) => {
const [target, setPermissionTarget] = useState<PermissionTarget>(PermissionTarget.None);
const [teamId, setTeamId] = useState(0);
const [userId, setUserId] = useState(0);
const [teamUid, setTeamUid] = useState('');
const [userUid, setUserUid] = useState('');
const [builtInRole, setBuiltinRole] = useState('');
const [permission, setPermission] = useState('');
@ -61,9 +61,9 @@ export const AddPermission = ({
}, [permissions]);
const isValid = () =>
(target === PermissionTarget.Team && teamId > 0) ||
(target === PermissionTarget.User && userId > 0) ||
(target === PermissionTarget.ServiceAccount && userId > 0) ||
(target === PermissionTarget.Team && teamUid) ||
(target === PermissionTarget.User && userUid) ||
(target === PermissionTarget.ServiceAccount && userUid) ||
(PermissionTarget.BuiltInRole && OrgRole.hasOwnProperty(builtInRole));
return (
@ -75,7 +75,7 @@ export const AddPermission = ({
name="addPermission"
onSubmit={(event) => {
event.preventDefault();
onAdd({ userId, teamId, builtInRole, permission, target });
onAdd({ userUid, teamUid, builtInRole, permission, target });
}}
>
<Stack gap={1} direction="row">
@ -88,13 +88,13 @@ export const AddPermission = ({
width="auto"
/>
{target === PermissionTarget.User && <UserPicker onSelected={(u) => setUserId(u?.value || 0)} />}
{target === PermissionTarget.User && <UserPicker onSelected={(u) => setUserUid(u?.value?.uid || '')} />}
{target === PermissionTarget.ServiceAccount && (
<ServiceAccountPicker onSelected={(u) => setUserId(u?.value || 0)} />
<ServiceAccountPicker onSelected={(u) => setUserUid(u?.value?.uid || '')} />
)}
{target === PermissionTarget.Team && <TeamPicker onSelected={(t) => setTeamId(t.value?.id || 0)} />}
{target === PermissionTarget.Team && <TeamPicker onSelected={(t) => setTeamUid(t.value?.uid || '')} />}
{target === PermissionTarget.BuiltInRole && (
<Select

@ -74,12 +74,10 @@ export const Permissions = ({
const onAdd = (state: SetPermission) => {
let promise: Promise<void> | null = null;
if (state.target === PermissionTarget.User) {
promise = setUserPermission(resource, resourceId, state.userId!, state.permission);
} else if (state.target === PermissionTarget.ServiceAccount) {
promise = setUserPermission(resource, resourceId, state.userId!, state.permission);
if (state.target === PermissionTarget.User || state.target === PermissionTarget.ServiceAccount) {
promise = setUserPermission(resource, resourceId, state.userUid!, state.permission);
} else if (state.target === PermissionTarget.Team) {
promise = setTeamPermission(resource, resourceId, state.teamId!, state.permission);
promise = setTeamPermission(resource, resourceId, state.teamUid!, state.permission);
} else if (state.target === PermissionTarget.BuiltInRole) {
promise = setBuiltInRolePermission(resource, resourceId, state.builtInRole!, state.permission);
}
@ -91,12 +89,10 @@ export const Permissions = ({
const onRemove = (item: ResourcePermission) => {
let promise: Promise<void> | null = null;
if (item.userId) {
promise = setUserPermission(resource, resourceId, item.userId, EMPTY_PERMISSION);
} else if (item.teamId) {
promise = setTeamPermission(resource, resourceId, item.teamId, EMPTY_PERMISSION);
} else if (item.isServiceAccount && item.userId) {
promise = setUserPermission(resource, resourceId, item.userId, EMPTY_PERMISSION);
if (item.userUid) {
promise = setUserPermission(resource, resourceId, item.userUid, EMPTY_PERMISSION);
} else if (item.teamUid) {
promise = setTeamPermission(resource, resourceId, item.teamUid, EMPTY_PERMISSION);
} else if (item.builtInRole) {
promise = setBuiltInRolePermission(resource, resourceId, item.builtInRole, EMPTY_PERMISSION);
}
@ -107,15 +103,14 @@ export const Permissions = ({
};
const onChange = (item: ResourcePermission, permission: string) => {
console.log('onChange', item, permission);
if (item.permission === permission) {
return;
}
if (item.userId) {
onAdd({ permission, userId: item.userId, target: PermissionTarget.User });
} else if (item.isServiceAccount) {
onAdd({ permission, userId: item.userId, target: PermissionTarget.User });
} else if (item.teamId) {
onAdd({ permission, teamId: item.teamId, target: PermissionTarget.Team });
if (item.userUid || item.isServiceAccount) {
onAdd({ permission, userUid: item.userUid, target: PermissionTarget.User });
} else if (item.teamUid) {
onAdd({ permission, teamUid: item.teamUid, target: PermissionTarget.Team });
} else if (item.builtInRole) {
onAdd({ permission, builtInRole: item.builtInRole, target: PermissionTarget.BuiltInRole });
}
@ -259,11 +254,11 @@ const getDescription = async (resource: string): Promise<Description> => {
const getPermissions = (resource: string, resourceId: ResourceId): Promise<ResourcePermission[]> =>
getBackendSrv().get(`/api/access-control/${resource}/${resourceId}`);
const setUserPermission = (resource: string, resourceId: ResourceId, userId: number, permission: string) =>
setPermission(resource, resourceId, 'users', userId, permission);
const setUserPermission = (resource: string, resourceId: ResourceId, userUid: string, permission: string) =>
setPermission(resource, resourceId, 'users', userUid, permission);
const setTeamPermission = (resource: string, resourceId: ResourceId, teamId: number, permission: string) =>
setPermission(resource, resourceId, 'teams', teamId, permission);
const setTeamPermission = (resource: string, resourceId: ResourceId, teamUid: string, permission: string) =>
setPermission(resource, resourceId, 'teams', teamUid, permission);
const setBuiltInRolePermission = (resource: string, resourceId: ResourceId, builtInRole: string, permission: string) =>
setPermission(resource, resourceId, 'builtInRoles', builtInRole, permission);

@ -7,10 +7,12 @@ export type ResourcePermission = {
isInherited: boolean;
isServiceAccount: boolean;
userId?: number;
userUid?: string;
userLogin?: string;
userAvatarUrl?: string;
team?: string;
teamId?: number;
teamUid?: string;
teamAvatarUrl?: string;
builtInRole?: string;
actions: AccessControlAction[];
@ -20,8 +22,8 @@ export type ResourcePermission = {
};
export type SetPermission = {
userId?: number;
teamId?: number;
userUid?: string;
teamUid?: string;
builtInRole?: string;
permission: string;
target: PermissionTarget;

@ -10,6 +10,7 @@ const props = {
user: {
login: 'admin',
email: 'email@example.com',
uid: 'uid',
avatarUrl: 'avatarURL',
lastSeenAt: 'lastSeenAt',
lastSeenAtAge: 'lastSeenAtAge',

@ -7,7 +7,7 @@ import { AsyncSelect } from '@grafana/ui';
import { ServiceAccountDTO, ServiceAccountsState } from 'app/types';
export interface Props {
onSelected: (user: SelectableValue<ServiceAccountDTO['id']>) => void;
onSelected: (user: SelectableValue<ServiceAccountDTO>) => void;
className?: string;
inputId?: string;
}
@ -42,7 +42,8 @@ export class ServiceAccountPicker extends Component<Props, State> {
.then((result: ServiceAccountsState) => {
return result.serviceAccounts.map((sa) => ({
id: sa.id,
value: sa.id,
uid: sa.uid,
value: sa,
label: sa.login,
imgUrl: sa.avatarUrl,
login: sa.login,

@ -7,7 +7,7 @@ import { AsyncSelect } from '@grafana/ui';
import { OrgUser } from 'app/types';
export interface Props {
onSelected: (user: SelectableValue<OrgUser['userId']>) => void;
onSelected: (user: SelectableValue<OrgUser>) => void;
className?: string;
inputId?: string;
}
@ -42,7 +42,8 @@ export class UserPicker extends Component<Props, State> {
.then((result: OrgUser[]) => {
return result.map((user) => ({
id: user.userId,
value: user.userId,
uid: user.uid,
value: user,
label: user.login,
imgUrl: user.avatarUrl,
login: user.login,

@ -25,6 +25,7 @@ export const getMockUsers = (amount: number): OrgUser[] => {
lastSeenAtAge: '',
login: `user-${i}`,
orgId: 1,
uid: `uid-${i}`,
role: 'Admin',
userId: i,
isDisabled: false,

@ -15,6 +15,7 @@ export interface OrgUser extends WithAccessControlMetadata {
// RBAC roles
roles?: Role[];
userId: number;
uid: string;
isDisabled: boolean;
authLabels?: string[];
isExternallySynced?: boolean;

@ -4480,6 +4480,9 @@
"format": "int64",
"type": "integer"
},
"teamUid": {
"type": "string"
},
"title": {
"type": "string"
},
@ -4505,6 +4508,9 @@
},
"userLogin": {
"type": "string"
},
"userUid": {
"type": "string"
}
},
"type": "object"
@ -7776,6 +7782,9 @@
"role": {
"type": "string"
},
"uid": {
"type": "string"
},
"userId": {
"format": "int64",
"type": "integer"
@ -12131,6 +12140,9 @@
"login": {
"type": "string"
},
"uid": {
"type": "string"
},
"userId": {
"format": "int64",
"type": "integer"
@ -13130,6 +13142,9 @@
"format": "int64",
"type": "integer"
},
"teamUid": {
"type": "string"
},
"userAvatarUrl": {
"type": "string"
},
@ -13139,6 +13154,9 @@
},
"userLogin": {
"type": "string"
},
"userUid": {
"type": "string"
}
},
"type": "object"

Loading…
Cancel
Save