mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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>
This commit is contained in:
parent
a54df0c3cd
commit
a9daaadd50
@ -3,6 +3,8 @@ package api
|
||||
import (
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
@ -159,8 +161,27 @@ func (hs *HTTPServer) LoginAPIPing(c *models.ReqContext) 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 {
|
||||
return Error(401, "Login is disabled", nil)
|
||||
response = Error(http.StatusUnauthorized, "Login is disabled", nil)
|
||||
return response
|
||||
}
|
||||
|
||||
authQuery := &models.LoginUserQuery{
|
||||
@ -170,27 +191,33 @@ func (hs *HTTPServer) LoginPost(c *models.ReqContext, cmd dtos.LoginCommand) Res
|
||||
IpAddress: c.Req.RemoteAddr,
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(authQuery); err != nil {
|
||||
e401 := Error(401, "Invalid username or password", err)
|
||||
if err == login.ErrInvalidCredentials || err == login.ErrTooManyLoginAttempts {
|
||||
return e401
|
||||
err := bus.Dispatch(authQuery)
|
||||
if authQuery.AuthModule != "" {
|
||||
action += fmt.Sprintf("-%s", authQuery.AuthModule)
|
||||
}
|
||||
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,
|
||||
// just show incorrect user credentials error (see #17947)
|
||||
if err == login.ErrUserDisabled {
|
||||
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 {
|
||||
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{}{
|
||||
@ -207,7 +234,8 @@ func (hs *HTTPServer) LoginPost(c *models.ReqContext, cmd dtos.LoginCommand) Res
|
||||
}
|
||||
|
||||
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 {
|
||||
@ -283,3 +311,11 @@ func (hs *HTTPServer) RedirectResponseWithError(ctx *models.ReqContext, err erro
|
||||
|
||||
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 (
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
@ -40,15 +41,25 @@ func GenStateString() (string, error) {
|
||||
}
|
||||
|
||||
func (hs *HTTPServer) OAuthLogin(ctx *models.ReqContext) {
|
||||
loginInfo := LoginInformation{
|
||||
Action: "login-oauth",
|
||||
}
|
||||
if setting.OAuthService == nil {
|
||||
ctx.Handle(404, "OAuth not enabled", nil)
|
||||
hs.handleOAuthLoginError(ctx, loginInfo, LoginError{
|
||||
HttpStatus: http.StatusNotFound,
|
||||
PublicMessage: "OAuth not enabled",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
name := ctx.Params(":name")
|
||||
loginInfo.Action += fmt.Sprintf("-%s", name)
|
||||
connect, ok := social.SocialMap[name]
|
||||
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
|
||||
}
|
||||
|
||||
@ -56,7 +67,7 @@ func (hs *HTTPServer) OAuthLogin(ctx *models.ReqContext) {
|
||||
if errorParam != "" {
|
||||
errorDesc := ctx.Query("error_description")
|
||||
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
|
||||
}
|
||||
|
||||
@ -65,7 +76,10 @@ func (hs *HTTPServer) OAuthLogin(ctx *models.ReqContext) {
|
||||
state, err := GenStateString()
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
@ -85,14 +99,20 @@ func (hs *HTTPServer) OAuthLogin(ctx *models.ReqContext) {
|
||||
middleware.DeleteCookie(ctx.Resp, OauthStateCookieName, hs.CookieOptionsFromCfg)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
queryState := hashStatecode(ctx.Query("state"), setting.OAuthService.OAuthInfos[name].ClientSecret)
|
||||
oauthLogger.Info("state check", "queryState", queryState, "cookieState", cookieState)
|
||||
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
|
||||
}
|
||||
|
||||
@ -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)
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
@ -122,7 +145,10 @@ func (hs *HTTPServer) OAuthLogin(ctx *models.ReqContext) {
|
||||
caCert, err := ioutil.ReadFile(setting.OAuthService.OAuthInfos[name].TlsClientCa)
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
@ -137,7 +163,11 @@ func (hs *HTTPServer) OAuthLogin(ctx *models.ReqContext) {
|
||||
// get token from provider
|
||||
token, err := connect.Exchange(oauthCtx, code)
|
||||
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
|
||||
}
|
||||
// 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) {
|
||||
userInfo, err := connect.UserInfo(client, token)
|
||||
if err != nil {
|
||||
if sErr, ok := err.(*social.Error); ok {
|
||||
hs.redirectWithError(ctx, sErr)
|
||||
hs.handleOAuthLoginErrorWithRedirect(ctx, loginInfo, sErr)
|
||||
} 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
|
||||
}
|
||||
@ -163,28 +197,36 @@ func (hs *HTTPServer) OAuthLogin(ctx *models.ReqContext) {
|
||||
|
||||
// validate that we got at least an email address
|
||||
if userInfo.Email == "" {
|
||||
hs.redirectWithError(ctx, login.ErrNoEmail)
|
||||
hs.handleOAuthLoginErrorWithRedirect(ctx, loginInfo, login.ErrNoEmail)
|
||||
return
|
||||
}
|
||||
|
||||
// validate that the email is allowed to login to grafana
|
||||
if !connect.IsEmailAllowed(userInfo.Email) {
|
||||
hs.redirectWithError(ctx, login.ErrEmailNotAllowed)
|
||||
hs.handleOAuthLoginErrorWithRedirect(ctx, loginInfo, login.ErrEmailNotAllowed)
|
||||
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 {
|
||||
hs.redirectWithError(ctx, err)
|
||||
hs.handleOAuthLoginErrorWithRedirect(ctx, loginInfo, err)
|
||||
return
|
||||
}
|
||||
|
||||
// login
|
||||
if err := hs.loginUserWithUser(user, ctx); err != nil {
|
||||
hs.redirectWithError(ctx, err)
|
||||
if err := hs.loginUserWithUser(loginInfo.User, ctx); err != nil {
|
||||
hs.handleOAuthLoginErrorWithRedirect(ctx, loginInfo, err)
|
||||
return
|
||||
}
|
||||
|
||||
hs.SendLoginLog(&models.SendLoginLogCommand{
|
||||
ReqContext: ctx,
|
||||
LogAction: loginInfo.Action,
|
||||
User: loginInfo.User,
|
||||
ExternalUser: loginInfo.ExtUserInfo,
|
||||
HTTPStatus: http.StatusOK,
|
||||
})
|
||||
metrics.MApiLoginOAuth.Inc()
|
||||
|
||||
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) {
|
||||
ctx.Redirect(setting.AppSubUrl + "/")
|
||||
}
|
||||
|
||||
// syncUser syncs a Grafana user profile with the corresponding OAuth profile.
|
||||
func syncUser(ctx *models.ReqContext, token *oauth2.Token, userInfo *social.BasicUserInfo, name string,
|
||||
connect social.SocialConnector) (*models.User, error) {
|
||||
oauthLogger.Debug("Syncing Grafana user with corresponding OAuth profile")
|
||||
// buildExternalUserInfo returns a ExternalUserInfo struct from OAuth user profile
|
||||
func buildExternalUserInfo(token *oauth2.Token, userInfo *social.BasicUserInfo, name string) *models.ExternalUserInfo {
|
||||
oauthLogger.Debug("Building external user info from OAuth user info")
|
||||
|
||||
extUser := &models.ExternalUserInfo{
|
||||
AuthModule: fmt.Sprintf("oauth_%s", name),
|
||||
OAuthToken: token,
|
||||
@ -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
|
||||
cmd := &models.UpsertUserCommand{
|
||||
ReqContext: ctx,
|
||||
@ -256,3 +308,43 @@ func hashStatecode(code, seed string) string {
|
||||
hashBytes := sha256.Sum256([]byte(code + setting.SecretKey + seed))
|
||||
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,
|
||||
})
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
@ -21,6 +22,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func mockSetIndexViewData() {
|
||||
@ -553,3 +555,110 @@ func setupAuthProxyLoginTest(enableLoginToken bool) *scenarioContext {
|
||||
|
||||
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 {
|
||||
|
||||
err := loginUsingGrafanaDB(query)
|
||||
if err == nil || (err != models.ErrUserNotFound && err != ErrInvalidCredentials && err != ErrUserDisabled) {
|
||||
query.AuthModule = "grafana"
|
||||
return err
|
||||
}
|
||||
|
||||
ldapEnabled, ldapErr := loginUsingLDAP(query)
|
||||
if ldapEnabled {
|
||||
query.AuthModule = models.AuthModuleLDAP
|
||||
if ldapErr == nil || ldapErr != ldap.ErrInvalidCredentials {
|
||||
return ldapErr
|
||||
}
|
||||
@ -63,10 +65,6 @@ func AuthenticateUser(query *models.LoginUserQuery) error {
|
||||
return ErrInvalidCredentials
|
||||
}
|
||||
|
||||
if err == models.ErrUserNotFound {
|
||||
return ErrInvalidCredentials
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -27,6 +27,7 @@ func TestAuthenticateUser(t *testing.T) {
|
||||
So(sc.grafanaLoginWasCalled, ShouldBeFalse)
|
||||
So(sc.ldapLoginWasCalled, ShouldBeFalse)
|
||||
So(err, ShouldEqual, ErrPasswordEmpty)
|
||||
So(sc.loginUserQuery.AuthModule, ShouldEqual, "")
|
||||
})
|
||||
})
|
||||
|
||||
@ -44,6 +45,7 @@ func TestAuthenticateUser(t *testing.T) {
|
||||
So(sc.grafanaLoginWasCalled, ShouldBeFalse)
|
||||
So(sc.ldapLoginWasCalled, ShouldBeFalse)
|
||||
So(sc.saveInvalidLoginAttemptWasCalled, ShouldBeFalse)
|
||||
So(sc.loginUserQuery.AuthModule, ShouldEqual, "")
|
||||
})
|
||||
})
|
||||
|
||||
@ -61,6 +63,7 @@ func TestAuthenticateUser(t *testing.T) {
|
||||
So(sc.grafanaLoginWasCalled, ShouldBeTrue)
|
||||
So(sc.ldapLoginWasCalled, ShouldBeFalse)
|
||||
So(sc.saveInvalidLoginAttemptWasCalled, ShouldBeFalse)
|
||||
So(sc.loginUserQuery.AuthModule, ShouldEqual, "grafana")
|
||||
})
|
||||
})
|
||||
|
||||
@ -79,6 +82,7 @@ func TestAuthenticateUser(t *testing.T) {
|
||||
So(sc.grafanaLoginWasCalled, ShouldBeTrue)
|
||||
So(sc.ldapLoginWasCalled, ShouldBeFalse)
|
||||
So(sc.saveInvalidLoginAttemptWasCalled, ShouldBeFalse)
|
||||
So(sc.loginUserQuery.AuthModule, ShouldEqual, "grafana")
|
||||
})
|
||||
})
|
||||
|
||||
@ -91,11 +95,12 @@ func TestAuthenticateUser(t *testing.T) {
|
||||
err := AuthenticateUser(sc.loginUserQuery)
|
||||
|
||||
Convey("it should result in", func() {
|
||||
So(err, ShouldEqual, ErrInvalidCredentials)
|
||||
So(err, ShouldEqual, models.ErrUserNotFound)
|
||||
So(sc.loginAttemptValidationWasCalled, ShouldBeTrue)
|
||||
So(sc.grafanaLoginWasCalled, ShouldBeTrue)
|
||||
So(sc.ldapLoginWasCalled, ShouldBeTrue)
|
||||
So(sc.saveInvalidLoginAttemptWasCalled, ShouldBeFalse)
|
||||
So(sc.loginUserQuery.AuthModule, ShouldEqual, "")
|
||||
})
|
||||
})
|
||||
|
||||
@ -113,6 +118,7 @@ func TestAuthenticateUser(t *testing.T) {
|
||||
So(sc.grafanaLoginWasCalled, ShouldBeTrue)
|
||||
So(sc.ldapLoginWasCalled, ShouldBeTrue)
|
||||
So(sc.saveInvalidLoginAttemptWasCalled, ShouldBeTrue)
|
||||
So(sc.loginUserQuery.AuthModule, ShouldEqual, "ldap")
|
||||
})
|
||||
})
|
||||
|
||||
@ -130,6 +136,7 @@ func TestAuthenticateUser(t *testing.T) {
|
||||
So(sc.grafanaLoginWasCalled, ShouldBeTrue)
|
||||
So(sc.ldapLoginWasCalled, ShouldBeTrue)
|
||||
So(sc.saveInvalidLoginAttemptWasCalled, ShouldBeFalse)
|
||||
So(sc.loginUserQuery.AuthModule, ShouldEqual, "ldap")
|
||||
})
|
||||
})
|
||||
|
||||
@ -148,6 +155,7 @@ func TestAuthenticateUser(t *testing.T) {
|
||||
So(sc.grafanaLoginWasCalled, ShouldBeTrue)
|
||||
So(sc.ldapLoginWasCalled, ShouldBeTrue)
|
||||
So(sc.saveInvalidLoginAttemptWasCalled, ShouldBeFalse)
|
||||
So(sc.loginUserQuery.AuthModule, ShouldEqual, "ldap")
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -14,6 +14,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/components/apikeygen"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/infra/remotecache"
|
||||
"github.com/grafana/grafana/pkg/login"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/rendering"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
@ -178,8 +179,12 @@ func initContextWithBasicAuth(ctx *models.ReqContext, orgId int64) bool {
|
||||
ctx.Logger.Debug(
|
||||
"Failed to authorize the user",
|
||||
"username", username,
|
||||
"err", err,
|
||||
)
|
||||
|
||||
if err == models.ErrUserNotFound {
|
||||
err = login.ErrInvalidCredentials
|
||||
}
|
||||
ctx.JsonApiErr(401, errStringInvalidUsernamePassword, err)
|
||||
return true
|
||||
}
|
||||
|
@ -65,6 +65,15 @@ type DeleteAuthInfoCommand struct {
|
||||
UserAuth *UserAuth
|
||||
}
|
||||
|
||||
type SendLoginLogCommand struct {
|
||||
ReqContext *ReqContext
|
||||
LogAction string
|
||||
User *User
|
||||
ExternalUser *ExternalUserInfo
|
||||
HTTPStatus int
|
||||
Error error
|
||||
}
|
||||
|
||||
// ----------------------
|
||||
// QUERIES
|
||||
|
||||
@ -74,6 +83,7 @@ type LoginUserQuery struct {
|
||||
Password string
|
||||
User *User
|
||||
IpAddress string
|
||||
AuthModule string
|
||||
}
|
||||
|
||||
type GetUserByAuthInfoQuery struct {
|
||||
|
Loading…
Reference in New Issue
Block a user