mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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:
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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'} />
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user