Access Control: Allow org admins to invite new users (#55585)
* backport change to allow org admins to invite new users to org * remove docs from backport
This commit is contained in:
@@ -443,13 +443,6 @@ var orgsCreateAccessEvaluator = ac.EvalAll(
|
|||||||
ac.EvalPermission(ActionOrgsCreate),
|
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
|
// 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
|
// grants access to a user when they can either create teams or can read and update a team
|
||||||
var teamsAccessEvaluator = ac.EvalAny(
|
var teamsAccessEvaluator = ac.EvalAny(
|
||||||
|
|||||||
+4
-4
@@ -59,7 +59,7 @@ func (hs *HTTPServer) registerRoutes() {
|
|||||||
r.Get("/datasources/edit/*", authorize(reqOrgAdmin, datasources.EditPageAccess), hs.Index)
|
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", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionOrgUsersRead)), hs.Index)
|
||||||
r.Get("/org/users/new", reqOrgAdmin, 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", authorize(reqCanAccessTeams, ac.EvalPermission(ac.ActionTeamsRead)), hs.Index)
|
||||||
r.Get("/org/teams/edit/*", authorize(reqCanAccessTeams, teamsEditAccessEvaluator), 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)
|
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))
|
orgRoute.Delete("/users/:userId", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionOrgUsersRemove, userIDScope)), routing.Wrap(hs.RemoveOrgUserForCurrentOrg))
|
||||||
|
|
||||||
// invites
|
// invites
|
||||||
orgRoute.Get("/invites", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionUsersCreate)), routing.Wrap(hs.GetPendingOrgInvites))
|
orgRoute.Get("/invites", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionOrgUsersAdd)), routing.Wrap(hs.GetPendingOrgInvites))
|
||||||
orgRoute.Post("/invites", authorize(reqOrgAdmin, usersInviteEvaluator), quota("user"), routing.Wrap(hs.AddOrgInvite))
|
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.ActionUsersCreate)), routing.Wrap(hs.RevokeInvite))
|
orgRoute.Patch("/invites/:code/revoke", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionOrgUsersAdd)), routing.Wrap(hs.RevokeInvite))
|
||||||
|
|
||||||
// prefs
|
// prefs
|
||||||
orgRoute.Get("/preferences", authorize(reqOrgAdmin, ac.EvalPermission(ActionOrgsPreferencesRead)), routing.Wrap(hs.GetOrgPreferences))
|
orgRoute.Get("/preferences", authorize(reqOrgAdmin, ac.EvalPermission(ActionOrgsPreferencesRead)), routing.Wrap(hs.GetOrgPreferences))
|
||||||
|
|||||||
@@ -64,15 +64,6 @@ func (hs *HTTPServer) AddOrgInvite(c *models.ReqContext) response.Response {
|
|||||||
return hs.inviteExistingUserToOrg(c, userQuery.Result, &inviteDto)
|
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 {
|
if setting.DisableLoginForm {
|
||||||
return response.Error(400, "Cannot invite when login is disabled.", nil)
|
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.Name = inviteDto.Name
|
||||||
cmd.Status = models.TmpUserInvitePending
|
cmd.Status = models.TmpUserInvitePending
|
||||||
cmd.InvitedByUserId = c.UserId
|
cmd.InvitedByUserId = c.UserId
|
||||||
|
var err error
|
||||||
cmd.Code, err = util.GetRandomString(30)
|
cmd.Code, err = util.GetRandomString(30)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return response.Error(500, "Could not generate random string", err)
|
return response.Error(500, "Could not generate random string", err)
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ func TestOrgInvitesAPIEndpointAccess(t *testing.T) {
|
|||||||
tests := []accessControlTestCase2{
|
tests := []accessControlTestCase2{
|
||||||
{
|
{
|
||||||
expectedCode: http.StatusOK,
|
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",
|
url: "/api/org/invites",
|
||||||
method: http.MethodPost,
|
method: http.MethodPost,
|
||||||
permissions: []*accesscontrol.Permission{{Action: accesscontrol.ActionOrgUsersAdd, Scope: accesscontrol.ScopeUsersAll}},
|
permissions: []*accesscontrol.Permission{{Action: accesscontrol.ActionOrgUsersAdd, Scope: accesscontrol.ScopeUsersAll}},
|
||||||
@@ -31,7 +31,7 @@ func TestOrgInvitesAPIEndpointAccess(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
expectedCode: http.StatusForbidden,
|
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",
|
url: "/api/org/invites",
|
||||||
method: http.MethodPost,
|
method: http.MethodPost,
|
||||||
permissions: []*accesscontrol.Permission{},
|
permissions: []*accesscontrol.Permission{},
|
||||||
@@ -39,26 +39,18 @@ func TestOrgInvitesAPIEndpointAccess(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
expectedCode: http.StatusForbidden,
|
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",
|
url: "/api/org/invites",
|
||||||
method: http.MethodPost,
|
method: http.MethodPost,
|
||||||
permissions: []*accesscontrol.Permission{{Action: accesscontrol.ActionOrgUsersAdd, Scope: "users:id:100"}},
|
permissions: []*accesscontrol.Permission{{Action: accesscontrol.ActionOrgUsersAdd, Scope: "users:id:100"}},
|
||||||
input: `{"loginOrEmail": "` + testAdminOrg2.Login + `", "role": "` + string(models.ROLE_VIEWER) + `"}`,
|
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,
|
expectedCode: http.StatusOK,
|
||||||
desc: "org viewer with the correct permissions can invite a new user to his org",
|
desc: "org viewer with the correct permissions can invite a new user to his org",
|
||||||
url: "/api/org/invites",
|
url: "/api/org/invites",
|
||||||
method: http.MethodPost,
|
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) + `"}`,
|
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",
|
desc: "org viewer with the correct permissions can invite a user as a viewer in his org",
|
||||||
url: "/api/org/invites",
|
url: "/api/org/invites",
|
||||||
method: http.MethodPost,
|
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) + `"}`,
|
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: 'Users', value: 'users' },
|
||||||
{ label: `Pending Invites (${pendingInvitesCount})`, value: 'invites' },
|
{ label: `Pending Invites (${pendingInvitesCount})`, value: 'invites' },
|
||||||
];
|
];
|
||||||
const canAddToOrg: boolean =
|
const canAddToOrg: boolean = contextSrv.hasAccess(AccessControlAction.OrgUsersAdd, canInvite);
|
||||||
contextSrv.hasAccess(AccessControlAction.UsersCreate, canInvite) ||
|
|
||||||
contextSrv.hasAccess(AccessControlAction.OrgUsersAdd, canInvite);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="page-action-bar">
|
<div className="page-action-bar">
|
||||||
|
|||||||
Reference in New Issue
Block a user