Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Configuration: Add an option to hide certain users in the UI #28942

Merged
merged 34 commits into from Nov 24, 2020
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
7bf8601
Add an option to hide certain users in the UI
AgnesToulet Nov 9, 2020
b68cf02
revert changes for admin users routes
AgnesToulet Nov 9, 2020
affa394
fix sqlstore function name
AgnesToulet Nov 9, 2020
403ba09
Improve slice management
AgnesToulet Nov 10, 2020
dff0bf2
Hidden users: convert slice to map
AgnesToulet Nov 10, 2020
a247aa7
filter with user logins instead of IDs
AgnesToulet Nov 10, 2020
22e0faa
put HiddenUsers in Cfg struct
AgnesToulet Nov 10, 2020
ec5e6d4
hide hidden users from dashboards/folders permissions list
AgnesToulet Nov 10, 2020
78b3f52
Update conf/defaults.ini
AgnesToulet Nov 10, 2020
11c48d8
fix params order
AgnesToulet Nov 10, 2020
85766f2
Merge branch 'config-hidden-users' of https://github.com/AgnesToulet/…
AgnesToulet Nov 10, 2020
ba41f68
fix tests
AgnesToulet Nov 10, 2020
69f5f88
Merge branch 'master' into config-hidden-users
AgnesToulet Nov 10, 2020
5c05906
fix dashboard/folder update with hidden user
AgnesToulet Nov 10, 2020
c6ae7a3
add team tests
AgnesToulet Nov 13, 2020
271c4c8
add dashboard and folder permissions tests
AgnesToulet Nov 13, 2020
f134875
Merge branch 'master' into config-hidden-users
AgnesToulet Nov 13, 2020
a14aed3
fixes after merge
AgnesToulet Nov 13, 2020
478fd94
fix tests
AgnesToulet Nov 13, 2020
30f7a70
Merge branch 'master' into config-hidden-users
AgnesToulet Nov 17, 2020
12e9164
API: add test for org users endpoints
AgnesToulet Nov 17, 2020
5df868a
update hidden users management for dashboard / folder permissions
AgnesToulet Nov 17, 2020
7da897b
Merge branch 'master' into config-hidden-users
AgnesToulet Nov 17, 2020
4d28116
improve dashboard / folder permissions tests
AgnesToulet Nov 17, 2020
319b968
Merge branch 'master' into config-hidden-users
AgnesToulet Nov 17, 2020
6661337
fixes after merge
AgnesToulet Nov 17, 2020
b6d7d44
Guardian: add hidden acl tests
AgnesToulet Nov 18, 2020
674a514
API: add team members tests
AgnesToulet Nov 18, 2020
868ff29
fix team sql syntax for postgres
AgnesToulet Nov 18, 2020
7461502
api tests update
AgnesToulet Nov 18, 2020
578647b
fix linter error
AgnesToulet Nov 18, 2020
e94c168
Merge branch 'master' into config-hidden-users
xlson Nov 24, 2020
9e91d73
fix tests errors after merge
AgnesToulet Nov 24, 2020
0f05c1a
Merge branch 'master' into config-hidden-users
xlson Nov 24, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions conf/defaults.ini
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions conf/sample.ini
Expand Up @@ -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
Expand Down
12 changes: 6 additions & 6 deletions pkg/api/api.go
Expand Up @@ -167,7 +167,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))
})

Expand All @@ -181,7 +181,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))
Expand All @@ -198,7 +198,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
Expand All @@ -213,7 +213,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))
Expand Down Expand Up @@ -284,7 +284,7 @@ func (hs *HTTPServer) registerRoutes() {
folderUidRoute.Delete("/", Wrap(DeleteFolder))

folderUidRoute.Group("/permissions", func(folderPermissionRoute routing.RouteRegister) {
folderPermissionRoute.Get("/", Wrap(GetFolderPermissionList))
folderPermissionRoute.Get("/", Wrap(hs.GetFolderPermissionList))
folderPermissionRoute.Post("/", bind(dtos.UpdateDashboardAclCommand{}), Wrap(UpdateFolderPermissions))
})
})
Expand All @@ -311,7 +311,7 @@ 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.Get("/", Wrap(hs.GetDashboardPermissionList))
dashboardPermissionRoute.Post("/", bind(dtos.UpdateDashboardAclCommand{}), Wrap(UpdateDashboardPermissions))
})
})
Expand Down
11 changes: 9 additions & 2 deletions pkg/api/dashboard_permission.go
Expand Up @@ -9,7 +9,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, "")
Expand All @@ -28,7 +28,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 {
Expand All @@ -37,9 +42,11 @@ 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 {
Expand Down
12 changes: 12 additions & 0 deletions pkg/api/dtos/models.go
Expand Up @@ -79,3 +79,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
}
11 changes: 9 additions & 2 deletions pkg/api/folder_permission.go
Expand Up @@ -11,7 +11,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"))

Expand All @@ -30,7 +30,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

Expand All @@ -43,9 +48,11 @@ 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 {
Expand Down
47 changes: 31 additions & 16 deletions pkg/api/org_users.go
Expand Up @@ -51,8 +51,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)
}
Expand All @@ -61,7 +66,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)
Expand All @@ -71,7 +76,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)
}
Expand Down Expand Up @@ -112,31 +122,36 @@ 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)
}

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
Expand Down
11 changes: 9 additions & 2 deletions pkg/api/team.go
Expand Up @@ -110,6 +110,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 {
Expand All @@ -127,8 +129,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 err == models.ErrTeamNotFound {
Expand Down
9 changes: 8 additions & 1 deletion pkg/api/team_members.go
Expand Up @@ -16,17 +16,24 @@ 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{}

if hs.License.HasValidLicense() && member.External {
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
Expand Down
10 changes: 7 additions & 3 deletions pkg/models/team.go
Expand Up @@ -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 {
Expand All @@ -68,6 +70,8 @@ type SearchTeamsQuery struct {
Page int
OrgId int64
UserIdFilter int64
SignedInUser *SignedInUser
HiddenUsers map[string]struct{}

Result SearchTeamQueryResult
}
Expand Down