diff options
Diffstat (limited to 'api/user.go')
-rw-r--r-- | api/user.go | 1399 |
1 files changed, 561 insertions, 838 deletions
diff --git a/api/user.go b/api/user.go index 08d096c51..16ba45dc4 100644 --- a/api/user.go +++ b/api/user.go @@ -5,8 +5,6 @@ package api import ( "bytes" - "crypto/tls" - b64 "encoding/base64" "fmt" l4g "github.com/alecthomas/log4go" "github.com/disintegration/imaging" @@ -34,45 +32,44 @@ import ( "time" ) -func InitUser(r *mux.Router) { +func InitUser() { l4g.Debug(utils.T("api.user.init.debug")) - sr := r.PathPrefix("/users").Subrouter() - sr.Handle("/create", ApiAppHandler(createUser)).Methods("POST") - sr.Handle("/update", ApiUserRequired(updateUser)).Methods("POST") - sr.Handle("/update_roles", ApiUserRequired(updateRoles)).Methods("POST") - sr.Handle("/update_active", ApiUserRequired(updateActive)).Methods("POST") - sr.Handle("/update_notify", ApiUserRequired(updateUserNotify)).Methods("POST") - sr.Handle("/newpassword", ApiUserRequired(updatePassword)).Methods("POST") - sr.Handle("/send_password_reset", ApiAppHandler(sendPasswordReset)).Methods("POST") - sr.Handle("/reset_password", ApiAppHandler(resetPassword)).Methods("POST") - sr.Handle("/login", ApiAppHandler(login)).Methods("POST") - sr.Handle("/logout", ApiUserRequired(logout)).Methods("POST") - sr.Handle("/login_ldap", ApiAppHandler(loginLdap)).Methods("POST") - sr.Handle("/revoke_session", ApiUserRequired(revokeSession)).Methods("POST") - sr.Handle("/attach_device", ApiUserRequired(attachDeviceId)).Methods("POST") - sr.Handle("/verify_email", ApiAppHandler(verifyEmail)).Methods("POST") - sr.Handle("/resend_verification", ApiAppHandler(resendVerification)).Methods("POST") - sr.Handle("/mfa", ApiAppHandler(checkMfa)).Methods("POST") - sr.Handle("/generate_mfa_qr", ApiUserRequiredTrustRequester(generateMfaQrCode)).Methods("GET") - sr.Handle("/update_mfa", ApiUserRequired(updateMfa)).Methods("POST") - - sr.Handle("/newimage", ApiUserRequired(uploadProfileImage)).Methods("POST") - - sr.Handle("/me", ApiAppHandler(getMe)).Methods("GET") - sr.Handle("/me_logged_in", ApiAppHandler(getMeLoggedIn)).Methods("GET") - sr.Handle("/status", ApiUserRequiredActivity(getStatuses, false)).Methods("POST") - sr.Handle("/profiles", ApiUserRequired(getProfiles)).Methods("GET") - sr.Handle("/profiles/{id:[A-Za-z0-9]+}", ApiUserRequired(getProfiles)).Methods("GET") - sr.Handle("/{id:[A-Za-z0-9]+}", ApiUserRequired(getUser)).Methods("GET") - sr.Handle("/{id:[A-Za-z0-9]+}/sessions", ApiUserRequired(getSessions)).Methods("GET") - sr.Handle("/{id:[A-Za-z0-9]+}/audits", ApiUserRequired(getAudits)).Methods("GET") - sr.Handle("/{id:[A-Za-z0-9]+}/image", ApiUserRequiredTrustRequester(getProfileImage)).Methods("GET") - - sr.Handle("/claim/email_to_oauth", ApiAppHandler(emailToOAuth)).Methods("POST") - sr.Handle("/claim/oauth_to_email", ApiUserRequired(oauthToEmail)).Methods("POST") - sr.Handle("/claim/email_to_ldap", ApiAppHandler(emailToLdap)).Methods("POST") - sr.Handle("/claim/ldap_to_email", ApiAppHandler(ldapToEmail)).Methods("POST") + BaseRoutes.Users.Handle("/create", ApiAppHandler(createUser)).Methods("POST") + BaseRoutes.Users.Handle("/update", ApiUserRequired(updateUser)).Methods("POST") + BaseRoutes.Users.Handle("/update_roles", ApiUserRequired(updateRoles)).Methods("POST") + BaseRoutes.Users.Handle("/update_active", ApiUserRequired(updateActive)).Methods("POST") + BaseRoutes.Users.Handle("/update_notify", ApiUserRequired(updateUserNotify)).Methods("POST") + BaseRoutes.Users.Handle("/newpassword", ApiUserRequired(updatePassword)).Methods("POST") + BaseRoutes.Users.Handle("/send_password_reset", ApiAppHandler(sendPasswordReset)).Methods("POST") + 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") + BaseRoutes.Users.Handle("/resend_verification", ApiAppHandler(resendVerification)).Methods("POST") + BaseRoutes.Users.Handle("/newimage", ApiUserRequired(uploadProfileImage)).Methods("POST") + BaseRoutes.Users.Handle("/me", ApiAppHandler(getMe)).Methods("GET") + BaseRoutes.Users.Handle("/initial_load", ApiAppHandler(getInitialLoad)).Methods("GET") + BaseRoutes.Users.Handle("/status", ApiUserRequiredActivity(getStatuses, false)).Methods("POST") + BaseRoutes.Users.Handle("/direct_profiles", ApiUserRequired(getDirectProfiles)).Methods("GET") + BaseRoutes.Users.Handle("/profiles/{id:[A-Za-z0-9]+}", ApiUserRequired(getProfiles)).Methods("GET") + + BaseRoutes.Users.Handle("/mfa", ApiAppHandler(checkMfa)).Methods("POST") + BaseRoutes.Users.Handle("/generate_mfa_qr", ApiUserRequiredTrustRequester(generateMfaQrCode)).Methods("GET") + BaseRoutes.Users.Handle("/update_mfa", ApiUserRequired(updateMfa)).Methods("POST") + + BaseRoutes.Users.Handle("/claim/email_to_oauth", ApiAppHandler(emailToOAuth)).Methods("POST") + BaseRoutes.Users.Handle("/claim/oauth_to_email", ApiUserRequired(oauthToEmail)).Methods("POST") + BaseRoutes.Users.Handle("/claim/email_to_ldap", ApiAppHandler(emailToLdap)).Methods("POST") + BaseRoutes.Users.Handle("/claim/ldap_to_email", ApiAppHandler(ldapToEmail)).Methods("POST") + + BaseRoutes.NeedUser.Handle("/get", ApiUserRequired(getUser)).Methods("GET") + BaseRoutes.NeedUser.Handle("/sessions", ApiUserRequired(getSessions)).Methods("GET") + BaseRoutes.NeedUser.Handle("/audits", ApiUserRequired(getAudits)).Methods("GET") + BaseRoutes.NeedUser.Handle("/image", ApiUserRequiredTrustRequester(getProfileImage)).Methods("GET") } func createUser(c *Context, w http.ResponseWriter, r *http.Request) { @@ -89,24 +86,13 @@ func createUser(c *Context, w http.ResponseWriter, r *http.Request) { return } - // the user's username is checked to be valid when they are saved to the database - - user.EmailVerified = false - - var team *model.Team - - if result := <-Srv.Store.Team().Get(user.TeamId); result.Err != nil { - c.Err = result.Err - return - } else { - team = result.Data.(*model.Team) - } - hash := r.URL.Query().Get("h") - + teamId := "" + var team *model.Team sendWelcomeEmail := true + user.EmailVerified = false - if IsVerifyHashRequired(user, team, hash) { + if len(hash) > 0 { data := r.URL.Query().Get("d") props := model.MapFromJson(strings.NewReader(data)) @@ -121,9 +107,14 @@ func createUser(c *Context, w http.ResponseWriter, r *http.Request) { return } - if user.TeamId != props["id"] { - c.Err = model.NewLocAppError("createUser", "api.user.create_user.team_name.app_error", nil, data) + teamId = props["id"] + + // try to load the team to make sure it exists + if result := <-Srv.Store.Team().Get(teamId); result.Err != nil { + c.Err = result.Err return + } else { + team = result.Data.(*model.Team) } user.Email = props["email"] @@ -131,8 +122,33 @@ func createUser(c *Context, w http.ResponseWriter, r *http.Request) { sendWelcomeEmail = false } - if user.IsSSOUser() { - user.EmailVerified = true + inviteId := r.URL.Query().Get("iid") + if len(inviteId) > 0 { + if result := <-Srv.Store.Team().GetByInviteId(inviteId); result.Err != nil { + c.Err = result.Err + return + } else { + team = result.Data.(*model.Team) + teamId = team.Id + } + } + + firstAccount := false + if sessionCache.Len() == 0 { + if cr := <-Srv.Store.User().GetTotalUsersCount(); cr.Err != nil { + c.Err = cr.Err + return + } else { + count := cr.Data.(int64) + if count <= 0 { + firstAccount = true + } + } + } + + if !firstAccount && !*utils.Cfg.TeamSettings.EnableOpenServer && len(teamId) == 0 { + c.Err = model.NewLocAppError("createUser", "api.user.create_user.no_open_server", nil, "email="+user.Email) + return } if !CheckUserDomain(user, utils.Cfg.TeamSettings.RestrictCreationToDomains) { @@ -140,14 +156,24 @@ func createUser(c *Context, w http.ResponseWriter, r *http.Request) { return } - ruser, err := CreateUser(team, user) + ruser, err := CreateUser(user) if err != nil { c.Err = err return } + if len(teamId) > 0 { + err := JoinUserToTeam(team, ruser) + if err != nil { + c.Err = err + return + } + + addDirectChannelsAndForget(team.Id, ruser) + } + if sendWelcomeEmail { - sendWelcomeEmailAndForget(c, ruser.Id, ruser.Email, team.Name, team.DisplayName, c.GetSiteURL(), c.GetTeamURLFromTeam(team), ruser.EmailVerified) + sendWelcomeEmailAndForget(c, ruser.Id, ruser.Email, c.GetSiteURL(), ruser.EmailVerified) } w.Write([]byte(ruser.ToJson())) @@ -196,26 +222,19 @@ func IsVerifyHashRequired(user *model.User, team *model.Team, hash string) bool return shouldVerifyHash } -func CreateUser(team *model.Team, user *model.User) (*model.User, *model.AppError) { +func CreateUser(user *model.User) (*model.User, *model.AppError) { - channelRole := "" - if team.Email == user.Email { - user.Roles = model.ROLE_TEAM_ADMIN - channelRole = model.CHANNEL_ROLE_ADMIN - - // Below is a speical case where the first user in the entire - // system is granted the system_admin role instead of admin - if result := <-Srv.Store.User().GetTotalUsersCount(); result.Err != nil { - return nil, result.Err - } else { - count := result.Data.(int64) - if count <= 0 { - user.Roles = model.ROLE_SYSTEM_ADMIN - } - } + user.Roles = "" + // Below is a speical case where the first user in the entire + // system is granted the system_admin role instead of admin + if result := <-Srv.Store.User().GetTotalUsersCount(); result.Err != nil { + return nil, result.Err } else { - user.Roles = "" + count := result.Data.(int64) + if count <= 0 { + user.Roles = model.ROLE_SYSTEM_ADMIN + } } user.MakeNonNil() @@ -226,13 +245,6 @@ func CreateUser(team *model.Team, user *model.User) (*model.User, *model.AppErro } else { ruser := result.Data.(*model.User) - // Soft error if there is an issue joining the default channels - if err := JoinDefaultChannels(ruser, channelRole); err != nil { - l4g.Error(utils.T("api.user.create_user.joining.error"), ruser.Id, ruser.TeamId, err) - } - - addDirectChannelsAndForget(ruser) - if user.EmailVerified { if cresult := <-Srv.Store.User().VerifyEmail(ruser.Id); cresult.Err != nil { l4g.Error(utils.T("api.user.create_user.verified.error"), cresult.Err) @@ -247,15 +259,13 @@ func CreateUser(team *model.Team, user *model.User) (*model.User, *model.AppErro ruser.Sanitize(map[string]bool{}) // This message goes to every channel, so the channelId is irrelevant - message := model.NewMessage(team.Id, "", ruser.Id, model.ACTION_NEW_USER) - - PublishAndForget(message) + PublishAndForget(model.NewMessage("", "", ruser.Id, model.ACTION_NEW_USER)) return ruser, nil } } -func CreateOAuthUser(c *Context, w http.ResponseWriter, r *http.Request, service string, userData io.Reader, team *model.Team) *model.User { +func CreateOAuthUser(c *Context, w http.ResponseWriter, r *http.Request, service string, userData io.Reader, teamId string) *model.User { var user *model.User provider := einterfaces.GetOauthProvider(service) if provider == nil { @@ -270,49 +280,59 @@ func CreateOAuthUser(c *Context, w http.ResponseWriter, r *http.Request, service return nil } - suchan := Srv.Store.User().GetByAuth(team.Id, user.AuthData, service) - euchan := Srv.Store.User().GetByEmail(team.Id, user.Email) + suchan := Srv.Store.User().GetByAuth(user.AuthData, service) + euchan := Srv.Store.User().GetByEmail(user.Email) - if team.Email == "" { - team.Email = user.Email - if result := <-Srv.Store.Team().Update(team); result.Err != nil { - c.Err = result.Err - return nil - } - } else { - found := true - count := 0 - for found { - if found = IsUsernameTaken(user.Username, team.Id); c.Err != nil { - return nil - } else if found { - user.Username = user.Username + strconv.Itoa(count) - count += 1 - } + var tchan store.StoreChannel + if len(teamId) != 0 { + tchan = Srv.Store.Team().Get(teamId) + } + + found := true + count := 0 + for found { + if found = IsUsernameTaken(user.Username); found { + user.Username = user.Username + strconv.Itoa(count) + count += 1 } } if result := <-suchan; result.Err == nil { - c.Err = model.NewLocAppError("signupCompleteOAuth", "api.user.create_oauth_user.already_used.app_error", - map[string]interface{}{"Service": service, "DisplayName": team.DisplayName}, "email="+user.Email) + c.Err = model.NewLocAppError("CreateOAuthUser", "api.user.create_oauth_user.already_used.app_error", + map[string]interface{}{"Service": service}, "email="+user.Email) return nil } if result := <-euchan; result.Err == nil { - c.Err = model.NewLocAppError("signupCompleteOAuth", "api.user.create_oauth_user.already_attached.app_error", - map[string]interface{}{"Service": service, "DisplayName": team.DisplayName}, "email="+user.Email) + c.Err = model.NewLocAppError("CreateOAuthUser", "api.user.create_oauth_user.already_attached.app_error", + map[string]interface{}{"Service": service}, "email="+user.Email) return nil } - user.TeamId = team.Id user.EmailVerified = true - ruser, err := CreateUser(team, user) + ruser, err := CreateUser(user) if err != nil { c.Err = err return nil } + if tchan != nil { + if result := <-tchan; result.Err != nil { + c.Err = result.Err + return nil + } else { + team := result.Data.(*model.Team) + err = JoinUserToTeam(team, user) + if err != nil { + c.Err = err + return nil + } + + addDirectChannelsAndForget(team.Id, user) + } + } + Login(c, w, r, ruser, "") if c.Err != nil { return nil @@ -321,23 +341,23 @@ func CreateOAuthUser(c *Context, w http.ResponseWriter, r *http.Request, service return ruser } -func sendWelcomeEmailAndForget(c *Context, userId, email, teamName, teamDisplayName, siteURL, teamURL string, verified bool) { +func sendWelcomeEmailAndForget(c *Context, userId string, email string, siteURL string, verified bool) { go func() { subjectPage := utils.NewHTMLTemplate("welcome_subject", c.Locale) - subjectPage.Props["Subject"] = c.T("api.templates.welcome_subject", map[string]interface{}{"TeamDisplayName": teamDisplayName}) + subjectPage.Props["Subject"] = c.T("api.templates.welcome_subject", map[string]interface{}{"TeamDisplayName": siteURL}) bodyPage := utils.NewHTMLTemplate("welcome_body", c.Locale) bodyPage.Props["SiteURL"] = siteURL - bodyPage.Props["Title"] = c.T("api.templates.welcome_body.title", map[string]interface{}{"TeamDisplayName": teamDisplayName}) + bodyPage.Props["Title"] = c.T("api.templates.welcome_body.title", map[string]interface{}{"TeamDisplayName": siteURL}) bodyPage.Props["Info"] = c.T("api.templates.welcome_body.info") bodyPage.Props["Button"] = c.T("api.templates.welcome_body.button") bodyPage.Props["Info2"] = c.T("api.templates.welcome_body.info2") bodyPage.Props["Info3"] = c.T("api.templates.welcome_body.info3") - bodyPage.Props["TeamURL"] = teamURL + bodyPage.Props["TeamURL"] = siteURL if !verified { - link := fmt.Sprintf("%s/do_verify_email?uid=%s&hid=%s&teamname=%s&email=%s", siteURL, userId, model.HashPassword(userId), teamName, email) + link := fmt.Sprintf("%s/do_verify_email?uid=%s&hid=%s&email=%s", siteURL, userId, model.HashPassword(userId), email) bodyPage.Props["VerifyUrl"] = link } @@ -347,11 +367,11 @@ func sendWelcomeEmailAndForget(c *Context, userId, email, teamName, teamDisplayN }() } -func addDirectChannelsAndForget(user *model.User) { +func addDirectChannelsAndForget(teamId string, user *model.User) { go func() { var profiles map[string]*model.User - if result := <-Srv.Store.User().GetProfiles(user.TeamId); result.Err != nil { - l4g.Error(utils.T("api.user.add_direct_channels_and_forget.failed.error"), user.Id, user.TeamId, result.Err.Error()) + if result := <-Srv.Store.User().GetProfiles(teamId); result.Err != nil { + l4g.Error(utils.T("api.user.add_direct_channels_and_forget.failed.error"), user.Id, teamId, result.Err.Error()) return } else { profiles = result.Data.(map[string]*model.User) @@ -381,23 +401,23 @@ func addDirectChannelsAndForget(user *model.User) { } if result := <-Srv.Store.Preference().Save(&preferences); result.Err != nil { - l4g.Error(utils.T("api.user.add_direct_channels_and_forget.failed.error"), user.Id, user.TeamId, result.Err.Error()) + l4g.Error(utils.T("api.user.add_direct_channels_and_forget.failed.error"), user.Id, teamId, result.Err.Error()) } }() } -func SendVerifyEmailAndForget(c *Context, userId, userEmail, teamName, teamDisplayName, siteURL, teamURL string) { +func SendVerifyEmailAndForget(c *Context, userId, userEmail, siteURL string) { go func() { - link := fmt.Sprintf("%s/do_verify_email?uid=%s&hid=%s&teamname=%s&email=%s", siteURL, userId, model.HashPassword(userId), teamName, userEmail) + link := fmt.Sprintf("%s/do_verify_email?uid=%s&hid=%s&email=%s", siteURL, userId, model.HashPassword(userId), userEmail) subjectPage := utils.NewHTMLTemplate("verify_subject", c.Locale) subjectPage.Props["Subject"] = c.T("api.templates.verify_subject", - map[string]interface{}{"TeamDisplayName": teamDisplayName, "SiteName": utils.ClientCfg["SiteName"]}) + map[string]interface{}{"TeamDisplayName": utils.ClientCfg["SiteName"], "SiteName": utils.ClientCfg["SiteName"]}) bodyPage := utils.NewHTMLTemplate("verify_body", c.Locale) bodyPage.Props["SiteURL"] = siteURL - bodyPage.Props["Title"] = c.T("api.templates.verify_body.title", map[string]interface{}{"TeamDisplayName": teamDisplayName}) + bodyPage.Props["Title"] = c.T("api.templates.verify_body.title", map[string]interface{}{"TeamDisplayName": utils.ClientCfg["SiteName"]}) bodyPage.Props["Info"] = c.T("api.templates.verify_body.info") bodyPage.Props["VerifyUrl"] = link bodyPage.Props["Button"] = c.T("api.templates.verify_body.button") @@ -408,6 +428,54 @@ func SendVerifyEmailAndForget(c *Context, userId, userEmail, teamName, teamDispl }() } +func login(c *Context, w http.ResponseWriter, r *http.Request) { + props := model.MapFromJson(r.Body) + + if len(props["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 + } + + if c.Err != nil { + return + } + + 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 @@ -415,8 +483,13 @@ func LoginById(c *Context, w http.ResponseWriter, r *http.Request, userId, passw } else { user := result.Data.(*model.User) - if authenticateUserPasswordAndToken(c, user, password, mfaToken) { - Login(c, w, r, user, deviceId) + 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 doUserPasswordAuthenticationAndLogin(c, w, r, user, password, mfaToken, deviceId) { return user } } @@ -425,18 +498,9 @@ func LoginById(c *Context, w http.ResponseWriter, r *http.Request, userId, passw } func LoginByEmail(c *Context, w http.ResponseWriter, r *http.Request, email, name, password, mfaToken, deviceId string) *model.User { - var team *model.Team - - if result := <-Srv.Store.Team().GetByName(name); result.Err != nil { + if result := <-Srv.Store.User().GetByEmail(email); result.Err != nil { c.Err = result.Err - return nil - } else { - team = result.Data.(*model.Team) - } - - if result := <-Srv.Store.User().GetByEmail(team.Id, email); result.Err != nil { - c.Err = result.Err - c.Err.StatusCode = http.StatusForbidden + c.Err.StatusCode = http.StatusUnauthorized return nil } else { user := result.Data.(*model.User) @@ -447,8 +511,7 @@ func LoginByEmail(c *Context, w http.ResponseWriter, r *http.Request, email, nam return nil } - if authenticateUserPasswordAndToken(c, user, password, mfaToken) { - Login(c, w, r, user, deviceId) + if doUserPasswordAuthenticationAndLogin(c, w, r, user, password, mfaToken, deviceId) { return user } } @@ -457,18 +520,9 @@ func LoginByEmail(c *Context, w http.ResponseWriter, r *http.Request, email, nam } func LoginByUsername(c *Context, w http.ResponseWriter, r *http.Request, username, name, password, mfaToken, deviceId string) *model.User { - var team *model.Team - - if result := <-Srv.Store.Team().GetByName(name); result.Err != nil { + if result := <-Srv.Store.User().GetByUsername(username); result.Err != nil { c.Err = result.Err - return nil - } else { - team = result.Data.(*model.Team) - } - - if result := <-Srv.Store.User().GetByUsername(team.Id, username); result.Err != nil { - c.Err = result.Err - c.Err.StatusCode = http.StatusForbidden + c.Err.StatusCode = http.StatusUnauthorized return nil } else { user := result.Data.(*model.User) @@ -479,8 +533,7 @@ func LoginByUsername(c *Context, w http.ResponseWriter, r *http.Request, usernam return nil } - if authenticateUserPasswordAndToken(c, user, password, mfaToken) { - Login(c, w, r, user, deviceId) + if doUserPasswordAuthenticationAndLogin(c, w, r, user, password, mfaToken, deviceId) { return user } } @@ -488,7 +541,7 @@ func LoginByUsername(c *Context, w http.ResponseWriter, r *http.Request, usernam return nil } -func LoginByOAuth(c *Context, w http.ResponseWriter, r *http.Request, service string, userData io.Reader, team *model.Team) *model.User { +func LoginByOAuth(c *Context, w http.ResponseWriter, r *http.Request, service string, userData io.Reader) *model.User { buf := bytes.Buffer{} buf.ReadFrom(userData) @@ -509,9 +562,9 @@ func LoginByOAuth(c *Context, w http.ResponseWriter, r *http.Request, service st } var user *model.User - if result := <-Srv.Store.User().GetByAuth(team.Id, authData, service); result.Err != nil { - if result.Err.Id == store.MISSING_AUTH_ACCOUNT_ERROR && team.AllowOpenInvite { - return CreateOAuthUser(c, w, r, service, bytes.NewReader(buf.Bytes()), team) + if result := <-Srv.Store.User().GetByAuth(authData, service); result.Err != nil { + if result.Err.Id == store.MISSING_AUTH_ACCOUNT_ERROR { + return CreateOAuthUser(c, w, r, service, bytes.NewReader(buf.Bytes()), "") } c.Err = result.Err return nil @@ -522,81 +575,77 @@ func LoginByOAuth(c *Context, w http.ResponseWriter, r *http.Request, service st } } -func authenticateUserPasswordAndToken(c *Context, user *model.User, password string, token string) bool { - return checkUserLoginAttempts(c, user) && checkUserMfa(c, user, token) && checkUserPassword(c, user, password) -} - -func checkUserLoginAttempts(c *Context, user *model.User) bool { - if user.FailedAttempts >= utils.Cfg.ServiceSettings.MaximumLoginAttempts { - c.LogAuditWithUserId(user.Id, "fail") - c.Err = model.NewLocAppError("checkUserLoginAttempts", "api.user.check_user_login_attempts.too_many.app_error", nil, "user_id="+user.Id) - c.Err.StatusCode = http.StatusForbidden - return false +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 } - return true -} - -func checkUserPassword(c *Context, user *model.User, password string) bool { - if !model.ComparePassword(user.Password, password) { - c.LogAuditWithUserId(user.Id, "fail") - c.Err = model.NewLocAppError("checkUserPassword", "api.user.check_user_password.invalid.app_error", nil, "user_id="+user.Id) - c.Err.StatusCode = http.StatusForbidden - - if result := <-Srv.Store.User().UpdateFailedPasswordAttempts(user.Id, user.FailedAttempts+1); result.Err != nil { - c.LogError(result.Err) - } + props := model.MapFromJson(r.Body) - return false - } else { - if result := <-Srv.Store.User().UpdateFailedPasswordAttempts(user.Id, 0); result.Err != nil { - c.LogError(result.Err) - } + password := props["password"] + id := props["id"] + mfaToken := props["token"] - return true + if len(password) == 0 { + c.Err = model.NewLocAppError("loginLdap", "api.user.login_ldap.blank_pwd.app_error", nil, "") + c.Err.StatusCode = http.StatusBadRequest + return } -} -func checkUserMfa(c *Context, user *model.User, token string) bool { - if !user.MfaActive || !utils.IsLicensed || !*utils.License.Features.MFA || !*utils.Cfg.ServiceSettings.EnableMultifactorAuthentication { - return true + if len(id) == 0 { + c.Err = model.NewLocAppError("loginLdap", "api.user.login_ldap.need_id.app_error", nil, "") + c.Err.StatusCode = http.StatusBadRequest + return } - mfaInterface := einterfaces.GetMfaInterface() - if mfaInterface == nil { - c.Err = model.NewLocAppError("checkUserMfa", "api.user.check_user_mfa.not_available.app_error", nil, "") + 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 false + return } - if ok, err := mfaInterface.ValidateToken(user.MfaSecret, token); err != nil { + 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 - return false - } else if !ok { - c.Err = model.NewLocAppError("checkUserMfa", "api.user.check_user_mfa.bad_code.app_error", nil, "") - return false - } else { - return true + c.Err.StatusCode = http.StatusUnauthorized + return } -} - -// User MUST be validated before calling Login -func Login(c *Context, w http.ResponseWriter, r *http.Request, user *model.User, deviceId string) { c.LogAuditWithUserId(user.Id, "attempt") - if !user.EmailVerified && utils.Cfg.EmailSettings.RequireEmailVerification { - c.Err = model.NewLocAppError("Login", "api.user.login.not_verified.app_error", nil, "user_id="+user.Id) - c.Err.StatusCode = http.StatusForbidden + if err = checkUserAdditionalAuthenticationCriteria(user, mfaToken); err != nil { + c.LogAuditWithUserId(user.Id, "fail") + c.Err = err + c.Err.StatusCode = http.StatusUnauthorized return } - if user.DeleteAt > 0 { - c.Err = model.NewLocAppError("Login", "api.user.login.inactive.app_error", nil, "user_id="+user.Id) - c.Err.StatusCode = http.StatusForbidden - 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())) +} - session := &model.Session{UserId: user.Id, TeamId: user.TeamId, Roles: user.Roles, DeviceId: deviceId, IsOAuth: false} +// User MUST be authenticated completely before calling Login +func Login(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} maxAge := *utils.Cfg.ServiceSettings.SessionLengthWebInDays * 60 * 60 * 24 @@ -607,7 +656,7 @@ func Login(c *Context, w http.ResponseWriter, r *http.Request, user *model.User, // A special case where we logout of all other sessions with the same Id if result := <-Srv.Store.Session().GetSessions(user.Id); result.Err != nil { c.Err = result.Err - c.Err.StatusCode = http.StatusForbidden + c.Err.StatusCode = http.StatusInternalServerError return } else { sessions := result.Data.([]*model.Session) @@ -653,7 +702,7 @@ func Login(c *Context, w http.ResponseWriter, r *http.Request, user *model.User, if result := <-Srv.Store.Session().Save(session); result.Err != nil { c.Err = result.Err - c.Err.StatusCode = http.StatusForbidden + c.Err.StatusCode = http.StatusInternalServerError return } else { session = result.Data.(*model.Session) @@ -675,110 +724,6 @@ func Login(c *Context, w http.ResponseWriter, r *http.Request, user *model.User, http.SetCookie(w, sessionCookie) c.Session = *session - c.LogAuditWithUserId(user.Id, "success") -} - -func login(c *Context, w http.ResponseWriter, r *http.Request) { - props := model.MapFromJson(r.Body) - - if len(props["password"]) == 0 { - c.Err = model.NewLocAppError("login", "api.user.login.blank_pwd.app_error", nil, "") - c.Err.StatusCode = http.StatusForbidden - 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 && len(props["name"]) != 0 { - user = LoginByEmail(c, w, r, props["email"], props["name"], props["password"], props["token"], props["device_id"]) - } else if len(props["username"]) != 0 && len(props["name"]) != 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.StatusForbidden - return - } - - if c.Err != nil { - return - } - - if user != nil { - user.Sanitize(map[string]bool{}) - } else { - user = &model.User{} - } - w.Write([]byte(user.ToJson())) -} - -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"] - teamName := props["teamName"] - 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.StatusForbidden - return - } - - if len(id) == 0 { - c.Err = model.NewLocAppError("loginLdap", "api.user.login_ldap.need_id.app_error", nil, "") - c.Err.StatusCode = http.StatusForbidden - return - } - - teamc := Srv.Store.Team().GetByName(teamName) - - 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 - } - - var team *model.Team - if result := <-teamc; result.Err != nil { - c.Err = result.Err - return - } else { - team = result.Data.(*model.Team) - } - - user, err := ldapInterface.DoLogin(team, id, password) - if err != nil { - c.Err = err - return - } - - if !checkUserLoginAttempts(c, user) { - return - } - - if !checkUserMfa(c, user, mfaToken) { - return - } - - // User is authenticated at this point - - Login(c, w, r, user, props["device_id"]) - - if user != nil { - user.Sanitize(map[string]bool{}) - } else { - user = &model.User{} - } - w.Write([]byte(user.ToJson())) } func revokeSession(c *Context, w http.ResponseWriter, r *http.Request) { @@ -805,7 +750,7 @@ func attachDeviceId(c *Context, w http.ResponseWriter, r *http.Request) { // A special case where we logout of all other sessions with the same Id if result := <-Srv.Store.Session().GetSessions(c.Session.UserId); result.Err != nil { c.Err = result.Err - c.Err.StatusCode = http.StatusForbidden + c.Err.StatusCode = http.StatusInternalServerError return } else { sessions := result.Data.([]*model.Session) @@ -875,7 +820,7 @@ func RevokeAllSession(c *Context, userId string) { func getSessions(c *Context, w http.ResponseWriter, r *http.Request) { params := mux.Vars(r) - id := params["id"] + id := params["user_id"] if !c.HasPermissionsToUser(id, "getSessions") { return @@ -907,9 +852,11 @@ func logout(c *Context, w http.ResponseWriter, r *http.Request) { func Logout(c *Context, w http.ResponseWriter, r *http.Request) { c.LogAudit("") c.RemoveSessionCookie(w, r) - if result := <-Srv.Store.Session().Remove(c.Session.Id); result.Err != nil { - c.Err = result.Err - return + if c.Session.Id != "" { + if result := <-Srv.Store.Session().Remove(c.Session.Id); result.Err != nil { + c.Err = result.Err + return + } } } @@ -934,29 +881,99 @@ func getMe(c *Context, w http.ResponseWriter, r *http.Request) { } } -func getMeLoggedIn(c *Context, w http.ResponseWriter, r *http.Request) { - data := make(map[string]string) - data["logged_in"] = "false" - data["team_name"] = "" +func getInitialLoad(c *Context, w http.ResponseWriter, r *http.Request) { + + il := model.InitialLoad{} + + var cchan store.StoreChannel + + if sessionCache.Len() == 0 { + // Below is a speical case when intializating a new server + // Lets check to make sure the server is really empty + + cchan = Srv.Store.User().GetTotalUsersCount() + } if len(c.Session.UserId) != 0 { - teamChan := Srv.Store.Team().Get(c.Session.TeamId) - var team *model.Team - if tr := <-teamChan; tr.Err != nil { - c.Err = tr.Err + uchan := Srv.Store.User().Get(c.Session.UserId) + pchan := Srv.Store.Preference().GetAll(c.Session.UserId) + tchan := Srv.Store.Team().GetTeamsByUserId(c.Session.UserId) + dpchan := Srv.Store.User().GetDirectProfiles(c.Session.UserId) + + il.TeamMembers = c.Session.TeamMembers + + if ru := <-uchan; ru.Err != nil { + c.Err = ru.Err + return + } else { + il.User = ru.Data.(*model.User) + il.User.Sanitize(map[string]bool{}) + } + + if rp := <-pchan; rp.Err != nil { + c.Err = rp.Err + return + } else { + il.Preferences = rp.Data.(model.Preferences) + } + + if rt := <-tchan; rt.Err != nil { + c.Err = rt.Err + return + } else { + il.Teams = rt.Data.([]*model.Team) + + for _, team := range il.Teams { + team.Sanitize() + } + } + + if dp := <-dpchan; dp.Err != nil { + c.Err = dp.Err return } else { - team = tr.Data.(*model.Team) + profiles := dp.Data.(map[string]*model.User) + + for k, p := range profiles { + options := utils.Cfg.GetSanitizeOptions() + options["passwordupdate"] = false + + if c.IsSystemAdmin() { + options["fullname"] = true + options["email"] = true + } else { + p.ClearNonProfileFields() + } + + p.Sanitize(options) + profiles[k] = p + } + + il.DirectProfiles = profiles } - data["logged_in"] = "true" - data["team_name"] = team.Name } - w.Write([]byte(model.MapToJson(data))) + + if cchan != nil { + if cr := <-cchan; cr.Err != nil { + c.Err = cr.Err + return + } else { + count := cr.Data.(int64) + if count <= 0 { + il.NoAccounts = true + } + } + } + + il.ClientCfg = utils.ClientCfg + il.LicenseCfg = utils.ClientLicense + + w.Write([]byte(il.ToJson())) } func getUser(c *Context, w http.ResponseWriter, r *http.Request) { params := mux.Vars(r) - id := params["id"] + id := params["user_id"] if !c.HasPermissionsToUser(id, "getUser") { return @@ -977,17 +994,12 @@ func getUser(c *Context, w http.ResponseWriter, r *http.Request) { func getProfiles(c *Context, w http.ResponseWriter, r *http.Request) { params := mux.Vars(r) - id, ok := params["id"] - if ok { - // You must be system admin to access another team - if id != c.Session.TeamId { - if !c.HasSystemAdminPermissions("getProfiles") { - return - } - } + id := params["id"] - } else { - id = c.Session.TeamId + if c.Session.GetTeamByTeamId(id) == nil { + if !c.HasSystemAdminPermissions("getProfiles") { + return + } } etag := (<-Srv.Store.User().GetEtagForProfiles(id)).Data.(string) @@ -1008,10 +1020,44 @@ func getProfiles(c *Context, w http.ResponseWriter, r *http.Request) { if c.IsSystemAdmin() { options["fullname"] = true options["email"] = true + } else { + p.ClearNonProfileFields() + } + + p.Sanitize(options) + profiles[k] = p + } + + w.Header().Set(model.HEADER_ETAG_SERVER, etag) + w.Write([]byte(model.UserMapToJson(profiles))) + return + } +} + +func getDirectProfiles(c *Context, w http.ResponseWriter, r *http.Request) { + etag := (<-Srv.Store.User().GetEtagForDirectProfiles(c.Session.UserId)).Data.(string) + if HandleEtag(etag, w, r) { + return + } + + if result := <-Srv.Store.User().GetDirectProfiles(c.Session.UserId); result.Err != nil { + c.Err = result.Err + return + } else { + profiles := result.Data.(map[string]*model.User) + + for k, p := range profiles { + options := utils.Cfg.GetSanitizeOptions() + options["passwordupdate"] = false + + if c.IsSystemAdmin() { + options["fullname"] = true + options["email"] = true + } else { + p.ClearNonProfileFields() } p.Sanitize(options) - p.ClearNonProfileFields() profiles[k] = p } @@ -1023,7 +1069,7 @@ func getProfiles(c *Context, w http.ResponseWriter, r *http.Request) { func getAudits(c *Context, w http.ResponseWriter, r *http.Request) { params := mux.Vars(r) - id := params["id"] + id := params["user_id"] if !c.HasPermissionsToUser(id, "getAudits") { return @@ -1133,7 +1179,7 @@ func createProfileImage(username string, userId string) ([]byte, *model.AppError func getProfileImage(c *Context, w http.ResponseWriter, r *http.Request) { params := mux.Vars(r) - id := params["id"] + id := params["user_id"] if result := <-Srv.Store.User().Get(id); result.Err != nil { c.Err = result.Err @@ -1148,7 +1194,7 @@ func getProfileImage(c *Context, w http.ResponseWriter, r *http.Request) { return } } else { - path := "teams/" + c.Session.TeamId + "/users/" + id + "/profile.png" + path := "/users/" + id + "/profile.png" if data, err := ReadFile(path); err != nil { @@ -1249,7 +1295,7 @@ func uploadProfileImage(c *Context, w http.ResponseWriter, r *http.Request) { return } - path := "teams/" + c.Session.TeamId + "/users/" + c.Session.UserId + "/profile.png" + path := "users/" + c.Session.UserId + "/profile.png" if err := WriteFile(buf.Bytes(), path); err != nil { c.Err = model.NewLocAppError("uploadProfileImage", "api.user.upload_profile_user.upload_profile.app_error", nil, "") @@ -1285,15 +1331,10 @@ func updateUser(c *Context, w http.ResponseWriter, r *http.Request) { rusers := result.Data.([2]*model.User) if rusers[0].Email != rusers[1].Email { - if tresult := <-Srv.Store.Team().Get(rusers[1].TeamId); tresult.Err != nil { - l4g.Error(tresult.Err.Message) - } else { - team := tresult.Data.(*model.Team) - sendEmailChangeEmailAndForget(c, rusers[1].Email, rusers[0].Email, team.DisplayName, c.GetTeamURLFromTeam(team), c.GetSiteURL()) + sendEmailChangeEmailAndForget(c, rusers[1].Email, rusers[0].Email, c.GetSiteURL()) - if utils.Cfg.EmailSettings.RequireEmailVerification { - SendEmailChangeVerifyEmailAndForget(c, rusers[0].Id, rusers[0].Email, team.Name, team.DisplayName, c.GetSiteURL(), c.GetTeamURLFromTeam(team)) - } + if utils.Cfg.EmailSettings.RequireEmailVerification { + SendEmailChangeVerifyEmailAndForget(c, rusers[0].Id, rusers[0].Email, c.GetSiteURL()) } } @@ -1346,12 +1387,10 @@ func updatePassword(c *Context, w http.ResponseWriter, r *http.Request) { user := result.Data.(*model.User) - tchan := Srv.Store.Team().Get(user.TeamId) - if user.AuthData != "" { c.LogAudit("failed - tried to update user password who was logged in through oauth") c.Err = model.NewLocAppError("updatePassword", "api.user.update_password.oauth.app_error", nil, "auth_service="+user.AuthService) - c.Err.StatusCode = http.StatusForbidden + c.Err.StatusCode = http.StatusBadRequest return } @@ -1363,17 +1402,11 @@ func updatePassword(c *Context, w http.ResponseWriter, r *http.Request) { if uresult := <-Srv.Store.User().UpdatePassword(c.Session.UserId, model.HashPassword(newPassword)); uresult.Err != nil { c.Err = model.NewLocAppError("updatePassword", "api.user.update_password.failed.app_error", nil, uresult.Err.Error()) - c.Err.StatusCode = http.StatusForbidden return } else { c.LogAudit("completed") - if tresult := <-tchan; tresult.Err != nil { - l4g.Error(tresult.Err.Message) - } else { - team := tresult.Data.(*model.Team) - sendPasswordChangeEmailAndForget(c, user.Email, team.DisplayName, c.GetTeamURLFromTeam(team), c.GetSiteURL(), c.T("api.user.update_password.menu")) - } + sendPasswordChangeEmailAndForget(c, user.Email, c.GetSiteURL(), c.T("api.user.update_password.menu")) data := make(map[string]string) data["user_id"] = uresult.Data.(string) @@ -1391,11 +1424,18 @@ func updateRoles(c *Context, w http.ResponseWriter, r *http.Request) { } new_roles := props["new_roles"] - if !model.IsValidRoles(new_roles) { + if !model.IsValidUserRoles(new_roles) { c.SetInvalidParam("updateRoles", "new_roles") return } + // If you are not the system admin then you can only demote yourself + if !c.IsSystemAdmin() && user_id != c.Session.UserId { + c.Err = model.NewLocAppError("updateRoles", "api.user.update_roles.system_admin_set.app_error", nil, "") + c.Err.StatusCode = http.StatusForbidden + } + + // Only another system admin can add the system admin role if model.IsInRole(new_roles, model.ROLE_SYSTEM_ADMIN) && !c.IsSystemAdmin() { c.Err = model.NewLocAppError("updateRoles", "api.user.update_roles.system_admin_set.app_error", nil, "") c.Err.StatusCode = http.StatusForbidden @@ -1410,22 +1450,6 @@ func updateRoles(c *Context, w http.ResponseWriter, r *http.Request) { user = result.Data.(*model.User) } - if !c.HasPermissionsToTeam(user.TeamId, "updateRoles") { - return - } - - if !c.IsTeamAdmin() { - c.Err = model.NewLocAppError("updateRoles", "api.user.update_roles.permissions.app_error", nil, "userId="+user_id) - c.Err.StatusCode = http.StatusForbidden - return - } - - if user.IsInRole(model.ROLE_SYSTEM_ADMIN) && !c.IsSystemAdmin() { - c.Err = model.NewLocAppError("updateRoles", "api.user.update_roles.system_admin_mod.app_error", nil, "") - c.Err.StatusCode = http.StatusForbidden - return - } - ruser := UpdateRoles(c, user, new_roles) if c.Err != nil { return @@ -1456,29 +1480,6 @@ func updateRoles(c *Context, w http.ResponseWriter, r *http.Request) { } func UpdateRoles(c *Context, user *model.User, roles string) *model.User { - // make sure there is at least 1 other active admin - - if !model.IsInRole(roles, model.ROLE_SYSTEM_ADMIN) { - if model.IsInRole(user.Roles, model.ROLE_TEAM_ADMIN) && !model.IsInRole(roles, model.ROLE_TEAM_ADMIN) { - if result := <-Srv.Store.User().GetProfiles(user.TeamId); result.Err != nil { - c.Err = result.Err - return nil - } else { - activeAdmins := -1 - profileUsers := result.Data.(map[string]*model.User) - for _, profileUser := range profileUsers { - if profileUser.DeleteAt == 0 && model.IsInRole(profileUser.Roles, model.ROLE_TEAM_ADMIN) { - activeAdmins = activeAdmins + 1 - } - } - - if activeAdmins <= 0 { - c.Err = model.NewLocAppError("updateRoles", "api.user.update_roles.one_admin.app_error", nil, "") - return nil - } - } - } - } user.Roles = roles @@ -1513,37 +1514,15 @@ func updateActive(c *Context, w http.ResponseWriter, r *http.Request) { user = result.Data.(*model.User) } - if !c.HasPermissionsToTeam(user.TeamId, "updateActive") { - return - } + // true when you're trying to de-activate yourself + isSelfDeactive := !active && user_id == c.Session.UserId - if !c.IsTeamAdmin() { + if !isSelfDeactive && !c.IsSystemAdmin() { c.Err = model.NewLocAppError("updateActive", "api.user.update_active.permissions.app_error", nil, "userId="+user_id) c.Err.StatusCode = http.StatusForbidden return } - // make sure there is at least 1 other active admin - if !active && model.IsInRole(user.Roles, model.ROLE_TEAM_ADMIN) { - if result := <-Srv.Store.User().GetProfiles(user.TeamId); result.Err != nil { - c.Err = result.Err - return - } else { - activeAdmins := -1 - profileUsers := result.Data.(map[string]*model.User) - for _, profileUser := range profileUsers { - if profileUser.DeleteAt == 0 && model.IsInRole(profileUser.Roles, model.ROLE_TEAM_ADMIN) { - activeAdmins = activeAdmins + 1 - } - } - - if activeAdmins <= 0 { - c.Err = model.NewLocAppError("updateRoles", "api.user.update_roles.one_admin.app_error", nil, "userId="+user_id) - return - } - } - } - ruser := UpdateActive(c, user, active) if c.Err == nil { @@ -1631,12 +1610,33 @@ func PermanentDeleteUser(c *Context, user *model.User) *model.AppError { return result.Err } + if result := <-Srv.Store.Team().RemoveAllMembersByUser(user.Id); result.Err != nil { + return result.Err + } + + if result := <-Srv.Store.PasswordRecovery().Delete(user.Id); result.Err != nil { + return result.Err + } + l4g.Warn(utils.T("api.user.permanent_delete_user.deleted.warn"), user.Email, user.Id) c.LogAuditWithUserId("", fmt.Sprintf("success userId=%v", user.Id)) return nil } +func PermanentDeleteAllUsers(c *Context) *model.AppError { + if result := <-Srv.Store.User().GetAll(); result.Err != nil { + return result.Err + } else { + users := result.Data.([]*model.User) + for _, user := range users { + PermanentDeleteUser(c, user) + } + } + + return nil +} + func sendPasswordReset(c *Context, w http.ResponseWriter, r *http.Request) { props := model.MapFromJson(r.Body) @@ -1646,41 +1646,28 @@ func sendPasswordReset(c *Context, w http.ResponseWriter, r *http.Request) { return } - name := props["name"] - if len(name) == 0 { - c.SetInvalidParam("sendPasswordReset", "name") - return - } - - var team *model.Team - if result := <-Srv.Store.Team().GetByName(name); result.Err != nil { - c.Err = result.Err - return - } else { - team = result.Data.(*model.Team) - } - var user *model.User - if result := <-Srv.Store.User().GetByEmail(team.Id, email); result.Err != nil { - c.Err = model.NewLocAppError("sendPasswordReset", "api.user.send_password_reset.find.app_error", nil, "email="+email+" team_id="+team.Id) + if result := <-Srv.Store.User().GetByEmail(email); result.Err != nil { + c.Err = model.NewLocAppError("sendPasswordReset", "api.user.send_password_reset.find.app_error", nil, "email="+email) return } else { user = result.Data.(*model.User) } if len(user.AuthData) != 0 { - c.Err = model.NewLocAppError("sendPasswordReset", "api.user.send_password_reset.sso.app_error", nil, "userId="+user.Id+", teamId="+team.Id) + c.Err = model.NewLocAppError("sendPasswordReset", "api.user.send_password_reset.sso.app_error", nil, "userId="+user.Id) return } - newProps := make(map[string]string) - newProps["user_id"] = user.Id - newProps["time"] = fmt.Sprintf("%v", model.GetMillis()) + recovery := &model.PasswordRecovery{} + recovery.UserId = user.Id - data := model.MapToJson(newProps) - hash := model.HashPassword(fmt.Sprintf("%v:%v", data, utils.Cfg.EmailSettings.PasswordResetSalt)) + if result := <-Srv.Store.PasswordRecovery().SaveOrUpdate(recovery); result.Err != nil { + c.Err = result.Err + return + } - link := fmt.Sprintf("%s/reset_password_complete?d=%s&h=%s", c.GetTeamURLFromTeam(team), url.QueryEscape(data), url.QueryEscape(hash)) + link := fmt.Sprintf("%s/reset_password_complete?code=%s", c.GetSiteURL(), url.QueryEscape(recovery.Code)) subjectPage := utils.NewHTMLTemplate("reset_subject", c.Locale) subjectPage.Props["Subject"] = c.T("api.templates.reset_subject") @@ -1706,110 +1693,89 @@ func resetPassword(c *Context, w http.ResponseWriter, r *http.Request) { props := model.MapFromJson(r.Body) newPassword := props["new_password"] - if len(newPassword) < 5 { + if len(newPassword) < model.MIN_PASSWORD_LENGTH { c.SetInvalidParam("resetPassword", "new_password") return } - name := props["name"] - if len(name) == 0 { - c.SetInvalidParam("resetPassword", "name") + code := props["code"] + if len(code) != model.PASSWORD_RECOVERY_CODE_SIZE { + c.SetInvalidParam("resetPassword", "code") return } - userId := props["user_id"] - hash := props["hash"] - timeStr := "" - - if !c.IsSystemAdmin() { - if len(hash) == 0 { - c.SetInvalidParam("resetPassword", "hash") - return - } + c.LogAudit("attempt") - data := model.MapFromJson(strings.NewReader(props["data"])) + userId := "" - userId = data["user_id"] + if result := <-Srv.Store.PasswordRecovery().GetByCode(code); result.Err != nil { + c.LogAuditWithUserId(userId, "fail - bad code") + c.Err = model.NewLocAppError("resetPassword", "api.user.reset_password.invalid_link.app_error", nil, result.Err.Error()) + return + } else { + recovery := result.Data.(*model.PasswordRecovery) - timeStr = data["time"] - if len(timeStr) == 0 { - c.SetInvalidParam("resetPassword", "data:time") + if model.GetMillis()-recovery.CreateAt < model.PASSWORD_RECOVER_EXPIRY_TIME { + userId = recovery.UserId + } else { + c.LogAuditWithUserId(userId, "fail - link expired") + c.Err = model.NewLocAppError("resetPassword", "api.user.reset_password.link_expired.app_error", nil, "") return } + + go func() { + if result := <-Srv.Store.PasswordRecovery().Delete(userId); result.Err != nil { + l4g.Error("%v", result.Err) + } + }() } - if len(userId) != 26 { - c.SetInvalidParam("resetPassword", "user_id") + if err := ResetPassword(c, userId, newPassword); err != nil { + c.Err = err return } - c.LogAuditWithUserId(userId, "attempt") + c.LogAuditWithUserId(userId, "success") - var team *model.Team - if result := <-Srv.Store.Team().GetByName(name); result.Err != nil { - c.Err = result.Err - return - } else { - team = result.Data.(*model.Team) - } + rdata := map[string]string{} + rdata["status"] = "ok" + w.Write([]byte(model.MapToJson(rdata))) +} +func ResetPassword(c *Context, userId, newPassword string) *model.AppError { var user *model.User if result := <-Srv.Store.User().Get(userId); result.Err != nil { - c.Err = result.Err - return + return result.Err } else { user = result.Data.(*model.User) } if len(user.AuthData) != 0 { - c.Err = model.NewLocAppError("resetPassword", "api.user.reset_password.sso.app_error", nil, "userId="+user.Id+", teamId="+team.Id) - return - } + return model.NewLocAppError("ResetPassword", "api.user.reset_password.sso.app_error", nil, "userId="+user.Id) - if user.TeamId != team.Id { - c.Err = model.NewLocAppError("resetPassword", "api.user.reset_password.wrong_team.app_error", nil, "userId="+user.Id+", teamId="+team.Id) - c.Err.StatusCode = http.StatusForbidden - return - } - - if !c.IsSystemAdmin() { - if !model.ComparePassword(hash, fmt.Sprintf("%v:%v", props["data"], utils.Cfg.EmailSettings.PasswordResetSalt)) { - c.Err = model.NewLocAppError("resetPassword", "api.user.reset_password.invalid_link.app_error", nil, "") - return - } - - t, err := strconv.ParseInt(timeStr, 10, 64) - if err != nil || model.GetMillis()-t > 1000*60*60 { // one hour - c.Err = model.NewLocAppError("resetPassword", "api.user.reset_password.link_expired.app_error", nil, "") - return - } } if result := <-Srv.Store.User().UpdatePassword(userId, model.HashPassword(newPassword)); result.Err != nil { - c.Err = result.Err - return - } else { - c.LogAuditWithUserId(userId, "success") + return result.Err } - sendPasswordChangeEmailAndForget(c, user.Email, team.DisplayName, c.GetTeamURLFromTeam(team), c.GetSiteURL(), c.T("api.user.reset_password.method")) + sendPasswordChangeEmailAndForget(c, user.Email, c.GetSiteURL(), c.T("api.user.reset_password.method")) - props["new_password"] = "" - w.Write([]byte(model.MapToJson(props))) + return nil } -func sendPasswordChangeEmailAndForget(c *Context, email, teamDisplayName, teamURL, siteURL, method string) { +func sendPasswordChangeEmailAndForget(c *Context, email, siteURL, method string) { go func() { subjectPage := utils.NewHTMLTemplate("password_change_subject", c.Locale) subjectPage.Props["Subject"] = c.T("api.templates.password_change_subject", - map[string]interface{}{"TeamDisplayName": teamDisplayName, "SiteName": utils.ClientCfg["SiteName"]}) + map[string]interface{}{"TeamDisplayName": utils.Cfg.TeamSettings.SiteName, "SiteName": utils.Cfg.TeamSettings.SiteName}) bodyPage := utils.NewHTMLTemplate("password_change_body", c.Locale) bodyPage.Props["SiteURL"] = siteURL bodyPage.Props["Title"] = c.T("api.templates.password_change_body.title") bodyPage.Html["Info"] = template.HTML(c.T("api.templates.password_change_body.info", - map[string]interface{}{"TeamDisplayName": teamDisplayName, "TeamURL": teamURL, "Method": method})) + map[string]interface{}{"TeamDisplayName": utils.Cfg.TeamSettings.SiteName, "TeamURL": siteURL, "Method": method})) if err := utils.SendMail(email, subjectPage.Render(), bodyPage.Render()); err != nil { l4g.Error(utils.T("api.user.send_password_change_email_and_forget.error"), err) @@ -1818,19 +1784,19 @@ func sendPasswordChangeEmailAndForget(c *Context, email, teamDisplayName, teamUR }() } -func sendEmailChangeEmailAndForget(c *Context, oldEmail, newEmail, teamDisplayName, teamURL, siteURL string) { +func sendEmailChangeEmailAndForget(c *Context, oldEmail, newEmail, siteURL string) { go func() { subjectPage := utils.NewHTMLTemplate("email_change_subject", c.Locale) subjectPage.Props["Subject"] = c.T("api.templates.email_change_subject", - map[string]interface{}{"TeamDisplayName": teamDisplayName}) + map[string]interface{}{"TeamDisplayName": utils.Cfg.TeamSettings.SiteName}) subjectPage.Props["SiteName"] = utils.Cfg.TeamSettings.SiteName bodyPage := utils.NewHTMLTemplate("email_change_body", c.Locale) bodyPage.Props["SiteURL"] = siteURL bodyPage.Props["Title"] = c.T("api.templates.email_change_body.title") bodyPage.Html["Info"] = template.HTML(c.T("api.templates.email_change_body.info", - map[string]interface{}{"TeamDisplayName": teamDisplayName, "NewEmail": newEmail})) + map[string]interface{}{"TeamDisplayName": utils.Cfg.TeamSettings.SiteName, "NewEmail": newEmail})) if err := utils.SendMail(oldEmail, subjectPage.Render(), bodyPage.Render()); err != nil { l4g.Error(utils.T("api.user.send_email_change_email_and_forget.error"), err) @@ -1839,21 +1805,21 @@ func sendEmailChangeEmailAndForget(c *Context, oldEmail, newEmail, teamDisplayNa }() } -func SendEmailChangeVerifyEmailAndForget(c *Context, userId, newUserEmail, teamName, teamDisplayName, siteURL, teamURL string) { +func SendEmailChangeVerifyEmailAndForget(c *Context, userId, newUserEmail, siteURL string) { go func() { - link := fmt.Sprintf("%s/do_verify_email?uid=%s&hid=%s&teamname=%s&email=%s", siteURL, userId, model.HashPassword(userId), teamName, newUserEmail) + link := fmt.Sprintf("%s/do_verify_email?uid=%s&hid=%s&email=%s", siteURL, userId, model.HashPassword(userId), newUserEmail) subjectPage := utils.NewHTMLTemplate("email_change_verify_subject", c.Locale) subjectPage.Props["Subject"] = c.T("api.templates.email_change_verify_subject", - map[string]interface{}{"TeamDisplayName": teamDisplayName}) + map[string]interface{}{"TeamDisplayName": utils.Cfg.TeamSettings.SiteName}) subjectPage.Props["SiteName"] = utils.Cfg.TeamSettings.SiteName bodyPage := utils.NewHTMLTemplate("email_change_verify_body", c.Locale) bodyPage.Props["SiteURL"] = siteURL bodyPage.Props["Title"] = c.T("api.templates.email_change_verify_body.title") bodyPage.Props["Info"] = c.T("api.templates.email_change_verify_body.info", - map[string]interface{}{"TeamDisplayName": teamDisplayName}) + map[string]interface{}{"TeamDisplayName": utils.Cfg.TeamSettings.SiteName}) bodyPage.Props["VerifyUrl"] = link bodyPage.Props["VerifyButton"] = c.T("api.templates.email_change_verify_body.button") @@ -1929,7 +1895,7 @@ func getStatuses(c *Context, w http.ResponseWriter, r *http.Request) { return } - if result := <-Srv.Store.User().GetProfiles(c.Session.TeamId); result.Err != nil { + if result := <-Srv.Store.User().GetProfileByIds(userIds); result.Err != nil { c.Err = result.Err return } else { @@ -1937,17 +1903,6 @@ func getStatuses(c *Context, w http.ResponseWriter, r *http.Request) { statuses := map[string]string{} for _, profile := range profiles { - found := false - for _, uid := range userIds { - if uid == profile.Id { - found = true - } - } - - if !found { - continue - } - if profile.IsOffline() { statuses[profile.Id] = model.USER_OFFLINE } else if profile.IsAway() { @@ -1957,131 +1912,18 @@ func getStatuses(c *Context, w http.ResponseWriter, r *http.Request) { } } - //w.Header().Set("Cache-Control", "max-age=9, public") // 2 mins w.Write([]byte(model.MapToJson(statuses))) return } } -func GetAuthorizationCode(c *Context, service, teamName string, props map[string]string, loginHint string) (string, *model.AppError) { - - sso := utils.Cfg.GetSSOService(service) - if sso != nil && !sso.Enable { - return "", model.NewLocAppError("GetAuthorizationCode", "api.user.get_authorization_code.unsupported.app_error", nil, "service="+service) - } - - clientId := sso.Id - endpoint := sso.AuthEndpoint - scope := sso.Scope - - props["hash"] = model.HashPassword(clientId) - props["team"] = teamName - state := b64.StdEncoding.EncodeToString([]byte(model.MapToJson(props))) - - redirectUri := c.GetSiteURL() + "/signup/" + service + "/complete" - - authUrl := endpoint + "?response_type=code&client_id=" + clientId + "&redirect_uri=" + url.QueryEscape(redirectUri) + "&state=" + url.QueryEscape(state) - - if len(scope) > 0 { - authUrl += "&scope=" + utils.UrlEncode(scope) - } - - if len(loginHint) > 0 { - authUrl += "&login_hint=" + utils.UrlEncode(loginHint) - } - - return authUrl, nil -} - -func AuthorizeOAuthUser(service, code, state, redirectUri string) (io.ReadCloser, *model.Team, map[string]string, *model.AppError) { - sso := utils.Cfg.GetSSOService(service) - if sso == nil || !sso.Enable { - return nil, nil, nil, model.NewLocAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.unsupported.app_error", nil, "service="+service) - } - - stateStr := "" - if b, err := b64.StdEncoding.DecodeString(state); err != nil { - return nil, nil, nil, model.NewLocAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.invalid_state.app_error", nil, err.Error()) - } else { - stateStr = string(b) - } - - stateProps := model.MapFromJson(strings.NewReader(stateStr)) - - if !model.ComparePassword(stateProps["hash"], sso.Id) { - return nil, nil, nil, model.NewLocAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.invalid_state.app_error", nil, "") - } - - ok := true - teamName := "" - if teamName, ok = stateProps["team"]; !ok { - return nil, nil, nil, model.NewLocAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.invalid_state_team.app_error", nil, "") - } - - tchan := Srv.Store.Team().GetByName(teamName) - - p := url.Values{} - p.Set("client_id", sso.Id) - p.Set("client_secret", sso.Secret) - p.Set("code", code) - p.Set("grant_type", model.ACCESS_TOKEN_GRANT_TYPE) - p.Set("redirect_uri", redirectUri) - - tr := &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: *utils.Cfg.ServiceSettings.EnableInsecureOutgoingConnections}, - } - client := &http.Client{Transport: tr} - req, _ := http.NewRequest("POST", sso.TokenEndpoint, strings.NewReader(p.Encode())) - - req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - req.Header.Set("Accept", "application/json") - - var ar *model.AccessResponse - if resp, err := client.Do(req); err != nil { - return nil, nil, nil, model.NewLocAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.token_failed.app_error", nil, err.Error()) - } else { - ar = model.AccessResponseFromJson(resp.Body) - if ar == nil { - return nil, nil, nil, model.NewLocAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.bad_response.app_error", nil, "") - } - } - - if strings.ToLower(ar.TokenType) != model.ACCESS_TOKEN_TYPE { - return nil, nil, nil, model.NewLocAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.bad_token.app_error", nil, "token_type="+ar.TokenType) - } - - if len(ar.AccessToken) == 0 { - return nil, nil, nil, model.NewLocAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.missing.app_error", nil, "") - } - - p = url.Values{} - p.Set("access_token", ar.AccessToken) - req, _ = http.NewRequest("GET", sso.UserApiEndpoint, strings.NewReader("")) - - req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - req.Header.Set("Accept", "application/json") - req.Header.Set("Authorization", "Bearer "+ar.AccessToken) - - if resp, err := client.Do(req); err != nil { - return nil, nil, nil, model.NewLocAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.service.app_error", - map[string]interface{}{"Service": service}, err.Error()) - } else { - if result := <-tchan; result.Err != nil { - return nil, nil, nil, result.Err - } else { - return resp.Body, result.Data.(*model.Team), stateProps, nil - } - } - -} - -func IsUsernameTaken(name string, teamId string) bool { +func IsUsernameTaken(name string) bool { if !model.IsValidUsername(name) { return false } - if result := <-Srv.Store.User().GetByUsername(teamId, name); result.Err != nil { + if result := <-Srv.Store.User().GetByUsername(name); result.Err != nil { return false } else { return true @@ -2099,12 +1941,6 @@ func emailToOAuth(c *Context, w http.ResponseWriter, r *http.Request) { return } - teamName := props["team_name"] - if len(teamName) == 0 { - c.SetInvalidParam("emailToOAuth", "team_name") - return - } - service := props["service"] if len(service) == 0 { c.SetInvalidParam("emailToOAuth", "service") @@ -2119,17 +1955,8 @@ func emailToOAuth(c *Context, w http.ResponseWriter, r *http.Request) { c.LogAudit("attempt") - var team *model.Team - if result := <-Srv.Store.Team().GetByName(teamName); result.Err != nil { - c.LogAudit("fail - couldn't get team") - c.Err = result.Err - return - } else { - team = result.Data.(*model.Team) - } - var user *model.User - if result := <-Srv.Store.User().GetByEmail(team.Id, email); result.Err != nil { + if result := <-Srv.Store.User().GetByEmail(email); result.Err != nil { c.LogAudit("fail - couldn't get user") c.Err = result.Err return @@ -2137,8 +1964,9 @@ func emailToOAuth(c *Context, w http.ResponseWriter, r *http.Request) { user = result.Data.(*model.User) } - if !checkUserLoginAttempts(c, user) || !checkUserPassword(c, user, password) { - c.LogAuditWithUserId(user.Id, "fail - invalid password") + if err := checkPasswordAndAllCriteria(user, password, ""); err != nil { + c.LogAuditWithUserId(user.Id, "failed - bad authentication") + c.Err = err return } @@ -2147,7 +1975,7 @@ func emailToOAuth(c *Context, w http.ResponseWriter, r *http.Request) { stateProps["email"] = email m := map[string]string{} - if authUrl, err := GetAuthorizationCode(c, service, teamName, stateProps, ""); err != nil { + if authUrl, err := GetAuthorizationCode(c, service, stateProps, ""); err != nil { c.LogAuditWithUserId(user.Id, "fail - oauth issue") c.Err = err return @@ -2159,52 +1987,6 @@ func emailToOAuth(c *Context, w http.ResponseWriter, r *http.Request) { w.Write([]byte(model.MapToJson(m))) } -func CompleteSwitchWithOAuth(c *Context, w http.ResponseWriter, r *http.Request, service string, userData io.ReadCloser, team *model.Team, email string) { - authData := "" - ssoEmail := "" - provider := einterfaces.GetOauthProvider(service) - if provider == nil { - c.Err = model.NewLocAppError("CompleteClaimWithOAuth", "api.user.complete_switch_with_oauth.unavailable.app_error", - map[string]interface{}{"Service": service}, "") - return - } else { - ssoUser := provider.GetUserFromJson(userData) - authData = ssoUser.AuthData - ssoEmail = ssoUser.Email - } - - if len(authData) == 0 { - c.Err = model.NewLocAppError("CompleteClaimWithOAuth", "api.user.complete_switch_with_oauth.parse.app_error", - map[string]interface{}{"Service": service}, "") - return - } - - if len(email) == 0 { - c.Err = model.NewLocAppError("CompleteClaimWithOAuth", "api.user.complete_switch_with_oauth.blank_email.app_error", nil, "") - return - } - - var user *model.User - if result := <-Srv.Store.User().GetByEmail(team.Id, email); result.Err != nil { - c.Err = result.Err - return - } else { - user = result.Data.(*model.User) - } - - RevokeAllSession(c, user.Id) - if c.Err != nil { - return - } - - if result := <-Srv.Store.User().UpdateAuthData(user.Id, service, authData, ssoEmail); result.Err != nil { - c.Err = result.Err - return - } - - sendSignInChangeEmailAndForget(c, user.Email, team.DisplayName, c.GetSiteURL()+"/"+team.Name, c.GetSiteURL(), strings.Title(service)+" SSO") -} - func oauthToEmail(c *Context, w http.ResponseWriter, r *http.Request) { props := model.MapFromJson(r.Body) @@ -2214,12 +1996,6 @@ func oauthToEmail(c *Context, w http.ResponseWriter, r *http.Request) { return } - teamName := props["team_name"] - if len(teamName) == 0 { - c.SetInvalidParam("oauthToEmail", "team_name") - return - } - email := props["email"] if len(email) == 0 { c.SetInvalidParam("oauthToEmail", "email") @@ -2228,17 +2004,8 @@ func oauthToEmail(c *Context, w http.ResponseWriter, r *http.Request) { c.LogAudit("attempt") - var team *model.Team - if result := <-Srv.Store.Team().GetByName(teamName); result.Err != nil { - c.LogAudit("fail - couldn't get team") - c.Err = result.Err - return - } else { - team = result.Data.(*model.Team) - } - var user *model.User - if result := <-Srv.Store.User().GetByEmail(team.Id, email); result.Err != nil { + if result := <-Srv.Store.User().GetByEmail(email); result.Err != nil { c.LogAudit("fail - couldn't get user") c.Err = result.Err return @@ -2259,7 +2026,7 @@ func oauthToEmail(c *Context, w http.ResponseWriter, r *http.Request) { return } - sendSignInChangeEmailAndForget(c, user.Email, team.DisplayName, c.GetSiteURL()+"/"+team.Name, c.GetSiteURL(), c.T("api.templates.signin_change_email.body.method_email")) + sendSignInChangeEmailAndForget(c, user.Email, c.GetSiteURL(), c.T("api.templates.signin_change_email.body.method_email")) RevokeAllSession(c, c.Session.UserId) if c.Err != nil { @@ -2267,7 +2034,7 @@ func oauthToEmail(c *Context, w http.ResponseWriter, r *http.Request) { } m := map[string]string{} - m["follow_link"] = c.GetTeamURL() + "/login?extra=signin_change" + m["follow_link"] = "/login?extra=signin_change" c.LogAudit("success") w.Write([]byte(model.MapToJson(m))) @@ -2288,12 +2055,6 @@ func emailToLdap(c *Context, w http.ResponseWriter, r *http.Request) { return } - teamName := props["team_name"] - if len(teamName) == 0 { - c.SetInvalidParam("emailToLdap", "team_name") - return - } - ldapId := props["ldap_id"] if len(ldapId) == 0 { c.SetInvalidParam("emailToLdap", "ldap_id") @@ -2308,17 +2069,8 @@ func emailToLdap(c *Context, w http.ResponseWriter, r *http.Request) { c.LogAudit("attempt") - var team *model.Team - if result := <-Srv.Store.Team().GetByName(teamName); result.Err != nil { - c.LogAudit("fail - couldn't get team") - c.Err = result.Err - return - } else { - team = result.Data.(*model.Team) - } - var user *model.User - if result := <-Srv.Store.User().GetByEmail(team.Id, email); result.Err != nil { + if result := <-Srv.Store.User().GetByEmail(email); result.Err != nil { c.LogAudit("fail - couldn't get user") c.Err = result.Err return @@ -2326,8 +2078,9 @@ func emailToLdap(c *Context, w http.ResponseWriter, r *http.Request) { user = result.Data.(*model.User) } - if !checkUserLoginAttempts(c, user) || !checkUserPassword(c, user, emailPassword) { - c.LogAuditWithUserId(user.Id, "fail - invalid email password") + if err := checkPasswordAndAllCriteria(user, emailPassword, ""); err != nil { + c.LogAuditWithUserId(user.Id, "failed - bad authentication") + c.Err = err return } @@ -2343,16 +2096,16 @@ func emailToLdap(c *Context, w http.ResponseWriter, r *http.Request) { return } - if err := ldapInterface.SwitchToEmail(user.Id, ldapId, ldapPassword); err != nil { + if err := ldapInterface.SwitchToLdap(user.Id, ldapId, ldapPassword); err != nil { c.LogAuditWithUserId(user.Id, "fail - ldap switch failed") c.Err = err return } - sendSignInChangeEmailAndForget(c, user.Email, team.DisplayName, c.GetSiteURL()+"/"+team.Name, c.GetSiteURL(), "LDAP") + sendSignInChangeEmailAndForget(c, user.Email, c.GetSiteURL(), "LDAP") m := map[string]string{} - m["follow_link"] = c.GetTeamURL() + "/login?extra=signin_change" + m["follow_link"] = "/login?extra=signin_change" c.LogAudit("success") w.Write([]byte(model.MapToJson(m))) @@ -2373,12 +2126,6 @@ func ldapToEmail(c *Context, w http.ResponseWriter, r *http.Request) { return } - teamName := props["team_name"] - if len(teamName) == 0 { - c.SetInvalidParam("ldapToEmail", "team_name") - return - } - ldapPassword := props["ldap_password"] if len(ldapPassword) == 0 { c.SetInvalidParam("ldapToEmail", "ldap_password") @@ -2387,17 +2134,8 @@ func ldapToEmail(c *Context, w http.ResponseWriter, r *http.Request) { c.LogAudit("attempt") - var team *model.Team - if result := <-Srv.Store.Team().GetByName(teamName); result.Err != nil { - c.LogAudit("fail - couldn't get team") - c.Err = result.Err - return - } else { - team = result.Data.(*model.Team) - } - var user *model.User - if result := <-Srv.Store.User().GetByEmail(team.Id, email); result.Err != nil { + if result := <-Srv.Store.User().GetByEmail(email); result.Err != nil { c.LogAudit("fail - couldn't get user") c.Err = result.Err return @@ -2434,27 +2172,27 @@ func ldapToEmail(c *Context, w http.ResponseWriter, r *http.Request) { return } - sendSignInChangeEmailAndForget(c, user.Email, team.DisplayName, c.GetSiteURL()+"/"+team.Name, c.GetSiteURL(), c.T("api.templates.signin_change_email.body.method_email")) + sendSignInChangeEmailAndForget(c, user.Email, c.GetSiteURL(), c.T("api.templates.signin_change_email.body.method_email")) m := map[string]string{} - m["follow_link"] = c.GetTeamURL() + "/login?extra=signin_change" + m["follow_link"] = "/login?extra=signin_change" c.LogAudit("success") w.Write([]byte(model.MapToJson(m))) } -func sendSignInChangeEmailAndForget(c *Context, email, teamDisplayName, teamURL, siteURL, method string) { +func sendSignInChangeEmailAndForget(c *Context, email, siteURL, method string) { go func() { subjectPage := utils.NewHTMLTemplate("signin_change_subject", c.Locale) subjectPage.Props["Subject"] = c.T("api.templates.singin_change_email.subject", - map[string]interface{}{"TeamDisplayName": teamDisplayName, "SiteName": utils.ClientCfg["SiteName"]}) + map[string]interface{}{"SiteName": utils.ClientCfg["SiteName"]}) bodyPage := utils.NewHTMLTemplate("signin_change_body", c.Locale) bodyPage.Props["SiteURL"] = siteURL bodyPage.Props["Title"] = c.T("api.templates.signin_change_email.body.title") bodyPage.Html["Info"] = template.HTML(c.T("api.templates.singin_change_email.body.info", - map[string]interface{}{"TeamDisplayName": teamDisplayName, "TeamURL": teamURL, "Method": method})) + map[string]interface{}{"SiteName": utils.ClientCfg["SiteName"], "Method": method})) if err := utils.SendMail(email, subjectPage.Render(), bodyPage.Render()); err != nil { l4g.Error(utils.T("api.user.send_sign_in_change_email_and_forget.error"), err) @@ -2488,49 +2226,34 @@ func verifyEmail(c *Context, w http.ResponseWriter, r *http.Request) { } c.Err = model.NewLocAppError("verifyEmail", "api.user.verify_email.bad_link.app_error", nil, "") - c.Err.StatusCode = http.StatusForbidden + c.Err.StatusCode = http.StatusBadRequest } func resendVerification(c *Context, w http.ResponseWriter, r *http.Request) { props := model.MapFromJson(r.Body) - teamName := props["team_name"] - if len(teamName) == 0 { - c.SetInvalidParam("resendVerification", "team_name") - return - } - email := props["email"] if len(email) == 0 { c.SetInvalidParam("resendVerification", "email") return } - var team *model.Team - if result := <-Srv.Store.Team().GetByName(teamName); result.Err != nil { - c.Err = result.Err - return - } else { - team = result.Data.(*model.Team) - } - - if result := <-Srv.Store.User().GetByEmail(team.Id, email); result.Err != nil { + if result := <-Srv.Store.User().GetByEmail(email); result.Err != nil { c.Err = result.Err return } else { user := result.Data.(*model.User) if user.LastActivityAt > 0 { - SendEmailChangeVerifyEmailAndForget(c, user.Id, user.Email, team.Name, team.DisplayName, c.GetSiteURL(), c.GetTeamURLFromTeam(team)) + SendEmailChangeVerifyEmailAndForget(c, user.Id, user.Email, c.GetSiteURL()) } else { - SendVerifyEmailAndForget(c, user.Id, user.Email, team.Name, team.DisplayName, c.GetSiteURL(), c.GetTeamURLFromTeam(team)) + SendVerifyEmailAndForget(c, user.Id, user.Email, c.GetSiteURL()) } } } func generateMfaQrCode(c *Context, w http.ResponseWriter, r *http.Request) { uchan := Srv.Store.User().Get(c.Session.UserId) - tchan := Srv.Store.Team().Get(c.Session.TeamId) var user *model.User if result := <-uchan; result.Err != nil { @@ -2540,14 +2263,6 @@ func generateMfaQrCode(c *Context, w http.ResponseWriter, r *http.Request) { user = result.Data.(*model.User) } - var team *model.Team - if result := <-tchan; result.Err != nil { - c.Err = result.Err - return - } else { - team = result.Data.(*model.Team) - } - mfaInterface := einterfaces.GetMfaInterface() if mfaInterface == nil { c.Err = model.NewLocAppError("generateMfaQrCode", "api.user.generate_mfa_qr.not_available.app_error", nil, "") @@ -2555,7 +2270,7 @@ func generateMfaQrCode(c *Context, w http.ResponseWriter, r *http.Request) { return } - img, err := mfaInterface.GenerateQrCode(team, user) + img, err := mfaInterface.GenerateQrCode(user) if err != nil { c.Err = err return @@ -2583,28 +2298,13 @@ func updateMfa(c *Context, w http.ResponseWriter, r *http.Request) { } } - mfaInterface := einterfaces.GetMfaInterface() - if mfaInterface == nil { - c.Err = model.NewLocAppError("generateMfaQrCode", "api.user.update_mfa.not_available.app_error", nil, "") - c.Err.StatusCode = http.StatusNotImplemented - return - } - if activate { - var user *model.User - if result := <-Srv.Store.User().Get(c.Session.UserId); result.Err != nil { - c.Err = result.Err - return - } else { - user = result.Data.(*model.User) - } - - if err := mfaInterface.Activate(user, token); err != nil { + if err := ActivateMfa(c.Session.UserId, token); err != nil { c.Err = err return } } else { - if err := mfaInterface.Deactivate(c.Session.UserId); err != nil { + if err := DeactivateMfa(c.Session.UserId); err != nil { c.Err = err return } @@ -2615,6 +2315,43 @@ func updateMfa(c *Context, w http.ResponseWriter, r *http.Request) { w.Write([]byte(model.MapToJson(rdata))) } +func ActivateMfa(userId, token string) *model.AppError { + mfaInterface := einterfaces.GetMfaInterface() + if mfaInterface == nil { + err := model.NewLocAppError("ActivateMfa", "api.user.update_mfa.not_available.app_error", nil, "") + err.StatusCode = http.StatusNotImplemented + return err + } + + var user *model.User + if result := <-Srv.Store.User().Get(userId); result.Err != nil { + return result.Err + } else { + user = result.Data.(*model.User) + } + + if err := mfaInterface.Activate(user, token); err != nil { + return err + } + + return nil +} + +func DeactivateMfa(userId string) *model.AppError { + mfaInterface := einterfaces.GetMfaInterface() + if mfaInterface == nil { + err := model.NewLocAppError("DeactivateMfa", "api.user.update_mfa.not_available.app_error", nil, "") + err.StatusCode = http.StatusNotImplemented + return err + } + + if err := mfaInterface.Deactivate(userId); err != nil { + return err + } + + return nil +} + func checkMfa(c *Context, w http.ResponseWriter, r *http.Request) { if !utils.IsLicensed || !*utils.License.Features.MFA || !*utils.Cfg.ServiceSettings.EnableMultifactorAuthentication { rdata := map[string]string{} @@ -2633,33 +2370,19 @@ func checkMfa(c *Context, w http.ResponseWriter, r *http.Request) { return } - teamName := props["team_name"] - if len(teamName) == 0 { - c.SetInvalidParam("checkMfa", "team_name") - return - } - loginId := props["login_id"] if len(loginId) == 0 { c.SetInvalidParam("checkMfa", "login_id") return } - var team *model.Team - if result := <-Srv.Store.Team().GetByName(teamName); result.Err != nil { - c.Err = result.Err - return - } else { - team = result.Data.(*model.Team) - } - var uchan store.StoreChannel if method == model.USER_AUTH_SERVICE_EMAIL { - uchan = Srv.Store.User().GetByEmail(team.Id, loginId) + uchan = Srv.Store.User().GetByEmail(loginId) } else if method == model.USER_AUTH_SERVICE_USERNAME { - uchan = Srv.Store.User().GetByUsername(team.Id, loginId) + uchan = Srv.Store.User().GetByUsername(loginId) } else if method == model.USER_AUTH_SERVICE_LDAP { - uchan = Srv.Store.User().GetByAuth(team.Id, loginId, model.USER_AUTH_SERVICE_LDAP) + uchan = Srv.Store.User().GetByAuth(loginId, model.USER_AUTH_SERVICE_LDAP) } rdata := map[string]string{} |