Access control: permissions for team creation (#43506)
* FGAC for team creation * tests * fix snapshot for UI tests * linting * update snapshots * Remove unecessary class and update tests Co-authored-by: ievaVasiljeva <ieva.vasiljeva@grafana.com> * Make the condition slightly easier Co-authored-by: ievaVasiljeva <ieva.vasiljeva@grafana.com> Co-authored-by: gamab <gabi.mabs@gmail.com>
This commit is contained in:
+10
-10
@@ -178,16 +178,16 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
|
||||
// team (admin permission required)
|
||||
apiRoute.Group("/teams", func(teamsRoute routing.RouteRegister) {
|
||||
teamsRoute.Post("/", routing.Wrap(hs.CreateTeam))
|
||||
teamsRoute.Put("/:teamId", routing.Wrap(hs.UpdateTeam))
|
||||
teamsRoute.Delete("/:teamId", routing.Wrap(hs.DeleteTeamByID))
|
||||
teamsRoute.Get("/:teamId/members", routing.Wrap(hs.GetTeamMembers))
|
||||
teamsRoute.Post("/:teamId/members", routing.Wrap(hs.AddTeamMember))
|
||||
teamsRoute.Put("/:teamId/members/:userId", routing.Wrap(hs.UpdateTeamMember))
|
||||
teamsRoute.Delete("/:teamId/members/:userId", routing.Wrap(hs.RemoveTeamMember))
|
||||
teamsRoute.Get("/:teamId/preferences", routing.Wrap(hs.GetTeamPreferences))
|
||||
teamsRoute.Put("/:teamId/preferences", routing.Wrap(hs.UpdateTeamPreferences))
|
||||
}, reqCanAccessTeams)
|
||||
teamsRoute.Post("/", authorize(reqCanAccessTeams, ac.EvalPermission(ac.ActionTeamsCreate)), routing.Wrap(hs.CreateTeam))
|
||||
teamsRoute.Put("/:teamId", reqCanAccessTeams, routing.Wrap(hs.UpdateTeam))
|
||||
teamsRoute.Delete("/:teamId", reqCanAccessTeams, routing.Wrap(hs.DeleteTeamByID))
|
||||
teamsRoute.Get("/:teamId/members", reqCanAccessTeams, routing.Wrap(hs.GetTeamMembers))
|
||||
teamsRoute.Post("/:teamId/members", reqCanAccessTeams, routing.Wrap(hs.AddTeamMember))
|
||||
teamsRoute.Put("/:teamId/members/:userId", reqCanAccessTeams, routing.Wrap(hs.UpdateTeamMember))
|
||||
teamsRoute.Delete("/:teamId/members/:userId", reqCanAccessTeams, routing.Wrap(hs.RemoveTeamMember))
|
||||
teamsRoute.Get("/:teamId/preferences", reqCanAccessTeams, routing.Wrap(hs.GetTeamPreferences))
|
||||
teamsRoute.Put("/:teamId/preferences", reqCanAccessTeams, routing.Wrap(hs.UpdateTeamPreferences))
|
||||
})
|
||||
|
||||
// team without requirement of user to be org admin
|
||||
apiRoute.Group("/teams", func(teamsRoute routing.RouteRegister) {
|
||||
|
||||
+14
-5
@@ -283,17 +283,17 @@ func setInitCtxSignedInViewer(initCtx *models.ReqContext) {
|
||||
initCtx.SignedInUser = &models.SignedInUser{UserId: testUserID, OrgId: 1, OrgRole: models.ROLE_VIEWER, Login: testUserLogin}
|
||||
}
|
||||
|
||||
func setInitCtxSignedInEditor(initCtx *models.ReqContext) {
|
||||
initCtx.IsSignedIn = true
|
||||
initCtx.SignedInUser = &models.SignedInUser{UserId: testUserID, OrgId: 1, OrgRole: models.ROLE_EDITOR, Login: testUserLogin}
|
||||
}
|
||||
|
||||
func setInitCtxSignedInOrgAdmin(initCtx *models.ReqContext) {
|
||||
initCtx.IsSignedIn = true
|
||||
initCtx.SignedInUser = &models.SignedInUser{UserId: testUserID, OrgId: 1, OrgRole: models.ROLE_ADMIN, Login: testUserLogin}
|
||||
}
|
||||
|
||||
func setupHTTPServer(t *testing.T, useFakeAccessControl bool, enableAccessControl bool) accessControlScenarioContext {
|
||||
t.Helper()
|
||||
|
||||
var acmock *accesscontrolmock.Mock
|
||||
var ac *ossaccesscontrol.OSSAccessControlService
|
||||
|
||||
// Use a new conf
|
||||
cfg := setting.NewCfg()
|
||||
cfg.FeatureToggles = make(map[string]bool)
|
||||
@@ -301,6 +301,15 @@ func setupHTTPServer(t *testing.T, useFakeAccessControl bool, enableAccessContro
|
||||
cfg.FeatureToggles["accesscontrol"] = enableAccessControl
|
||||
}
|
||||
|
||||
return setupHTTPServerWithCfg(t, useFakeAccessControl, enableAccessControl, cfg)
|
||||
}
|
||||
|
||||
func setupHTTPServerWithCfg(t *testing.T, useFakeAccessControl, enableAccessControl bool, cfg *setting.Cfg) accessControlScenarioContext {
|
||||
t.Helper()
|
||||
|
||||
var acmock *accesscontrolmock.Mock
|
||||
var ac *ossaccesscontrol.OSSAccessControlService
|
||||
|
||||
// Use a test DB
|
||||
db := sqlstore.InitTestDB(t)
|
||||
db.Cfg = cfg
|
||||
|
||||
+3
-2
@@ -19,7 +19,8 @@ func (hs *HTTPServer) CreateTeam(c *models.ReqContext) response.Response {
|
||||
if err := web.Bind(c.Req, &cmd); err != nil {
|
||||
return response.Error(http.StatusBadRequest, "bad request data", err)
|
||||
}
|
||||
if c.OrgRole == models.ROLE_VIEWER {
|
||||
accessControlEnabled := hs.Cfg.FeatureToggles["accesscontrol"]
|
||||
if !accessControlEnabled && c.OrgRole == models.ROLE_VIEWER {
|
||||
return response.Error(403, "Not allowed to create team.", nil)
|
||||
}
|
||||
|
||||
@@ -31,7 +32,7 @@ func (hs *HTTPServer) CreateTeam(c *models.ReqContext) response.Response {
|
||||
return response.Error(500, "Failed to create Team", err)
|
||||
}
|
||||
|
||||
if c.OrgRole == models.ROLE_EDITOR && hs.Cfg.EditorsCanAdmin {
|
||||
if accessControlEnabled || (c.OrgRole == models.ROLE_EDITOR && hs.Cfg.EditorsCanAdmin) {
|
||||
// if the request is authenticated using API tokens
|
||||
// the SignedInUser is an empty struct therefore
|
||||
// an additional check whether it is an actual user is required
|
||||
|
||||
+61
-4
@@ -2,18 +2,19 @@ package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/web"
|
||||
|
||||
"net/http"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -156,3 +157,59 @@ func TestTeamAPIEndpoint(t *testing.T) {
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
var (
|
||||
createTeamURL = "/api/teams/"
|
||||
createTeamCmd = `{"name": "MyTestTeam%d"}`
|
||||
)
|
||||
|
||||
func TestTeamAPIEndpoint_CreateTeam_LegacyAccessControl(t *testing.T) {
|
||||
sc := setupHTTPServer(t, true, false)
|
||||
setInitCtxSignedInOrgAdmin(sc.initCtx)
|
||||
|
||||
input := strings.NewReader(fmt.Sprintf(createTeamCmd, 1))
|
||||
t.Run("Organisation admin can create a team", func(t *testing.T) {
|
||||
response := callAPI(sc.server, http.MethodPost, createTeamURL, input, t)
|
||||
assert.Equal(t, http.StatusOK, response.Code)
|
||||
})
|
||||
|
||||
setInitCtxSignedInEditor(sc.initCtx)
|
||||
sc.initCtx.IsGrafanaAdmin = true
|
||||
input = strings.NewReader(fmt.Sprintf(createTeamCmd, 2))
|
||||
t.Run("Org editor and server admin cannot create a team", func(t *testing.T) {
|
||||
response := callAPI(sc.server, http.MethodPost, createTeamURL, strings.NewReader(createTeamCmd), t)
|
||||
assert.Equal(t, http.StatusForbidden, response.Code)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTeamAPIEndpoint_CreateTeam_LegacyAccessControl_EditorsCanAdmin(t *testing.T) {
|
||||
cfg := setting.NewCfg()
|
||||
cfg.EditorsCanAdmin = true
|
||||
sc := setupHTTPServerWithCfg(t, true, false, cfg)
|
||||
|
||||
setInitCtxSignedInEditor(sc.initCtx)
|
||||
input := strings.NewReader(fmt.Sprintf(createTeamCmd, 1))
|
||||
t.Run("Editors can create a team if editorsCanAdmin is set to true", func(t *testing.T) {
|
||||
response := callAPI(sc.server, http.MethodPost, createTeamURL, input, t)
|
||||
assert.Equal(t, http.StatusOK, response.Code)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTeamAPIEndpoint_CreateTeam_FGAC(t *testing.T) {
|
||||
sc := setupHTTPServer(t, true, true)
|
||||
|
||||
setInitCtxSignedInViewer(sc.initCtx)
|
||||
input := strings.NewReader(fmt.Sprintf(createTeamCmd, 1))
|
||||
t.Run("Access control allows creating teams with the correct permissions", func(t *testing.T) {
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: accesscontrol.ActionTeamsCreate}}, 1)
|
||||
response := callAPI(sc.server, http.MethodPost, createTeamURL, input, t)
|
||||
assert.Equal(t, http.StatusOK, response.Code)
|
||||
})
|
||||
|
||||
input = strings.NewReader(fmt.Sprintf(createTeamCmd, 2))
|
||||
t.Run("Access control prevents creating teams with the incorrect permissions", func(t *testing.T) {
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: "teams:invalid"}}, accesscontrol.GlobalOrgID)
|
||||
response := callAPI(sc.server, http.MethodPost, createTeamURL, input, t)
|
||||
assert.Equal(t, http.StatusForbidden, response.Code)
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user