mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Access control: Using RBAC to filter users in list view that you have read access to (#47963)
* Add SQL filter for global user search * Remove scope requirements from endpoints Co-authored-by: Gabriel MABILLE <gamab@users.noreply.github.com> Co-authored-by: Ieva <ieva.vasiljeva@grafana.com> Co-authored-by: Karl Persson <kalle.persson@grafana.com>
This commit is contained in:
parent
a51c2774b8
commit
555867135b
@ -176,8 +176,8 @@ func (hs *HTTPServer) registerRoutes() {
|
|||||||
|
|
||||||
apiRoute.Group("/users", func(usersRoute routing.RouteRegister) {
|
apiRoute.Group("/users", func(usersRoute routing.RouteRegister) {
|
||||||
userIDScope := ac.Scope("global.users", "id", ac.Parameter(":id"))
|
userIDScope := ac.Scope("global.users", "id", ac.Parameter(":id"))
|
||||||
usersRoute.Get("/", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionUsersRead, ac.ScopeGlobalUsersAll)), routing.Wrap(hs.searchUsersService.SearchUsers))
|
usersRoute.Get("/", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionUsersRead)), routing.Wrap(hs.searchUsersService.SearchUsers))
|
||||||
usersRoute.Get("/search", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionUsersRead, ac.ScopeGlobalUsersAll)), routing.Wrap(hs.searchUsersService.SearchUsersWithPaging))
|
usersRoute.Get("/search", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionUsersRead)), routing.Wrap(hs.searchUsersService.SearchUsersWithPaging))
|
||||||
usersRoute.Get("/:id", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionUsersRead, userIDScope)), routing.Wrap(hs.GetUserByID))
|
usersRoute.Get("/:id", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionUsersRead, userIDScope)), routing.Wrap(hs.GetUserByID))
|
||||||
usersRoute.Get("/:id/teams", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionUsersTeamRead, userIDScope)), routing.Wrap(hs.GetUserTeams))
|
usersRoute.Get("/:id/teams", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionUsersTeamRead, userIDScope)), routing.Wrap(hs.GetUserTeams))
|
||||||
usersRoute.Get("/:id/orgs", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionUsersRead, userIDScope)), routing.Wrap(hs.GetUserOrgList))
|
usersRoute.Get("/:id/orgs", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionUsersRead, userIDScope)), routing.Wrap(hs.GetUserOrgList))
|
||||||
@ -277,7 +277,7 @@ func (hs *HTTPServer) registerRoutes() {
|
|||||||
orgsRoute.Put("/", authorizeInOrg(reqGrafanaAdmin, ac.UseOrgFromContextParams, ac.EvalPermission(ActionOrgsWrite)), routing.Wrap(hs.UpdateOrg))
|
orgsRoute.Put("/", authorizeInOrg(reqGrafanaAdmin, ac.UseOrgFromContextParams, ac.EvalPermission(ActionOrgsWrite)), routing.Wrap(hs.UpdateOrg))
|
||||||
orgsRoute.Put("/address", authorizeInOrg(reqGrafanaAdmin, ac.UseOrgFromContextParams, ac.EvalPermission(ActionOrgsWrite)), routing.Wrap(hs.UpdateOrgAddress))
|
orgsRoute.Put("/address", authorizeInOrg(reqGrafanaAdmin, ac.UseOrgFromContextParams, ac.EvalPermission(ActionOrgsWrite)), routing.Wrap(hs.UpdateOrgAddress))
|
||||||
orgsRoute.Delete("/", authorizeInOrg(reqGrafanaAdmin, ac.UseOrgFromContextParams, ac.EvalPermission(ActionOrgsDelete)), routing.Wrap(hs.DeleteOrgByID))
|
orgsRoute.Delete("/", authorizeInOrg(reqGrafanaAdmin, ac.UseOrgFromContextParams, ac.EvalPermission(ActionOrgsDelete)), routing.Wrap(hs.DeleteOrgByID))
|
||||||
orgsRoute.Get("/users", authorizeInOrg(reqGrafanaAdmin, ac.UseOrgFromContextParams, ac.EvalPermission(ac.ActionOrgUsersRead, ac.ScopeUsersAll)), routing.Wrap(hs.GetOrgUsers))
|
orgsRoute.Get("/users", authorizeInOrg(reqGrafanaAdmin, ac.UseOrgFromContextParams, ac.EvalPermission(ac.ActionOrgUsersRead)), routing.Wrap(hs.GetOrgUsers))
|
||||||
orgsRoute.Post("/users", authorizeInOrg(reqGrafanaAdmin, ac.UseOrgFromContextParams, ac.EvalPermission(ac.ActionOrgUsersAdd, ac.ScopeUsersAll)), routing.Wrap(hs.AddOrgUser))
|
orgsRoute.Post("/users", authorizeInOrg(reqGrafanaAdmin, ac.UseOrgFromContextParams, ac.EvalPermission(ac.ActionOrgUsersAdd, ac.ScopeUsersAll)), routing.Wrap(hs.AddOrgUser))
|
||||||
orgsRoute.Patch("/users/:userId", authorizeInOrg(reqGrafanaAdmin, ac.UseOrgFromContextParams, ac.EvalPermission(ac.ActionOrgUsersRoleUpdate, userIDScope)), routing.Wrap(hs.UpdateOrgUser))
|
orgsRoute.Patch("/users/:userId", authorizeInOrg(reqGrafanaAdmin, ac.UseOrgFromContextParams, ac.EvalPermission(ac.ActionOrgUsersRoleUpdate, userIDScope)), routing.Wrap(hs.UpdateOrgUser))
|
||||||
orgsRoute.Delete("/users/:userId", authorizeInOrg(reqGrafanaAdmin, ac.UseOrgFromContextParams, ac.EvalPermission(ac.ActionOrgUsersRemove, userIDScope)), routing.Wrap(hs.RemoveOrgUser))
|
orgsRoute.Delete("/users/:userId", authorizeInOrg(reqGrafanaAdmin, ac.UseOrgFromContextParams, ac.EvalPermission(ac.ActionOrgUsersRemove, userIDScope)), routing.Wrap(hs.RemoveOrgUser))
|
||||||
|
@ -141,12 +141,13 @@ type GetUserProfileQuery struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type SearchUsersQuery struct {
|
type SearchUsersQuery struct {
|
||||||
OrgId int64
|
SignedInUser *SignedInUser
|
||||||
Query string
|
OrgId int64
|
||||||
Page int
|
Query string
|
||||||
Limit int
|
Page int
|
||||||
AuthModule string
|
Limit int
|
||||||
Filters []Filter
|
AuthModule string
|
||||||
|
Filters []Filter
|
||||||
|
|
||||||
IsDisabled *bool
|
IsDisabled *bool
|
||||||
|
|
||||||
|
@ -146,7 +146,13 @@ func (s *Service) Get(ctx context.Context, orgID int64, signedInUser *models.Sig
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: probably replace with comment and user table join.
|
// NOTE: probably replace with comment and user table join.
|
||||||
query := &models.SearchUsersQuery{Query: "", Filters: []models.Filter{NewIDFilter(userIds)}, Page: 0, Limit: len(userIds)}
|
query := &models.SearchUsersQuery{
|
||||||
|
Query: "",
|
||||||
|
Page: 0,
|
||||||
|
Limit: len(userIds),
|
||||||
|
SignedInUser: signedInUser,
|
||||||
|
Filters: []models.Filter{NewIDFilter(userIds)},
|
||||||
|
}
|
||||||
if err := s.sqlStore.SearchUsers(ctx, query); err != nil {
|
if err := s.sqlStore.SearchUsers(ctx, query); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -61,7 +61,14 @@ func (s *OSSService) SearchUser(c *models.ReqContext) (*models.SearchUsersQuery,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
query := &models.SearchUsersQuery{Query: searchQuery, Filters: filters, Page: page, Limit: perPage}
|
query := &models.SearchUsersQuery{
|
||||||
|
// added SignedInUser to the query, as to only list the users that the user has permission to read
|
||||||
|
SignedInUser: c.SignedInUser,
|
||||||
|
Query: searchQuery,
|
||||||
|
Filters: filters,
|
||||||
|
Page: page,
|
||||||
|
Limit: perPage,
|
||||||
|
}
|
||||||
if err := s.sqlStore.SearchUsers(c.Req.Context(), query); err != nil {
|
if err := s.sqlStore.SearchUsers(c.Req.Context(), query); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -620,6 +620,16 @@ func (ss *SQLStore) SearchUsers(ctx context.Context, query *models.SearchUsersQu
|
|||||||
whereParams = append(whereParams, query.OrgId)
|
whereParams = append(whereParams, query.OrgId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// user only sees the users for which it has read permissions
|
||||||
|
if !ac.IsDisabled(ss.Cfg) {
|
||||||
|
acFilter, err := ac.Filter(query.SignedInUser, "u.id", "global.users:id:", ac.ActionUsersRead)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
whereConditions = append(whereConditions, acFilter.Where)
|
||||||
|
whereParams = append(whereParams, acFilter.Args...)
|
||||||
|
}
|
||||||
|
|
||||||
if query.Query != "" {
|
if query.Query != "" {
|
||||||
whereConditions = append(whereConditions, "(email "+dialect.LikeStr()+" ? OR name "+dialect.LikeStr()+" ? OR login "+dialect.LikeStr()+" ?)")
|
whereConditions = append(whereConditions, "(email "+dialect.LikeStr()+" ? OR name "+dialect.LikeStr()+" ? OR login "+dialect.LikeStr()+" ?)")
|
||||||
whereParams = append(whereParams, queryWithWildcards, queryWithWildcards, queryWithWildcards)
|
whereParams = append(whereParams, queryWithWildcards, queryWithWildcards, queryWithWildcards)
|
||||||
|
@ -9,7 +9,9 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/models"
|
"github.com/grafana/grafana/pkg/models"
|
||||||
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -336,6 +338,26 @@ func TestUserDataAccess(t *testing.T) {
|
|||||||
require.Len(t, permQuery.Result, 0)
|
require.Len(t, permQuery.Result, 0)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("Testing DB - return list of users that the SignedInUser has permission to read", func(t *testing.T) {
|
||||||
|
ss := InitTestDB(t, InitTestDBOpt{FeatureFlags: []string{featuremgmt.FlagAccesscontrol}})
|
||||||
|
createFiveTestUsers(t, ss, func(i int) *models.CreateUserCommand {
|
||||||
|
return &models.CreateUserCommand{
|
||||||
|
Email: fmt.Sprint("user", i, "@test.com"),
|
||||||
|
Name: fmt.Sprint("user", i),
|
||||||
|
Login: fmt.Sprint("loginuser", i),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
testUser := &models.SignedInUser{
|
||||||
|
OrgId: 1,
|
||||||
|
Permissions: map[int64]map[string][]string{1: {"users:read": {"global.users:id:1", "global.users:id:3"}}},
|
||||||
|
}
|
||||||
|
query := models.SearchUsersQuery{SignedInUser: testUser}
|
||||||
|
err := ss.SearchUsers(context.Background(), &query)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Len(t, query.Result.Users, 2)
|
||||||
|
})
|
||||||
|
|
||||||
ss = InitTestDB(t)
|
ss = InitTestDB(t)
|
||||||
|
|
||||||
t.Run("Testing DB - enable all users", func(t *testing.T) {
|
t.Run("Testing DB - enable all users", func(t *testing.T) {
|
||||||
|
Loading…
Reference in New Issue
Block a user