mirror of
https://github.com/grafana/grafana.git
synced 2024-11-21 16:38:03 -06:00
Add an option to hide certain users in the UI (#28942)
* Add an option to hide certain users in the UI * revert changes for admin users routes * fix sqlstore function name * Improve slice management Co-authored-by: Emil Tullstedt <emil.tullstedt@grafana.com> * Hidden users: convert slice to map * filter with user logins instead of IDs * put HiddenUsers in Cfg struct * hide hidden users from dashboards/folders permissions list * Update conf/defaults.ini Co-authored-by: Torkel Ödegaard <torkel@grafana.com> * fix params order * fix tests * fix dashboard/folder update with hidden user * add team tests * add dashboard and folder permissions tests * fixes after merge * fix tests * API: add test for org users endpoints * update hidden users management for dashboard / folder permissions * improve dashboard / folder permissions tests * fixes after merge * Guardian: add hidden acl tests * API: add team members tests * fix team sql syntax for postgres * api tests update * fix linter error * fix tests errors after merge Co-authored-by: Emil Tullstedt <emil.tullstedt@grafana.com> Co-authored-by: Torkel Ödegaard <torkel@grafana.com> Co-authored-by: Leonard Gram <leo@xlson.com>
This commit is contained in:
parent
4c47fc56bb
commit
22788d1d86
@ -296,6 +296,9 @@ editors_can_admin = false
|
||||
# The duration in time a user invitation remains valid before expiring. This setting should be expressed as a duration. Examples: 6h (hours), 2d (days), 1w (week). Default is 24h (24 hours). The minimum supported duration is 15m (15 minutes).
|
||||
user_invite_max_lifetime_duration = 24h
|
||||
|
||||
# Enter a comma-separated list of usernames to hide them in the Grafana UI. These users are shown to Grafana admins and to themselves.
|
||||
hidden_users =
|
||||
|
||||
[auth]
|
||||
# Login cookie name
|
||||
login_cookie_name = grafana_session
|
||||
|
@ -295,6 +295,9 @@
|
||||
# The duration in time a user invitation remains valid before expiring. This setting should be expressed as a duration. Examples: 6h (hours), 2d (days), 1w (week). Default is 24h (24 hours). The minimum supported duration is 15m (15 minutes).
|
||||
;user_invite_max_lifetime_duration = 24h
|
||||
|
||||
# Enter a comma-separated list of users login to hide them in the Grafana UI. These users are shown to Grafana admins and themselves.
|
||||
; hidden_users =
|
||||
|
||||
[auth]
|
||||
# Login cookie name
|
||||
;login_cookie_name = grafana_session
|
||||
|
@ -170,7 +170,7 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
|
||||
// team without requirement of user to be org admin
|
||||
apiRoute.Group("/teams", func(teamsRoute routing.RouteRegister) {
|
||||
teamsRoute.Get("/:teamId", Wrap(GetTeamByID))
|
||||
teamsRoute.Get("/:teamId", Wrap(hs.GetTeamByID))
|
||||
teamsRoute.Get("/search", Wrap(hs.SearchTeams))
|
||||
})
|
||||
|
||||
@ -184,7 +184,7 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
apiRoute.Group("/org", func(orgRoute routing.RouteRegister) {
|
||||
orgRoute.Put("/", bind(dtos.UpdateOrgForm{}), Wrap(UpdateOrgCurrent))
|
||||
orgRoute.Put("/address", bind(dtos.UpdateOrgAddressForm{}), Wrap(UpdateOrgAddressCurrent))
|
||||
orgRoute.Get("/users", Wrap(GetOrgUsersForCurrentOrg))
|
||||
orgRoute.Get("/users", Wrap(hs.GetOrgUsersForCurrentOrg))
|
||||
orgRoute.Post("/users", quota("user"), bind(models.AddOrgUserCommand{}), Wrap(AddOrgUserToCurrentOrg))
|
||||
orgRoute.Patch("/users/:userId", bind(models.UpdateOrgUserCommand{}), Wrap(UpdateOrgUserForCurrentOrg))
|
||||
orgRoute.Delete("/users/:userId", Wrap(RemoveOrgUserForCurrentOrg))
|
||||
@ -201,7 +201,7 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
|
||||
// current org without requirement of user to be org admin
|
||||
apiRoute.Group("/org", func(orgRoute routing.RouteRegister) {
|
||||
orgRoute.Get("/users/lookup", Wrap(GetOrgUsersForCurrentOrgLookup))
|
||||
orgRoute.Get("/users/lookup", Wrap(hs.GetOrgUsersForCurrentOrgLookup))
|
||||
})
|
||||
|
||||
// create new org
|
||||
@ -216,7 +216,7 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
orgsRoute.Put("/", bind(dtos.UpdateOrgForm{}), Wrap(UpdateOrg))
|
||||
orgsRoute.Put("/address", bind(dtos.UpdateOrgAddressForm{}), Wrap(UpdateOrgAddress))
|
||||
orgsRoute.Delete("/", Wrap(DeleteOrgByID))
|
||||
orgsRoute.Get("/users", Wrap(GetOrgUsers))
|
||||
orgsRoute.Get("/users", Wrap(hs.GetOrgUsers))
|
||||
orgsRoute.Post("/users", bind(models.AddOrgUserCommand{}), Wrap(AddOrgUser))
|
||||
orgsRoute.Patch("/users/:userId", bind(models.UpdateOrgUserCommand{}), Wrap(UpdateOrgUser))
|
||||
orgsRoute.Delete("/users/:userId", Wrap(RemoveOrgUser))
|
||||
@ -287,8 +287,8 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
folderUidRoute.Delete("/", Wrap(DeleteFolder))
|
||||
|
||||
folderUidRoute.Group("/permissions", func(folderPermissionRoute routing.RouteRegister) {
|
||||
folderPermissionRoute.Get("/", Wrap(GetFolderPermissionList))
|
||||
folderPermissionRoute.Post("/", bind(dtos.UpdateDashboardAclCommand{}), Wrap(UpdateFolderPermissions))
|
||||
folderPermissionRoute.Get("/", Wrap(hs.GetFolderPermissionList))
|
||||
folderPermissionRoute.Post("/", bind(dtos.UpdateDashboardAclCommand{}), Wrap(hs.UpdateFolderPermissions))
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -314,8 +314,8 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
dashIdRoute.Post("/restore", bind(dtos.RestoreDashboardVersionCommand{}), Wrap(hs.RestoreDashboardVersion))
|
||||
|
||||
dashIdRoute.Group("/permissions", func(dashboardPermissionRoute routing.RouteRegister) {
|
||||
dashboardPermissionRoute.Get("/", Wrap(GetDashboardPermissionList))
|
||||
dashboardPermissionRoute.Post("/", bind(dtos.UpdateDashboardAclCommand{}), Wrap(UpdateDashboardPermissions))
|
||||
dashboardPermissionRoute.Get("/", Wrap(hs.GetDashboardPermissionList))
|
||||
dashboardPermissionRoute.Post("/", bind(dtos.UpdateDashboardAclCommand{}), Wrap(hs.UpdateDashboardPermissions))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -28,6 +28,7 @@ func loggedInUserScenarioWithRole(t *testing.T, desc string, method string, url
|
||||
sc.context = c
|
||||
sc.context.UserId = testUserID
|
||||
sc.context.OrgId = testOrgID
|
||||
sc.context.Login = testUserLogin
|
||||
sc.context.OrgRole = role
|
||||
if sc.handlerFunc != nil {
|
||||
return sc.handlerFunc(sc.context)
|
||||
|
@ -10,7 +10,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/guardian"
|
||||
)
|
||||
|
||||
func GetDashboardPermissionList(c *models.ReqContext) Response {
|
||||
func (hs *HTTPServer) GetDashboardPermissionList(c *models.ReqContext) Response {
|
||||
dashID := c.ParamsInt64(":dashboardId")
|
||||
|
||||
_, rsp := getDashboardHelper(c.OrgId, "", dashID, "")
|
||||
@ -29,7 +29,12 @@ func GetDashboardPermissionList(c *models.ReqContext) Response {
|
||||
return Error(500, "Failed to get dashboard permissions", err)
|
||||
}
|
||||
|
||||
filteredAcls := make([]*models.DashboardAclInfoDTO, 0, len(acl))
|
||||
for _, perm := range acl {
|
||||
if dtos.IsHiddenUser(perm.UserLogin, c.SignedInUser, hs.Cfg) {
|
||||
continue
|
||||
}
|
||||
|
||||
perm.UserAvatarUrl = dtos.GetGravatarUrl(perm.UserEmail)
|
||||
|
||||
if perm.TeamId > 0 {
|
||||
@ -38,12 +43,14 @@ func GetDashboardPermissionList(c *models.ReqContext) Response {
|
||||
if perm.Slug != "" {
|
||||
perm.Url = models.GetDashboardFolderUrl(perm.IsFolder, perm.Uid, perm.Slug)
|
||||
}
|
||||
|
||||
filteredAcls = append(filteredAcls, perm)
|
||||
}
|
||||
|
||||
return JSON(200, acl)
|
||||
return JSON(200, filteredAcls)
|
||||
}
|
||||
|
||||
func UpdateDashboardPermissions(c *models.ReqContext, apiCmd dtos.UpdateDashboardAclCommand) Response {
|
||||
func (hs *HTTPServer) UpdateDashboardPermissions(c *models.ReqContext, apiCmd dtos.UpdateDashboardAclCommand) Response {
|
||||
if err := validatePermissionsUpdate(apiCmd); err != nil {
|
||||
return Error(400, err.Error(), err)
|
||||
}
|
||||
@ -76,6 +83,12 @@ func UpdateDashboardPermissions(c *models.ReqContext, apiCmd dtos.UpdateDashboar
|
||||
})
|
||||
}
|
||||
|
||||
hiddenACL, err := g.GetHiddenACL(hs.Cfg)
|
||||
if err != nil {
|
||||
return Error(500, "Error while retrieving hidden permissions", err)
|
||||
}
|
||||
cmd.Items = append(cmd.Items, hiddenACL...)
|
||||
|
||||
if okToUpdate, err := g.CheckPermissionBeforeUpdate(models.PERMISSION_ADMIN, cmd.Items); err != nil || !okToUpdate {
|
||||
if err != nil {
|
||||
if errors.Is(err, guardian.ErrGuardianPermissionExists) || errors.Is(err, guardian.ErrGuardianOverride) {
|
||||
|
@ -1,20 +1,24 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/dtos"
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/guardian"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestDashboardPermissionAPIEndpoint(t *testing.T) {
|
||||
t.Run("Dashboard permissions test", func(t *testing.T) {
|
||||
settings := setting.NewCfg()
|
||||
hs := &HTTPServer{Cfg: settings}
|
||||
|
||||
t.Run("Given dashboard not exists", func(t *testing.T) {
|
||||
setUp := func() {
|
||||
bus.AddHandler("test", func(query *models.GetDashboardQuery) error {
|
||||
@ -25,7 +29,7 @@ func TestDashboardPermissionAPIEndpoint(t *testing.T) {
|
||||
loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/dashboards/id/1/permissions",
|
||||
"/api/dashboards/id/:id/permissions", models.ROLE_EDITOR, func(sc *scenarioContext) {
|
||||
setUp()
|
||||
callGetDashboardPermissions(sc)
|
||||
callGetDashboardPermissions(sc, hs)
|
||||
assert.Equal(t, 404, sc.resp.Code)
|
||||
})
|
||||
|
||||
@ -35,12 +39,17 @@ func TestDashboardPermissionAPIEndpoint(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
updateDashboardPermissionScenario(t, "When calling POST on", "/api/dashboards/id/1/permissions",
|
||||
"/api/dashboards/id/:id/permissions", cmd, func(sc *scenarioContext) {
|
||||
updateDashboardPermissionScenario(t, updatePermissionContext{
|
||||
desc: "When calling POST on",
|
||||
url: "/api/dashboards/id/1/permissions",
|
||||
routePattern: "/api/dashboards/id/:id/permissions",
|
||||
cmd: cmd,
|
||||
fn: func(sc *scenarioContext) {
|
||||
setUp()
|
||||
callUpdateDashboardPermissions(sc)
|
||||
assert.Equal(t, 404, sc.resp.Code)
|
||||
})
|
||||
},
|
||||
}, hs)
|
||||
})
|
||||
|
||||
t.Run("Given user has no admin permissions", func(t *testing.T) {
|
||||
@ -63,7 +72,7 @@ func TestDashboardPermissionAPIEndpoint(t *testing.T) {
|
||||
loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/dashboards/id/1/permissions",
|
||||
"/api/dashboards/id/:id/permissions", models.ROLE_EDITOR, func(sc *scenarioContext) {
|
||||
setUp()
|
||||
callGetDashboardPermissions(sc)
|
||||
callGetDashboardPermissions(sc, hs)
|
||||
assert.Equal(t, 403, sc.resp.Code)
|
||||
})
|
||||
|
||||
@ -73,12 +82,17 @@ func TestDashboardPermissionAPIEndpoint(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
updateDashboardPermissionScenario(t, "When calling POST on", "/api/dashboards/id/1/permissions",
|
||||
"/api/dashboards/id/:id/permissions", cmd, func(sc *scenarioContext) {
|
||||
updateDashboardPermissionScenario(t, updatePermissionContext{
|
||||
desc: "When calling POST on",
|
||||
url: "/api/dashboards/id/1/permissions",
|
||||
routePattern: "/api/dashboards/id/:id/permissions",
|
||||
cmd: cmd,
|
||||
fn: func(sc *scenarioContext) {
|
||||
setUp()
|
||||
callUpdateDashboardPermissions(sc)
|
||||
assert.Equal(t, 403, sc.resp.Code)
|
||||
})
|
||||
},
|
||||
}, hs)
|
||||
})
|
||||
|
||||
t.Run("Given user has admin permissions and permissions to update", func(t *testing.T) {
|
||||
@ -110,13 +124,16 @@ func TestDashboardPermissionAPIEndpoint(t *testing.T) {
|
||||
loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/dashboards/id/1/permissions",
|
||||
"/api/dashboards/id/:id/permissions", models.ROLE_ADMIN, func(sc *scenarioContext) {
|
||||
setUp()
|
||||
callGetDashboardPermissions(sc)
|
||||
callGetDashboardPermissions(sc, hs)
|
||||
assert.Equal(t, 200, sc.resp.Code)
|
||||
respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes())
|
||||
|
||||
var resp []*models.DashboardAclInfoDTO
|
||||
err := json.Unmarshal(sc.resp.Body.Bytes(), &resp)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 5, len(respJSON.MustArray()))
|
||||
assert.Equal(t, 2, respJSON.GetIndex(0).Get("userId").MustInt())
|
||||
assert.Equal(t, int(models.PERMISSION_VIEW), respJSON.GetIndex(0).Get("permission").MustInt())
|
||||
|
||||
assert.Len(t, resp, 5)
|
||||
assert.Equal(t, int64(2), resp[0].UserId)
|
||||
assert.Equal(t, models.PERMISSION_VIEW, resp[0].Permission)
|
||||
})
|
||||
|
||||
cmd := dtos.UpdateDashboardAclCommand{
|
||||
@ -125,12 +142,17 @@ func TestDashboardPermissionAPIEndpoint(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
updateDashboardPermissionScenario(t, "When calling POST on", "/api/dashboards/id/1/permissions",
|
||||
"/api/dashboards/id/:id/permissions", cmd, func(sc *scenarioContext) {
|
||||
updateDashboardPermissionScenario(t, updatePermissionContext{
|
||||
desc: "When calling POST on",
|
||||
url: "/api/dashboards/id/1/permissions",
|
||||
routePattern: "/api/dashboards/id/:id/permissions",
|
||||
cmd: cmd,
|
||||
fn: func(sc *scenarioContext) {
|
||||
setUp()
|
||||
callUpdateDashboardPermissions(sc)
|
||||
assert.Equal(t, 200, sc.resp.Code)
|
||||
})
|
||||
},
|
||||
}, hs)
|
||||
})
|
||||
|
||||
t.Run("When trying to update permissions with duplicate permissions", func(t *testing.T) {
|
||||
@ -158,12 +180,17 @@ func TestDashboardPermissionAPIEndpoint(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
updateDashboardPermissionScenario(t, "When calling POST on", "/api/dashboards/id/1/permissions",
|
||||
"/api/dashboards/id/:id/permissions", cmd, func(sc *scenarioContext) {
|
||||
updateDashboardPermissionScenario(t, updatePermissionContext{
|
||||
desc: "When calling POST on",
|
||||
url: "/api/dashboards/id/1/permissions",
|
||||
routePattern: "/api/dashboards/id/:id/permissions",
|
||||
cmd: cmd,
|
||||
fn: func(sc *scenarioContext) {
|
||||
setUp()
|
||||
callUpdateDashboardPermissions(sc)
|
||||
assert.Equal(t, 400, sc.resp.Code)
|
||||
})
|
||||
},
|
||||
}, hs)
|
||||
})
|
||||
|
||||
t.Run("When trying to update team or user permissions with a role", func(t *testing.T) {
|
||||
@ -182,14 +209,19 @@ func TestDashboardPermissionAPIEndpoint(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, cmd := range cmds {
|
||||
updateDashboardPermissionScenario(t, "When calling POST on", "/api/dashboards/id/1/permissions",
|
||||
"/api/dashboards/id/:id/permissions", cmd, func(sc *scenarioContext) {
|
||||
updateDashboardPermissionScenario(t, updatePermissionContext{
|
||||
desc: "When calling POST on",
|
||||
url: "/api/dashboards/id/1/permissions",
|
||||
routePattern: "/api/dashboards/id/:id/permissions",
|
||||
cmd: cmd,
|
||||
fn: func(sc *scenarioContext) {
|
||||
callUpdateDashboardPermissions(sc)
|
||||
assert.Equal(t, 400, sc.resp.Code)
|
||||
respJSON, err := jsonMap(sc.resp.Body.Bytes())
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, models.ErrPermissionsWithRoleNotAllowed.Error(), respJSON["error"])
|
||||
})
|
||||
},
|
||||
}, hs)
|
||||
}
|
||||
})
|
||||
|
||||
@ -219,18 +251,103 @@ func TestDashboardPermissionAPIEndpoint(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
updateDashboardPermissionScenario(t, "When calling POST on", "/api/dashboards/id/1/permissions",
|
||||
"/api/dashboards/id/:id/permissions", cmd, func(sc *scenarioContext) {
|
||||
updateDashboardPermissionScenario(t, updatePermissionContext{
|
||||
desc: "When calling POST on",
|
||||
url: "/api/dashboards/id/1/permissions",
|
||||
routePattern: "/api/dashboards/id/:id/permissions",
|
||||
cmd: cmd,
|
||||
fn: func(sc *scenarioContext) {
|
||||
setUp()
|
||||
callUpdateDashboardPermissions(sc)
|
||||
assert.Equal(t, 400, sc.resp.Code)
|
||||
},
|
||||
}, hs)
|
||||
})
|
||||
|
||||
t.Run("Getting and updating dashboard permissions with hidden users", func(t *testing.T) {
|
||||
origNewGuardian := guardian.New
|
||||
settings.HiddenUsers = map[string]struct{}{
|
||||
"hiddenUser": {},
|
||||
testUserLogin: {},
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
guardian.New = origNewGuardian
|
||||
settings.HiddenUsers = make(map[string]struct{})
|
||||
})
|
||||
|
||||
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{
|
||||
CanAdminValue: true,
|
||||
CheckPermissionBeforeUpdateValue: true,
|
||||
GetAclValue: []*models.DashboardAclInfoDTO{
|
||||
{OrgId: 1, DashboardId: 1, UserId: 2, UserLogin: "hiddenUser", Permission: models.PERMISSION_VIEW},
|
||||
{OrgId: 1, DashboardId: 1, UserId: 3, UserLogin: testUserLogin, Permission: models.PERMISSION_EDIT},
|
||||
{OrgId: 1, DashboardId: 1, UserId: 4, UserLogin: "user_1", Permission: models.PERMISSION_ADMIN},
|
||||
},
|
||||
GetHiddenAclValue: []*models.DashboardAcl{
|
||||
{OrgID: 1, DashboardID: 1, UserID: 2, Permission: models.PERMISSION_VIEW},
|
||||
},
|
||||
})
|
||||
|
||||
setUp := func() {
|
||||
getDashboardQueryResult := models.NewDashboard("Dash")
|
||||
bus.AddHandler("test", func(query *models.GetDashboardQuery) error {
|
||||
query.Result = getDashboardQueryResult
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
var resp []*models.DashboardAclInfoDTO
|
||||
loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/dashboards/id/1/permissions",
|
||||
"/api/dashboards/id/:id/permissions", models.ROLE_ADMIN, func(sc *scenarioContext) {
|
||||
setUp()
|
||||
callGetDashboardPermissions(sc, hs)
|
||||
assert.Equal(t, 200, sc.resp.Code)
|
||||
|
||||
err := json.Unmarshal(sc.resp.Body.Bytes(), &resp)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Len(t, resp, 2)
|
||||
assert.Equal(t, int64(3), resp[0].UserId)
|
||||
assert.Equal(t, models.PERMISSION_EDIT, resp[0].Permission)
|
||||
assert.Equal(t, int64(4), resp[1].UserId)
|
||||
assert.Equal(t, models.PERMISSION_ADMIN, resp[1].Permission)
|
||||
})
|
||||
|
||||
cmd := dtos.UpdateDashboardAclCommand{
|
||||
Items: []dtos.DashboardAclUpdateItem{
|
||||
{UserID: 1000, Permission: models.PERMISSION_ADMIN},
|
||||
},
|
||||
}
|
||||
for _, acl := range resp {
|
||||
cmd.Items = append(cmd.Items, dtos.DashboardAclUpdateItem{
|
||||
UserID: acl.UserId,
|
||||
Permission: acl.Permission,
|
||||
})
|
||||
}
|
||||
assert.Len(t, cmd.Items, 3)
|
||||
|
||||
updateDashboardPermissionScenario(t, updatePermissionContext{
|
||||
desc: "When calling POST on",
|
||||
url: "/api/dashboards/id/1/permissions",
|
||||
routePattern: "/api/dashboards/id/:id/permissions",
|
||||
cmd: cmd,
|
||||
fn: func(sc *scenarioContext) {
|
||||
setUp()
|
||||
bus.AddHandler("test", func(cmd *models.UpdateDashboardAclCommand) error {
|
||||
assert.Len(t, cmd.Items, 4)
|
||||
return nil
|
||||
})
|
||||
|
||||
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
|
||||
assert.Equal(t, 200, sc.resp.Code)
|
||||
},
|
||||
}, hs)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func callGetDashboardPermissions(sc *scenarioContext) {
|
||||
sc.handlerFunc = GetDashboardPermissionList
|
||||
func callGetDashboardPermissions(sc *scenarioContext, hs *HTTPServer) {
|
||||
sc.handlerFunc = hs.GetDashboardPermissionList
|
||||
sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
|
||||
}
|
||||
|
||||
@ -242,22 +359,30 @@ func callUpdateDashboardPermissions(sc *scenarioContext) {
|
||||
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
|
||||
}
|
||||
|
||||
func updateDashboardPermissionScenario(t *testing.T, desc string, url string, routePattern string, cmd dtos.UpdateDashboardAclCommand, fn scenarioFunc) {
|
||||
t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
|
||||
type updatePermissionContext struct {
|
||||
desc string
|
||||
url string
|
||||
routePattern string
|
||||
cmd dtos.UpdateDashboardAclCommand
|
||||
fn scenarioFunc
|
||||
}
|
||||
|
||||
func updateDashboardPermissionScenario(t *testing.T, ctx updatePermissionContext, hs *HTTPServer) {
|
||||
t.Run(fmt.Sprintf("%s %s", ctx.desc, ctx.url), func(t *testing.T) {
|
||||
t.Cleanup(bus.ClearBusHandlers)
|
||||
|
||||
sc := setupScenarioContext(t, url)
|
||||
sc := setupScenarioContext(t, ctx.url)
|
||||
|
||||
sc.defaultHandler = Wrap(func(c *models.ReqContext) Response {
|
||||
sc.context = c
|
||||
sc.context.OrgId = testOrgID
|
||||
sc.context.UserId = testUserID
|
||||
|
||||
return UpdateDashboardPermissions(c, cmd)
|
||||
return hs.UpdateDashboardPermissions(c, ctx.cmd)
|
||||
})
|
||||
|
||||
sc.m.Post(routePattern, sc.defaultHandler)
|
||||
sc.m.Post(ctx.routePattern, sc.defaultHandler)
|
||||
|
||||
fn(sc)
|
||||
ctx.fn(sc)
|
||||
})
|
||||
}
|
||||
|
@ -12,8 +12,9 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
testOrgID int64 = 1
|
||||
testUserID int64 = 1
|
||||
testOrgID int64 = 1
|
||||
testUserID int64 = 1
|
||||
testUserLogin string = "testUser"
|
||||
)
|
||||
|
||||
func TestDataSourcesProxy_userLoggedIn(t *testing.T) {
|
||||
|
@ -75,3 +75,15 @@ func GetGravatarUrlWithDefault(text string, defaultText string) string {
|
||||
|
||||
return GetGravatarUrl(text)
|
||||
}
|
||||
|
||||
func IsHiddenUser(userLogin string, signedInUser *models.SignedInUser, cfg *setting.Cfg) bool {
|
||||
if signedInUser.IsGrafanaAdmin || userLogin == signedInUser.Login {
|
||||
return false
|
||||
}
|
||||
|
||||
if _, hidden := cfg.HiddenUsers[userLogin]; hidden {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
func GetFolderPermissionList(c *models.ReqContext) Response {
|
||||
func (hs *HTTPServer) GetFolderPermissionList(c *models.ReqContext) Response {
|
||||
s := dashboards.NewFolderService(c.OrgId, c.SignedInUser)
|
||||
folder, err := s.GetFolderByUID(c.Params(":uid"))
|
||||
|
||||
@ -31,7 +31,12 @@ func GetFolderPermissionList(c *models.ReqContext) Response {
|
||||
return Error(500, "Failed to get folder permissions", err)
|
||||
}
|
||||
|
||||
filteredAcls := make([]*models.DashboardAclInfoDTO, 0, len(acl))
|
||||
for _, perm := range acl {
|
||||
if dtos.IsHiddenUser(perm.UserLogin, c.SignedInUser, hs.Cfg) {
|
||||
continue
|
||||
}
|
||||
|
||||
perm.FolderId = folder.Id
|
||||
perm.DashboardId = 0
|
||||
|
||||
@ -44,12 +49,14 @@ func GetFolderPermissionList(c *models.ReqContext) Response {
|
||||
if perm.Slug != "" {
|
||||
perm.Url = models.GetDashboardFolderUrl(perm.IsFolder, perm.Uid, perm.Slug)
|
||||
}
|
||||
|
||||
filteredAcls = append(filteredAcls, perm)
|
||||
}
|
||||
|
||||
return JSON(200, acl)
|
||||
return JSON(200, filteredAcls)
|
||||
}
|
||||
|
||||
func UpdateFolderPermissions(c *models.ReqContext, apiCmd dtos.UpdateDashboardAclCommand) Response {
|
||||
func (hs *HTTPServer) UpdateFolderPermissions(c *models.ReqContext, apiCmd dtos.UpdateDashboardAclCommand) Response {
|
||||
if err := validatePermissionsUpdate(apiCmd); err != nil {
|
||||
return Error(400, err.Error(), err)
|
||||
}
|
||||
@ -87,6 +94,12 @@ func UpdateFolderPermissions(c *models.ReqContext, apiCmd dtos.UpdateDashboardAc
|
||||
})
|
||||
}
|
||||
|
||||
hiddenACL, err := g.GetHiddenACL(hs.Cfg)
|
||||
if err != nil {
|
||||
return Error(500, "Error while retrieving hidden permissions", err)
|
||||
}
|
||||
cmd.Items = append(cmd.Items, hiddenACL...)
|
||||
|
||||
if okToUpdate, err := g.CheckPermissionBeforeUpdate(models.PERMISSION_ADMIN, cmd.Items); err != nil || !okToUpdate {
|
||||
if err != nil {
|
||||
if errors.Is(err, guardian.ErrGuardianPermissionExists) ||
|
||||
|
@ -1,20 +1,24 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/dtos"
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/guardian"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestFolderPermissionAPIEndpoint(t *testing.T) {
|
||||
settings := setting.NewCfg()
|
||||
hs := &HTTPServer{Cfg: settings}
|
||||
|
||||
t.Run("Given folder not exists", func(t *testing.T) {
|
||||
mock := &fakeFolderService{
|
||||
GetFolderByUIDError: models.ErrFolderNotFound,
|
||||
@ -27,7 +31,7 @@ func TestFolderPermissionAPIEndpoint(t *testing.T) {
|
||||
mockFolderService(mock)
|
||||
|
||||
loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/folders/uid/permissions", "/api/folders/:uid/permissions", models.ROLE_EDITOR, func(sc *scenarioContext) {
|
||||
callGetFolderPermissions(sc)
|
||||
callGetFolderPermissions(sc, hs)
|
||||
assert.Equal(t, 404, sc.resp.Code)
|
||||
})
|
||||
|
||||
@ -37,10 +41,16 @@ func TestFolderPermissionAPIEndpoint(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
updateFolderPermissionScenario(t, "When calling POST on", "/api/folders/uid/permissions", "/api/folders/:uid/permissions", cmd, func(sc *scenarioContext) {
|
||||
callUpdateFolderPermissions(sc)
|
||||
assert.Equal(t, 404, sc.resp.Code)
|
||||
})
|
||||
updateFolderPermissionScenario(t, updatePermissionContext{
|
||||
desc: "When calling POST on",
|
||||
url: "/api/folders/uid/permissions",
|
||||
routePattern: "/api/folders/:uid/permissions",
|
||||
cmd: cmd,
|
||||
fn: func(sc *scenarioContext) {
|
||||
callUpdateFolderPermissions(sc)
|
||||
assert.Equal(t, 404, sc.resp.Code)
|
||||
},
|
||||
}, hs)
|
||||
})
|
||||
|
||||
t.Run("Given user has no admin permissions", func(t *testing.T) {
|
||||
@ -64,7 +74,7 @@ func TestFolderPermissionAPIEndpoint(t *testing.T) {
|
||||
mockFolderService(mock)
|
||||
|
||||
loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/folders/uid/permissions", "/api/folders/:uid/permissions", models.ROLE_EDITOR, func(sc *scenarioContext) {
|
||||
callGetFolderPermissions(sc)
|
||||
callGetFolderPermissions(sc, hs)
|
||||
assert.Equal(t, 403, sc.resp.Code)
|
||||
})
|
||||
|
||||
@ -74,10 +84,16 @@ func TestFolderPermissionAPIEndpoint(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
updateFolderPermissionScenario(t, "When calling POST on", "/api/folders/uid/permissions", "/api/folders/:uid/permissions", cmd, func(sc *scenarioContext) {
|
||||
callUpdateFolderPermissions(sc)
|
||||
assert.Equal(t, 403, sc.resp.Code)
|
||||
})
|
||||
updateFolderPermissionScenario(t, updatePermissionContext{
|
||||
desc: "When calling POST on",
|
||||
url: "/api/folders/uid/permissions",
|
||||
routePattern: "/api/folders/:uid/permissions",
|
||||
cmd: cmd,
|
||||
fn: func(sc *scenarioContext) {
|
||||
callUpdateFolderPermissions(sc)
|
||||
assert.Equal(t, 403, sc.resp.Code)
|
||||
},
|
||||
}, hs)
|
||||
})
|
||||
|
||||
t.Run("Given user has admin permissions and permissions to update", func(t *testing.T) {
|
||||
@ -111,13 +127,16 @@ func TestFolderPermissionAPIEndpoint(t *testing.T) {
|
||||
mockFolderService(mock)
|
||||
|
||||
loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/folders/uid/permissions", "/api/folders/:uid/permissions", models.ROLE_ADMIN, func(sc *scenarioContext) {
|
||||
callGetFolderPermissions(sc)
|
||||
callGetFolderPermissions(sc, hs)
|
||||
assert.Equal(t, 200, sc.resp.Code)
|
||||
respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes())
|
||||
|
||||
var resp []*models.DashboardAclInfoDTO
|
||||
err := json.Unmarshal(sc.resp.Body.Bytes(), &resp)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 5, len(respJSON.MustArray()))
|
||||
assert.Equal(t, 2, respJSON.GetIndex(0).Get("userId").MustInt())
|
||||
assert.Equal(t, int(models.PERMISSION_VIEW), respJSON.GetIndex(0).Get("permission").MustInt())
|
||||
|
||||
assert.Len(t, resp, 5)
|
||||
assert.Equal(t, int64(2), resp[0].UserId)
|
||||
assert.Equal(t, models.PERMISSION_VIEW, resp[0].Permission)
|
||||
})
|
||||
|
||||
cmd := dtos.UpdateDashboardAclCommand{
|
||||
@ -126,14 +145,26 @@ func TestFolderPermissionAPIEndpoint(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
updateFolderPermissionScenario(t, "When calling POST on", "/api/folders/uid/permissions", "/api/folders/:uid/permissions", cmd, func(sc *scenarioContext) {
|
||||
callUpdateFolderPermissions(sc)
|
||||
assert.Equal(t, 200, sc.resp.Code)
|
||||
respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes())
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 1, respJSON.Get("id").MustInt())
|
||||
assert.Equal(t, "Folder", respJSON.Get("title").MustString())
|
||||
})
|
||||
updateFolderPermissionScenario(t, updatePermissionContext{
|
||||
desc: "When calling POST on",
|
||||
url: "/api/folders/uid/permissions",
|
||||
routePattern: "/api/folders/:uid/permissions",
|
||||
cmd: cmd,
|
||||
fn: func(sc *scenarioContext) {
|
||||
callUpdateFolderPermissions(sc)
|
||||
assert.Equal(t, 200, sc.resp.Code)
|
||||
|
||||
var resp struct {
|
||||
ID int64
|
||||
Title string
|
||||
}
|
||||
err := json.Unmarshal(sc.resp.Body.Bytes(), &resp)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, int64(1), resp.ID)
|
||||
assert.Equal(t, "Folder", resp.Title)
|
||||
},
|
||||
}, hs)
|
||||
})
|
||||
|
||||
t.Run("When trying to update permissions with duplicate permissions", func(t *testing.T) {
|
||||
@ -166,10 +197,16 @@ func TestFolderPermissionAPIEndpoint(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
updateFolderPermissionScenario(t, "When calling POST on", "/api/folders/uid/permissions", "/api/folders/:uid/permissions", cmd, func(sc *scenarioContext) {
|
||||
callUpdateFolderPermissions(sc)
|
||||
assert.Equal(t, 400, sc.resp.Code)
|
||||
})
|
||||
updateFolderPermissionScenario(t, updatePermissionContext{
|
||||
desc: "When calling POST on",
|
||||
url: "/api/folders/uid/permissions",
|
||||
routePattern: "/api/folders/:uid/permissions",
|
||||
cmd: cmd,
|
||||
fn: func(sc *scenarioContext) {
|
||||
callUpdateFolderPermissions(sc)
|
||||
assert.Equal(t, 400, sc.resp.Code)
|
||||
},
|
||||
}, hs)
|
||||
})
|
||||
|
||||
t.Run("When trying to update team or user permissions with a role", func(t *testing.T) {
|
||||
@ -188,14 +225,19 @@ func TestFolderPermissionAPIEndpoint(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, cmd := range cmds {
|
||||
updateFolderPermissionScenario(t, "When calling POST on", "/api/folders/uid/permissions",
|
||||
"/api/folders/:uid/permissions", cmd, func(sc *scenarioContext) {
|
||||
updateFolderPermissionScenario(t, updatePermissionContext{
|
||||
desc: "When calling POST on",
|
||||
url: "/api/folders/uid/permissions",
|
||||
routePattern: "/api/folders/:uid/permissions",
|
||||
cmd: cmd,
|
||||
fn: func(sc *scenarioContext) {
|
||||
callUpdateFolderPermissions(sc)
|
||||
assert.Equal(t, 400, sc.resp.Code)
|
||||
respJSON, err := jsonMap(sc.resp.Body.Bytes())
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, models.ErrPermissionsWithRoleNotAllowed.Error(), respJSON["error"])
|
||||
})
|
||||
},
|
||||
}, hs)
|
||||
}
|
||||
})
|
||||
|
||||
@ -229,15 +271,102 @@ func TestFolderPermissionAPIEndpoint(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
updateFolderPermissionScenario(t, "When calling POST on", "/api/folders/uid/permissions", "/api/folders/:uid/permissions", cmd, func(sc *scenarioContext) {
|
||||
callUpdateFolderPermissions(sc)
|
||||
assert.Equal(t, 400, sc.resp.Code)
|
||||
updateFolderPermissionScenario(t, updatePermissionContext{
|
||||
desc: "When calling POST on",
|
||||
url: "/api/folders/uid/permissions",
|
||||
routePattern: "/api/folders/:uid/permissions",
|
||||
cmd: cmd,
|
||||
fn: func(sc *scenarioContext) {
|
||||
callUpdateFolderPermissions(sc)
|
||||
assert.Equal(t, 400, sc.resp.Code)
|
||||
},
|
||||
}, hs)
|
||||
})
|
||||
|
||||
t.Run("Getting and updating folder permissions with hidden users", func(t *testing.T) {
|
||||
origNewGuardian := guardian.New
|
||||
origNewFolderService := dashboards.NewFolderService
|
||||
settings.HiddenUsers = map[string]struct{}{
|
||||
"hiddenUser": {},
|
||||
testUserLogin: {},
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
guardian.New = origNewGuardian
|
||||
dashboards.NewFolderService = origNewFolderService
|
||||
settings.HiddenUsers = make(map[string]struct{})
|
||||
})
|
||||
|
||||
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{
|
||||
CanAdminValue: true,
|
||||
CheckPermissionBeforeUpdateValue: true,
|
||||
GetAclValue: []*models.DashboardAclInfoDTO{
|
||||
{OrgId: 1, DashboardId: 1, UserId: 2, UserLogin: "hiddenUser", Permission: models.PERMISSION_VIEW},
|
||||
{OrgId: 1, DashboardId: 1, UserId: 3, UserLogin: testUserLogin, Permission: models.PERMISSION_EDIT},
|
||||
{OrgId: 1, DashboardId: 1, UserId: 4, UserLogin: "user_1", Permission: models.PERMISSION_ADMIN},
|
||||
},
|
||||
GetHiddenAclValue: []*models.DashboardAcl{
|
||||
{OrgID: 1, DashboardID: 1, UserID: 2, Permission: models.PERMISSION_VIEW},
|
||||
},
|
||||
})
|
||||
|
||||
mock := &fakeFolderService{
|
||||
GetFolderByUIDResult: &models.Folder{
|
||||
Id: 1,
|
||||
Uid: "uid",
|
||||
Title: "Folder",
|
||||
},
|
||||
}
|
||||
|
||||
mockFolderService(mock)
|
||||
|
||||
var resp []*models.DashboardAclInfoDTO
|
||||
loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/folders/uid/permissions", "/api/folders/:uid/permissions", models.ROLE_ADMIN, func(sc *scenarioContext) {
|
||||
callGetFolderPermissions(sc, hs)
|
||||
assert.Equal(t, 200, sc.resp.Code)
|
||||
|
||||
err := json.Unmarshal(sc.resp.Body.Bytes(), &resp)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Len(t, resp, 2)
|
||||
assert.Equal(t, int64(3), resp[0].UserId)
|
||||
assert.Equal(t, models.PERMISSION_EDIT, resp[0].Permission)
|
||||
assert.Equal(t, int64(4), resp[1].UserId)
|
||||
assert.Equal(t, models.PERMISSION_ADMIN, resp[1].Permission)
|
||||
})
|
||||
|
||||
cmd := dtos.UpdateDashboardAclCommand{
|
||||
Items: []dtos.DashboardAclUpdateItem{
|
||||
{UserID: 1000, Permission: models.PERMISSION_ADMIN},
|
||||
},
|
||||
}
|
||||
for _, acl := range resp {
|
||||
cmd.Items = append(cmd.Items, dtos.DashboardAclUpdateItem{
|
||||
UserID: acl.UserId,
|
||||
Permission: acl.Permission,
|
||||
})
|
||||
}
|
||||
assert.Len(t, cmd.Items, 3)
|
||||
|
||||
updateFolderPermissionScenario(t, updatePermissionContext{
|
||||
desc: "When calling POST on",
|
||||
url: "/api/folders/uid/permissions",
|
||||
routePattern: "/api/folders/:uid/permissions",
|
||||
cmd: cmd,
|
||||
fn: func(sc *scenarioContext) {
|
||||
bus.AddHandler("test", func(cmd *models.UpdateDashboardAclCommand) error {
|
||||
assert.Len(t, cmd.Items, 4)
|
||||
return nil
|
||||
})
|
||||
|
||||
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
|
||||
assert.Equal(t, 200, sc.resp.Code)
|
||||
},
|
||||
}, hs)
|
||||
})
|
||||
}
|
||||
|
||||
func callGetFolderPermissions(sc *scenarioContext) {
|
||||
sc.handlerFunc = GetFolderPermissionList
|
||||
func callGetFolderPermissions(sc *scenarioContext, hs *HTTPServer) {
|
||||
sc.handlerFunc = hs.GetFolderPermissionList
|
||||
sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
|
||||
}
|
||||
|
||||
@ -249,22 +378,22 @@ func callUpdateFolderPermissions(sc *scenarioContext) {
|
||||
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
|
||||
}
|
||||
|
||||
func updateFolderPermissionScenario(t *testing.T, desc string, url string, routePattern string, cmd dtos.UpdateDashboardAclCommand, fn scenarioFunc) {
|
||||
t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
|
||||
func updateFolderPermissionScenario(t *testing.T, ctx updatePermissionContext, hs *HTTPServer) {
|
||||
t.Run(fmt.Sprintf("%s %s", ctx.desc, ctx.url), func(t *testing.T) {
|
||||
defer bus.ClearBusHandlers()
|
||||
|
||||
sc := setupScenarioContext(t, url)
|
||||
sc := setupScenarioContext(t, ctx.url)
|
||||
|
||||
sc.defaultHandler = Wrap(func(c *models.ReqContext) Response {
|
||||
sc.context = c
|
||||
sc.context.OrgId = testOrgID
|
||||
sc.context.UserId = testUserID
|
||||
|
||||
return UpdateFolderPermissions(c, cmd)
|
||||
return hs.UpdateFolderPermissions(c, ctx.cmd)
|
||||
})
|
||||
|
||||
sc.m.Post(routePattern, sc.defaultHandler)
|
||||
sc.m.Post(ctx.routePattern, sc.defaultHandler)
|
||||
|
||||
fn(sc)
|
||||
ctx.fn(sc)
|
||||
})
|
||||
}
|
||||
|
@ -53,8 +53,13 @@ func addOrgUserHelper(cmd models.AddOrgUserCommand) Response {
|
||||
}
|
||||
|
||||
// GET /api/org/users
|
||||
func GetOrgUsersForCurrentOrg(c *models.ReqContext) Response {
|
||||
result, err := getOrgUsersHelper(c.OrgId, c.Query("query"), c.QueryInt("limit"))
|
||||
func (hs *HTTPServer) GetOrgUsersForCurrentOrg(c *models.ReqContext) Response {
|
||||
result, err := hs.getOrgUsersHelper(&models.GetOrgUsersQuery{
|
||||
OrgId: c.OrgId,
|
||||
Query: c.Query("query"),
|
||||
Limit: c.QueryInt("limit"),
|
||||
}, c.SignedInUser)
|
||||
|
||||
if err != nil {
|
||||
return Error(500, "Failed to get users for current organization", err)
|
||||
}
|
||||
@ -63,7 +68,7 @@ func GetOrgUsersForCurrentOrg(c *models.ReqContext) Response {
|
||||
}
|
||||
|
||||
// GET /api/org/users/lookup
|
||||
func GetOrgUsersForCurrentOrgLookup(c *models.ReqContext) Response {
|
||||
func (hs *HTTPServer) GetOrgUsersForCurrentOrgLookup(c *models.ReqContext) Response {
|
||||
isAdmin, err := isOrgAdminFolderAdminOrTeamAdmin(c)
|
||||
if err != nil {
|
||||
return Error(500, "Failed to get users for current organization", err)
|
||||
@ -73,7 +78,12 @@ func GetOrgUsersForCurrentOrgLookup(c *models.ReqContext) Response {
|
||||
return Error(403, "Permission denied", nil)
|
||||
}
|
||||
|
||||
orgUsers, err := getOrgUsersHelper(c.OrgId, c.Query("query"), c.QueryInt("limit"))
|
||||
orgUsers, err := hs.getOrgUsersHelper(&models.GetOrgUsersQuery{
|
||||
OrgId: c.OrgId,
|
||||
Query: c.Query("query"),
|
||||
Limit: c.QueryInt("limit"),
|
||||
}, c.SignedInUser)
|
||||
|
||||
if err != nil {
|
||||
return Error(500, "Failed to get users for current organization", err)
|
||||
}
|
||||
@ -114,8 +124,13 @@ func isOrgAdminFolderAdminOrTeamAdmin(c *models.ReqContext) (bool, error) {
|
||||
}
|
||||
|
||||
// GET /api/orgs/:orgId/users
|
||||
func GetOrgUsers(c *models.ReqContext) Response {
|
||||
result, err := getOrgUsersHelper(c.ParamsInt64(":orgId"), "", 0)
|
||||
func (hs *HTTPServer) GetOrgUsers(c *models.ReqContext) Response {
|
||||
result, err := hs.getOrgUsersHelper(&models.GetOrgUsersQuery{
|
||||
OrgId: c.ParamsInt64(":orgId"),
|
||||
Query: "",
|
||||
Limit: 0,
|
||||
}, c.SignedInUser)
|
||||
|
||||
if err != nil {
|
||||
return Error(500, "Failed to get users for organization", err)
|
||||
}
|
||||
@ -123,22 +138,22 @@ func GetOrgUsers(c *models.ReqContext) Response {
|
||||
return JSON(200, result)
|
||||
}
|
||||
|
||||
func getOrgUsersHelper(orgID int64, query string, limit int) ([]*models.OrgUserDTO, error) {
|
||||
q := models.GetOrgUsersQuery{
|
||||
OrgId: orgID,
|
||||
Query: query,
|
||||
Limit: limit,
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(&q); err != nil {
|
||||
func (hs *HTTPServer) getOrgUsersHelper(query *models.GetOrgUsersQuery, signedInUser *models.SignedInUser) ([]*models.OrgUserDTO, error) {
|
||||
if err := bus.Dispatch(query); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, user := range q.Result {
|
||||
filteredUsers := make([]*models.OrgUserDTO, 0, len(query.Result))
|
||||
for _, user := range query.Result {
|
||||
if dtos.IsHiddenUser(user.Login, signedInUser, hs.Cfg) {
|
||||
continue
|
||||
}
|
||||
user.AvatarUrl = dtos.GetGravatarUrl(user.Email)
|
||||
|
||||
filteredUsers = append(filteredUsers, user)
|
||||
}
|
||||
|
||||
return q.Result, nil
|
||||
return filteredUsers, nil
|
||||
}
|
||||
|
||||
// PATCH /api/org/users/:userId
|
||||
|
126
pkg/api/org_users_test.go
Normal file
126
pkg/api/org_users_test.go
Normal file
@ -0,0 +1,126 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/dtos"
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func setUpGetOrgUsersHandler() {
|
||||
bus.AddHandler("test", func(query *models.GetOrgUsersQuery) error {
|
||||
query.Result = []*models.OrgUserDTO{
|
||||
{Email: "testUser@grafana.com", Login: testUserLogin},
|
||||
{Email: "user1@grafana.com", Login: "user1"},
|
||||
{Email: "user2@grafana.com", Login: "user2"},
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func TestOrgUsersAPIEndpoint_userLoggedIn(t *testing.T) {
|
||||
settings := setting.NewCfg()
|
||||
hs := &HTTPServer{Cfg: settings}
|
||||
|
||||
loggedInUserScenario(t, "When calling GET on", "api/org/users", func(sc *scenarioContext) {
|
||||
setUpGetOrgUsersHandler()
|
||||
|
||||
sc.handlerFunc = hs.GetOrgUsersForCurrentOrg
|
||||
sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
|
||||
|
||||
require.Equal(t, http.StatusOK, sc.resp.Code)
|
||||
|
||||
var resp []models.OrgUserDTO
|
||||
err := json.Unmarshal(sc.resp.Body.Bytes(), &resp)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, resp, 3)
|
||||
})
|
||||
|
||||
loggedInUserScenario(t, "When calling GET as an editor with no team / folder permissions on",
|
||||
"api/org/users/lookup", func(sc *scenarioContext) {
|
||||
setUpGetOrgUsersHandler()
|
||||
bus.AddHandler("test", func(query *models.HasAdminPermissionInFoldersQuery) error {
|
||||
query.Result = false
|
||||
return nil
|
||||
})
|
||||
bus.AddHandler("test", func(query *models.IsAdminOfTeamsQuery) error {
|
||||
query.Result = false
|
||||
return nil
|
||||
})
|
||||
|
||||
sc.handlerFunc = hs.GetOrgUsersForCurrentOrgLookup
|
||||
sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
|
||||
|
||||
assert.Equal(t, http.StatusForbidden, sc.resp.Code)
|
||||
|
||||
var resp struct {
|
||||
Message string
|
||||
}
|
||||
err := json.Unmarshal(sc.resp.Body.Bytes(), &resp)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "Permission denied", resp.Message)
|
||||
})
|
||||
|
||||
loggedInUserScenarioWithRole(t, "When calling GET as an admin on", "GET", "api/org/users/lookup",
|
||||
"api/org/users/lookup", models.ROLE_ADMIN, func(sc *scenarioContext) {
|
||||
setUpGetOrgUsersHandler()
|
||||
|
||||
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, 3)
|
||||
})
|
||||
|
||||
t.Run("Given there is 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", func(sc *scenarioContext) {
|
||||
setUpGetOrgUsersHandler()
|
||||
|
||||
sc.handlerFunc = hs.GetOrgUsersForCurrentOrg
|
||||
sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
|
||||
|
||||
require.Equal(t, http.StatusOK, sc.resp.Code)
|
||||
|
||||
var resp []models.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)
|
||||
})
|
||||
|
||||
loggedInUserScenarioWithRole(t, "When calling GET as an admin on", "GET", "api/org/users/lookup",
|
||||
"api/org/users/lookup", models.ROLE_ADMIN, func(sc *scenarioContext) {
|
||||
setUpGetOrgUsersHandler()
|
||||
|
||||
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)
|
||||
})
|
||||
})
|
||||
}
|
@ -112,6 +112,8 @@ func (hs *HTTPServer) SearchTeams(c *models.ReqContext) Response {
|
||||
UserIdFilter: userIdFilter,
|
||||
Page: page,
|
||||
Limit: perPage,
|
||||
SignedInUser: c.SignedInUser,
|
||||
HiddenUsers: hs.Cfg.HiddenUsers,
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
@ -129,8 +131,13 @@ func (hs *HTTPServer) SearchTeams(c *models.ReqContext) Response {
|
||||
}
|
||||
|
||||
// GET /api/teams/:teamId
|
||||
func GetTeamByID(c *models.ReqContext) Response {
|
||||
query := models.GetTeamByIdQuery{OrgId: c.OrgId, Id: c.ParamsInt64(":teamId")}
|
||||
func (hs *HTTPServer) GetTeamByID(c *models.ReqContext) Response {
|
||||
query := models.GetTeamByIdQuery{
|
||||
OrgId: c.OrgId,
|
||||
Id: c.ParamsInt64(":teamId"),
|
||||
SignedInUser: c.SignedInUser,
|
||||
HiddenUsers: hs.Cfg.HiddenUsers,
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
if errors.Is(err, models.ErrTeamNotFound) {
|
||||
|
@ -18,7 +18,12 @@ func (hs *HTTPServer) GetTeamMembers(c *models.ReqContext) Response {
|
||||
return Error(500, "Failed to get Team Members", err)
|
||||
}
|
||||
|
||||
filteredMembers := make([]*models.TeamMemberDTO, 0, len(query.Result))
|
||||
for _, member := range query.Result {
|
||||
if dtos.IsHiddenUser(member.Login, c.SignedInUser, hs.Cfg) {
|
||||
continue
|
||||
}
|
||||
|
||||
member.AvatarUrl = dtos.GetGravatarUrl(member.Email)
|
||||
member.Labels = []string{}
|
||||
|
||||
@ -26,9 +31,11 @@ func (hs *HTTPServer) GetTeamMembers(c *models.ReqContext) Response {
|
||||
authProvider := GetAuthProviderLabel(member.AuthModule)
|
||||
member.Labels = append(member.Labels, authProvider)
|
||||
}
|
||||
|
||||
filteredMembers = append(filteredMembers, member)
|
||||
}
|
||||
|
||||
return JSON(200, query.Result)
|
||||
return JSON(200, filteredMembers)
|
||||
}
|
||||
|
||||
// POST /api/teams/:teamId/members
|
||||
|
73
pkg/api/team_members_test.go
Normal file
73
pkg/api/team_members_test.go
Normal file
@ -0,0 +1,73 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/licensing"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func setUpGetTeamMembersHandler() {
|
||||
bus.AddHandler("test", func(query *models.GetTeamMembersQuery) error {
|
||||
query.Result = []*models.TeamMemberDTO{
|
||||
{Email: "testUser@grafana.com", Login: testUserLogin},
|
||||
{Email: "user1@grafana.com", Login: "user1"},
|
||||
{Email: "user2@grafana.com", Login: "user2"},
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func TestTeamMembersAPIEndpoint_userLoggedIn(t *testing.T) {
|
||||
settings := setting.NewCfg()
|
||||
hs := &HTTPServer{
|
||||
Cfg: settings,
|
||||
License: &licensing.OSSLicensingService{},
|
||||
}
|
||||
|
||||
loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "api/teams/1/members",
|
||||
"api/teams/:teamId/members", models.ROLE_ADMIN, func(sc *scenarioContext) {
|
||||
setUpGetTeamMembersHandler()
|
||||
|
||||
sc.handlerFunc = hs.GetTeamMembers
|
||||
sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
|
||||
|
||||
require.Equal(t, http.StatusOK, sc.resp.Code)
|
||||
|
||||
var resp []models.TeamMemberDTO
|
||||
err := json.Unmarshal(sc.resp.Body.Bytes(), &resp)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, resp, 3)
|
||||
})
|
||||
|
||||
t.Run("Given there is two hidden users", func(t *testing.T) {
|
||||
settings.HiddenUsers = map[string]struct{}{
|
||||
"user1": {},
|
||||
testUserLogin: {},
|
||||
}
|
||||
t.Cleanup(func() { settings.HiddenUsers = make(map[string]struct{}) })
|
||||
|
||||
loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "api/teams/1/members",
|
||||
"api/teams/:teamId/members", models.ROLE_ADMIN, func(sc *scenarioContext) {
|
||||
setUpGetTeamMembersHandler()
|
||||
|
||||
sc.handlerFunc = hs.GetTeamMembers
|
||||
sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
|
||||
|
||||
require.Equal(t, http.StatusOK, sc.resp.Code)
|
||||
|
||||
var resp []models.TeamMemberDTO
|
||||
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)
|
||||
})
|
||||
})
|
||||
}
|
@ -50,9 +50,11 @@ type DeleteTeamCommand struct {
|
||||
}
|
||||
|
||||
type GetTeamByIdQuery struct {
|
||||
OrgId int64
|
||||
Id int64
|
||||
Result *TeamDTO
|
||||
OrgId int64
|
||||
Id int64
|
||||
SignedInUser *SignedInUser
|
||||
HiddenUsers map[string]struct{}
|
||||
Result *TeamDTO
|
||||
}
|
||||
|
||||
type GetTeamsByUserQuery struct {
|
||||
@ -68,6 +70,8 @@ type SearchTeamsQuery struct {
|
||||
Page int
|
||||
OrgId int64
|
||||
UserIdFilter int64
|
||||
SignedInUser *SignedInUser
|
||||
HiddenUsers map[string]struct{}
|
||||
|
||||
Result SearchTeamQueryResult
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ type DashboardGuardian interface {
|
||||
HasPermission(permission models.PermissionType) (bool, error)
|
||||
CheckPermissionBeforeUpdate(permission models.PermissionType, updatePermissions []*models.DashboardAcl) (bool, error)
|
||||
GetAcl() ([]*models.DashboardAclInfoDTO, error)
|
||||
GetHiddenACL(*setting.Cfg) ([]*models.DashboardAcl, error)
|
||||
}
|
||||
|
||||
type dashboardGuardianImpl struct {
|
||||
@ -213,6 +214,38 @@ func (g *dashboardGuardianImpl) getTeams() ([]*models.TeamDTO, error) {
|
||||
return query.Result, err
|
||||
}
|
||||
|
||||
func (g *dashboardGuardianImpl) GetHiddenACL(cfg *setting.Cfg) ([]*models.DashboardAcl, error) {
|
||||
hiddenACL := make([]*models.DashboardAcl, 0)
|
||||
if g.user.IsGrafanaAdmin {
|
||||
return hiddenACL, nil
|
||||
}
|
||||
|
||||
existingPermissions, err := g.GetAcl()
|
||||
if err != nil {
|
||||
return hiddenACL, err
|
||||
}
|
||||
|
||||
for _, item := range existingPermissions {
|
||||
if item.Inherited || item.UserLogin == g.user.Login {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, hidden := cfg.HiddenUsers[item.UserLogin]; hidden {
|
||||
hiddenACL = append(hiddenACL, &models.DashboardAcl{
|
||||
OrgID: item.OrgId,
|
||||
DashboardID: item.DashboardId,
|
||||
UserID: item.UserId,
|
||||
TeamID: item.TeamId,
|
||||
Role: item.Role,
|
||||
Permission: item.Permission,
|
||||
Created: item.Created,
|
||||
Updated: item.Updated,
|
||||
})
|
||||
}
|
||||
}
|
||||
return hiddenACL, nil
|
||||
}
|
||||
|
||||
// nolint:unused
|
||||
type FakeDashboardGuardian struct {
|
||||
DashId int64
|
||||
@ -226,6 +259,7 @@ type FakeDashboardGuardian struct {
|
||||
CheckPermissionBeforeUpdateValue bool
|
||||
CheckPermissionBeforeUpdateError error
|
||||
GetAclValue []*models.DashboardAclInfoDTO
|
||||
GetHiddenAclValue []*models.DashboardAcl
|
||||
}
|
||||
|
||||
func (g *FakeDashboardGuardian) CanSave() (bool, error) {
|
||||
@ -256,6 +290,10 @@ func (g *FakeDashboardGuardian) GetAcl() ([]*models.DashboardAclInfoDTO, error)
|
||||
return g.GetAclValue, nil
|
||||
}
|
||||
|
||||
func (g *FakeDashboardGuardian) GetHiddenACL(cfg *setting.Cfg) ([]*models.DashboardAcl, error) {
|
||||
return g.GetHiddenAclValue, nil
|
||||
}
|
||||
|
||||
// nolint:unused
|
||||
func MockDashboardGuardian(mock *FakeDashboardGuardian) {
|
||||
New = func(dashId int64, orgId int64, user *models.SignedInUser) DashboardGuardian {
|
||||
|
@ -6,7 +6,10 @@ import (
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@ -673,3 +676,51 @@ func (sc *scenarioContext) verifyUpdateChildDashboardPermissionsWithOverrideShou
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGuardianGetHiddenACL(t *testing.T) {
|
||||
Convey("Get hidden ACL tests", t, func() {
|
||||
bus.ClearBusHandlers()
|
||||
|
||||
bus.AddHandler("test", func(query *models.GetDashboardAclInfoListQuery) error {
|
||||
query.Result = []*models.DashboardAclInfoDTO{
|
||||
{Inherited: false, UserId: 1, UserLogin: "user1", Permission: models.PERMISSION_EDIT},
|
||||
{Inherited: false, UserId: 2, UserLogin: "user2", Permission: models.PERMISSION_ADMIN},
|
||||
{Inherited: true, UserId: 3, UserLogin: "user3", Permission: models.PERMISSION_VIEW},
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
cfg := setting.NewCfg()
|
||||
cfg.HiddenUsers = map[string]struct{}{"user2": {}}
|
||||
|
||||
Convey("Should get hidden acl", func() {
|
||||
user := &models.SignedInUser{
|
||||
OrgId: orgID,
|
||||
UserId: 1,
|
||||
Login: "user1",
|
||||
}
|
||||
g := New(dashboardID, orgID, user)
|
||||
|
||||
hiddenACL, err := g.GetHiddenACL(cfg)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(hiddenACL, ShouldHaveLength, 1)
|
||||
So(hiddenACL[0].UserID, ShouldEqual, 2)
|
||||
})
|
||||
|
||||
Convey("Grafana admin should not get hidden acl", func() {
|
||||
user := &models.SignedInUser{
|
||||
OrgId: orgID,
|
||||
UserId: 1,
|
||||
Login: "user1",
|
||||
IsGrafanaAdmin: true,
|
||||
}
|
||||
g := New(dashboardID, orgID, user)
|
||||
|
||||
hiddenACL, err := g.GetHiddenACL(cfg)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(hiddenACL, ShouldHaveLength, 0)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package sqlstore
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
@ -24,26 +25,54 @@ func init() {
|
||||
bus.AddHandler("sql", IsAdminOfTeams)
|
||||
}
|
||||
|
||||
func getTeamSearchSQLBase() string {
|
||||
return `SELECT
|
||||
team.id as id,
|
||||
team.org_id,
|
||||
team.name as name,
|
||||
team.email as email,
|
||||
(SELECT COUNT(*) from team_member where team_member.team_id = team.id) as member_count,
|
||||
team_member.permission
|
||||
FROM team as team
|
||||
INNER JOIN team_member on team.id = team_member.team_id AND team_member.user_id = ? `
|
||||
func getFilteredUsers(signedInUser *models.SignedInUser, hiddenUsers map[string]struct{}) []string {
|
||||
filteredUsers := make([]string, 0, len(hiddenUsers))
|
||||
if signedInUser == nil || signedInUser.IsGrafanaAdmin {
|
||||
return filteredUsers
|
||||
}
|
||||
|
||||
for u := range hiddenUsers {
|
||||
if u == signedInUser.Login {
|
||||
continue
|
||||
}
|
||||
filteredUsers = append(filteredUsers, u)
|
||||
}
|
||||
|
||||
return filteredUsers
|
||||
}
|
||||
|
||||
func getTeamSelectSQLBase() string {
|
||||
func getTeamMemberCount(filteredUsers []string) string {
|
||||
if len(filteredUsers) > 0 {
|
||||
return `(SELECT COUNT(*) FROM team_member
|
||||
INNER JOIN ` + dialect.Quote("user") + ` ON team_member.user_id = ` + dialect.Quote("user") + `.id
|
||||
WHERE team_member.team_id = team.id AND ` + dialect.Quote("user") + `.login NOT IN (?` +
|
||||
strings.Repeat(",?", len(filteredUsers)-1) + ")" +
|
||||
`) AS member_count `
|
||||
}
|
||||
|
||||
return "(SELECT COUNT(*) FROM team_member WHERE team_member.team_id = team.id) AS member_count "
|
||||
}
|
||||
|
||||
func getTeamSearchSQLBase(filteredUsers []string) string {
|
||||
return `SELECT
|
||||
team.id AS id,
|
||||
team.org_id,
|
||||
team.name AS name,
|
||||
team.email AS email,
|
||||
team_member.permission, ` +
|
||||
getTeamMemberCount(filteredUsers) +
|
||||
` FROM team AS team
|
||||
INNER JOIN team_member ON team.id = team_member.team_id AND team_member.user_id = ? `
|
||||
}
|
||||
|
||||
func getTeamSelectSQLBase(filteredUsers []string) string {
|
||||
return `SELECT
|
||||
team.id as id,
|
||||
team.org_id,
|
||||
team.name as name,
|
||||
team.email as email,
|
||||
(SELECT COUNT(*) from team_member where team_member.team_id = team.id) as member_count
|
||||
FROM team as team `
|
||||
team.email as email, ` +
|
||||
getTeamMemberCount(filteredUsers) +
|
||||
` FROM team as team `
|
||||
}
|
||||
|
||||
func CreateTeam(cmd *models.CreateTeamCommand) error {
|
||||
@ -157,14 +186,21 @@ func SearchTeams(query *models.SearchTeamsQuery) error {
|
||||
var sql bytes.Buffer
|
||||
params := make([]interface{}, 0)
|
||||
|
||||
filteredUsers := getFilteredUsers(query.SignedInUser, query.HiddenUsers)
|
||||
if query.UserIdFilter > 0 {
|
||||
sql.WriteString(getTeamSearchSQLBase())
|
||||
sql.WriteString(getTeamSearchSQLBase(filteredUsers))
|
||||
for _, user := range filteredUsers {
|
||||
params = append(params, user)
|
||||
}
|
||||
params = append(params, query.UserIdFilter)
|
||||
} else {
|
||||
sql.WriteString(getTeamSelectSQLBase())
|
||||
sql.WriteString(getTeamSelectSQLBase(filteredUsers))
|
||||
for _, user := range filteredUsers {
|
||||
params = append(params, user)
|
||||
}
|
||||
}
|
||||
sql.WriteString(` WHERE team.org_id = ?`)
|
||||
|
||||
sql.WriteString(` WHERE team.org_id = ?`)
|
||||
params = append(params, query.OrgId)
|
||||
|
||||
if query.Query != "" {
|
||||
@ -206,12 +242,19 @@ func SearchTeams(query *models.SearchTeamsQuery) error {
|
||||
|
||||
func GetTeamById(query *models.GetTeamByIdQuery) error {
|
||||
var sql bytes.Buffer
|
||||
params := make([]interface{}, 0)
|
||||
|
||||
filteredUsers := getFilteredUsers(query.SignedInUser, query.HiddenUsers)
|
||||
sql.WriteString(getTeamSelectSQLBase(filteredUsers))
|
||||
for _, user := range filteredUsers {
|
||||
params = append(params, user)
|
||||
}
|
||||
|
||||
sql.WriteString(getTeamSelectSQLBase())
|
||||
sql.WriteString(` WHERE team.org_id = ? and team.id = ?`)
|
||||
params = append(params, query.OrgId, query.Id)
|
||||
|
||||
var team models.TeamDTO
|
||||
exists, err := x.SQL(sql.String(), query.OrgId, query.Id).Get(&team)
|
||||
exists, err := x.SQL(sql.String(), params...).Get(&team)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
@ -231,7 +274,7 @@ func GetTeamsByUser(query *models.GetTeamsByUserQuery) error {
|
||||
|
||||
var sql bytes.Buffer
|
||||
|
||||
sql.WriteString(getTeamSelectSQLBase())
|
||||
sql.WriteString(getTeamSelectSQLBase([]string{}))
|
||||
sql.WriteString(` INNER JOIN team_member on team.id = team_member.team_id`)
|
||||
sql.WriteString(` WHERE team.org_id = ? and team_member.user_id = ?`)
|
||||
|
||||
|
@ -48,6 +48,7 @@ func TestTeamCommandsAndQueries(t *testing.T) {
|
||||
So(team1.Name, ShouldEqual, "group1 name")
|
||||
So(team1.Email, ShouldEqual, "test1@test.com")
|
||||
So(team1.OrgId, ShouldEqual, testOrgId)
|
||||
So(team1.MemberCount, ShouldEqual, 0)
|
||||
|
||||
err = AddTeamMember(&models.AddTeamMemberCommand{OrgId: testOrgId, TeamId: team1.Id, UserId: userIds[0]})
|
||||
So(err, ShouldBeNil)
|
||||
@ -74,6 +75,20 @@ func TestTeamCommandsAndQueries(t *testing.T) {
|
||||
So(q2.Result[0].Login, ShouldEqual, "loginuser1")
|
||||
So(q2.Result[0].OrgId, ShouldEqual, testOrgId)
|
||||
So(q2.Result[0].External, ShouldEqual, true)
|
||||
|
||||
err = SearchTeams(query)
|
||||
So(err, ShouldBeNil)
|
||||
team1 = query.Result.Teams[0]
|
||||
So(team1.MemberCount, ShouldEqual, 2)
|
||||
|
||||
getTeamQuery := &models.GetTeamByIdQuery{OrgId: testOrgId, Id: team1.Id}
|
||||
err = GetTeamById(getTeamQuery)
|
||||
So(err, ShouldBeNil)
|
||||
team1 = getTeamQuery.Result
|
||||
So(team1.Name, ShouldEqual, "group1 name")
|
||||
So(team1.Email, ShouldEqual, "test1@test.com")
|
||||
So(team1.OrgId, ShouldEqual, testOrgId)
|
||||
So(team1.MemberCount, ShouldEqual, 2)
|
||||
})
|
||||
|
||||
Convey("Should return latest auth module for users when getting team members", func() {
|
||||
@ -275,6 +290,38 @@ func TestTeamCommandsAndQueries(t *testing.T) {
|
||||
So(err, ShouldBeNil)
|
||||
So(query.Result, ShouldBeTrue)
|
||||
})
|
||||
|
||||
Convey("Should not return hidden users in team member count", func() {
|
||||
signedInUser := &models.SignedInUser{Login: "loginuser0"}
|
||||
hiddenUsers := map[string]struct{}{"loginuser0": {}, "loginuser1": {}}
|
||||
|
||||
teamId := group1.Result.Id
|
||||
err = AddTeamMember(&models.AddTeamMemberCommand{OrgId: testOrgId, TeamId: teamId, UserId: userIds[0]})
|
||||
So(err, ShouldBeNil)
|
||||
err = AddTeamMember(&models.AddTeamMemberCommand{OrgId: testOrgId, TeamId: teamId, UserId: userIds[1]})
|
||||
So(err, ShouldBeNil)
|
||||
err = AddTeamMember(&models.AddTeamMemberCommand{OrgId: testOrgId, TeamId: teamId, UserId: userIds[2]})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
searchQuery := &models.SearchTeamsQuery{OrgId: testOrgId, Page: 1, Limit: 10, SignedInUser: signedInUser, HiddenUsers: hiddenUsers}
|
||||
err = SearchTeams(searchQuery)
|
||||
So(err, ShouldBeNil)
|
||||
So(searchQuery.Result.Teams, ShouldHaveLength, 2)
|
||||
team1 := searchQuery.Result.Teams[0]
|
||||
So(team1.MemberCount, ShouldEqual, 2)
|
||||
|
||||
searchQueryFilteredByUser := &models.SearchTeamsQuery{OrgId: testOrgId, Page: 1, Limit: 10, UserIdFilter: userIds[0], SignedInUser: signedInUser, HiddenUsers: hiddenUsers}
|
||||
err = SearchTeams(searchQueryFilteredByUser)
|
||||
So(err, ShouldBeNil)
|
||||
So(searchQueryFilteredByUser.Result.Teams, ShouldHaveLength, 1)
|
||||
team1 = searchQuery.Result.Teams[0]
|
||||
So(team1.MemberCount, ShouldEqual, 2)
|
||||
|
||||
getTeamQuery := &models.GetTeamByIdQuery{OrgId: testOrgId, Id: teamId, SignedInUser: signedInUser, HiddenUsers: hiddenUsers}
|
||||
err = GetTeamById(getTeamQuery)
|
||||
So(err, ShouldBeNil)
|
||||
So(getTeamQuery.Result.MemberCount, ShouldEqual, 2)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -308,6 +308,7 @@ type Cfg struct {
|
||||
|
||||
// User
|
||||
UserInviteMaxLifetime time.Duration
|
||||
HiddenUsers map[string]struct{}
|
||||
|
||||
// Annotations
|
||||
AlertingAnnotationCleanupSetting AnnotationCleanupSettings
|
||||
@ -1118,6 +1119,13 @@ func readUserSettings(iniFile *ini.File, cfg *Cfg) error {
|
||||
return errors.New("the minimum supported value for the `user_invite_max_lifetime_duration` configuration is 15m (15 minutes)")
|
||||
}
|
||||
|
||||
cfg.HiddenUsers = make(map[string]struct{})
|
||||
hiddenUsers := users.Key("hidden_users").MustString("")
|
||||
for _, user := range strings.Split(hiddenUsers, ",") {
|
||||
user = strings.TrimSpace(user)
|
||||
cfg.HiddenUsers[user] = struct{}{}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user