Commit a9daaadd by Agnès Toulet Committed by GitHub

API: send Login actions (#27249)

* API: first version to send events about login actions

* API: improve login actions events

* Login: update auth test with new behavior

* Login: update auth test for auth module

* Login OAuth: improve functions structure

* API: make struct public to use for saml

* API: add send login log tests for grafana and ldap login

* API: remove log from tests

* Login API: fix test linting

* Update pkg/api/login_oauth.go

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

* Login API: refactor using defer

Co-authored-by: Emil Tullstedt <emil.tullstedt@grafana.com>
parent a54df0c3
...@@ -3,6 +3,8 @@ package api ...@@ -3,6 +3,8 @@ package api
import ( import (
"encoding/hex" "encoding/hex"
"errors" "errors"
"fmt"
"net/http"
"net/url" "net/url"
"strings" "strings"
...@@ -159,8 +161,27 @@ func (hs *HTTPServer) LoginAPIPing(c *models.ReqContext) Response { ...@@ -159,8 +161,27 @@ func (hs *HTTPServer) LoginAPIPing(c *models.ReqContext) Response {
} }
func (hs *HTTPServer) LoginPost(c *models.ReqContext, cmd dtos.LoginCommand) Response { func (hs *HTTPServer) LoginPost(c *models.ReqContext, cmd dtos.LoginCommand) Response {
action := "login"
var user *models.User
var response *NormalResponse
defer func() {
err := response.err
if err == nil && response.errMessage != "" {
err = errors.New(response.errMessage)
}
hs.SendLoginLog(&models.SendLoginLogCommand{
ReqContext: c,
LogAction: action,
User: user,
HTTPStatus: response.status,
Error: err,
})
}()
if setting.DisableLoginForm { if setting.DisableLoginForm {
return Error(401, "Login is disabled", nil) response = Error(http.StatusUnauthorized, "Login is disabled", nil)
return response
} }
authQuery := &models.LoginUserQuery{ authQuery := &models.LoginUserQuery{
...@@ -170,27 +191,33 @@ func (hs *HTTPServer) LoginPost(c *models.ReqContext, cmd dtos.LoginCommand) Res ...@@ -170,27 +191,33 @@ func (hs *HTTPServer) LoginPost(c *models.ReqContext, cmd dtos.LoginCommand) Res
IpAddress: c.Req.RemoteAddr, IpAddress: c.Req.RemoteAddr,
} }
if err := bus.Dispatch(authQuery); err != nil { err := bus.Dispatch(authQuery)
e401 := Error(401, "Invalid username or password", err) if authQuery.AuthModule != "" {
if err == login.ErrInvalidCredentials || err == login.ErrTooManyLoginAttempts { action += fmt.Sprintf("-%s", authQuery.AuthModule)
return e401 }
if err != nil {
response = Error(401, "Invalid username or password", err)
if err == login.ErrInvalidCredentials || err == login.ErrTooManyLoginAttempts || err == models.ErrUserNotFound {
return response
} }
// Do not expose disabled status, // Do not expose disabled status,
// just show incorrect user credentials error (see #17947) // just show incorrect user credentials error (see #17947)
if err == login.ErrUserDisabled { if err == login.ErrUserDisabled {
hs.log.Warn("User is disabled", "user", cmd.User) hs.log.Warn("User is disabled", "user", cmd.User)
return e401 return response
} }
return Error(500, "Error while trying to authenticate user", err) response = Error(500, "Error while trying to authenticate user", err)
return response
} }
user := authQuery.User user = authQuery.User
err := hs.loginUserWithUser(user, c) err = hs.loginUserWithUser(user, c)
if err != nil { if err != nil {
return Error(500, "Error while signing in user", err) response = Error(http.StatusInternalServerError, "Error while signing in user", err)
return response
} }
result := map[string]interface{}{ result := map[string]interface{}{
...@@ -207,7 +234,8 @@ func (hs *HTTPServer) LoginPost(c *models.ReqContext, cmd dtos.LoginCommand) Res ...@@ -207,7 +234,8 @@ func (hs *HTTPServer) LoginPost(c *models.ReqContext, cmd dtos.LoginCommand) Res
} }
metrics.MApiLoginPost.Inc() metrics.MApiLoginPost.Inc()
return JSON(200, result) response = JSON(http.StatusOK, result)
return response
} }
func (hs *HTTPServer) loginUserWithUser(user *models.User, c *models.ReqContext) error { func (hs *HTTPServer) loginUserWithUser(user *models.User, c *models.ReqContext) error {
...@@ -283,3 +311,11 @@ func (hs *HTTPServer) RedirectResponseWithError(ctx *models.ReqContext, err erro ...@@ -283,3 +311,11 @@ func (hs *HTTPServer) RedirectResponseWithError(ctx *models.ReqContext, err erro
return Redirect(setting.AppSubUrl + "/login") return Redirect(setting.AppSubUrl + "/login")
} }
func (hs *HTTPServer) SendLoginLog(cmd *models.SendLoginLogCommand) {
if err := bus.Dispatch(cmd); err != nil {
if err != bus.ErrHandlerNotFound {
hs.log.Warn("Error while sending login log", "err", err)
}
}
}
...@@ -8,6 +8,7 @@ import ( ...@@ -8,6 +8,7 @@ import (
"crypto/x509" "crypto/x509"
"encoding/base64" "encoding/base64"
"encoding/hex" "encoding/hex"
"errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
...@@ -40,15 +41,25 @@ func GenStateString() (string, error) { ...@@ -40,15 +41,25 @@ func GenStateString() (string, error) {
} }
func (hs *HTTPServer) OAuthLogin(ctx *models.ReqContext) { func (hs *HTTPServer) OAuthLogin(ctx *models.ReqContext) {
loginInfo := LoginInformation{
Action: "login-oauth",
}
if setting.OAuthService == nil { if setting.OAuthService == nil {
ctx.Handle(404, "OAuth not enabled", nil) hs.handleOAuthLoginError(ctx, loginInfo, LoginError{
HttpStatus: http.StatusNotFound,
PublicMessage: "OAuth not enabled",
})
return return
} }
name := ctx.Params(":name") name := ctx.Params(":name")
loginInfo.Action += fmt.Sprintf("-%s", name)
connect, ok := social.SocialMap[name] connect, ok := social.SocialMap[name]
if !ok { if !ok {
ctx.Handle(404, fmt.Sprintf("No OAuth with name %s configured", name), nil) hs.handleOAuthLoginError(ctx, loginInfo, LoginError{
HttpStatus: http.StatusNotFound,
PublicMessage: fmt.Sprintf("No OAuth with name %s configured", name),
})
return return
} }
...@@ -56,7 +67,7 @@ func (hs *HTTPServer) OAuthLogin(ctx *models.ReqContext) { ...@@ -56,7 +67,7 @@ func (hs *HTTPServer) OAuthLogin(ctx *models.ReqContext) {
if errorParam != "" { if errorParam != "" {
errorDesc := ctx.Query("error_description") errorDesc := ctx.Query("error_description")
oauthLogger.Error("failed to login ", "error", errorParam, "errorDesc", errorDesc) oauthLogger.Error("failed to login ", "error", errorParam, "errorDesc", errorDesc)
hs.redirectWithError(ctx, login.ErrProviderDeniedRequest, "error", errorParam, "errorDesc", errorDesc) hs.handleOAuthLoginErrorWithRedirect(ctx, loginInfo, login.ErrProviderDeniedRequest, "error", errorParam, "errorDesc", errorDesc)
return return
} }
...@@ -65,7 +76,10 @@ func (hs *HTTPServer) OAuthLogin(ctx *models.ReqContext) { ...@@ -65,7 +76,10 @@ func (hs *HTTPServer) OAuthLogin(ctx *models.ReqContext) {
state, err := GenStateString() state, err := GenStateString()
if err != nil { if err != nil {
ctx.Logger.Error("Generating state string failed", "err", err) ctx.Logger.Error("Generating state string failed", "err", err)
ctx.Handle(500, "An internal error occurred", nil) hs.handleOAuthLoginError(ctx, loginInfo, LoginError{
HttpStatus: http.StatusInternalServerError,
PublicMessage: "An internal error occurred",
})
return return
} }
...@@ -85,14 +99,20 @@ func (hs *HTTPServer) OAuthLogin(ctx *models.ReqContext) { ...@@ -85,14 +99,20 @@ func (hs *HTTPServer) OAuthLogin(ctx *models.ReqContext) {
middleware.DeleteCookie(ctx.Resp, OauthStateCookieName, hs.CookieOptionsFromCfg) middleware.DeleteCookie(ctx.Resp, OauthStateCookieName, hs.CookieOptionsFromCfg)
if cookieState == "" { if cookieState == "" {
ctx.Handle(500, "login.OAuthLogin(missing saved state)", nil) hs.handleOAuthLoginError(ctx, loginInfo, LoginError{
HttpStatus: http.StatusInternalServerError,
PublicMessage: "login.OAuthLogin(missing saved state)",
})
return return
} }
queryState := hashStatecode(ctx.Query("state"), setting.OAuthService.OAuthInfos[name].ClientSecret) queryState := hashStatecode(ctx.Query("state"), setting.OAuthService.OAuthInfos[name].ClientSecret)
oauthLogger.Info("state check", "queryState", queryState, "cookieState", cookieState) oauthLogger.Info("state check", "queryState", queryState, "cookieState", cookieState)
if cookieState != queryState { if cookieState != queryState {
ctx.Handle(500, "login.OAuthLogin(state mismatch)", nil) hs.handleOAuthLoginError(ctx, loginInfo, LoginError{
HttpStatus: http.StatusInternalServerError,
PublicMessage: "login.OAuthLogin(state mismatch)",
})
return return
} }
...@@ -111,7 +131,10 @@ func (hs *HTTPServer) OAuthLogin(ctx *models.ReqContext) { ...@@ -111,7 +131,10 @@ func (hs *HTTPServer) OAuthLogin(ctx *models.ReqContext) {
cert, err := tls.LoadX509KeyPair(setting.OAuthService.OAuthInfos[name].TlsClientCert, setting.OAuthService.OAuthInfos[name].TlsClientKey) cert, err := tls.LoadX509KeyPair(setting.OAuthService.OAuthInfos[name].TlsClientCert, setting.OAuthService.OAuthInfos[name].TlsClientKey)
if err != nil { if err != nil {
ctx.Logger.Error("Failed to setup TlsClientCert", "oauth", name, "error", err) ctx.Logger.Error("Failed to setup TlsClientCert", "oauth", name, "error", err)
ctx.Handle(500, "login.OAuthLogin(Failed to setup TlsClientCert)", nil) hs.handleOAuthLoginError(ctx, loginInfo, LoginError{
HttpStatus: http.StatusInternalServerError,
PublicMessage: "login.OAuthLogin(Failed to setup TlsClientCert)",
})
return return
} }
...@@ -122,7 +145,10 @@ func (hs *HTTPServer) OAuthLogin(ctx *models.ReqContext) { ...@@ -122,7 +145,10 @@ func (hs *HTTPServer) OAuthLogin(ctx *models.ReqContext) {
caCert, err := ioutil.ReadFile(setting.OAuthService.OAuthInfos[name].TlsClientCa) caCert, err := ioutil.ReadFile(setting.OAuthService.OAuthInfos[name].TlsClientCa)
if err != nil { if err != nil {
ctx.Logger.Error("Failed to setup TlsClientCa", "oauth", name, "error", err) ctx.Logger.Error("Failed to setup TlsClientCa", "oauth", name, "error", err)
ctx.Handle(500, "login.OAuthLogin(Failed to setup TlsClientCa)", nil) hs.handleOAuthLoginError(ctx, loginInfo, LoginError{
HttpStatus: http.StatusInternalServerError,
PublicMessage: "login.OAuthLogin(Failed to setup TlsClientCa)",
})
return return
} }
...@@ -137,7 +163,11 @@ func (hs *HTTPServer) OAuthLogin(ctx *models.ReqContext) { ...@@ -137,7 +163,11 @@ func (hs *HTTPServer) OAuthLogin(ctx *models.ReqContext) {
// get token from provider // get token from provider
token, err := connect.Exchange(oauthCtx, code) token, err := connect.Exchange(oauthCtx, code)
if err != nil { if err != nil {
ctx.Handle(500, "login.OAuthLogin(NewTransportWithCode)", err) hs.handleOAuthLoginError(ctx, loginInfo, LoginError{
HttpStatus: http.StatusInternalServerError,
PublicMessage: "login.OAuthLogin(NewTransportWithCode)",
Err: err,
})
return return
} }
// token.TokenType was defaulting to "bearer", which is out of spec, so we explicitly set to "Bearer" // token.TokenType was defaulting to "bearer", which is out of spec, so we explicitly set to "Bearer"
...@@ -152,9 +182,13 @@ func (hs *HTTPServer) OAuthLogin(ctx *models.ReqContext) { ...@@ -152,9 +182,13 @@ func (hs *HTTPServer) OAuthLogin(ctx *models.ReqContext) {
userInfo, err := connect.UserInfo(client, token) userInfo, err := connect.UserInfo(client, token)
if err != nil { if err != nil {
if sErr, ok := err.(*social.Error); ok { if sErr, ok := err.(*social.Error); ok {
hs.redirectWithError(ctx, sErr) hs.handleOAuthLoginErrorWithRedirect(ctx, loginInfo, sErr)
} else { } else {
ctx.Handle(500, fmt.Sprintf("login.OAuthLogin(get info from %s)", name), err) hs.handleOAuthLoginError(ctx, loginInfo, LoginError{
HttpStatus: http.StatusInternalServerError,
PublicMessage: fmt.Sprintf("login.OAuthLogin(get info from %s)", name),
Err: err,
})
} }
return return
} }
...@@ -163,28 +197,36 @@ func (hs *HTTPServer) OAuthLogin(ctx *models.ReqContext) { ...@@ -163,28 +197,36 @@ func (hs *HTTPServer) OAuthLogin(ctx *models.ReqContext) {
// validate that we got at least an email address // validate that we got at least an email address
if userInfo.Email == "" { if userInfo.Email == "" {
hs.redirectWithError(ctx, login.ErrNoEmail) hs.handleOAuthLoginErrorWithRedirect(ctx, loginInfo, login.ErrNoEmail)
return return
} }
// validate that the email is allowed to login to grafana // validate that the email is allowed to login to grafana
if !connect.IsEmailAllowed(userInfo.Email) { if !connect.IsEmailAllowed(userInfo.Email) {
hs.redirectWithError(ctx, login.ErrEmailNotAllowed) hs.handleOAuthLoginErrorWithRedirect(ctx, loginInfo, login.ErrEmailNotAllowed)
return return
} }
user, err := syncUser(ctx, token, userInfo, name, connect) loginInfo.ExtUserInfo = buildExternalUserInfo(token, userInfo, name)
loginInfo.User, err = syncUser(ctx, loginInfo.ExtUserInfo, connect)
if err != nil { if err != nil {
hs.redirectWithError(ctx, err) hs.handleOAuthLoginErrorWithRedirect(ctx, loginInfo, err)
return return
} }
// login // login
if err := hs.loginUserWithUser(user, ctx); err != nil { if err := hs.loginUserWithUser(loginInfo.User, ctx); err != nil {
hs.redirectWithError(ctx, err) hs.handleOAuthLoginErrorWithRedirect(ctx, loginInfo, err)
return return
} }
hs.SendLoginLog(&models.SendLoginLogCommand{
ReqContext: ctx,
LogAction: loginInfo.Action,
User: loginInfo.User,
ExternalUser: loginInfo.ExtUserInfo,
HTTPStatus: http.StatusOK,
})
metrics.MApiLoginOAuth.Inc() metrics.MApiLoginOAuth.Inc()
if redirectTo, err := url.QueryUnescape(ctx.GetCookie("redirect_to")); err == nil && len(redirectTo) > 0 { if redirectTo, err := url.QueryUnescape(ctx.GetCookie("redirect_to")); err == nil && len(redirectTo) > 0 {
...@@ -199,10 +241,10 @@ func (hs *HTTPServer) OAuthLogin(ctx *models.ReqContext) { ...@@ -199,10 +241,10 @@ func (hs *HTTPServer) OAuthLogin(ctx *models.ReqContext) {
ctx.Redirect(setting.AppSubUrl + "/") ctx.Redirect(setting.AppSubUrl + "/")
} }
// syncUser syncs a Grafana user profile with the corresponding OAuth profile. // buildExternalUserInfo returns a ExternalUserInfo struct from OAuth user profile
func syncUser(ctx *models.ReqContext, token *oauth2.Token, userInfo *social.BasicUserInfo, name string, func buildExternalUserInfo(token *oauth2.Token, userInfo *social.BasicUserInfo, name string) *models.ExternalUserInfo {
connect social.SocialConnector) (*models.User, error) { oauthLogger.Debug("Building external user info from OAuth user info")
oauthLogger.Debug("Syncing Grafana user with corresponding OAuth profile")
extUser := &models.ExternalUserInfo{ extUser := &models.ExternalUserInfo{
AuthModule: fmt.Sprintf("oauth_%s", name), AuthModule: fmt.Sprintf("oauth_%s", name),
OAuthToken: token, OAuthToken: token,
...@@ -232,6 +274,16 @@ func syncUser(ctx *models.ReqContext, token *oauth2.Token, userInfo *social.Basi ...@@ -232,6 +274,16 @@ func syncUser(ctx *models.ReqContext, token *oauth2.Token, userInfo *social.Basi
} }
} }
return extUser
}
// syncUser syncs a Grafana user profile with the corresponding OAuth profile.
func syncUser(
ctx *models.ReqContext,
extUser *models.ExternalUserInfo,
connect social.SocialConnector,
) (*models.User, error) {
oauthLogger.Debug("Syncing Grafana user with corresponding OAuth profile")
// add/update user in Grafana // add/update user in Grafana
cmd := &models.UpsertUserCommand{ cmd := &models.UpsertUserCommand{
ReqContext: ctx, ReqContext: ctx,
...@@ -256,3 +308,43 @@ func hashStatecode(code, seed string) string { ...@@ -256,3 +308,43 @@ func hashStatecode(code, seed string) string {
hashBytes := sha256.Sum256([]byte(code + setting.SecretKey + seed)) hashBytes := sha256.Sum256([]byte(code + setting.SecretKey + seed))
return hex.EncodeToString(hashBytes[:]) return hex.EncodeToString(hashBytes[:])
} }
type LoginError struct {
HttpStatus int
PublicMessage string
Err error
}
type LoginInformation struct {
Action string
User *models.User
ExtUserInfo *models.ExternalUserInfo
}
func (hs *HTTPServer) handleOAuthLoginError(ctx *models.ReqContext, info LoginInformation, err LoginError) {
ctx.Handle(err.HttpStatus, err.PublicMessage, err.Err)
logErr := err.Err
if logErr == nil {
logErr = errors.New(err.PublicMessage)
}
hs.SendLoginLog(&models.SendLoginLogCommand{
ReqContext: ctx,
LogAction: info.Action,
HTTPStatus: err.HttpStatus,
Error: logErr,
})
}
func (hs *HTTPServer) handleOAuthLoginErrorWithRedirect(ctx *models.ReqContext, info LoginInformation, err error, v ...interface{}) {
hs.redirectWithError(ctx, err, v...)
hs.SendLoginLog(&models.SendLoginLogCommand{
ReqContext: ctx,
LogAction: info.Action,
User: info.User,
ExternalUser: info.ExtUserInfo,
Error: err,
})
}
package api package api
import ( import (
"context"
"encoding/hex" "encoding/hex"
"errors" "errors"
"fmt" "fmt"
...@@ -21,6 +22,7 @@ import ( ...@@ -21,6 +22,7 @@ import (
"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"
"github.com/stretchr/testify/require"
) )
func mockSetIndexViewData() { func mockSetIndexViewData() {
...@@ -553,3 +555,110 @@ func setupAuthProxyLoginTest(enableLoginToken bool) *scenarioContext { ...@@ -553,3 +555,110 @@ func setupAuthProxyLoginTest(enableLoginToken bool) *scenarioContext {
return sc return sc
} }
type loginLogTestReceiver struct {
cmd *models.SendLoginLogCommand
}
func (r *loginLogTestReceiver) SaveLoginLog(ctx context.Context, cmd *models.SendLoginLogCommand) error {
r.cmd = cmd
return nil
}
func TestLoginPostSendLoginLog(t *testing.T) {
sc := setupScenarioContext("/login")
hs := &HTTPServer{
log: log.New("test"),
Cfg: setting.NewCfg(),
License: &licensing.OSSLicensingService{},
AuthTokenService: auth.NewFakeUserAuthTokenService(),
}
sc.defaultHandler = Wrap(func(w http.ResponseWriter, c *models.ReqContext) Response {
cmd := dtos.LoginCommand{
User: "admin",
Password: "admin",
}
return hs.LoginPost(c, cmd)
})
testReceiver := loginLogTestReceiver{}
bus.AddHandlerCtx("login-log-receiver", testReceiver.SaveLoginLog)
type sendLoginLogCase struct {
desc string
authUser *models.User
authModule string
authErr error
cmd models.SendLoginLogCommand
}
testUser := &models.User{
Id: 42,
Email: "",
}
testCases := []sendLoginLogCase{
{
desc: "invalid credentials",
authErr: login.ErrInvalidCredentials,
cmd: models.SendLoginLogCommand{
LogAction: "login",
HTTPStatus: 401,
Error: login.ErrInvalidCredentials,
},
},
{
desc: "user disabled",
authErr: login.ErrUserDisabled,
cmd: models.SendLoginLogCommand{
LogAction: "login",
HTTPStatus: 401,
Error: login.ErrUserDisabled,
},
},
{
desc: "valid Grafana user",
authUser: testUser,
authModule: "grafana",
cmd: models.SendLoginLogCommand{
LogAction: "login-grafana",
User: testUser,
HTTPStatus: 200,
},
},
{
desc: "valid LDAP user",
authUser: testUser,
authModule: "ldap",
cmd: models.SendLoginLogCommand{
LogAction: "login-ldap",
User: testUser,
HTTPStatus: 200,
},
},
}
for _, c := range testCases {
t.Run(c.desc, func(t *testing.T) {
bus.AddHandler("grafana-auth", func(query *models.LoginUserQuery) error {
query.User = c.authUser
query.AuthModule = c.authModule
return c.authErr
})
sc.m.Post(sc.url, sc.defaultHandler)
sc.fakeReqNoAssertions("POST", sc.url).exec()
cmd := testReceiver.cmd
assert.Equal(t, c.cmd.LogAction, cmd.LogAction)
assert.Equal(t, c.cmd.HTTPStatus, cmd.HTTPStatus)
assert.Equal(t, c.cmd.Error, cmd.Error)
if c.cmd.User != nil {
require.NotEmpty(t, cmd.User)
assert.Equal(t, c.cmd.User.Id, cmd.User.Id)
}
})
}
}
...@@ -41,11 +41,13 @@ func AuthenticateUser(query *models.LoginUserQuery) error { ...@@ -41,11 +41,13 @@ func AuthenticateUser(query *models.LoginUserQuery) error {
err := loginUsingGrafanaDB(query) err := loginUsingGrafanaDB(query)
if err == nil || (err != models.ErrUserNotFound && err != ErrInvalidCredentials && err != ErrUserDisabled) { if err == nil || (err != models.ErrUserNotFound && err != ErrInvalidCredentials && err != ErrUserDisabled) {
query.AuthModule = "grafana"
return err return err
} }
ldapEnabled, ldapErr := loginUsingLDAP(query) ldapEnabled, ldapErr := loginUsingLDAP(query)
if ldapEnabled { if ldapEnabled {
query.AuthModule = models.AuthModuleLDAP
if ldapErr == nil || ldapErr != ldap.ErrInvalidCredentials { if ldapErr == nil || ldapErr != ldap.ErrInvalidCredentials {
return ldapErr return ldapErr
} }
...@@ -63,10 +65,6 @@ func AuthenticateUser(query *models.LoginUserQuery) error { ...@@ -63,10 +65,6 @@ func AuthenticateUser(query *models.LoginUserQuery) error {
return ErrInvalidCredentials return ErrInvalidCredentials
} }
if err == models.ErrUserNotFound {
return ErrInvalidCredentials
}
return err return err
} }
......
...@@ -27,6 +27,7 @@ func TestAuthenticateUser(t *testing.T) { ...@@ -27,6 +27,7 @@ func TestAuthenticateUser(t *testing.T) {
So(sc.grafanaLoginWasCalled, ShouldBeFalse) So(sc.grafanaLoginWasCalled, ShouldBeFalse)
So(sc.ldapLoginWasCalled, ShouldBeFalse) So(sc.ldapLoginWasCalled, ShouldBeFalse)
So(err, ShouldEqual, ErrPasswordEmpty) So(err, ShouldEqual, ErrPasswordEmpty)
So(sc.loginUserQuery.AuthModule, ShouldEqual, "")
}) })
}) })
...@@ -44,6 +45,7 @@ func TestAuthenticateUser(t *testing.T) { ...@@ -44,6 +45,7 @@ func TestAuthenticateUser(t *testing.T) {
So(sc.grafanaLoginWasCalled, ShouldBeFalse) So(sc.grafanaLoginWasCalled, ShouldBeFalse)
So(sc.ldapLoginWasCalled, ShouldBeFalse) So(sc.ldapLoginWasCalled, ShouldBeFalse)
So(sc.saveInvalidLoginAttemptWasCalled, ShouldBeFalse) So(sc.saveInvalidLoginAttemptWasCalled, ShouldBeFalse)
So(sc.loginUserQuery.AuthModule, ShouldEqual, "")
}) })
}) })
...@@ -61,6 +63,7 @@ func TestAuthenticateUser(t *testing.T) { ...@@ -61,6 +63,7 @@ func TestAuthenticateUser(t *testing.T) {
So(sc.grafanaLoginWasCalled, ShouldBeTrue) So(sc.grafanaLoginWasCalled, ShouldBeTrue)
So(sc.ldapLoginWasCalled, ShouldBeFalse) So(sc.ldapLoginWasCalled, ShouldBeFalse)
So(sc.saveInvalidLoginAttemptWasCalled, ShouldBeFalse) So(sc.saveInvalidLoginAttemptWasCalled, ShouldBeFalse)
So(sc.loginUserQuery.AuthModule, ShouldEqual, "grafana")
}) })
}) })
...@@ -79,6 +82,7 @@ func TestAuthenticateUser(t *testing.T) { ...@@ -79,6 +82,7 @@ func TestAuthenticateUser(t *testing.T) {
So(sc.grafanaLoginWasCalled, ShouldBeTrue) So(sc.grafanaLoginWasCalled, ShouldBeTrue)
So(sc.ldapLoginWasCalled, ShouldBeFalse) So(sc.ldapLoginWasCalled, ShouldBeFalse)
So(sc.saveInvalidLoginAttemptWasCalled, ShouldBeFalse) So(sc.saveInvalidLoginAttemptWasCalled, ShouldBeFalse)
So(sc.loginUserQuery.AuthModule, ShouldEqual, "grafana")
}) })
}) })
...@@ -91,11 +95,12 @@ func TestAuthenticateUser(t *testing.T) { ...@@ -91,11 +95,12 @@ func TestAuthenticateUser(t *testing.T) {
err := AuthenticateUser(sc.loginUserQuery) err := AuthenticateUser(sc.loginUserQuery)
Convey("it should result in", func() { Convey("it should result in", func() {
So(err, ShouldEqual, ErrInvalidCredentials) So(err, ShouldEqual, models.ErrUserNotFound)
So(sc.loginAttemptValidationWasCalled, ShouldBeTrue) So(sc.loginAttemptValidationWasCalled, ShouldBeTrue)
So(sc.grafanaLoginWasCalled, ShouldBeTrue) So(sc.grafanaLoginWasCalled, ShouldBeTrue)
So(sc.ldapLoginWasCalled, ShouldBeTrue) So(sc.ldapLoginWasCalled, ShouldBeTrue)
So(sc.saveInvalidLoginAttemptWasCalled, ShouldBeFalse) So(sc.saveInvalidLoginAttemptWasCalled, ShouldBeFalse)
So(sc.loginUserQuery.AuthModule, ShouldEqual, "")
}) })
}) })
...@@ -113,6 +118,7 @@ func TestAuthenticateUser(t *testing.T) { ...@@ -113,6 +118,7 @@ func TestAuthenticateUser(t *testing.T) {
So(sc.grafanaLoginWasCalled, ShouldBeTrue) So(sc.grafanaLoginWasCalled, ShouldBeTrue)
So(sc.ldapLoginWasCalled, ShouldBeTrue) So(sc.ldapLoginWasCalled, ShouldBeTrue)
So(sc.saveInvalidLoginAttemptWasCalled, ShouldBeTrue) So(sc.saveInvalidLoginAttemptWasCalled, ShouldBeTrue)
So(sc.loginUserQuery.AuthModule, ShouldEqual, "ldap")
}) })
}) })
...@@ -130,6 +136,7 @@ func TestAuthenticateUser(t *testing.T) { ...@@ -130,6 +136,7 @@ func TestAuthenticateUser(t *testing.T) {
So(sc.grafanaLoginWasCalled, ShouldBeTrue) So(sc.grafanaLoginWasCalled, ShouldBeTrue)
So(sc.ldapLoginWasCalled, ShouldBeTrue) So(sc.ldapLoginWasCalled, ShouldBeTrue)
So(sc.saveInvalidLoginAttemptWasCalled, ShouldBeFalse) So(sc.saveInvalidLoginAttemptWasCalled, ShouldBeFalse)
So(sc.loginUserQuery.AuthModule, ShouldEqual, "ldap")
}) })
}) })
...@@ -148,6 +155,7 @@ func TestAuthenticateUser(t *testing.T) { ...@@ -148,6 +155,7 @@ func TestAuthenticateUser(t *testing.T) {
So(sc.grafanaLoginWasCalled, ShouldBeTrue) So(sc.grafanaLoginWasCalled, ShouldBeTrue)
So(sc.ldapLoginWasCalled, ShouldBeTrue) So(sc.ldapLoginWasCalled, ShouldBeTrue)
So(sc.saveInvalidLoginAttemptWasCalled, ShouldBeFalse) So(sc.saveInvalidLoginAttemptWasCalled, ShouldBeFalse)
So(sc.loginUserQuery.AuthModule, ShouldEqual, "ldap")
}) })
}) })
......
...@@ -14,6 +14,7 @@ import ( ...@@ -14,6 +14,7 @@ import (
"github.com/grafana/grafana/pkg/components/apikeygen" "github.com/grafana/grafana/pkg/components/apikeygen"
"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/login"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/rendering" "github.com/grafana/grafana/pkg/services/rendering"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
...@@ -178,8 +179,12 @@ func initContextWithBasicAuth(ctx *models.ReqContext, orgId int64) bool { ...@@ -178,8 +179,12 @@ func initContextWithBasicAuth(ctx *models.ReqContext, orgId int64) bool {
ctx.Logger.Debug( ctx.Logger.Debug(
"Failed to authorize the user", "Failed to authorize the user",
"username", username, "username", username,
"err", err,
) )
if err == models.ErrUserNotFound {
err = login.ErrInvalidCredentials
}
ctx.JsonApiErr(401, errStringInvalidUsernamePassword, err) ctx.JsonApiErr(401, errStringInvalidUsernamePassword, err)
return true return true
} }
......
...@@ -65,6 +65,15 @@ type DeleteAuthInfoCommand struct { ...@@ -65,6 +65,15 @@ type DeleteAuthInfoCommand struct {
UserAuth *UserAuth UserAuth *UserAuth
} }
type SendLoginLogCommand struct {
ReqContext *ReqContext
LogAction string
User *User
ExternalUser *ExternalUserInfo
HTTPStatus int
Error error
}
// ---------------------- // ----------------------
// QUERIES // QUERIES
...@@ -74,6 +83,7 @@ type LoginUserQuery struct { ...@@ -74,6 +83,7 @@ type LoginUserQuery struct {
Password string Password string
User *User User *User
IpAddress string IpAddress string
AuthModule string
} }
type GetUserByAuthInfoQuery struct { type GetUserByAuthInfoQuery struct {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment