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:
@@ -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()+" ?)")
|
||||
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user