mirror of
https://github.com/grafana/grafana.git
synced 2025-01-17 12:03:26 -06:00
Access Control: Allow org admins to invite new users (#52894)
* allow org admins to invite new users to Grafana * doc updates * fix test
This commit is contained in:
parent
6ecc420534
commit
0d324e931d
@ -77,7 +77,7 @@ The following list contains role-based access control actions.
|
||||
| `licensing:read` | n/a | Read licensing information. |
|
||||
| `licensing:write` | n/a | Update the license token. |
|
||||
| `org.users:write` | `users:*` <br> `users:id:*` | Update the organization role (`Viewer`, `Editor`, or `Admin`) of a user. |
|
||||
| `org.users:add` | `users:*` | Add a user to an organization. |
|
||||
| `org.users:add` | `users:*` | Add a user to an organization or invite a new user to an organization. |
|
||||
| `org.users:read` | `users:*` <br> `users:id:*` | Get user profiles within an organization. |
|
||||
| `org.users:remove` | `users:*` <br> `users:id:*` | Remove a user from an organization. |
|
||||
| `org:create` | n/a | Create an organization. |
|
||||
|
@ -61,7 +61,7 @@ The following tables list permissions associated with basic and fixed roles.
|
||||
| `fixed:licensing:reader` | `licensing:read`<br>`licensing.reports:read` | Read licensing information and licensing reports. |
|
||||
| `fixed:licensing:writer` | All permissions from `fixed:licensing:viewer` and <br>`licensing:write`<br>`licensing:delete` | Read licensing information and licensing reports, update and delete the license token. |
|
||||
| `fixed:org.users:reader` | `org.users:read` | Read users within a single organization. |
|
||||
| `fixed:org.users:writer` | All permissions from `fixed:org.users:reader` and <br>`org.users:add`<br>`org.users:remove`<br>`org.users:write` | Within a single organization, add a user, invite a user, read information about a user and their role, remove a user from that organization, or change the role of a user. |
|
||||
| `fixed:org.users:writer` | All permissions from `fixed:org.users:reader` and <br>`org.users:add`<br>`org.users:remove`<br>`org.users:write` | Within a single organization, add a user, invite a new user, read information about a user and their role, remove a user from that organization, or change the role of a user. |
|
||||
| `fixed:organization:maintainer` | All permissions from `fixed:organization:reader` and <br> `orgs:write`<br>`orgs:create`<br>`orgs:delete`<br>`orgs.quotas:write` | Create, read, write, or delete an organization. Read or write its quotas. This role needs to be assigned globally. |
|
||||
| `fixed:organization:reader` | `orgs:read`<br>`orgs.quotas:read` | Read an organization and its quotas. |
|
||||
| `fixed:organization:writer` | All permissions from `fixed:organization:reader` and <br> `orgs:write`<br>`orgs.preferences:read`<br>`orgs.preferences:write` | Read an organization, its quotas, or its preferences. Update organization properties, or its preferences. |
|
||||
|
@ -429,13 +429,6 @@ var orgsCreateAccessEvaluator = ac.EvalAll(
|
||||
ac.EvalPermission(ActionOrgsCreate),
|
||||
)
|
||||
|
||||
// usersInviteEvaluator is used to protect the "Configuration > Users > Invite" page access
|
||||
// accessible to org admins and server admins by default
|
||||
var usersInviteEvaluator = ac.EvalAny(
|
||||
ac.EvalPermission(ac.ActionUsersCreate),
|
||||
ac.EvalPermission(ac.ActionOrgUsersAdd),
|
||||
)
|
||||
|
||||
// teamsAccessEvaluator is used to protect the "Configuration > Teams" page access
|
||||
// grants access to a user when they can either create teams or can read and update a team
|
||||
var teamsAccessEvaluator = ac.EvalAny(
|
||||
|
@ -90,7 +90,7 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
r.Get("/datasources/edit/*", authorize(reqOrgAdmin, datasources.EditPageAccess), hs.Index)
|
||||
r.Get("/org/users", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionOrgUsersRead)), hs.Index)
|
||||
r.Get("/org/users/new", reqOrgAdmin, hs.Index)
|
||||
r.Get("/org/users/invite", authorize(reqOrgAdmin, usersInviteEvaluator), hs.Index)
|
||||
r.Get("/org/users/invite", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionOrgUsersAdd)), hs.Index)
|
||||
r.Get("/org/teams", authorize(reqCanAccessTeams, ac.EvalPermission(ac.ActionTeamsRead)), hs.Index)
|
||||
r.Get("/org/teams/edit/*", authorize(reqCanAccessTeams, teamsEditAccessEvaluator), hs.Index)
|
||||
r.Get("/org/teams/new", authorize(reqCanAccessTeams, ac.EvalPermission(ac.ActionTeamsCreate)), hs.Index)
|
||||
@ -281,9 +281,9 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
orgRoute.Delete("/users/:userId", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionOrgUsersRemove, userIDScope)), routing.Wrap(hs.RemoveOrgUserForCurrentOrg))
|
||||
|
||||
// invites
|
||||
orgRoute.Get("/invites", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionUsersCreate)), routing.Wrap(hs.GetPendingOrgInvites))
|
||||
orgRoute.Post("/invites", authorize(reqOrgAdmin, usersInviteEvaluator), quota("user"), routing.Wrap(hs.AddOrgInvite))
|
||||
orgRoute.Patch("/invites/:code/revoke", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionUsersCreate)), routing.Wrap(hs.RevokeInvite))
|
||||
orgRoute.Get("/invites", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionOrgUsersAdd)), routing.Wrap(hs.GetPendingOrgInvites))
|
||||
orgRoute.Post("/invites", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionOrgUsersAdd)), quota("user"), routing.Wrap(hs.AddOrgInvite))
|
||||
orgRoute.Patch("/invites/:code/revoke", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionOrgUsersAdd)), routing.Wrap(hs.RevokeInvite))
|
||||
|
||||
// prefs
|
||||
orgRoute.Get("/preferences", authorize(reqOrgAdmin, ac.EvalPermission(ActionOrgsPreferencesRead)), routing.Wrap(hs.GetOrgPreferences))
|
||||
|
@ -84,15 +84,6 @@ func (hs *HTTPServer) AddOrgInvite(c *models.ReqContext) response.Response {
|
||||
return hs.inviteExistingUserToOrg(c, userQuery.Result, &inviteDto)
|
||||
}
|
||||
|
||||
// Evaluate permissions for inviting a new user to Grafana
|
||||
hasAccess, err := hs.AccessControl.Evaluate(c.Req.Context(), c.SignedInUser, ac.EvalPermission(ac.ActionUsersCreate))
|
||||
if err != nil {
|
||||
return response.Error(http.StatusInternalServerError, "Failed to evaluate permissions", err)
|
||||
}
|
||||
if !hasAccess {
|
||||
return response.Error(http.StatusForbidden, "Permission denied: not permitted to create a new user", err)
|
||||
}
|
||||
|
||||
if setting.DisableLoginForm {
|
||||
return response.Error(400, "Cannot invite when login is disabled.", nil)
|
||||
}
|
||||
@ -103,6 +94,7 @@ func (hs *HTTPServer) AddOrgInvite(c *models.ReqContext) response.Response {
|
||||
cmd.Name = inviteDto.Name
|
||||
cmd.Status = models.TmpUserInvitePending
|
||||
cmd.InvitedByUserId = c.UserId
|
||||
var err error
|
||||
cmd.Code, err = util.GetRandomString(30)
|
||||
if err != nil {
|
||||
return response.Error(500, "Could not generate random string", err)
|
||||
|
@ -23,7 +23,7 @@ func TestOrgInvitesAPIEndpointAccess(t *testing.T) {
|
||||
tests := []accessControlTestCase2{
|
||||
{
|
||||
expectedCode: http.StatusOK,
|
||||
desc: "org viewer with the correct permissions can invite and existing user to his org",
|
||||
desc: "org viewer with the correct permissions can invite an existing user to his org",
|
||||
url: "/api/org/invites",
|
||||
method: http.MethodPost,
|
||||
permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionOrgUsersAdd, Scope: accesscontrol.ScopeUsersAll}},
|
||||
@ -31,7 +31,7 @@ func TestOrgInvitesAPIEndpointAccess(t *testing.T) {
|
||||
},
|
||||
{
|
||||
expectedCode: http.StatusForbidden,
|
||||
desc: "org viewer with missing permissions cannot invite and existing user to his org",
|
||||
desc: "org viewer with missing permissions cannot invite an existing user to his org",
|
||||
url: "/api/org/invites",
|
||||
method: http.MethodPost,
|
||||
permissions: []accesscontrol.Permission{},
|
||||
@ -39,26 +39,18 @@ func TestOrgInvitesAPIEndpointAccess(t *testing.T) {
|
||||
},
|
||||
{
|
||||
expectedCode: http.StatusForbidden,
|
||||
desc: "org viewer with the wrong scope cannot invite and existing user to his org",
|
||||
desc: "org viewer with the wrong scope cannot invite an existing user to his org",
|
||||
url: "/api/org/invites",
|
||||
method: http.MethodPost,
|
||||
permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionOrgUsersAdd, Scope: "users:id:100"}},
|
||||
input: `{"loginOrEmail": "` + testAdminOrg2.Login + `", "role": "` + string(models.ROLE_VIEWER) + `"}`,
|
||||
},
|
||||
{
|
||||
expectedCode: http.StatusForbidden,
|
||||
desc: "org viewer with user add permission cannot invite a new user to his org",
|
||||
url: "/api/org/invites",
|
||||
method: http.MethodPost,
|
||||
permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionOrgUsersAdd, Scope: accesscontrol.ScopeUsersAll}},
|
||||
input: `{"loginOrEmail": "new user", "role": "` + string(models.ROLE_VIEWER) + `"}`,
|
||||
},
|
||||
{
|
||||
expectedCode: http.StatusOK,
|
||||
desc: "org viewer with the correct permissions can invite a new user to his org",
|
||||
url: "/api/org/invites",
|
||||
method: http.MethodPost,
|
||||
permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionUsersCreate}},
|
||||
permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionOrgUsersAdd, Scope: accesscontrol.ScopeUsersAll}},
|
||||
input: `{"loginOrEmail": "new user", "role": "` + string(models.ROLE_VIEWER) + `"}`,
|
||||
},
|
||||
{
|
||||
|
@ -650,7 +650,7 @@ func TestOrgUsersAPIEndpointWithSetPerms_AccessControl(t *testing.T) {
|
||||
desc: "org viewer with the correct permissions can invite a user as a viewer in his org",
|
||||
url: "/api/org/invites",
|
||||
method: http.MethodPost,
|
||||
permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionUsersCreate}},
|
||||
permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionOrgUsersAdd, Scope: accesscontrol.ScopeUsersAll}},
|
||||
input: `{"loginOrEmail": "newUserEmail@test.com", "sendEmail": false, "role": "` + string(models.ROLE_VIEWER) + `"}`,
|
||||
},
|
||||
{
|
||||
|
@ -37,9 +37,7 @@ export class UsersActionBar extends PureComponent<Props> {
|
||||
{ label: 'Users', value: 'users' },
|
||||
{ label: `Pending Invites (${pendingInvitesCount})`, value: 'invites' },
|
||||
];
|
||||
const canAddToOrg: boolean =
|
||||
contextSrv.hasAccess(AccessControlAction.UsersCreate, canInvite) ||
|
||||
contextSrv.hasAccess(AccessControlAction.OrgUsersAdd, canInvite);
|
||||
const canAddToOrg: boolean = contextSrv.hasAccess(AccessControlAction.OrgUsersAdd, canInvite);
|
||||
|
||||
return (
|
||||
<div className="page-action-bar" data-testid="users-action-bar">
|
||||
|
Loading…
Reference in New Issue
Block a user