Access control: Allow users with permission to update team, dashboard and folder permissions to list users in OSS (#48275)

* Remove banner when missing permissions to list users

* For OSS allow users to list other users if they have permissions to
write either team, dashboard or folder permissions
This commit is contained in:
Karl Persson
2022-05-06 10:31:53 +02:00
committed by GitHub
parent 9826a694a8
commit 817cf52744
9 changed files with 28 additions and 51 deletions

View File

@@ -249,7 +249,19 @@ func (hs *HTTPServer) registerRoutes() {
// current org without requirement of user to be org admin // current org without requirement of user to be org admin
apiRoute.Group("/org", func(orgRoute routing.RouteRegister) { apiRoute.Group("/org", func(orgRoute routing.RouteRegister) {
orgRoute.Get("/users/lookup", authorize(reqOrgAdminFolderAdminOrTeamAdmin, ac.EvalPermission(ac.ActionOrgUsersRead)), routing.Wrap(hs.GetOrgUsersForCurrentOrgLookup)) lookupEvaluator := func() ac.Evaluator {
if hs.Cfg.IsEnterprise {
return ac.EvalPermission(ac.ActionOrgUsersRead)
}
// For oss we allow users with access to update permissions on either folders, teams or dashboards to perform the lookup
return ac.EvalAny(
ac.EvalPermission(ac.ActionOrgUsersRead),
ac.EvalPermission(ac.ActionTeamsPermissionsWrite),
ac.EvalPermission(dashboards.ActionDashboardsPermissionsWrite),
ac.EvalPermission(dashboards.ActionFoldersPermissionsWrite),
)
}
orgRoute.Get("/users/lookup", authorize(reqOrgAdminFolderAdminOrTeamAdmin, lookupEvaluator()), routing.Wrap(hs.GetOrgUsersForCurrentOrgLookup))
}) })
// create new org // create new org

View File

@@ -109,7 +109,7 @@ func (ss *SQLStore) GetOrgUsers(ctx context.Context, query *models.GetOrgUsersQu
whereConditions = append(whereConditions, fmt.Sprintf("%s.is_service_account = ?", ss.Dialect.Quote("user"))) whereConditions = append(whereConditions, fmt.Sprintf("%s.is_service_account = ?", ss.Dialect.Quote("user")))
whereParams = append(whereParams, ss.Dialect.BooleanStr(false)) whereParams = append(whereParams, ss.Dialect.BooleanStr(false))
if !accesscontrol.IsDisabled(ss.Cfg) && query.User != nil { if ss.Cfg.IsEnterprise && !accesscontrol.IsDisabled(ss.Cfg) && query.User != nil {
acFilter, err := accesscontrol.Filter(query.User, "org_user.user_id", "users:id:", accesscontrol.ActionOrgUsersRead) acFilter, err := accesscontrol.Filter(query.User, "org_user.user_id", "users:id:", accesscontrol.ActionOrgUsersRead)
if err != nil { if err != nil {
return err return err

View File

@@ -62,6 +62,10 @@ func TestSQLStore_GetOrgUsers(t *testing.T) {
} }
store := InitTestDB(t, InitTestDBOpt{FeatureFlags: []string{featuremgmt.FlagAccesscontrol}}) store := InitTestDB(t, InitTestDBOpt{FeatureFlags: []string{featuremgmt.FlagAccesscontrol}})
store.Cfg.IsEnterprise = true
defer func() {
store.Cfg.IsEnterprise = false
}()
seedOrgUsers(t, store, 10) seedOrgUsers(t, store, 10)
for _, tt := range tests { for _, tt := range tests {

View File

@@ -1,6 +1,6 @@
import React, { useEffect, useMemo, useState } from 'react'; import React, { useEffect, useMemo, useState } from 'react';
import { Alert, Button, Form, HorizontalGroup, Input, Select } from '@grafana/ui'; import { Button, Form, HorizontalGroup, Select } from '@grafana/ui';
import { CloseButton } from 'app/core/components/CloseButton/CloseButton'; import { CloseButton } from 'app/core/components/CloseButton/CloseButton';
import { TeamPicker } from 'app/core/components/Select/TeamPicker'; import { TeamPicker } from 'app/core/components/Select/TeamPicker';
import { UserPicker } from 'app/core/components/Select/UserPicker'; import { UserPicker } from 'app/core/components/Select/UserPicker';
@@ -12,20 +12,12 @@ export interface Props {
title?: string; title?: string;
permissions: string[]; permissions: string[];
assignments: Assignments; assignments: Assignments;
canListUsers: boolean;
onCancel: () => void; onCancel: () => void;
onAdd: (state: SetPermission) => void; onAdd: (state: SetPermission) => void;
} }
export const AddPermission = ({ export const AddPermission = ({ title = 'Add Permission For', permissions, assignments, onAdd, onCancel }: Props) => {
title = 'Add Permission For', const [target, setPermissionTarget] = useState<PermissionTarget>(PermissionTarget.None);
permissions,
assignments,
canListUsers,
onAdd,
onCancel,
}: Props) => {
const [target, setPermissionTarget] = useState<PermissionTarget>(PermissionTarget.User);
const [teamId, setTeamId] = useState(0); const [teamId, setTeamId] = useState(0);
const [userId, setUserId] = useState(0); const [userId, setUserId] = useState(0);
const [builtInRole, setBuiltinRole] = useState(''); const [builtInRole, setBuiltinRole] = useState('');
@@ -33,8 +25,8 @@ export const AddPermission = ({
const targetOptions = useMemo(() => { const targetOptions = useMemo(() => {
const options = []; const options = [];
if (assignments.users && canListUsers) { if (assignments.users) {
options.push({ value: PermissionTarget.User, label: 'User', isDisabled: false }); options.push({ value: PermissionTarget.User, label: 'User' });
} }
if (assignments.teams) { if (assignments.teams) {
options.push({ value: PermissionTarget.Team, label: 'Team' }); options.push({ value: PermissionTarget.Team, label: 'Team' });
@@ -43,7 +35,7 @@ export const AddPermission = ({
options.push({ value: PermissionTarget.BuiltInRole, label: 'Role' }); options.push({ value: PermissionTarget.BuiltInRole, label: 'Role' });
} }
return options; return options;
}, [assignments, canListUsers]); }, [assignments]);
useEffect(() => { useEffect(() => {
if (permissions.length > 0) { if (permissions.length > 0) {
@@ -56,22 +48,11 @@ export const AddPermission = ({
(target === PermissionTarget.User && userId > 0) || (target === PermissionTarget.User && userId > 0) ||
(PermissionTarget.BuiltInRole && OrgRole.hasOwnProperty(builtInRole)); (PermissionTarget.BuiltInRole && OrgRole.hasOwnProperty(builtInRole));
const renderMissingListUserRights = () => {
return (
<Alert severity="info" title="Missing permission">
You are missing the permission to list users (org.users:read). Please contact your administrator to get this
resolved.
</Alert>
);
};
return ( return (
<div className="cta-form" aria-label="Permissions slider"> <div className="cta-form" aria-label="Permissions slider">
<CloseButton onClick={onCancel} /> <CloseButton onClick={onCancel} />
<h5>{title}</h5> <h5>{title}</h5>
{target === PermissionTarget.User && !canListUsers && renderMissingListUserRights()}
<Form <Form
name="addPermission" name="addPermission"
maxWidth="none" maxWidth="none"
@@ -87,10 +68,9 @@ export const AddPermission = ({
disabled={targetOptions.length === 0} disabled={targetOptions.length === 0}
/> />
{target === PermissionTarget.User && canListUsers && ( {target === PermissionTarget.User && (
<UserPicker onSelected={(u) => setUserId(u.value || 0)} className={'width-20'} /> <UserPicker onSelected={(u) => setUserId(u.value || 0)} className={'width-20'} />
)} )}
{target === PermissionTarget.User && !canListUsers && <Input disabled={true} className={'width-20'} />}
{target === PermissionTarget.Team && ( {target === PermissionTarget.Team && (
<TeamPicker onSelected={(t) => setTeamId(t.value?.id || 0)} className={'width-20'} /> <TeamPicker onSelected={(t) => setTeamId(t.value?.id || 0)} className={'width-20'} />

View File

@@ -29,8 +29,6 @@ export type Props = {
addPermissionTitle?: string; addPermissionTitle?: string;
resource: string; resource: string;
resourceId: ResourceId; resourceId: ResourceId;
canListUsers: boolean;
canSetPermissions: boolean; canSetPermissions: boolean;
}; };
@@ -39,7 +37,6 @@ export const Permissions = ({
buttonLabel = 'Add a permission', buttonLabel = 'Add a permission',
resource, resource,
resourceId, resourceId,
canListUsers,
canSetPermissions, canSetPermissions,
addPermissionTitle, addPermissionTitle,
}: Props) => { }: Props) => {
@@ -145,7 +142,6 @@ export const Permissions = ({
onAdd={onAdd} onAdd={onAdd}
permissions={desc.permissions} permissions={desc.permissions}
assignments={desc.assignments} assignments={desc.assignments}
canListUsers={canListUsers}
onCancel={() => setIsAdding(false)} onCancel={() => setIsAdding(false)}
/> />
</SlideDown> </SlideDown>

View File

@@ -22,6 +22,7 @@ export type SetPermission = {
}; };
export enum PermissionTarget { export enum PermissionTarget {
None = 'None',
Team = 'Team', Team = 'Team',
User = 'User', User = 'User',
BuiltInRole = 'builtInRole', BuiltInRole = 'builtInRole',

View File

@@ -11,15 +11,7 @@ interface Props {
} }
export const AccessControlDashboardPermissions = ({ dashboard }: Props) => { export const AccessControlDashboardPermissions = ({ dashboard }: Props) => {
const canListUsers = contextSrv.hasPermission(AccessControlAction.OrgUsersRead);
const canSetPermissions = contextSrv.hasPermission(AccessControlAction.DashboardsPermissionsWrite); const canSetPermissions = contextSrv.hasPermission(AccessControlAction.DashboardsPermissionsWrite);
return ( return <Permissions resource={'dashboards'} resourceId={dashboard.uid} canSetPermissions={canSetPermissions} />;
<Permissions
resource={'dashboards'}
resourceId={dashboard.uid}
canListUsers={canListUsers}
canSetPermissions={canSetPermissions}
/>
);
}; };

View File

@@ -33,18 +33,12 @@ export const AccessControlFolderPermissions = ({ uid, getFolderByUid, navModel }
getFolderByUid(uid); getFolderByUid(uid);
}, [getFolderByUid, uid]); }, [getFolderByUid, uid]);
const canListUsers = contextSrv.hasPermission(AccessControlAction.OrgUsersRead);
const canSetPermissions = contextSrv.hasPermission(AccessControlAction.FoldersPermissionsWrite); const canSetPermissions = contextSrv.hasPermission(AccessControlAction.FoldersPermissionsWrite);
return ( return (
<Page navModel={navModel}> <Page navModel={navModel}>
<Page.Contents> <Page.Contents>
<Permissions <Permissions resource="folders" resourceId={uid} canSetPermissions={canSetPermissions} />
resource="folders"
resourceId={uid}
canListUsers={canListUsers}
canSetPermissions={canSetPermissions}
/>
</Page.Contents> </Page.Contents>
</Page> </Page>
); );

View File

@@ -11,7 +11,6 @@ type TeamPermissionsProps = {
// TeamPermissions component replaces TeamMembers component when the accesscontrol feature flag is set // TeamPermissions component replaces TeamMembers component when the accesscontrol feature flag is set
const TeamPermissions = (props: TeamPermissionsProps) => { const TeamPermissions = (props: TeamPermissionsProps) => {
const canListUsers = contextSrv.hasPermission(AccessControlAction.OrgUsersRead);
const canSetPermissions = contextSrv.hasPermissionInMetadata( const canSetPermissions = contextSrv.hasPermissionInMetadata(
AccessControlAction.ActionTeamsPermissionsWrite, AccessControlAction.ActionTeamsPermissionsWrite,
props.team props.team
@@ -24,7 +23,6 @@ const TeamPermissions = (props: TeamPermissionsProps) => {
buttonLabel="Add member" buttonLabel="Add member"
resource="teams" resource="teams"
resourceId={props.team.id} resourceId={props.team.id}
canListUsers={canListUsers}
canSetPermissions={canSetPermissions} canSetPermissions={canSetPermissions}
/> />
); );