mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Access control: Add access control sql filter to org user queries (#43961)
* Add access control SQL filter to org user queries
This commit is contained in:
parent
ccd9e46dda
commit
f999910dc6
@ -54,7 +54,7 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
r.Get("/datasources/", authorize(reqOrgAdmin, dataSourcesConfigurationAccessEvaluator), hs.Index)
|
||||
r.Get("/datasources/new", authorize(reqOrgAdmin, dataSourcesNewAccessEvaluator), hs.Index)
|
||||
r.Get("/datasources/edit/*", authorize(reqOrgAdmin, dataSourcesEditAccessEvaluator), hs.Index)
|
||||
r.Get("/org/users", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionOrgUsersRead, ac.ScopeUsersAll)), hs.Index)
|
||||
r.Get("/org/users", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionOrgUsersRead)), hs.Index)
|
||||
r.Get("/org/users/new", reqOrgAdmin, hs.Index)
|
||||
r.Get("/org/users/invite", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionUsersCreate)), hs.Index)
|
||||
r.Get("/org/teams", reqCanAccessTeams, hs.Index)
|
||||
@ -206,8 +206,8 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
userIDScope := ac.Scope("users", "id", ac.Parameter(":userId"))
|
||||
orgRoute.Put("/", authorize(reqOrgAdmin, ac.EvalPermission(ActionOrgsWrite)), routing.Wrap(UpdateCurrentOrg))
|
||||
orgRoute.Put("/address", authorize(reqOrgAdmin, ac.EvalPermission(ActionOrgsWrite)), routing.Wrap(UpdateCurrentOrgAddress))
|
||||
orgRoute.Get("/users", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionOrgUsersRead, ac.ScopeUsersAll)), routing.Wrap(hs.GetOrgUsersForCurrentOrg))
|
||||
orgRoute.Get("/users/search", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionOrgUsersRead, ac.ScopeUsersAll)), routing.Wrap(hs.SearchOrgUsersWithPaging))
|
||||
orgRoute.Get("/users", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionOrgUsersRead)), routing.Wrap(hs.GetOrgUsersForCurrentOrg))
|
||||
orgRoute.Get("/users/search", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionOrgUsersRead)), routing.Wrap(hs.SearchOrgUsersWithPaging))
|
||||
orgRoute.Post("/users", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionOrgUsersAdd, ac.ScopeUsersAll)), quota("user"), routing.Wrap(hs.AddOrgUserToCurrentOrg))
|
||||
orgRoute.Patch("/users/:userId", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionOrgUsersRoleUpdate, userIDScope)), routing.Wrap(hs.UpdateOrgUserForCurrentOrg))
|
||||
orgRoute.Delete("/users/:userId", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionOrgUsersRemove, userIDScope)), routing.Wrap(hs.RemoveOrgUserForCurrentOrg))
|
||||
@ -224,7 +224,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", authorize(reqOrgAdminFolderAdminOrTeamAdmin, ac.EvalPermission(ac.ActionOrgUsersRead, ac.ScopeUsersAll)), routing.Wrap(hs.GetOrgUsersForCurrentOrgLookup))
|
||||
orgRoute.Get("/users/lookup", authorize(reqOrgAdminFolderAdminOrTeamAdmin, ac.EvalPermission(ac.ActionOrgUsersRead)), routing.Wrap(hs.GetOrgUsersForCurrentOrgLookup))
|
||||
})
|
||||
|
||||
// create new org
|
||||
|
@ -22,6 +22,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/infra/usagestats"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
acmiddleware "github.com/grafana/grafana/pkg/services/accesscontrol/middleware"
|
||||
accesscontrolmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/ossaccesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/auth"
|
||||
@ -355,6 +356,8 @@ func setupHTTPServerWithCfg(t *testing.T, useFakeAccessControl, enableAccessCont
|
||||
c.Map(initCtx)
|
||||
})
|
||||
|
||||
m.Use(acmiddleware.LoadPermissionsMiddleware(hs.AccessControl))
|
||||
|
||||
// Register all routes
|
||||
hs.registerRoutes()
|
||||
hs.RouteRegister.Register(m.Router)
|
||||
|
@ -251,7 +251,7 @@ func (hs *HTTPServer) getNavTree(c *models.ReqContext, hasEditPerm bool) ([]*dto
|
||||
})
|
||||
}
|
||||
|
||||
if hasAccess(ac.ReqOrgAdmin, ac.EvalPermission(ac.ActionOrgUsersRead, ac.ScopeUsersAll)) {
|
||||
if hasAccess(ac.ReqOrgAdmin, ac.EvalPermission(ac.ActionOrgUsersRead)) {
|
||||
configNodes = append(configNodes, &dtos.NavLink{
|
||||
Text: "Users",
|
||||
Id: "users",
|
||||
|
@ -71,6 +71,7 @@ func (hs *HTTPServer) GetOrgUsersForCurrentOrg(c *models.ReqContext) response.Re
|
||||
OrgId: c.OrgId,
|
||||
Query: c.Query("query"),
|
||||
Limit: c.QueryInt("limit"),
|
||||
User: c.SignedInUser,
|
||||
}, c.SignedInUser)
|
||||
|
||||
if err != nil {
|
||||
@ -86,6 +87,7 @@ func (hs *HTTPServer) GetOrgUsersForCurrentOrgLookup(c *models.ReqContext) respo
|
||||
OrgId: c.OrgId,
|
||||
Query: c.Query("query"),
|
||||
Limit: c.QueryInt("limit"),
|
||||
User: c.SignedInUser,
|
||||
}, c.SignedInUser)
|
||||
|
||||
if err != nil {
|
||||
@ -124,6 +126,7 @@ func (hs *HTTPServer) GetOrgUsers(c *models.ReqContext) response.Response {
|
||||
OrgId: c.ParamsInt64(":orgId"),
|
||||
Query: "",
|
||||
Limit: 0,
|
||||
User: c.SignedInUser,
|
||||
}, c.SignedInUser)
|
||||
|
||||
if err != nil {
|
||||
@ -183,8 +186,9 @@ func (hs *HTTPServer) SearchOrgUsersWithPaging(c *models.ReqContext) response.Re
|
||||
query := &models.SearchOrgUsersQuery{
|
||||
OrgID: c.OrgId,
|
||||
Query: c.Query("query"),
|
||||
Limit: perPage,
|
||||
Page: page,
|
||||
Limit: perPage,
|
||||
User: c.SignedInUser,
|
||||
}
|
||||
|
||||
if err := hs.SQLStore.SearchOrgUsers(ctx, query); err != nil {
|
||||
|
@ -38,6 +38,7 @@ func TestOrgUsersAPIEndpoint_userLoggedIn(t *testing.T) {
|
||||
hs := &HTTPServer{Cfg: settings}
|
||||
|
||||
sqlStore := sqlstore.InitTestDB(t)
|
||||
sqlStore.Cfg = settings
|
||||
hs.SQLStore = sqlStore
|
||||
|
||||
loggedInUserScenario(t, "When calling GET on", "api/org/users", func(sc *scenarioContext) {
|
||||
@ -556,7 +557,10 @@ func TestPostOrgUsersAPIEndpoint_AccessControl(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
assert.EqualValuesf(t, tc.expectedMessage, message, "server did not answer expected message")
|
||||
|
||||
getUsersQuery := models.GetOrgUsersQuery{OrgId: tc.targetOrg}
|
||||
getUsersQuery := models.GetOrgUsersQuery{OrgId: tc.targetOrg, User: &models.SignedInUser{
|
||||
OrgId: tc.targetOrg,
|
||||
Permissions: map[int64]map[string][]string{tc.targetOrg: {"org.users:read": {"users:*"}}},
|
||||
}}
|
||||
err = sc.db.GetOrgUsers(context.Background(), &getUsersQuery)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, getUsersQuery.Result, tc.expectedUserCount)
|
||||
@ -799,7 +803,13 @@ func TestDeleteOrgUsersAPIEndpoint_AccessControl(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tc.expectedMessage, message)
|
||||
|
||||
getUsersQuery := models.GetOrgUsersQuery{OrgId: tc.targetOrg}
|
||||
getUsersQuery := models.GetOrgUsersQuery{
|
||||
OrgId: tc.targetOrg,
|
||||
User: &models.SignedInUser{
|
||||
OrgId: tc.targetOrg,
|
||||
Permissions: map[int64]map[string][]string{tc.targetOrg: {accesscontrol.ActionOrgUsersRead: {accesscontrol.ScopeUsersAll}}},
|
||||
},
|
||||
}
|
||||
err = sc.db.GetOrgUsers(context.Background(), &getUsersQuery)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, getUsersQuery.Result, tc.expectedUserCount)
|
||||
|
@ -112,6 +112,7 @@ type GetOrgUsersQuery struct {
|
||||
Limit int
|
||||
IsServiceAccount bool
|
||||
|
||||
User *SignedInUser
|
||||
Result []*OrgUserDTO
|
||||
}
|
||||
|
||||
@ -122,6 +123,7 @@ type SearchOrgUsersQuery struct {
|
||||
Limit int
|
||||
IsServiceAccount bool
|
||||
|
||||
User *SignedInUser
|
||||
Result SearchOrgUsersQueryResult
|
||||
}
|
||||
|
||||
|
@ -10,7 +10,9 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
|
||||
)
|
||||
|
||||
var sqlIDAcceptList = map[string]struct{}{}
|
||||
var sqlIDAcceptList = map[string]struct{}{
|
||||
"org_user.user_id": {},
|
||||
}
|
||||
|
||||
type SQLDialect interface {
|
||||
DriverName() string
|
||||
@ -97,3 +99,12 @@ func postgresQuery(scopes []string, sqlID, prefix string) (string, []interface{}
|
||||
)
|
||||
`, sqlID, sqlID), args
|
||||
}
|
||||
|
||||
// SetAcceptListForTest allow us to mutate the list for blackbox testing
|
||||
func SetAcceptListForTest(list map[string]struct{}) func() {
|
||||
original := sqlIDAcceptList
|
||||
sqlIDAcceptList = list
|
||||
return func() {
|
||||
sqlIDAcceptList = original
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
package accesscontrol
|
||||
package accesscontrol_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
@ -9,6 +9,7 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
)
|
||||
|
||||
@ -21,19 +22,20 @@ func benchmarkFilter(b *testing.B, numDs, numPermissions int) {
|
||||
b.ResetTimer()
|
||||
|
||||
// set sqlIDAcceptList before running tests
|
||||
sqlIDAcceptList = map[string]struct{}{
|
||||
restore := accesscontrol.SetAcceptListForTest(map[string]struct{}{
|
||||
"data_source.id": {},
|
||||
}
|
||||
})
|
||||
defer restore()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
baseSql := `SELECT data_source.* FROM data_source WHERE`
|
||||
query, args, err := Filter(
|
||||
query, args, err := accesscontrol.Filter(
|
||||
context.Background(),
|
||||
&FakeDriver{name: "sqlite3"},
|
||||
"data_source.id",
|
||||
"datasources",
|
||||
"datasources:read",
|
||||
&models.SignedInUser{OrgId: 1, Permissions: map[int64]map[string][]string{1: GroupScopesByAction(permissions)}},
|
||||
&models.SignedInUser{OrgId: 1, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByAction(permissions)}},
|
||||
)
|
||||
require.NoError(b, err)
|
||||
|
||||
@ -46,7 +48,7 @@ func benchmarkFilter(b *testing.B, numDs, numPermissions int) {
|
||||
}
|
||||
}
|
||||
|
||||
func setupFilterBenchmark(b *testing.B, numDs, numPermissions int) (*sqlstore.SQLStore, []*Permission) {
|
||||
func setupFilterBenchmark(b *testing.B, numDs, numPermissions int) (*sqlstore.SQLStore, []*accesscontrol.Permission) {
|
||||
b.Helper()
|
||||
store := sqlstore.InitTestDB(b)
|
||||
|
||||
@ -62,11 +64,11 @@ func setupFilterBenchmark(b *testing.B, numDs, numPermissions int) (*sqlstore.SQ
|
||||
numPermissions = numDs
|
||||
}
|
||||
|
||||
permissions := make([]*Permission, 0, numPermissions)
|
||||
permissions := make([]*accesscontrol.Permission, 0, numPermissions)
|
||||
for i := 1; i <= numPermissions; i++ {
|
||||
permissions = append(permissions, &Permission{
|
||||
permissions = append(permissions, &accesscontrol.Permission{
|
||||
Action: "datasources:read",
|
||||
Scope: Scope("datasources", "id", strconv.Itoa(i)),
|
||||
Scope: accesscontrol.Scope("datasources", "id", strconv.Itoa(i)),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
package accesscontrol
|
||||
package accesscontrol_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
@ -9,6 +9,7 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
)
|
||||
|
||||
@ -18,7 +19,7 @@ type filterTest struct {
|
||||
sqlID string
|
||||
action string
|
||||
prefix string
|
||||
permissions []*Permission
|
||||
permissions []*accesscontrol.Permission
|
||||
expectedQuery string
|
||||
expectedArgs []interface{}
|
||||
}
|
||||
@ -31,7 +32,7 @@ func TestFilter(t *testing.T) {
|
||||
sqlID: "data_source.id",
|
||||
prefix: "datasources",
|
||||
action: "datasources:query",
|
||||
permissions: []*Permission{
|
||||
permissions: []*accesscontrol.Permission{
|
||||
{Action: "datasources:query", Scope: "datasources:id:1"},
|
||||
{Action: "datasources:query", Scope: "datasources:id:2"},
|
||||
{Action: "datasources:query", Scope: "datasources:id:3"},
|
||||
@ -65,7 +66,7 @@ func TestFilter(t *testing.T) {
|
||||
sqlID: "dashboard.id",
|
||||
prefix: "dashboards",
|
||||
action: "dashboards:read",
|
||||
permissions: []*Permission{
|
||||
permissions: []*accesscontrol.Permission{
|
||||
{Action: "dashboards:read", Scope: "dashboards:id:1"},
|
||||
{Action: "dashboards:read", Scope: "dashboards:id:2"},
|
||||
{Action: "dashboards:read", Scope: "dashboards:id:5"},
|
||||
@ -95,7 +96,7 @@ func TestFilter(t *testing.T) {
|
||||
sqlID: "user.id",
|
||||
prefix: "users",
|
||||
action: "users:read",
|
||||
permissions: []*Permission{
|
||||
permissions: []*accesscontrol.Permission{
|
||||
{Action: "users:read", Scope: "users:id:1"},
|
||||
{Action: "users:read", Scope: "users:id:100"},
|
||||
// Other permissions
|
||||
@ -123,21 +124,22 @@ func TestFilter(t *testing.T) {
|
||||
}
|
||||
|
||||
// set sqlIDAcceptList before running tests
|
||||
sqlIDAcceptList = map[string]struct{}{
|
||||
restore := accesscontrol.SetAcceptListForTest(map[string]struct{}{
|
||||
"user.id": {},
|
||||
"dashboard.id": {},
|
||||
"data_source.id": {},
|
||||
}
|
||||
})
|
||||
defer restore()
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.desc, func(t *testing.T) {
|
||||
query, args, err := Filter(
|
||||
query, args, err := accesscontrol.Filter(
|
||||
context.Background(),
|
||||
FakeDriver{name: tt.driverName},
|
||||
tt.sqlID,
|
||||
tt.prefix,
|
||||
tt.action,
|
||||
&models.SignedInUser{OrgId: 1, Permissions: map[int64]map[string][]string{1: GroupScopesByAction(tt.permissions)}},
|
||||
&models.SignedInUser{OrgId: 1, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByAction(tt.permissions)}},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tt.expectedQuery, query)
|
||||
@ -153,7 +155,7 @@ func TestFilter(t *testing.T) {
|
||||
type filterDatasourcesTestCase struct {
|
||||
desc string
|
||||
sqlID string
|
||||
permissions []*Permission
|
||||
permissions []*accesscontrol.Permission
|
||||
expectedDataSources []string
|
||||
expectErr bool
|
||||
}
|
||||
@ -163,7 +165,7 @@ func TestFilter_Datasources(t *testing.T) {
|
||||
{
|
||||
desc: "expect all data sources to be returned",
|
||||
sqlID: "data_source.id",
|
||||
permissions: []*Permission{
|
||||
permissions: []*accesscontrol.Permission{
|
||||
{Action: "datasources:read", Scope: "datasources:*"},
|
||||
},
|
||||
expectedDataSources: []string{"ds:1", "ds:2", "ds:3", "ds:4", "ds:5", "ds:6", "ds:7", "ds:8", "ds:9", "ds:10"},
|
||||
@ -171,13 +173,13 @@ func TestFilter_Datasources(t *testing.T) {
|
||||
{
|
||||
desc: "expect no data sources to be returned",
|
||||
sqlID: "data_source.id",
|
||||
permissions: []*Permission{},
|
||||
permissions: []*accesscontrol.Permission{},
|
||||
expectedDataSources: []string{},
|
||||
},
|
||||
{
|
||||
desc: "expect data sources with id 3, 7 and 8 to be returned",
|
||||
sqlID: "data_source.id",
|
||||
permissions: []*Permission{
|
||||
permissions: []*accesscontrol.Permission{
|
||||
{Action: "datasources:read", Scope: "datasources:id:3"},
|
||||
{Action: "datasources:read", Scope: "datasources:id:7"},
|
||||
{Action: "datasources:read", Scope: "datasources:id:8"},
|
||||
@ -187,7 +189,7 @@ func TestFilter_Datasources(t *testing.T) {
|
||||
{
|
||||
desc: "expect error if sqlID is not in the accept list",
|
||||
sqlID: "other.id",
|
||||
permissions: []*Permission{
|
||||
permissions: []*accesscontrol.Permission{
|
||||
{Action: "datasources:read", Scope: "datasources:id:3"},
|
||||
{Action: "datasources:read", Scope: "datasources:id:7"},
|
||||
{Action: "datasources:read", Scope: "datasources:id:8"},
|
||||
@ -198,9 +200,10 @@ func TestFilter_Datasources(t *testing.T) {
|
||||
}
|
||||
|
||||
// set sqlIDAcceptList before running tests
|
||||
sqlIDAcceptList = map[string]struct{}{
|
||||
restore := accesscontrol.SetAcceptListForTest(map[string]struct{}{
|
||||
"data_source.id": {},
|
||||
}
|
||||
})
|
||||
defer restore()
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.desc, func(t *testing.T) {
|
||||
@ -216,13 +219,13 @@ func TestFilter_Datasources(t *testing.T) {
|
||||
}
|
||||
|
||||
baseSql := `SELECT data_source.* FROM data_source WHERE`
|
||||
query, args, err := Filter(
|
||||
query, args, err := accesscontrol.Filter(
|
||||
context.Background(),
|
||||
&FakeDriver{name: "sqlite3"},
|
||||
tt.sqlID,
|
||||
"datasources",
|
||||
"datasources:read",
|
||||
&models.SignedInUser{OrgId: 1, Permissions: map[int64]map[string][]string{1: GroupScopesByAction(tt.permissions)}},
|
||||
&models.SignedInUser{OrgId: 1, Permissions: map[int64]map[string][]string{1: accesscontrol.GroupScopesByAction(tt.permissions)}},
|
||||
)
|
||||
|
||||
if !tt.expectErr {
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
@ -111,6 +112,15 @@ func (ss *SQLStore) GetOrgUsers(ctx context.Context, query *models.GetOrgUsersQu
|
||||
// service accounts table in the modelling
|
||||
whereConditions = append(whereConditions, fmt.Sprintf("%s.is_service_account = %t", x.Dialect().Quote("user"), query.IsServiceAccount))
|
||||
|
||||
if ss.Cfg.FeatureToggles["accesscontrol"] {
|
||||
q, args, err := accesscontrol.Filter(ctx, ss.Dialect, "org_user.user_id", "users", "org.users:read", query.User)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
whereConditions = append(whereConditions, q)
|
||||
whereParams = append(whereParams, args...)
|
||||
}
|
||||
|
||||
if query.Query != "" {
|
||||
queryWithWildcards := "%" + query.Query + "%"
|
||||
whereConditions = append(whereConditions, "(email "+dialect.LikeStr()+" ? OR name "+dialect.LikeStr()+" ? OR login "+dialect.LikeStr()+" ?)")
|
||||
@ -165,6 +175,15 @@ func (ss *SQLStore) SearchOrgUsers(ctx context.Context, query *models.SearchOrgU
|
||||
// service accounts table in the modelling
|
||||
whereConditions = append(whereConditions, fmt.Sprintf("%s.is_service_account = %t", x.Dialect().Quote("user"), query.IsServiceAccount))
|
||||
|
||||
if ss.Cfg.FeatureToggles["accesscontrol"] {
|
||||
q, args, err := accesscontrol.Filter(ctx, ss.Dialect, "org_user.user_id", "users", "org.users:read", query.User)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
whereConditions = append(whereConditions, q)
|
||||
whereParams = append(whereParams, args...)
|
||||
}
|
||||
|
||||
if query.Query != "" {
|
||||
queryWithWildcards := "%" + query.Query + "%"
|
||||
whereConditions = append(whereConditions, "(email "+dialect.LikeStr()+" ? OR name "+dialect.LikeStr()+" ? OR login "+dialect.LikeStr()+" ?)")
|
||||
|
176
pkg/services/sqlstore/org_users_test.go
Normal file
176
pkg/services/sqlstore/org_users_test.go
Normal file
@ -0,0 +1,176 @@
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
)
|
||||
|
||||
type getOrgUsersTestCase struct {
|
||||
desc string
|
||||
query *models.GetOrgUsersQuery
|
||||
expectedNumUsers int
|
||||
}
|
||||
|
||||
func TestSQLStore_GetOrgUsers(t *testing.T) {
|
||||
tests := []getOrgUsersTestCase{
|
||||
{
|
||||
desc: "should return all users",
|
||||
query: &models.GetOrgUsersQuery{
|
||||
OrgId: 1,
|
||||
User: &models.SignedInUser{
|
||||
OrgId: 1,
|
||||
Permissions: map[int64]map[string][]string{1: {ac.ActionOrgUsersRead: {ac.ScopeUsersAll}}},
|
||||
},
|
||||
},
|
||||
expectedNumUsers: 10,
|
||||
},
|
||||
{
|
||||
desc: "should return no users",
|
||||
query: &models.GetOrgUsersQuery{
|
||||
OrgId: 1,
|
||||
User: &models.SignedInUser{
|
||||
OrgId: 1,
|
||||
Permissions: map[int64]map[string][]string{1: {ac.ActionOrgUsersRead: {""}}},
|
||||
},
|
||||
},
|
||||
expectedNumUsers: 0,
|
||||
},
|
||||
{
|
||||
desc: "should return some users",
|
||||
query: &models.GetOrgUsersQuery{
|
||||
OrgId: 1,
|
||||
User: &models.SignedInUser{
|
||||
OrgId: 1,
|
||||
Permissions: map[int64]map[string][]string{1: {ac.ActionOrgUsersRead: {
|
||||
"users:id:1",
|
||||
"users:id:5",
|
||||
"users:id:9",
|
||||
}}},
|
||||
},
|
||||
},
|
||||
expectedNumUsers: 3,
|
||||
},
|
||||
}
|
||||
|
||||
store := InitTestDB(t)
|
||||
store.Cfg.FeatureToggles = map[string]bool{"accesscontrol": true}
|
||||
seedOrgUsers(t, store, 10)
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.desc, func(t *testing.T) {
|
||||
err := store.GetOrgUsers(context.Background(), tt.query)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, tt.query.Result, tt.expectedNumUsers)
|
||||
|
||||
if !hasWildcardScope(tt.query.User, ac.ActionOrgUsersRead) {
|
||||
for _, u := range tt.query.Result {
|
||||
assert.Contains(t, tt.query.User.Permissions[tt.query.User.OrgId][ac.ActionOrgUsersRead], fmt.Sprintf("users:id:%d", u.UserId))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type searchOrgUsersTestCase struct {
|
||||
desc string
|
||||
query *models.SearchOrgUsersQuery
|
||||
expectedNumUsers int
|
||||
}
|
||||
|
||||
func TestSQLStore_SearchOrgUsers(t *testing.T) {
|
||||
tests := []searchOrgUsersTestCase{
|
||||
{
|
||||
desc: "should return all users",
|
||||
query: &models.SearchOrgUsersQuery{
|
||||
OrgID: 1,
|
||||
User: &models.SignedInUser{
|
||||
OrgId: 1,
|
||||
Permissions: map[int64]map[string][]string{1: {ac.ActionOrgUsersRead: {ac.ScopeUsersAll}}},
|
||||
},
|
||||
},
|
||||
expectedNumUsers: 10,
|
||||
},
|
||||
{
|
||||
desc: "should return no users",
|
||||
query: &models.SearchOrgUsersQuery{
|
||||
OrgID: 1,
|
||||
User: &models.SignedInUser{
|
||||
OrgId: 1,
|
||||
Permissions: map[int64]map[string][]string{1: {ac.ActionOrgUsersRead: {""}}},
|
||||
},
|
||||
},
|
||||
expectedNumUsers: 0,
|
||||
},
|
||||
{
|
||||
desc: "should return some users",
|
||||
query: &models.SearchOrgUsersQuery{
|
||||
OrgID: 1,
|
||||
User: &models.SignedInUser{
|
||||
OrgId: 1,
|
||||
Permissions: map[int64]map[string][]string{1: {ac.ActionOrgUsersRead: {
|
||||
"users:id:1",
|
||||
"users:id:5",
|
||||
"users:id:9",
|
||||
}}},
|
||||
},
|
||||
},
|
||||
expectedNumUsers: 3,
|
||||
},
|
||||
}
|
||||
|
||||
store := InitTestDB(t)
|
||||
store.Cfg.FeatureToggles = map[string]bool{"accesscontrol": true}
|
||||
seedOrgUsers(t, store, 10)
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.desc, func(t *testing.T) {
|
||||
err := store.SearchOrgUsers(context.Background(), tt.query)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, tt.query.Result.OrgUsers, tt.expectedNumUsers)
|
||||
|
||||
if !hasWildcardScope(tt.query.User, ac.ActionOrgUsersRead) {
|
||||
for _, u := range tt.query.Result.OrgUsers {
|
||||
assert.Contains(t, tt.query.User.Permissions[tt.query.User.OrgId][ac.ActionOrgUsersRead], fmt.Sprintf("users:id:%d", u.UserId))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func seedOrgUsers(t *testing.T, store *SQLStore, numUsers int) {
|
||||
t.Helper()
|
||||
// Seed users
|
||||
for i := 1; i <= numUsers; i++ {
|
||||
user, err := store.CreateUser(context.Background(), models.CreateUserCommand{
|
||||
Login: fmt.Sprintf("user-%d", i),
|
||||
OrgId: 1,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
if i != 1 {
|
||||
err = store.AddOrgUser(context.Background(), &models.AddOrgUserCommand{
|
||||
Role: "Viewer",
|
||||
OrgId: 1,
|
||||
UserId: user.Id,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func hasWildcardScope(user *models.SignedInUser, action string) bool {
|
||||
for _, scope := range user.Permissions[user.OrgId][action] {
|
||||
if strings.HasSuffix(scope, ":*") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
Loading…
Reference in New Issue
Block a user