diff --git a/pkg/api/accesscontrol.go b/pkg/api/accesscontrol.go index 1a6895109c1..f24529b06ee 100644 --- a/pkg/api/accesscontrol.go +++ b/pkg/api/accesscontrol.go @@ -443,13 +443,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( diff --git a/pkg/api/api.go b/pkg/api/api.go index a9adb3f9acc..f6ee549b46f 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -59,7 +59,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) @@ -235,9 +235,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)) diff --git a/pkg/api/org_invite.go b/pkg/api/org_invite.go index c06d06ae4a5..0071e3f4275 100644 --- a/pkg/api/org_invite.go +++ b/pkg/api/org_invite.go @@ -64,15 +64,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) } @@ -83,6 +74,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) diff --git a/pkg/api/org_invite_test.go b/pkg/api/org_invite_test.go index cdd338a5a49..319a11b1167 100644 --- a/pkg/api/org_invite_test.go +++ b/pkg/api/org_invite_test.go @@ -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) + `"}`, }, { diff --git a/pkg/api/org_users_test.go b/pkg/api/org_users_test.go index 5479ead2df5..3399af60a69 100644 --- a/pkg/api/org_users_test.go +++ b/pkg/api/org_users_test.go @@ -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) + `"}`, }, { diff --git a/public/app/features/users/UsersActionBar.tsx b/public/app/features/users/UsersActionBar.tsx index b36a378e728..eaa1f159077 100644 --- a/public/app/features/users/UsersActionBar.tsx +++ b/public/app/features/users/UsersActionBar.tsx @@ -37,9 +37,7 @@ export class UsersActionBar extends PureComponent { { 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 (