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:
Karl Persson
2022-01-13 14:40:32 +01:00
committed by GitHub
parent ccd9e46dda
commit f999910dc6
11 changed files with 266 additions and 36 deletions
+19
View File
@@ -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
View 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
}