Chore: Split get user by ID (#52442)

* Remove user from preferences, stars, orguser, team member

* Fix lint

* Add Delete user from org and dashboard acl

* Delete user from user auth

* Add DeleteUser to quota

* Add test files and adjust user auth store

* Rename package in wire for user auth

* Import Quota Service interface in other services

* do the same in tests

* fix lint tests

* Fix tests

* Add some tests

* Rename InsertUser and DeleteUser to InsertOrgUser and DeleteOrgUser

* Rename DeleteUser to DeleteByUser in quota

* changing a method name in few additional places

* Fix in other places

* Fix lint

* Fix tests

* Chore: Split Delete User method

* Add fakes for userauth

* Add mock for access control Delete User permossion, use interface

* Use interface for ream guardian

* Add simple fake for dashboard acl

* Add go routines, clean up, use interfaces

* fix lint

* Update pkg/services/user/userimpl/user_test.go

Co-authored-by: Sofia Papagiannaki <1632407+papagian@users.noreply.github.com>

* Update pkg/services/user/userimpl/user_test.go

Co-authored-by: Sofia Papagiannaki <1632407+papagian@users.noreply.github.com>

* Update pkg/services/user/userimpl/user_test.go

Co-authored-by: Sofia Papagiannaki <1632407+papagian@users.noreply.github.com>

* Split get user by ID

* Use new method in api

* Add tests

* Aplly emthod in auth info service

* Fix lint and some tests

* Fix get user by ID

* Fix lint
Remove unused fakes

* Use split get user id in admin users

* Use GetbyID in cli commands

* Clean up after merge

* Remove commented out code

* Clena up imports

* add back )

* Fix wire generation for runner after merge with main

Co-authored-by: Sofia Papagiannaki <1632407+papagian@users.noreply.github.com>
This commit is contained in:
idafurjes
2022-08-02 16:58:05 +02:00
committed by GitHub
co-authored by Sofia Papagiannaki
parent 64488f6b90
commit fab6c38c95
28 changed files with 1182 additions and 1522 deletions
@@ -7,6 +7,7 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/login"
"github.com/grafana/grafana/pkg/services/secrets"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/user"
@@ -18,13 +19,15 @@ type AuthInfoStore struct {
sqlStore sqlstore.Store
secretsService secrets.Service
logger log.Logger
userService user.Service
}
func ProvideAuthInfoStore(sqlStore sqlstore.Store, secretsService secrets.Service) *AuthInfoStore {
func ProvideAuthInfoStore(sqlStore sqlstore.Store, secretsService secrets.Service, userService user.Service) login.Store {
store := &AuthInfoStore{
sqlStore: sqlStore,
secretsService: secretsService,
logger: log.New("login.authinfo.store"),
userService: userService,
}
InitMetrics()
return store
@@ -221,12 +224,13 @@ func (s *AuthInfoStore) DeleteAuthInfo(ctx context.Context, cmd *models.DeleteAu
}
func (s *AuthInfoStore) GetUserById(ctx context.Context, id int64) (*user.User, error) {
query := models.GetUserByIdQuery{Id: id}
if err := s.sqlStore.GetUserById(ctx, &query); err != nil {
query := user.GetUserByIDQuery{ID: id}
user, err := s.userService.GetByID(ctx, &query)
if err != nil {
return nil, err
}
return query.Result, nil
return user, nil
}
func (s *AuthInfoStore) GetUserByLogin(ctx context.Context, login string) (*user.User, error) {
@@ -2,62 +2,38 @@ package database
import (
"context"
"sync"
"time"
"github.com/grafana/grafana/pkg/services/login"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/sqlstore/db"
"github.com/prometheus/client_golang/prometheus"
)
type LoginStats struct {
DuplicateUserEntries int `xorm:"duplicate_user_entries"`
MixedCasedUsers int `xorm:"mixed_cased_users"`
}
const (
ExporterName = "grafana"
metricsCollectionInterval = time.Second * 60 * 4 // every 4 hours, indication of duplicate users
)
var (
// MStatDuplicateUserEntries is a indication metric gauge for number of users with duplicate emails or logins
MStatDuplicateUserEntries prometheus.Gauge
// MStatHasDuplicateEntries is a metric for if there is duplicate users
MStatHasDuplicateEntries prometheus.Gauge
// MStatMixedCasedUsers is a metric for if there is duplicate users
MStatMixedCasedUsers prometheus.Gauge
once sync.Once
Initialised bool = false
)
func InitMetrics() {
once.Do(func() {
MStatDuplicateUserEntries = prometheus.NewGauge(prometheus.GaugeOpts{
login.Once.Do(func() {
login.MStatDuplicateUserEntries = prometheus.NewGauge(prometheus.GaugeOpts{
Name: "stat_users_total_duplicate_user_entries",
Help: "total number of duplicate user entries by email or login",
Namespace: ExporterName,
Namespace: login.ExporterName,
})
MStatHasDuplicateEntries = prometheus.NewGauge(prometheus.GaugeOpts{
login.MStatHasDuplicateEntries = prometheus.NewGauge(prometheus.GaugeOpts{
Name: "stat_users_has_duplicate_user_entries",
Help: "instance has duplicate user entries by email or login",
Namespace: ExporterName,
Namespace: login.ExporterName,
})
MStatMixedCasedUsers = prometheus.NewGauge(prometheus.GaugeOpts{
login.MStatMixedCasedUsers = prometheus.NewGauge(prometheus.GaugeOpts{
Name: "stat_users_total_mixed_cased_users",
Help: "total number of users with upper and lower case logins or emails",
Namespace: ExporterName,
Namespace: login.ExporterName,
})
prometheus.MustRegister(
MStatDuplicateUserEntries,
MStatHasDuplicateEntries,
MStatMixedCasedUsers,
login.MStatDuplicateUserEntries,
login.MStatHasDuplicateEntries,
login.MStatMixedCasedUsers,
)
})
}
@@ -66,7 +42,7 @@ func (s *AuthInfoStore) RunMetricsCollection(ctx context.Context) error {
if _, err := s.GetLoginStats(ctx); err != nil {
s.logger.Warn("Failed to get authinfo metrics", "error", err.Error())
}
updateStatsTicker := time.NewTicker(metricsCollectionInterval)
updateStatsTicker := time.NewTicker(login.MetricsCollectionInterval)
defer updateStatsTicker.Stop()
for {
@@ -81,8 +57,8 @@ func (s *AuthInfoStore) RunMetricsCollection(ctx context.Context) error {
}
}
func (s *AuthInfoStore) GetLoginStats(ctx context.Context) (LoginStats, error) {
var stats LoginStats
func (s *AuthInfoStore) GetLoginStats(ctx context.Context) (login.LoginStats, error) {
var stats login.LoginStats
outerErr := s.sqlStore.WithDbSession(ctx, func(dbSession *sqlstore.DBSession) error {
rawSQL := `SELECT
(SELECT COUNT(*) FROM (` + s.duplicateUserEntriesSQL(ctx) + `) AS d WHERE (d.dup_login IS NOT NULL OR d.dup_email IS NOT NULL)) as duplicate_user_entries,
@@ -96,14 +72,14 @@ func (s *AuthInfoStore) GetLoginStats(ctx context.Context) (LoginStats, error) {
}
// set prometheus metrics stats
MStatDuplicateUserEntries.Set(float64(stats.DuplicateUserEntries))
login.MStatDuplicateUserEntries.Set(float64(stats.DuplicateUserEntries))
if stats.DuplicateUserEntries == 0 {
MStatHasDuplicateEntries.Set(float64(0))
login.MStatHasDuplicateEntries.Set(float64(0))
} else {
MStatHasDuplicateEntries.Set(float64(1))
login.MStatHasDuplicateEntries.Set(float64(1))
}
MStatMixedCasedUsers.Set(float64(stats.MixedCasedUsers))
login.MStatMixedCasedUsers.Set(float64(stats.MixedCasedUsers))
return stats, nil
}
@@ -115,14 +91,12 @@ func (s *AuthInfoStore) CollectLoginStats(ctx context.Context) (map[string]inter
s.logger.Error("Failed to get login stats", "error", err)
return nil, err
}
m["stats.users.duplicate_user_entries"] = loginStats.DuplicateUserEntries
if loginStats.DuplicateUserEntries > 0 {
m["stats.users.has_duplicate_user_entries"] = 1
} else {
m["stats.users.has_duplicate_user_entries"] = 0
}
m["stats.users.mixed_cased_users"] = loginStats.MixedCasedUsers
return m, nil
@@ -2,26 +2,26 @@ package authinfoservice
import (
"context"
"errors"
"fmt"
"testing"
"time"
"github.com/grafana/grafana/pkg/infra/usagestats"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/login/authinfoservice/database"
secretstore "github.com/grafana/grafana/pkg/services/secrets/database"
secretsManager "github.com/grafana/grafana/pkg/services/secrets/manager"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/user"
"github.com/stretchr/testify/require"
"golang.org/x/oauth2"
"github.com/grafana/grafana/pkg/infra/usagestats"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/login"
"github.com/grafana/grafana/pkg/services/login/authinfoservice/database"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/user"
)
//nolint:goconst
func TestUserAuth(t *testing.T) {
sqlStore := sqlstore.InitTestDB(t)
secretsService := secretsManager.SetupTestService(t, secretstore.ProvideSecretsStore(sqlStore))
authInfoStore := database.ProvideAuthInfoStore(sqlStore, secretsService)
authInfoStore := newFakeAuthInfoStore()
srv := ProvideAuthInfoService(
&OSSUserProtectionImpl{},
authInfoStore,
@@ -42,7 +42,11 @@ func TestUserAuth(t *testing.T) {
t.Run("Can find existing user", func(t *testing.T) {
// By Login
login := "loginuser0"
authInfoStore.ExpectedUser = &user.User{
Login: "loginuser0",
ID: 1,
Email: "user1@test.com",
}
query := &models.GetUserByAuthInfoQuery{UserLookupParams: models.UserLookupParams{Login: &login}}
usr, err := srv.LookupAndUpdate(context.Background(), query)
@@ -69,6 +73,7 @@ func TestUserAuth(t *testing.T) {
require.Nil(t, err)
require.Equal(t, usr.Email, email)
authInfoStore.ExpectedUser = nil
// Don't find nonexistent user
email = "nonexistent@test.com"
@@ -82,6 +87,8 @@ func TestUserAuth(t *testing.T) {
t.Run("Can set & locate by AuthModule and AuthId", func(t *testing.T) {
// get nonexistent user_auth entry
authInfoStore.ExpectedUser = &user.User{}
authInfoStore.ExpectedError = user.ErrUserNotFound
query := &models.GetUserByAuthInfoQuery{AuthModule: "test", AuthId: "test"}
usr, err := srv.LookupAndUpdate(context.Background(), query)
@@ -90,7 +97,9 @@ func TestUserAuth(t *testing.T) {
// create user_auth entry
login := "loginuser0"
authInfoStore.ExpectedUser = &user.User{Login: "loginuser0", ID: 1, Email: ""}
authInfoStore.ExpectedError = nil
authInfoStore.ExpectedOAuth = &models.UserAuth{Id: 1}
query.UserLookupParams.Login = &login
usr, err = srv.LookupAndUpdate(context.Background(), query)
@@ -107,6 +116,7 @@ func TestUserAuth(t *testing.T) {
// get with non-matching id
idPlusOne := usr.ID + 1
authInfoStore.ExpectedUser.Login = "loginuser1"
query.UserLookupParams.UserID = &idPlusOne
usr, err = srv.LookupAndUpdate(context.Background(), query)
@@ -127,6 +137,8 @@ func TestUserAuth(t *testing.T) {
})
require.NoError(t, err)
authInfoStore.ExpectedUser = nil
authInfoStore.ExpectedError = user.ErrUserNotFound
// get via user_auth for deleted user
query = &models.GetUserByAuthInfoQuery{AuthModule: "test", AuthId: "test"}
usr, err = srv.LookupAndUpdate(context.Background(), query)
@@ -147,7 +159,16 @@ func TestUserAuth(t *testing.T) {
// Find a user to set tokens on
login := "loginuser0"
authInfoStore.ExpectedUser = &user.User{Login: "loginuser0", ID: 1, Email: ""}
authInfoStore.ExpectedError = nil
authInfoStore.ExpectedOAuth = &models.UserAuth{
Id: 1,
OAuthAccessToken: token.AccessToken,
OAuthRefreshToken: token.RefreshToken,
OAuthTokenType: token.TokenType,
OAuthIdToken: idToken,
OAuthExpiry: token.Expiry,
}
// Calling GetUserByAuthInfoQuery on an existing user will populate an entry in the user_auth table
query := &models.GetUserByAuthInfoQuery{AuthModule: "test", AuthId: "test", UserLookupParams: models.UserLookupParams{
Login: &login,
@@ -220,7 +241,7 @@ func TestUserAuth(t *testing.T) {
require.Nil(t, err)
require.Equal(t, user.Login, login)
authInfoStore.ExpectedOAuth.AuthModule = "test2"
// Get the latest entry by not supply an authmodule or authid
getAuthQuery := &models.GetAuthInfoQuery{
UserId: user.ID,
@@ -236,7 +257,7 @@ func TestUserAuth(t *testing.T) {
err = authInfoStore.UpdateAuthInfo(context.Background(), updateAuthCmd)
require.Nil(t, err)
authInfoStore.ExpectedOAuth.AuthModule = "test1"
// Get the latest entry by not supply an authmodule or authid
getAuthQuery = &models.GetAuthInfoQuery{
UserId: user.ID,
@@ -292,6 +313,7 @@ func TestUserAuth(t *testing.T) {
getAuthQuery := &models.GetAuthInfoQuery{
UserId: user.ID,
}
authInfoStore.ExpectedOAuth.AuthModule = "test2"
err = authInfoStore.GetAuthInfo(context.Background(), getAuthQuery)
@@ -314,7 +336,8 @@ func TestUserAuth(t *testing.T) {
require.Nil(t, err)
require.Equal(t, user.Login, login)
authInfoStore.ExpectedOAuth.AuthModule = "test1"
authInfoStore.ExpectedOAuth.OAuthAccessToken = "access_token"
err = authInfoStore.GetAuthInfo(context.Background(), getAuthQuery)
require.Nil(t, err)
@@ -327,6 +350,7 @@ func TestUserAuth(t *testing.T) {
user, err = srv.LookupAndUpdate(context.Background(), queryTwo)
require.Nil(t, err)
require.Equal(t, user.Login, login)
authInfoStore.ExpectedOAuth.AuthModule = "test2"
err = authInfoStore.GetAuthInfo(context.Background(), getAuthQuery)
require.Nil(t, err)
@@ -337,10 +361,11 @@ func TestUserAuth(t *testing.T) {
UserId: user.ID,
AuthModule: "test1",
}
authInfoStore.ExpectedOAuth.AuthModule = "test1"
err = authInfoStore.GetAuthInfo(context.Background(), getAuthQueryUnchanged)
require.Nil(t, err)
require.Equal(t, "test1", getAuthQueryUnchanged.Result.AuthModule)
require.Less(t, getAuthQueryUnchanged.Result.Created, getAuthQuery.Result.Created)
})
t.Run("Can set & locate by generic oauth auth module and user id", func(t *testing.T) {
@@ -364,11 +389,14 @@ func TestUserAuth(t *testing.T) {
query = &models.GetUserByAuthInfoQuery{AuthModule: genericOAuthModule, AuthId: "", UserLookupParams: models.UserLookupParams{
Login: &otherLoginUser,
}}
authInfoStore.ExpectedError = errors.New("some error")
user, err = srv.LookupAndUpdate(context.Background(), query)
database.GetTime = time.Now
require.NotNil(t, err)
require.Nil(t, user)
authInfoStore.ExpectedError = nil
})
t.Run("should be able to run loginstats query in all dbs", func(t *testing.T) {
@@ -426,8 +454,18 @@ func TestUserAuth(t *testing.T) {
}
_, err = sqlStore.CreateUser(context.Background(), dupUserLogincmd)
require.NoError(t, err)
// require stats to populate
authInfoStore.ExpectedUser = &user.User{
Email: "userduplicatetest1@test.com",
Name: "user name 1",
Login: "user_duplicate_test_1_login",
}
authInfoStore.ExpectedDuplicateUserEntries = 2
authInfoStore.ExpectedHasDuplicateUserEntries = 1
authInfoStore.ExpectedLoginStats = login.LoginStats{
DuplicateUserEntries: 2,
MixedCasedUsers: 1,
}
// require metrics and statistics to be 2
m, err := srv.authInfoStore.CollectLoginStats(context.Background())
require.NoError(t, err)
require.Equal(t, 2, m["stats.users.duplicate_user_entries"])
@@ -438,3 +476,67 @@ func TestUserAuth(t *testing.T) {
})
})
}
type FakeAuthInfoStore struct {
ExpectedError error
ExpectedUser *user.User
ExpectedOAuth *models.UserAuth
ExpectedDuplicateUserEntries int
ExpectedHasDuplicateUserEntries int
ExpectedLoginStats login.LoginStats
}
func newFakeAuthInfoStore() *FakeAuthInfoStore {
return &FakeAuthInfoStore{}
}
func (f *FakeAuthInfoStore) GetExternalUserInfoByLogin(ctx context.Context, query *models.GetExternalUserInfoByLoginQuery) error {
return f.ExpectedError
}
func (f *FakeAuthInfoStore) GetAuthInfo(ctx context.Context, query *models.GetAuthInfoQuery) error {
query.Result = f.ExpectedOAuth
return f.ExpectedError
}
func (f *FakeAuthInfoStore) SetAuthInfo(ctx context.Context, cmd *models.SetAuthInfoCommand) error {
return f.ExpectedError
}
func (f *FakeAuthInfoStore) UpdateAuthInfoDate(ctx context.Context, authInfo *models.UserAuth) error {
return f.ExpectedError
}
func (f *FakeAuthInfoStore) UpdateAuthInfo(ctx context.Context, cmd *models.UpdateAuthInfoCommand) error {
return f.ExpectedError
}
func (f *FakeAuthInfoStore) DeleteAuthInfo(ctx context.Context, cmd *models.DeleteAuthInfoCommand) error {
return f.ExpectedError
}
func (f *FakeAuthInfoStore) GetUserById(ctx context.Context, id int64) (*user.User, error) {
return f.ExpectedUser, f.ExpectedError
}
func (f *FakeAuthInfoStore) GetUserByLogin(ctx context.Context, login string) (*user.User, error) {
return f.ExpectedUser, f.ExpectedError
}
func (f *FakeAuthInfoStore) GetUserByEmail(ctx context.Context, email string) (*user.User, error) {
return f.ExpectedUser, f.ExpectedError
}
func (f *FakeAuthInfoStore) CollectLoginStats(ctx context.Context) (map[string]interface{}, error) {
var res = make(map[string]interface{})
res["stats.users.duplicate_user_entries"] = f.ExpectedDuplicateUserEntries
res["stats.users.has_duplicate_user_entries"] = f.ExpectedHasDuplicateUserEntries
res["stats.users.duplicate_user_entries_by_login"] = 0
res["stats.users.has_duplicate_user_entries_by_login"] = 0
res["stats.users.duplicate_user_entries_by_email"] = 0
res["stats.users.has_duplicate_user_entries_by_email"] = 0
res["stats.users.mixed_cased_users"] = f.ExpectedLoginStats.MixedCasedUsers
return res, f.ExpectedError
}
func (f *FakeAuthInfoStore) RunMetricsCollection(ctx context.Context) error {
return f.ExpectedError
}
func (f *FakeAuthInfoStore) GetLoginStats(ctx context.Context) (login.LoginStats, error) {
return f.ExpectedLoginStats, f.ExpectedError
}
+32
View File
@@ -0,0 +1,32 @@
package login
import (
"sync"
"time"
"github.com/prometheus/client_golang/prometheus"
)
type LoginStats struct {
DuplicateUserEntries int `xorm:"duplicate_user_entries"`
MixedCasedUsers int `xorm:"mixed_cased_users"`
}
const (
ExporterName = "grafana"
MetricsCollectionInterval = time.Second * 60 * 4 // every 4 hours, indication of duplicate users
)
var (
// MStatDuplicateUserEntries is a indication metric gauge for number of users with duplicate emails or logins
MStatDuplicateUserEntries prometheus.Gauge
// MStatHasDuplicateEntries is a metric for if there is duplicate users
MStatHasDuplicateEntries prometheus.Gauge
// MStatMixedCasedUsers is a metric for if there is duplicate users
MStatMixedCasedUsers prometheus.Gauge
Once sync.Once
Initialised bool = false
)
+1 -2
View File
@@ -4,7 +4,6 @@ import (
"context"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/login/authinfoservice/database"
"github.com/grafana/grafana/pkg/services/user"
)
@@ -24,5 +23,5 @@ type Store interface {
GetUserByEmail(ctx context.Context, email string) (*user.User, error)
CollectLoginStats(ctx context.Context) (map[string]interface{}, error)
RunMetricsCollection(ctx context.Context) error
GetLoginStats(ctx context.Context) (database.LoginStats, error)
GetLoginStats(ctx context.Context) (LoginStats, error)
}
+27
View File
@@ -2,6 +2,8 @@ package user
import (
"errors"
"fmt"
"strings"
"time"
)
@@ -69,3 +71,28 @@ func (u *User) NameOrFallback() string {
type DeleteUserCommand struct {
UserID int64
}
type GetUserByIDQuery struct {
ID int64
}
type ErrCaseInsensitiveLoginConflict struct {
Users []User
}
func (e *ErrCaseInsensitiveLoginConflict) Unwrap() error {
return ErrCaseInsensitive
}
func (e *ErrCaseInsensitiveLoginConflict) Error() string {
n := len(e.Users)
userStrings := make([]string, 0, n)
for _, v := range e.Users {
userStrings = append(userStrings, fmt.Sprintf("%s (email:%s, id:%d)", v.Login, v.Email, v.ID))
}
return fmt.Sprintf(
"Found a conflict in user login information. %d users already exist with either the same login or email: [%s].",
n, strings.Join(userStrings, ", "))
}
+1
View File
@@ -7,4 +7,5 @@ import (
type Service interface {
Create(context.Context, *CreateUserCommand) (*User, error)
Delete(context.Context, *DeleteUserCommand) error
GetByID(context.Context, *GetUserByIDQuery) (*User, error)
}
+36
View File
@@ -14,8 +14,10 @@ import (
type store interface {
Insert(context.Context, *user.User) (int64, error)
Get(context.Context, *user.User) (*user.User, error)
GetByID(context.Context, int64) (*user.User, error)
GetNotServiceAccount(context.Context, int64) (*user.User, error)
Delete(context.Context, int64) error
CaseInsensitiveLoginConflict(context.Context, string, string) error
}
type sqlStore struct {
@@ -91,8 +93,42 @@ func (ss *sqlStore) GetNotServiceAccount(ctx context.Context, userID int64) (*us
return &usr, err
}
func (ss *sqlStore) GetByID(ctx context.Context, userID int64) (*user.User, error) {
var usr user.User
err := ss.db.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
has, err := sess.ID(&userID).
Where(ss.notServiceAccountFilter()).
Get(&usr)
if err != nil {
return err
} else if !has {
return user.ErrUserNotFound
}
return nil
})
return &usr, err
}
func (ss *sqlStore) notServiceAccountFilter() string {
return fmt.Sprintf("%s.is_service_account = %s",
ss.dialect.Quote("user"),
ss.dialect.BooleanStr(false))
}
func (ss *sqlStore) CaseInsensitiveLoginConflict(ctx context.Context, login, email string) error {
users := make([]user.User, 0)
err := ss.db.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
if err := sess.Where("LOWER(email)=LOWER(?) OR LOWER(login)=LOWER(?)",
email, login).Find(&users); err != nil {
return err
}
if len(users) > 1 {
return &user.ErrCaseInsensitiveLoginConflict{Users: users}
}
return nil
})
return err
}
+18 -2
View File
@@ -3,7 +3,6 @@ package userimpl
import (
"context"
"errors"
"fmt"
"time"
"github.com/grafana/grafana/pkg/services/accesscontrol"
@@ -32,6 +31,8 @@ type Service struct {
userAuthService userauth.Service
quotaService quota.Service
accessControlStore accesscontrol.AccessControl
cfg *setting.Cfg
}
func ProvideService(
@@ -44,6 +45,7 @@ func ProvideService(
userAuthService userauth.Service,
quotaService quota.Service,
accessControlStore accesscontrol.AccessControl,
cfg *setting.Cfg,
) user.Service {
return &Service{
store: &sqlStore{
@@ -58,6 +60,7 @@ func ProvideService(
userAuthService: userAuthService,
quotaService: quotaService,
accessControlStore: accessControlStore,
cfg: cfg,
}
}
@@ -157,7 +160,7 @@ func (s *Service) Create(ctx context.Context, cmd *user.CreateUserCommand) (*use
func (s *Service) Delete(ctx context.Context, cmd *user.DeleteUserCommand) error {
_, err := s.store.GetNotServiceAccount(ctx, cmd.UserID)
if err != nil {
return fmt.Errorf("failed to get user with not service account: %w", err)
return err
}
// delete from all the stores
if err := s.store.Delete(ctx, cmd.UserID); err != nil {
@@ -225,3 +228,16 @@ func (s *Service) Delete(ctx context.Context, cmd *user.DeleteUserCommand) error
return nil
}
func (s *Service) GetByID(ctx context.Context, query *user.GetUserByIDQuery) (*user.User, error) {
user, err := s.store.GetByID(ctx, query.ID)
if err != nil {
return nil, err
}
if s.cfg.CaseInsensitiveLogin {
if err := s.store.CaseInsensitiveLoginConflict(ctx, user.Login, user.Email); err != nil {
return nil, err
}
}
return user, nil
}
+71 -1
View File
@@ -14,6 +14,8 @@ import (
"github.com/grafana/grafana/pkg/services/teamguardian/manager"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/services/userauth/userauthtest"
"github.com/grafana/grafana/pkg/setting"
"github.com/stretchr/testify/require"
)
@@ -40,7 +42,67 @@ func TestUserService(t *testing.T) {
}
t.Run("create user", func(t *testing.T) {
_, err := userService.Create(context.Background(), &user.CreateUserCommand{})
_, err := userService.Create(context.Background(), &user.CreateUserCommand{
Email: "email",
Login: "login",
Name: "name",
})
require.NoError(t, err)
})
t.Run("get user by ID", func(t *testing.T) {
userService.cfg = setting.NewCfg()
userService.cfg.CaseInsensitiveLogin = false
userStore.ExpectedUser = &user.User{ID: 1, Email: "email", Login: "login", Name: "name"}
u, err := userService.GetByID(context.Background(), &user.GetUserByIDQuery{ID: 1})
require.NoError(t, err)
require.Equal(t, "login", u.Login)
require.Equal(t, "name", u.Name)
require.Equal(t, "email", u.Email)
})
t.Run("get user by ID with case insensitive login", func(t *testing.T) {
userService.cfg = setting.NewCfg()
userService.cfg.CaseInsensitiveLogin = true
userStore.ExpectedUser = &user.User{ID: 1, Email: "email", Login: "login", Name: "name"}
u, err := userService.GetByID(context.Background(), &user.GetUserByIDQuery{ID: 1})
require.NoError(t, err)
require.Equal(t, "login", u.Login)
require.Equal(t, "name", u.Name)
require.Equal(t, "email", u.Email)
})
t.Run("delete user store returns error", func(t *testing.T) {
userStore.ExpectedDeleteUserError = user.ErrUserNotFound
t.Cleanup(func() {
userStore.ExpectedDeleteUserError = nil
})
err := userService.Delete(context.Background(), &user.DeleteUserCommand{UserID: 1})
require.Error(t, err, user.ErrUserNotFound)
})
t.Run("delete user returns from team", func(t *testing.T) {
teamMemberService.ExpectedError = errors.New("some error")
t.Cleanup(func() {
teamMemberService.ExpectedError = nil
})
err := userService.Delete(context.Background(), &user.DeleteUserCommand{UserID: 1})
require.Error(t, err)
})
t.Run("delete user returns from team and pref", func(t *testing.T) {
teamMemberService.ExpectedError = errors.New("some error")
preferenceService.ExpectedError = errors.New("some error 2")
t.Cleanup(func() {
teamMemberService.ExpectedError = nil
preferenceService.ExpectedError = nil
})
err := userService.Delete(context.Background(), &user.DeleteUserCommand{UserID: 1})
require.Error(t, err)
})
t.Run("delete user successfully", func(t *testing.T) {
err := userService.Delete(context.Background(), &user.DeleteUserCommand{UserID: 1})
require.NoError(t, err)
})
@@ -104,3 +166,11 @@ func (f *FakeUserStore) Delete(ctx context.Context, userID int64) error {
func (f *FakeUserStore) GetNotServiceAccount(ctx context.Context, userID int64) (*user.User, error) {
return f.ExpectedUser, f.ExpectedError
}
func (f *FakeUserStore) GetByID(context.Context, int64) (*user.User, error) {
return f.ExpectedUser, f.ExpectedError
}
func (f *FakeUserStore) CaseInsensitiveLoginConflict(context.Context, string, string) error {
return f.ExpectedError
}
+4
View File
@@ -22,3 +22,7 @@ func (f *FakeUserService) Create(ctx context.Context, cmd *user.CreateUserComman
func (f *FakeUserService) Delete(ctx context.Context, cmd *user.DeleteUserCommand) error {
return f.ExpectedError
}
func (f *FakeUserService) GetByID(ctx context.Context, query *user.GetUserByIDQuery) (*user.User, error) {
return f.ExpectedUser, f.ExpectedError
}