Chore: Remove bus.Dispatch from some login packages (#47248)

* Chore: Remove bus.Dispatch from some login packages

* remove debug log

Co-authored-by: Emil Tullstedt <emil.tullstedt@grafana.com>

* remove login.Init()

* remove unused reset function

* remove AuthenticateUserFunc global

* swap conditional branches

Co-authored-by: Emil Tullstedt <emil.tullstedt@grafana.com>

* fix formatting

Co-authored-by: Emil Tullstedt <emil.tullstedt@grafana.com>
This commit is contained in:
Serge Zaitsev
2022-04-04 20:36:15 +02:00
committed by GitHub
parent 6992d17924
commit 33006436cc
21 changed files with 190 additions and 143 deletions

View File

@ -13,6 +13,7 @@ import (
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/auth" "github.com/grafana/grafana/pkg/services/auth"
"github.com/grafana/grafana/pkg/services/login/loginservice" "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"
"github.com/grafana/grafana/pkg/services/sqlstore/mockstore" "github.com/grafana/grafana/pkg/services/sqlstore/mockstore"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
@ -27,28 +28,6 @@ const (
existingTestLogin = "existing@example.com" existingTestLogin = "existing@example.com"
) )
type mockAuthInfoService struct {
LatestUserID int64
ExpectedError error
}
func (m *mockAuthInfoService) LookupAndUpdate(ctx context.Context, query *models.GetUserByAuthInfoQuery) (*models.User, error) {
m.LatestUserID = query.UserId
return nil, m.ExpectedError
}
func (m *mockAuthInfoService) GetAuthInfo(ctx context.Context, query *models.GetAuthInfoQuery) error {
m.LatestUserID = query.UserId
return m.ExpectedError
}
func (m *mockAuthInfoService) SetAuthInfo(ctx context.Context, query *models.SetAuthInfoCommand) error {
return m.ExpectedError
}
func (m *mockAuthInfoService) UpdateAuthInfo(ctx context.Context, query *models.UpdateAuthInfoCommand) error {
return m.ExpectedError
}
func TestAdminAPIEndpoint(t *testing.T) { func TestAdminAPIEndpoint(t *testing.T) {
const role = models.ROLE_ADMIN const role = models.ROLE_ADMIN
@ -282,7 +261,7 @@ func putAdminScenario(t *testing.T, desc string, url string, routePattern string
hs := &HTTPServer{ hs := &HTTPServer{
Cfg: setting.NewCfg(), Cfg: setting.NewCfg(),
SQLStore: sqlStore, SQLStore: sqlStore,
authInfoService: &mockAuthInfoService{}, authInfoService: &logintest.AuthInfoServiceFake{},
} }
sc := setupScenarioContext(t, url) sc := setupScenarioContext(t, url)
@ -397,7 +376,7 @@ func adminDisableUserScenario(t *testing.T, desc string, action string, url stri
fakeAuthTokenService := auth.NewFakeUserAuthTokenService() fakeAuthTokenService := auth.NewFakeUserAuthTokenService()
authInfoService := &mockAuthInfoService{} authInfoService := &logintest.AuthInfoServiceFake{}
hs := HTTPServer{ hs := HTTPServer{
Bus: bus.GetBus(), Bus: bus.GetBus(),
@ -435,7 +414,7 @@ func adminDeleteUserScenario(t *testing.T, desc string, url string, routePattern
sc := setupScenarioContext(t, url) sc := setupScenarioContext(t, url)
sc.sqlStore = hs.SQLStore sc.sqlStore = hs.SQLStore
sc.authInfoService = &mockAuthInfoService{} sc.authInfoService = &logintest.AuthInfoServiceFake{}
sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response { sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response {
sc.context = c sc.context = c
sc.context.UserId = testUserID sc.context.UserId = testUserID

View File

@ -34,6 +34,7 @@ import (
"github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/ldap" "github.com/grafana/grafana/pkg/services/ldap"
"github.com/grafana/grafana/pkg/services/login/loginservice" "github.com/grafana/grafana/pkg/services/login/loginservice"
"github.com/grafana/grafana/pkg/services/login/logintest"
"github.com/grafana/grafana/pkg/services/quota" "github.com/grafana/grafana/pkg/services/quota"
"github.com/grafana/grafana/pkg/services/rendering" "github.com/grafana/grafana/pkg/services/rendering"
"github.com/grafana/grafana/pkg/services/searchusers" "github.com/grafana/grafana/pkg/services/searchusers"
@ -168,7 +169,7 @@ type scenarioContext struct {
url string url string
userAuthTokenService *auth.FakeUserAuthTokenService userAuthTokenService *auth.FakeUserAuthTokenService
sqlStore sqlstore.Store sqlStore sqlstore.Store
authInfoService *mockAuthInfoService authInfoService *logintest.AuthInfoServiceFake
} }
func (sc *scenarioContext) exec() { func (sc *scenarioContext) exec() {

View File

@ -21,6 +21,7 @@ import (
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/remotecache" "github.com/grafana/grafana/pkg/infra/remotecache"
"github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/infra/tracing"
loginpkg "github.com/grafana/grafana/pkg/login"
"github.com/grafana/grafana/pkg/login/social" "github.com/grafana/grafana/pkg/login/social"
"github.com/grafana/grafana/pkg/middleware" "github.com/grafana/grafana/pkg/middleware"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
@ -134,6 +135,7 @@ type HTTPServer struct {
queryDataService *query.Service queryDataService *query.Service
serviceAccountsService serviceaccounts.Service serviceAccountsService serviceaccounts.Service
authInfoService login.AuthInfoService authInfoService login.AuthInfoService
authenticator loginpkg.Authenticator
teamPermissionsService accesscontrol.PermissionsService teamPermissionsService accesscontrol.PermissionsService
permissionServices accesscontrol.PermissionsServices permissionServices accesscontrol.PermissionsServices
NotificationService *notifications.NotificationService NotificationService *notifications.NotificationService
@ -160,7 +162,7 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi
dataSourceCache datasources.CacheService, userTokenService models.UserTokenService, dataSourceCache datasources.CacheService, userTokenService models.UserTokenService,
cleanUpService *cleanup.CleanUpService, shortURLService shorturls.Service, queryHistoryService queryhistory.Service, cleanUpService *cleanup.CleanUpService, shortURLService shorturls.Service, queryHistoryService queryhistory.Service,
thumbService thumbs.Service, remoteCache *remotecache.RemoteCache, provisioningService provisioning.ProvisioningService, thumbService thumbs.Service, remoteCache *remotecache.RemoteCache, provisioningService provisioning.ProvisioningService,
loginService login.Service, accessControl accesscontrol.AccessControl, loginService login.Service, authenticator loginpkg.Authenticator, accessControl accesscontrol.AccessControl,
dataSourceProxy *datasourceproxy.DataSourceProxyService, searchService *search.SearchService, dataSourceProxy *datasourceproxy.DataSourceProxyService, searchService *search.SearchService,
live *live.GrafanaLive, livePushGateway *pushhttp.Gateway, plugCtxProvider *plugincontext.Provider, live *live.GrafanaLive, livePushGateway *pushhttp.Gateway, plugCtxProvider *plugincontext.Provider,
contextHandler *contexthandler.ContextHandler, features *featuremgmt.FeatureManager, contextHandler *contexthandler.ContextHandler, features *featuremgmt.FeatureManager,
@ -236,6 +238,7 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi
queryDataService: queryDataService, queryDataService: queryDataService,
serviceAccountsService: serviceaccountsService, serviceAccountsService: serviceaccountsService,
authInfoService: authInfoService, authInfoService: authInfoService,
authenticator: authenticator,
NotificationService: notificationService, NotificationService: notificationService,
dashboardService: dashboardService, dashboardService: dashboardService,
dashboardProvisioningService: dashboardProvisioningService, dashboardProvisioningService: dashboardProvisioningService,

View File

@ -9,7 +9,6 @@ import (
"github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/login"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/ldap" "github.com/grafana/grafana/pkg/services/ldap"
"github.com/grafana/grafana/pkg/services/multildap" "github.com/grafana/grafana/pkg/services/multildap"
@ -199,7 +198,7 @@ func (hs *HTTPServer) PostSyncUserWithLDAP(c *models.ReqContext) response.Respon
} }
// Since the user was not in the LDAP server. Let's disable it. // Since the user was not in the LDAP server. Let's disable it.
err := login.DisableExternalUser(c.Req.Context(), query.Result.Login) err := hs.Login.DisableExternalUser(c.Req.Context(), query.Result.Login)
if err != nil { if err != nil {
return response.Error(http.StatusInternalServerError, "Failed to disable the user", err) return response.Error(http.StatusInternalServerError, "Failed to disable the user", err)
} }

View File

@ -1,7 +1,6 @@
package api package api
import ( import (
"context"
"errors" "errors"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
@ -10,12 +9,12 @@ import (
"github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/login/loginservice" "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"
"github.com/grafana/grafana/pkg/services/sqlstore/mockstore" "github.com/grafana/grafana/pkg/services/sqlstore/mockstore"
"github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/api/routing" "github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/auth" "github.com/grafana/grafana/pkg/services/auth"
"github.com/grafana/grafana/pkg/services/ldap" "github.com/grafana/grafana/pkg/services/ldap"
@ -368,6 +367,7 @@ func postSyncUserWithLDAPContext(t *testing.T, requestURL string, preHook func(*
t.Helper() t.Helper()
sc := setupScenarioContext(t, requestURL) sc := setupScenarioContext(t, requestURL)
sc.authInfoService = &logintest.AuthInfoServiceFake{}
ldap := setting.LDAPEnabled ldap := setting.LDAPEnabled
t.Cleanup(func() { t.Cleanup(func() {
@ -380,7 +380,7 @@ func postSyncUserWithLDAPContext(t *testing.T, requestURL string, preHook func(*
AuthTokenService: auth.NewFakeUserAuthTokenService(), AuthTokenService: auth.NewFakeUserAuthTokenService(),
SQLStore: sqlstoremock, SQLStore: sqlstoremock,
Login: loginservice.LoginServiceMock{}, Login: loginservice.LoginServiceMock{},
authInfoService: &mockAuthInfoService{}, authInfoService: sc.authInfoService,
} }
sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response { sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response {
@ -483,6 +483,7 @@ func TestPostSyncUserWithLDAPAPIEndpoint_WhenGrafanaAdmin(t *testing.T) {
func TestPostSyncUserWithLDAPAPIEndpoint_WhenUserNotInLDAP(t *testing.T) { func TestPostSyncUserWithLDAPAPIEndpoint_WhenUserNotInLDAP(t *testing.T) {
sqlstoremock := mockstore.SQLStoreMock{ExpectedUser: &models.User{Login: "ldap-daniel", Id: 34}} sqlstoremock := mockstore.SQLStoreMock{ExpectedUser: &models.User{Login: "ldap-daniel", Id: 34}}
sc := postSyncUserWithLDAPContext(t, "/api/admin/ldap/sync/34", func(t *testing.T, sc *scenarioContext) { 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) { getLDAPConfig = func(*setting.Cfg) (*ldap.Config, error) {
return &ldap.Config{}, nil return &ldap.Config{}, nil
} }
@ -492,18 +493,7 @@ func TestPostSyncUserWithLDAPAPIEndpoint_WhenUserNotInLDAP(t *testing.T) {
} }
userSearchResult = nil userSearchResult = nil
userSearchError = multildap.ErrDidNotFindUser
bus.AddHandler("test", func(ctx context.Context, q *models.GetExternalUserInfoByLoginQuery) error {
assert.Equal(t, "ldap-daniel", q.LoginOrEmail)
q.Result = &models.ExternalUserInfo{IsDisabled: true, UserId: 34}
return nil
})
bus.AddHandler("test", func(ctx context.Context, cmd *models.DisableUserCommand) error {
assert.Equal(t, 34, cmd.UserId)
return nil
})
}, &sqlstoremock) }, &sqlstoremock)
assert.Equal(t, http.StatusBadRequest, sc.resp.Code) assert.Equal(t, http.StatusBadRequest, sc.resp.Code)
@ -616,7 +606,7 @@ func TestLDAP_AccessControl(t *testing.T) {
cfg.LDAPEnabled = true cfg.LDAPEnabled = true
sc, hs := setupAccessControlScenarioContext(t, cfg, test.url, test.permissions) sc, hs := setupAccessControlScenarioContext(t, cfg, test.url, test.permissions)
hs.SQLStore = &mockstore.SQLStoreMock{ExpectedUser: &models.User{}} hs.SQLStore = &mockstore.SQLStoreMock{ExpectedUser: &models.User{}}
hs.authInfoService = &mockAuthInfoService{} hs.authInfoService = &logintest.AuthInfoServiceFake{}
hs.Login = &loginservice.LoginServiceMock{} hs.Login = &loginservice.LoginServiceMock{}
sc.resp = httptest.NewRecorder() sc.resp = httptest.NewRecorder()
sc.req, err = http.NewRequest(test.method, test.url, nil) sc.req, err = http.NewRequest(test.method, test.url, nil)

View File

@ -209,7 +209,7 @@ func (hs *HTTPServer) LoginPost(c *models.ReqContext) response.Response {
Cfg: hs.Cfg, Cfg: hs.Cfg,
} }
err := login.AuthenticateUserFunc(c.Req.Context(), authQuery) err := hs.authenticator.AuthenticateUser(c.Req.Context(), authQuery)
authModule = authQuery.AuthModule authModule = authQuery.AuthModule
if err != nil { if err != nil {
resp = response.Error(401, "Invalid username or password", err) resp = response.Error(401, "Invalid username or password", err)

View File

@ -351,8 +351,7 @@ func TestLoginPostRedirect(t *testing.T) {
Email: "", Email: "",
} }
mockAuthenticateUserFunc(user, "", nil) hs.authenticator = &fakeAuthenticator{user, "", nil}
t.Cleanup(resetAuthenticateUserFunc)
redirectCases := []redirectCase{ redirectCases := []redirectCase{
{ {
@ -684,8 +683,7 @@ func TestLoginPostRunLokingHook(t *testing.T) {
for _, c := range testCases { for _, c := range testCases {
t.Run(c.desc, func(t *testing.T) { t.Run(c.desc, func(t *testing.T) {
mockAuthenticateUserFunc(c.authUser, c.authModule, c.authErr) hs.authenticator = &fakeAuthenticator{c.authUser, c.authModule, c.authErr}
t.Cleanup(resetAuthenticateUserFunc)
sc.m.Post(sc.url, sc.defaultHandler) sc.m.Post(sc.url, sc.defaultHandler)
sc.fakeReqNoAssertions("POST", sc.url).exec() sc.fakeReqNoAssertions("POST", sc.url).exec()
@ -732,13 +730,14 @@ func (m *mockSocialService) GetConnector(string) (social.SocialConnector, error)
return m.socialConnector, m.err return m.socialConnector, m.err
} }
func mockAuthenticateUserFunc(user *models.User, authmodule string, err error) { type fakeAuthenticator struct {
login.AuthenticateUserFunc = func(ctx context.Context, query *models.LoginUserQuery) error { ExpectedUser *models.User
query.User = user ExpectedAuthModule string
query.AuthModule = authmodule ExpectedError error
return err
}
} }
func resetAuthenticateUserFunc() {
login.AuthenticateUserFunc = login.AuthenticateUser func (fa *fakeAuthenticator) AuthenticateUser(c context.Context, query *models.LoginUserQuery) error {
query.User = fa.ExpectedUser
query.AuthModule = fa.ExpectedAuthModule
return fa.ExpectedError
} }

View File

@ -8,6 +8,8 @@ import (
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/ldap" "github.com/grafana/grafana/pkg/services/ldap"
"github.com/grafana/grafana/pkg/services/login"
"github.com/grafana/grafana/pkg/services/sqlstore"
) )
var ( var (
@ -25,14 +27,26 @@ var (
var loginLogger = log.New("login") var loginLogger = log.New("login")
var AuthenticateUserFunc = AuthenticateUser type Authenticator interface {
AuthenticateUser(context.Context, *models.LoginUserQuery) error
}
func Init() { type AuthenticatorService struct {
bus.AddHandler("auth", AuthenticateUser) store sqlstore.Store
loginService login.Service
}
func ProvideService(store sqlstore.Store, loginService login.Service) *AuthenticatorService {
a := &AuthenticatorService{
store: store,
loginService: loginService,
}
bus.AddHandler("auth", a.AuthenticateUser)
return a
} }
// AuthenticateUser authenticates the user via username & password // AuthenticateUser authenticates the user via username & password
func AuthenticateUser(ctx context.Context, query *models.LoginUserQuery) error { func (a *AuthenticatorService) AuthenticateUser(ctx context.Context, query *models.LoginUserQuery) error {
if err := validateLoginAttempts(ctx, query); err != nil { if err := validateLoginAttempts(ctx, query); err != nil {
return err return err
} }
@ -48,7 +62,7 @@ func AuthenticateUser(ctx context.Context, query *models.LoginUserQuery) error {
return err return err
} }
ldapEnabled, ldapErr := loginUsingLDAP(ctx, query) ldapEnabled, ldapErr := loginUsingLDAP(ctx, query, a.loginService)
if ldapEnabled { if ldapEnabled {
query.AuthModule = models.AuthModuleLDAP query.AuthModule = models.AuthModuleLDAP
if ldapErr == nil || !errors.Is(ldapErr, ldap.ErrInvalidCredentials) { if ldapErr == nil || !errors.Is(ldapErr, ldap.ErrInvalidCredentials) {

View File

@ -7,6 +7,9 @@ import (
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/ldap" "github.com/grafana/grafana/pkg/services/ldap"
"github.com/grafana/grafana/pkg/services/login"
"github.com/grafana/grafana/pkg/services/login/logintest"
"github.com/grafana/grafana/pkg/services/sqlstore/mockstore"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -21,7 +24,8 @@ func TestAuthenticateUser(t *testing.T) {
Username: "user", Username: "user",
Password: "", Password: "",
} }
err := AuthenticateUserFunc(context.Background(), &loginQuery) a := AuthenticatorService{store: mockstore.NewSQLStoreMock(), loginService: &logintest.LoginServiceFake{}}
err := a.AuthenticateUser(context.Background(), &loginQuery)
require.EqualError(t, err, ErrPasswordEmpty.Error()) require.EqualError(t, err, ErrPasswordEmpty.Error())
assert.False(t, sc.grafanaLoginWasCalled) assert.False(t, sc.grafanaLoginWasCalled)
@ -35,7 +39,8 @@ func TestAuthenticateUser(t *testing.T) {
mockLoginUsingLDAP(true, nil, sc) mockLoginUsingLDAP(true, nil, sc)
mockSaveInvalidLoginAttempt(sc) mockSaveInvalidLoginAttempt(sc)
err := AuthenticateUserFunc(context.Background(), sc.loginUserQuery) a := AuthenticatorService{store: mockstore.NewSQLStoreMock(), loginService: &logintest.LoginServiceFake{}}
err := a.AuthenticateUser(context.Background(), sc.loginUserQuery)
require.EqualError(t, err, ErrTooManyLoginAttempts.Error()) require.EqualError(t, err, ErrTooManyLoginAttempts.Error())
assert.True(t, sc.loginAttemptValidationWasCalled) assert.True(t, sc.loginAttemptValidationWasCalled)
@ -51,7 +56,8 @@ func TestAuthenticateUser(t *testing.T) {
mockLoginUsingLDAP(true, ErrInvalidCredentials, sc) mockLoginUsingLDAP(true, ErrInvalidCredentials, sc)
mockSaveInvalidLoginAttempt(sc) mockSaveInvalidLoginAttempt(sc)
err := AuthenticateUserFunc(context.Background(), sc.loginUserQuery) a := AuthenticatorService{store: mockstore.NewSQLStoreMock(), loginService: &logintest.LoginServiceFake{}}
err := a.AuthenticateUser(context.Background(), sc.loginUserQuery)
require.NoError(t, err) require.NoError(t, err)
assert.True(t, sc.loginAttemptValidationWasCalled) assert.True(t, sc.loginAttemptValidationWasCalled)
@ -68,7 +74,8 @@ func TestAuthenticateUser(t *testing.T) {
mockLoginUsingLDAP(true, ErrInvalidCredentials, sc) mockLoginUsingLDAP(true, ErrInvalidCredentials, sc)
mockSaveInvalidLoginAttempt(sc) mockSaveInvalidLoginAttempt(sc)
err := AuthenticateUserFunc(context.Background(), sc.loginUserQuery) a := AuthenticatorService{store: mockstore.NewSQLStoreMock(), loginService: &logintest.LoginServiceFake{}}
err := a.AuthenticateUser(context.Background(), sc.loginUserQuery)
require.EqualError(t, err, customErr.Error()) require.EqualError(t, err, customErr.Error())
assert.True(t, sc.loginAttemptValidationWasCalled) assert.True(t, sc.loginAttemptValidationWasCalled)
@ -84,7 +91,8 @@ func TestAuthenticateUser(t *testing.T) {
mockLoginUsingLDAP(false, nil, sc) mockLoginUsingLDAP(false, nil, sc)
mockSaveInvalidLoginAttempt(sc) mockSaveInvalidLoginAttempt(sc)
err := AuthenticateUserFunc(context.Background(), sc.loginUserQuery) a := AuthenticatorService{store: mockstore.NewSQLStoreMock(), loginService: &logintest.LoginServiceFake{}}
err := a.AuthenticateUser(context.Background(), sc.loginUserQuery)
require.EqualError(t, err, models.ErrUserNotFound.Error()) require.EqualError(t, err, models.ErrUserNotFound.Error())
assert.True(t, sc.loginAttemptValidationWasCalled) assert.True(t, sc.loginAttemptValidationWasCalled)
@ -100,7 +108,8 @@ func TestAuthenticateUser(t *testing.T) {
mockLoginUsingLDAP(true, ldap.ErrInvalidCredentials, sc) mockLoginUsingLDAP(true, ldap.ErrInvalidCredentials, sc)
mockSaveInvalidLoginAttempt(sc) mockSaveInvalidLoginAttempt(sc)
err := AuthenticateUserFunc(context.Background(), sc.loginUserQuery) a := AuthenticatorService{store: mockstore.NewSQLStoreMock(), loginService: &logintest.LoginServiceFake{}}
err := a.AuthenticateUser(context.Background(), sc.loginUserQuery)
require.EqualError(t, err, ErrInvalidCredentials.Error()) require.EqualError(t, err, ErrInvalidCredentials.Error())
assert.True(t, sc.loginAttemptValidationWasCalled) assert.True(t, sc.loginAttemptValidationWasCalled)
@ -116,7 +125,8 @@ func TestAuthenticateUser(t *testing.T) {
mockLoginUsingLDAP(true, nil, sc) mockLoginUsingLDAP(true, nil, sc)
mockSaveInvalidLoginAttempt(sc) mockSaveInvalidLoginAttempt(sc)
err := AuthenticateUserFunc(context.Background(), sc.loginUserQuery) a := AuthenticatorService{store: mockstore.NewSQLStoreMock(), loginService: &logintest.LoginServiceFake{}}
err := a.AuthenticateUser(context.Background(), sc.loginUserQuery)
require.NoError(t, err) require.NoError(t, err)
assert.True(t, sc.loginAttemptValidationWasCalled) assert.True(t, sc.loginAttemptValidationWasCalled)
@ -133,7 +143,8 @@ func TestAuthenticateUser(t *testing.T) {
mockLoginUsingLDAP(true, customErr, sc) mockLoginUsingLDAP(true, customErr, sc)
mockSaveInvalidLoginAttempt(sc) mockSaveInvalidLoginAttempt(sc)
err := AuthenticateUserFunc(context.Background(), sc.loginUserQuery) a := AuthenticatorService{store: mockstore.NewSQLStoreMock(), loginService: &logintest.LoginServiceFake{}}
err := a.AuthenticateUser(context.Background(), sc.loginUserQuery)
require.EqualError(t, err, customErr.Error()) require.EqualError(t, err, customErr.Error())
assert.True(t, sc.loginAttemptValidationWasCalled) assert.True(t, sc.loginAttemptValidationWasCalled)
@ -149,7 +160,8 @@ func TestAuthenticateUser(t *testing.T) {
mockLoginUsingLDAP(true, ldap.ErrInvalidCredentials, sc) mockLoginUsingLDAP(true, ldap.ErrInvalidCredentials, sc)
mockSaveInvalidLoginAttempt(sc) mockSaveInvalidLoginAttempt(sc)
err := AuthenticateUserFunc(context.Background(), sc.loginUserQuery) a := AuthenticatorService{store: mockstore.NewSQLStoreMock(), loginService: &logintest.LoginServiceFake{}}
err := a.AuthenticateUser(context.Background(), sc.loginUserQuery)
require.EqualError(t, err, ErrInvalidCredentials.Error()) require.EqualError(t, err, ErrInvalidCredentials.Error())
assert.True(t, sc.loginAttemptValidationWasCalled) assert.True(t, sc.loginAttemptValidationWasCalled)
@ -177,7 +189,7 @@ func mockLoginUsingGrafanaDB(err error, sc *authScenarioContext) {
} }
func mockLoginUsingLDAP(enabled bool, err error, sc *authScenarioContext) { func mockLoginUsingLDAP(enabled bool, err error, sc *authScenarioContext) {
loginUsingLDAP = func(ctx context.Context, query *models.LoginUserQuery) (bool, error) { loginUsingLDAP = func(ctx context.Context, query *models.LoginUserQuery, _ login.Service) (bool, error) {
sc.ldapLoginWasCalled = true sc.ldapLoginWasCalled = true
return enabled, err return enabled, err
} }

View File

@ -8,6 +8,7 @@ import (
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/ldap" "github.com/grafana/grafana/pkg/services/ldap"
"github.com/grafana/grafana/pkg/services/login"
"github.com/grafana/grafana/pkg/services/multildap" "github.com/grafana/grafana/pkg/services/multildap"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util/errutil" "github.com/grafana/grafana/pkg/util/errutil"
@ -27,7 +28,7 @@ var ldapLogger = log.New("login.ldap")
// loginUsingLDAP logs in user using LDAP. It returns whether LDAP is enabled and optional error and query arg will be // loginUsingLDAP logs in user using LDAP. It returns whether LDAP is enabled and optional error and query arg will be
// populated with the logged in user if successful. // populated with the logged in user if successful.
var loginUsingLDAP = func(ctx context.Context, query *models.LoginUserQuery) (bool, error) { var loginUsingLDAP = func(ctx context.Context, query *models.LoginUserQuery, loginService login.Service) (bool, error) {
enabled := isLDAPEnabled() enabled := isLDAPEnabled()
if !enabled { if !enabled {
@ -43,7 +44,7 @@ var loginUsingLDAP = func(ctx context.Context, query *models.LoginUserQuery) (bo
if err != nil { if err != nil {
if errors.Is(err, ldap.ErrCouldNotFindUser) { if errors.Is(err, ldap.ErrCouldNotFindUser) {
// Ignore the error since user might not be present anyway // Ignore the error since user might not be present anyway
if err := DisableExternalUser(ctx, query.Username); err != nil { if err := loginService.DisableExternalUser(ctx, query.Username); err != nil {
ldapLogger.Debug("Failed to disable external user", "err", err) ldapLogger.Debug("Failed to disable external user", "err", err)
} }
@ -66,42 +67,3 @@ var loginUsingLDAP = func(ctx context.Context, query *models.LoginUserQuery) (bo
return true, nil return true, nil
} }
// DisableExternalUser marks external user as disabled in Grafana db
func DisableExternalUser(ctx context.Context, username string) error {
// Check if external user exist in Grafana
userQuery := &models.GetExternalUserInfoByLoginQuery{
LoginOrEmail: username,
}
if err := bus.Dispatch(ctx, userQuery); err != nil {
return err
}
userInfo := userQuery.Result
if !userInfo.IsDisabled {
ldapLogger.Debug(
"Disabling external user",
"user",
userQuery.Result.Login,
)
// Mark user as disabled in grafana db
disableUserCmd := &models.DisableUserCommand{
UserId: userQuery.Result.UserId,
IsDisabled: true,
}
if err := bus.Dispatch(ctx, disableUserCmd); err != nil {
ldapLogger.Debug(
"Error disabling external user",
"user",
userQuery.Result.Login,
"message",
err.Error(),
)
return err
}
}
return nil
}

View File

@ -7,6 +7,7 @@ import (
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/ldap" "github.com/grafana/grafana/pkg/services/ldap"
"github.com/grafana/grafana/pkg/services/login/logintest"
"github.com/grafana/grafana/pkg/services/multildap" "github.com/grafana/grafana/pkg/services/multildap"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -28,7 +29,8 @@ func TestLoginUsingLDAP(t *testing.T) {
return config, nil return config, nil
} }
enabled, err := loginUsingLDAP(context.Background(), sc.loginUserQuery) loginService := &logintest.LoginServiceFake{}
enabled, err := loginUsingLDAP(context.Background(), sc.loginUserQuery, loginService)
require.EqualError(t, err, errTest.Error()) require.EqualError(t, err, errTest.Error())
assert.True(t, enabled) assert.True(t, enabled)
@ -39,7 +41,8 @@ func TestLoginUsingLDAP(t *testing.T) {
setting.LDAPEnabled = false setting.LDAPEnabled = false
sc.withLoginResult(false) sc.withLoginResult(false)
enabled, err := loginUsingLDAP(context.Background(), sc.loginUserQuery) loginService := &logintest.LoginServiceFake{}
enabled, err := loginUsingLDAP(context.Background(), sc.loginUserQuery, loginService)
require.NoError(t, err) require.NoError(t, err)
assert.False(t, enabled) assert.False(t, enabled)

View File

@ -9,6 +9,7 @@ import (
"github.com/grafana/grafana/pkg/login" "github.com/grafana/grafana/pkg/login"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/contexthandler" "github.com/grafana/grafana/pkg/services/contexthandler"
"github.com/grafana/grafana/pkg/services/login/logintest"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -78,7 +79,7 @@ func TestMiddlewareBasicAuth(t *testing.T) {
const password = "MyPass" const password = "MyPass"
const salt = "Salt" const salt = "Salt"
login.Init() login.ProvideService(sc.sqlStore, &logintest.LoginServiceFake{})
bus.AddHandler("user-query", func(ctx context.Context, query *models.GetUserByLoginQuery) error { bus.AddHandler("user-query", func(ctx context.Context, query *models.GetUserByLoginQuery) error {
encoded, err := util.EncodePassword(password, salt) encoded, err := util.EncodePassword(password, salt)

View File

@ -118,7 +118,7 @@ func (s *Server) init() error {
return err return err
} }
login.Init() login.ProvideService(s.HTTPServer.SQLStore, s.HTTPServer.Login)
social.ProvideService(s.cfg) social.ProvideService(s.cfg)
if err := s.roleRegistry.RegisterFixedRoles(); err != nil { if err := s.roleRegistry.RegisterFixedRoles(); err != nil {

View File

@ -20,6 +20,7 @@ import (
"github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/infra/usagestats" "github.com/grafana/grafana/pkg/infra/usagestats"
uss "github.com/grafana/grafana/pkg/infra/usagestats/service" uss "github.com/grafana/grafana/pkg/infra/usagestats/service"
loginpkg "github.com/grafana/grafana/pkg/login"
"github.com/grafana/grafana/pkg/login/social" "github.com/grafana/grafana/pkg/login/social"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins"
@ -159,6 +160,8 @@ var wireBasicSet = wire.NewSet(
wire.Bind(new(login.AuthInfoService), new(*authinfoservice.Implementation)), wire.Bind(new(login.AuthInfoService), new(*authinfoservice.Implementation)),
authinfodatabase.ProvideAuthInfoStore, authinfodatabase.ProvideAuthInfoStore,
wire.Bind(new(login.Store), new(*authinfodatabase.AuthInfoStore)), wire.Bind(new(login.Store), new(*authinfodatabase.AuthInfoStore)),
loginpkg.ProvideService,
wire.Bind(new(loginpkg.Authenticator), new(*loginpkg.AuthenticatorService)),
datasourceproxy.ProvideService, datasourceproxy.ProvideService,
search.ProvideService, search.ProvideService,
searchV2.ProvideService, searchV2.ProvideService,

View File

@ -9,6 +9,7 @@ import (
type AuthInfoService interface { type AuthInfoService interface {
LookupAndUpdate(ctx context.Context, query *models.GetUserByAuthInfoQuery) (*models.User, error) LookupAndUpdate(ctx context.Context, query *models.GetUserByAuthInfoQuery) (*models.User, error)
GetAuthInfo(ctx context.Context, query *models.GetAuthInfoQuery) error GetAuthInfo(ctx context.Context, query *models.GetAuthInfoQuery) error
GetExternalUserInfoByLogin(ctx context.Context, query *models.GetExternalUserInfoByLoginQuery) error
SetAuthInfo(ctx context.Context, cmd *models.SetAuthInfoCommand) error SetAuthInfo(ctx context.Context, cmd *models.SetAuthInfoCommand) error
UpdateAuthInfo(ctx context.Context, cmd *models.UpdateAuthInfoCommand) error UpdateAuthInfo(ctx context.Context, cmd *models.UpdateAuthInfoCommand) error
} }

View File

@ -183,3 +183,7 @@ func (s *Implementation) UpdateAuthInfo(ctx context.Context, cmd *models.UpdateA
func (s *Implementation) SetAuthInfo(ctx context.Context, cmd *models.SetAuthInfoCommand) error { func (s *Implementation) SetAuthInfo(ctx context.Context, cmd *models.SetAuthInfoCommand) error {
return s.authInfoStore.SetAuthInfo(ctx, cmd) return s.authInfoStore.SetAuthInfo(ctx, cmd)
} }
func (s *Implementation) GetExternalUserInfoByLogin(ctx context.Context, query *models.GetExternalUserInfoByLoginQuery) error {
return s.authInfoStore.GetExternalUserInfoByLogin(ctx, query)
}

View File

@ -18,5 +18,6 @@ type TeamSyncFunc func(user *models.User, externalUser *models.ExternalUserInfo)
type Service interface { type Service interface {
CreateUser(cmd models.CreateUserCommand) (*models.User, error) CreateUser(cmd models.CreateUserCommand) (*models.User, error)
UpsertUser(ctx context.Context, cmd *models.UpsertUserCommand) error UpsertUser(ctx context.Context, cmd *models.UpsertUserCommand) error
DisableExternalUser(ctx context.Context, username string) error
SetTeamSyncFunc(TeamSyncFunc) SetTeamSyncFunc(TeamSyncFunc)
} }

View File

@ -130,6 +130,46 @@ func (ls *Implementation) UpsertUser(ctx context.Context, cmd *models.UpsertUser
return nil return nil
} }
func (ls *Implementation) DisableExternalUser(ctx context.Context, username string) error {
// Check if external user exist in Grafana
userQuery := &models.GetExternalUserInfoByLoginQuery{
LoginOrEmail: username,
}
if err := ls.AuthInfoService.GetExternalUserInfoByLogin(ctx, userQuery); err != nil {
return err
}
userInfo := userQuery.Result
if userInfo.IsDisabled {
return nil
}
logger.Debug(
"Disabling external user",
"user",
userQuery.Result.Login,
)
// Mark user as disabled in grafana db
disableUserCmd := &models.DisableUserCommand{
UserId: userQuery.Result.UserId,
IsDisabled: true,
}
if err := ls.SQLStore.DisableUser(ctx, disableUserCmd); err != nil {
logger.Debug(
"Error disabling external user",
"user",
userQuery.Result.Login,
"message",
err.Error(),
)
return err
}
return nil
}
// SetTeamSyncFunc sets the function received through args as the team sync function. // SetTeamSyncFunc sets the function received through args as the team sync function.
func (ls *Implementation) SetTeamSyncFunc(teamSyncFunc login.TeamSyncFunc) { func (ls *Implementation) SetTeamSyncFunc(teamSyncFunc login.TeamSyncFunc) {
ls.TeamSync = teamSyncFunc ls.TeamSync = teamSyncFunc

View File

@ -45,3 +45,7 @@ func (s LoginServiceMock) UpsertUser(ctx context.Context, cmd *models.UpsertUser
cmd.Result = s.ExpectedUser cmd.Result = s.ExpectedUser
return s.ExpectedError return s.ExpectedError
} }
func (s LoginServiceMock) DisableExternalUser(ctx context.Context, username string) error {
return nil
}

View File

@ -10,6 +10,7 @@ import (
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/infra/log/level" "github.com/grafana/grafana/pkg/infra/log/level"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/login/logintest"
"github.com/grafana/grafana/pkg/services/quota" "github.com/grafana/grafana/pkg/services/quota"
"github.com/grafana/grafana/pkg/services/sqlstore/mockstore" "github.com/grafana/grafana/pkg/services/sqlstore/mockstore"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -19,7 +20,7 @@ import (
func Test_syncOrgRoles_doesNotBreakWhenTryingToRemoveLastOrgAdmin(t *testing.T) { func Test_syncOrgRoles_doesNotBreakWhenTryingToRemoveLastOrgAdmin(t *testing.T) {
user := createSimpleUser() user := createSimpleUser()
externalUser := createSimpleExternalUser() externalUser := createSimpleExternalUser()
authInfoMock := &authInfoServiceMock{} authInfoMock := &logintest.AuthInfoServiceFake{}
store := &mockstore.SQLStoreMock{ store := &mockstore.SQLStoreMock{
ExpectedUserOrgList: createUserOrgDTO(), ExpectedUserOrgList: createUserOrgDTO(),
@ -44,7 +45,7 @@ func Test_syncOrgRoles_whenTryingToRemoveLastOrgLogsError(t *testing.T) {
user := createSimpleUser() user := createSimpleUser()
externalUser := createSimpleExternalUser() externalUser := createSimpleExternalUser()
authInfoMock := &authInfoServiceMock{} authInfoMock := &logintest.AuthInfoServiceFake{}
store := &mockstore.SQLStoreMock{ store := &mockstore.SQLStoreMock{
ExpectedUserOrgList: createUserOrgDTO(), ExpectedUserOrgList: createUserOrgDTO(),
@ -63,29 +64,8 @@ func Test_syncOrgRoles_whenTryingToRemoveLastOrgLogsError(t *testing.T) {
assert.Contains(t, buf.String(), models.ErrLastOrgAdmin.Error()) assert.Contains(t, buf.String(), models.ErrLastOrgAdmin.Error())
} }
type authInfoServiceMock struct {
user *models.User
err error
}
func (a *authInfoServiceMock) LookupAndUpdate(ctx context.Context, query *models.GetUserByAuthInfoQuery) (*models.User, error) {
return a.user, a.err
}
func (a *authInfoServiceMock) GetAuthInfo(ctx context.Context, query *models.GetAuthInfoQuery) error {
return nil
}
func (a *authInfoServiceMock) SetAuthInfo(ctx context.Context, cmd *models.SetAuthInfoCommand) error {
return nil
}
func (a *authInfoServiceMock) UpdateAuthInfo(ctx context.Context, cmd *models.UpdateAuthInfoCommand) error {
return nil
}
func Test_teamSync(t *testing.T) { func Test_teamSync(t *testing.T) {
authInfoMock := &authInfoServiceMock{} authInfoMock := &logintest.AuthInfoServiceFake{}
login := Implementation{ login := Implementation{
Bus: bus.New(), Bus: bus.New(),
QuotaService: &quota.QuotaService{}, QuotaService: &quota.QuotaService{},
@ -99,7 +79,7 @@ func Test_teamSync(t *testing.T) {
Name: "test_user", Name: "test_user",
Login: "test_user", Login: "test_user",
} }
authInfoMock.user = expectedUser authInfoMock.ExpectedUser = expectedUser
bus.ClearBusHandlers() bus.ClearBusHandlers()
t.Cleanup(func() { bus.ClearBusHandlers() }) t.Cleanup(func() { bus.ClearBusHandlers() })

View File

@ -0,0 +1,51 @@
package logintest
import (
"context"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/login"
)
type LoginServiceFake struct{}
func (l *LoginServiceFake) CreateUser(cmd models.CreateUserCommand) (*models.User, error) {
return nil, nil
}
func (l *LoginServiceFake) UpsertUser(ctx context.Context, cmd *models.UpsertUserCommand) error {
return nil
}
func (l *LoginServiceFake) DisableExternalUser(ctx context.Context, username string) error {
return nil
}
func (l *LoginServiceFake) SetTeamSyncFunc(login.TeamSyncFunc) {}
type AuthInfoServiceFake struct {
LatestUserID int64
ExpectedUser *models.User
ExpectedExternalUser *models.ExternalUserInfo
ExpectedError error
}
func (a *AuthInfoServiceFake) LookupAndUpdate(ctx context.Context, query *models.GetUserByAuthInfoQuery) (*models.User, error) {
a.LatestUserID = query.UserId
return a.ExpectedUser, a.ExpectedError
}
func (a *AuthInfoServiceFake) GetAuthInfo(ctx context.Context, query *models.GetAuthInfoQuery) error {
a.LatestUserID = query.UserId
return a.ExpectedError
}
func (a *AuthInfoServiceFake) SetAuthInfo(ctx context.Context, cmd *models.SetAuthInfoCommand) error {
return a.ExpectedError
}
func (a *AuthInfoServiceFake) UpdateAuthInfo(ctx context.Context, cmd *models.UpdateAuthInfoCommand) error {
return a.ExpectedError
}
func (a *AuthInfoServiceFake) GetExternalUserInfoByLogin(ctx context.Context, query *models.GetExternalUserInfoByLoginQuery) error {
query.Result = a.ExpectedExternalUser
return a.ExpectedError
}