This commit is contained in:
Ieva
2022-07-08 15:28:41 +01:00
committed by GitHub
parent f1f6feee8b
commit 0ad8a8fc4f
6 changed files with 121 additions and 7 deletions
+7
View File
@@ -443,6 +443,13 @@ 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(
+2 -2
View File
@@ -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, ac.EvalPermission(ac.ActionUsersCreate)), hs.Index)
r.Get("/org/users/invite", authorize(reqOrgAdmin, usersInviteEvaluator), 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)
@@ -236,7 +236,7 @@ func (hs *HTTPServer) registerRoutes() {
// invites
orgRoute.Get("/invites", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionUsersCreate)), routing.Wrap(hs.GetPendingOrgInvites))
orgRoute.Post("/invites", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionUsersCreate)), quota("user"), routing.Wrap(hs.AddOrgInvite))
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))
// prefs
+20 -1
View File
@@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"net/http"
"strconv"
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/api/response"
@@ -12,6 +13,7 @@ import (
"github.com/grafana/grafana/pkg/events"
"github.com/grafana/grafana/pkg/infra/metrics"
"github.com/grafana/grafana/pkg/models"
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
"github.com/grafana/grafana/pkg/web"
@@ -50,9 +52,27 @@ func (hs *HTTPServer) AddOrgInvite(c *models.ReqContext) response.Response {
return response.Error(500, "Failed to query db for existing user check", err)
}
} else {
// Evaluate permissions for adding an existing user to the organization
userIDScope := ac.Scope("users", "id", strconv.Itoa(int(userQuery.Result.Id)))
hasAccess, err := hs.AccessControl.Evaluate(c.Req.Context(), c.SignedInUser, ac.EvalPermission(ac.ActionOrgUsersAdd, userIDScope))
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 add an existing user to this organisation", err)
}
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)
}
@@ -63,7 +83,6 @@ 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)
+86
View File
@@ -0,0 +1,86 @@
package api
import (
"net/http"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/accesscontrol"
)
func TestOrgInvitesAPIEndpointAccess(t *testing.T) {
type accessControlTestCase2 struct {
expectedCode int
desc string
url string
method string
permissions []*accesscontrol.Permission
input string
}
tests := []accessControlTestCase2{
{
expectedCode: http.StatusOK,
desc: "org viewer with the correct permissions can invite and existing user to his org",
url: "/api/org/invites",
method: http.MethodPost,
permissions: []*accesscontrol.Permission{{Action: accesscontrol.ActionOrgUsersAdd, Scope: accesscontrol.ScopeUsersAll}},
input: `{"loginOrEmail": "` + testAdminOrg2.Login + `", "role": "` + string(models.ROLE_VIEWER) + `"}`,
},
{
expectedCode: http.StatusForbidden,
desc: "org viewer with missing permissions cannot invite and existing user to his org",
url: "/api/org/invites",
method: http.MethodPost,
permissions: []*accesscontrol.Permission{},
input: `{"loginOrEmail": "` + testAdminOrg2.Login + `", "role": "` + string(models.ROLE_VIEWER) + `"}`,
},
{
expectedCode: http.StatusForbidden,
desc: "org viewer with the wrong scope cannot invite and 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}},
input: `{"loginOrEmail": "new user", "role": "` + string(models.ROLE_VIEWER) + `"}`,
},
{
expectedCode: http.StatusForbidden,
desc: "org viewer with missing permissions cannot invite a new user to his org",
url: "/api/org/invites",
method: http.MethodPost,
permissions: []*accesscontrol.Permission{},
input: `{"loginOrEmail": "new user", "role": "` + string(models.ROLE_VIEWER) + `"}`,
},
}
for _, test := range tests {
t.Run(test.desc, func(t *testing.T) {
sc := setupHTTPServer(t, true, true)
setInitCtxSignedInViewer(sc.initCtx)
setupOrgUsersDBForAccessControlTests(t, sc.db)
setAccessControlPermissions(sc.acmock, test.permissions, sc.initCtx.OrgId)
input := strings.NewReader(test.input)
response := callAPI(sc.server, test.method, test.url, input, t)
assert.Equal(t, test.expectedCode, response.Code)
})
}
}