summaryrefslogtreecommitdiffstats
path: root/api/user.go
diff options
context:
space:
mode:
authorHarrison Healey <harrisonmhealey@gmail.com>2016-05-03 14:10:36 -0400
committerChristopher Speller <crspeller@gmail.com>2016-05-03 14:10:36 -0400
commit87989b8afd4666a72940389db716b6500d0a9ec3 (patch)
treed4b0270eb4a9adbff0dd1b6f527ddcccbc9a83d9 /api/user.go
parente76a30bca0690bad53a4cabd6c7c629e89c17268 (diff)
downloadchat-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.go236
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 {