Chore: Remove methods from sqlstore interface (#55802)

* Remove SearchOrgUsers from sqlstore interface

* Remove RemoveOrgUser method from sqlstore interface

* Delete RemoveOrgUser from sqlstore

* Fix lint
This commit is contained in:
idafurjes
2022-09-27 15:33:38 +02:00
committed by GitHub
parent 332e8477c7
commit 56cfe02ca2
10 changed files with 501 additions and 493 deletions

View File

@@ -24,11 +24,6 @@ func TestIntegrationAccountDataAccess(t *testing.T) {
}
t.Run("Testing Account DB Access", func(t *testing.T) {
sqlStore := InitTestDB(t)
testUser := &user.SignedInUser{
Permissions: map[int64]map[string][]string{
1: {accesscontrol.ActionOrgUsersRead: []string{accesscontrol.ScopeUsersAll}},
},
}
t.Run("Given we have organizations, we can query them by IDs", func(t *testing.T) {
var err error
@@ -109,47 +104,6 @@ func TestIntegrationAccountDataAccess(t *testing.T) {
})
})
t.Run("Given single org and 2 users inserted", func(t *testing.T) {
sqlStore = InitTestDB(t)
sqlStore.Cfg.AutoAssignOrg = true
sqlStore.Cfg.AutoAssignOrgId = 1
sqlStore.Cfg.AutoAssignOrgRole = "Viewer"
ac1cmd := user.CreateUserCommand{Login: "ac1", Email: "ac1@test.com", Name: "ac1 name"}
ac2cmd := user.CreateUserCommand{Login: "ac2", Email: "ac2@test.com", Name: "ac2 name"}
ac1, err := sqlStore.CreateUser(context.Background(), ac1cmd)
testUser.OrgID = ac1.OrgID
require.NoError(t, err)
_, err = sqlStore.CreateUser(context.Background(), ac2cmd)
require.NoError(t, err)
t.Run("Can get organization users paginated with query", func(t *testing.T) {
query := models.SearchOrgUsersQuery{
OrgID: ac1.OrgID,
Page: 1,
User: testUser,
}
err = sqlStore.SearchOrgUsers(context.Background(), &query)
require.NoError(t, err)
require.Equal(t, len(query.Result.OrgUsers), 2)
})
t.Run("Can get organization users paginated and limited", func(t *testing.T) {
query := models.SearchOrgUsersQuery{
OrgID: ac1.OrgID,
Limit: 1,
Page: 1,
User: testUser,
}
err = sqlStore.SearchOrgUsers(context.Background(), &query)
require.NoError(t, err)
require.Equal(t, len(query.Result.OrgUsers), 1)
})
})
t.Run("Given two saved users", func(t *testing.T) {
sqlStore = InitTestDB(t)
sqlStore.Cfg.AutoAssignOrg = false
@@ -241,38 +195,18 @@ func TestIntegrationAccountDataAccess(t *testing.T) {
require.Equal(t, query.Result.OrgName, "ac1@test.com")
})
t.Run("Should set last org as current when removing user from current", func(t *testing.T) {
remCmd := models.RemoveOrgUserCommand{OrgId: ac1.OrgID, UserId: ac2.ID}
err := sqlStore.RemoveOrgUser(context.Background(), &remCmd)
require.NoError(t, err)
// TODO: This test should be moved to user store
// t.Run("Should set last org as current when removing user from current", func(t *testing.T) {
// remCmd := models.RemoveOrgUserCommand{OrgId: ac1.OrgID, UserId: ac2.ID}
// err := sqlStore.RemoveOrgUser(context.Background(), &remCmd)
// require.NoError(t, err)
query := models.GetSignedInUserQuery{UserId: ac2.ID}
err = sqlStore.GetSignedInUser(context.Background(), &query)
// query := models.GetSignedInUserQuery{UserId: ac2.ID}
// err = sqlStore.GetSignedInUser(context.Background(), &query)
require.NoError(t, err)
require.Equal(t, query.Result.OrgID, ac2.OrgID)
})
})
t.Run("Removing user from org should delete user completely if in no other org", func(t *testing.T) {
// make sure ac2 has no org
err := sqlStore.DeleteOrg(context.Background(), &models.DeleteOrgCommand{Id: ac2.OrgID})
require.NoError(t, err)
// remove ac2 user from ac1 org
remCmd := models.RemoveOrgUserCommand{OrgId: ac1.OrgID, UserId: ac2.ID, ShouldDeleteOrphanedUser: true}
err = sqlStore.RemoveOrgUser(context.Background(), &remCmd)
require.NoError(t, err)
require.True(t, remCmd.UserWasDeleted)
err = sqlStore.GetSignedInUser(context.Background(), &models.GetSignedInUserQuery{UserId: ac2.ID})
require.Equal(t, err, user.ErrUserNotFound)
})
t.Run("Cannot delete last admin org user", func(t *testing.T) {
cmd := models.RemoveOrgUserCommand{OrgId: ac1.OrgID, UserId: ac1.ID}
err := sqlStore.RemoveOrgUser(context.Background(), &cmd)
require.Equal(t, err, models.ErrLastOrgAdmin)
// require.NoError(t, err)
// require.Equal(t, query.Result.OrgID, ac2.OrgID)
// })
})
t.Run("Given an org user with dashboard permissions", func(t *testing.T) {
@@ -302,31 +236,32 @@ func TestIntegrationAccountDataAccess(t *testing.T) {
})
require.NoError(t, err)
t.Run("When org user is deleted", func(t *testing.T) {
cmdRemove := models.RemoveOrgUserCommand{OrgId: ac1.OrgID, UserId: ac3.ID}
err := sqlStore.RemoveOrgUser(context.Background(), &cmdRemove)
require.NoError(t, err)
// TODO: should be moved to dashboard service
// t.Run("When org user is deleted", func(t *testing.T) {
// cmdRemove := models.RemoveOrgUserCommand{OrgId: ac1.OrgID, UserId: ac3.ID}
// err := sqlStore.RemoveOrgUser(context.Background(), &cmdRemove)
// require.NoError(t, err)
t.Run("Should remove dependent permissions for deleted org user", func(t *testing.T) {
permQuery := &models.GetDashboardACLInfoListQuery{DashboardID: dash1.Id, OrgID: ac1.OrgID}
// t.Run("Should remove dependent permissions for deleted org user", func(t *testing.T) {
// permQuery := &models.GetDashboardACLInfoListQuery{DashboardID: dash1.Id, OrgID: ac1.OrgID}
err = getDashboardACLInfoList(sqlStore, permQuery)
require.NoError(t, err)
// err = getDashboardACLInfoList(sqlStore, permQuery)
// require.NoError(t, err)
require.Equal(t, len(permQuery.Result), 0)
})
// require.Equal(t, len(permQuery.Result), 0)
// })
t.Run("Should not remove dashboard permissions for same user in another org", func(t *testing.T) {
permQuery := &models.GetDashboardACLInfoListQuery{DashboardID: dash2.Id, OrgID: ac3.OrgID}
// t.Run("Should not remove dashboard permissions for same user in another org", func(t *testing.T) {
// permQuery := &models.GetDashboardACLInfoListQuery{DashboardID: dash2.Id, OrgID: ac3.OrgID}
err = getDashboardACLInfoList(sqlStore, permQuery)
require.NoError(t, err)
// err = getDashboardACLInfoList(sqlStore, permQuery)
// require.NoError(t, err)
require.Equal(t, len(permQuery.Result), 1)
require.Equal(t, permQuery.Result[0].OrgId, ac3.OrgID)
require.Equal(t, permQuery.Result[0].UserId, ac3.ID)
})
})
// require.Equal(t, len(permQuery.Result), 1)
// require.Equal(t, permQuery.Result[0].OrgId, ac3.OrgID)
// require.Equal(t, permQuery.Result[0].UserId, ac3.ID)
// })
// })
})
})
})

View File

@@ -2,14 +2,10 @@ package sqlstore
import (
"context"
"fmt"
"strings"
"time"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/util"
)
func (ss *SQLStore) AddOrgUser(ctx context.Context, cmd *models.AddOrgUserCommand) error {
@@ -70,171 +66,3 @@ func (ss *SQLStore) AddOrgUser(ctx context.Context, cmd *models.AddOrgUserComman
return nil
})
}
func (ss *SQLStore) SearchOrgUsers(ctx context.Context, query *models.SearchOrgUsersQuery) error {
return ss.WithDbSession(ctx, func(dbSession *DBSession) error {
query.Result = models.SearchOrgUsersQueryResult{
OrgUsers: make([]*models.OrgUserDTO, 0),
}
sess := dbSession.Table("org_user")
sess.Join("INNER", ss.Dialect.Quote("user"), fmt.Sprintf("org_user.user_id=%s.id", ss.Dialect.Quote("user")))
whereConditions := make([]string, 0)
whereParams := make([]interface{}, 0)
whereConditions = append(whereConditions, "org_user.org_id = ?")
whereParams = append(whereParams, query.OrgID)
whereConditions = append(whereConditions, fmt.Sprintf("%s.is_service_account = %s", ss.Dialect.Quote("user"), ss.Dialect.BooleanStr(false)))
if !accesscontrol.IsDisabled(ss.Cfg) {
acFilter, err := accesscontrol.Filter(query.User, "org_user.user_id", "users:id:", accesscontrol.ActionOrgUsersRead)
if err != nil {
return err
}
whereConditions = append(whereConditions, acFilter.Where)
whereParams = append(whereParams, acFilter.Args...)
}
if query.Query != "" {
queryWithWildcards := "%" + query.Query + "%"
whereConditions = append(whereConditions, "(email "+ss.Dialect.LikeStr()+" ? OR name "+ss.Dialect.LikeStr()+" ? OR login "+ss.Dialect.LikeStr()+" ?)")
whereParams = append(whereParams, queryWithWildcards, queryWithWildcards, queryWithWildcards)
}
if len(whereConditions) > 0 {
sess.Where(strings.Join(whereConditions, " AND "), whereParams...)
}
if query.Limit > 0 {
offset := query.Limit * (query.Page - 1)
sess.Limit(query.Limit, offset)
}
sess.Cols(
"org_user.org_id",
"org_user.user_id",
"user.email",
"user.name",
"user.login",
"org_user.role",
"user.last_seen_at",
)
sess.Asc("user.email", "user.login")
if err := sess.Find(&query.Result.OrgUsers); err != nil {
return err
}
// get total count
orgUser := models.OrgUser{}
countSess := dbSession.Table("org_user").
Join("INNER", ss.Dialect.Quote("user"), fmt.Sprintf("org_user.user_id=%s.id", ss.Dialect.Quote("user")))
if len(whereConditions) > 0 {
countSess.Where(strings.Join(whereConditions, " AND "), whereParams...)
}
count, err := countSess.Count(&orgUser)
if err != nil {
return err
}
query.Result.TotalCount = count
for _, user := range query.Result.OrgUsers {
user.LastSeenAtAge = util.GetAgeString(user.LastSeenAt)
}
return nil
})
}
func (ss *SQLStore) RemoveOrgUser(ctx context.Context, cmd *models.RemoveOrgUserCommand) error {
return ss.WithTransactionalDbSession(ctx, func(sess *DBSession) error {
// check if user exists
var usr user.User
if exists, err := sess.ID(cmd.UserId).Where(notServiceAccountFilter(ss)).Get(&usr); err != nil {
return err
} else if !exists {
return user.ErrUserNotFound
}
deletes := []string{
"DELETE FROM org_user WHERE org_id=? and user_id=?",
"DELETE FROM dashboard_acl WHERE org_id=? and user_id = ?",
"DELETE FROM team_member WHERE org_id=? and user_id = ?",
"DELETE FROM query_history_star WHERE org_id=? and user_id = ?",
}
for _, sql := range deletes {
_, err := sess.Exec(sql, cmd.OrgId, cmd.UserId)
if err != nil {
return err
}
}
// validate that after delete, there is at least one user with admin role in org
if err := validateOneAdminLeftInOrg(cmd.OrgId, sess); err != nil {
return err
}
// check user other orgs and update user current org
var userOrgs []*models.UserOrgDTO
sess.Table("org_user")
sess.Join("INNER", "org", "org_user.org_id=org.id")
sess.Where("org_user.user_id=?", usr.ID)
sess.Cols("org.name", "org_user.role", "org_user.org_id")
err := sess.Find(&userOrgs)
if err != nil {
return err
}
if len(userOrgs) > 0 {
hasCurrentOrgSet := false
for _, userOrg := range userOrgs {
if usr.OrgID == userOrg.OrgId {
hasCurrentOrgSet = true
break
}
}
if !hasCurrentOrgSet {
err = setUsingOrgInTransaction(sess, usr.ID, userOrgs[0].OrgId)
if err != nil {
return err
}
}
} else if cmd.ShouldDeleteOrphanedUser {
// no other orgs, delete the full user
if err := deleteUserInTransaction(ss, sess, &models.DeleteUserCommand{UserId: usr.ID}); err != nil {
return err
}
cmd.UserWasDeleted = true
} else {
// no orgs, but keep the user -> clean up orgId
err = removeUserOrg(sess, usr.ID)
if err != nil {
return err
}
}
return nil
})
}
// validate that there is an org admin user left
func validateOneAdminLeftInOrg(orgId int64, sess *DBSession) error {
res, err := sess.Query("SELECT 1 from org_user WHERE org_id=? and role='Admin'", orgId)
if err != nil {
return err
}
if len(res) == 0 {
return models.ErrLastOrgAdmin
}
return err
}

View File

@@ -2,83 +2,14 @@ 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"
"github.com/grafana/grafana/pkg/services/user"
)
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: &user.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: &user.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: &user.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, InitTestDBOpt{})
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 TestSQLStore_AddOrgUser(t *testing.T) {
var orgID int64 = 1
store := InitTestDB(t)
@@ -133,80 +64,3 @@ func TestSQLStore_AddOrgUser(t *testing.T) {
require.NoError(t, err)
require.Equal(t, saFound.OrgID, orgID)
}
func TestSQLStore_RemoveOrgUser(t *testing.T) {
store := InitTestDB(t)
// create org and admin
_, err := store.CreateUser(context.Background(), user.CreateUserCommand{
Login: "admin",
OrgID: 1,
})
require.NoError(t, err)
// create a user with no org
_, err = store.CreateUser(context.Background(), user.CreateUserCommand{
Login: "user",
OrgID: 1,
SkipOrgSetup: true,
})
require.NoError(t, err)
// assign the user to the org
err = store.AddOrgUser(context.Background(), &models.AddOrgUserCommand{
Role: "Viewer",
OrgId: 1,
UserId: 2,
})
require.NoError(t, err)
// assert the org has been assigned
user := &models.GetUserByIdQuery{Id: 2}
err = store.GetUserById(context.Background(), user)
require.NoError(t, err)
require.Equal(t, user.Result.OrgID, int64(1))
// remove the user org
err = store.RemoveOrgUser(context.Background(), &models.RemoveOrgUserCommand{
UserId: 2,
OrgId: 1,
ShouldDeleteOrphanedUser: false,
})
require.NoError(t, err)
// assert the org has been removed
user = &models.GetUserByIdQuery{Id: 2}
err = store.GetUserById(context.Background(), user)
require.NoError(t, err)
require.Equal(t, user.Result.OrgID, int64(0))
}
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(), user.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 *user.SignedInUser, action string) bool {
for _, scope := range user.Permissions[user.OrgID][action] {
if strings.HasSuffix(scope, ":*") {
return true
}
}
return false
}

View File

@@ -39,8 +39,6 @@ type Store interface {
GetGlobalQuotaByTarget(ctx context.Context, query *models.GetGlobalQuotaByTargetQuery) error
WithTransactionalDbSession(ctx context.Context, callback DBTransactionFunc) error
InTransaction(ctx context.Context, fn func(ctx context.Context) error) error
SearchOrgUsers(ctx context.Context, query *models.SearchOrgUsersQuery) error
RemoveOrgUser(ctx context.Context, cmd *models.RemoveOrgUserCommand) error
Migrate(bool) error
Sync() error
Reset() error

View File

@@ -374,16 +374,6 @@ func setUsingOrgInTransaction(sess *DBSession, userID int64, orgID int64) error
return err
}
func removeUserOrg(sess *DBSession, userID int64) error {
user := user.User{
ID: userID,
OrgID: 0,
}
_, err := sess.ID(userID).MustCols("org_id").Update(&user)
return err
}
func (ss *SQLStore) GetUserProfile(ctx context.Context, query *models.GetUserProfileQuery) error {
return ss.WithDbSession(ctx, func(sess *DBSession) error {
var usr user.User