mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
AccessControl: Remove scopes from orgs endpoints (#41709)
* AccessControl: Check permissions in target org * Remove org scopes and add an authorizeInOrg middleware * Use query result org id and perform users permission check globally for GetOrgByName * Remove scope translation for orgs current * Suggestion from Ieva
This commit is contained in:
@@ -33,6 +33,7 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
reqSnapshotPublicModeOrSignedIn := middleware.SnapshotPublicModeOrSignedIn(hs.Cfg)
|
||||
redirectFromLegacyPanelEditURL := middleware.RedirectFromLegacyPanelEditURL(hs.Cfg)
|
||||
authorize := acmiddleware.Middleware(hs.AccessControl)
|
||||
authorizeInOrg := acmiddleware.AuthorizeInOrgMiddleware(hs.AccessControl, hs.SQLStore)
|
||||
quota := middleware.Quota(hs.QuotaService)
|
||||
bind := binding.Bind
|
||||
|
||||
@@ -200,20 +201,20 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
|
||||
// org information available to all users.
|
||||
apiRoute.Group("/org", func(orgRoute routing.RouteRegister) {
|
||||
orgRoute.Get("/", authorize(reqSignedIn, ac.EvalPermission(ActionOrgsRead, ScopeOrgCurrentID)), routing.Wrap(GetCurrentOrg))
|
||||
orgRoute.Get("/quotas", authorize(reqSignedIn, ac.EvalPermission(ActionOrgsQuotasRead, ScopeOrgCurrentID)), routing.Wrap(hs.GetCurrentOrgQuotas))
|
||||
orgRoute.Get("/", authorize(reqSignedIn, ac.EvalPermission(ActionOrgsRead)), routing.Wrap(GetCurrentOrg))
|
||||
orgRoute.Get("/quotas", authorize(reqSignedIn, ac.EvalPermission(ActionOrgsQuotasRead)), routing.Wrap(hs.GetCurrentOrgQuotas))
|
||||
})
|
||||
|
||||
// current org
|
||||
apiRoute.Group("/org", func(orgRoute routing.RouteRegister) {
|
||||
userIDScope := ac.Scope("users", "id", ac.Parameter(":userId"))
|
||||
orgRoute.Put("/", authorize(reqOrgAdmin, ac.EvalPermission(ActionOrgsWrite, ScopeOrgCurrentID)), bind(dtos.UpdateOrgForm{}), routing.Wrap(UpdateCurrentOrg))
|
||||
orgRoute.Put("/address", authorize(reqOrgAdmin, ac.EvalPermission(ActionOrgsWrite, ScopeOrgCurrentID)), bind(dtos.UpdateOrgAddressForm{}), routing.Wrap(UpdateCurrentOrgAddress))
|
||||
orgRoute.Put("/", authorize(reqOrgAdmin, ac.EvalPermission(ActionOrgsWrite)), bind(dtos.UpdateOrgForm{}), routing.Wrap(UpdateCurrentOrg))
|
||||
orgRoute.Put("/address", authorize(reqOrgAdmin, ac.EvalPermission(ActionOrgsWrite)), bind(dtos.UpdateOrgAddressForm{}), routing.Wrap(UpdateCurrentOrgAddress))
|
||||
orgRoute.Get("/users", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionOrgUsersRead, ac.ScopeUsersAll)), routing.Wrap(hs.GetOrgUsersForCurrentOrg))
|
||||
orgRoute.Get("/users/search", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionOrgUsersRead, ac.ScopeUsersAll)), routing.Wrap(hs.SearchOrgUsersWithPaging))
|
||||
orgRoute.Post("/users", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionOrgUsersAdd, ac.ScopeUsersAll)), quota("user"), bind(models.AddOrgUserCommand{}), routing.Wrap(AddOrgUserToCurrentOrg))
|
||||
orgRoute.Patch("/users/:userId", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionOrgUsersRoleUpdate, userIDScope)), bind(models.UpdateOrgUserCommand{}), routing.Wrap(UpdateOrgUserForCurrentOrg))
|
||||
orgRoute.Delete("/users/:userId", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionOrgUsersRemove, userIDScope)), routing.Wrap(RemoveOrgUserForCurrentOrg))
|
||||
orgRoute.Post("/users", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionOrgUsersAdd, ac.ScopeUsersAll)), quota("user"), bind(models.AddOrgUserCommand{}), routing.Wrap(hs.AddOrgUserToCurrentOrg))
|
||||
orgRoute.Patch("/users/:userId", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionOrgUsersRoleUpdate, userIDScope)), bind(models.UpdateOrgUserCommand{}), routing.Wrap(hs.UpdateOrgUserForCurrentOrg))
|
||||
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(GetPendingOrgInvites))
|
||||
@@ -221,8 +222,8 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
orgRoute.Patch("/invites/:code/revoke", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionUsersCreate)), routing.Wrap(RevokeInvite))
|
||||
|
||||
// prefs
|
||||
orgRoute.Get("/preferences", authorize(reqOrgAdmin, ac.EvalPermission(ActionOrgsPreferencesRead, ScopeOrgCurrentID)), routing.Wrap(hs.GetOrgPreferences))
|
||||
orgRoute.Put("/preferences", authorize(reqOrgAdmin, ac.EvalPermission(ActionOrgsPreferencesWrite, ScopeOrgCurrentID)), bind(dtos.UpdatePrefsCmd{}), routing.Wrap(hs.UpdateOrgPreferences))
|
||||
orgRoute.Get("/preferences", authorize(reqOrgAdmin, ac.EvalPermission(ActionOrgsPreferencesRead)), routing.Wrap(hs.GetOrgPreferences))
|
||||
orgRoute.Put("/preferences", authorize(reqOrgAdmin, ac.EvalPermission(ActionOrgsPreferencesWrite)), bind(dtos.UpdatePrefsCmd{}), routing.Wrap(hs.UpdateOrgPreferences))
|
||||
})
|
||||
|
||||
// current org without requirement of user to be org admin
|
||||
@@ -231,27 +232,28 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
})
|
||||
|
||||
// create new org
|
||||
apiRoute.Post("/orgs", authorize(reqSignedIn, ac.EvalPermission(ActionOrgsCreate)), quota("org"), bind(models.CreateOrgCommand{}), routing.Wrap(hs.CreateOrg))
|
||||
apiRoute.Post("/orgs", authorizeInOrg(reqSignedIn, acmiddleware.UseGlobalOrg, ac.EvalPermission(ActionOrgsCreate)), quota("org"), bind(models.CreateOrgCommand{}), routing.Wrap(hs.CreateOrg))
|
||||
|
||||
// search all orgs
|
||||
apiRoute.Get("/orgs", authorize(reqGrafanaAdmin, ac.EvalPermission(ActionOrgsRead, ScopeOrgsAll)), routing.Wrap(SearchOrgs))
|
||||
apiRoute.Get("/orgs", authorizeInOrg(reqGrafanaAdmin, acmiddleware.UseGlobalOrg, ac.EvalPermission(ActionOrgsRead)), routing.Wrap(SearchOrgs))
|
||||
|
||||
// orgs (admin routes)
|
||||
apiRoute.Group("/orgs/:orgId", func(orgsRoute routing.RouteRegister) {
|
||||
orgsRoute.Get("/", authorize(reqGrafanaAdmin, ac.EvalPermission(ActionOrgsRead, ScopeOrgID)), routing.Wrap(GetOrgByID))
|
||||
orgsRoute.Put("/", authorize(reqGrafanaAdmin, ac.EvalPermission(ActionOrgsWrite, ScopeOrgID)), bind(dtos.UpdateOrgForm{}), routing.Wrap(UpdateOrg))
|
||||
orgsRoute.Put("/address", authorize(reqGrafanaAdmin, ac.EvalPermission(ActionOrgsWrite, ScopeOrgID)), bind(dtos.UpdateOrgAddressForm{}), routing.Wrap(UpdateOrgAddress))
|
||||
orgsRoute.Delete("/", authorize(reqGrafanaAdmin, ac.EvalPermission(ActionOrgsDelete, ScopeOrgID)), routing.Wrap(DeleteOrgByID))
|
||||
orgsRoute.Get("/users", reqGrafanaAdmin, routing.Wrap(hs.GetOrgUsers))
|
||||
orgsRoute.Post("/users", reqGrafanaAdmin, bind(models.AddOrgUserCommand{}), routing.Wrap(AddOrgUser))
|
||||
orgsRoute.Patch("/users/:userId", reqGrafanaAdmin, bind(models.UpdateOrgUserCommand{}), routing.Wrap(UpdateOrgUser))
|
||||
orgsRoute.Delete("/users/:userId", reqGrafanaAdmin, routing.Wrap(RemoveOrgUser))
|
||||
orgsRoute.Get("/quotas", authorize(reqGrafanaAdmin, ac.EvalPermission(ActionOrgsQuotasRead, ScopeOrgID)), routing.Wrap(hs.GetOrgQuotas))
|
||||
orgsRoute.Put("/quotas/:target", authorize(reqGrafanaAdmin, ac.EvalPermission(ActionOrgsQuotasWrite, ScopeOrgID)), bind(models.UpdateOrgQuotaCmd{}), routing.Wrap(hs.UpdateOrgQuota))
|
||||
userIDScope := ac.Scope("users", "id", ac.Parameter(":userId"))
|
||||
orgsRoute.Get("/", authorizeInOrg(reqGrafanaAdmin, acmiddleware.UseOrgFromContextParams, ac.EvalPermission(ActionOrgsRead)), routing.Wrap(GetOrgByID))
|
||||
orgsRoute.Put("/", authorizeInOrg(reqGrafanaAdmin, acmiddleware.UseOrgFromContextParams, ac.EvalPermission(ActionOrgsWrite)), bind(dtos.UpdateOrgForm{}), routing.Wrap(UpdateOrg))
|
||||
orgsRoute.Put("/address", authorizeInOrg(reqGrafanaAdmin, acmiddleware.UseOrgFromContextParams, ac.EvalPermission(ActionOrgsWrite)), bind(dtos.UpdateOrgAddressForm{}), routing.Wrap(UpdateOrgAddress))
|
||||
orgsRoute.Delete("/", authorizeInOrg(reqGrafanaAdmin, acmiddleware.UseOrgFromContextParams, ac.EvalPermission(ActionOrgsDelete)), routing.Wrap(DeleteOrgByID))
|
||||
orgsRoute.Get("/users", authorizeInOrg(reqGrafanaAdmin, acmiddleware.UseOrgFromContextParams, ac.EvalPermission(ac.ActionOrgUsersRead, ac.ScopeUsersAll)), routing.Wrap(hs.GetOrgUsers))
|
||||
orgsRoute.Post("/users", authorizeInOrg(reqGrafanaAdmin, acmiddleware.UseOrgFromContextParams, ac.EvalPermission(ac.ActionOrgUsersAdd, ac.ScopeUsersAll)), bind(models.AddOrgUserCommand{}), routing.Wrap(hs.AddOrgUser))
|
||||
orgsRoute.Patch("/users/:userId", authorizeInOrg(reqGrafanaAdmin, acmiddleware.UseOrgFromContextParams, ac.EvalPermission(ac.ActionOrgUsersRoleUpdate, userIDScope)), bind(models.UpdateOrgUserCommand{}), routing.Wrap(hs.UpdateOrgUser))
|
||||
orgsRoute.Delete("/users/:userId", authorizeInOrg(reqGrafanaAdmin, acmiddleware.UseOrgFromContextParams, ac.EvalPermission(ac.ActionOrgUsersRemove, userIDScope)), routing.Wrap(hs.RemoveOrgUser))
|
||||
orgsRoute.Get("/quotas", authorizeInOrg(reqGrafanaAdmin, acmiddleware.UseOrgFromContextParams, ac.EvalPermission(ActionOrgsQuotasRead)), routing.Wrap(hs.GetOrgQuotas))
|
||||
orgsRoute.Put("/quotas/:target", authorizeInOrg(reqGrafanaAdmin, acmiddleware.UseOrgFromContextParams, ac.EvalPermission(ActionOrgsQuotasWrite)), bind(models.UpdateOrgQuotaCmd{}), routing.Wrap(hs.UpdateOrgQuota))
|
||||
})
|
||||
|
||||
// orgs (admin routes)
|
||||
apiRoute.Get("/orgs/name/:name/", authorize(reqGrafanaAdmin, ac.EvalPermission(ActionOrgsRead, ScopeOrgName)), routing.Wrap(hs.GetOrgByName))
|
||||
apiRoute.Get("/orgs/name/:name/", authorizeInOrg(reqGrafanaAdmin, acmiddleware.UseGlobalOrg, ac.EvalPermission(ActionOrgsRead)), routing.Wrap(hs.GetOrgByName))
|
||||
|
||||
// auth api keys
|
||||
apiRoute.Group("/auth/keys", func(keysRoute routing.RouteRegister) {
|
||||
|
||||
@@ -9,9 +9,7 @@ import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/searchusers/filters"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/searchusers"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/response"
|
||||
"github.com/grafana/grafana/pkg/api/routing"
|
||||
@@ -19,17 +17,20 @@ import (
|
||||
"github.com/grafana/grafana/pkg/infra/fs"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/infra/remotecache"
|
||||
"github.com/grafana/grafana/pkg/infra/usagestats"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
accesscontrolmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/ossaccesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/auth"
|
||||
"github.com/grafana/grafana/pkg/services/contexthandler"
|
||||
"github.com/grafana/grafana/pkg/services/quota"
|
||||
"github.com/grafana/grafana/pkg/services/rendering"
|
||||
"github.com/grafana/grafana/pkg/services/searchusers"
|
||||
"github.com/grafana/grafana/pkg/services/searchusers/filters"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/web"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func loggedInUserScenario(t *testing.T, desc string, url string, fn scenarioFunc) {
|
||||
@@ -270,12 +271,21 @@ type accessControlScenarioContext struct {
|
||||
cfg *setting.Cfg
|
||||
}
|
||||
|
||||
func setAccessControlPermissions(acmock *accesscontrolmock.Mock, perms []*accesscontrol.Permission) {
|
||||
acmock.GetUserPermissionsFunc = func(_ context.Context, _ *models.SignedInUser) ([]*accesscontrol.Permission, error) {
|
||||
return perms, nil
|
||||
func setAccessControlPermissions(acmock *accesscontrolmock.Mock, perms []*accesscontrol.Permission, org int64) {
|
||||
acmock.GetUserPermissionsFunc = func(_ context.Context, u *models.SignedInUser) ([]*accesscontrol.Permission, error) {
|
||||
if u.OrgId == org {
|
||||
return perms, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
// setInitCtxSignedInUser sets a copy of the user in initCtx
|
||||
func setInitCtxSignedInUser(initCtx *models.ReqContext, user models.SignedInUser) {
|
||||
initCtx.IsSignedIn = true
|
||||
initCtx.SignedInUser = &user
|
||||
}
|
||||
|
||||
func setInitCtxSignedInViewer(initCtx *models.ReqContext) {
|
||||
initCtx.IsSignedIn = true
|
||||
initCtx.SignedInUser = &models.SignedInUser{UserId: testUserID, OrgId: 1, OrgRole: models.ROLE_VIEWER, Login: testUserLogin}
|
||||
@@ -286,22 +296,17 @@ func setInitCtxSignedInOrgAdmin(initCtx *models.ReqContext) {
|
||||
initCtx.SignedInUser = &models.SignedInUser{UserId: testUserID, OrgId: 1, OrgRole: models.ROLE_ADMIN, Login: testUserLogin}
|
||||
}
|
||||
|
||||
func setupHTTPServer(t *testing.T, enableAccessControl bool) accessControlScenarioContext {
|
||||
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)
|
||||
|
||||
// Use an accesscontrol mock
|
||||
acmock := accesscontrolmock.New()
|
||||
|
||||
// Handle accesscontrol enablement
|
||||
if enableAccessControl {
|
||||
cfg.FeatureToggles["accesscontrol"] = enableAccessControl
|
||||
} else {
|
||||
// Disabling accesscontrol has to be done before registering routes
|
||||
acmock = acmock.WithDisabled()
|
||||
}
|
||||
|
||||
// Use a test DB
|
||||
@@ -317,11 +322,27 @@ func setupHTTPServer(t *testing.T, enableAccessControl bool) accessControlScenar
|
||||
Live: newTestLive(t),
|
||||
QuotaService: "a.QuotaService{Cfg: cfg},
|
||||
RouteRegister: routing.NewRouteRegister(),
|
||||
AccessControl: acmock,
|
||||
SQLStore: db,
|
||||
searchUsersService: searchusers.ProvideUsersService(bus, filters.ProvideOSSSearchUserFilter()),
|
||||
}
|
||||
|
||||
// Defining the accesscontrol service has to be done before registering routes
|
||||
if useFakeAccessControl {
|
||||
acmock = accesscontrolmock.New()
|
||||
if !enableAccessControl {
|
||||
acmock = acmock.WithDisabled()
|
||||
}
|
||||
hs.AccessControl = acmock
|
||||
} else {
|
||||
ac = ossaccesscontrol.ProvideService(cfg, &usagestats.UsageStatsMock{T: t})
|
||||
hs.AccessControl = ac
|
||||
// Perform role registration
|
||||
err := hs.declareFixedRoles()
|
||||
require.NoError(t, err)
|
||||
err = ac.RegisterFixedRoles()
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// Instantiate a new Server
|
||||
m := web.New()
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
@@ -9,7 +10,9 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
@@ -37,13 +40,170 @@ var (
|
||||
testCreateOrgCmd = `{ "name": "TestOrg%v"}`
|
||||
)
|
||||
|
||||
func TestAPIEndpoint_CreateOrgs_LegacyAccessControl(t *testing.T) {
|
||||
sc := setupHTTPServer(t, false)
|
||||
// `/api/org` endpoints test
|
||||
|
||||
func TestAPIEndpoint_GetCurrentOrg_LegacyAccessControl(t *testing.T) {
|
||||
sc := setupHTTPServer(t, true, false)
|
||||
setInitCtxSignedInViewer(sc.initCtx)
|
||||
|
||||
_, err := sc.db.CreateOrgWithMember("TestOrg", testUserID)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("Viewer can view CurrentOrg", func(t *testing.T) {
|
||||
response := callAPI(sc.server, http.MethodGet, getCurrentOrgURL, nil, t)
|
||||
assert.Equal(t, http.StatusOK, response.Code)
|
||||
})
|
||||
|
||||
sc.initCtx.IsSignedIn = false
|
||||
t.Run("Unsigned user cannot view CurrentOrg", func(t *testing.T) {
|
||||
response := callAPI(sc.server, http.MethodGet, getCurrentOrgURL, nil, t)
|
||||
assert.Equal(t, http.StatusUnauthorized, response.Code)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAPIEndpoint_GetCurrentOrg_AccessControl(t *testing.T) {
|
||||
sc := setupHTTPServer(t, true, true)
|
||||
setInitCtxSignedInViewer(sc.initCtx)
|
||||
|
||||
_, err := sc.db.CreateOrgWithMember("TestOrg", testUserID)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("AccessControl allows viewing CurrentOrg with correct permissions", func(t *testing.T) {
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: ActionOrgsRead}}, sc.initCtx.OrgId)
|
||||
response := callAPI(sc.server, http.MethodGet, getCurrentOrgURL, nil, t)
|
||||
assert.Equal(t, http.StatusOK, response.Code)
|
||||
})
|
||||
t.Run("AccessControl prevents viewing CurrentOrg with correct permissions in another org", func(t *testing.T) {
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: ActionOrgsRead}}, 2)
|
||||
response := callAPI(sc.server, http.MethodGet, getCurrentOrgURL, nil, t)
|
||||
assert.Equal(t, http.StatusForbidden, response.Code)
|
||||
})
|
||||
t.Run("AccessControl prevents viewing CurrentOrg with incorrect permissions", func(t *testing.T) {
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: "orgs:invalid"}}, sc.initCtx.OrgId)
|
||||
response := callAPI(sc.server, http.MethodGet, getCurrentOrgURL, nil, t)
|
||||
assert.Equal(t, http.StatusForbidden, response.Code)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAPIEndpoint_PutCurrentOrg_LegacyAccessControl(t *testing.T) {
|
||||
sc := setupHTTPServer(t, true, false)
|
||||
|
||||
_, err := sc.db.CreateOrgWithMember("TestOrg", testUserID)
|
||||
require.NoError(t, err)
|
||||
|
||||
input := strings.NewReader(testUpdateOrgNameForm)
|
||||
|
||||
setInitCtxSignedInViewer(sc.initCtx)
|
||||
t.Run("Viewer cannot update current org", func(t *testing.T) {
|
||||
response := callAPI(sc.server, http.MethodPut, putCurrentOrgURL, input, t)
|
||||
assert.Equal(t, http.StatusForbidden, response.Code)
|
||||
})
|
||||
|
||||
setInitCtxSignedInOrgAdmin(sc.initCtx)
|
||||
t.Run("Admin can update current org", func(t *testing.T) {
|
||||
response := callAPI(sc.server, http.MethodPut, putCurrentOrgURL, input, t)
|
||||
assert.Equal(t, http.StatusOK, response.Code)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAPIEndpoint_PutCurrentOrg_AccessControl(t *testing.T) {
|
||||
sc := setupHTTPServer(t, true, true)
|
||||
setInitCtxSignedInViewer(sc.initCtx)
|
||||
|
||||
_, err := sc.db.CreateOrgWithMember("TestOrg", sc.initCtx.UserId)
|
||||
require.NoError(t, err)
|
||||
|
||||
input := strings.NewReader(testUpdateOrgNameForm)
|
||||
t.Run("AccessControl allows updating current org with correct permissions", func(t *testing.T) {
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: ActionOrgsWrite}}, sc.initCtx.OrgId)
|
||||
response := callAPI(sc.server, http.MethodPut, putCurrentOrgURL, input, t)
|
||||
assert.Equal(t, http.StatusOK, response.Code)
|
||||
})
|
||||
|
||||
t.Run("AccessControl prevents updating current org with correct permissions in another org", func(t *testing.T) {
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: ActionOrgsWrite}}, 2)
|
||||
response := callAPI(sc.server, http.MethodPut, putCurrentOrgURL, input, t)
|
||||
assert.Equal(t, http.StatusForbidden, response.Code)
|
||||
})
|
||||
|
||||
t.Run("AccessControl prevents updating current org with incorrect permissions", func(t *testing.T) {
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: "orgs:invalid"}}, sc.initCtx.OrgId)
|
||||
response := callAPI(sc.server, http.MethodPut, putCurrentOrgURL, input, t)
|
||||
assert.Equal(t, http.StatusForbidden, response.Code)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAPIEndpoint_PutCurrentOrgAddress_LegacyAccessControl(t *testing.T) {
|
||||
sc := setupHTTPServer(t, true, false)
|
||||
|
||||
_, err := sc.db.CreateOrgWithMember("TestOrg", testUserID)
|
||||
require.NoError(t, err)
|
||||
|
||||
input := strings.NewReader(testUpdateOrgAddressForm)
|
||||
|
||||
setInitCtxSignedInViewer(sc.initCtx)
|
||||
t.Run("Viewer cannot update current org address", func(t *testing.T) {
|
||||
response := callAPI(sc.server, http.MethodPut, putCurrentOrgAddressURL, input, t)
|
||||
assert.Equal(t, http.StatusForbidden, response.Code)
|
||||
})
|
||||
|
||||
setInitCtxSignedInOrgAdmin(sc.initCtx)
|
||||
t.Run("Admin can update current org address", func(t *testing.T) {
|
||||
response := callAPI(sc.server, http.MethodPut, putCurrentOrgAddressURL, input, t)
|
||||
assert.Equal(t, http.StatusOK, response.Code)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAPIEndpoint_PutCurrentOrgAddress_AccessControl(t *testing.T) {
|
||||
sc := setupHTTPServer(t, true, true)
|
||||
setInitCtxSignedInViewer(sc.initCtx)
|
||||
|
||||
_, err := sc.db.CreateOrgWithMember("TestOrg", testUserID)
|
||||
require.NoError(t, err)
|
||||
|
||||
input := strings.NewReader(testUpdateOrgAddressForm)
|
||||
t.Run("AccessControl allows updating current org address with correct permissions", func(t *testing.T) {
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: ActionOrgsWrite}}, sc.initCtx.OrgId)
|
||||
response := callAPI(sc.server, http.MethodPut, putCurrentOrgAddressURL, input, t)
|
||||
assert.Equal(t, http.StatusOK, response.Code)
|
||||
})
|
||||
|
||||
input = strings.NewReader(testUpdateOrgAddressForm)
|
||||
t.Run("AccessControl prevents updating current org address with correct permissions in another org", func(t *testing.T) {
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: ActionOrgsWrite}}, 2)
|
||||
response := callAPI(sc.server, http.MethodPut, putCurrentOrgAddressURL, input, t)
|
||||
assert.Equal(t, http.StatusForbidden, response.Code)
|
||||
})
|
||||
|
||||
t.Run("AccessControl prevents updating current org address with incorrect permissions", func(t *testing.T) {
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: "orgs:invalid"}}, sc.initCtx.OrgId)
|
||||
response := callAPI(sc.server, http.MethodPut, putCurrentOrgAddressURL, input, t)
|
||||
assert.Equal(t, http.StatusForbidden, response.Code)
|
||||
})
|
||||
}
|
||||
|
||||
// `/api/orgs/` endpoints test
|
||||
|
||||
// setupOrgsDBForAccessControlTests stores users and create specified number of orgs
|
||||
func setupOrgsDBForAccessControlTests(t *testing.T, db sqlstore.SQLStore, user models.SignedInUser, orgsCount int) {
|
||||
t.Helper()
|
||||
|
||||
_, err := db.CreateUser(context.Background(), models.CreateUserCommand{Email: user.Email, SkipOrgSetup: true, Login: user.Login})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create `orgsCount` orgs
|
||||
for i := 1; i <= orgsCount; i++ {
|
||||
_, err = db.CreateOrgWithMember(fmt.Sprintf("TestOrg%v", i), 0)
|
||||
require.NoError(t, err)
|
||||
err = db.AddOrgUser(context.Background(), &models.AddOrgUserCommand{LoginOrEmail: user.Login, Role: user.OrgRole, OrgId: int64(i), UserId: user.UserId})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAPIEndpoint_CreateOrgs_LegacyAccessControl(t *testing.T) {
|
||||
sc := setupHTTPServer(t, true, false)
|
||||
setInitCtxSignedInViewer(sc.initCtx)
|
||||
|
||||
setting.AllowUserOrgCreate = false
|
||||
input := strings.NewReader(fmt.Sprintf(testCreateOrgCmd, 2))
|
||||
t.Run("Viewer cannot create Orgs", func(t *testing.T) {
|
||||
@@ -68,36 +228,31 @@ func TestAPIEndpoint_CreateOrgs_LegacyAccessControl(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAPIEndpoint_CreateOrgs_AccessControl(t *testing.T) {
|
||||
sc := setupHTTPServer(t, true)
|
||||
sc := setupHTTPServer(t, true, true)
|
||||
setInitCtxSignedInViewer(sc.initCtx)
|
||||
|
||||
_, err := sc.db.CreateOrgWithMember("TestOrg", testUserID)
|
||||
require.NoError(t, err)
|
||||
setupOrgsDBForAccessControlTests(t, *sc.db, *sc.initCtx.SignedInUser, 0)
|
||||
|
||||
input := strings.NewReader(fmt.Sprintf(testCreateOrgCmd, 2))
|
||||
t.Run("AccessControl allows creating Orgs with correct permissions", func(t *testing.T) {
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: ActionOrgsCreate}})
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: ActionOrgsCreate}}, accesscontrol.GlobalOrgID)
|
||||
response := callAPI(sc.server, http.MethodPost, createOrgsURL, input, t)
|
||||
assert.Equal(t, http.StatusOK, response.Code)
|
||||
})
|
||||
|
||||
input = strings.NewReader(fmt.Sprintf(testCreateOrgCmd, 3))
|
||||
t.Run("AccessControl prevents creating Orgs with incorrect permissions", func(t *testing.T) {
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: "orgs:invalid"}})
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: "orgs:invalid"}}, accesscontrol.GlobalOrgID)
|
||||
response := callAPI(sc.server, http.MethodPost, createOrgsURL, input, t)
|
||||
assert.Equal(t, http.StatusForbidden, response.Code)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAPIEndpoint_DeleteOrgs_LegacyAccessControl(t *testing.T) {
|
||||
sc := setupHTTPServer(t, false)
|
||||
sc := setupHTTPServer(t, true, false)
|
||||
setInitCtxSignedInViewer(sc.initCtx)
|
||||
|
||||
// Create two orgs
|
||||
_, err := sc.db.CreateOrgWithMember("TestOrg", testUserID)
|
||||
require.NoError(t, err)
|
||||
_, err = sc.db.CreateOrgWithMember("TestOrg2", testUserID)
|
||||
require.NoError(t, err)
|
||||
setupOrgsDBForAccessControlTests(t, *sc.db, *sc.initCtx.SignedInUser, 2)
|
||||
|
||||
t.Run("Viewer cannot delete Orgs", func(t *testing.T) {
|
||||
response := callAPI(sc.server, http.MethodDelete, fmt.Sprintf(deleteOrgsURL, 2), nil, t)
|
||||
@@ -112,49 +267,32 @@ func TestAPIEndpoint_DeleteOrgs_LegacyAccessControl(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAPIEndpoint_DeleteOrgs_AccessControl(t *testing.T) {
|
||||
sc := setupHTTPServer(t, true)
|
||||
sc := setupHTTPServer(t, true, true)
|
||||
setInitCtxSignedInViewer(sc.initCtx)
|
||||
|
||||
// Create three orgs (to delete org2 then org3)
|
||||
_, err := sc.db.CreateOrgWithMember("TestOrg", testUserID)
|
||||
require.NoError(t, err)
|
||||
_, err = sc.db.CreateOrgWithMember("TestOrg2", testUserID)
|
||||
require.NoError(t, err)
|
||||
_, err = sc.db.CreateOrgWithMember("TestOrg3", testUserID)
|
||||
require.NoError(t, err)
|
||||
setupOrgsDBForAccessControlTests(t, *sc.db, *sc.initCtx.SignedInUser, 2)
|
||||
|
||||
t.Run("AccessControl allows deleting Orgs with correct permissions", func(t *testing.T) {
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: ActionOrgsDelete, Scope: ScopeOrgsAll}})
|
||||
response := callAPI(sc.server, http.MethodDelete, fmt.Sprintf(deleteOrgsURL, 2), nil, t)
|
||||
assert.Equal(t, http.StatusOK, response.Code)
|
||||
})
|
||||
t.Run("AccessControl allows deleting Orgs with exact permissions", func(t *testing.T) {
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: ActionOrgsDelete, Scope: accesscontrol.Scope("orgs", "id", "3")}})
|
||||
response := callAPI(sc.server, http.MethodDelete, fmt.Sprintf(deleteOrgsURL, 3), nil, t)
|
||||
assert.Equal(t, http.StatusOK, response.Code)
|
||||
})
|
||||
t.Run("AccessControl prevents deleting Orgs with too narrow permissions", func(t *testing.T) {
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: ActionOrgsDelete, Scope: accesscontrol.Scope("orgs", "id", "1")}})
|
||||
response := callAPI(sc.server, http.MethodDelete, fmt.Sprintf(deleteOrgsURL, 2), nil, t)
|
||||
assert.Equal(t, http.StatusForbidden, response.Code)
|
||||
})
|
||||
t.Run("AccessControl prevents deleting Orgs with incorrect permissions", func(t *testing.T) {
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: "orgs:invalid"}})
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: "orgs:invalid"}}, 2)
|
||||
response := callAPI(sc.server, http.MethodDelete, fmt.Sprintf(deleteOrgsURL, 2), nil, t)
|
||||
assert.Equal(t, http.StatusForbidden, response.Code)
|
||||
})
|
||||
t.Run("AccessControl prevents deleting Orgs with correct permissions in another org", func(t *testing.T) {
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: ActionOrgsDelete}}, 1)
|
||||
response := callAPI(sc.server, http.MethodDelete, fmt.Sprintf(deleteOrgsURL, 2), nil, t)
|
||||
assert.Equal(t, http.StatusForbidden, response.Code)
|
||||
})
|
||||
t.Run("AccessControl allows deleting Orgs with correct permissions", func(t *testing.T) {
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: ActionOrgsDelete}}, 2)
|
||||
response := callAPI(sc.server, http.MethodDelete, fmt.Sprintf(deleteOrgsURL, 2), nil, t)
|
||||
assert.Equal(t, http.StatusOK, response.Code)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAPIEndpoint_SearchOrgs_LegacyAccessControl(t *testing.T) {
|
||||
sc := setupHTTPServer(t, false)
|
||||
sc := setupHTTPServer(t, true, false)
|
||||
setInitCtxSignedInViewer(sc.initCtx)
|
||||
|
||||
// Create two orgs
|
||||
_, err := sc.db.CreateOrgWithMember("TestOrg", testUserID)
|
||||
require.NoError(t, err)
|
||||
_, err = sc.db.CreateOrgWithMember("TestOrg2", testUserID)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("Viewer cannot list Orgs", func(t *testing.T) {
|
||||
response := callAPI(sc.server, http.MethodGet, searchOrgsURL, nil, t)
|
||||
assert.Equal(t, http.StatusForbidden, response.Code)
|
||||
@@ -168,84 +306,32 @@ func TestAPIEndpoint_SearchOrgs_LegacyAccessControl(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAPIEndpoint_SearchOrgs_AccessControl(t *testing.T) {
|
||||
sc := setupHTTPServer(t, true)
|
||||
sc := setupHTTPServer(t, true, true)
|
||||
setInitCtxSignedInViewer(sc.initCtx)
|
||||
|
||||
// Create two orgs
|
||||
_, err := sc.db.CreateOrgWithMember("TestOrg", testUserID)
|
||||
require.NoError(t, err)
|
||||
_, err = sc.db.CreateOrgWithMember("TestOrg2", testUserID)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("AccessControl allows listing Orgs with correct permissions", func(t *testing.T) {
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: ActionOrgsRead, Scope: ScopeOrgsAll}})
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: ActionOrgsRead}}, accesscontrol.GlobalOrgID)
|
||||
response := callAPI(sc.server, http.MethodGet, searchOrgsURL, nil, t)
|
||||
assert.Equal(t, http.StatusOK, response.Code)
|
||||
})
|
||||
t.Run("AccessControl prevents listing Orgs with too narrow permissions", func(t *testing.T) {
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: ActionOrgsRead, Scope: accesscontrol.Scope("orgs", "id", "1")}})
|
||||
t.Run("AccessControl prevents listing Orgs with correct permissions not granted globally", func(t *testing.T) {
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: ActionOrgsRead}}, 1)
|
||||
response := callAPI(sc.server, http.MethodGet, searchOrgsURL, nil, t)
|
||||
assert.Equal(t, http.StatusForbidden, response.Code)
|
||||
})
|
||||
t.Run("AccessControl prevents listing Orgs with incorrect permissions", func(t *testing.T) {
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: "orgs:invalid"}})
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: "orgs:invalid"}}, accesscontrol.GlobalOrgID)
|
||||
response := callAPI(sc.server, http.MethodGet, searchOrgsURL, nil, t)
|
||||
assert.Equal(t, http.StatusForbidden, response.Code)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAPIEndpoint_GetCurrentOrg_LegacyAccessControl(t *testing.T) {
|
||||
sc := setupHTTPServer(t, false)
|
||||
setInitCtxSignedInViewer(sc.initCtx)
|
||||
|
||||
_, err := sc.db.CreateOrgWithMember("TestOrg", testUserID)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("Viewer can view CurrentOrg", func(t *testing.T) {
|
||||
response := callAPI(sc.server, http.MethodGet, getCurrentOrgURL, nil, t)
|
||||
assert.Equal(t, http.StatusOK, response.Code)
|
||||
})
|
||||
|
||||
sc.initCtx.IsSignedIn = false
|
||||
t.Run("Unsigned user cannot view CurrentOrg", func(t *testing.T) {
|
||||
response := callAPI(sc.server, http.MethodGet, getCurrentOrgURL, nil, t)
|
||||
assert.Equal(t, http.StatusUnauthorized, response.Code)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAPIEndpoint_GetCurrentOrg_AccessControl(t *testing.T) {
|
||||
sc := setupHTTPServer(t, true)
|
||||
setInitCtxSignedInViewer(sc.initCtx)
|
||||
|
||||
_, err := sc.db.CreateOrgWithMember("TestOrg", testUserID)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("AccessControl allows viewing CurrentOrg with correct permissions", func(t *testing.T) {
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: ActionOrgsRead, Scope: ScopeOrgsAll}})
|
||||
response := callAPI(sc.server, http.MethodGet, getCurrentOrgURL, nil, t)
|
||||
assert.Equal(t, http.StatusOK, response.Code)
|
||||
})
|
||||
t.Run("AccessControl allows viewing CurrentOrg with exact permissions", func(t *testing.T) {
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: ActionOrgsRead, Scope: accesscontrol.Scope("orgs", "id", "1")}})
|
||||
response := callAPI(sc.server, http.MethodGet, getCurrentOrgURL, nil, t)
|
||||
assert.Equal(t, http.StatusOK, response.Code)
|
||||
})
|
||||
t.Run("AccessControl prevents viewing CurrentOrg with incorrect permissions", func(t *testing.T) {
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: "orgs:invalid"}})
|
||||
response := callAPI(sc.server, http.MethodGet, getCurrentOrgURL, nil, t)
|
||||
assert.Equal(t, http.StatusForbidden, response.Code)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAPIEndpoint_GetOrg_LegacyAccessControl(t *testing.T) {
|
||||
sc := setupHTTPServer(t, false)
|
||||
sc := setupHTTPServer(t, true, false)
|
||||
setInitCtxSignedInViewer(sc.initCtx)
|
||||
|
||||
// Create two orgs, to fetch another one than the logged in one
|
||||
_, err := sc.db.CreateOrgWithMember("TestOrg", testUserID)
|
||||
require.NoError(t, err)
|
||||
_, err = sc.db.CreateOrgWithMember("TestOrg2", testUserID)
|
||||
require.NoError(t, err)
|
||||
setupOrgsDBForAccessControlTests(t, *sc.db, *sc.initCtx.SignedInUser, 2)
|
||||
|
||||
t.Run("Viewer cannot view another Org", func(t *testing.T) {
|
||||
response := callAPI(sc.server, http.MethodGet, fmt.Sprintf(getOrgsURL, 2), nil, t)
|
||||
@@ -260,46 +346,35 @@ func TestAPIEndpoint_GetOrg_LegacyAccessControl(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAPIEndpoint_GetOrg_AccessControl(t *testing.T) {
|
||||
sc := setupHTTPServer(t, true)
|
||||
sc := setupHTTPServer(t, true, true)
|
||||
setInitCtxSignedInViewer(sc.initCtx)
|
||||
|
||||
// Create two orgs, to fetch another one than the logged in one
|
||||
_, err := sc.db.CreateOrgWithMember("TestOrg", testUserID)
|
||||
require.NoError(t, err)
|
||||
_, err = sc.db.CreateOrgWithMember("TestOrg2", testUserID)
|
||||
require.NoError(t, err)
|
||||
setupOrgsDBForAccessControlTests(t, *sc.db, *sc.initCtx.SignedInUser, 2)
|
||||
|
||||
t.Run("AccessControl allows viewing another org with correct permissions", func(t *testing.T) {
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: ActionOrgsRead, Scope: ScopeOrgsAll}})
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: ActionOrgsRead}}, 2)
|
||||
response := callAPI(sc.server, http.MethodGet, fmt.Sprintf(getOrgsURL, 2), nil, t)
|
||||
assert.Equal(t, http.StatusOK, response.Code)
|
||||
})
|
||||
t.Run("AccessControl allows viewing another org with exact permissions", func(t *testing.T) {
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: ActionOrgsRead, Scope: accesscontrol.Scope("orgs", "id", "2")}})
|
||||
response := callAPI(sc.server, http.MethodGet, fmt.Sprintf(getOrgsURL, 2), nil, t)
|
||||
assert.Equal(t, http.StatusOK, response.Code)
|
||||
})
|
||||
t.Run("AccessControl prevents viewing another org with too narrow permissions", func(t *testing.T) {
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: ActionOrgsRead, Scope: accesscontrol.Scope("orgs", "id", "1")}})
|
||||
t.Run("AccessControl prevents viewing another org with correct permissions in another org", func(t *testing.T) {
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: ActionOrgsRead}}, 1)
|
||||
response := callAPI(sc.server, http.MethodGet, fmt.Sprintf(getOrgsURL, 2), nil, t)
|
||||
assert.Equal(t, http.StatusForbidden, response.Code)
|
||||
})
|
||||
t.Run("AccessControl prevents viewing another org with incorrect permissions", func(t *testing.T) {
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: "orgs:invalid"}})
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: "orgs:invalid"}}, 2)
|
||||
response := callAPI(sc.server, http.MethodGet, fmt.Sprintf(getOrgsURL, 2), nil, t)
|
||||
assert.Equal(t, http.StatusForbidden, response.Code)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAPIEndpoint_GetOrgByName_LegacyAccessControl(t *testing.T) {
|
||||
sc := setupHTTPServer(t, false)
|
||||
sc := setupHTTPServer(t, true, false)
|
||||
setInitCtxSignedInViewer(sc.initCtx)
|
||||
|
||||
// Create two orgs, to fetch another one than the logged in one
|
||||
_, err := sc.db.CreateOrgWithMember("TestOrg", testUserID)
|
||||
require.NoError(t, err)
|
||||
_, err = sc.db.CreateOrgWithMember("TestOrg2", testUserID)
|
||||
require.NoError(t, err)
|
||||
setupOrgsDBForAccessControlTests(t, *sc.db, *sc.initCtx.SignedInUser, 2)
|
||||
|
||||
t.Run("Viewer cannot view another Org", func(t *testing.T) {
|
||||
response := callAPI(sc.server, http.MethodGet, fmt.Sprintf(getOrgsByNameURL, "TestOrg2"), nil, t)
|
||||
@@ -314,101 +389,30 @@ func TestAPIEndpoint_GetOrgByName_LegacyAccessControl(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAPIEndpoint_GetOrgByName_AccessControl(t *testing.T) {
|
||||
sc := setupHTTPServer(t, true)
|
||||
sc := setupHTTPServer(t, true, true)
|
||||
setInitCtxSignedInViewer(sc.initCtx)
|
||||
|
||||
// Create two orgs, to fetch another one than the logged in one
|
||||
_, err := sc.db.CreateOrgWithMember("TestOrg", testUserID)
|
||||
require.NoError(t, err)
|
||||
_, err = sc.db.CreateOrgWithMember("TestOrg2", testUserID)
|
||||
require.NoError(t, err)
|
||||
setupOrgsDBForAccessControlTests(t, *sc.db, *sc.initCtx.SignedInUser, 2)
|
||||
|
||||
t.Run("AccessControl allows viewing another org with correct permissions", func(t *testing.T) {
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: ActionOrgsRead, Scope: ScopeOrgsAll}})
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: ActionOrgsRead}}, accesscontrol.GlobalOrgID)
|
||||
response := callAPI(sc.server, http.MethodGet, fmt.Sprintf(getOrgsByNameURL, "TestOrg2"), nil, t)
|
||||
assert.Equal(t, http.StatusOK, response.Code)
|
||||
})
|
||||
t.Run("AccessControl allows viewing another org with exact permissions", func(t *testing.T) {
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: ActionOrgsRead, Scope: accesscontrol.Scope("orgs", "name", "TestOrg2")}})
|
||||
response := callAPI(sc.server, http.MethodGet, fmt.Sprintf(getOrgsByNameURL, "TestOrg2"), nil, t)
|
||||
assert.Equal(t, http.StatusOK, response.Code)
|
||||
})
|
||||
t.Run("AccessControl prevents viewing another org with too narrow permissions", func(t *testing.T) {
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: ActionOrgsRead, Scope: accesscontrol.Scope("orgs", "name", "TestOrg1")}})
|
||||
response := callAPI(sc.server, http.MethodGet, fmt.Sprintf(getOrgsByNameURL, "TestOrg2"), nil, t)
|
||||
assert.Equal(t, http.StatusForbidden, response.Code)
|
||||
})
|
||||
t.Run("AccessControl prevents viewing another org with incorrect permissions", func(t *testing.T) {
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: "orgs:invalid"}})
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: "orgs:invalid"}}, accesscontrol.GlobalOrgID)
|
||||
response := callAPI(sc.server, http.MethodGet, fmt.Sprintf(getOrgsByNameURL, "TestOrg2"), nil, t)
|
||||
assert.Equal(t, http.StatusForbidden, response.Code)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAPIEndpoint_PutCurrentOrg_LegacyAccessControl(t *testing.T) {
|
||||
sc := setupHTTPServer(t, false)
|
||||
|
||||
_, err := sc.db.CreateOrgWithMember("TestOrg", testUserID)
|
||||
require.NoError(t, err)
|
||||
|
||||
input := strings.NewReader(testUpdateOrgNameForm)
|
||||
|
||||
setInitCtxSignedInViewer(sc.initCtx)
|
||||
t.Run("Viewer cannot update current org", func(t *testing.T) {
|
||||
response := callAPI(sc.server, http.MethodPut, putCurrentOrgURL, input, t)
|
||||
assert.Equal(t, http.StatusForbidden, response.Code)
|
||||
})
|
||||
|
||||
setInitCtxSignedInOrgAdmin(sc.initCtx)
|
||||
t.Run("Admin can update current org", func(t *testing.T) {
|
||||
response := callAPI(sc.server, http.MethodPut, putCurrentOrgURL, input, t)
|
||||
assert.Equal(t, http.StatusOK, response.Code)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAPIEndpoint_PutCurrentOrg_AccessControl(t *testing.T) {
|
||||
sc := setupHTTPServer(t, true)
|
||||
setInitCtxSignedInViewer(sc.initCtx)
|
||||
|
||||
_, err := sc.db.CreateOrgWithMember("TestOrg", testUserID)
|
||||
require.NoError(t, err)
|
||||
|
||||
input := strings.NewReader(testUpdateOrgNameForm)
|
||||
t.Run("AccessControl allows updating current org with correct permissions", func(t *testing.T) {
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: ActionOrgsWrite, Scope: ScopeOrgsAll}})
|
||||
response := callAPI(sc.server, http.MethodPut, putCurrentOrgURL, input, t)
|
||||
assert.Equal(t, http.StatusOK, response.Code)
|
||||
})
|
||||
|
||||
input = strings.NewReader(testUpdateOrgNameForm)
|
||||
t.Run("AccessControl allows updating current org with exact permissions", func(t *testing.T) {
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: ActionOrgsWrite, Scope: accesscontrol.Scope("orgs", "id", "1")}})
|
||||
response := callAPI(sc.server, http.MethodPut, putCurrentOrgURL, input, t)
|
||||
assert.Equal(t, http.StatusOK, response.Code)
|
||||
})
|
||||
|
||||
t.Run("AccessControl prevents updating current org with too narrow permissions", func(t *testing.T) {
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: ActionOrgsWrite, Scope: accesscontrol.Scope("orgs", "id", "2")}})
|
||||
response := callAPI(sc.server, http.MethodPut, putCurrentOrgURL, input, t)
|
||||
assert.Equal(t, http.StatusForbidden, response.Code)
|
||||
})
|
||||
|
||||
t.Run("AccessControl prevents updating current org with incorrect permissions", func(t *testing.T) {
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: "orgs:invalid"}})
|
||||
response := callAPI(sc.server, http.MethodPut, putCurrentOrgURL, input, t)
|
||||
assert.Equal(t, http.StatusForbidden, response.Code)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAPIEndpoint_PutOrg_LegacyAccessControl(t *testing.T) {
|
||||
sc := setupHTTPServer(t, false)
|
||||
sc := setupHTTPServer(t, true, false)
|
||||
setInitCtxSignedInViewer(sc.initCtx)
|
||||
|
||||
// Create two orgs, to update another one than the logged in one
|
||||
_, err := sc.db.CreateOrgWithMember("TestOrg", testUserID)
|
||||
require.NoError(t, err)
|
||||
_, err = sc.db.CreateOrgWithMember("TestOrg2", testUserID)
|
||||
require.NoError(t, err)
|
||||
setupOrgsDBForAccessControlTests(t, *sc.db, *sc.initCtx.SignedInUser, 2)
|
||||
|
||||
input := strings.NewReader(testUpdateOrgNameForm)
|
||||
|
||||
@@ -425,101 +429,38 @@ func TestAPIEndpoint_PutOrg_LegacyAccessControl(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAPIEndpoint_PutOrg_AccessControl(t *testing.T) {
|
||||
sc := setupHTTPServer(t, true)
|
||||
sc := setupHTTPServer(t, true, true)
|
||||
setInitCtxSignedInViewer(sc.initCtx)
|
||||
|
||||
// Create two orgs, to update another one than the logged in one
|
||||
_, err := sc.db.CreateOrgWithMember("TestOrg", testUserID)
|
||||
require.NoError(t, err)
|
||||
_, err = sc.db.CreateOrgWithMember("TestOrg2", testUserID)
|
||||
require.NoError(t, err)
|
||||
setupOrgsDBForAccessControlTests(t, *sc.db, *sc.initCtx.SignedInUser, 2)
|
||||
|
||||
input := strings.NewReader(testUpdateOrgNameForm)
|
||||
t.Run("AccessControl allows updating another org with correct permissions", func(t *testing.T) {
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: ActionOrgsWrite, Scope: ScopeOrgsAll}})
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: ActionOrgsWrite}}, 2)
|
||||
response := callAPI(sc.server, http.MethodPut, fmt.Sprintf(putOrgsURL, 2), input, t)
|
||||
assert.Equal(t, http.StatusOK, response.Code)
|
||||
})
|
||||
|
||||
input = strings.NewReader(testUpdateOrgNameForm)
|
||||
t.Run("AccessControl allows updating another org with exact permissions", func(t *testing.T) {
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: ActionOrgsWrite, Scope: accesscontrol.Scope("orgs", "id", "2")}})
|
||||
response := callAPI(sc.server, http.MethodPut, fmt.Sprintf(putOrgsURL, 2), input, t)
|
||||
assert.Equal(t, http.StatusOK, response.Code)
|
||||
})
|
||||
|
||||
input = strings.NewReader(testUpdateOrgNameForm)
|
||||
t.Run("AccessControl prevents updating another org with too narrow permissions", func(t *testing.T) {
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: ActionOrgsWrite, Scope: accesscontrol.Scope("orgs", "id", "1")}})
|
||||
t.Run("AccessControl prevents updating another org with correct permissions in another org", func(t *testing.T) {
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: ActionOrgsWrite}}, 1)
|
||||
response := callAPI(sc.server, http.MethodPut, fmt.Sprintf(putOrgsURL, 2), input, t)
|
||||
assert.Equal(t, http.StatusForbidden, response.Code)
|
||||
})
|
||||
|
||||
t.Run("AccessControl prevents updating another org with incorrect permissions", func(t *testing.T) {
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: "orgs:invalid"}})
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: "orgs:invalid"}}, 2)
|
||||
response := callAPI(sc.server, http.MethodPut, fmt.Sprintf(putOrgsURL, 2), input, t)
|
||||
assert.Equal(t, http.StatusForbidden, response.Code)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAPIEndpoint_PutCurrentOrgAddress_LegacyAccessControl(t *testing.T) {
|
||||
sc := setupHTTPServer(t, false)
|
||||
|
||||
_, err := sc.db.CreateOrgWithMember("TestOrg", testUserID)
|
||||
require.NoError(t, err)
|
||||
|
||||
input := strings.NewReader(testUpdateOrgAddressForm)
|
||||
|
||||
setInitCtxSignedInViewer(sc.initCtx)
|
||||
t.Run("Viewer cannot update current org address", func(t *testing.T) {
|
||||
response := callAPI(sc.server, http.MethodPut, putCurrentOrgAddressURL, input, t)
|
||||
assert.Equal(t, http.StatusForbidden, response.Code)
|
||||
})
|
||||
|
||||
setInitCtxSignedInOrgAdmin(sc.initCtx)
|
||||
t.Run("Admin can update current org address", func(t *testing.T) {
|
||||
response := callAPI(sc.server, http.MethodPut, putCurrentOrgAddressURL, input, t)
|
||||
assert.Equal(t, http.StatusOK, response.Code)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAPIEndpoint_PutCurrentOrgAddress_AccessControl(t *testing.T) {
|
||||
sc := setupHTTPServer(t, true)
|
||||
setInitCtxSignedInViewer(sc.initCtx)
|
||||
|
||||
_, err := sc.db.CreateOrgWithMember("TestOrg", testUserID)
|
||||
require.NoError(t, err)
|
||||
|
||||
input := strings.NewReader(testUpdateOrgAddressForm)
|
||||
t.Run("AccessControl allows updating current org address with correct permissions", func(t *testing.T) {
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: ActionOrgsWrite, Scope: ScopeOrgsAll}})
|
||||
response := callAPI(sc.server, http.MethodPut, putCurrentOrgAddressURL, input, t)
|
||||
assert.Equal(t, http.StatusOK, response.Code)
|
||||
})
|
||||
|
||||
input = strings.NewReader(testUpdateOrgAddressForm)
|
||||
t.Run("AccessControl allows updating current org address with exact permissions", func(t *testing.T) {
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: ActionOrgsWrite, Scope: accesscontrol.Scope("orgs", "id", "1")}})
|
||||
response := callAPI(sc.server, http.MethodPut, putCurrentOrgAddressURL, input, t)
|
||||
assert.Equal(t, http.StatusOK, response.Code)
|
||||
})
|
||||
|
||||
t.Run("AccessControl prevents updating current org address with incorrect permissions", func(t *testing.T) {
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: "orgs:invalid"}})
|
||||
response := callAPI(sc.server, http.MethodPut, putCurrentOrgAddressURL, input, t)
|
||||
assert.Equal(t, http.StatusForbidden, response.Code)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAPIEndpoint_PutOrgAddress_LegacyAccessControl(t *testing.T) {
|
||||
sc := setupHTTPServer(t, false)
|
||||
sc := setupHTTPServer(t, true, false)
|
||||
setInitCtxSignedInViewer(sc.initCtx)
|
||||
|
||||
// Create two orgs, to update another one than the logged in one
|
||||
_, err := sc.db.CreateOrgWithMember("TestOrg", testUserID)
|
||||
require.NoError(t, err)
|
||||
_, err = sc.db.CreateOrgWithMember("TestOrg2", testUserID)
|
||||
require.NoError(t, err)
|
||||
setupOrgsDBForAccessControlTests(t, *sc.db, *sc.initCtx.SignedInUser, 2)
|
||||
|
||||
input := strings.NewReader(testUpdateOrgAddressForm)
|
||||
|
||||
@@ -536,31 +477,28 @@ func TestAPIEndpoint_PutOrgAddress_LegacyAccessControl(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAPIEndpoint_PutOrgAddress_AccessControl(t *testing.T) {
|
||||
sc := setupHTTPServer(t, true)
|
||||
sc := setupHTTPServer(t, true, true)
|
||||
setInitCtxSignedInViewer(sc.initCtx)
|
||||
|
||||
// Create two orgs, to update another one than the logged in one
|
||||
_, err := sc.db.CreateOrgWithMember("TestOrg", testUserID)
|
||||
require.NoError(t, err)
|
||||
_, err = sc.db.CreateOrgWithMember("TestOrg2", testUserID)
|
||||
require.NoError(t, err)
|
||||
setupOrgsDBForAccessControlTests(t, *sc.db, *sc.initCtx.SignedInUser, 2)
|
||||
|
||||
input := strings.NewReader(testUpdateOrgAddressForm)
|
||||
t.Run("AccessControl allows updating another org address with correct permissions", func(t *testing.T) {
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: ActionOrgsWrite, Scope: ScopeOrgsAll}})
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: ActionOrgsWrite}}, 2)
|
||||
response := callAPI(sc.server, http.MethodPut, fmt.Sprintf(putOrgsAddressURL, 2), input, t)
|
||||
assert.Equal(t, http.StatusOK, response.Code)
|
||||
})
|
||||
|
||||
input = strings.NewReader(testUpdateOrgAddressForm)
|
||||
t.Run("AccessControl prevents updating another org address with too narrow permissions", func(t *testing.T) {
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: ActionOrgsWrite, Scope: accesscontrol.Scope("orgs", "id", "1")}})
|
||||
t.Run("AccessControl prevents updating another org address with correct permissions in the current org", func(t *testing.T) {
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: ActionOrgsWrite}}, 1)
|
||||
response := callAPI(sc.server, http.MethodPut, fmt.Sprintf(putOrgsAddressURL, 2), input, t)
|
||||
assert.Equal(t, http.StatusForbidden, response.Code)
|
||||
})
|
||||
|
||||
t.Run("AccessControl prevents updating another org address with incorrect permissions", func(t *testing.T) {
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: "orgs:invalid"}})
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: "orgs:invalid"}}, 2)
|
||||
response := callAPI(sc.server, http.MethodPut, fmt.Sprintf(putOrgsAddressURL, 2), input, t)
|
||||
assert.Equal(t, http.StatusForbidden, response.Code)
|
||||
})
|
||||
|
||||
@@ -6,30 +6,29 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/dtos"
|
||||
"github.com/grafana/grafana/pkg/api/response"
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
// POST /api/org/users
|
||||
func AddOrgUserToCurrentOrg(c *models.ReqContext, cmd models.AddOrgUserCommand) response.Response {
|
||||
func (hs *HTTPServer) AddOrgUserToCurrentOrg(c *models.ReqContext, cmd models.AddOrgUserCommand) response.Response {
|
||||
cmd.OrgId = c.OrgId
|
||||
return addOrgUserHelper(c.Req.Context(), cmd)
|
||||
return hs.addOrgUserHelper(c.Req.Context(), cmd)
|
||||
}
|
||||
|
||||
// POST /api/orgs/:orgId/users
|
||||
func AddOrgUser(c *models.ReqContext, cmd models.AddOrgUserCommand) response.Response {
|
||||
func (hs *HTTPServer) AddOrgUser(c *models.ReqContext, cmd models.AddOrgUserCommand) response.Response {
|
||||
cmd.OrgId = c.ParamsInt64(":orgId")
|
||||
return addOrgUserHelper(c.Req.Context(), cmd)
|
||||
return hs.addOrgUserHelper(c.Req.Context(), cmd)
|
||||
}
|
||||
|
||||
func addOrgUserHelper(ctx context.Context, cmd models.AddOrgUserCommand) response.Response {
|
||||
func (hs *HTTPServer) addOrgUserHelper(ctx context.Context, cmd models.AddOrgUserCommand) response.Response {
|
||||
if !cmd.Role.IsValid() {
|
||||
return response.Error(400, "Invalid role specified", nil)
|
||||
}
|
||||
|
||||
userQuery := models.GetUserByLoginQuery{LoginOrEmail: cmd.LoginOrEmail}
|
||||
err := bus.DispatchCtx(ctx, &userQuery)
|
||||
err := hs.SQLStore.GetUserByLogin(ctx, &userQuery)
|
||||
if err != nil {
|
||||
return response.Error(404, "User not found", nil)
|
||||
}
|
||||
@@ -38,7 +37,7 @@ func addOrgUserHelper(ctx context.Context, cmd models.AddOrgUserCommand) respons
|
||||
|
||||
cmd.UserId = userToAdd.Id
|
||||
|
||||
if err := bus.DispatchCtx(ctx, &cmd); err != nil {
|
||||
if err := hs.SQLStore.AddOrgUser(ctx, &cmd); err != nil {
|
||||
if errors.Is(err, models.ErrOrgUserAlreadyAdded) {
|
||||
return response.JSON(409, util.DynMap{
|
||||
"message": "User is already member of this organization",
|
||||
@@ -169,24 +168,24 @@ func (hs *HTTPServer) SearchOrgUsersWithPaging(ctx context.Context, c *models.Re
|
||||
}
|
||||
|
||||
// PATCH /api/org/users/:userId
|
||||
func UpdateOrgUserForCurrentOrg(c *models.ReqContext, cmd models.UpdateOrgUserCommand) response.Response {
|
||||
func (hs *HTTPServer) UpdateOrgUserForCurrentOrg(c *models.ReqContext, cmd models.UpdateOrgUserCommand) response.Response {
|
||||
cmd.OrgId = c.OrgId
|
||||
cmd.UserId = c.ParamsInt64(":userId")
|
||||
return updateOrgUserHelper(c.Req.Context(), cmd)
|
||||
return hs.updateOrgUserHelper(c.Req.Context(), cmd)
|
||||
}
|
||||
|
||||
// PATCH /api/orgs/:orgId/users/:userId
|
||||
func UpdateOrgUser(c *models.ReqContext, cmd models.UpdateOrgUserCommand) response.Response {
|
||||
func (hs *HTTPServer) UpdateOrgUser(c *models.ReqContext, cmd models.UpdateOrgUserCommand) response.Response {
|
||||
cmd.OrgId = c.ParamsInt64(":orgId")
|
||||
cmd.UserId = c.ParamsInt64(":userId")
|
||||
return updateOrgUserHelper(c.Req.Context(), cmd)
|
||||
return hs.updateOrgUserHelper(c.Req.Context(), cmd)
|
||||
}
|
||||
|
||||
func updateOrgUserHelper(ctx context.Context, cmd models.UpdateOrgUserCommand) response.Response {
|
||||
func (hs *HTTPServer) updateOrgUserHelper(ctx context.Context, cmd models.UpdateOrgUserCommand) response.Response {
|
||||
if !cmd.Role.IsValid() {
|
||||
return response.Error(400, "Invalid role specified", nil)
|
||||
}
|
||||
if err := bus.DispatchCtx(ctx, &cmd); err != nil {
|
||||
if err := hs.SQLStore.UpdateOrgUser(ctx, &cmd); err != nil {
|
||||
if errors.Is(err, models.ErrLastOrgAdmin) {
|
||||
return response.Error(400, "Cannot change role so that there is no organization admin left", nil)
|
||||
}
|
||||
@@ -197,8 +196,8 @@ func updateOrgUserHelper(ctx context.Context, cmd models.UpdateOrgUserCommand) r
|
||||
}
|
||||
|
||||
// DELETE /api/org/users/:userId
|
||||
func RemoveOrgUserForCurrentOrg(c *models.ReqContext) response.Response {
|
||||
return removeOrgUserHelper(c.Req.Context(), &models.RemoveOrgUserCommand{
|
||||
func (hs *HTTPServer) RemoveOrgUserForCurrentOrg(c *models.ReqContext) response.Response {
|
||||
return hs.removeOrgUserHelper(c.Req.Context(), &models.RemoveOrgUserCommand{
|
||||
UserId: c.ParamsInt64(":userId"),
|
||||
OrgId: c.OrgId,
|
||||
ShouldDeleteOrphanedUser: true,
|
||||
@@ -206,15 +205,15 @@ func RemoveOrgUserForCurrentOrg(c *models.ReqContext) response.Response {
|
||||
}
|
||||
|
||||
// DELETE /api/orgs/:orgId/users/:userId
|
||||
func RemoveOrgUser(c *models.ReqContext) response.Response {
|
||||
return removeOrgUserHelper(c.Req.Context(), &models.RemoveOrgUserCommand{
|
||||
func (hs *HTTPServer) RemoveOrgUser(c *models.ReqContext) response.Response {
|
||||
return hs.removeOrgUserHelper(c.Req.Context(), &models.RemoveOrgUserCommand{
|
||||
UserId: c.ParamsInt64(":userId"),
|
||||
OrgId: c.ParamsInt64(":orgId"),
|
||||
})
|
||||
}
|
||||
|
||||
func removeOrgUserHelper(ctx context.Context, cmd *models.RemoveOrgUserCommand) response.Response {
|
||||
if err := bus.DispatchCtx(ctx, cmd); err != nil {
|
||||
func (hs *HTTPServer) removeOrgUserHelper(ctx context.Context, cmd *models.RemoveOrgUserCommand) response.Response {
|
||||
if err := hs.SQLStore.RemoveOrgUser(ctx, cmd); err != nil {
|
||||
if errors.Is(err, models.ErrLastOrgAdmin) {
|
||||
return response.Error(400, "Cannot remove last organization admin", nil)
|
||||
}
|
||||
|
||||
@@ -3,27 +3,22 @@ package api
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/searchusers/filters"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/services/searchusers"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/dtos"
|
||||
"github.com/grafana/grafana/pkg/api/routing"
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
accesscontrolmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
|
||||
"github.com/grafana/grafana/pkg/services/quota"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
func setUpGetOrgUsersDB(t *testing.T, sqlStore *sqlstore.SQLStore) {
|
||||
@@ -137,39 +132,9 @@ func TestOrgUsersAPIEndpoint_userLoggedIn(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func setupOrgUsersAPIcontext(t *testing.T, role models.RoleType) (*scenarioContext, *sqlstore.SQLStore) {
|
||||
cfg := setting.NewCfg()
|
||||
db := sqlstore.InitTestDB(t)
|
||||
|
||||
hs := &HTTPServer{
|
||||
Cfg: cfg,
|
||||
QuotaService: "a.QuotaService{Cfg: cfg},
|
||||
RouteRegister: routing.NewRouteRegister(),
|
||||
AccessControl: accesscontrolmock.New().WithDisabled(),
|
||||
SQLStore: db,
|
||||
searchUsersService: searchusers.ProvideUsersService(bus.New(), filters.ProvideOSSSearchUserFilter()),
|
||||
}
|
||||
|
||||
sc := setupScenarioContext(t, "/api/org/users/lookup")
|
||||
// Create a middleware to pretend user is logged in
|
||||
pretendSignInMiddleware := func(c *models.ReqContext) {
|
||||
sc.context = c
|
||||
sc.context.UserId = testUserID
|
||||
sc.context.OrgId = testOrgID
|
||||
sc.context.Login = testUserLogin
|
||||
sc.context.OrgRole = role
|
||||
sc.context.IsSignedIn = true
|
||||
}
|
||||
sc.m.Use(pretendSignInMiddleware)
|
||||
|
||||
hs.registerRoutes()
|
||||
hs.RouteRegister.Register(sc.m.Router)
|
||||
|
||||
return sc, db
|
||||
}
|
||||
|
||||
func TestOrgUsersAPIEndpoint_LegacyAccessControl_FolderAdmin(t *testing.T) {
|
||||
sc, db := setupOrgUsersAPIcontext(t, models.ROLE_VIEWER)
|
||||
sc := setupHTTPServer(t, true, false)
|
||||
setInitCtxSignedInViewer(sc.initCtx)
|
||||
|
||||
// Create a dashboard folder
|
||||
cmd := models.SaveDashboardCommand{
|
||||
@@ -182,7 +147,7 @@ func TestOrgUsersAPIEndpoint_LegacyAccessControl_FolderAdmin(t *testing.T) {
|
||||
"tags": "prod",
|
||||
}),
|
||||
}
|
||||
folder, err := db.SaveDashboard(cmd)
|
||||
folder, err := sc.db.SaveDashboard(cmd)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, folder)
|
||||
|
||||
@@ -197,58 +162,41 @@ func TestOrgUsersAPIEndpoint_LegacyAccessControl_FolderAdmin(t *testing.T) {
|
||||
Updated: time.Now(),
|
||||
},
|
||||
}
|
||||
err = db.UpdateDashboardACL(folder.Id, acls)
|
||||
err = sc.db.UpdateDashboardACL(folder.Id, acls)
|
||||
require.NoError(t, err)
|
||||
|
||||
sc.resp = httptest.NewRecorder()
|
||||
|
||||
sc.req, err = http.NewRequest(http.MethodGet, "/api/org/users/lookup", nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
sc.exec()
|
||||
assert.Equal(t, http.StatusOK, sc.resp.Code)
|
||||
response := callAPI(sc.server, http.MethodGet, "/api/org/users/lookup", nil, t)
|
||||
assert.Equal(t, http.StatusOK, response.Code)
|
||||
}
|
||||
|
||||
func TestOrgUsersAPIEndpoint_LegacyAccessControl_TeamAdmin(t *testing.T) {
|
||||
sc, db := setupOrgUsersAPIcontext(t, models.ROLE_VIEWER)
|
||||
sc := setupHTTPServer(t, true, false)
|
||||
setInitCtxSignedInViewer(sc.initCtx)
|
||||
|
||||
// Setup store teams
|
||||
team1, err := db.CreateTeam("testteam1", "testteam1@example.org", testOrgID)
|
||||
team1, err := sc.db.CreateTeam("testteam1", "testteam1@example.org", testOrgID)
|
||||
require.NoError(t, err)
|
||||
err = db.AddTeamMember(testUserID, testOrgID, team1.Id, false, models.PERMISSION_ADMIN)
|
||||
err = sc.db.AddTeamMember(testUserID, testOrgID, team1.Id, false, models.PERMISSION_ADMIN)
|
||||
require.NoError(t, err)
|
||||
|
||||
sc.resp = httptest.NewRecorder()
|
||||
|
||||
sc.req, err = http.NewRequest(http.MethodGet, "/api/org/users/lookup", nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
sc.exec()
|
||||
assert.Equal(t, http.StatusOK, sc.resp.Code)
|
||||
response := callAPI(sc.server, http.MethodGet, "/api/org/users/lookup", nil, t)
|
||||
assert.Equal(t, http.StatusOK, response.Code)
|
||||
}
|
||||
|
||||
func TestOrgUsersAPIEndpoint_LegacyAccessControl_Admin(t *testing.T) {
|
||||
sc, _ := setupOrgUsersAPIcontext(t, models.ROLE_ADMIN)
|
||||
sc.resp = httptest.NewRecorder()
|
||||
sc := setupHTTPServer(t, true, false)
|
||||
setInitCtxSignedInOrgAdmin(sc.initCtx)
|
||||
|
||||
var err error
|
||||
sc.req, err = http.NewRequest(http.MethodGet, "/api/org/users/lookup", nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
sc.exec()
|
||||
assert.Equal(t, http.StatusOK, sc.resp.Code)
|
||||
response := callAPI(sc.server, http.MethodGet, "/api/org/users/lookup", nil, t)
|
||||
assert.Equal(t, http.StatusOK, response.Code)
|
||||
}
|
||||
|
||||
func TestOrgUsersAPIEndpoint_LegacyAccessControl_Viewer(t *testing.T) {
|
||||
sc, _ := setupOrgUsersAPIcontext(t, models.ROLE_VIEWER)
|
||||
sc.resp = httptest.NewRecorder()
|
||||
sc := setupHTTPServer(t, true, false)
|
||||
setInitCtxSignedInViewer(sc.initCtx)
|
||||
|
||||
var err error
|
||||
sc.req, err = http.NewRequest(http.MethodGet, "/api/org/users/lookup", nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
sc.exec()
|
||||
assert.Equal(t, http.StatusForbidden, sc.resp.Code)
|
||||
response := callAPI(sc.server, http.MethodGet, "/api/org/users/lookup", nil, t)
|
||||
assert.Equal(t, http.StatusForbidden, response.Code)
|
||||
}
|
||||
|
||||
func TestOrgUsersAPIEndpoint_AccessControl(t *testing.T) {
|
||||
@@ -271,29 +219,534 @@ func TestOrgUsersAPIEndpoint_AccessControl(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
cfg := setting.NewCfg()
|
||||
sc, hs := setupAccessControlScenarioContext(t, cfg, test.url, test.permissions)
|
||||
sc := setupHTTPServer(t, true, true)
|
||||
setInitCtxSignedInViewer(sc.initCtx)
|
||||
setAccessControlPermissions(sc.acmock, test.permissions, sc.initCtx.OrgId)
|
||||
|
||||
// Create a middleware to pretend user is logged in
|
||||
pretendSignInMiddleware := func(c *models.ReqContext) {
|
||||
sc.context = c
|
||||
sc.context.UserId = testUserID
|
||||
sc.context.OrgId = testOrgID
|
||||
sc.context.Login = testUserLogin
|
||||
sc.context.OrgRole = models.ROLE_VIEWER
|
||||
sc.context.IsSignedIn = true
|
||||
}
|
||||
sc.m.Use(pretendSignInMiddleware)
|
||||
|
||||
sc.resp = httptest.NewRecorder()
|
||||
hs.SettingsProvider = &setting.OSSImpl{Cfg: cfg}
|
||||
|
||||
var err error
|
||||
sc.req, err = http.NewRequest(test.method, test.url, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
sc.exec()
|
||||
assert.Equal(t, test.expectedCode, sc.resp.Code)
|
||||
response := callAPI(sc.server, http.MethodGet, test.url, nil, t)
|
||||
assert.Equal(t, test.expectedCode, response.Code)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
testServerAdminViewer = models.SignedInUser{
|
||||
UserId: 1,
|
||||
OrgId: 1,
|
||||
OrgName: "TestOrg1",
|
||||
OrgRole: models.ROLE_VIEWER,
|
||||
Login: "testServerAdmin",
|
||||
Name: "testServerAdmin",
|
||||
Email: "testServerAdmin@example.org",
|
||||
OrgCount: 2,
|
||||
IsGrafanaAdmin: true,
|
||||
IsAnonymous: false,
|
||||
}
|
||||
|
||||
testAdminOrg2 = models.SignedInUser{
|
||||
UserId: 2,
|
||||
OrgId: 2,
|
||||
OrgName: "TestOrg2",
|
||||
OrgRole: models.ROLE_ADMIN,
|
||||
Login: "testAdmin",
|
||||
Name: "testAdmin",
|
||||
Email: "testAdmin@example.org",
|
||||
OrgCount: 1,
|
||||
IsGrafanaAdmin: false,
|
||||
IsAnonymous: false,
|
||||
}
|
||||
|
||||
testEditorOrg1 = models.SignedInUser{
|
||||
UserId: 3,
|
||||
OrgId: 1,
|
||||
OrgName: "TestOrg1",
|
||||
OrgRole: models.ROLE_EDITOR,
|
||||
Login: "testEditor",
|
||||
Name: "testEditor",
|
||||
Email: "testEditor@example.org",
|
||||
OrgCount: 1,
|
||||
IsGrafanaAdmin: false,
|
||||
IsAnonymous: false,
|
||||
}
|
||||
)
|
||||
|
||||
// setupOrgUsersDBForAccessControlTests creates three users placed in two orgs
|
||||
// Org1: testServerAdminViewer, testEditorOrg1
|
||||
// Org2: testServerAdminViewer, testAdminOrg2
|
||||
func setupOrgUsersDBForAccessControlTests(t *testing.T, db sqlstore.SQLStore) {
|
||||
t.Helper()
|
||||
|
||||
var err error
|
||||
|
||||
_, err = db.CreateUser(context.Background(), models.CreateUserCommand{Email: testServerAdminViewer.Email, SkipOrgSetup: true, Login: testServerAdminViewer.Login})
|
||||
require.NoError(t, err)
|
||||
_, err = db.CreateUser(context.Background(), models.CreateUserCommand{Email: testAdminOrg2.Email, SkipOrgSetup: true, Login: testAdminOrg2.Login})
|
||||
require.NoError(t, err)
|
||||
_, err = db.CreateUser(context.Background(), models.CreateUserCommand{Email: testEditorOrg1.Email, SkipOrgSetup: true, Login: testEditorOrg1.Login})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create both orgs with server admin
|
||||
_, err = db.CreateOrgWithMember(testServerAdminViewer.OrgName, testServerAdminViewer.UserId)
|
||||
require.NoError(t, err)
|
||||
_, err = db.CreateOrgWithMember(testAdminOrg2.OrgName, testServerAdminViewer.UserId)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = db.AddOrgUser(context.Background(), &models.AddOrgUserCommand{LoginOrEmail: testAdminOrg2.Login, Role: testAdminOrg2.OrgRole, OrgId: testAdminOrg2.OrgId, UserId: testAdminOrg2.UserId})
|
||||
require.NoError(t, err)
|
||||
err = db.AddOrgUser(context.Background(), &models.AddOrgUserCommand{LoginOrEmail: testEditorOrg1.Login, Role: testEditorOrg1.OrgRole, OrgId: testEditorOrg1.OrgId, UserId: testEditorOrg1.UserId})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestGetOrgUsersAPIEndpoint_AccessControl(t *testing.T) {
|
||||
url := "/api/orgs/%v/users/"
|
||||
type testCase struct {
|
||||
name string
|
||||
enableAccessControl bool
|
||||
expectedCode int
|
||||
expectedUserCount int
|
||||
user models.SignedInUser
|
||||
targetOrg int64
|
||||
}
|
||||
|
||||
tests := []testCase{
|
||||
{
|
||||
name: "server admin can get users in his org (legacy)",
|
||||
enableAccessControl: false,
|
||||
expectedCode: http.StatusOK,
|
||||
expectedUserCount: 2,
|
||||
user: testServerAdminViewer,
|
||||
targetOrg: testServerAdminViewer.OrgId,
|
||||
},
|
||||
{
|
||||
name: "server admin can get users in another org (legacy)",
|
||||
enableAccessControl: false,
|
||||
expectedCode: http.StatusOK,
|
||||
expectedUserCount: 2,
|
||||
user: testServerAdminViewer,
|
||||
targetOrg: 2,
|
||||
},
|
||||
{
|
||||
name: "org admin cannot get users in his org (legacy)",
|
||||
enableAccessControl: false,
|
||||
expectedCode: http.StatusForbidden,
|
||||
user: testAdminOrg2,
|
||||
targetOrg: testAdminOrg2.OrgId,
|
||||
},
|
||||
{
|
||||
name: "org admin cannot get users in another org (legacy)",
|
||||
enableAccessControl: false,
|
||||
expectedCode: http.StatusForbidden,
|
||||
user: testAdminOrg2,
|
||||
targetOrg: 1,
|
||||
},
|
||||
{
|
||||
name: "server admin can get users in his org",
|
||||
enableAccessControl: true,
|
||||
expectedCode: http.StatusOK,
|
||||
expectedUserCount: 2,
|
||||
user: testServerAdminViewer,
|
||||
targetOrg: testServerAdminViewer.OrgId,
|
||||
},
|
||||
{
|
||||
name: "server admin can get users in another org",
|
||||
enableAccessControl: true,
|
||||
expectedCode: http.StatusOK,
|
||||
expectedUserCount: 2,
|
||||
user: testServerAdminViewer,
|
||||
targetOrg: 2,
|
||||
},
|
||||
{
|
||||
name: "org admin can get users in his org",
|
||||
enableAccessControl: true,
|
||||
expectedCode: http.StatusOK,
|
||||
expectedUserCount: 2,
|
||||
user: testAdminOrg2,
|
||||
targetOrg: testAdminOrg2.OrgId,
|
||||
},
|
||||
{
|
||||
name: "org admin cannot get users in another org",
|
||||
enableAccessControl: true,
|
||||
expectedCode: http.StatusForbidden,
|
||||
user: testAdminOrg2,
|
||||
targetOrg: 1,
|
||||
},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
sc := setupHTTPServer(t, false, tc.enableAccessControl)
|
||||
setupOrgUsersDBForAccessControlTests(t, *sc.db)
|
||||
setInitCtxSignedInUser(sc.initCtx, tc.user)
|
||||
|
||||
// Perform test
|
||||
response := callAPI(sc.server, http.MethodGet, fmt.Sprintf(url, tc.targetOrg), nil, t)
|
||||
require.Equal(t, tc.expectedCode, response.Code)
|
||||
|
||||
if tc.expectedCode != http.StatusForbidden {
|
||||
var userList []*models.OrgUserDTO
|
||||
err := json.NewDecoder(response.Body).Decode(&userList)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Len(t, userList, tc.expectedUserCount)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPostOrgUsersAPIEndpoint_AccessControl(t *testing.T) {
|
||||
url := "/api/orgs/%v/users/"
|
||||
type testCase struct {
|
||||
name string
|
||||
enableAccessControl bool
|
||||
user models.SignedInUser
|
||||
targetOrg int64
|
||||
input string
|
||||
expectedCode int
|
||||
expectedMessage util.DynMap
|
||||
expectedUserCount int
|
||||
}
|
||||
|
||||
tests := []testCase{
|
||||
{
|
||||
name: "server admin can add users to his org (legacy)",
|
||||
enableAccessControl: false,
|
||||
user: testServerAdminViewer,
|
||||
targetOrg: testServerAdminViewer.OrgId,
|
||||
input: `{"loginOrEmail": "` + testAdminOrg2.Login + `", "role": "` + string(testAdminOrg2.OrgRole) + `"}`,
|
||||
expectedCode: http.StatusOK,
|
||||
expectedMessage: util.DynMap{"message": "User added to organization", "userId": float64(testAdminOrg2.UserId)},
|
||||
expectedUserCount: 3,
|
||||
},
|
||||
{
|
||||
name: "server admin can add users to another org (legacy)",
|
||||
enableAccessControl: false,
|
||||
user: testServerAdminViewer,
|
||||
targetOrg: 2,
|
||||
input: `{"loginOrEmail": "` + testEditorOrg1.Login + `", "role": "` + string(testEditorOrg1.OrgRole) + `"}`,
|
||||
expectedCode: http.StatusOK,
|
||||
expectedMessage: util.DynMap{"message": "User added to organization", "userId": float64(testEditorOrg1.UserId)},
|
||||
expectedUserCount: 3,
|
||||
},
|
||||
{
|
||||
name: "org admin cannot add users to his org (legacy)",
|
||||
enableAccessControl: false,
|
||||
expectedCode: http.StatusForbidden,
|
||||
user: testAdminOrg2,
|
||||
targetOrg: testAdminOrg2.OrgId,
|
||||
input: `{"loginOrEmail": "` + testEditorOrg1.Login + `", "role": "` + string(testEditorOrg1.OrgRole) + `"}`,
|
||||
},
|
||||
{
|
||||
name: "org admin cannot add users to another org (legacy)",
|
||||
enableAccessControl: false,
|
||||
expectedCode: http.StatusForbidden,
|
||||
user: testAdminOrg2,
|
||||
targetOrg: 1,
|
||||
input: `{"loginOrEmail": "` + testAdminOrg2.Login + `", "role": "` + string(testAdminOrg2.OrgRole) + `"}`,
|
||||
},
|
||||
{
|
||||
name: "server admin can add users to his org",
|
||||
enableAccessControl: true,
|
||||
user: testServerAdminViewer,
|
||||
targetOrg: testServerAdminViewer.OrgId,
|
||||
input: `{"loginOrEmail": "` + testAdminOrg2.Login + `", "role": "` + string(testAdminOrg2.OrgRole) + `"}`,
|
||||
expectedCode: http.StatusOK,
|
||||
expectedMessage: util.DynMap{"message": "User added to organization", "userId": float64(testAdminOrg2.UserId)},
|
||||
expectedUserCount: 3,
|
||||
},
|
||||
{
|
||||
name: "server admin can add users to another org",
|
||||
enableAccessControl: true,
|
||||
user: testServerAdminViewer,
|
||||
targetOrg: 2,
|
||||
input: `{"loginOrEmail": "` + testEditorOrg1.Login + `", "role": "` + string(testEditorOrg1.OrgRole) + `"}`,
|
||||
expectedCode: http.StatusOK,
|
||||
expectedMessage: util.DynMap{"message": "User added to organization", "userId": float64(testEditorOrg1.UserId)},
|
||||
expectedUserCount: 3,
|
||||
},
|
||||
{
|
||||
name: "org admin can add users to his org",
|
||||
enableAccessControl: true,
|
||||
user: testAdminOrg2,
|
||||
targetOrg: testAdminOrg2.OrgId,
|
||||
input: `{"loginOrEmail": "` + testEditorOrg1.Login + `", "role": "` + string(testEditorOrg1.OrgRole) + `"}`,
|
||||
expectedCode: http.StatusOK,
|
||||
expectedMessage: util.DynMap{"message": "User added to organization", "userId": float64(testEditorOrg1.UserId)},
|
||||
expectedUserCount: 3,
|
||||
},
|
||||
{
|
||||
name: "org admin cannot add users to another org",
|
||||
enableAccessControl: true,
|
||||
expectedCode: http.StatusForbidden,
|
||||
user: testAdminOrg2,
|
||||
targetOrg: 1,
|
||||
input: `{"loginOrEmail": "` + testAdminOrg2.Login + `", "role": "` + string(testAdminOrg2.OrgRole) + `"}`,
|
||||
},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
sc := setupHTTPServer(t, false, tc.enableAccessControl)
|
||||
setupOrgUsersDBForAccessControlTests(t, *sc.db)
|
||||
setInitCtxSignedInUser(sc.initCtx, tc.user)
|
||||
|
||||
// Perform request
|
||||
input := strings.NewReader(tc.input)
|
||||
response := callAPI(sc.server, http.MethodPost, fmt.Sprintf(url, tc.targetOrg), input, t)
|
||||
assert.Equal(t, tc.expectedCode, response.Code)
|
||||
|
||||
if tc.expectedCode != http.StatusForbidden {
|
||||
// Check result
|
||||
var message util.DynMap
|
||||
err := json.NewDecoder(response.Body).Decode(&message)
|
||||
require.NoError(t, err)
|
||||
assert.EqualValuesf(t, tc.expectedMessage, message, "server did not answer expected message")
|
||||
|
||||
getUsersQuery := models.GetOrgUsersQuery{OrgId: tc.targetOrg}
|
||||
err = sc.db.GetOrgUsers(context.Background(), &getUsersQuery)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, getUsersQuery.Result, tc.expectedUserCount)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPatchOrgUsersAPIEndpoint_AccessControl(t *testing.T) {
|
||||
url := "/api/orgs/%v/users/%v"
|
||||
type testCase struct {
|
||||
name string
|
||||
enableAccessControl bool
|
||||
user models.SignedInUser
|
||||
targetUserId int64
|
||||
targetOrg int64
|
||||
input string
|
||||
expectedCode int
|
||||
expectedMessage util.DynMap
|
||||
expectedUserRole models.RoleType
|
||||
}
|
||||
|
||||
tests := []testCase{
|
||||
{
|
||||
name: "server admin can update users in his org (legacy)",
|
||||
enableAccessControl: false,
|
||||
user: testServerAdminViewer,
|
||||
targetUserId: testEditorOrg1.UserId,
|
||||
targetOrg: testServerAdminViewer.OrgId,
|
||||
input: `{"role": "Viewer"}`,
|
||||
expectedCode: http.StatusOK,
|
||||
expectedMessage: util.DynMap{"message": "Organization user updated"},
|
||||
expectedUserRole: models.ROLE_VIEWER,
|
||||
},
|
||||
{
|
||||
name: "server admin can update users in another org (legacy)",
|
||||
enableAccessControl: false,
|
||||
user: testServerAdminViewer,
|
||||
targetUserId: testServerAdminViewer.UserId,
|
||||
targetOrg: 2,
|
||||
input: `{"role": "Editor"}`,
|
||||
expectedCode: http.StatusOK,
|
||||
expectedMessage: util.DynMap{"message": "Organization user updated"},
|
||||
expectedUserRole: models.ROLE_EDITOR,
|
||||
},
|
||||
{
|
||||
name: "org admin cannot update users in his org (legacy)",
|
||||
enableAccessControl: false,
|
||||
user: testAdminOrg2,
|
||||
targetUserId: testServerAdminViewer.UserId,
|
||||
targetOrg: testAdminOrg2.OrgId,
|
||||
input: `{"role": "Editor"}`,
|
||||
expectedCode: http.StatusForbidden,
|
||||
},
|
||||
{
|
||||
name: "org admin cannot update users in another org (legacy)",
|
||||
enableAccessControl: false,
|
||||
user: testAdminOrg2,
|
||||
targetUserId: testServerAdminViewer.UserId,
|
||||
targetOrg: 1,
|
||||
input: `{"role": "Editor"}`,
|
||||
expectedCode: http.StatusForbidden,
|
||||
},
|
||||
{
|
||||
name: "server admin can update users in his org",
|
||||
enableAccessControl: true,
|
||||
user: testServerAdminViewer,
|
||||
targetUserId: testEditorOrg1.UserId,
|
||||
targetOrg: testServerAdminViewer.OrgId,
|
||||
input: `{"role": "Viewer"}`,
|
||||
expectedCode: http.StatusOK,
|
||||
expectedMessage: util.DynMap{"message": "Organization user updated"},
|
||||
expectedUserRole: models.ROLE_VIEWER,
|
||||
},
|
||||
{
|
||||
name: "server admin can update users in another org",
|
||||
enableAccessControl: true,
|
||||
user: testServerAdminViewer,
|
||||
targetUserId: testServerAdminViewer.UserId,
|
||||
targetOrg: 2,
|
||||
input: `{"role": "Editor"}`,
|
||||
expectedCode: http.StatusOK,
|
||||
expectedMessage: util.DynMap{"message": "Organization user updated"},
|
||||
expectedUserRole: models.ROLE_EDITOR,
|
||||
},
|
||||
{
|
||||
name: "org admin can update users in his org",
|
||||
enableAccessControl: true,
|
||||
user: testAdminOrg2,
|
||||
targetUserId: testServerAdminViewer.UserId,
|
||||
targetOrg: testAdminOrg2.OrgId,
|
||||
input: `{"role": "Editor"}`,
|
||||
expectedCode: http.StatusOK,
|
||||
expectedMessage: util.DynMap{"message": "Organization user updated"},
|
||||
expectedUserRole: models.ROLE_EDITOR,
|
||||
},
|
||||
{
|
||||
name: "org admin cannot update users in another org",
|
||||
enableAccessControl: true,
|
||||
user: testAdminOrg2,
|
||||
targetUserId: testServerAdminViewer.UserId,
|
||||
targetOrg: 1,
|
||||
input: `{"role": "Editor"}`,
|
||||
expectedCode: http.StatusForbidden,
|
||||
},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
sc := setupHTTPServer(t, false, tc.enableAccessControl)
|
||||
setupOrgUsersDBForAccessControlTests(t, *sc.db)
|
||||
setInitCtxSignedInUser(sc.initCtx, tc.user)
|
||||
|
||||
// Perform request
|
||||
input := strings.NewReader(tc.input)
|
||||
setInitCtxSignedInUser(sc.initCtx, tc.user)
|
||||
response := callAPI(sc.server, http.MethodPatch, fmt.Sprintf(url, tc.targetOrg, tc.targetUserId), input, t)
|
||||
assert.Equal(t, tc.expectedCode, response.Code)
|
||||
|
||||
if tc.expectedCode != http.StatusForbidden {
|
||||
// Check result
|
||||
var message util.DynMap
|
||||
err := json.NewDecoder(response.Body).Decode(&message)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tc.expectedMessage, message)
|
||||
|
||||
getUserQuery := models.GetSignedInUserQuery{
|
||||
UserId: tc.targetUserId,
|
||||
OrgId: tc.targetOrg,
|
||||
}
|
||||
err = sqlstore.GetSignedInUser(context.TODO(), &getUserQuery)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tc.expectedUserRole, getUserQuery.Result.OrgRole)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteOrgUsersAPIEndpoint_AccessControl(t *testing.T) {
|
||||
url := "/api/orgs/%v/users/%v"
|
||||
type testCase struct {
|
||||
name string
|
||||
enableAccessControl bool
|
||||
user models.SignedInUser
|
||||
targetUserId int64
|
||||
targetOrg int64
|
||||
expectedCode int
|
||||
expectedMessage util.DynMap
|
||||
expectedUserCount int
|
||||
}
|
||||
|
||||
tests := []testCase{
|
||||
{
|
||||
name: "server admin can delete users from his org (legacy)",
|
||||
enableAccessControl: false,
|
||||
user: testServerAdminViewer,
|
||||
targetUserId: testEditorOrg1.UserId,
|
||||
targetOrg: testServerAdminViewer.OrgId,
|
||||
expectedCode: http.StatusOK,
|
||||
expectedMessage: util.DynMap{"message": "User removed from organization"},
|
||||
expectedUserCount: 1,
|
||||
},
|
||||
{
|
||||
name: "server admin can delete users from another org (legacy)",
|
||||
enableAccessControl: false,
|
||||
user: testServerAdminViewer,
|
||||
targetUserId: testServerAdminViewer.UserId,
|
||||
targetOrg: 2,
|
||||
expectedCode: http.StatusOK,
|
||||
expectedMessage: util.DynMap{"message": "User removed from organization"},
|
||||
expectedUserCount: 1,
|
||||
},
|
||||
{
|
||||
name: "org admin can delete users from his org (legacy)",
|
||||
enableAccessControl: false,
|
||||
user: testAdminOrg2,
|
||||
targetUserId: testServerAdminViewer.UserId,
|
||||
targetOrg: testAdminOrg2.OrgId,
|
||||
expectedCode: http.StatusForbidden,
|
||||
},
|
||||
{
|
||||
name: "org admin cannot delete users from another org (legacy)",
|
||||
enableAccessControl: false,
|
||||
user: testAdminOrg2,
|
||||
targetUserId: testEditorOrg1.UserId,
|
||||
targetOrg: 1,
|
||||
expectedCode: http.StatusForbidden,
|
||||
},
|
||||
{
|
||||
name: "server admin can delete users from his org",
|
||||
enableAccessControl: true,
|
||||
user: testServerAdminViewer,
|
||||
targetUserId: testEditorOrg1.UserId,
|
||||
targetOrg: testServerAdminViewer.OrgId,
|
||||
expectedCode: http.StatusOK,
|
||||
expectedMessage: util.DynMap{"message": "User removed from organization"},
|
||||
expectedUserCount: 1,
|
||||
},
|
||||
{
|
||||
name: "server admin can delete users from another org",
|
||||
enableAccessControl: true,
|
||||
user: testServerAdminViewer,
|
||||
targetUserId: testServerAdminViewer.UserId,
|
||||
targetOrg: 2,
|
||||
expectedCode: http.StatusOK,
|
||||
expectedMessage: util.DynMap{"message": "User removed from organization"},
|
||||
expectedUserCount: 1,
|
||||
},
|
||||
{
|
||||
name: "org admin can delete users from his org",
|
||||
enableAccessControl: true,
|
||||
user: testAdminOrg2,
|
||||
targetUserId: testServerAdminViewer.UserId,
|
||||
targetOrg: testAdminOrg2.OrgId,
|
||||
expectedCode: http.StatusOK,
|
||||
expectedMessage: util.DynMap{"message": "User removed from organization"},
|
||||
expectedUserCount: 1,
|
||||
},
|
||||
{
|
||||
name: "org admin cannot delete users from another org",
|
||||
enableAccessControl: true,
|
||||
user: testAdminOrg2,
|
||||
targetUserId: testEditorOrg1.UserId,
|
||||
targetOrg: 1,
|
||||
expectedCode: http.StatusForbidden,
|
||||
},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
sc := setupHTTPServer(t, false, tc.enableAccessControl)
|
||||
setupOrgUsersDBForAccessControlTests(t, *sc.db)
|
||||
setInitCtxSignedInUser(sc.initCtx, tc.user)
|
||||
|
||||
response := callAPI(sc.server, http.MethodDelete, fmt.Sprintf(url, tc.targetOrg, tc.targetUserId), nil, t)
|
||||
assert.Equal(t, tc.expectedCode, response.Code)
|
||||
|
||||
if tc.expectedCode != http.StatusForbidden {
|
||||
// Check result
|
||||
var message util.DynMap
|
||||
err := json.NewDecoder(response.Body).Decode(&message)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tc.expectedMessage, message)
|
||||
|
||||
getUsersQuery := models.GetOrgUsersQuery{OrgId: tc.targetOrg}
|
||||
err = sc.db.GetOrgUsers(context.Background(), &getUsersQuery)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, getUsersQuery.Result, tc.expectedUserCount)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ var (
|
||||
)
|
||||
|
||||
func TestAPIEndpoint_GetCurrentOrgPreferences_LegacyAccessControl(t *testing.T) {
|
||||
sc := setupHTTPServer(t, false)
|
||||
sc := setupHTTPServer(t, true, false)
|
||||
|
||||
_, err := sc.db.CreateOrgWithMember("TestOrg", testUserID)
|
||||
require.NoError(t, err)
|
||||
@@ -37,31 +37,31 @@ func TestAPIEndpoint_GetCurrentOrgPreferences_LegacyAccessControl(t *testing.T)
|
||||
}
|
||||
|
||||
func TestAPIEndpoint_GetCurrentOrgPreferences_AccessControl(t *testing.T) {
|
||||
sc := setupHTTPServer(t, true)
|
||||
sc := setupHTTPServer(t, true, true)
|
||||
setInitCtxSignedInViewer(sc.initCtx)
|
||||
|
||||
_, err := sc.db.CreateOrgWithMember("TestOrg", testUserID)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("AccessControl allows getting org preferences with correct permissions", func(t *testing.T) {
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: ActionOrgsPreferencesRead, Scope: ScopeOrgsAll}})
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: ActionOrgsPreferencesRead}}, sc.initCtx.OrgId)
|
||||
response := callAPI(sc.server, http.MethodGet, getOrgPreferencesURL, nil, t)
|
||||
assert.Equal(t, http.StatusOK, response.Code)
|
||||
})
|
||||
t.Run("AccessControl allows getting org preferences with exact permissions", func(t *testing.T) {
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: ActionOrgsPreferencesRead, Scope: accesscontrol.Scope("orgs", "id", "1")}})
|
||||
t.Run("AccessControl prevents getting org preferences with correct permissions in another org", func(t *testing.T) {
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: ActionOrgsPreferencesRead}}, 2)
|
||||
response := callAPI(sc.server, http.MethodGet, getOrgPreferencesURL, nil, t)
|
||||
assert.Equal(t, http.StatusOK, response.Code)
|
||||
assert.Equal(t, http.StatusForbidden, response.Code)
|
||||
})
|
||||
t.Run("AccessControl prevents getting org preferences with incorrect permissions", func(t *testing.T) {
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: "orgs:invalid"}})
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: "orgs:invalid"}}, sc.initCtx.OrgId)
|
||||
response := callAPI(sc.server, http.MethodGet, getOrgPreferencesURL, nil, t)
|
||||
assert.Equal(t, http.StatusForbidden, response.Code)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAPIEndpoint_PutCurrentOrgPreferences_LegacyAccessControl(t *testing.T) {
|
||||
sc := setupHTTPServer(t, false)
|
||||
sc := setupHTTPServer(t, true, false)
|
||||
|
||||
_, err := sc.db.CreateOrgWithMember("TestOrg", testUserID)
|
||||
require.NoError(t, err)
|
||||
@@ -82,7 +82,7 @@ func TestAPIEndpoint_PutCurrentOrgPreferences_LegacyAccessControl(t *testing.T)
|
||||
}
|
||||
|
||||
func TestAPIEndpoint_PutCurrentOrgPreferences_AccessControl(t *testing.T) {
|
||||
sc := setupHTTPServer(t, true)
|
||||
sc := setupHTTPServer(t, true, true)
|
||||
setInitCtxSignedInViewer(sc.initCtx)
|
||||
|
||||
_, err := sc.db.CreateOrgWithMember("TestOrg", testUserID)
|
||||
@@ -90,21 +90,21 @@ func TestAPIEndpoint_PutCurrentOrgPreferences_AccessControl(t *testing.T) {
|
||||
|
||||
input := strings.NewReader(testUpdateOrgPreferencesCmd)
|
||||
t.Run("AccessControl allows updating org preferences with correct permissions", func(t *testing.T) {
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: ActionOrgsPreferencesWrite, Scope: ScopeOrgsAll}})
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: ActionOrgsPreferencesWrite}}, sc.initCtx.OrgId)
|
||||
response := callAPI(sc.server, http.MethodPut, putOrgPreferencesURL, input, t)
|
||||
assert.Equal(t, http.StatusOK, response.Code)
|
||||
})
|
||||
|
||||
input = strings.NewReader(testUpdateOrgPreferencesCmd)
|
||||
t.Run("AccessControl allows updating org preferences with exact permissions", func(t *testing.T) {
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: ActionOrgsPreferencesWrite, Scope: accesscontrol.Scope("orgs", "id", "1")}})
|
||||
t.Run("AccessControl prevents updating org preferences with correct permissions in another org", func(t *testing.T) {
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: ActionOrgsPreferencesWrite}}, 2)
|
||||
response := callAPI(sc.server, http.MethodPut, putOrgPreferencesURL, input, t)
|
||||
assert.Equal(t, http.StatusOK, response.Code)
|
||||
assert.Equal(t, http.StatusForbidden, response.Code)
|
||||
})
|
||||
|
||||
input = strings.NewReader(testUpdateOrgPreferencesCmd)
|
||||
t.Run("AccessControl prevents updating org preferences with incorrect permissions", func(t *testing.T) {
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: "orgs:invalid"}})
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: "orgs:invalid"}}, sc.initCtx.OrgId)
|
||||
response := callAPI(sc.server, http.MethodPut, putOrgPreferencesURL, input, t)
|
||||
assert.Equal(t, http.StatusForbidden, response.Code)
|
||||
})
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
@@ -29,17 +28,24 @@ var testOrgQuota = setting.OrgQuota{
|
||||
AlertRule: 10,
|
||||
}
|
||||
|
||||
func TestAPIEndpoint_GetCurrentOrgQuotas_LegacyAccessControl(t *testing.T) {
|
||||
sc := setupHTTPServer(t, false)
|
||||
setInitCtxSignedInViewer(sc.initCtx)
|
||||
// setupDBAndSettingsForAccessControlQuotaTests stores users and create two orgs
|
||||
func setupDBAndSettingsForAccessControlQuotaTests(t *testing.T, sc accessControlScenarioContext) {
|
||||
t.Helper()
|
||||
|
||||
sc.hs.Cfg.Quota.Enabled = true
|
||||
sc.hs.Cfg.Quota.Org = &testOrgQuota
|
||||
// Required while sqlstore quota.go relies on setting global variables
|
||||
setting.Quota = sc.hs.Cfg.Quota
|
||||
|
||||
_, err := sc.db.CreateOrgWithMember("TestOrg", testUserID)
|
||||
require.NoError(t, err)
|
||||
// Create two orgs with the context user
|
||||
setupOrgsDBForAccessControlTests(t, *sc.db, *sc.initCtx.SignedInUser, 2)
|
||||
}
|
||||
|
||||
func TestAPIEndpoint_GetCurrentOrgQuotas_LegacyAccessControl(t *testing.T) {
|
||||
sc := setupHTTPServer(t, true, false)
|
||||
setInitCtxSignedInViewer(sc.initCtx)
|
||||
|
||||
setupDBAndSettingsForAccessControlQuotaTests(t, sc)
|
||||
|
||||
t.Run("Viewer can view CurrentOrgQuotas", func(t *testing.T) {
|
||||
response := callAPI(sc.server, http.MethodGet, getCurrentOrgQuotasURL, nil, t)
|
||||
@@ -54,48 +60,33 @@ func TestAPIEndpoint_GetCurrentOrgQuotas_LegacyAccessControl(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAPIEndpoint_GetCurrentOrgQuotas_AccessControl(t *testing.T) {
|
||||
sc := setupHTTPServer(t, true)
|
||||
sc := setupHTTPServer(t, true, true)
|
||||
setInitCtxSignedInViewer(sc.initCtx)
|
||||
|
||||
sc.hs.Cfg.Quota.Enabled = true
|
||||
sc.hs.Cfg.Quota.Org = &testOrgQuota
|
||||
// Required while sqlstore quota.go relies on setting global variables
|
||||
setting.Quota = sc.hs.Cfg.Quota
|
||||
|
||||
_, err := sc.db.CreateOrgWithMember("TestOrg", testUserID)
|
||||
require.NoError(t, err)
|
||||
setupDBAndSettingsForAccessControlQuotaTests(t, sc)
|
||||
|
||||
t.Run("AccessControl allows viewing CurrentOrgQuotas with correct permissions", func(t *testing.T) {
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: ActionOrgsQuotasRead, Scope: ScopeOrgsAll}})
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: ActionOrgsQuotasRead}}, sc.initCtx.OrgId)
|
||||
response := callAPI(sc.server, http.MethodGet, getCurrentOrgQuotasURL, nil, t)
|
||||
assert.Equal(t, http.StatusOK, response.Code)
|
||||
})
|
||||
t.Run("AccessControl allows viewing CurrentOrgQuotas with exact permissions", func(t *testing.T) {
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: ActionOrgsQuotasRead, Scope: accesscontrol.Scope("orgs", "id", "1")}})
|
||||
t.Run("AccessControl prevents viewing CurrentOrgQuotas with correct permissions in another org", func(t *testing.T) {
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: ActionOrgsQuotasRead}}, 2)
|
||||
response := callAPI(sc.server, http.MethodGet, getCurrentOrgQuotasURL, nil, t)
|
||||
assert.Equal(t, http.StatusOK, response.Code)
|
||||
assert.Equal(t, http.StatusForbidden, response.Code)
|
||||
})
|
||||
t.Run("AccessControl prevents viewing CurrentOrgQuotas with incorrect permissions", func(t *testing.T) {
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: "orgs:invalid"}})
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: "orgs:invalid"}}, sc.initCtx.OrgId)
|
||||
response := callAPI(sc.server, http.MethodGet, getCurrentOrgQuotasURL, nil, t)
|
||||
assert.Equal(t, http.StatusForbidden, response.Code)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAPIEndpoint_GetOrgQuotas_LegacyAccessControl(t *testing.T) {
|
||||
sc := setupHTTPServer(t, false)
|
||||
sc := setupHTTPServer(t, true, false)
|
||||
setInitCtxSignedInViewer(sc.initCtx)
|
||||
|
||||
sc.hs.Cfg.Quota.Enabled = true
|
||||
sc.hs.Cfg.Quota.Org = &testOrgQuota
|
||||
// Required while sqlstore quota.go relies on setting global variables
|
||||
setting.Quota = sc.hs.Cfg.Quota
|
||||
|
||||
// Create two orgs, to fetch another one than the logged in one
|
||||
_, err := sc.db.CreateOrgWithMember("TestOrg", testUserID)
|
||||
require.NoError(t, err)
|
||||
_, err = sc.db.CreateOrgWithMember("TestOrg2", testUserID)
|
||||
require.NoError(t, err)
|
||||
setupDBAndSettingsForAccessControlQuotaTests(t, sc)
|
||||
|
||||
t.Run("Viewer cannot view another org quotas", func(t *testing.T) {
|
||||
response := callAPI(sc.server, http.MethodGet, fmt.Sprintf(getOrgsQuotasURL, 2), nil, t)
|
||||
@@ -110,54 +101,33 @@ func TestAPIEndpoint_GetOrgQuotas_LegacyAccessControl(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAPIEndpoint_GetOrgQuotas_AccessControl(t *testing.T) {
|
||||
sc := setupHTTPServer(t, true)
|
||||
sc := setupHTTPServer(t, true, true)
|
||||
setInitCtxSignedInViewer(sc.initCtx)
|
||||
|
||||
sc.hs.Cfg.Quota.Enabled = true
|
||||
sc.hs.Cfg.Quota.Org = &testOrgQuota
|
||||
// Required while sqlstore quota.go relies on setting global variables
|
||||
setting.Quota = sc.hs.Cfg.Quota
|
||||
|
||||
// Create two orgs, to fetch another one than the logged in one
|
||||
_, err := sc.db.CreateOrgWithMember("TestOrg", testUserID)
|
||||
require.NoError(t, err)
|
||||
_, err = sc.db.CreateOrgWithMember("TestOrg2", testUserID)
|
||||
require.NoError(t, err)
|
||||
setupDBAndSettingsForAccessControlQuotaTests(t, sc)
|
||||
|
||||
t.Run("AccessControl allows viewing another org quotas with correct permissions", func(t *testing.T) {
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: ActionOrgsQuotasRead, Scope: ScopeOrgsAll}})
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: ActionOrgsQuotasRead}}, 2)
|
||||
response := callAPI(sc.server, http.MethodGet, fmt.Sprintf(getOrgsQuotasURL, 2), nil, t)
|
||||
assert.Equal(t, http.StatusOK, response.Code)
|
||||
})
|
||||
t.Run("AccessControl allows viewing another org quotas with exact permissions", func(t *testing.T) {
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: ActionOrgsQuotasRead, Scope: accesscontrol.Scope("orgs", "id", "2")}})
|
||||
response := callAPI(sc.server, http.MethodGet, fmt.Sprintf(getOrgsQuotasURL, 2), nil, t)
|
||||
assert.Equal(t, http.StatusOK, response.Code)
|
||||
})
|
||||
t.Run("AccessControl prevents viewing another org quotas with too narrow permissions", func(t *testing.T) {
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: ActionOrgsQuotasRead, Scope: accesscontrol.Scope("orgs", "id", "1")}})
|
||||
t.Run("AccessControl prevents viewing another org quotas with correct permissions in another org", func(t *testing.T) {
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: ActionOrgsQuotasRead}}, 1)
|
||||
response := callAPI(sc.server, http.MethodGet, fmt.Sprintf(getOrgsQuotasURL, 2), nil, t)
|
||||
assert.Equal(t, http.StatusForbidden, response.Code)
|
||||
})
|
||||
t.Run("AccessControl prevents viewing another org quotas with incorrect permissions", func(t *testing.T) {
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: "orgs:invalid"}})
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: "orgs:invalid"}}, 2)
|
||||
response := callAPI(sc.server, http.MethodGet, fmt.Sprintf(getOrgsQuotasURL, 2), nil, t)
|
||||
assert.Equal(t, http.StatusForbidden, response.Code)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAPIEndpoint_PutOrgQuotas_LegacyAccessControl(t *testing.T) {
|
||||
sc := setupHTTPServer(t, false)
|
||||
sc := setupHTTPServer(t, true, false)
|
||||
setInitCtxSignedInViewer(sc.initCtx)
|
||||
|
||||
sc.hs.Cfg.Quota.Enabled = true
|
||||
sc.hs.Cfg.Quota.Org = &testOrgQuota
|
||||
|
||||
// Create two orgs, to update another one than the logged in one
|
||||
_, err := sc.db.CreateOrgWithMember("TestOrg", testUserID)
|
||||
require.NoError(t, err)
|
||||
_, err = sc.db.CreateOrgWithMember("TestOrg2", testUserID)
|
||||
require.NoError(t, err)
|
||||
setupDBAndSettingsForAccessControlQuotaTests(t, sc)
|
||||
|
||||
input := strings.NewReader(testUpdateOrgQuotaCmd)
|
||||
t.Run("Viewer cannot update another org quotas", func(t *testing.T) {
|
||||
@@ -174,42 +144,28 @@ func TestAPIEndpoint_PutOrgQuotas_LegacyAccessControl(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAPIEndpoint_PutOrgQuotas_AccessControl(t *testing.T) {
|
||||
sc := setupHTTPServer(t, true)
|
||||
sc := setupHTTPServer(t, true, true)
|
||||
setInitCtxSignedInViewer(sc.initCtx)
|
||||
|
||||
sc.hs.Cfg.Quota.Enabled = true
|
||||
sc.hs.Cfg.Quota.Org = &testOrgQuota
|
||||
|
||||
// Create two orgs, to update another one than the logged in one
|
||||
_, err := sc.db.CreateOrgWithMember("TestOrg", testUserID)
|
||||
require.NoError(t, err)
|
||||
_, err = sc.db.CreateOrgWithMember("TestOrg2", testUserID)
|
||||
require.NoError(t, err)
|
||||
setupDBAndSettingsForAccessControlQuotaTests(t, sc)
|
||||
|
||||
input := strings.NewReader(testUpdateOrgQuotaCmd)
|
||||
t.Run("AccessControl allows updating another org quotas with correct permissions", func(t *testing.T) {
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: ActionOrgsQuotasWrite, Scope: ScopeOrgsAll}})
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: ActionOrgsQuotasWrite}}, 2)
|
||||
response := callAPI(sc.server, http.MethodPut, fmt.Sprintf(putOrgsQuotasURL, 2, "org_user"), input, t)
|
||||
assert.Equal(t, http.StatusOK, response.Code)
|
||||
})
|
||||
|
||||
input = strings.NewReader(testUpdateOrgQuotaCmd)
|
||||
t.Run("AccessControl allows updating another org quotas with exact permissions", func(t *testing.T) {
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: ActionOrgsQuotasWrite, Scope: accesscontrol.Scope("orgs", "id", "2")}})
|
||||
response := callAPI(sc.server, http.MethodPut, fmt.Sprintf(putOrgsQuotasURL, 2, "org_user"), input, t)
|
||||
assert.Equal(t, http.StatusOK, response.Code)
|
||||
})
|
||||
|
||||
input = strings.NewReader(testUpdateOrgQuotaCmd)
|
||||
t.Run("AccessControl prevents updating another org quotas with too narrow permissions", func(t *testing.T) {
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: ActionOrgsQuotasWrite, Scope: accesscontrol.Scope("orgs", "id", "1")}})
|
||||
t.Run("AccessControl prevents updating another org quotas with correct permissions in another org", func(t *testing.T) {
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: ActionOrgsQuotasWrite}}, 1)
|
||||
response := callAPI(sc.server, http.MethodPut, fmt.Sprintf(putOrgsQuotasURL, 2, "org_user"), input, t)
|
||||
assert.Equal(t, http.StatusForbidden, response.Code)
|
||||
})
|
||||
|
||||
input = strings.NewReader(testUpdateOrgQuotaCmd)
|
||||
t.Run("AccessControl prevents updating another org quotas with incorrect permissions", func(t *testing.T) {
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: "orgs:invalid"}})
|
||||
setAccessControlPermissions(sc.acmock, []*accesscontrol.Permission{{Action: "orgs:invalid"}}, 2)
|
||||
response := callAPI(sc.server, http.MethodPut, fmt.Sprintf(putOrgsQuotasURL, 2, "org_user"), input, t)
|
||||
assert.Equal(t, http.StatusForbidden, response.Code)
|
||||
})
|
||||
|
||||
@@ -38,12 +38,6 @@ var (
|
||||
ScopeDatasourceID = accesscontrol.Scope("datasources", "id", accesscontrol.Parameter(":id"))
|
||||
ScopeDatasourceUID = accesscontrol.Scope("datasources", "uid", accesscontrol.Parameter(":uid"))
|
||||
ScopeDatasourceName = accesscontrol.Scope("datasources", "name", accesscontrol.Parameter(":name"))
|
||||
|
||||
ScopeOrgsAll = accesscontrol.Scope("orgs", "*")
|
||||
ScopeOrgID = accesscontrol.Scope("orgs", "id", accesscontrol.Parameter(":orgId"))
|
||||
ScopeOrgCurrentID = accesscontrol.Scope("orgs", "id", accesscontrol.Field("OrgID"))
|
||||
ScopeOrgName = accesscontrol.Scope("orgs", "name", accesscontrol.Parameter(":name"))
|
||||
ScopeOrgCurrent = accesscontrol.Scope("orgs", "current")
|
||||
)
|
||||
|
||||
// declareFixedRoles declares to the AccessControl service fixed roles and their
|
||||
@@ -121,17 +115,15 @@ func (hs *HTTPServer) declareFixedRoles() error {
|
||||
},
|
||||
{
|
||||
Role: accesscontrol.RoleDTO{
|
||||
Version: 1,
|
||||
Version: 2,
|
||||
Name: "fixed:current:org:reader",
|
||||
Description: "Read current organization and its quotas.",
|
||||
Permissions: []accesscontrol.Permission{
|
||||
{
|
||||
Action: ActionOrgsRead,
|
||||
Scope: ScopeOrgCurrent,
|
||||
},
|
||||
{
|
||||
Action: ActionOrgsQuotasRead,
|
||||
Scope: ScopeOrgCurrent,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -139,29 +131,24 @@ func (hs *HTTPServer) declareFixedRoles() error {
|
||||
},
|
||||
{
|
||||
Role: accesscontrol.RoleDTO{
|
||||
Version: 1,
|
||||
Version: 2,
|
||||
Name: "fixed:current:org:writer",
|
||||
Description: "Read current organization, its quotas, and its preferences. Write current organization and its preferences.",
|
||||
Permissions: []accesscontrol.Permission{
|
||||
{
|
||||
Action: ActionOrgsRead,
|
||||
Scope: ScopeOrgCurrent,
|
||||
},
|
||||
{
|
||||
Action: ActionOrgsQuotasRead,
|
||||
Scope: ScopeOrgCurrent,
|
||||
},
|
||||
{
|
||||
Action: ActionOrgsPreferencesRead,
|
||||
Scope: ScopeOrgCurrent,
|
||||
},
|
||||
{
|
||||
Action: ActionOrgsWrite,
|
||||
Scope: ScopeOrgCurrent,
|
||||
},
|
||||
{
|
||||
Action: ActionOrgsPreferencesWrite,
|
||||
Scope: ScopeOrgCurrent,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -169,34 +156,29 @@ func (hs *HTTPServer) declareFixedRoles() error {
|
||||
},
|
||||
{
|
||||
Role: accesscontrol.RoleDTO{
|
||||
Version: 1,
|
||||
Version: 2,
|
||||
Name: "fixed:orgs:writer",
|
||||
Description: "Create, read, write, or delete an organization. Read or write an organization's quotas.",
|
||||
Permissions: []accesscontrol.Permission{
|
||||
{Action: ActionOrgsCreate},
|
||||
{
|
||||
Action: ActionOrgsRead,
|
||||
Scope: ScopeOrgsAll,
|
||||
},
|
||||
{
|
||||
Action: ActionOrgsWrite,
|
||||
Scope: ScopeOrgsAll,
|
||||
},
|
||||
{
|
||||
Action: ActionOrgsDelete,
|
||||
Scope: ScopeOrgsAll,
|
||||
},
|
||||
{
|
||||
Action: ActionOrgsQuotasRead,
|
||||
Scope: ScopeOrgsAll,
|
||||
},
|
||||
{
|
||||
Action: ActionOrgsQuotasWrite,
|
||||
Scope: ScopeOrgsAll,
|
||||
},
|
||||
},
|
||||
},
|
||||
Grants: []string{string(accesscontrol.RoleGrafanaAdmin)},
|
||||
Grants: []string{accesscontrol.RoleGrafanaAdmin},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -7,11 +7,26 @@ import (
|
||||
|
||||
"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/util"
|
||||
"github.com/grafana/grafana/pkg/web"
|
||||
)
|
||||
|
||||
func authorize(c *models.ReqContext, ac accesscontrol.AccessControl, user *models.SignedInUser, evaluator accesscontrol.Evaluator) {
|
||||
injected, err := evaluator.Inject(buildScopeParams(c))
|
||||
if err != nil {
|
||||
c.JsonApiErr(http.StatusInternalServerError, "Internal server error", err)
|
||||
return
|
||||
}
|
||||
|
||||
hasAccess, err := ac.Evaluate(c.Req.Context(), user, injected)
|
||||
if !hasAccess || err != nil {
|
||||
Deny(c, injected, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func Middleware(ac accesscontrol.AccessControl) func(web.Handler, accesscontrol.Evaluator) web.Handler {
|
||||
return func(fallback web.Handler, evaluator accesscontrol.Evaluator) web.Handler {
|
||||
if ac.IsDisabled() {
|
||||
@@ -19,17 +34,7 @@ func Middleware(ac accesscontrol.AccessControl) func(web.Handler, accesscontrol.
|
||||
}
|
||||
|
||||
return func(c *models.ReqContext) {
|
||||
injected, err := evaluator.Inject(buildScopeParams(c))
|
||||
if err != nil {
|
||||
c.JsonApiErr(http.StatusInternalServerError, "Internal server error", err)
|
||||
return
|
||||
}
|
||||
|
||||
hasAccess, err := ac.Evaluate(c.Req.Context(), c.SignedInUser, injected)
|
||||
if !hasAccess || err != nil {
|
||||
Deny(c, injected, err)
|
||||
return
|
||||
}
|
||||
authorize(c, ac, c.SignedInUser, evaluator)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -82,3 +87,53 @@ func buildScopeParams(c *models.ReqContext) accesscontrol.ScopeParams {
|
||||
URLParams: web.Params(c.Req),
|
||||
}
|
||||
}
|
||||
|
||||
type OrgIDGetter func(c *models.ReqContext) (int64, error)
|
||||
|
||||
func AuthorizeInOrgMiddleware(ac accesscontrol.AccessControl, db *sqlstore.SQLStore) func(web.Handler, OrgIDGetter, accesscontrol.Evaluator) web.Handler {
|
||||
return func(fallback web.Handler, getTargetOrg OrgIDGetter, evaluator accesscontrol.Evaluator) web.Handler {
|
||||
if ac.IsDisabled() {
|
||||
return fallback
|
||||
}
|
||||
|
||||
return func(c *models.ReqContext) {
|
||||
// using a copy of the user not to modify the signedInUser, yet perform the permission evaluation in another org
|
||||
userCopy := *(c.SignedInUser)
|
||||
orgID, err := getTargetOrg(c)
|
||||
if err != nil {
|
||||
Deny(c, nil, fmt.Errorf("failed to get target org: %w", err))
|
||||
return
|
||||
}
|
||||
if orgID == accesscontrol.GlobalOrgID {
|
||||
userCopy.OrgId = orgID
|
||||
userCopy.OrgName = ""
|
||||
userCopy.OrgRole = ""
|
||||
} else {
|
||||
query := models.GetSignedInUserQuery{UserId: c.UserId, OrgId: orgID}
|
||||
if err := db.GetSignedInUserWithCacheCtx(c.Req.Context(), &query); err != nil {
|
||||
Deny(c, nil, fmt.Errorf("failed to authenticate user in target org: %w", err))
|
||||
return
|
||||
}
|
||||
userCopy.OrgId = query.Result.OrgId
|
||||
userCopy.OrgName = query.Result.OrgName
|
||||
userCopy.OrgRole = query.Result.OrgRole
|
||||
}
|
||||
|
||||
authorize(c, ac, &userCopy, evaluator)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func UseOrgFromContextParams(c *models.ReqContext) (int64, error) {
|
||||
orgID := c.ParamsInt64(":orgId")
|
||||
// Special case of macaron handling invalid params
|
||||
if orgID == 0 {
|
||||
return 0, models.ErrOrgNotFound
|
||||
}
|
||||
|
||||
return orgID, nil
|
||||
}
|
||||
|
||||
func UseGlobalOrg(c *models.ReqContext) (int64, error) {
|
||||
return accesscontrol.GlobalOrgID, nil
|
||||
}
|
||||
|
||||
@@ -5,13 +5,14 @@ import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/infra/usagestats"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func setupTestEnv(t testing.TB) *OSSAccessControlService {
|
||||
@@ -19,7 +20,14 @@ func setupTestEnv(t testing.TB) *OSSAccessControlService {
|
||||
|
||||
cfg := setting.NewCfg()
|
||||
cfg.FeatureToggles = map[string]bool{"accesscontrol": true}
|
||||
ac := ProvideService(cfg, &usagestats.UsageStatsMock{T: t})
|
||||
|
||||
ac := &OSSAccessControlService{
|
||||
Cfg: cfg,
|
||||
UsageStats: &usagestats.UsageStatsMock{T: t},
|
||||
Log: log.New("accesscontrol"),
|
||||
registrations: accesscontrol.RegistrationList{},
|
||||
scopeResolver: accesscontrol.NewScopeResolver(),
|
||||
}
|
||||
return ac
|
||||
}
|
||||
|
||||
@@ -77,8 +85,8 @@ func TestEvaluatingPermissions(t *testing.T) {
|
||||
desc: "should successfully evaluate access to the endpoint",
|
||||
user: userTestCase{
|
||||
name: "testuser",
|
||||
orgRole: "Grafana Admin",
|
||||
isGrafanaAdmin: false,
|
||||
orgRole: models.ROLE_VIEWER,
|
||||
isGrafanaAdmin: true,
|
||||
},
|
||||
endpoints: []endpointTestCase{
|
||||
{evaluator: accesscontrol.EvalPermission(accesscontrol.ActionUsersDisable, accesscontrol.ScopeGlobalUsersAll)},
|
||||
@@ -501,7 +509,7 @@ func TestOSSAccessControlService_RegisterFixedRoles(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestOSSAccessControlService_GetUserPermissions(t *testing.T) {
|
||||
testUser := &models.SignedInUser{
|
||||
testUser := models.SignedInUser{
|
||||
UserId: 2,
|
||||
OrgId: 3,
|
||||
OrgName: "TestOrg",
|
||||
@@ -522,18 +530,11 @@ func TestOSSAccessControlService_GetUserPermissions(t *testing.T) {
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
user *models.SignedInUser
|
||||
user models.SignedInUser
|
||||
rawPerm accesscontrol.Permission
|
||||
wantPerm accesscontrol.Permission
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "Translate orgs:current",
|
||||
user: testUser,
|
||||
rawPerm: accesscontrol.Permission{Action: "orgs:read", Scope: "orgs:current"},
|
||||
wantPerm: accesscontrol.Permission{Action: "orgs:read", Scope: "orgs:id:3"},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Translate users:self",
|
||||
user: testUser,
|
||||
@@ -550,13 +551,7 @@ func TestOSSAccessControlService_GetUserPermissions(t *testing.T) {
|
||||
})
|
||||
|
||||
// Setup
|
||||
ac := &OSSAccessControlService{
|
||||
Cfg: setting.NewCfg(),
|
||||
UsageStats: &usagestats.UsageStatsMock{T: t},
|
||||
Log: log.New("accesscontrol-test"),
|
||||
registrations: accesscontrol.RegistrationList{},
|
||||
scopeResolver: accesscontrol.NewScopeResolver(),
|
||||
}
|
||||
ac := setupTestEnv(t)
|
||||
ac.Cfg.FeatureToggles = map[string]bool{"accesscontrol": true}
|
||||
|
||||
registration.Role.Permissions = []accesscontrol.Permission{tt.rawPerm}
|
||||
@@ -567,7 +562,7 @@ func TestOSSAccessControlService_GetUserPermissions(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
// Test
|
||||
userPerms, err := ac.GetUserPermissions(context.TODO(), tt.user)
|
||||
userPerms, err := ac.GetUserPermissions(context.TODO(), &tt.user)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err, "Expected an error with GetUserPermissions.")
|
||||
return
|
||||
|
||||
@@ -42,16 +42,11 @@ type ScopeResolver struct {
|
||||
func NewScopeResolver() ScopeResolver {
|
||||
return ScopeResolver{
|
||||
keywordResolvers: map[string]KeywordScopeResolveFunc{
|
||||
"orgs:current": resolveCurrentOrg,
|
||||
"users:self": resolveUserSelf,
|
||||
"users:self": resolveUserSelf,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func resolveCurrentOrg(u *models.SignedInUser) (string, error) {
|
||||
return Scope("orgs", "id", fmt.Sprintf("%v", u.OrgId)), nil
|
||||
}
|
||||
|
||||
func resolveUserSelf(u *models.SignedInUser) (string, error) {
|
||||
return Scope("users", "id", fmt.Sprintf("%v", u.UserId)), nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user