@@ -64,8 +64,8 @@ func (a *App) IsPasswordValid(rctx request.CTX, password string) *model.AppError
|
||||
func (a *App) CheckPasswordAndAllCriteria(rctx request.CTX, userID string, password string, mfaToken string) *model.AppError {
|
||||
// MM-37585
|
||||
// Use locks to avoid concurrently checking AND updating the failed login attempts.
|
||||
a.ch.loginAttemptsMut.Lock()
|
||||
defer a.ch.loginAttemptsMut.Unlock()
|
||||
a.ch.emailLoginAttemptsMut.Lock()
|
||||
defer a.ch.emailLoginAttemptsMut.Unlock()
|
||||
|
||||
user, err := a.GetUser(userID)
|
||||
if err != nil {
|
||||
@@ -149,31 +149,86 @@ func (a *App) DoubleCheckPassword(rctx request.CTX, user *model.User, password s
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) checkLdapUserPasswordAndAllCriteria(rctx request.CTX, ldapId *string, password string, mfaToken string) (*model.User, *model.AppError) {
|
||||
if a.Ldap() == nil || ldapId == nil {
|
||||
func (a *App) checkLdapUserPasswordAndAllCriteria(rctx request.CTX, user *model.User, password, mfaToken string) (*model.User, *model.AppError) {
|
||||
// MM-37585: Use locks to avoid concurrently checking AND updating the failed login attempts.
|
||||
a.ch.ldapLoginAttemptsMut.Lock()
|
||||
defer a.ch.ldapLoginAttemptsMut.Unlock()
|
||||
|
||||
// We need to get the latest value of the user from the database after we acquire the lock. user is nil for first-time LDAP users.
|
||||
if user.Id != "" {
|
||||
var err *model.AppError
|
||||
user, err = a.GetUser(user.Id)
|
||||
if err != nil {
|
||||
if err.Id != MissingAccountError {
|
||||
err.StatusCode = http.StatusInternalServerError
|
||||
return nil, err
|
||||
}
|
||||
err.StatusCode = http.StatusBadRequest
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
ldapID := user.AuthData
|
||||
|
||||
if a.Ldap() == nil || ldapID == nil {
|
||||
err := model.NewAppError("doLdapAuthentication", "api.user.login_ldap.not_available.app_error", nil, "", http.StatusNotImplemented)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ldapUser, err := a.Ldap().DoLogin(rctx, *ldapId, password)
|
||||
// First time LDAP users will not have a userID
|
||||
if user.Id != "" {
|
||||
if err := checkUserLoginAttempts(user, *a.Config().LdapSettings.MaximumLoginAttempts); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
ldapUser, err := a.Ldap().DoLogin(rctx, *ldapID, password)
|
||||
if err != nil {
|
||||
// If this is a new LDAP user, we need to get the user from the database because DoLogin will have created the user.
|
||||
if user.Id == "" {
|
||||
var getUserByAuthErr *model.AppError
|
||||
ldapUser, getUserByAuthErr = a.GetUserByAuth(ldapID, model.UserAuthServiceLdap)
|
||||
if getUserByAuthErr != nil {
|
||||
return nil, getUserByAuthErr
|
||||
}
|
||||
} else {
|
||||
ldapUser = user
|
||||
}
|
||||
|
||||
// Log a info to make it easier to admin to spot that a user tried to log in with a legitimate user name.
|
||||
if err.Id == "ent.ldap.do_login.invalid_password.app_error" {
|
||||
rctx.Logger().LogM(mlog.MlvlLDAPInfo, "A user tried to sign in, which matched an LDAP account, but the password was incorrect.", mlog.String("ldap_id", *ldapId))
|
||||
rctx.Logger().LogM(mlog.MlvlLDAPInfo, "A user tried to sign in, which matched an LDAP account, but the password was incorrect.", mlog.String("ldap_id", *ldapID))
|
||||
|
||||
if passErr := a.Srv().Store().User().UpdateFailedPasswordAttempts(ldapUser.Id, ldapUser.FailedAttempts+1); passErr != nil {
|
||||
return nil, model.NewAppError("CheckPasswordAndAllCriteria", "app.user.update_failed_pwd_attempts.app_error", nil, "", http.StatusInternalServerError).Wrap(passErr)
|
||||
}
|
||||
}
|
||||
|
||||
err.StatusCode = http.StatusUnauthorized
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := a.CheckUserMfa(rctx, ldapUser, mfaToken); err != nil {
|
||||
if err = a.CheckUserMfa(rctx, ldapUser, mfaToken); err != nil {
|
||||
// If the mfaToken is not set, we assume the client used this as a pre-flight request to query the server
|
||||
// about the MFA state of the user in question
|
||||
if mfaToken != "" && ldapUser.Id != "" {
|
||||
if passErr := a.Srv().Store().User().UpdateFailedPasswordAttempts(ldapUser.Id, ldapUser.FailedAttempts+1); passErr != nil {
|
||||
return nil, model.NewAppError("CheckPasswordAndAllCriteria", "app.user.update_failed_pwd_attempts.app_error", nil, "", http.StatusInternalServerError).Wrap(passErr)
|
||||
}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := checkUserNotDisabled(ldapUser); err != nil {
|
||||
if err = checkUserNotDisabled(ldapUser); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if ldapUser.FailedAttempts > 0 {
|
||||
if passErr := a.Srv().Store().User().UpdateFailedPasswordAttempts(ldapUser.Id, 0); passErr != nil {
|
||||
return nil, model.NewAppError("CheckPasswordAndAllCriteria", "app.user.update_failed_pwd_attempts.app_error", nil, "", http.StatusInternalServerError).Wrap(passErr)
|
||||
}
|
||||
}
|
||||
|
||||
// user successfully authenticated
|
||||
return ldapUser, nil
|
||||
}
|
||||
@@ -286,6 +341,9 @@ func (a *App) MFARequired(rctx request.CTX) *model.AppError {
|
||||
|
||||
func checkUserLoginAttempts(user *model.User, max int) *model.AppError {
|
||||
if user.FailedAttempts >= max {
|
||||
if user.AuthService == model.UserAuthServiceLdap {
|
||||
return model.NewAppError("checkUserLoginAttempts", "api.user.check_user_login_attempts.too_many_ldap.app_error", nil, "user_id="+user.Id, http.StatusUnauthorized)
|
||||
}
|
||||
return model.NewAppError("checkUserLoginAttempts", "api.user.check_user_login_attempts.too_many.app_error", nil, "user_id="+user.Id, http.StatusUnauthorized)
|
||||
}
|
||||
|
||||
@@ -316,7 +374,7 @@ func (a *App) authenticateUser(rctx request.CTX, user *model.User, password, mfa
|
||||
return user, err
|
||||
}
|
||||
|
||||
ldapUser, err := a.checkLdapUserPasswordAndAllCriteria(rctx, user.AuthData, password, mfaToken)
|
||||
ldapUser, err := a.checkLdapUserPasswordAndAllCriteria(rctx, user, password, mfaToken)
|
||||
if err != nil {
|
||||
err.StatusCode = http.StatusUnauthorized
|
||||
return user, err
|
||||
|
||||
Ссылка в новой задаче
Block a user