mirror of
https://github.com/grafana/grafana.git
synced 2025-07-31 16:32:15 +08:00
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:
@ -109,13 +109,14 @@ func (hs *HTTPServer) AdminUpdateUserPassword(c *models.ReqContext) response.Res
|
||||
return response.Error(400, "New password too short", nil)
|
||||
}
|
||||
|
||||
userQuery := models.GetUserByIdQuery{Id: userID}
|
||||
userQuery := user.GetUserByIDQuery{ID: userID}
|
||||
|
||||
if err := hs.SQLStore.GetUserById(c.Req.Context(), &userQuery); err != nil {
|
||||
usr, err := hs.userService.GetByID(c.Req.Context(), &userQuery)
|
||||
if err != nil {
|
||||
return response.Error(500, "Could not read user from database", err)
|
||||
}
|
||||
|
||||
passwordHashed, err := util.EncodePassword(form.Password, userQuery.Result.Salt)
|
||||
passwordHashed, err := util.EncodePassword(form.Password, usr.Salt)
|
||||
if err != nil {
|
||||
return response.Error(500, "Could not encode password", err)
|
||||
}
|
||||
|
@ -4,6 +4,9 @@ import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/dtos"
|
||||
"github.com/grafana/grafana/pkg/api/response"
|
||||
"github.com/grafana/grafana/pkg/api/routing"
|
||||
@ -15,9 +18,8 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/mockstore"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/services/user/usertest"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -29,7 +31,7 @@ const (
|
||||
|
||||
func TestAdminAPIEndpoint(t *testing.T) {
|
||||
const role = models.ROLE_ADMIN
|
||||
|
||||
userService := usertest.NewUserServiceFake()
|
||||
t.Run("Given a server admin attempts to remove themselves as an admin", func(t *testing.T) {
|
||||
updateCmd := dtos.AdminUpdateUserPermissionsForm{
|
||||
IsGrafanaAdmin: false,
|
||||
@ -45,46 +47,43 @@ func TestAdminAPIEndpoint(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("When a server admin attempts to logout himself from all devices", func(t *testing.T) {
|
||||
mock := mockstore.NewSQLStoreMock()
|
||||
adminLogoutUserScenario(t, "Should not be allowed when calling POST on",
|
||||
"/api/admin/users/1/logout", "/api/admin/users/:id/logout", func(sc *scenarioContext) {
|
||||
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
|
||||
assert.Equal(t, 400, sc.resp.Code)
|
||||
}, mock)
|
||||
}, userService)
|
||||
})
|
||||
|
||||
t.Run("When a server admin attempts to logout a non-existing user from all devices", func(t *testing.T) {
|
||||
mock := &mockstore.SQLStoreMock{
|
||||
ExpectedError: user.ErrUserNotFound,
|
||||
}
|
||||
mockUserService := usertest.NewUserServiceFake()
|
||||
mockUserService.ExpectedError = user.ErrUserNotFound
|
||||
|
||||
adminLogoutUserScenario(t, "Should return not found when calling POST on", "/api/admin/users/200/logout",
|
||||
"/api/admin/users/:id/logout", func(sc *scenarioContext) {
|
||||
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
|
||||
assert.Equal(t, 404, sc.resp.Code)
|
||||
}, mock)
|
||||
}, mockUserService)
|
||||
})
|
||||
|
||||
t.Run("When a server admin attempts to revoke an auth token for a non-existing user", func(t *testing.T) {
|
||||
cmd := models.RevokeAuthTokenCmd{AuthTokenId: 2}
|
||||
mock := &mockstore.SQLStoreMock{
|
||||
ExpectedError: user.ErrUserNotFound,
|
||||
}
|
||||
mockUser := usertest.NewUserServiceFake()
|
||||
mockUser.ExpectedError = user.ErrUserNotFound
|
||||
adminRevokeUserAuthTokenScenario(t, "Should return not found when calling POST on",
|
||||
"/api/admin/users/200/revoke-auth-token", "/api/admin/users/:id/revoke-auth-token", cmd, func(sc *scenarioContext) {
|
||||
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
|
||||
assert.Equal(t, 404, sc.resp.Code)
|
||||
}, mock)
|
||||
}, mockUser)
|
||||
})
|
||||
|
||||
t.Run("When a server admin gets auth tokens for a non-existing user", func(t *testing.T) {
|
||||
mock := &mockstore.SQLStoreMock{
|
||||
ExpectedError: user.ErrUserNotFound,
|
||||
}
|
||||
mockUserService := usertest.NewUserServiceFake()
|
||||
mockUserService.ExpectedError = user.ErrUserNotFound
|
||||
adminGetUserAuthTokensScenario(t, "Should return not found when calling GET on",
|
||||
"/api/admin/users/200/auth-tokens", "/api/admin/users/:id/auth-tokens", func(sc *scenarioContext) {
|
||||
sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
|
||||
assert.Equal(t, 404, sc.resp.Code)
|
||||
}, mock)
|
||||
}, mockUserService)
|
||||
})
|
||||
|
||||
t.Run("When a server admin attempts to enable/disable a nonexistent user", func(t *testing.T) {
|
||||
@ -154,17 +153,15 @@ func TestAdminAPIEndpoint(t *testing.T) {
|
||||
adminDeleteUserScenario(t, "Should return user not found error", "/api/admin/users/42",
|
||||
"/api/admin/users/:id", func(sc *scenarioContext) {
|
||||
sc.sqlStore.(*mockstore.SQLStoreMock).ExpectedError = user.ErrUserNotFound
|
||||
sc.userService.(*usertest.FakeUserService).ExpectedError = user.ErrUserNotFound
|
||||
sc.authInfoService.ExpectedError = user.ErrUserNotFound
|
||||
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
|
||||
userID := sc.sqlStore.(*mockstore.SQLStoreMock).LatestUserId
|
||||
|
||||
assert.Equal(t, 404, sc.resp.Code)
|
||||
|
||||
respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes())
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "user not found", respJSON.Get("message").MustString())
|
||||
|
||||
assert.Equal(t, int64(42), userID)
|
||||
})
|
||||
})
|
||||
|
||||
@ -266,11 +263,11 @@ func putAdminScenario(t *testing.T, desc string, url string, routePattern string
|
||||
})
|
||||
}
|
||||
|
||||
func adminLogoutUserScenario(t *testing.T, desc string, url string, routePattern string, fn scenarioFunc, sqlStore sqlstore.Store) {
|
||||
func adminLogoutUserScenario(t *testing.T, desc string, url string, routePattern string, fn scenarioFunc, userService *usertest.FakeUserService) {
|
||||
t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
|
||||
hs := HTTPServer{
|
||||
AuthTokenService: auth.NewFakeUserAuthTokenService(),
|
||||
SQLStore: sqlStore,
|
||||
userService: userService,
|
||||
}
|
||||
|
||||
sc := setupScenarioContext(t, url)
|
||||
@ -291,13 +288,13 @@ func adminLogoutUserScenario(t *testing.T, desc string, url string, routePattern
|
||||
})
|
||||
}
|
||||
|
||||
func adminRevokeUserAuthTokenScenario(t *testing.T, desc string, url string, routePattern string, cmd models.RevokeAuthTokenCmd, fn scenarioFunc, sqlStore sqlstore.Store) {
|
||||
func adminRevokeUserAuthTokenScenario(t *testing.T, desc string, url string, routePattern string, cmd models.RevokeAuthTokenCmd, fn scenarioFunc, userService user.Service) {
|
||||
t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
|
||||
fakeAuthTokenService := auth.NewFakeUserAuthTokenService()
|
||||
|
||||
hs := HTTPServer{
|
||||
AuthTokenService: fakeAuthTokenService,
|
||||
SQLStore: sqlStore,
|
||||
userService: userService,
|
||||
}
|
||||
|
||||
sc := setupScenarioContext(t, url)
|
||||
@ -319,13 +316,13 @@ func adminRevokeUserAuthTokenScenario(t *testing.T, desc string, url string, rou
|
||||
})
|
||||
}
|
||||
|
||||
func adminGetUserAuthTokensScenario(t *testing.T, desc string, url string, routePattern string, fn scenarioFunc, sqlStore sqlstore.Store) {
|
||||
func adminGetUserAuthTokensScenario(t *testing.T, desc string, url string, routePattern string, fn scenarioFunc, userService *usertest.FakeUserService) {
|
||||
t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
|
||||
fakeAuthTokenService := auth.NewFakeUserAuthTokenService()
|
||||
|
||||
hs := HTTPServer{
|
||||
AuthTokenService: fakeAuthTokenService,
|
||||
SQLStore: sqlStore,
|
||||
userService: userService,
|
||||
}
|
||||
|
||||
sc := setupScenarioContext(t, url)
|
||||
@ -379,7 +376,8 @@ func adminDisableUserScenario(t *testing.T, desc string, action string, url stri
|
||||
|
||||
func adminDeleteUserScenario(t *testing.T, desc string, url string, routePattern string, fn scenarioFunc) {
|
||||
hs := HTTPServer{
|
||||
SQLStore: mockstore.NewSQLStoreMock(),
|
||||
SQLStore: mockstore.NewSQLStoreMock(),
|
||||
userService: usertest.NewUserServiceFake(),
|
||||
}
|
||||
t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
|
||||
sc := setupScenarioContext(t, url)
|
||||
@ -391,6 +389,7 @@ func adminDeleteUserScenario(t *testing.T, desc string, url string, routePattern
|
||||
|
||||
return hs.AdminDeleteUser(c)
|
||||
})
|
||||
sc.userService = hs.userService
|
||||
|
||||
sc.m.Delete(routePattern, sc.defaultHandler)
|
||||
|
||||
|
@ -43,6 +43,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/searchusers"
|
||||
"github.com/grafana/grafana/pkg/services/searchusers/filters"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/web"
|
||||
"github.com/grafana/grafana/pkg/web/webtest"
|
||||
@ -168,6 +169,7 @@ type scenarioContext struct {
|
||||
sqlStore sqlstore.Store
|
||||
authInfoService *logintest.AuthInfoServiceFake
|
||||
dashboardVersionService dashver.Service
|
||||
userService user.Service
|
||||
}
|
||||
|
||||
func (sc *scenarioContext) exec() {
|
||||
|
@ -28,6 +28,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/guardian"
|
||||
pref "github.com/grafana/grafana/pkg/services/preference"
|
||||
"github.com/grafana/grafana/pkg/services/star"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
"github.com/grafana/grafana/pkg/web"
|
||||
)
|
||||
@ -250,12 +251,12 @@ func (hs *HTTPServer) getAnnotationPermissionsByScope(c *models.ReqContext, acti
|
||||
}
|
||||
|
||||
func (hs *HTTPServer) getUserLogin(ctx context.Context, userID int64) string {
|
||||
query := models.GetUserByIdQuery{Id: userID}
|
||||
err := hs.SQLStore.GetUserById(ctx, &query)
|
||||
query := user.GetUserByIDQuery{ID: userID}
|
||||
user, err := hs.userService.GetByID(ctx, &query)
|
||||
if err != nil {
|
||||
return anonString
|
||||
}
|
||||
return query.Result.Login
|
||||
return user.Login
|
||||
}
|
||||
|
||||
func (hs *HTTPServer) getDashboardHelper(ctx context.Context, orgID int64, id int64, uid string) (*models.Dashboard, response.Response) {
|
||||
|
@ -209,9 +209,10 @@ func (hs *HTTPServer) PostSyncUserWithLDAP(c *models.ReqContext) response.Respon
|
||||
return response.Error(http.StatusBadRequest, "id is invalid", err)
|
||||
}
|
||||
|
||||
query := models.GetUserByIdQuery{Id: userId}
|
||||
query := user.GetUserByIDQuery{ID: userId}
|
||||
|
||||
if err := hs.SQLStore.GetUserById(c.Req.Context(), &query); err != nil { // validate the userId exists
|
||||
usr, err := hs.userService.GetByID(c.Req.Context(), &query)
|
||||
if err != nil { // validate the userId exists
|
||||
if errors.Is(err, user.ErrUserNotFound) {
|
||||
return response.Error(404, user.ErrUserNotFound.Error(), nil)
|
||||
}
|
||||
@ -219,7 +220,7 @@ func (hs *HTTPServer) PostSyncUserWithLDAP(c *models.ReqContext) response.Respon
|
||||
return response.Error(500, "Failed to get user", err)
|
||||
}
|
||||
|
||||
authModuleQuery := &models.GetAuthInfoQuery{UserId: query.Result.ID, AuthModule: models.AuthModuleLDAP}
|
||||
authModuleQuery := &models.GetAuthInfoQuery{UserId: usr.ID, AuthModule: models.AuthModuleLDAP}
|
||||
if err := hs.authInfoService.GetAuthInfo(c.Req.Context(), authModuleQuery); err != nil { // validate the userId comes from LDAP
|
||||
if errors.Is(err, user.ErrUserNotFound) {
|
||||
return response.Error(404, user.ErrUserNotFound.Error(), nil)
|
||||
@ -229,17 +230,17 @@ func (hs *HTTPServer) PostSyncUserWithLDAP(c *models.ReqContext) response.Respon
|
||||
}
|
||||
|
||||
ldapServer := newLDAP(ldapConfig.Servers)
|
||||
user, _, err := ldapServer.User(query.Result.Login)
|
||||
userInfo, _, err := ldapServer.User(usr.Login)
|
||||
if err != nil {
|
||||
if errors.Is(err, multildap.ErrDidNotFindUser) { // User was not in the LDAP server - we need to take action:
|
||||
if hs.Cfg.AdminUser == query.Result.Login { // User is *the* Grafana Admin. We cannot disable it.
|
||||
errMsg := fmt.Sprintf(`Refusing to sync grafana super admin "%s" - it would be disabled`, query.Result.Login)
|
||||
if hs.Cfg.AdminUser == usr.Login { // User is *the* Grafana Admin. We cannot disable it.
|
||||
errMsg := fmt.Sprintf(`Refusing to sync grafana super admin "%s" - it would be disabled`, usr.Login)
|
||||
ldapLogger.Error(errMsg)
|
||||
return response.Error(http.StatusBadRequest, errMsg, err)
|
||||
}
|
||||
|
||||
// Since the user was not in the LDAP server. Let's disable it.
|
||||
err := hs.Login.DisableExternalUser(c.Req.Context(), query.Result.Login)
|
||||
err := hs.Login.DisableExternalUser(c.Req.Context(), usr.Login)
|
||||
if err != nil {
|
||||
return response.Error(http.StatusInternalServerError, "Failed to disable the user", err)
|
||||
}
|
||||
@ -258,10 +259,10 @@ func (hs *HTTPServer) PostSyncUserWithLDAP(c *models.ReqContext) response.Respon
|
||||
|
||||
upsertCmd := &models.UpsertUserCommand{
|
||||
ReqContext: c,
|
||||
ExternalUser: user,
|
||||
ExternalUser: userInfo,
|
||||
SignupAllowed: hs.Cfg.LDAPAllowSignup,
|
||||
UserLookupParams: models.UserLookupParams{
|
||||
UserID: &query.Result.ID, // Upsert by ID only
|
||||
UserID: &usr.ID, // Upsert by ID only
|
||||
Email: nil,
|
||||
Login: nil,
|
||||
},
|
||||
|
@ -8,22 +8,22 @@ import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/login/loginservice"
|
||||
"github.com/grafana/grafana/pkg/services/login/logintest"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/mockstore"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/response"
|
||||
"github.com/grafana/grafana/pkg/api/routing"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/auth"
|
||||
"github.com/grafana/grafana/pkg/services/ldap"
|
||||
"github.com/grafana/grafana/pkg/services/login/loginservice"
|
||||
"github.com/grafana/grafana/pkg/services/login/logintest"
|
||||
"github.com/grafana/grafana/pkg/services/multildap"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/mockstore"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/services/user/usertest"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type LDAPMock struct {
|
||||
@ -363,7 +363,7 @@ func TestGetLDAPStatusAPIEndpoint(t *testing.T) {
|
||||
// PostSyncUserWithLDAP tests
|
||||
// ***
|
||||
|
||||
func postSyncUserWithLDAPContext(t *testing.T, requestURL string, preHook func(*testing.T, *scenarioContext), sqlstoremock sqlstore.Store) *scenarioContext {
|
||||
func postSyncUserWithLDAPContext(t *testing.T, requestURL string, preHook func(*testing.T, *scenarioContext), userService user.Service) *scenarioContext {
|
||||
t.Helper()
|
||||
|
||||
sc := setupScenarioContext(t, requestURL)
|
||||
@ -378,9 +378,9 @@ func postSyncUserWithLDAPContext(t *testing.T, requestURL string, preHook func(*
|
||||
hs := &HTTPServer{
|
||||
Cfg: sc.cfg,
|
||||
AuthTokenService: auth.NewFakeUserAuthTokenService(),
|
||||
SQLStore: sqlstoremock,
|
||||
Login: loginservice.LoginServiceMock{},
|
||||
authInfoService: sc.authInfoService,
|
||||
userService: userService,
|
||||
}
|
||||
|
||||
sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response {
|
||||
@ -403,8 +403,8 @@ func postSyncUserWithLDAPContext(t *testing.T, requestURL string, preHook func(*
|
||||
}
|
||||
|
||||
func TestPostSyncUserWithLDAPAPIEndpoint_Success(t *testing.T) {
|
||||
sqlstoremock := mockstore.SQLStoreMock{}
|
||||
sqlstoremock.ExpectedUser = &user.User{Login: "ldap-daniel", ID: 34}
|
||||
userServiceMock := usertest.NewUserServiceFake()
|
||||
userServiceMock.ExpectedUser = &user.User{Login: "ldap-daniel", ID: 34}
|
||||
sc := postSyncUserWithLDAPContext(t, "/api/admin/ldap/sync/34", func(t *testing.T, sc *scenarioContext) {
|
||||
getLDAPConfig = func(*setting.Cfg) (*ldap.Config, error) {
|
||||
return &ldap.Config{}, nil
|
||||
@ -417,7 +417,7 @@ func TestPostSyncUserWithLDAPAPIEndpoint_Success(t *testing.T) {
|
||||
userSearchResult = &models.ExternalUserInfo{
|
||||
Login: "ldap-daniel",
|
||||
}
|
||||
}, &sqlstoremock)
|
||||
}, userServiceMock)
|
||||
|
||||
assert.Equal(t, http.StatusOK, sc.resp.Code)
|
||||
|
||||
@ -431,7 +431,8 @@ func TestPostSyncUserWithLDAPAPIEndpoint_Success(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestPostSyncUserWithLDAPAPIEndpoint_WhenUserNotFound(t *testing.T) {
|
||||
sqlstoremock := mockstore.SQLStoreMock{ExpectedError: user.ErrUserNotFound}
|
||||
userServiceMock := usertest.NewUserServiceFake()
|
||||
userServiceMock.ExpectedError = user.ErrUserNotFound
|
||||
sc := postSyncUserWithLDAPContext(t, "/api/admin/ldap/sync/34", func(t *testing.T, sc *scenarioContext) {
|
||||
getLDAPConfig = func(*setting.Cfg) (*ldap.Config, error) {
|
||||
return &ldap.Config{}, nil
|
||||
@ -440,7 +441,7 @@ func TestPostSyncUserWithLDAPAPIEndpoint_WhenUserNotFound(t *testing.T) {
|
||||
newLDAP = func(_ []*ldap.ServerConfig) multildap.IMultiLDAP {
|
||||
return &LDAPMock{}
|
||||
}
|
||||
}, &sqlstoremock)
|
||||
}, userServiceMock)
|
||||
|
||||
assert.Equal(t, http.StatusNotFound, sc.resp.Code)
|
||||
|
||||
@ -454,7 +455,8 @@ func TestPostSyncUserWithLDAPAPIEndpoint_WhenUserNotFound(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestPostSyncUserWithLDAPAPIEndpoint_WhenGrafanaAdmin(t *testing.T) {
|
||||
sqlstoremock := mockstore.SQLStoreMock{ExpectedUser: &user.User{Login: "ldap-daniel", ID: 34}}
|
||||
userServiceMock := usertest.NewUserServiceFake()
|
||||
userServiceMock.ExpectedUser = &user.User{Login: "ldap-daniel", ID: 34}
|
||||
sc := postSyncUserWithLDAPContext(t, "/api/admin/ldap/sync/34", func(t *testing.T, sc *scenarioContext) {
|
||||
getLDAPConfig = func(*setting.Cfg) (*ldap.Config, error) {
|
||||
return &ldap.Config{}, nil
|
||||
@ -467,7 +469,7 @@ func TestPostSyncUserWithLDAPAPIEndpoint_WhenGrafanaAdmin(t *testing.T) {
|
||||
userSearchError = multildap.ErrDidNotFindUser
|
||||
|
||||
sc.cfg.AdminUser = "ldap-daniel"
|
||||
}, &sqlstoremock)
|
||||
}, userServiceMock)
|
||||
assert.Equal(t, http.StatusBadRequest, sc.resp.Code)
|
||||
|
||||
var res map[string]interface{}
|
||||
@ -478,7 +480,8 @@ func TestPostSyncUserWithLDAPAPIEndpoint_WhenGrafanaAdmin(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestPostSyncUserWithLDAPAPIEndpoint_WhenUserNotInLDAP(t *testing.T) {
|
||||
sqlstoremock := mockstore.SQLStoreMock{ExpectedUser: &user.User{Login: "ldap-daniel", ID: 34}}
|
||||
userServiceMock := usertest.NewUserServiceFake()
|
||||
userServiceMock.ExpectedUser = &user.User{Login: "ldap-daniel", ID: 34}
|
||||
sc := postSyncUserWithLDAPContext(t, "/api/admin/ldap/sync/34", func(t *testing.T, sc *scenarioContext) {
|
||||
sc.authInfoService.ExpectedExternalUser = &models.ExternalUserInfo{IsDisabled: true, UserId: 34}
|
||||
getLDAPConfig = func(*setting.Cfg) (*ldap.Config, error) {
|
||||
@ -491,7 +494,7 @@ func TestPostSyncUserWithLDAPAPIEndpoint_WhenUserNotInLDAP(t *testing.T) {
|
||||
|
||||
userSearchResult = nil
|
||||
userSearchError = multildap.ErrDidNotFindUser
|
||||
}, &sqlstoremock)
|
||||
}, userServiceMock)
|
||||
|
||||
assert.Equal(t, http.StatusBadRequest, sc.resp.Code)
|
||||
|
||||
@ -603,6 +606,7 @@ func TestLDAP_AccessControl(t *testing.T) {
|
||||
cfg.LDAPEnabled = true
|
||||
sc, hs := setupAccessControlScenarioContext(t, cfg, test.url, test.permissions)
|
||||
hs.SQLStore = &mockstore.SQLStoreMock{ExpectedUser: &user.User{}}
|
||||
hs.userService = &usertest.FakeUserService{ExpectedUser: &user.User{}}
|
||||
hs.authInfoService = &logintest.AuthInfoServiceFake{}
|
||||
hs.Login = &loginservice.LoginServiceMock{}
|
||||
sc.resp = httptest.NewRecorder()
|
||||
|
@ -387,17 +387,18 @@ func (hs *HTTPServer) ChangeUserPassword(c *models.ReqContext) response.Response
|
||||
return response.Error(400, "Not allowed to change password when LDAP or Auth Proxy is enabled", nil)
|
||||
}
|
||||
|
||||
userQuery := models.GetUserByIdQuery{Id: c.UserId}
|
||||
userQuery := user.GetUserByIDQuery{ID: c.UserId}
|
||||
|
||||
if err := hs.SQLStore.GetUserById(c.Req.Context(), &userQuery); err != nil {
|
||||
user, err := hs.userService.GetByID(c.Req.Context(), &userQuery)
|
||||
if err != nil {
|
||||
return response.Error(500, "Could not read user from database", err)
|
||||
}
|
||||
|
||||
passwordHashed, err := util.EncodePassword(cmd.OldPassword, userQuery.Result.Salt)
|
||||
passwordHashed, err := util.EncodePassword(cmd.OldPassword, user.Salt)
|
||||
if err != nil {
|
||||
return response.Error(500, "Failed to encode password", err)
|
||||
}
|
||||
if passwordHashed != userQuery.Result.Password {
|
||||
if passwordHashed != user.Password {
|
||||
return response.Error(401, "Invalid old password", nil)
|
||||
}
|
||||
|
||||
@ -407,7 +408,7 @@ func (hs *HTTPServer) ChangeUserPassword(c *models.ReqContext) response.Response
|
||||
}
|
||||
|
||||
cmd.UserId = c.UserId
|
||||
cmd.NewPassword, err = util.EncodePassword(cmd.NewPassword, userQuery.Result.Salt)
|
||||
cmd.NewPassword, err = util.EncodePassword(cmd.NewPassword, user.Salt)
|
||||
if err != nil {
|
||||
return response.Error(500, "Failed to encode password", err)
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/mockstore"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/services/user/usertest"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
@ -49,7 +50,7 @@ func TestUserAPIEndpoint_userLoggedIn(t *testing.T) {
|
||||
loggedInUserScenario(t, "When calling GET on", "api/users/1", "api/users/:id", func(sc *scenarioContext) {
|
||||
fakeNow := time.Date(2019, 2, 11, 17, 30, 40, 0, time.UTC)
|
||||
secretsService := secretsManager.SetupTestService(t, database.ProvideSecretsStore(sqlStore))
|
||||
authInfoStore := authinfostore.ProvideAuthInfoStore(sqlStore, secretsService)
|
||||
authInfoStore := authinfostore.ProvideAuthInfoStore(sqlStore, secretsService, usertest.NewUserServiceFake())
|
||||
srv := authinfoservice.ProvideAuthInfoService(
|
||||
&authinfoservice.OSSUserProtectionImpl{},
|
||||
authInfoStore,
|
||||
|
@ -51,16 +51,17 @@ func (hs *HTTPServer) RevokeUserAuthToken(c *models.ReqContext) response.Respons
|
||||
}
|
||||
|
||||
func (hs *HTTPServer) logoutUserFromAllDevicesInternal(ctx context.Context, userID int64) response.Response {
|
||||
userQuery := models.GetUserByIdQuery{Id: userID}
|
||||
userQuery := user.GetUserByIDQuery{ID: userID}
|
||||
|
||||
if err := hs.SQLStore.GetUserById(ctx, &userQuery); err != nil {
|
||||
_, err := hs.userService.GetByID(ctx, &userQuery)
|
||||
if err != nil {
|
||||
if errors.Is(err, user.ErrUserNotFound) {
|
||||
return response.Error(404, "User not found", err)
|
||||
}
|
||||
return response.Error(500, "Could not read user from database", err)
|
||||
}
|
||||
|
||||
err := hs.AuthTokenService.RevokeAllUserTokens(ctx, userID)
|
||||
err = hs.AuthTokenService.RevokeAllUserTokens(ctx, userID)
|
||||
if err != nil {
|
||||
return response.Error(500, "Failed to logout user", err)
|
||||
}
|
||||
@ -71,9 +72,10 @@ func (hs *HTTPServer) logoutUserFromAllDevicesInternal(ctx context.Context, user
|
||||
}
|
||||
|
||||
func (hs *HTTPServer) getUserAuthTokensInternal(c *models.ReqContext, userID int64) response.Response {
|
||||
userQuery := models.GetUserByIdQuery{Id: userID}
|
||||
userQuery := user.GetUserByIDQuery{ID: userID}
|
||||
|
||||
if err := hs.SQLStore.GetUserById(c.Req.Context(), &userQuery); err != nil {
|
||||
_, err := hs.userService.GetByID(c.Req.Context(), &userQuery)
|
||||
if err != nil {
|
||||
if errors.Is(err, user.ErrUserNotFound) {
|
||||
return response.Error(http.StatusNotFound, "User not found", err)
|
||||
} else if errors.Is(err, user.ErrCaseInsensitive) {
|
||||
@ -142,8 +144,9 @@ func (hs *HTTPServer) getUserAuthTokensInternal(c *models.ReqContext, userID int
|
||||
}
|
||||
|
||||
func (hs *HTTPServer) revokeUserAuthTokenInternal(c *models.ReqContext, userID int64, cmd models.RevokeAuthTokenCmd) response.Response {
|
||||
userQuery := models.GetUserByIdQuery{Id: userID}
|
||||
if err := hs.SQLStore.GetUserById(c.Req.Context(), &userQuery); err != nil {
|
||||
userQuery := user.GetUserByIDQuery{ID: userID}
|
||||
_, err := hs.userService.GetByID(c.Req.Context(), &userQuery)
|
||||
if err != nil {
|
||||
if errors.Is(err, user.ErrUserNotFound) {
|
||||
return response.Error(404, "User not found", err)
|
||||
}
|
||||
|
@ -6,62 +6,61 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/response"
|
||||
"github.com/grafana/grafana/pkg/api/routing"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/auth"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/mockstore"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/grafana/grafana/pkg/services/user/usertest"
|
||||
)
|
||||
|
||||
func TestUserTokenAPIEndpoint(t *testing.T) {
|
||||
mock := mockstore.NewSQLStoreMock()
|
||||
userMock := usertest.NewUserServiceFake()
|
||||
t.Run("When current user attempts to revoke an auth token for a non-existing user", func(t *testing.T) {
|
||||
cmd := models.RevokeAuthTokenCmd{AuthTokenId: 2}
|
||||
mock.ExpectedError = user.ErrUserNotFound
|
||||
userMock.ExpectedError = user.ErrUserNotFound
|
||||
revokeUserAuthTokenScenario(t, "Should return not found when calling POST on", "/api/user/revoke-auth-token",
|
||||
"/api/user/revoke-auth-token", cmd, 200, func(sc *scenarioContext) {
|
||||
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
|
||||
assert.Equal(t, 404, sc.resp.Code)
|
||||
}, mock)
|
||||
}, userMock)
|
||||
})
|
||||
|
||||
t.Run("When current user gets auth tokens for a non-existing user", func(t *testing.T) {
|
||||
mock := &mockstore.SQLStoreMock{
|
||||
mockUser := &usertest.FakeUserService{
|
||||
ExpectedUser: &user.User{ID: 200},
|
||||
ExpectedError: user.ErrUserNotFound,
|
||||
}
|
||||
getUserAuthTokensScenario(t, "Should return not found when calling GET on", "/api/user/auth-tokens", "/api/user/auth-tokens", 200, func(sc *scenarioContext) {
|
||||
sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
|
||||
assert.Equal(t, 404, sc.resp.Code)
|
||||
}, mock)
|
||||
}, mockUser)
|
||||
})
|
||||
|
||||
t.Run("When logging out an existing user from all devices", func(t *testing.T) {
|
||||
mock := &mockstore.SQLStoreMock{
|
||||
userMock := &usertest.FakeUserService{
|
||||
ExpectedUser: &user.User{ID: 200},
|
||||
}
|
||||
logoutUserFromAllDevicesInternalScenario(t, "Should be successful", 1, func(sc *scenarioContext) {
|
||||
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
|
||||
assert.Equal(t, 200, sc.resp.Code)
|
||||
}, mock)
|
||||
}, userMock)
|
||||
})
|
||||
|
||||
t.Run("When logout a non-existing user from all devices", func(t *testing.T) {
|
||||
logoutUserFromAllDevicesInternalScenario(t, "Should return not found", testUserID, func(sc *scenarioContext) {
|
||||
mock.ExpectedError = user.ErrUserNotFound
|
||||
|
||||
userMock.ExpectedError = user.ErrUserNotFound
|
||||
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
|
||||
assert.Equal(t, 404, sc.resp.Code)
|
||||
}, mock)
|
||||
}, userMock)
|
||||
})
|
||||
|
||||
t.Run("When revoke an auth token for a user", func(t *testing.T) {
|
||||
cmd := models.RevokeAuthTokenCmd{AuthTokenId: 2}
|
||||
token := &models.UserToken{Id: 1}
|
||||
mock := &mockstore.SQLStoreMock{
|
||||
mockUser := &usertest.FakeUserService{
|
||||
ExpectedUser: &user.User{ID: 200},
|
||||
}
|
||||
|
||||
@ -71,25 +70,25 @@ func TestUserTokenAPIEndpoint(t *testing.T) {
|
||||
}
|
||||
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
|
||||
assert.Equal(t, 200, sc.resp.Code)
|
||||
}, mock)
|
||||
}, mockUser)
|
||||
})
|
||||
|
||||
t.Run("When revoke the active auth token used by himself", func(t *testing.T) {
|
||||
cmd := models.RevokeAuthTokenCmd{AuthTokenId: 2}
|
||||
token := &models.UserToken{Id: 2}
|
||||
mock := mockstore.NewSQLStoreMock()
|
||||
mockUser := usertest.NewUserServiceFake()
|
||||
revokeUserAuthTokenInternalScenario(t, "Should not be successful", cmd, testUserID, token, func(sc *scenarioContext) {
|
||||
sc.userAuthTokenService.GetUserTokenProvider = func(ctx context.Context, userId, userTokenId int64) (*models.UserToken, error) {
|
||||
return token, nil
|
||||
}
|
||||
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
|
||||
assert.Equal(t, 400, sc.resp.Code)
|
||||
}, mock)
|
||||
}, mockUser)
|
||||
})
|
||||
|
||||
t.Run("When gets auth tokens for a user", func(t *testing.T) {
|
||||
currentToken := &models.UserToken{Id: 1}
|
||||
mock := mockstore.NewSQLStoreMock()
|
||||
mockUser := usertest.NewUserServiceFake()
|
||||
getUserAuthTokensInternalScenario(t, "Should be successful", currentToken, func(sc *scenarioContext) {
|
||||
tokens := []*models.UserToken{
|
||||
{
|
||||
@ -141,18 +140,18 @@ func TestUserTokenAPIEndpoint(t *testing.T) {
|
||||
assert.Equal(t, "11.0", resultTwo.Get("browserVersion").MustString())
|
||||
assert.Equal(t, "iOS", resultTwo.Get("os").MustString())
|
||||
assert.Equal(t, "11.0", resultTwo.Get("osVersion").MustString())
|
||||
}, mock)
|
||||
}, mockUser)
|
||||
})
|
||||
}
|
||||
|
||||
func revokeUserAuthTokenScenario(t *testing.T, desc string, url string, routePattern string, cmd models.RevokeAuthTokenCmd,
|
||||
userId int64, fn scenarioFunc, sqlStore sqlstore.Store) {
|
||||
userId int64, fn scenarioFunc, userService user.Service) {
|
||||
t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
|
||||
fakeAuthTokenService := auth.NewFakeUserAuthTokenService()
|
||||
|
||||
hs := HTTPServer{
|
||||
AuthTokenService: fakeAuthTokenService,
|
||||
SQLStore: sqlStore,
|
||||
userService: userService,
|
||||
}
|
||||
|
||||
sc := setupScenarioContext(t, url)
|
||||
@ -173,13 +172,13 @@ func revokeUserAuthTokenScenario(t *testing.T, desc string, url string, routePat
|
||||
})
|
||||
}
|
||||
|
||||
func getUserAuthTokensScenario(t *testing.T, desc string, url string, routePattern string, userId int64, fn scenarioFunc, sqlStore sqlstore.Store) {
|
||||
func getUserAuthTokensScenario(t *testing.T, desc string, url string, routePattern string, userId int64, fn scenarioFunc, userService user.Service) {
|
||||
t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
|
||||
fakeAuthTokenService := auth.NewFakeUserAuthTokenService()
|
||||
|
||||
hs := HTTPServer{
|
||||
AuthTokenService: fakeAuthTokenService,
|
||||
SQLStore: sqlStore,
|
||||
userService: userService,
|
||||
}
|
||||
|
||||
sc := setupScenarioContext(t, url)
|
||||
@ -199,11 +198,11 @@ func getUserAuthTokensScenario(t *testing.T, desc string, url string, routePatte
|
||||
})
|
||||
}
|
||||
|
||||
func logoutUserFromAllDevicesInternalScenario(t *testing.T, desc string, userId int64, fn scenarioFunc, sqlStore sqlstore.Store) {
|
||||
func logoutUserFromAllDevicesInternalScenario(t *testing.T, desc string, userId int64, fn scenarioFunc, userService user.Service) {
|
||||
t.Run(desc, func(t *testing.T) {
|
||||
hs := HTTPServer{
|
||||
AuthTokenService: auth.NewFakeUserAuthTokenService(),
|
||||
SQLStore: sqlStore,
|
||||
userService: userService,
|
||||
}
|
||||
|
||||
sc := setupScenarioContext(t, "/")
|
||||
@ -223,13 +222,13 @@ func logoutUserFromAllDevicesInternalScenario(t *testing.T, desc string, userId
|
||||
}
|
||||
|
||||
func revokeUserAuthTokenInternalScenario(t *testing.T, desc string, cmd models.RevokeAuthTokenCmd, userId int64,
|
||||
token *models.UserToken, fn scenarioFunc, sqlStore sqlstore.Store) {
|
||||
token *models.UserToken, fn scenarioFunc, userService user.Service) {
|
||||
t.Run(desc, func(t *testing.T) {
|
||||
fakeAuthTokenService := auth.NewFakeUserAuthTokenService()
|
||||
|
||||
hs := HTTPServer{
|
||||
AuthTokenService: fakeAuthTokenService,
|
||||
SQLStore: sqlStore,
|
||||
userService: userService,
|
||||
}
|
||||
|
||||
sc := setupScenarioContext(t, "/")
|
||||
@ -248,13 +247,13 @@ func revokeUserAuthTokenInternalScenario(t *testing.T, desc string, cmd models.R
|
||||
})
|
||||
}
|
||||
|
||||
func getUserAuthTokensInternalScenario(t *testing.T, desc string, token *models.UserToken, fn scenarioFunc, sqlStore sqlstore.Store) {
|
||||
func getUserAuthTokensInternalScenario(t *testing.T, desc string, token *models.UserToken, fn scenarioFunc, userService user.Service) {
|
||||
t.Run(desc, func(t *testing.T) {
|
||||
fakeAuthTokenService := auth.NewFakeUserAuthTokenService()
|
||||
|
||||
hs := HTTPServer{
|
||||
AuthTokenService: fakeAuthTokenService,
|
||||
SQLStore: sqlStore,
|
||||
userService: userService,
|
||||
}
|
||||
|
||||
sc := setupScenarioContext(t, "/")
|
||||
|
@ -151,7 +151,7 @@ var adminCommands = []*cli.Command{
|
||||
{
|
||||
Name: "reset-admin-password",
|
||||
Usage: "reset-admin-password <new password>",
|
||||
Action: runDbCommand(resetPasswordCommand),
|
||||
Action: runRunnerCommand(resetPasswordCommand),
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "password-from-stdin",
|
||||
|
@ -8,15 +8,16 @@ import (
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/grafana/grafana/pkg/cmd/grafana-cli/logger"
|
||||
"github.com/grafana/grafana/pkg/cmd/grafana-cli/runner"
|
||||
"github.com/grafana/grafana/pkg/cmd/grafana-cli/utils"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
const AdminUserId = 1
|
||||
|
||||
func resetPasswordCommand(c utils.CommandLine, sqlStore *sqlstore.SQLStore) error {
|
||||
func resetPasswordCommand(c utils.CommandLine, runner runner.Runner) error {
|
||||
newPassword := ""
|
||||
|
||||
if c.Bool("password-from-stdin") {
|
||||
@ -39,13 +40,14 @@ func resetPasswordCommand(c utils.CommandLine, sqlStore *sqlstore.SQLStore) erro
|
||||
return fmt.Errorf("new password is too short")
|
||||
}
|
||||
|
||||
userQuery := models.GetUserByIdQuery{Id: AdminUserId}
|
||||
userQuery := user.GetUserByIDQuery{ID: AdminUserId}
|
||||
|
||||
if err := sqlStore.GetUserById(context.Background(), &userQuery); err != nil {
|
||||
usr, err := runner.UserService.GetByID(context.Background(), &userQuery)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not read user from database. Error: %v", err)
|
||||
}
|
||||
|
||||
passwordHashed, err := util.EncodePassword(newPassword, userQuery.Result.Salt)
|
||||
passwordHashed, err := util.EncodePassword(newPassword, usr.Salt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -55,7 +57,7 @@ func resetPasswordCommand(c utils.CommandLine, sqlStore *sqlstore.SQLStore) erro
|
||||
NewPassword: passwordHashed,
|
||||
}
|
||||
|
||||
if err := sqlStore.ChangeUserPassword(context.Background(), &cmd); err != nil {
|
||||
if err := runner.SQLStore.ChangeUserPassword(context.Background(), &cmd); err != nil {
|
||||
return fmt.Errorf("failed to update user password: %w", err)
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/secrets"
|
||||
"github.com/grafana/grafana/pkg/services/secrets/manager"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
@ -17,11 +18,13 @@ type Runner struct {
|
||||
EncryptionService encryption.Internal
|
||||
SecretsService *manager.SecretsService
|
||||
SecretsMigrator secrets.Migrator
|
||||
UserService user.Service
|
||||
}
|
||||
|
||||
func New(cfg *setting.Cfg, sqlStore *sqlstore.SQLStore, settingsProvider setting.Provider,
|
||||
encryptionService encryption.Internal, features featuremgmt.FeatureToggles,
|
||||
secretsService *manager.SecretsService, secretsMigrator secrets.Migrator,
|
||||
userService user.Service,
|
||||
) Runner {
|
||||
return Runner{
|
||||
Cfg: cfg,
|
||||
@ -31,5 +34,6 @@ func New(cfg *setting.Cfg, sqlStore *sqlstore.SQLStore, settingsProvider setting
|
||||
SecretsService: secretsService,
|
||||
SecretsMigrator: secretsMigrator,
|
||||
Features: features,
|
||||
UserService: userService,
|
||||
}
|
||||
}
|
||||
|
@ -7,21 +7,132 @@ import (
|
||||
"context"
|
||||
|
||||
"github.com/google/wire"
|
||||
|
||||
sdkhttpclient "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
|
||||
"github.com/grafana/grafana/pkg/api"
|
||||
"github.com/grafana/grafana/pkg/api/avatar"
|
||||
"github.com/grafana/grafana/pkg/api/routing"
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/cuectx"
|
||||
"github.com/grafana/grafana/pkg/expr"
|
||||
cmreg "github.com/grafana/grafana/pkg/framework/coremodel/registry"
|
||||
"github.com/grafana/grafana/pkg/infra/httpclient"
|
||||
"github.com/grafana/grafana/pkg/infra/httpclient/httpclientprovider"
|
||||
"github.com/grafana/grafana/pkg/infra/kvstore"
|
||||
"github.com/grafana/grafana/pkg/infra/localcache"
|
||||
"github.com/grafana/grafana/pkg/infra/metrics"
|
||||
"github.com/grafana/grafana/pkg/infra/remotecache"
|
||||
"github.com/grafana/grafana/pkg/infra/serverlock"
|
||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||
"github.com/grafana/grafana/pkg/infra/usagestats"
|
||||
uss "github.com/grafana/grafana/pkg/infra/usagestats/service"
|
||||
"github.com/grafana/grafana/pkg/infra/usagestats/statscollector"
|
||||
loginpkg "github.com/grafana/grafana/pkg/login"
|
||||
"github.com/grafana/grafana/pkg/login/social"
|
||||
"github.com/grafana/grafana/pkg/middleware/csrf"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/plugins/backendplugin/coreplugin"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/loader"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/registry"
|
||||
"github.com/grafana/grafana/pkg/plugins/plugincontext"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/ossaccesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/alerting"
|
||||
"github.com/grafana/grafana/pkg/services/auth"
|
||||
"github.com/grafana/grafana/pkg/services/auth/jwt"
|
||||
"github.com/grafana/grafana/pkg/services/cleanup"
|
||||
"github.com/grafana/grafana/pkg/services/comments"
|
||||
"github.com/grafana/grafana/pkg/services/contexthandler"
|
||||
"github.com/grafana/grafana/pkg/services/contexthandler/authproxy"
|
||||
"github.com/grafana/grafana/pkg/services/dashboardimport"
|
||||
dashboardimportservice "github.com/grafana/grafana/pkg/services/dashboardimport/service"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
dashboardstore "github.com/grafana/grafana/pkg/services/dashboards/database"
|
||||
dashboardservice "github.com/grafana/grafana/pkg/services/dashboards/service"
|
||||
"github.com/grafana/grafana/pkg/services/dashboardsnapshots"
|
||||
dashsnapstore "github.com/grafana/grafana/pkg/services/dashboardsnapshots/database"
|
||||
dashsnapsvc "github.com/grafana/grafana/pkg/services/dashboardsnapshots/service"
|
||||
"github.com/grafana/grafana/pkg/services/dashboardversion/dashverimpl"
|
||||
"github.com/grafana/grafana/pkg/services/datasourceproxy"
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
datasourceservice "github.com/grafana/grafana/pkg/services/datasources/service"
|
||||
"github.com/grafana/grafana/pkg/services/encryption"
|
||||
encryptionservice "github.com/grafana/grafana/pkg/services/encryption/service"
|
||||
"github.com/grafana/grafana/pkg/services/export"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/guardian"
|
||||
"github.com/grafana/grafana/pkg/services/hooks"
|
||||
"github.com/grafana/grafana/pkg/services/libraryelements"
|
||||
"github.com/grafana/grafana/pkg/services/librarypanels"
|
||||
"github.com/grafana/grafana/pkg/services/live"
|
||||
"github.com/grafana/grafana/pkg/services/live/pushhttp"
|
||||
"github.com/grafana/grafana/pkg/services/login"
|
||||
"github.com/grafana/grafana/pkg/services/login/authinfoservice"
|
||||
authinfodatabase "github.com/grafana/grafana/pkg/services/login/authinfoservice/database"
|
||||
"github.com/grafana/grafana/pkg/services/login/loginservice"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert"
|
||||
ngmetrics "github.com/grafana/grafana/pkg/services/ngalert/metrics"
|
||||
"github.com/grafana/grafana/pkg/services/notifications"
|
||||
"github.com/grafana/grafana/pkg/services/oauthtoken"
|
||||
"github.com/grafana/grafana/pkg/services/org/orgimpl"
|
||||
"github.com/grafana/grafana/pkg/services/playlist/playlistimpl"
|
||||
"github.com/grafana/grafana/pkg/services/plugindashboards"
|
||||
plugindashboardsservice "github.com/grafana/grafana/pkg/services/plugindashboards/service"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsettings"
|
||||
pluginSettings "github.com/grafana/grafana/pkg/services/pluginsettings/service"
|
||||
"github.com/grafana/grafana/pkg/services/preference/prefimpl"
|
||||
"github.com/grafana/grafana/pkg/services/publicdashboards"
|
||||
publicdashboardsApi "github.com/grafana/grafana/pkg/services/publicdashboards/api"
|
||||
publicdashboardsStore "github.com/grafana/grafana/pkg/services/publicdashboards/database"
|
||||
publicdashboardsService "github.com/grafana/grafana/pkg/services/publicdashboards/service"
|
||||
"github.com/grafana/grafana/pkg/services/query"
|
||||
"github.com/grafana/grafana/pkg/services/queryhistory"
|
||||
"github.com/grafana/grafana/pkg/services/quota/quotaimpl"
|
||||
"github.com/grafana/grafana/pkg/services/rendering"
|
||||
"github.com/grafana/grafana/pkg/services/search"
|
||||
"github.com/grafana/grafana/pkg/services/searchV2"
|
||||
"github.com/grafana/grafana/pkg/services/secrets"
|
||||
secretsDatabase "github.com/grafana/grafana/pkg/services/secrets/database"
|
||||
secretsStore "github.com/grafana/grafana/pkg/services/secrets/kvstore"
|
||||
secretsMigrations "github.com/grafana/grafana/pkg/services/secrets/kvstore/migrations"
|
||||
secretsManager "github.com/grafana/grafana/pkg/services/secrets/manager"
|
||||
secretsMigrator "github.com/grafana/grafana/pkg/services/secrets/migrator"
|
||||
"github.com/grafana/grafana/pkg/services/serviceaccounts"
|
||||
"github.com/grafana/grafana/pkg/services/serviceaccounts/database"
|
||||
serviceaccountsmanager "github.com/grafana/grafana/pkg/services/serviceaccounts/manager"
|
||||
"github.com/grafana/grafana/pkg/services/shorturls"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/db"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/mockstore"
|
||||
"github.com/grafana/grafana/pkg/services/star/starimpl"
|
||||
"github.com/grafana/grafana/pkg/services/store"
|
||||
"github.com/grafana/grafana/pkg/services/store/sanitizer"
|
||||
"github.com/grafana/grafana/pkg/services/teamguardian"
|
||||
teamguardianDatabase "github.com/grafana/grafana/pkg/services/teamguardian/database"
|
||||
teamguardianManager "github.com/grafana/grafana/pkg/services/teamguardian/manager"
|
||||
"github.com/grafana/grafana/pkg/services/thumbs"
|
||||
"github.com/grafana/grafana/pkg/services/updatechecker"
|
||||
"github.com/grafana/grafana/pkg/services/user/userimpl"
|
||||
"github.com/grafana/grafana/pkg/services/userauth/userauthimpl"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/tsdb/azuremonitor"
|
||||
"github.com/grafana/grafana/pkg/tsdb/cloudmonitoring"
|
||||
"github.com/grafana/grafana/pkg/tsdb/cloudwatch"
|
||||
"github.com/grafana/grafana/pkg/tsdb/elasticsearch"
|
||||
"github.com/grafana/grafana/pkg/tsdb/grafanads"
|
||||
"github.com/grafana/grafana/pkg/tsdb/graphite"
|
||||
"github.com/grafana/grafana/pkg/tsdb/influxdb"
|
||||
"github.com/grafana/grafana/pkg/tsdb/legacydata"
|
||||
legacydataservice "github.com/grafana/grafana/pkg/tsdb/legacydata/service"
|
||||
"github.com/grafana/grafana/pkg/tsdb/loki"
|
||||
"github.com/grafana/grafana/pkg/tsdb/mssql"
|
||||
"github.com/grafana/grafana/pkg/tsdb/mysql"
|
||||
"github.com/grafana/grafana/pkg/tsdb/postgres"
|
||||
"github.com/grafana/grafana/pkg/tsdb/prometheus"
|
||||
"github.com/grafana/grafana/pkg/tsdb/tempo"
|
||||
"github.com/grafana/grafana/pkg/tsdb/testdatasource"
|
||||
"github.com/grafana/grafana/pkg/web"
|
||||
)
|
||||
|
||||
@ -42,9 +153,171 @@ var wireSet = wire.NewSet(
|
||||
wire.Bind(new(secrets.Store), new(*secretsDatabase.SecretsStoreImpl)),
|
||||
secretsManager.ProvideSecretsService,
|
||||
wire.Bind(new(secrets.Service), new(*secretsManager.SecretsService)),
|
||||
hooks.ProvideService,
|
||||
legacydataservice.ProvideService,
|
||||
wire.Bind(new(legacydata.RequestHandler), new(*legacydataservice.Service)),
|
||||
alerting.ProvideAlertEngine,
|
||||
wire.Bind(new(alerting.UsageStatsQuerier), new(*alerting.AlertEngine)),
|
||||
api.ProvideHTTPServer,
|
||||
query.ProvideService,
|
||||
thumbs.ProvideService,
|
||||
rendering.ProvideService,
|
||||
wire.Bind(new(rendering.Service), new(*rendering.RenderingService)),
|
||||
kvstore.ProvideService,
|
||||
updatechecker.ProvideGrafanaService,
|
||||
updatechecker.ProvidePluginsService,
|
||||
uss.ProvideService,
|
||||
registry.ProvideService,
|
||||
wire.Bind(new(registry.Service), new(*registry.InMemory)),
|
||||
manager.ProvideService,
|
||||
wire.Bind(new(plugins.Manager), new(*manager.PluginManager)),
|
||||
wire.Bind(new(plugins.Client), new(*manager.PluginManager)),
|
||||
wire.Bind(new(plugins.Store), new(*manager.PluginManager)),
|
||||
wire.Bind(new(plugins.DashboardFileStore), new(*manager.PluginManager)),
|
||||
wire.Bind(new(plugins.StaticRouteResolver), new(*manager.PluginManager)),
|
||||
wire.Bind(new(plugins.RendererManager), new(*manager.PluginManager)),
|
||||
wire.Bind(new(plugins.SecretsPluginManager), new(*manager.PluginManager)),
|
||||
coreplugin.ProvideCoreRegistry,
|
||||
loader.ProvideService,
|
||||
wire.Bind(new(loader.Service), new(*loader.Loader)),
|
||||
wire.Bind(new(plugins.ErrorResolver), new(*loader.Loader)),
|
||||
cloudwatch.ProvideService,
|
||||
cloudmonitoring.ProvideService,
|
||||
azuremonitor.ProvideService,
|
||||
postgres.ProvideService,
|
||||
mysql.ProvideService,
|
||||
mssql.ProvideService,
|
||||
store.ProvideEntityEventsService,
|
||||
httpclientprovider.New,
|
||||
wire.Bind(new(httpclient.Provider), new(*sdkhttpclient.Provider)),
|
||||
serverlock.ProvideService,
|
||||
cleanup.ProvideService,
|
||||
shorturls.ProvideService,
|
||||
wire.Bind(new(shorturls.Service), new(*shorturls.ShortURLService)),
|
||||
queryhistory.ProvideService,
|
||||
wire.Bind(new(queryhistory.Service), new(*queryhistory.QueryHistoryService)),
|
||||
quotaimpl.ProvideService,
|
||||
remotecache.ProvideService,
|
||||
loginservice.ProvideService,
|
||||
wire.Bind(new(login.Service), new(*loginservice.Implementation)),
|
||||
authinfoservice.ProvideAuthInfoService,
|
||||
wire.Bind(new(login.AuthInfoService), new(*authinfoservice.Implementation)),
|
||||
authinfodatabase.ProvideAuthInfoStore,
|
||||
loginpkg.ProvideService,
|
||||
wire.Bind(new(loginpkg.Authenticator), new(*loginpkg.AuthenticatorService)),
|
||||
datasourceproxy.ProvideService,
|
||||
search.ProvideService,
|
||||
searchV2.ProvideService,
|
||||
store.ProvideService,
|
||||
export.ProvideService,
|
||||
live.ProvideService,
|
||||
pushhttp.ProvideService,
|
||||
plugincontext.ProvideService,
|
||||
contexthandler.ProvideService,
|
||||
jwt.ProvideService,
|
||||
wire.Bind(new(models.JWTService), new(*jwt.AuthService)),
|
||||
ngalert.ProvideService,
|
||||
librarypanels.ProvideService,
|
||||
wire.Bind(new(librarypanels.Service), new(*librarypanels.LibraryPanelService)),
|
||||
libraryelements.ProvideService,
|
||||
wire.Bind(new(libraryelements.Service), new(*libraryelements.LibraryElementService)),
|
||||
notifications.ProvideService,
|
||||
notifications.ProvideSmtpService,
|
||||
metrics.ProvideService,
|
||||
testdatasource.ProvideService,
|
||||
social.ProvideService,
|
||||
influxdb.ProvideService,
|
||||
wire.Bind(new(social.Service), new(*social.SocialService)),
|
||||
oauthtoken.ProvideService,
|
||||
auth.ProvideActiveAuthTokenService,
|
||||
wire.Bind(new(models.ActiveTokenService), new(*auth.ActiveAuthTokenService)),
|
||||
wire.Bind(new(oauthtoken.OAuthTokenService), new(*oauthtoken.Service)),
|
||||
tempo.ProvideService,
|
||||
loki.ProvideService,
|
||||
graphite.ProvideService,
|
||||
prometheus.ProvideService,
|
||||
elasticsearch.ProvideService,
|
||||
secretsMigrator.ProvideSecretsMigrator,
|
||||
wire.Bind(new(secrets.Migrator), new(*secretsMigrator.SecretsMigrator)),
|
||||
hooks.ProvideService,
|
||||
grafanads.ProvideService,
|
||||
wire.Bind(new(dashboardsnapshots.Store), new(*dashsnapstore.DashboardSnapshotStore)),
|
||||
dashsnapstore.ProvideStore,
|
||||
wire.Bind(new(dashboardsnapshots.Service), new(*dashsnapsvc.ServiceImpl)),
|
||||
dashsnapsvc.ProvideService,
|
||||
datasourceservice.ProvideService,
|
||||
wire.Bind(new(datasources.DataSourceService), new(*datasourceservice.Service)),
|
||||
pluginSettings.ProvideService,
|
||||
wire.Bind(new(pluginsettings.Service), new(*pluginSettings.Service)),
|
||||
alerting.ProvideService,
|
||||
database.ProvideServiceAccountsStore,
|
||||
wire.Bind(new(serviceaccounts.Store), new(*database.ServiceAccountsStoreImpl)),
|
||||
ossaccesscontrol.ProvideServiceAccountPermissions,
|
||||
wire.Bind(new(accesscontrol.ServiceAccountPermissionsService), new(*ossaccesscontrol.ServiceAccountPermissionsService)),
|
||||
serviceaccountsmanager.ProvideServiceAccountsService,
|
||||
wire.Bind(new(serviceaccounts.Service), new(*serviceaccountsmanager.ServiceAccountsService)),
|
||||
expr.ProvideService,
|
||||
teamguardianDatabase.ProvideTeamGuardianStore,
|
||||
wire.Bind(new(teamguardian.Store), new(*teamguardianDatabase.TeamGuardianStoreImpl)),
|
||||
teamguardianManager.ProvideService,
|
||||
dashboardservice.ProvideDashboardService,
|
||||
dashboardservice.ProvideFolderService,
|
||||
dashboardstore.ProvideDashboardStore,
|
||||
wire.Bind(new(dashboards.DashboardService), new(*dashboardservice.DashboardServiceImpl)),
|
||||
wire.Bind(new(dashboards.DashboardProvisioningService), new(*dashboardservice.DashboardServiceImpl)),
|
||||
wire.Bind(new(dashboards.PluginService), new(*dashboardservice.DashboardServiceImpl)),
|
||||
wire.Bind(new(dashboards.FolderService), new(*dashboardservice.FolderServiceImpl)),
|
||||
wire.Bind(new(dashboards.Store), new(*dashboardstore.DashboardStore)),
|
||||
dashboardimportservice.ProvideService,
|
||||
wire.Bind(new(dashboardimport.Service), new(*dashboardimportservice.ImportDashboardService)),
|
||||
plugindashboardsservice.ProvideService,
|
||||
wire.Bind(new(plugindashboards.Service), new(*plugindashboardsservice.Service)),
|
||||
plugindashboardsservice.ProvideDashboardUpdater,
|
||||
alerting.ProvideDashAlertExtractorService,
|
||||
wire.Bind(new(alerting.DashAlertExtractor), new(*alerting.DashAlertExtractorService)),
|
||||
comments.ProvideService,
|
||||
guardian.ProvideService,
|
||||
sanitizer.ProvideService,
|
||||
secretsStore.ProvideService,
|
||||
avatar.ProvideAvatarCacheServer,
|
||||
authproxy.ProvideAuthProxy,
|
||||
statscollector.ProvideService,
|
||||
cmreg.CoremodelSet,
|
||||
cuectx.ProvideCUEContext,
|
||||
cuectx.ProvideThemaLibrary,
|
||||
csrf.ProvideCSRFFilter,
|
||||
ossaccesscontrol.ProvideTeamPermissions,
|
||||
wire.Bind(new(accesscontrol.TeamPermissionsService), new(*ossaccesscontrol.TeamPermissionsService)),
|
||||
ossaccesscontrol.ProvideFolderPermissions,
|
||||
wire.Bind(new(accesscontrol.FolderPermissionsService), new(*ossaccesscontrol.FolderPermissionsService)),
|
||||
ossaccesscontrol.ProvideDashboardPermissions,
|
||||
wire.Bind(new(accesscontrol.DashboardPermissionsService), new(*ossaccesscontrol.DashboardPermissionsService)),
|
||||
starimpl.ProvideService,
|
||||
playlistimpl.ProvideService,
|
||||
dashverimpl.ProvideService,
|
||||
publicdashboardsService.ProvideService,
|
||||
wire.Bind(new(publicdashboards.Service), new(*publicdashboardsService.PublicDashboardServiceImpl)),
|
||||
publicdashboardsStore.ProvideStore,
|
||||
wire.Bind(new(publicdashboards.Store), new(*publicdashboardsStore.PublicDashboardStoreImpl)),
|
||||
publicdashboardsApi.ProvideApi,
|
||||
userimpl.ProvideService,
|
||||
orgimpl.ProvideService,
|
||||
datasourceservice.ProvideDataSourceMigrationService,
|
||||
secretsStore.ProvidePluginSecretMigrationService,
|
||||
secretsMigrations.ProvideSecretMigrationService,
|
||||
wire.Bind(new(secretsMigrations.SecretMigrationService), new(*secretsMigrations.SecretMigrationServiceImpl)),
|
||||
userauthimpl.ProvideService,
|
||||
ngmetrics.ProvideServiceForTest,
|
||||
wire.Bind(new(alerting.AlertStore), new(*sqlstore.SQLStore)),
|
||||
wire.Bind(new(sqlstore.TeamStore), new(*sqlstore.SQLStore)),
|
||||
notifications.MockNotificationService,
|
||||
wire.Bind(new(notifications.TempUserStore), new(*mockstore.SQLStoreMock)),
|
||||
wire.Bind(new(notifications.Service), new(*notifications.NotificationServiceMock)),
|
||||
wire.Bind(new(notifications.WebhookSender), new(*notifications.NotificationServiceMock)),
|
||||
wire.Bind(new(notifications.EmailSender), new(*notifications.NotificationServiceMock)),
|
||||
mockstore.NewSQLStoreMock,
|
||||
wire.Bind(new(sqlstore.Store), new(*sqlstore.SQLStore)),
|
||||
wire.Bind(new(db.DB), new(*sqlstore.SQLStore)),
|
||||
prefimpl.ProvideService,
|
||||
)
|
||||
|
||||
func Initialize(cfg *setting.Cfg) (Runner, error) {
|
||||
|
@ -5,14 +5,37 @@ package runner
|
||||
|
||||
import (
|
||||
"github.com/google/wire"
|
||||
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/plugins/backendplugin/provider"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/signature"
|
||||
"github.com/grafana/grafana/pkg/registry"
|
||||
"github.com/grafana/grafana/pkg/server/backgroundsvcs"
|
||||
"github.com/grafana/grafana/pkg/server/usagestatssvcs"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
acdb "github.com/grafana/grafana/pkg/services/accesscontrol/database"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/ossaccesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/resourcepermissions"
|
||||
"github.com/grafana/grafana/pkg/services/auth"
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
"github.com/grafana/grafana/pkg/services/datasources/permissions"
|
||||
datasourceservice "github.com/grafana/grafana/pkg/services/datasources/service"
|
||||
"github.com/grafana/grafana/pkg/services/encryption"
|
||||
encryptionprovider "github.com/grafana/grafana/pkg/services/encryption/provider"
|
||||
"github.com/grafana/grafana/pkg/services/kmsproviders"
|
||||
"github.com/grafana/grafana/pkg/services/kmsproviders/osskmsproviders"
|
||||
"github.com/grafana/grafana/pkg/services/ldap"
|
||||
"github.com/grafana/grafana/pkg/services/licensing"
|
||||
"github.com/grafana/grafana/pkg/services/login"
|
||||
"github.com/grafana/grafana/pkg/services/login/authinfoservice"
|
||||
"github.com/grafana/grafana/pkg/services/provisioning"
|
||||
"github.com/grafana/grafana/pkg/services/searchusers"
|
||||
"github.com/grafana/grafana/pkg/services/searchusers/filters"
|
||||
secretsStore "github.com/grafana/grafana/pkg/services/secrets/kvstore"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/migrations"
|
||||
"github.com/grafana/grafana/pkg/services/thumbs"
|
||||
"github.com/grafana/grafana/pkg/services/validations"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
@ -26,6 +49,47 @@ var wireExtsSet = wire.NewSet(
|
||||
wire.Bind(new(setting.Provider), new(*setting.OSSImpl)),
|
||||
osskmsproviders.ProvideService,
|
||||
wire.Bind(new(kmsproviders.Service), new(osskmsproviders.Service)),
|
||||
// ossencryption.ProvideService,
|
||||
// wire.Bind(new(encryption.Internal), new(*ossencryption.Service)),
|
||||
auth.ProvideUserAuthTokenService,
|
||||
wire.Bind(new(models.UserTokenService), new(*auth.UserAuthTokenService)),
|
||||
wire.Bind(new(models.UserTokenBackgroundService), new(*auth.UserAuthTokenService)),
|
||||
ossaccesscontrol.ProvideService,
|
||||
wire.Bind(new(accesscontrol.RoleRegistry), new(*ossaccesscontrol.OSSAccessControlService)),
|
||||
wire.Bind(new(accesscontrol.AccessControl), new(*ossaccesscontrol.OSSAccessControlService)),
|
||||
thumbs.ProvideCrawlerAuthSetupService,
|
||||
wire.Bind(new(thumbs.CrawlerAuthSetupService), new(*thumbs.OSSCrawlerAuthSetupService)),
|
||||
validations.ProvideValidator,
|
||||
wire.Bind(new(models.PluginRequestValidator), new(*validations.OSSPluginRequestValidator)),
|
||||
provisioning.ProvideService,
|
||||
wire.Bind(new(provisioning.ProvisioningService), new(*provisioning.ProvisioningServiceImpl)),
|
||||
backgroundsvcs.ProvideBackgroundServiceRegistry,
|
||||
wire.Bind(new(registry.BackgroundServiceRegistry), new(*backgroundsvcs.BackgroundServiceRegistry)),
|
||||
datasourceservice.ProvideCacheService,
|
||||
wire.Bind(new(datasources.CacheService), new(*datasourceservice.CacheServiceImpl)),
|
||||
authinfoservice.ProvideOSSUserProtectionService,
|
||||
wire.Bind(new(login.UserProtectionService), new(*authinfoservice.OSSUserProtectionImpl)),
|
||||
filters.ProvideOSSSearchUserFilter,
|
||||
wire.Bind(new(models.SearchUserFilter), new(*filters.OSSSearchUserFilter)),
|
||||
searchusers.ProvideUsersService,
|
||||
wire.Bind(new(searchusers.Service), new(*searchusers.OSSService)),
|
||||
signature.ProvideOSSAuthorizer,
|
||||
wire.Bind(new(plugins.PluginLoaderAuthorizer), new(*signature.UnsignedPluginAuthorizer)),
|
||||
provider.ProvideService,
|
||||
wire.Bind(new(plugins.BackendFactoryProvider), new(*provider.Service)),
|
||||
acdb.ProvideService,
|
||||
wire.Bind(new(resourcepermissions.Store), new(*acdb.AccessControlStore)),
|
||||
wire.Bind(new(accesscontrol.PermissionsStore), new(*acdb.AccessControlStore)),
|
||||
ldap.ProvideGroupsService,
|
||||
wire.Bind(new(ldap.Groups), new(*ldap.OSSGroups)),
|
||||
permissions.ProvideDatasourcePermissionsService,
|
||||
wire.Bind(new(permissions.DatasourcePermissionsService), new(*permissions.OSSDatasourcePermissionsService)),
|
||||
usagestatssvcs.ProvideUsageStatsProvidersRegistry,
|
||||
wire.Bind(new(registry.UsageStatsProvidersRegistry), new(*usagestatssvcs.UsageStatsProvidersRegistry)),
|
||||
ossaccesscontrol.ProvideDatasourcePermissionsService,
|
||||
wire.Bind(new(accesscontrol.DatasourcePermissionsService), new(*ossaccesscontrol.DatasourcePermissionsService)),
|
||||
secretsStore.ProvideRemotePluginCheck,
|
||||
wire.Bind(new(secretsStore.UseRemoteSecretsPluginCheck), new(*secretsStore.OSSRemoteSecretsPluginCheck)),
|
||||
encryptionprovider.ProvideEncryptionProvider,
|
||||
wire.Bind(new(encryption.Provider), new(encryptionprovider.Provider)),
|
||||
)
|
||||
|
@ -197,7 +197,6 @@ var wireBasicSet = wire.NewSet(
|
||||
authinfoservice.ProvideAuthInfoService,
|
||||
wire.Bind(new(login.AuthInfoService), new(*authinfoservice.Implementation)),
|
||||
authinfodatabase.ProvideAuthInfoStore,
|
||||
wire.Bind(new(login.Store), new(*authinfodatabase.AuthInfoStore)),
|
||||
loginpkg.ProvideService,
|
||||
wire.Bind(new(loginpkg.Authenticator), new(*loginpkg.AuthenticatorService)),
|
||||
datasourceproxy.ProvideService,
|
||||
|
@ -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
pkg/services/login/model.go
Normal file
32
pkg/services/login/model.go
Normal 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
|
||||
)
|
@ -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)
|
||||
}
|
||||
|
@ -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, ", "))
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user