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:
Eric Leijonmarck 2022-05-13 09:26:34 +02:00 committed by GitHub
parent a51c2774b8
commit 555867135b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 57 additions and 11 deletions

View File

@ -176,8 +176,8 @@ func (hs *HTTPServer) registerRoutes() {
apiRoute.Group("/users", func(usersRoute routing.RouteRegister) {
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("/search", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionUsersRead, ac.ScopeGlobalUsersAll)), routing.Wrap(hs.searchUsersService.SearchUsersWithPaging))
usersRoute.Get("/", authorize(reqGrafanaAdmin, ac.EvalPermission(ac.ActionUsersRead)), routing.Wrap(hs.searchUsersService.SearchUsers))
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/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))
@ -277,7 +277,7 @@ func (hs *HTTPServer) registerRoutes() {
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.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.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))

View File

@ -141,12 +141,13 @@ type GetUserProfileQuery struct {
}
type SearchUsersQuery struct {
OrgId int64
Query string
Page int
Limit int
AuthModule string
Filters []Filter
SignedInUser *SignedInUser
OrgId int64
Query string
Page int
Limit int
AuthModule string
Filters []Filter
IsDisabled *bool

View File

@ -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.
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 {
return nil, err
}

View File

@ -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 {
return nil, err
}

View File

@ -620,6 +620,16 @@ func (ss *SQLStore) SearchUsers(ctx context.Context, query *models.SearchUsersQu
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 != "" {
whereConditions = append(whereConditions, "(email "+dialect.LikeStr()+" ? OR name "+dialect.LikeStr()+" ? OR login "+dialect.LikeStr()+" ?)")
whereParams = append(whereParams, queryWithWildcards, queryWithWildcards, queryWithWildcards)

View File

@ -9,7 +9,9 @@ import (
"testing"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/setting"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -336,6 +338,26 @@ func TestUserDataAccess(t *testing.T) {
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)
t.Run("Testing DB - enable all users", func(t *testing.T) {