The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/pkg/services/user/userimpl/store_test.go

1127 lines
34 KiB

package userimpl
import (
"context"
"fmt"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/org/orgimpl"
"github.com/grafana/grafana/pkg/services/quota/quotaimpl"
"github.com/grafana/grafana/pkg/services/searchusers/sortopts"
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
"github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/setting"
)
func TestIntegrationUserGet(t *testing.T) {
testCases := []struct {
name string
wantErr error
searchLogin string
searchEmail string
caseInsensitive bool
}{
{
name: "user not found non exact - not case insensitive",
wantErr: user.ErrUserNotFound,
searchLogin: "Test",
searchEmail: "Test@email.com",
caseInsensitive: false,
},
{
name: "user found exact - not case insensitive",
wantErr: nil,
searchLogin: "test",
searchEmail: "test@email.com",
caseInsensitive: false,
},
{
name: "user found non exact - case insensitive",
wantErr: nil,
searchLogin: "Test",
searchEmail: "Test@email.com",
caseInsensitive: true,
},
{
name: "user found exact - case insensitive",
wantErr: nil,
searchLogin: "Test",
searchEmail: "Test@email.com",
caseInsensitive: true,
},
{
name: "user not found - case insensitive",
wantErr: user.ErrUserNotFound,
searchLogin: "Test_login",
searchEmail: "Test*@email.com",
caseInsensitive: true,
},
}
if testing.Short() {
t.Skip("skipping integration test")
}
ss := db.InitTestDB(t)
cfg := ss.Cfg
userStore := ProvideStore(ss, cfg)
_, errUser := userStore.Insert(context.Background(),
&user.User{
Email: "test@email.com",
Name: "test",
Login: "test",
Created: time.Now(),
Updated: time.Now(),
},
)
require.NoError(t, errUser)
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if !tc.caseInsensitive && db.IsTestDbMySQL() {
t.Skip("mysql is always case insensitive")
}
cfg.CaseInsensitiveLogin = tc.caseInsensitive
usr, err := userStore.Get(context.Background(),
&user.User{
Email: tc.searchEmail,
Login: tc.searchLogin,
},
)
if tc.wantErr != nil {
require.Error(t, err)
require.Nil(t, usr)
} else {
require.NoError(t, err)
require.NotNil(t, usr)
}
})
}
}
func TestIntegrationUserDataAccess(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test")
}
ss := db.InitTestDB(t)
quotaService := quotaimpl.ProvideService(ss, ss.Cfg)
orgService, err := orgimpl.ProvideService(ss, ss.Cfg, quotaService)
require.NoError(t, err)
userStore := ProvideStore(ss, setting.NewCfg())
usrSvc, err := ProvideService(ss, orgService, ss.Cfg, nil, nil, quotaService, supportbundlestest.NewFakeBundleService())
require.NoError(t, err)
usr := &user.SignedInUser{
OrgID: 1,
Permissions: map[int64]map[string][]string{1: {"users:read": {"global.users:*"}}},
}
t.Run("user not found", func(t *testing.T) {
_, err := userStore.Get(context.Background(),
&user.User{
Email: "test@email.com",
Name: "test1",
Login: "test1",
},
)
require.Error(t, err, user.ErrUserNotFound)
})
t.Run("insert user", func(t *testing.T) {
_, err := userStore.Insert(context.Background(),
&user.User{
Email: "test@email.com",
Name: "test1",
Login: "test1",
Created: time.Now(),
Updated: time.Now(),
},
)
require.NoError(t, err)
})
t.Run("get user", func(t *testing.T) {
_, err := userStore.Get(context.Background(),
&user.User{
Email: "test@email.com",
Name: "test1",
Login: "test1",
},
)
require.NoError(t, err)
})
t.Run("Testing DB - creates and loads user", func(t *testing.T) {
ss := db.InitTestDB(t)
_, usrSvc := createOrgAndUserSvc(t, ss, ss.Cfg)
cmd := user.CreateUserCommand{
Email: "usertest@test.com",
Name: "user name",
Login: "user_test_login",
}
usr, err := usrSvc.Create(context.Background(), &cmd)
require.NoError(t, err)
result, err := userStore.GetByID(context.Background(), usr.ID)
require.Nil(t, err)
require.Equal(t, result.Email, "usertest@test.com")
require.Equal(t, result.Password, "")
require.Len(t, result.Rands, 10)
require.Len(t, result.Salt, 10)
require.False(t, result.IsDisabled)
result, err = userStore.GetByID(context.Background(), usr.ID)
require.Nil(t, err)
require.Equal(t, result.Email, "usertest@test.com")
require.Equal(t, result.Password, "")
require.Len(t, result.Rands, 10)
require.Len(t, result.Salt, 10)
require.False(t, result.IsDisabled)
t.Run("Get User by email case insensitive", func(t *testing.T) {
userStore.cfg.CaseInsensitiveLogin = true
query := user.GetUserByEmailQuery{Email: "USERtest@TEST.COM"}
result, err := userStore.GetByEmail(context.Background(), &query)
require.Nil(t, err)
require.Equal(t, result.Email, "usertest@test.com")
require.Equal(t, result.Password, "")
require.Len(t, result.Rands, 10)
require.Len(t, result.Salt, 10)
require.False(t, result.IsDisabled)
userStore.cfg.CaseInsensitiveLogin = false
})
t.Run("Testing DB - creates and loads user", func(t *testing.T) {
result, err = userStore.GetByID(context.Background(), usr.ID)
require.Nil(t, err)
require.Equal(t, result.Email, "usertest@test.com")
require.Equal(t, result.Password, "")
require.Len(t, result.Rands, 10)
require.Len(t, result.Salt, 10)
require.False(t, result.IsDisabled)
result, err = userStore.GetByID(context.Background(), usr.ID)
require.Nil(t, err)
require.Equal(t, result.Email, "usertest@test.com")
require.Equal(t, result.Password, "")
require.Len(t, result.Rands, 10)
require.Len(t, result.Salt, 10)
require.False(t, result.IsDisabled)
ss.Cfg.CaseInsensitiveLogin = false
})
})
t.Run("Testing DB - error on case insensitive conflict", func(t *testing.T) {
if ss.GetDBType() == migrator.MySQL {
t.Skip("Skipping on MySQL due to case insensitive indexes")
}
userStore.cfg.CaseInsensitiveLogin = true
cmd := user.CreateUserCommand{
Email: "confusertest@test.com",
Name: "user name",
Login: "user_email_conflict",
}
// userEmailConflict
_, err = usrSvc.Create(context.Background(), &cmd)
require.NoError(t, err)
cmd = user.CreateUserCommand{
Email: "confusertest@TEST.COM",
Name: "user name",
Login: "user_email_conflict_two",
}
_, err := usrSvc.Create(context.Background(), &cmd)
require.NoError(t, err)
cmd = user.CreateUserCommand{
Email: "user_test_login_conflict@test.com",
Name: "user name",
Login: "user_test_login_conflict",
}
// userLoginConflict
_, err = usrSvc.Create(context.Background(), &cmd)
require.NoError(t, err)
cmd = user.CreateUserCommand{
Email: "user_test_login_conflict_two@test.com",
Name: "user name",
Login: "user_test_login_CONFLICT",
}
_, err = usrSvc.Create(context.Background(), &cmd)
require.NoError(t, err)
ss.Cfg.CaseInsensitiveLogin = true
t.Run("GetByEmail - email conflict", func(t *testing.T) {
query := user.GetUserByEmailQuery{Email: "confusertest@test.com"}
_, err = userStore.GetByEmail(context.Background(), &query)
require.Error(t, err)
})
t.Run("GetByEmail - login conflict", func(t *testing.T) {
query := user.GetUserByEmailQuery{Email: "user_test_login_conflict@test.com"}
_, err = userStore.GetByEmail(context.Background(), &query)
require.Error(t, err)
})
t.Run("GetByLogin - email conflict", func(t *testing.T) {
query := user.GetUserByLoginQuery{LoginOrEmail: "user_email_conflict_two"}
_, err = userStore.GetByLogin(context.Background(), &query)
require.Error(t, err)
})
t.Run("GetByLogin - login conflict", func(t *testing.T) {
query := user.GetUserByLoginQuery{LoginOrEmail: "user_test_login_conflict"}
_, err = userStore.GetByLogin(context.Background(), &query)
require.Error(t, err)
})
t.Run("GetByLogin - login conflict by email", func(t *testing.T) {
query := user.GetUserByLoginQuery{LoginOrEmail: "user_test_login_conflict@test.com"}
_, err = userStore.GetByLogin(context.Background(), &query)
require.Error(t, err)
})
t.Run("GetByLogin - user2 uses user1.email as login", func(t *testing.T) {
// create user_1
user1 := &user.User{
Email: "user_1@mail.com",
Name: "user_1",
Login: "user_1",
Password: "user_1_password",
Created: time.Now(),
Updated: time.Now(),
IsDisabled: true,
}
_, err := userStore.Insert(context.Background(), user1)
require.Nil(t, err)
// create user_2
user2 := &user.User{
Email: "user_2@mail.com",
Name: "user_2",
Login: "user_1@mail.com",
Password: "user_2_password",
Created: time.Now(),
Updated: time.Now(),
IsDisabled: true,
}
_, err = userStore.Insert(context.Background(), user2)
require.Nil(t, err)
// query user database for user_1 email
query := user.GetUserByLoginQuery{LoginOrEmail: "user_1@mail.com"}
result, err := userStore.GetByLogin(context.Background(), &query)
require.Nil(t, err)
// expect user_1 as result
require.Equal(t, user1.Email, result.Email)
require.Equal(t, user1.Login, result.Login)
require.Equal(t, user1.Name, result.Name)
require.NotEqual(t, user2.Email, result.Email)
require.NotEqual(t, user2.Login, result.Login)
require.NotEqual(t, user2.Name, result.Name)
})
ss.Cfg.CaseInsensitiveLogin = false
})
t.Run("Change user password", func(t *testing.T) {
err := userStore.ChangePassword(context.Background(), &user.ChangeUserPasswordCommand{})
require.NoError(t, err)
})
t.Run("update last seen at", func(t *testing.T) {
err := userStore.UpdateLastSeenAt(context.Background(), &user.UpdateUserLastSeenAtCommand{
UserID: 10, // Requires UserID
})
require.NoError(t, err)
err = userStore.UpdateLastSeenAt(context.Background(), &user.UpdateUserLastSeenAtCommand{
UserID: -1,
})
require.Error(t, err)
})
t.Run("get signed in user", func(t *testing.T) {
ss := db.InitTestDB(t)
orgService, usrSvc := createOrgAndUserSvc(t, ss, ss.Cfg)
users := createFiveTestUsers(t, usrSvc, func(i int) *user.CreateUserCommand {
return &user.CreateUserCommand{
Email: fmt.Sprint("user", i, "@test.com"),
Name: fmt.Sprint("user", i),
Login: fmt.Sprint("loginuser", i),
IsDisabled: false,
}
})
err := orgService.AddOrgUser(context.Background(), &org.AddOrgUserCommand{
LoginOrEmail: users[1].Login, Role: org.RoleViewer,
OrgID: users[0].OrgID, UserID: users[1].ID,
})
require.Nil(t, err)
err = updateDashboardACL(t, ss, 1, &dashboards.DashboardACL{
DashboardID: 1, OrgID: users[0].OrgID, UserID: users[1].ID,
Permission: dashboards.PERMISSION_EDIT,
})
require.Nil(t, err)
ss.CacheService.Flush()
query := &user.GetSignedInUserQuery{OrgID: users[1].OrgID, UserID: users[1].ID}
result, err := userStore.GetSignedInUser(context.Background(), query)
require.NoError(t, err)
require.Equal(t, result.Email, "user1@test.com")
// Throw errors for invalid user IDs
for _, userID := range []int64{-1, 0} {
_, err = userStore.GetSignedInUser(context.Background(),
&user.GetSignedInUserQuery{
OrgID: users[1].OrgID,
UserID: userID}) // zero
require.Error(t, err)
}
})
t.Run("update user", func(t *testing.T) {
err := userStore.UpdateUser(context.Background(), &user.User{ID: 1, Name: "testtestest", Login: "loginloginlogin"})
require.NoError(t, err)
result, err := userStore.GetByID(context.Background(), 1)
require.NoError(t, err)
assert.Equal(t, result.Name, "testtestest")
assert.Equal(t, result.Login, "loginloginlogin")
})
t.Run("Testing DB - grafana admin users", func(t *testing.T) {
ss := db.InitTestDB(t)
_, usrSvc := createOrgAndUserSvc(t, ss, ss.Cfg)
createUserCmd := user.CreateUserCommand{
Email: fmt.Sprint("admin", "@test.com"),
Name: "admin",
Login: "admin",
IsAdmin: true,
}
usr, err := usrSvc.Create(context.Background(), &createUserCmd)
require.Nil(t, err)
// Cannot make themselves a non-admin
updatePermsError := userStore.UpdatePermissions(context.Background(), usr.ID, false)
require.Equal(t, user.ErrLastGrafanaAdmin, updatePermsError)
query := user.GetUserByIDQuery{ID: usr.ID}
queryResult, getUserError := userStore.GetByID(context.Background(), query.ID)
require.Nil(t, getUserError)
require.True(t, queryResult.IsAdmin)
// One user
const email = "user@test.com"
const username = "user"
createUserCmd = user.CreateUserCommand{
Email: email,
Name: "user",
Login: username,
}
_, err = usrSvc.Create(context.Background(), &createUserCmd)
require.Nil(t, err)
// When trying to create a new user with the same email, an error is returned
createUserCmd = user.CreateUserCommand{
Email: email,
Name: "user2",
Login: "user2",
SkipOrgSetup: true,
}
_, err = usrSvc.Create(context.Background(), &createUserCmd)
require.Equal(t, user.ErrUserAlreadyExists, err)
// When trying to create a new user with the same login, an error is returned
createUserCmd = user.CreateUserCommand{
Email: "user2@test.com",
Name: "user2",
Login: username,
SkipOrgSetup: true,
}
_, err = usrSvc.Create(context.Background(), &createUserCmd)
require.Equal(t, user.ErrUserAlreadyExists, err)
})
t.Run("GetProfile", func(t *testing.T) {
_, err := userStore.GetProfile(context.Background(), &user.GetUserProfileQuery{UserID: 1})
require.NoError(t, err)
})
t.Run("SetHelpFlag", func(t *testing.T) {
err := userStore.SetHelpFlag(context.Background(), &user.SetUserHelpFlagCommand{UserID: 1, HelpFlags1: user.HelpFlags1(1)})
require.NoError(t, err)
})
t.Run("Testing DB - return list users based on their is_disabled flag", func(t *testing.T) {
ss = db.InitTestDB(t)
_, usrSvc := createOrgAndUserSvc(t, ss, ss.Cfg)
userStore := ProvideStore(ss, ss.Cfg)
createFiveTestUsers(t, usrSvc, func(i int) *user.CreateUserCommand {
return &user.CreateUserCommand{
Email: fmt.Sprint("user", i, "@test.com"),
Name: fmt.Sprint("user", i),
Login: fmt.Sprint("loginuser", i),
IsDisabled: i%2 == 0,
}
})
isDisabled := false
query := user.SearchUsersQuery{IsDisabled: &isDisabled, SignedInUser: usr}
result, err := userStore.Search(context.Background(), &query)
require.Nil(t, err)
require.Len(t, result.Users, 2)
first, third := false, false
for _, user := range result.Users {
if user.Name == "user1" {
first = true
}
if user.Name == "user3" {
third = true
}
}
require.True(t, first)
require.True(t, third)
// Re-init DB
ss := db.InitTestDB(t)
orgService, usrSvc = createOrgAndUserSvc(t, ss, ss.Cfg)
users := createFiveTestUsers(t, usrSvc, func(i int) *user.CreateUserCommand {
return &user.CreateUserCommand{
Email: fmt.Sprint("user", i, "@test.com"),
Name: fmt.Sprint("user", i),
Login: fmt.Sprint("loginuser", i),
IsDisabled: false,
}
})
err = orgService.AddOrgUser(context.Background(), &org.AddOrgUserCommand{
LoginOrEmail: users[1].Login, Role: org.RoleViewer,
OrgID: users[0].OrgID, UserID: users[1].ID,
})
require.Nil(t, err)
err = updateDashboardACL(t, ss, 1, &dashboards.DashboardACL{
DashboardID: 1, OrgID: users[0].OrgID, UserID: users[1].ID,
Permission: dashboards.PERMISSION_EDIT,
})
require.Nil(t, err)
// When the user is deleted
err = userStore.Delete(context.Background(), users[1].ID)
require.Nil(t, err)
permQuery := &dashboards.GetDashboardACLInfoListQuery{DashboardID: 1, OrgID: users[0].OrgID}
permQueryResult, err := userStore.getDashboardACLInfoList(permQuery)
require.Nil(t, err)
require.Len(t, permQueryResult, 0)
// A user is an org member and has been assigned permissions
// Re-init DB
ss = db.InitTestDB(t)
orgService, usrSvc = createOrgAndUserSvc(t, ss, ss.Cfg)
users = createFiveTestUsers(t, usrSvc, func(i int) *user.CreateUserCommand {
return &user.CreateUserCommand{
Email: fmt.Sprint("user", i, "@test.com"),
Name: fmt.Sprint("user", i),
Login: fmt.Sprint("loginuser", i),
IsDisabled: false,
}
})
err = orgService.AddOrgUser(context.Background(), &org.AddOrgUserCommand{
LoginOrEmail: users[1].Login, Role: org.RoleViewer,
OrgID: users[0].OrgID, UserID: users[1].ID,
})
require.Nil(t, err)
err = updateDashboardACL(t, ss, 1, &dashboards.DashboardACL{
DashboardID: 1, OrgID: users[0].OrgID, UserID: users[1].ID,
Permission: dashboards.PERMISSION_EDIT,
})
require.Nil(t, err)
ss.CacheService.Flush()
query3 := &user.GetSignedInUserQuery{OrgID: users[1].OrgID, UserID: users[1].ID}
query3Result, err := userStore.GetSignedInUser(context.Background(), query3)
require.Nil(t, err)
require.NotNil(t, query3Result)
require.Equal(t, query3.OrgID, users[1].OrgID)
disableCmd := user.BatchDisableUsersCommand{
UserIDs: []int64{users[0].ID, users[1].ID, users[2].ID, users[3].ID, users[4].ID},
IsDisabled: true,
}
err = userStore.BatchDisableUsers(context.Background(), &disableCmd)
require.Nil(t, err)
isDisabled = true
query5 := &user.SearchUsersQuery{IsDisabled: &isDisabled, SignedInUser: usr}
query5Result, err := userStore.Search(context.Background(), query5)
require.Nil(t, err)
require.EqualValues(t, query5Result.TotalCount, 5)
// the user is deleted
err = userStore.Delete(context.Background(), users[1].ID)
require.Nil(t, err)
permQuery = &dashboards.GetDashboardACLInfoListQuery{DashboardID: 1, OrgID: users[0].OrgID}
permQueryResult, err = userStore.getDashboardACLInfoList(permQuery)
require.Nil(t, err)
require.Len(t, permQueryResult, 0)
})
t.Run("Testing DB - return list of users that the SignedInUser has permission to read", func(t *testing.T) {
ss := db.InitTestDB(t)
orgService, err := orgimpl.ProvideService(ss, ss.Cfg, quotaService)
require.NoError(t, err)
usrSvc, err := ProvideService(ss, orgService, ss.Cfg, nil, nil, quotaService, supportbundlestest.NewFakeBundleService())
require.NoError(t, err)
createFiveTestUsers(t, usrSvc, func(i int) *user.CreateUserCommand {
return &user.CreateUserCommand{
Email: fmt.Sprint("user", i, "@test.com"),
Name: fmt.Sprint("user", i),
Login: fmt.Sprint("loginuser", i),
}
})
testUser := &user.SignedInUser{
OrgID: 1,
Permissions: map[int64]map[string][]string{1: {"users:read": {"global.users:id:1", "global.users:id:3"}}},
}
query := user.SearchUsersQuery{SignedInUser: testUser}
queryResult, err := userStore.Search(context.Background(), &query)
assert.Nil(t, err)
assert.Len(t, queryResult.Users, 2)
})
ss = db.InitTestDB(t)
t.Run("Testing DB - enable all users", func(t *testing.T) {
users := createFiveTestUsers(t, usrSvc, func(i int) *user.CreateUserCommand {
return &user.CreateUserCommand{
Email: fmt.Sprint("user", i, "@test.com"),
Name: fmt.Sprint("user", i),
Login: fmt.Sprint("loginuser", i),
IsDisabled: true,
}
})
disableCmd := user.BatchDisableUsersCommand{
UserIDs: []int64{users[0].ID, users[1].ID, users[2].ID, users[3].ID, users[4].ID},
IsDisabled: false,
}
err := userStore.BatchDisableUsers(context.Background(), &disableCmd)
require.Nil(t, err)
isDisabled := false
query := &user.SearchUsersQuery{IsDisabled: &isDisabled, SignedInUser: usr}
queryResult, err := userStore.Search(context.Background(), query)
require.Nil(t, err)
require.EqualValues(t, queryResult.TotalCount, 5)
})
t.Run("Can search users", func(t *testing.T) {
ss = db.InitTestDB(t)
userStore.cfg.AutoAssignOrg = false
ac1cmd := user.CreateUserCommand{Login: "ac1", Email: "ac1@test.com", Name: "ac1 name"}
ac2cmd := user.CreateUserCommand{Login: "ac2", Email: "ac2@test.com", Name: "ac2 name", IsAdmin: true}
serviceaccountcmd := user.CreateUserCommand{Login: "serviceaccount", Email: "service@test.com", Name: "serviceaccount name", IsAdmin: true, IsServiceAccount: true}
_, err := usrSvc.Create(context.Background(), &ac1cmd)
require.NoError(t, err)
_, err = usrSvc.Create(context.Background(), &ac2cmd)
require.NoError(t, err)
// user only used for making sure we filter out the service accounts
_, err = usrSvc.Create(context.Background(), &serviceaccountcmd)
require.NoError(t, err)
query := user.SearchUsersQuery{Query: "", SignedInUser: &user.SignedInUser{
OrgID: 1,
Permissions: map[int64]map[string][]string{
1: {accesscontrol.ActionUsersRead: {accesscontrol.ScopeGlobalUsersAll}},
},
}}
queryResult, err := userStore.Search(context.Background(), &query)
require.NoError(t, err)
require.Len(t, queryResult.Users, 2)
require.Equal(t, queryResult.Users[0].Email, "ac1@test.com")
require.Equal(t, queryResult.Users[1].Email, "ac2@test.com")
})
ss = db.InitTestDB(t)
t.Run("Testing DB - disable only specific users", func(t *testing.T) {
users := createFiveTestUsers(t, usrSvc, func(i int) *user.CreateUserCommand {
return &user.CreateUserCommand{
Email: fmt.Sprint("user", i, "@test.com"),
Name: fmt.Sprint("user", i),
Login: fmt.Sprint("loginuser", i),
IsDisabled: false,
}
})
userIdsToDisable := []int64{}
for i := 0; i < 3; i++ {
userIdsToDisable = append(userIdsToDisable, users[i].ID)
}
disableCmd := user.BatchDisableUsersCommand{
UserIDs: userIdsToDisable,
IsDisabled: true,
}
err := userStore.BatchDisableUsers(context.Background(), &disableCmd)
require.Nil(t, err)
query := user.SearchUsersQuery{SignedInUser: usr}
queryResult, err := userStore.Search(context.Background(), &query)
require.Nil(t, err)
require.EqualValues(t, queryResult.TotalCount, 5)
for _, user := range queryResult.Users {
shouldBeDisabled := false
// Check if user id is in the userIdsToDisable list
for _, disabledUserId := range userIdsToDisable {
if user.ID == disabledUserId {
require.True(t, user.IsDisabled)
shouldBeDisabled = true
}
}
// Otherwise user shouldn't be disabled
if !shouldBeDisabled {
require.False(t, user.IsDisabled)
}
}
})
ss = db.InitTestDB(t)
t.Run("Testing DB - search users", func(t *testing.T) {
// Since previous tests were destructive
createFiveTestUsers(t, usrSvc, func(i int) *user.CreateUserCommand {
return &user.CreateUserCommand{
Email: fmt.Sprint("user", i, "@test.com"),
Name: fmt.Sprint("user", i),
Login: fmt.Sprint("loginuser", i),
IsDisabled: false,
}
})
})
t.Run("Disable user", func(t *testing.T) {
id, err := userStore.Insert(context.Background(), &user.User{
Name: "user111",
Created: time.Now(),
Updated: time.Now(),
})
require.NoError(t, err)
err = userStore.Disable(context.Background(), &user.DisableUserCommand{UserID: id})
require.NoError(t, err)
})
t.Run("Testing DB - multiple users", func(t *testing.T) {
ss = db.InitTestDB(t)
createFiveTestUsers(t, usrSvc, func(i int) *user.CreateUserCommand {
return &user.CreateUserCommand{
Email: fmt.Sprint("user", i, "@test.com"),
Name: fmt.Sprint("user", i),
Login: fmt.Sprint("loginuser", i),
IsDisabled: false,
}
})
// Return the first page of users and a total count
query := user.SearchUsersQuery{Query: "", Page: 1, Limit: 3, SignedInUser: usr}
queryResult, err := userStore.Search(context.Background(), &query)
require.Nil(t, err)
require.Len(t, queryResult.Users, 3)
require.EqualValues(t, queryResult.TotalCount, 5)
// Return the second page of users and a total count
query = user.SearchUsersQuery{Query: "", Page: 2, Limit: 3, SignedInUser: usr}
queryResult, err = userStore.Search(context.Background(), &query)
require.Nil(t, err)
require.Len(t, queryResult.Users, 2)
require.EqualValues(t, queryResult.TotalCount, 5)
// Return list of users matching query on user name
query = user.SearchUsersQuery{Query: "use", Page: 1, Limit: 3, SignedInUser: usr}
queryResult, err = userStore.Search(context.Background(), &query)
require.Nil(t, err)
require.Len(t, queryResult.Users, 3)
require.EqualValues(t, queryResult.TotalCount, 5)
query = user.SearchUsersQuery{Query: "ser1", Page: 1, Limit: 3, SignedInUser: usr}
queryResult, err = userStore.Search(context.Background(), &query)
require.Nil(t, err)
require.Len(t, queryResult.Users, 1)
require.EqualValues(t, queryResult.TotalCount, 1)
query = user.SearchUsersQuery{Query: "USER1", Page: 1, Limit: 3, SignedInUser: usr}
queryResult, err = userStore.Search(context.Background(), &query)
require.Nil(t, err)
require.Len(t, queryResult.Users, 1)
require.EqualValues(t, queryResult.TotalCount, 1)
query = user.SearchUsersQuery{Query: "idontexist", Page: 1, Limit: 3, SignedInUser: usr}
queryResult, err = userStore.Search(context.Background(), &query)
require.Nil(t, err)
require.Len(t, queryResult.Users, 0)
require.EqualValues(t, queryResult.TotalCount, 0)
// Return list of users matching query on email
query = user.SearchUsersQuery{Query: "ser1@test.com", Page: 1, Limit: 3, SignedInUser: usr}
queryResult, err = userStore.Search(context.Background(), &query)
require.Nil(t, err)
require.Len(t, queryResult.Users, 1)
require.EqualValues(t, queryResult.TotalCount, 1)
// Return list of users matching query on login name
query = user.SearchUsersQuery{Query: "loginuser1", Page: 1, Limit: 3, SignedInUser: usr}
queryResult, err = userStore.Search(context.Background(), &query)
require.Nil(t, err)
require.Len(t, queryResult.Users, 1)
require.EqualValues(t, queryResult.TotalCount, 1)
// Custom ordering
sortOpts, err := sortopts.ParseSortQueryParam("login-asc,email-asc")
require.NoError(t, err)
query = user.SearchUsersQuery{Query: "", Page: 1, Limit: 3, SignedInUser: usr, SortOpts: sortOpts}
queryResult, err = userStore.Search(context.Background(), &query)
require.Nil(t, err)
require.Len(t, queryResult.Users, 3)
require.EqualValues(t, queryResult.TotalCount, 5)
for i := 0; i < 3; i++ {
require.Equal(t, fmt.Sprint("loginuser", i), queryResult.Users[i].Login)
}
sortOpts2, err := sortopts.ParseSortQueryParam("login-desc,email-asc")
require.NoError(t, err)
query = user.SearchUsersQuery{Query: "", Page: 1, Limit: 3, SignedInUser: usr, SortOpts: sortOpts2}
queryResult, err = userStore.Search(context.Background(), &query)
require.Nil(t, err)
require.Len(t, queryResult.Users, 3)
require.EqualValues(t, queryResult.TotalCount, 5)
for i := 0; i < 3; i++ {
require.Equal(t, fmt.Sprint("loginuser", 4-i), queryResult.Users[i].Login)
}
})
t.Run("Can get logged in user projection", func(t *testing.T) {
query := user.GetSignedInUserQuery{UserID: 2}
queryResult, err := userStore.GetSignedInUser(context.Background(), &query)
require.NoError(t, err)
assert.Equal(t, queryResult.Email, "user1@test.com")
assert.EqualValues(t, queryResult.OrgID, 2)
assert.Equal(t, queryResult.Name, "user1")
assert.Equal(t, queryResult.Login, "loginuser1")
assert.EqualValues(t, queryResult.OrgRole, "Admin")
assert.Equal(t, queryResult.OrgName, "user1@test.com")
assert.Equal(t, queryResult.IsGrafanaAdmin, false)
})
}
func TestIntegrationUserUpdate(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test")
}
ss := db.InitTestDB(t)
userStore := ProvideStore(ss, setting.NewCfg())
_, usrSvc := createOrgAndUserSvc(t, ss, ss.Cfg)
users := createFiveTestUsers(t, usrSvc, func(i int) *user.CreateUserCommand {
return &user.CreateUserCommand{
Email: fmt.Sprint("USER", i, "@test.com"),
Name: fmt.Sprint("USER", i),
Login: fmt.Sprint("loginUSER", i),
IsDisabled: false,
}
})
userStore.cfg.CaseInsensitiveLogin = true
t.Run("Testing DB - update generates duplicate user", func(t *testing.T) {
err := userStore.Update(context.Background(), &user.UpdateUserCommand{
Login: "loginuser2",
UserID: users[0].ID,
})
require.Error(t, err)
})
t.Run("Testing DB - update lowercases existing user", func(t *testing.T) {
err := userStore.Update(context.Background(), &user.UpdateUserCommand{
Login: "loginUSER0",
Email: "USER0@test.com",
UserID: users[0].ID,
})
require.NoError(t, err)
result, err := userStore.GetByID(context.Background(), users[0].ID)
require.NoError(t, err)
require.Equal(t, "loginuser0", result.Login)
require.Equal(t, "user0@test.com", result.Email)
})
t.Run("Testing DB - no user info provided", func(t *testing.T) {
err := userStore.Update(context.Background(), &user.UpdateUserCommand{
Login: "",
Email: "",
Name: "Change Name",
UserID: users[3].ID,
})
require.NoError(t, err)
// query := user.GetUserByIDQuery{ID: users[3].ID}
result, err := userStore.GetByID(context.Background(), users[3].ID)
require.NoError(t, err)
// Changed
require.Equal(t, "Change Name", result.Name)
// Unchanged
require.Equal(t, "loginUSER3", result.Login)
require.Equal(t, "USER3@test.com", result.Email)
})
ss.Cfg.CaseInsensitiveLogin = false
}
func createFiveTestUsers(t *testing.T, svc user.Service, fn func(i int) *user.CreateUserCommand) []user.User {
t.Helper()
users := make([]user.User, 5)
for i := 0; i < 5; i++ {
cmd := fn(i)
user, err := svc.Create(context.Background(), cmd)
require.Nil(t, err)
users[i] = *user
}
return users
}
// TODO: Use FakeDashboardStore when org has its own service
func updateDashboardACL(t *testing.T, sqlStore db.DB, dashboardID int64, items ...*dashboards.DashboardACL) error {
t.Helper()
err := sqlStore.WithDbSession(context.Background(), func(sess *db.Session) error {
_, err := sess.Exec("DELETE FROM dashboard_acl WHERE dashboard_id=?", dashboardID)
if err != nil {
return fmt.Errorf("deleting from dashboard_acl failed: %w", err)
}
for _, item := range items {
item.Created = time.Now()
item.Updated = time.Now()
if item.UserID == 0 && item.TeamID == 0 && (item.Role == nil || !item.Role.IsValid()) {
return dashboards.ErrDashboardACLInfoMissing
}
if item.DashboardID == 0 {
return dashboards.ErrDashboardPermissionDashboardEmpty
}
sess.Nullable("user_id", "team_id")
if _, err := sess.Insert(item); err != nil {
return err
}
}
// Update dashboard HasACL flag
dashboard := dashboards.Dashboard{HasACL: true}
_, err = sess.Cols("has_acl").Where("id=?", dashboardID).Update(&dashboard)
return err
})
return err
}
func TestMetricsUsage(t *testing.T) {
ss := db.InitTestDB(t)
userStore := ProvideStore(ss, setting.NewCfg())
quotaService := quotaimpl.ProvideService(ss, ss.Cfg)
orgService, err := orgimpl.ProvideService(ss, ss.Cfg, quotaService)
require.NoError(t, err)
_, usrSvc := createOrgAndUserSvc(t, ss, ss.Cfg)
t.Run("Get empty role metrics for an org", func(t *testing.T) {
orgId := int64(1)
// create first user
createFirtUserCmd := &user.CreateUserCommand{
Login: "admin",
Email: "admin@admin.com",
Name: "admin",
OrgID: orgId,
}
_, err := usrSvc.Create(context.Background(), createFirtUserCmd)
require.NoError(t, err)
// create second user
createSecondUserCmd := &user.CreateUserCommand{
Login: "userWithoutRole",
Email: "userWithoutRole@userWithoutRole.com",
Name: "userWithoutRole",
}
secondUser, err := usrSvc.Create(context.Background(), createSecondUserCmd)
require.NoError(t, err)
// assign the user to the org
cmd := org.AddOrgUserCommand{
OrgID: secondUser.OrgID,
UserID: orgId,
Role: org.RoleNone,
}
err = orgService.AddOrgUser(context.Background(), &cmd)
require.NoError(t, err)
// get metric usage
stats, err := userStore.CountUserAccountsWithEmptyRole(context.Background())
require.NoError(t, err)
assert.Equal(t, int64(1), stats)
})
}
// This function was copied from pkg/services/dashboards/database to circumvent
// import cycles. When this org-related code is refactored into a service the
// tests can the real GetDashboardACLInfoList functions
func (ss *sqlStore) getDashboardACLInfoList(query *dashboards.GetDashboardACLInfoListQuery) ([]*dashboards.DashboardACLInfoDTO, error) {
queryResult := make([]*dashboards.DashboardACLInfoDTO, 0)
outerErr := ss.db.WithDbSession(context.Background(), func(dbSession *db.Session) error {
falseStr := ss.dialect.BooleanStr(false)
if query.DashboardID == 0 {
sql := `SELECT
da.id,
da.org_id,
da.dashboard_id,
da.user_id,
da.team_id,
da.permission,
da.role,
da.created,
da.updated,
'' as user_login,
'' as user_email,
'' as team,
'' as title,
'' as slug,
'' as uid,` +
falseStr + ` AS is_folder,` +
falseStr + ` AS inherited
FROM dashboard_acl as da
WHERE da.dashboard_id = -1`
return dbSession.SQL(sql).Find(&queryResult)
}
rawSQL := `
-- get permissions for the dashboard and its parent folder
SELECT
da.id,
da.org_id,
da.dashboard_id,
da.user_id,
da.team_id,
da.permission,
da.role,
da.created,
da.updated,
u.login AS user_login,
u.email AS user_email,
ug.name AS team,
ug.email AS team_email,
d.title,
d.slug,
d.uid,
d.is_folder,
CASE WHEN (da.dashboard_id = -1 AND d.folder_id > 0) OR da.dashboard_id = d.folder_id THEN ` + ss.dialect.BooleanStr(true) + ` ELSE ` + falseStr + ` END AS inherited
FROM dashboard as d
LEFT JOIN dashboard folder on folder.id = d.folder_id
LEFT JOIN dashboard_acl AS da ON
da.dashboard_id = d.id OR
da.dashboard_id = d.folder_id OR
(
-- include default permissions -->
da.org_id = -1 AND (
(folder.id IS NOT NULL AND folder.has_acl = ` + falseStr + `) OR
(folder.id IS NULL AND d.has_acl = ` + falseStr + `)
)
)
LEFT JOIN ` + ss.dialect.Quote("user") + ` AS u ON u.id = da.user_id
LEFT JOIN team ug on ug.id = da.team_id
WHERE d.org_id = ? AND d.id = ? AND da.id IS NOT NULL
ORDER BY da.id ASC
`
return dbSession.SQL(rawSQL, query.OrgID, query.DashboardID).Find(&queryResult)
})
if outerErr != nil {
return nil, outerErr
}
for _, p := range queryResult {
p.PermissionName = p.Permission.String()
}
return queryResult, nil
}
func createOrgAndUserSvc(t *testing.T, store db.DB, cfg *setting.Cfg) (org.Service, user.Service) {
t.Helper()
quotaService := quotaimpl.ProvideService(store, cfg)
orgService, err := orgimpl.ProvideService(store, cfg, quotaService)
require.NoError(t, err)
usrSvc, err := ProvideService(store, orgService, cfg, nil, nil, quotaService, supportbundlestest.NewFakeBundleService())
require.NoError(t, err)
return orgService, usrSvc
}