diff options
author | Harrison Healey <harrisonmhealey@gmail.com> | 2016-05-03 14:10:36 -0400 |
---|---|---|
committer | Christopher Speller <crspeller@gmail.com> | 2016-05-03 14:10:36 -0400 |
commit | 87989b8afd4666a72940389db716b6500d0a9ec3 (patch) | |
tree | d4b0270eb4a9adbff0dd1b6f527ddcccbc9a83d9 /api/user.go | |
parent | e76a30bca0690bad53a4cabd6c7c629e89c17268 (diff) | |
download | chat-87989b8afd4666a72940389db716b6500d0a9ec3.tar.gz chat-87989b8afd4666a72940389db716b6500d0a9ec3.tar.bz2 chat-87989b8afd4666a72940389db716b6500d0a9ec3.zip |
PLT-2258 Unified login screen and related APIs (#2820)
* Unified login screen and related APIs
* Refactored login API call to be less convoluted
* Removed LDAP login prompt from invite process
* Fixed existing LDAP users being able to log in if LDAP was configured, but disabled
* Gofmt
* Future proofed login API
* Updated login APIs based on feedback
* Added additional auditing to login API
* Actually removed loginById
Diffstat (limited to 'api/user.go')
-rw-r--r-- | api/user.go | 236 |
1 files changed, 66 insertions, 170 deletions
diff --git a/api/user.go b/api/user.go index d8e2e6623..aee4dab61 100644 --- a/api/user.go +++ b/api/user.go @@ -45,7 +45,6 @@ func InitUser() { BaseRoutes.Users.Handle("/reset_password", ApiAppHandler(resetPassword)).Methods("POST") BaseRoutes.Users.Handle("/login", ApiAppHandler(login)).Methods("POST") BaseRoutes.Users.Handle("/logout", ApiAppHandler(logout)).Methods("POST") - BaseRoutes.Users.Handle("/login_ldap", ApiAppHandler(loginLdap)).Methods("POST") BaseRoutes.Users.Handle("/revoke_session", ApiUserRequired(revokeSession)).Methods("POST") BaseRoutes.Users.Handle("/attach_device", ApiUserRequired(attachDeviceId)).Methods("POST") BaseRoutes.Users.Handle("/verify_email", ApiAppHandler(verifyEmail)).Methods("POST") @@ -333,7 +332,7 @@ func CreateOAuthUser(c *Context, w http.ResponseWriter, r *http.Request, service } } - Login(c, w, r, ruser, "") + doLogin(c, w, r, ruser, "") if c.Err != nil { return nil } @@ -431,114 +430,86 @@ func SendVerifyEmailAndForget(c *Context, userId, userEmail, siteURL string) { func login(c *Context, w http.ResponseWriter, r *http.Request) { props := model.MapFromJson(r.Body) - if len(props["password"]) == 0 { + id := props["id"] + loginId := props["login_id"] + password := props["password"] + mfaToken := props["token"] + deviceId := props["device_id"] + + if len(password) == 0 { c.Err = model.NewLocAppError("login", "api.user.login.blank_pwd.app_error", nil, "") c.Err.StatusCode = http.StatusBadRequest return } var user *model.User - if len(props["id"]) != 0 { - user = LoginById(c, w, r, props["id"], props["password"], props["token"], props["device_id"]) - } else if len(props["email"]) != 0 { - user = LoginByEmail(c, w, r, props["email"], props["name"], props["password"], props["token"], props["device_id"]) - } else if len(props["username"]) != 0 { - user = LoginByUsername(c, w, r, props["username"], props["name"], props["password"], props["token"], props["device_id"]) - } else { - c.Err = model.NewLocAppError("login", "api.user.login.not_provided.app_error", nil, "") - c.Err.StatusCode = http.StatusBadRequest - return - } + var err *model.AppError - if c.Err != nil { - return - } + if len(id) != 0 { + c.LogAuditWithUserId(id, "attempt") - if user != nil { - user.Sanitize(map[string]bool{}) - } else { - user = &model.User{} - } - w.Write([]byte(user.ToJson())) -} - -func doUserPasswordAuthenticationAndLogin(c *Context, w http.ResponseWriter, r *http.Request, user *model.User, password string, mfaToken string, deviceId string) bool { - c.LogAuditWithUserId(user.Id, "attempt") - if err := checkPasswordAndAllCriteria(user, password, mfaToken); err != nil { - c.LogAuditWithUserId(user.Id, "fail") - c.Err = err - c.Err.StatusCode = http.StatusUnauthorized - return false - } else { - Login(c, w, r, user, deviceId) - c.LogAuditWithUserId(user.Id, "success") - return true - } -} - -func LoginById(c *Context, w http.ResponseWriter, r *http.Request, userId, password, mfaToken, deviceId string) *model.User { - if result := <-Srv.Store.User().Get(userId); result.Err != nil { - c.Err = result.Err - return nil + if result := <-Srv.Store.User().Get(id); result.Err != nil { + c.LogAuditWithUserId(user.Id, "failure") + c.Err = result.Err + c.Err.StatusCode = http.StatusBadRequest + return + } else { + user = result.Data.(*model.User) + } } else { - user := result.Data.(*model.User) + c.LogAudit("attempt") - if len(user.AuthData) != 0 { - c.Err = model.NewLocAppError("LoginById", "api.user.login_by_email.sign_in.app_error", - map[string]interface{}{"AuthService": user.AuthService}, "") - return nil + if user, err = getUserForLogin(loginId); err != nil { + c.LogAudit("failure") + c.Err = err + return } - if doUserPasswordAuthenticationAndLogin(c, w, r, user, password, mfaToken, deviceId) { - return user - } + c.LogAuditWithUserId(user.Id, "attempt") } - return nil -} + // and then authenticate them + if user, err = authenticateUser(user, password, mfaToken); err != nil { + c.LogAuditWithUserId(user.Id, "failure") + c.Err = err + return + } -func LoginByEmail(c *Context, w http.ResponseWriter, r *http.Request, email, name, password, mfaToken, deviceId string) *model.User { - if result := <-Srv.Store.User().GetByEmail(email); result.Err != nil { - c.Err = result.Err - c.Err.StatusCode = http.StatusUnauthorized - return nil - } else { - user := result.Data.(*model.User) + c.LogAuditWithUserId(user.Id, "success") - if len(user.AuthData) != 0 { - c.Err = model.NewLocAppError("LoginByEmail", "api.user.login_by_email.sign_in.app_error", - map[string]interface{}{"AuthService": user.AuthService}, "") - return nil - } + doLogin(c, w, r, user, deviceId) - if doUserPasswordAuthenticationAndLogin(c, w, r, user, password, mfaToken, deviceId) { - return user - } - } + user.Sanitize(map[string]bool{}) - return nil + w.Write([]byte(user.ToJson())) } -func LoginByUsername(c *Context, w http.ResponseWriter, r *http.Request, username, name, password, mfaToken, deviceId string) *model.User { - if result := <-Srv.Store.User().GetByUsername(username); result.Err != nil { - c.Err = result.Err - c.Err.StatusCode = http.StatusUnauthorized - return nil - } else { - user := result.Data.(*model.User) +func getUserForLogin(loginId string) (*model.User, *model.AppError) { + ldapAvailable := *utils.Cfg.LdapSettings.Enable && einterfaces.GetLdapInterface() != nil - if len(user.AuthData) != 0 { - c.Err = model.NewLocAppError("LoginByUsername", "api.user.login_by_email.sign_in.app_error", - map[string]interface{}{"AuthService": user.AuthService}, "") - return nil + if result := <-Srv.Store.User().GetForLogin( + loginId, + *utils.Cfg.EmailSettings.EnableSignInWithUsername, + *utils.Cfg.EmailSettings.EnableSignInWithEmail, + ldapAvailable, + ); result.Err != nil { + + if !ldapAvailable { + // failed to find user and no LDAP server to fall back on + result.Err.StatusCode = http.StatusBadRequest + return nil, result.Err } - if doUserPasswordAuthenticationAndLogin(c, w, r, user, password, mfaToken, deviceId) { - return user + // fall back to LDAP server to see if we can find a user + if ldapUser, ldapErr := einterfaces.GetLdapInterface().GetUser(loginId); ldapErr != nil { + ldapErr.StatusCode = http.StatusBadRequest + return nil, ldapErr + } else { + return ldapUser, nil } + } else { + return result.Data.(*model.User), nil } - - return nil } func LoginByOAuth(c *Context, w http.ResponseWriter, r *http.Request, service string, userData io.Reader) *model.User { @@ -570,80 +541,13 @@ func LoginByOAuth(c *Context, w http.ResponseWriter, r *http.Request, service st return nil } else { user = result.Data.(*model.User) - Login(c, w, r, user, "") + doLogin(c, w, r, user, "") return user } } -func loginLdap(c *Context, w http.ResponseWriter, r *http.Request) { - if !*utils.Cfg.LdapSettings.Enable { - c.Err = model.NewLocAppError("loginLdap", "api.user.login_ldap.disabled.app_error", nil, "") - c.Err.StatusCode = http.StatusNotImplemented - return - } - - props := model.MapFromJson(r.Body) - - password := props["password"] - id := props["id"] - mfaToken := props["token"] - - if len(password) == 0 { - c.Err = model.NewLocAppError("loginLdap", "api.user.login_ldap.blank_pwd.app_error", nil, "") - c.Err.StatusCode = http.StatusBadRequest - return - } - - if len(id) == 0 { - c.Err = model.NewLocAppError("loginLdap", "api.user.login_ldap.need_id.app_error", nil, "") - c.Err.StatusCode = http.StatusBadRequest - return - } - - ldapInterface := einterfaces.GetLdapInterface() - if ldapInterface == nil { - c.Err = model.NewLocAppError("loginLdap", "api.user.login_ldap.not_available.app_error", nil, "") - c.Err.StatusCode = http.StatusNotImplemented - return - } - - user, err := ldapInterface.DoLogin(id, password) - if err != nil { - if user != nil { - c.LogAuditWithUserId(user.Id, "attempt") - c.LogAuditWithUserId(user.Id, "fail") - } else { - c.LogAudit("attempt") - c.LogAudit("fail") - } - c.Err = err - c.Err.StatusCode = http.StatusUnauthorized - return - } - c.LogAuditWithUserId(user.Id, "attempt") - - if err = checkUserAdditionalAuthenticationCriteria(user, mfaToken); err != nil { - c.LogAuditWithUserId(user.Id, "fail") - c.Err = err - c.Err.StatusCode = http.StatusUnauthorized - return - } - - // User is authenticated at this point - - Login(c, w, r, user, props["device_id"]) - c.LogAuditWithUserId(user.Id, "success") - - if user != nil { - user.Sanitize(map[string]bool{}) - } else { - user = &model.User{} - } - w.Write([]byte(user.ToJson())) -} - // User MUST be authenticated completely before calling Login -func Login(c *Context, w http.ResponseWriter, r *http.Request, user *model.User, deviceId string) { +func doLogin(c *Context, w http.ResponseWriter, r *http.Request, user *model.User, deviceId string) { session := &model.Session{UserId: user.Id, Roles: user.Roles, DeviceId: deviceId, IsOAuth: false} @@ -2371,28 +2275,20 @@ func checkMfa(c *Context, w http.ResponseWriter, r *http.Request) { props := model.MapFromJson(r.Body) - method := props["method"] - if method != model.USER_AUTH_SERVICE_EMAIL && - method != model.USER_AUTH_SERVICE_USERNAME && - method != model.USER_AUTH_SERVICE_LDAP { - c.SetInvalidParam("checkMfa", "method") - return - } - loginId := props["login_id"] if len(loginId) == 0 { c.SetInvalidParam("checkMfa", "login_id") return } - var uchan store.StoreChannel - if method == model.USER_AUTH_SERVICE_EMAIL { - uchan = Srv.Store.User().GetByEmail(loginId) - } else if method == model.USER_AUTH_SERVICE_USERNAME { - uchan = Srv.Store.User().GetByUsername(loginId) - } else if method == model.USER_AUTH_SERVICE_LDAP { - uchan = Srv.Store.User().GetByAuth(loginId, model.USER_AUTH_SERVICE_LDAP) - } + // we don't need to worry about contacting the ldap server to get this user because + // only users already in the system could have MFA enabled + uchan := Srv.Store.User().GetForLogin( + loginId, + *utils.Cfg.EmailSettings.EnableSignInWithUsername, + *utils.Cfg.EmailSettings.EnableSignInWithEmail, + *utils.Cfg.LdapSettings.Enable, + ) rdata := map[string]string{} if result := <-uchan; result.Err != nil { |