grafana/pkg/api/org_users_test.go
Alexander Zobnin 82a88cc83f
Access control: Extend GetUserPermissions() to query permissions in org (#83392)
* Access control: Extend GetUserPermissions() to query permissions in specific org

* Use db query to fetch permissions in org

* refactor

* refactor

* use conditional join

* minor refactor

* Add test cases

* Search permissions correctly in OSS vs Enterprise

* Get permissions from memory

* Refactor

* remove unused func

* Add tests for GetUserPermissionsInOrg

* fix linter
2024-03-04 13:29:13 +01:00

727 lines
25 KiB
Go

package api
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/db/dbtest"
"github.com/grafana/grafana/pkg/login/social"
"github.com/grafana/grafana/pkg/login/social/socialtest"
"github.com/grafana/grafana/pkg/models/roletype"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/accesscontrol/actest"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/login"
"github.com/grafana/grafana/pkg/services/login/authinfotest"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/org/orgimpl"
"github.com/grafana/grafana/pkg/services/org/orgtest"
"github.com/grafana/grafana/pkg/services/quota/quotaimpl"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/services/user/userimpl"
"github.com/grafana/grafana/pkg/services/user/usertest"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/web/webtest"
)
func setUpGetOrgUsersDB(t *testing.T, sqlStore *sqlstore.SQLStore) {
sqlStore.Cfg.AutoAssignOrg = true
sqlStore.Cfg.AutoAssignOrgId = int(testOrgID)
quotaService := quotaimpl.ProvideService(sqlStore, sqlStore.Cfg)
orgService, err := orgimpl.ProvideService(sqlStore, sqlStore.Cfg, quotaService)
require.NoError(t, err)
usrSvc, err := userimpl.ProvideService(sqlStore, orgService, sqlStore.Cfg, nil, nil, quotaService, supportbundlestest.NewFakeBundleService())
require.NoError(t, err)
id, err := orgService.GetOrCreate(context.Background(), "testOrg")
require.NoError(t, err)
require.Equal(t, testOrgID, id)
_, err = usrSvc.Create(context.Background(), &user.CreateUserCommand{Email: "testUser@grafana.com", Login: testUserLogin})
require.NoError(t, err)
_, err = usrSvc.Create(context.Background(), &user.CreateUserCommand{Email: "user1@grafana.com", Login: "user1"})
require.NoError(t, err)
_, err = usrSvc.Create(context.Background(), &user.CreateUserCommand{Email: "user2@grafana.com", Login: "user2"})
require.NoError(t, err)
}
func TestOrgUsersAPIEndpoint_userLoggedIn(t *testing.T) {
hs := setupSimpleHTTPServer(featuremgmt.WithFeatures())
settings := hs.Cfg
sqlStore := db.InitTestDB(t)
sqlStore.Cfg = settings
hs.SQLStore = sqlStore
orgService := orgtest.NewOrgServiceFake()
orgService.ExpectedSearchOrgUsersResult = &org.SearchOrgUsersQueryResult{}
hs.orgService = orgService
setUpGetOrgUsersDB(t, sqlStore)
mock := dbtest.NewFakeDB()
loggedInUserScenario(t, "When calling GET on", "api/org/users", "api/org/users", func(sc *scenarioContext) {
orgService.ExpectedSearchOrgUsersResult = &org.SearchOrgUsersQueryResult{
OrgUsers: []*org.OrgUserDTO{
{Login: testUserLogin, Email: "testUser@grafana.com"},
{Login: "user1", Email: "user1@grafana.com"},
{Login: "user2", Email: "user2@grafana.com"},
},
}
sc.handlerFunc = hs.GetOrgUsersForCurrentOrg
sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
require.Equal(t, http.StatusOK, sc.resp.Code)
var resp []org.OrgUserDTO
err := json.Unmarshal(sc.resp.Body.Bytes(), &resp)
require.NoError(t, err)
assert.Len(t, resp, 3)
}, mock)
loggedInUserScenario(t, "When calling GET on", "api/org/users/search", "api/org/users/search", func(sc *scenarioContext) {
orgService.ExpectedSearchOrgUsersResult = &org.SearchOrgUsersQueryResult{
OrgUsers: []*org.OrgUserDTO{
{
Login: "user1",
},
{
Login: "user2",
},
{
Login: "user3",
},
},
TotalCount: 3,
PerPage: 1000,
Page: 1,
}
sc.handlerFunc = hs.SearchOrgUsersWithPaging
sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
require.Equal(t, http.StatusOK, sc.resp.Code)
var resp org.SearchOrgUsersQueryResult
err := json.Unmarshal(sc.resp.Body.Bytes(), &resp)
require.NoError(t, err)
assert.Len(t, resp.OrgUsers, 3)
assert.Equal(t, int64(3), resp.TotalCount)
assert.Equal(t, 1000, resp.PerPage)
assert.Equal(t, 1, resp.Page)
}, mock)
loggedInUserScenario(t, "When calling GET with page and limit query parameters on", "api/org/users/search", "api/org/users/search", func(sc *scenarioContext) {
orgService.ExpectedSearchOrgUsersResult = &org.SearchOrgUsersQueryResult{
OrgUsers: []*org.OrgUserDTO{
{
Login: "user1",
},
},
TotalCount: 3,
PerPage: 2,
Page: 2,
}
sc.handlerFunc = hs.SearchOrgUsersWithPaging
sc.fakeReqWithParams("GET", sc.url, map[string]string{"perpage": "2", "page": "2"}).exec()
require.Equal(t, http.StatusOK, sc.resp.Code)
var resp org.SearchOrgUsersQueryResult
err := json.Unmarshal(sc.resp.Body.Bytes(), &resp)
require.NoError(t, err)
assert.Len(t, resp.OrgUsers, 1)
assert.Equal(t, int64(3), resp.TotalCount)
assert.Equal(t, 2, resp.PerPage)
assert.Equal(t, 2, resp.Page)
}, mock)
t.Run("Given there are two hidden users", func(t *testing.T) {
settings.HiddenUsers = map[string]struct{}{
"user1": {},
testUserLogin: {},
}
t.Cleanup(func() { settings.HiddenUsers = make(map[string]struct{}) })
loggedInUserScenario(t, "When calling GET on", "api/org/users", "api/org/users", func(sc *scenarioContext) {
orgService.ExpectedSearchOrgUsersResult = &org.SearchOrgUsersQueryResult{
OrgUsers: []*org.OrgUserDTO{
{Login: testUserLogin, Email: "testUser@grafana.com"},
{Login: "user1", Email: "user1@grafana.com"},
{Login: "user2", Email: "user2@grafana.com"},
},
}
sc.handlerFunc = hs.GetOrgUsersForCurrentOrg
sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
require.Equal(t, http.StatusOK, sc.resp.Code)
var resp []org.OrgUserDTO
err := json.Unmarshal(sc.resp.Body.Bytes(), &resp)
require.NoError(t, err)
assert.Len(t, resp, 2)
assert.Equal(t, testUserLogin, resp[0].Login)
assert.Equal(t, "user2", resp[1].Login)
}, mock)
loggedInUserScenarioWithRole(t, "When calling GET as an admin on", "GET", "api/org/users/lookup",
"api/org/users/lookup", org.RoleAdmin, func(sc *scenarioContext) {
sc.handlerFunc = hs.GetOrgUsersForCurrentOrgLookup
sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
require.Equal(t, http.StatusOK, sc.resp.Code)
var resp []dtos.UserLookupDTO
err := json.Unmarshal(sc.resp.Body.Bytes(), &resp)
require.NoError(t, err)
assert.Len(t, resp, 2)
assert.Equal(t, testUserLogin, resp[0].Login)
assert.Equal(t, "user2", resp[1].Login)
}, mock)
})
}
func TestOrgUsersAPIEndpoint_updateOrgRole(t *testing.T) {
type testCase struct {
desc string
SkipOrgRoleSync bool
AuthEnabled bool
AuthModule string
expectedCode int
}
permissions := []accesscontrol.Permission{
{Action: accesscontrol.ActionOrgUsersRead, Scope: "users:*"},
{Action: accesscontrol.ActionOrgUsersWrite, Scope: "users:*"},
{Action: accesscontrol.ActionOrgUsersAdd, Scope: "users:*"},
{Action: accesscontrol.ActionOrgUsersRemove, Scope: "users:*"},
}
tests := []testCase{
{
desc: "should be able to change basicRole when skip_org_role_sync true",
SkipOrgRoleSync: true,
AuthEnabled: true,
AuthModule: login.LDAPAuthModule,
expectedCode: http.StatusOK,
},
{
desc: "should not be able to change basicRole when skip_org_role_sync false",
SkipOrgRoleSync: false,
AuthEnabled: true,
AuthModule: login.LDAPAuthModule,
expectedCode: http.StatusForbidden,
},
{
desc: "should not be able to change basicRole for a user synced through an OAuth provider",
SkipOrgRoleSync: false,
AuthEnabled: true,
AuthModule: login.GrafanaComAuthModule,
expectedCode: http.StatusForbidden,
},
{
desc: "should be able to change basicRole with a basic Auth",
SkipOrgRoleSync: false,
AuthEnabled: false,
AuthModule: "",
expectedCode: http.StatusOK,
},
{
desc: "should be able to change basicRole with a basic Auth",
SkipOrgRoleSync: true,
AuthEnabled: true,
AuthModule: "",
expectedCode: http.StatusOK,
},
}
userWithPermissions := userWithPermissions(1, permissions)
userRequesting := &user.User{ID: 2, OrgID: 1}
reqBody := `{"userId": "1", "role": "Admin", "orgId": "1"}`
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
server := SetupAPITestServer(t, func(hs *HTTPServer) {
hs.Cfg = setting.NewCfg()
hs.Cfg.LDAPAuthEnabled = tt.AuthEnabled
if tt.AuthModule == login.LDAPAuthModule {
hs.Cfg.LDAPAuthEnabled = tt.AuthEnabled
hs.Cfg.LDAPSkipOrgRoleSync = tt.SkipOrgRoleSync
}
// AuthModule empty means basic auth
hs.authInfoService = &authinfotest.FakeService{
ExpectedUserAuth: &login.UserAuth{AuthModule: tt.AuthModule},
}
hs.userService = &usertest.FakeUserService{ExpectedSignedInUser: userWithPermissions}
hs.orgService = &orgtest.FakeOrgService{}
hs.SocialService = &socialtest.FakeSocialService{
ExpectedAuthInfoProvider: &social.OAuthInfo{Enabled: tt.AuthEnabled, SkipOrgRoleSync: tt.SkipOrgRoleSync},
}
hs.accesscontrolService = &actest.FakeService{
ExpectedPermissions: permissions,
}
})
req := server.NewRequest(http.MethodPatch, fmt.Sprintf("/api/orgs/%d/users/%d", userRequesting.OrgID, userRequesting.ID), strings.NewReader(reqBody))
req.Header.Set("Content-Type", "application/json")
userWithPermissions.OrgRole = roletype.RoleAdmin
res, err := server.Send(webtest.RequestWithSignedInUser(req, userWithPermissions))
require.NoError(t, err)
assert.Equal(t, tt.expectedCode, res.StatusCode)
require.NoError(t, res.Body.Close())
})
}
}
func TestOrgUsersAPIEndpoint(t *testing.T) {
type testCase struct {
desc string
permissions []accesscontrol.Permission
expectedCode int
}
tests := []testCase{
{
expectedCode: http.StatusOK,
desc: "UsersLookupGet should return 200 for user with correct permissions",
permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionOrgUsersRead, Scope: accesscontrol.ScopeUsersAll}},
},
{
expectedCode: http.StatusForbidden,
desc: "UsersLookupGet should return 403 for user without required permissions",
permissions: []accesscontrol.Permission{{Action: "wrong"}},
},
}
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
server := SetupAPITestServer(t, func(hs *HTTPServer) {
hs.Cfg = setting.NewCfg()
hs.orgService = &orgtest.FakeOrgService{ExpectedSearchOrgUsersResult: &org.SearchOrgUsersQueryResult{}}
hs.authInfoService = &authinfotest.FakeService{}
})
res, err := server.Send(webtest.RequestWithSignedInUser(server.NewGetRequest("/api/org/users/lookup"), userWithPermissions(1, tt.permissions)))
require.NoError(t, err)
assert.Equal(t, tt.expectedCode, res.StatusCode)
require.NoError(t, res.Body.Close())
})
}
}
func TestGetOrgUsersAPIEndpoint_AccessControlMetadata(t *testing.T) {
type testCase struct {
desc string
permissions []accesscontrol.Permission
includeMetadata bool
expectedCode int
expectedMetadata map[string]bool
}
tests := []testCase{
{
desc: "should not get access control metadata",
includeMetadata: false,
permissions: []accesscontrol.Permission{
{Action: accesscontrol.ActionOrgUsersRead, Scope: "users:*"},
{Action: accesscontrol.ActionOrgUsersWrite, Scope: "users:*"},
{Action: accesscontrol.ActionOrgUsersAdd, Scope: "users:*"},
{Action: accesscontrol.ActionOrgUsersRemove, Scope: "users:*"},
},
expectedCode: http.StatusOK,
expectedMetadata: nil,
},
{
desc: "should get access control metadata",
includeMetadata: true,
permissions: []accesscontrol.Permission{
{Action: accesscontrol.ActionOrgUsersRead, Scope: "users:*"},
{Action: accesscontrol.ActionOrgUsersWrite, Scope: "users:*"},
{Action: accesscontrol.ActionOrgUsersAdd, Scope: "users:*"},
{Action: accesscontrol.ActionOrgUsersRemove, Scope: "users:*"},
},
expectedCode: http.StatusOK,
expectedMetadata: map[string]bool{
"org.users:write": true,
"org.users:add": true,
"org.users:read": true,
"org.users:remove": true,
},
},
}
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
server := SetupAPITestServer(t, func(hs *HTTPServer) {
hs.Cfg = setting.NewCfg()
hs.orgService = &orgtest.FakeOrgService{
ExpectedSearchOrgUsersResult: &org.SearchOrgUsersQueryResult{OrgUsers: []*org.OrgUserDTO{{UserID: 1}}},
}
hs.authInfoService = &authinfotest.FakeService{}
hs.userService = &usertest.FakeUserService{ExpectedSignedInUser: userWithPermissions(1, tt.permissions)}
hs.accesscontrolService = actest.FakeService{ExpectedPermissions: tt.permissions}
})
url := "/api/orgs/1/users"
if tt.includeMetadata {
url += "?accesscontrol=true"
}
res, err := server.Send(webtest.RequestWithSignedInUser(server.NewGetRequest(url), userWithPermissions(1, tt.permissions)))
require.NoError(t, err)
assert.Equal(t, tt.expectedCode, res.StatusCode)
var userList []*org.OrgUserDTO
err = json.NewDecoder(res.Body).Decode(&userList)
require.NoError(t, err)
if tt.expectedMetadata != nil {
assert.Equal(t, tt.expectedMetadata, userList[0].AccessControl)
} else {
assert.Nil(t, userList[0].AccessControl)
}
require.NoError(t, res.Body.Close())
})
}
}
func TestGetOrgUsersAPIEndpoint_AccessControl(t *testing.T) {
type testCase struct {
name string
permissions []accesscontrol.Permission
expectedCode int
targetOrg int64
}
tests := []testCase{
{
name: "user with permissions can get users in org",
permissions: []accesscontrol.Permission{
{Action: accesscontrol.ActionOrgUsersRead, Scope: "users:*"},
},
expectedCode: http.StatusOK,
targetOrg: 1,
},
{
name: "user without permissions cannot get users in org",
expectedCode: http.StatusForbidden,
targetOrg: 1,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
server := SetupAPITestServer(t, func(hs *HTTPServer) {
hs.Cfg = setting.NewCfg()
hs.orgService = &orgtest.FakeOrgService{
ExpectedSearchOrgUsersResult: &org.SearchOrgUsersQueryResult{},
}
hs.authInfoService = &authinfotest.FakeService{}
hs.userService = &usertest.FakeUserService{ExpectedSignedInUser: userWithPermissions(1, tc.permissions)}
hs.accesscontrolService = &actest.FakeService{}
})
u := userWithPermissions(1, tc.permissions)
res, err := server.Send(webtest.RequestWithSignedInUser(server.NewGetRequest(fmt.Sprintf("/api/orgs/%d/users/", tc.targetOrg)), u))
require.NoError(t, err)
assert.Equal(t, tc.expectedCode, res.StatusCode)
require.NoError(t, res.Body.Close())
})
}
}
func TestPostOrgUsersAPIEndpoint_AccessControl(t *testing.T) {
type testCase struct {
desc string
permissions []accesscontrol.Permission
input string
expectedCode int
}
tests := []testCase{
{
desc: "user with permissions can add users to org",
permissions: []accesscontrol.Permission{
{Action: accesscontrol.ActionOrgUsersAdd, Scope: "users:*"},
},
input: `{"loginOrEmail": "user", "role": "Viewer"}`,
expectedCode: http.StatusOK,
},
{
desc: "user without permissions cannot add users to org",
expectedCode: http.StatusForbidden,
input: `{"loginOrEmail": "user", "role": "Viewer"}`,
},
}
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
server := SetupAPITestServer(t, func(hs *HTTPServer) {
hs.Cfg = setting.NewCfg()
hs.orgService = &orgtest.FakeOrgService{}
hs.authInfoService = &authinfotest.FakeService{}
hs.userService = &usertest.FakeUserService{
ExpectedUser: &user.User{},
ExpectedSignedInUser: userWithPermissions(1, tt.permissions),
}
hs.accesscontrolService = &actest.FakeService{}
})
u := userWithPermissions(1, tt.permissions)
res, err := server.SendJSON(webtest.RequestWithSignedInUser(server.NewPostRequest("/api/orgs/1/users", strings.NewReader(tt.input)), u))
require.NoError(t, err)
assert.Equal(t, tt.expectedCode, res.StatusCode)
require.NoError(t, res.Body.Close())
})
}
}
func TestOrgUsersAPIEndpointWithSetPerms_AccessControl(t *testing.T) {
type testCase struct {
expectedCode int
desc string
url string
method string
role org.RoleType
permissions []accesscontrol.Permission
input string
}
tests := []testCase{
{
expectedCode: http.StatusOK,
desc: "org viewer with the correct permissions can add a user as a viewer to his org",
url: "/api/org/users",
method: http.MethodPost,
role: org.RoleViewer,
permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionOrgUsersAdd, Scope: accesscontrol.ScopeUsersAll}},
input: `{"loginOrEmail": "user", "role": "Viewer"}`,
},
{
expectedCode: http.StatusForbidden,
desc: "org viewer with the correct permissions cannot add a user as an editor to his org",
url: "/api/org/users",
role: org.RoleViewer,
method: http.MethodPost,
permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionOrgUsersAdd, Scope: accesscontrol.ScopeUsersAll}},
input: `{"loginOrEmail": "user", "role": "Editor"}`,
},
{
expectedCode: http.StatusOK,
desc: "org viewer with the correct permissions can add a user as a viewer to his org",
url: "/api/orgs/1/users",
method: http.MethodPost,
role: org.RoleViewer,
permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionOrgUsersAdd, Scope: accesscontrol.ScopeUsersAll}},
input: `{"loginOrEmail": "user", "role": "Viewer"}`,
},
{
expectedCode: http.StatusForbidden,
desc: "org viewer with the correct permissions cannot add a user as an editor to his org",
url: "/api/orgs/1/users",
method: http.MethodPost,
role: org.RoleViewer,
permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionOrgUsersAdd, Scope: accesscontrol.ScopeUsersAll}},
input: `{"loginOrEmail": "user", "role": "Editor"}`,
},
{
expectedCode: http.StatusOK,
desc: "org viewer with the correct permissions can update a user's role to a viewer in his org",
url: fmt.Sprintf("/api/org/users/%d", 1),
method: http.MethodPatch,
role: org.RoleViewer,
permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionOrgUsersWrite, Scope: accesscontrol.ScopeUsersAll}},
input: `{"role": "Viewer"}`,
},
{
expectedCode: http.StatusForbidden,
desc: "org viewer with the correct permissions cannot update a user's role to a Editorin his org",
url: fmt.Sprintf("/api/org/users/%d", 1),
method: http.MethodPatch,
permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionOrgUsersWrite, Scope: accesscontrol.ScopeUsersAll}},
input: `{"role": "Editor"}`,
},
{
expectedCode: http.StatusOK,
desc: "org viewer with the correct permissions can update a user's role to a viewer in his org",
url: fmt.Sprintf("/api/orgs/1/users/%d", 1),
method: http.MethodPatch,
role: org.RoleViewer,
permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionOrgUsersWrite, Scope: accesscontrol.ScopeUsersAll}},
input: `{"role": "Viewer"}`,
},
{
expectedCode: http.StatusForbidden,
desc: "org viewer with the correct permissions cannot update a user's role to a editor in his org",
url: fmt.Sprintf("/api/orgs/1/users/%d", 1),
method: http.MethodPatch,
role: org.RoleViewer,
permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionOrgUsersWrite, Scope: accesscontrol.ScopeUsersAll}},
input: `{"role": "Editor"}`,
},
{
expectedCode: http.StatusOK,
desc: "org viewer with the correct permissions can invite a user as a viewer in his org",
url: "/api/org/invites",
method: http.MethodPost,
role: org.RoleViewer,
permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionOrgUsersAdd, Scope: accesscontrol.ScopeUsersAll}},
input: `{"loginOrEmail": "newUserEmail@test.com", "sendEmail": false, "role": "Viewer"}`,
},
{
expectedCode: http.StatusForbidden,
desc: "org viewer with the correct permissions cannot invite a user as an editor in his org",
url: "/api/org/invites",
method: http.MethodPost,
role: org.RoleEditor,
permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionUsersCreate}},
input: `{"loginOrEmail": "newUserEmail@test.com", "sendEmail": false, "role": "Editor"}`,
},
}
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
server := SetupAPITestServer(t, func(hs *HTTPServer) {
hs.Cfg = setting.NewCfg()
hs.orgService = &orgtest.FakeOrgService{}
hs.authInfoService = &authinfotest.FakeService{
ExpectedUserAuth: &login.UserAuth{
AuthModule: "",
},
}
hs.userService = &usertest.FakeUserService{
ExpectedUser: &user.User{},
ExpectedSignedInUser: userWithPermissions(1, tt.permissions),
}
hs.accesscontrolService = &actest.FakeService{}
})
u := userWithPermissions(1, tt.permissions)
var reader io.Reader
if tt.input != "" {
reader = strings.NewReader(tt.input)
}
res, err := server.SendJSON(webtest.RequestWithSignedInUser(server.NewRequest(tt.method, tt.url, reader), u))
require.NoError(t, err)
assert.Equal(t, tt.expectedCode, res.StatusCode)
require.NoError(t, res.Body.Close())
})
}
}
func TestPatchOrgUsersAPIEndpoint_AccessControl(t *testing.T) {
type testCase struct {
name string
role org.RoleType
permissions []accesscontrol.Permission
input string
expectedCode int
}
tests := []testCase{
{
name: "user with permissions can update org role",
permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionOrgUsersWrite, Scope: "users:*"}},
role: org.RoleAdmin,
input: `{"role": "Viewer"}`,
expectedCode: http.StatusOK,
},
{
name: "user without permissions cannot update org role",
permissions: []accesscontrol.Permission{},
input: `{"role": "Editor"}`,
expectedCode: http.StatusForbidden,
},
{
name: "user with permissions cannot update org role with more privileges",
permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionOrgUsersWrite, Scope: "users:*"}},
role: org.RoleViewer,
input: `{"role": "Admin"}`,
expectedCode: http.StatusForbidden,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
server := SetupAPITestServer(t, func(hs *HTTPServer) {
hs.Cfg = setting.NewCfg()
hs.orgService = &orgtest.FakeOrgService{}
hs.authInfoService = &authinfotest.FakeService{
ExpectedUserAuth: &login.UserAuth{
AuthModule: "",
},
}
hs.accesscontrolService = &actest.FakeService{}
hs.userService = &usertest.FakeUserService{
ExpectedUser: &user.User{},
ExpectedSignedInUser: userWithPermissions(1, tt.permissions),
}
})
u := userWithPermissions(1, tt.permissions)
res, err := server.SendJSON(webtest.RequestWithSignedInUser(server.NewRequest(http.MethodPatch, "/api/orgs/1/users/1", strings.NewReader(tt.input)), u))
require.NoError(t, err)
assert.Equal(t, tt.expectedCode, res.StatusCode)
require.NoError(t, res.Body.Close())
})
}
}
func TestDeleteOrgUsersAPIEndpoint_AccessControl(t *testing.T) {
type testCase struct {
name string
permissions []accesscontrol.Permission
isGrafanaAdmin bool
expectedCode int
}
tests := []testCase{
{
name: "user with permissions can remove user from org",
permissions: []accesscontrol.Permission{
{Action: accesscontrol.ActionOrgUsersRemove, Scope: "users:*"},
},
expectedCode: http.StatusOK,
},
{
name: "user without permissions cannot remove user from org",
expectedCode: http.StatusForbidden,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
server := SetupAPITestServer(t, func(hs *HTTPServer) {
hs.Cfg = setting.NewCfg()
hs.accesscontrolService = actest.FakeService{}
hs.orgService = &orgtest.FakeOrgService{
ExpectedOrgListResponse: orgtest.OrgListResponse{struct {
OrgID int64
Response error
}{OrgID: 1, Response: nil}},
}
hs.authInfoService = &authinfotest.FakeService{}
hs.userService = &usertest.FakeUserService{
ExpectedUser: &user.User{},
ExpectedSignedInUser: userWithPermissions(1, tt.permissions),
}
})
u := userWithPermissions(1, tt.permissions)
u.IsGrafanaAdmin = tt.isGrafanaAdmin
res, err := server.SendJSON(webtest.RequestWithSignedInUser(server.NewRequest(http.MethodDelete, "/api/orgs/1/users/1", nil), u))
require.NoError(t, err)
assert.Equal(t, tt.expectedCode, res.StatusCode)
require.NoError(t, res.Body.Close())
})
}
}