// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. package api import ( "bytes" b64 "encoding/base64" "fmt" "io" "net/http" "strconv" "strings" "time" l4g "github.com/alecthomas/log4go" "github.com/gorilla/mux" "github.com/mattermost/platform/app" "github.com/mattermost/platform/einterfaces" "github.com/mattermost/platform/model" "github.com/mattermost/platform/store" "github.com/mattermost/platform/utils" ) func InitUser() { l4g.Debug(utils.T("api.user.init.debug")) BaseRoutes.Users.Handle("/create", ApiAppHandler(createUser)).Methods("POST") BaseRoutes.Users.Handle("/update", ApiUserRequired(updateUser)).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("/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", ApiUserRequired(getMe)).Methods("GET") BaseRoutes.Users.Handle("/initial_load", ApiAppHandler(getInitialLoad)).Methods("GET") BaseRoutes.Users.Handle("/{offset:[0-9]+}/{limit:[0-9]+}", ApiUserRequired(getProfiles)).Methods("GET") BaseRoutes.NeedTeam.Handle("/users/{offset:[0-9]+}/{limit:[0-9]+}", ApiUserRequired(getProfilesInTeam)).Methods("GET") BaseRoutes.NeedChannel.Handle("/users/{offset:[0-9]+}/{limit:[0-9]+}", ApiUserRequired(getProfilesInChannel)).Methods("GET") BaseRoutes.NeedChannel.Handle("/users/not_in_channel/{offset:[0-9]+}/{limit:[0-9]+}", ApiUserRequired(getProfilesNotInChannel)).Methods("GET") BaseRoutes.Users.Handle("/search", ApiUserRequired(searchUsers)).Methods("POST") BaseRoutes.Users.Handle("/ids", ApiUserRequired(getProfilesByIds)).Methods("POST") BaseRoutes.Users.Handle("/autocomplete", ApiUserRequired(autocompleteUsers)).Methods("GET") BaseRoutes.NeedTeam.Handle("/users/autocomplete", ApiUserRequired(autocompleteUsersInTeam)).Methods("GET") BaseRoutes.NeedChannel.Handle("/users/autocomplete", ApiUserRequired(autocompleteUsersInChannel)).Methods("GET") BaseRoutes.Users.Handle("/mfa", ApiAppHandler(checkMfa)).Methods("POST") BaseRoutes.Users.Handle("/generate_mfa_secret", ApiUserRequiredMfa(generateMfaSecret)).Methods("GET") BaseRoutes.Users.Handle("/update_mfa", ApiUserRequiredMfa(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.Users.Handle("/name/{username:[A-Za-z0-9_\\-.]+}", ApiUserRequired(getByUsername)).Methods("GET") BaseRoutes.Users.Handle("/email/{email}", ApiUserRequired(getByEmail)).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") BaseRoutes.NeedUser.Handle("/update_roles", ApiUserRequired(updateRoles)).Methods("POST") BaseRoutes.Root.Handle("/login/sso/saml", AppHandlerIndependent(loginWithSaml)).Methods("GET") BaseRoutes.Root.Handle("/login/sso/saml", AppHandlerIndependent(completeSaml)).Methods("POST") app.Srv.WebSocketRouter.Handle("user_typing", ApiWebSocketHandler(userTyping)) } func createUser(c *Context, w http.ResponseWriter, r *http.Request) { user := model.UserFromJson(r.Body) if user == nil { c.SetInvalidParam("createUser", "user") return } hash := r.URL.Query().Get("h") inviteId := r.URL.Query().Get("iid") var ruser *model.User var err *model.AppError if len(hash) > 0 { ruser, err = app.CreateUserWithHash(user, hash, r.URL.Query().Get("d")) } else if len(inviteId) > 0 { ruser, err = app.CreateUserWithInviteId(user, inviteId, c.GetSiteURL()) } else { ruser, err = app.CreateUserFromSignup(user, c.GetSiteURL()) } if err != nil { c.Err = err return } w.Write([]byte(ruser.ToJson())) } func login(c *Context, w http.ResponseWriter, r *http.Request) { props := model.MapFromJson(r.Body) id := props["id"] loginId := props["login_id"] password := props["password"] mfaToken := props["token"] deviceId := props["device_id"] ldapOnly := props["ldap_only"] == "true" c.LogAudit("attempt - user_id=" + id + " login_id=" + loginId) user, err := app.AuthenticateUserForLogin(id, loginId, password, mfaToken, deviceId, ldapOnly) if err != nil { c.LogAudit("failure - user_id=" + id + " login_id=" + loginId) c.Err = err return } c.LogAuditWithUserId(user.Id, "success") doLogin(c, w, r, user, deviceId) if c.Err != nil { return } user.Sanitize(map[string]bool{}) w.Write([]byte(user.ToJson())) } func LoginByOAuth(c *Context, w http.ResponseWriter, r *http.Request, service string, userData io.Reader) *model.User { buf := bytes.Buffer{} buf.ReadFrom(userData) authData := "" provider := einterfaces.GetOauthProvider(service) if provider == nil { c.Err = model.NewLocAppError("LoginByOAuth", "api.user.login_by_oauth.not_available.app_error", map[string]interface{}{"Service": strings.Title(service)}, "") return nil } else { authData = provider.GetAuthDataFromJson(bytes.NewReader(buf.Bytes())) } if len(authData) == 0 { c.Err = model.NewLocAppError("LoginByOAuth", "api.user.login_by_oauth.parse.app_error", map[string]interface{}{"Service": service}, "") return nil } var user *model.User var err *model.AppError if user, err = app.GetUserByAuth(&authData, service); err != nil { if err.Id == store.MISSING_AUTH_ACCOUNT_ERROR { if user, err = app.CreateOAuthUser(service, bytes.NewReader(buf.Bytes()), ""); err != nil { c.Err = err return nil } } c.Err = err return nil } if err = app.UpdateOAuthUserAttrs(bytes.NewReader(buf.Bytes()), user, provider, service, c.siteURL); err != nil { c.Err = err return nil } doLogin(c, w, r, user, "") if c.Err != nil { return nil } return user } // User MUST be authenticated completely before calling Login func doLogin(c *Context, w http.ResponseWriter, r *http.Request, user *model.User, deviceId string) { session, err := app.DoLogin(w, r, user, deviceId) if err != nil { c.Err = err return } c.Session = *session } func revokeSession(c *Context, w http.ResponseWriter, r *http.Request) { props := model.MapFromJson(r.Body) id := props["id"] if err := app.RevokeSessionById(id); err != nil { c.Err = err return } w.Write([]byte(model.MapToJson(props))) } func attachDeviceId(c *Context, w http.ResponseWriter, r *http.Request) { props := model.MapFromJson(r.Body) deviceId := props["device_id"] if len(deviceId) == 0 { c.SetInvalidParam("attachDevice", "deviceId") return } // A special case where we logout of all other sessions with the same device id if err := app.RevokeSessionsForDeviceId(c.Session.UserId, deviceId, c.Session.Id); err != nil { c.Err = err c.Err.StatusCode = http.StatusInternalServerError return } app.ClearSessionCacheForUser(c.Session.UserId) c.Session.SetExpireInDays(*utils.Cfg.ServiceSettings.SessionLengthMobileInDays) maxAge := *utils.Cfg.ServiceSettings.SessionLengthMobileInDays * 60 * 60 * 24 secure := false if app.GetProtocol(r) == "https" { secure = true } expiresAt := time.Unix(model.GetMillis()/1000+int64(maxAge), 0) sessionCookie := &http.Cookie{ Name: model.SESSION_COOKIE_TOKEN, Value: c.Session.Token, Path: "/", MaxAge: maxAge, Expires: expiresAt, HttpOnly: true, Secure: secure, } http.SetCookie(w, sessionCookie) if err := app.AttachDeviceId(c.Session.Id, deviceId, c.Session.ExpiresAt); err != nil { c.Err = err return } w.Write([]byte(model.MapToJson(props))) } func getSessions(c *Context, w http.ResponseWriter, r *http.Request) { params := mux.Vars(r) id := params["user_id"] if !app.SessionHasPermissionToUser(c.Session, id) { c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS) return } if sessions, err := app.GetSessions(id); err != nil { c.Err = err return } else { for _, session := range sessions { session.Sanitize() } w.Write([]byte(model.SessionsToJson(sessions))) } } func logout(c *Context, w http.ResponseWriter, r *http.Request) { data := make(map[string]string) data["user_id"] = c.Session.UserId Logout(c, w, r) if c.Err == nil { w.Write([]byte(model.MapToJson(data))) } } func Logout(c *Context, w http.ResponseWriter, r *http.Request) { c.LogAudit("") c.RemoveSessionCookie(w, r) if c.Session.Id != "" { if err := app.RevokeSessionById(c.Session.Id); err != nil { c.Err = err return } } } func getMe(c *Context, w http.ResponseWriter, r *http.Request) { if user, err := app.GetUser(c.Session.UserId); err != nil { c.Err = err c.RemoveSessionCookie(w, r) l4g.Error(utils.T("api.user.get_me.getting.error"), c.Session.UserId) return } else if HandleEtag(user.Etag(utils.Cfg.PrivacySettings.ShowFullName, utils.Cfg.PrivacySettings.ShowEmailAddress), "Get Me", w, r) { return } else { user.Sanitize(map[string]bool{}) w.Header().Set(model.HEADER_ETAG_SERVER, user.Etag(utils.Cfg.PrivacySettings.ShowFullName, utils.Cfg.PrivacySettings.ShowEmailAddress)) w.Write([]byte(user.ToJson())) return } } func getInitialLoad(c *Context, w http.ResponseWriter, r *http.Request) { il := model.InitialLoad{} if len(c.Session.UserId) != 0 { var err *model.AppError il.User, err = app.GetUser(c.Session.UserId) if err != nil { c.Err = err return } il.User.Sanitize(map[string]bool{}) il.Preferences, err = app.GetPreferencesForUser(c.Session.UserId) if err != nil { c.Err = err return } il.Teams, err = app.GetTeamsForUser(c.Session.UserId) if err != nil { c.Err = err return } for _, team := range il.Teams { team.Sanitize() } il.TeamMembers = c.Session.TeamMembers } if app.SessionCacheLength() == 0 { // Below is a special case when intializating a new server // Lets check to make sure the server is really empty il.NoAccounts = app.IsFirstUserAccount() } il.ClientCfg = utils.ClientCfg if app.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) { il.LicenseCfg = utils.ClientLicense } else { il.LicenseCfg = utils.GetSanitizedClientLicense() } w.Write([]byte(il.ToJson())) } func getUser(c *Context, w http.ResponseWriter, r *http.Request) { params := mux.Vars(r) id := params["user_id"] var user *model.User var err *model.AppError if user, err = app.GetUser(id); err != nil { c.Err = err return } etag := user.Etag(utils.Cfg.PrivacySettings.ShowFullName, utils.Cfg.PrivacySettings.ShowEmailAddress) if HandleEtag(etag, "Get User", w, r) { return } else { app.SanitizeProfile(user, c.IsSystemAdmin()) w.Header().Set(model.HEADER_ETAG_SERVER, etag) w.Write([]byte(user.ToJson())) return } } func getByUsername(c *Context, w http.ResponseWriter, r *http.Request) { params := mux.Vars(r) username := params["username"] var user *model.User var err *model.AppError if user, err = app.GetUserByUsername(username); err != nil { c.Err = err return } else if HandleEtag(user.Etag(utils.Cfg.PrivacySettings.ShowFullName, utils.Cfg.PrivacySettings.ShowEmailAddress), "Get By Username", w, r) { return } else { sanitizeProfile(c, user) w.Header().Set(model.HEADER_ETAG_SERVER, user.Etag(utils.Cfg.PrivacySettings.ShowFullName, utils.Cfg.PrivacySettings.ShowEmailAddress)) w.Write([]byte(user.ToJson())) return } } func getByEmail(c *Context, w http.ResponseWriter, r *http.Request) { params := mux.Vars(r) email := params["email"] if user, err := app.GetUserByEmail(email); err != nil { c.Err = err return } else if HandleEtag(user.Etag(utils.Cfg.PrivacySettings.ShowFullName, utils.Cfg.PrivacySettings.ShowEmailAddress), "Get By Email", w, r) { return } else { sanitizeProfile(c, user) w.Header().Set(model.HEADER_ETAG_SERVER, user.Etag(utils.Cfg.PrivacySettings.ShowFullName, utils.Cfg.PrivacySettings.ShowEmailAddress)) w.Write([]byte(user.ToJson())) return } } func getProfiles(c *Context, w http.ResponseWriter, r *http.Request) { params := mux.Vars(r) offset, err := strconv.Atoi(params["offset"]) if err != nil { c.SetInvalidParam("getProfiles", "offset") return } limit, err := strconv.Atoi(params["limit"]) if err != nil { c.SetInvalidParam("getProfiles", "limit") return } etag := app.GetUsersEtag() + params["offset"] + "." + params["limit"] if HandleEtag(etag, "Get Profiles", w, r) { return } if profiles, err := app.GetUsersMap(offset, limit, c.IsSystemAdmin()); err != nil { c.Err = err return } else { w.Header().Set(model.HEADER_ETAG_SERVER, etag) w.Write([]byte(model.UserMapToJson(profiles))) } } func getProfilesInTeam(c *Context, w http.ResponseWriter, r *http.Request) { params := mux.Vars(r) teamId := params["team_id"] if c.Session.GetTeamByTeamId(teamId) == nil { if !app.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) { return } } offset, err := strconv.Atoi(params["offset"]) if err != nil { c.SetInvalidParam("getProfilesInTeam", "offset") return } limit, err := strconv.Atoi(params["limit"]) if err != nil { c.SetInvalidParam("getProfilesInTeam", "limit") return } etag := app.GetUsersInTeamEtag(teamId) if HandleEtag(etag, "Get Profiles In Team", w, r) { return } if profiles, err := app.GetUsersInTeamMap(teamId, offset, limit, c.IsSystemAdmin()); err != nil { c.Err = err return } else { w.Header().Set(model.HEADER_ETAG_SERVER, etag) w.Write([]byte(model.UserMapToJson(profiles))) } } func getProfilesInChannel(c *Context, w http.ResponseWriter, r *http.Request) { params := mux.Vars(r) channelId := params["channel_id"] offset, err := strconv.Atoi(params["offset"]) if err != nil { c.SetInvalidParam("getProfiles", "offset") return } limit, err := strconv.Atoi(params["limit"]) if err != nil { c.SetInvalidParam("getProfiles", "limit") return } if c.Session.GetTeamByTeamId(c.TeamId) == nil { if !app.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) { c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM) return } } if !app.SessionHasPermissionToChannel(c.Session, channelId, model.PERMISSION_READ_CHANNEL) { c.SetPermissionError(model.PERMISSION_READ_CHANNEL) return } if profiles, err := app.GetUsersInChannelMap(channelId, offset, limit, c.IsSystemAdmin()); err != nil { c.Err = err return } else { w.Write([]byte(model.UserMapToJson(profiles))) } } func getProfilesNotInChannel(c *Context, w http.ResponseWriter, r *http.Request) { params := mux.Vars(r) channelId := params["channel_id"] if c.Session.GetTeamByTeamId(c.TeamId) == nil { if !app.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) { c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM) return } } if !app.SessionHasPermissionToChannel(c.Session, channelId, model.PERMISSION_READ_CHANNEL) { c.SetPermissionError(model.PERMISSION_READ_CHANNEL) return } offset, err := strconv.Atoi(params["offset"]) if err != nil { c.SetInvalidParam("getProfiles", "offset") return } limit, err := strconv.Atoi(params["limit"]) if err != nil { c.SetInvalidParam("getProfiles", "limit") return } if profiles, err := app.GetUsersNotInChannelMap(c.TeamId, channelId, offset, limit, c.IsSystemAdmin()); err != nil { c.Err = err return } else { w.Write([]byte(model.UserMapToJson(profiles))) } } func getAudits(c *Context, w http.ResponseWriter, r *http.Request) { params := mux.Vars(r) id := params["user_id"] if !app.SessionHasPermissionToUser(c.Session, id) { c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS) return } if audits, err := app.GetAudits(id, 20); err != nil { c.Err = err return } else { etag := audits.Etag() if HandleEtag(etag, "Get Audits", w, r) { return } if len(etag) > 0 { w.Header().Set(model.HEADER_ETAG_SERVER, etag) } w.Write([]byte(audits.ToJson())) return } } func getProfileImage(c *Context, w http.ResponseWriter, r *http.Request) { params := mux.Vars(r) id := params["user_id"] readFailed := false var etag string if users, err := app.GetUsersByIds([]string{id}, false); err != nil { c.Err = err return } else { if len(users) == 0 { c.Err = model.NewLocAppError("getProfileImage", "store.sql_user.get_profiles.app_error", nil, "") return } user := users[0] etag = strconv.FormatInt(user.LastPictureUpdate, 10) if HandleEtag(etag, "Profile Image", w, r) { return } var img []byte img, readFailed, err = app.GetProfileImage(user) if err != nil { c.Err = err return } if readFailed { w.Header().Set("Cache-Control", "max-age=300, public") // 5 mins } else { w.Header().Set("Cache-Control", "max-age=86400, public") // 24 hrs } w.Header().Set("Content-Type", "image/png") w.Header().Set(model.HEADER_ETAG_SERVER, etag) w.Write(img) } } func uploadProfileImage(c *Context, w http.ResponseWriter, r *http.Request) { if len(utils.Cfg.FileSettings.DriverName) == 0 { c.Err = model.NewLocAppError("uploadProfileImage", "api.user.upload_profile_user.storage.app_error", nil, "") c.Err.StatusCode = http.StatusNotImplemented return } if r.ContentLength > *utils.Cfg.FileSettings.MaxFileSize { c.Err = model.NewLocAppError("uploadProfileImage", "api.user.upload_profile_user.too_large.app_error", nil, "") c.Err.StatusCode = http.StatusRequestEntityTooLarge return } if err := r.ParseMultipartForm(*utils.Cfg.FileSettings.MaxFileSize); err != nil { c.Err = model.NewLocAppError("uploadProfileImage", "api.user.upload_profile_user.parse.app_error", nil, "") return } m := r.MultipartForm imageArray, ok := m.File["image"] if !ok { c.Err = model.NewLocAppError("uploadProfileImage", "api.user.upload_profile_user.no_file.app_error", nil, "") c.Err.StatusCode = http.StatusBadRequest return } if len(imageArray) <= 0 { c.Err = model.NewLocAppError("uploadProfileImage", "api.user.upload_profile_user.array.app_error", nil, "") c.Err.StatusCode = http.StatusBadRequest return } imageData := imageArray[0] if err := app.SetProfileImage(c.Session.UserId, imageData); err != nil { c.Err = err return } c.LogAudit("") // write something as the response since jQuery expects a json response w.Write([]byte("true")) } func updateUser(c *Context, w http.ResponseWriter, r *http.Request) { user := model.UserFromJson(r.Body) if user == nil { c.SetInvalidParam("updateUser", "user") return } if !app.SessionHasPermissionToUser(c.Session, user.Id) { c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS) return } if ruser, err := app.UpdateUserAsUser(user, c.GetSiteURL(), c.IsSystemAdmin()); err != nil { c.Err = err return } else { c.LogAudit("") w.Write([]byte(ruser.ToJson())) } } func updatePassword(c *Context, w http.ResponseWriter, r *http.Request) { c.LogAudit("attempted") props := model.MapFromJson(r.Body) userId := props["user_id"] if len(userId) != 26 { c.SetInvalidParam("updatePassword", "user_id") return } currentPassword := props["current_password"] if len(currentPassword) <= 0 { c.SetInvalidParam("updatePassword", "current_password") return } newPassword := props["new_password"] if userId != c.Session.UserId { c.Err = model.NewLocAppError("updatePassword", "api.user.update_password.context.app_error", nil, "") c.Err.StatusCode = http.StatusForbidden return } if err := app.UpdatePasswordAsUser(userId, currentPassword, newPassword, c.GetSiteURL()); err != nil { c.LogAudit("failed") c.Err = err return } else { c.LogAudit("completed") data := make(map[string]string) data["user_id"] = c.Session.UserId w.Write([]byte(model.MapToJson(data))) } } func updateRoles(c *Context, w http.ResponseWriter, r *http.Request) { props := model.MapFromJson(r.Body) params := mux.Vars(r) userId := params["user_id"] if len(userId) != 26 { c.SetInvalidParam("updateMemberRoles", "user_id") return } newRoles := props["new_roles"] if !(model.IsValidUserRoles(newRoles)) { c.SetInvalidParam("updateMemberRoles", "new_roles") return } if !app.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_ROLES) { c.SetPermissionError(model.PERMISSION_MANAGE_ROLES) return } if _, err := app.UpdateUserRoles(userId, newRoles); err != nil { return } else { c.LogAuditWithUserId(userId, "roles="+newRoles) } rdata := map[string]string{} rdata["status"] = "ok" w.Write([]byte(model.MapToJson(rdata))) } func updateActive(c *Context, w http.ResponseWriter, r *http.Request) { props := model.MapFromJson(r.Body) userId := props["user_id"] if len(userId) != 26 { c.SetInvalidParam("updateActive", "user_id") return } active := props["active"] == "true" // true when you're trying to de-activate yourself isSelfDeactive := !active && userId == c.Session.UserId if !isSelfDeactive && !app.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) { c.Err = model.NewLocAppError("updateActive", "api.user.update_active.permissions.app_error", nil, "userId="+userId) c.Err.StatusCode = http.StatusForbidden return } if ruser, err := app.UpdateActiveNoLdap(userId, active); err != nil { c.Err = err } else { c.LogAuditWithUserId(ruser.Id, fmt.Sprintf("active=%v", active)) w.Write([]byte(ruser.ToJson())) } } func sendPasswordReset(c *Context, w http.ResponseWriter, r *http.Request) { props := model.MapFromJson(r.Body) email := props["email"] if len(email) == 0 { c.SetInvalidParam("sendPasswordReset", "email") return } if sent, err := app.SendPasswordReset(email, c.GetSiteURL()); err != nil { c.Err = err return } else if sent { c.LogAudit("sent=" + email) } w.Write([]byte(model.MapToJson(props))) } func resetPassword(c *Context, w http.ResponseWriter, r *http.Request) { props := model.MapFromJson(r.Body) code := props["code"] if len(code) != model.PASSWORD_RECOVERY_CODE_SIZE { c.SetInvalidParam("resetPassword", "code") return } newPassword := props["new_password"] c.LogAudit("attempt - code=" + code) if err := app.ResetPasswordFromCode(code, newPassword, c.GetSiteURL()); err != nil { c.LogAudit("fail - code=" + code) c.Err = err return } c.LogAudit("success - code=" + code) rdata := map[string]string{} rdata["status"] = "ok" w.Write([]byte(model.MapToJson(rdata))) } func updateUserNotify(c *Context, w http.ResponseWriter, r *http.Request) { props := model.MapFromJson(r.Body) userId := props["user_id"] if len(userId) != 26 { c.SetInvalidParam("updateUserNotify", "user_id") return } if !app.SessionHasPermissionToUser(c.Session, userId) { c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS) return } delete(props, "user_id") email := props["email"] if len(email) == 0 { c.SetInvalidParam("updateUserNotify", "email") return } desktop_sound := props["desktop_sound"] if len(desktop_sound) == 0 { c.SetInvalidParam("updateUserNotify", "desktop_sound") return } desktop := props["desktop"] if len(desktop) == 0 { c.SetInvalidParam("updateUserNotify", "desktop") return } comments := props["comments"] if len(comments) == 0 { c.SetInvalidParam("updateUserNotify", "comments") return } ruser, err := app.UpdateUserNotifyProps(userId, props, c.GetSiteURL()) if err != nil { c.Err = err return } c.LogAuditWithUserId(ruser.Id, "") options := utils.Cfg.GetSanitizeOptions() options["passwordupdate"] = false ruser.Sanitize(options) w.Write([]byte(ruser.ToJson())) } func emailToOAuth(c *Context, w http.ResponseWriter, r *http.Request) { props := model.MapFromJson(r.Body) password := props["password"] if len(password) == 0 { c.SetInvalidParam("emailToOAuth", "password") return } mfaToken := props["token"] service := props["service"] if len(service) == 0 { c.SetInvalidParam("emailToOAuth", "service") return } email := props["email"] if len(email) == 0 { c.SetInvalidParam("emailToOAuth", "email") return } c.LogAudit("attempt") var user *model.User var err *model.AppError if user, err = app.GetUserByEmail(email); err != nil { c.LogAudit("fail - couldn't get user") c.Err = err return } if err := app.CheckPasswordAndAllCriteria(user, password, mfaToken); err != nil { c.LogAuditWithUserId(user.Id, "failed - bad authentication") c.Err = err return } stateProps := map[string]string{} stateProps["action"] = model.OAUTH_ACTION_EMAIL_TO_SSO stateProps["email"] = email m := map[string]string{} if service == model.USER_AUTH_SERVICE_SAML { m["follow_link"] = c.GetSiteURL() + "/login/sso/saml?action=" + model.OAUTH_ACTION_EMAIL_TO_SSO + "&email=" + email } else { if authUrl, err := GetAuthorizationCode(c, service, stateProps, ""); err != nil { c.LogAuditWithUserId(user.Id, "fail - oauth issue") c.Err = err return } else { m["follow_link"] = authUrl } } c.LogAuditWithUserId(user.Id, "success") w.Write([]byte(model.MapToJson(m))) } func oauthToEmail(c *Context, w http.ResponseWriter, r *http.Request) { props := model.MapFromJson(r.Body) password := props["password"] if err := utils.IsPasswordValid(password); err != nil { c.Err = err return } email := props["email"] if len(email) == 0 { c.SetInvalidParam("oauthToEmail", "email") return } c.LogAudit("attempt") var user *model.User var err *model.AppError if user, err = app.GetUserByEmail(email); err != nil { c.LogAudit("fail - couldn't get user") c.Err = err return } if user.Id != c.Session.UserId { c.LogAudit("fail - user ids didn't match") c.Err = model.NewLocAppError("oauthToEmail", "api.user.oauth_to_email.context.app_error", nil, "") c.Err.StatusCode = http.StatusForbidden return } if err := app.UpdatePassword(user, password); err != nil { c.LogAudit("fail - database issue") c.Err = err return } go func() { if err := app.SendSignInChangeEmail(user.Email, c.T("api.templates.signin_change_email.body.method_email"), user.Locale, c.GetSiteURL()); err != nil { l4g.Error(err.Error()) } }() if err := app.RevokeAllSessions(c.Session.UserId); err != nil { c.Err = err return } c.LogAuditWithUserId(c.Session.UserId, "Revoked all sessions for user") c.RemoveSessionCookie(w, r) if c.Err != nil { return } m := map[string]string{} m["follow_link"] = "/login?extra=signin_change" c.LogAudit("success") w.Write([]byte(model.MapToJson(m))) } func emailToLdap(c *Context, w http.ResponseWriter, r *http.Request) { props := model.MapFromJson(r.Body) email := props["email"] if len(email) == 0 { c.SetInvalidParam("emailToLdap", "email") return } emailPassword := props["email_password"] if len(emailPassword) == 0 { c.SetInvalidParam("emailToLdap", "email_password") return } ldapId := props["ldap_id"] if len(ldapId) == 0 { c.SetInvalidParam("emailToLdap", "ldap_id") return } ldapPassword := props["ldap_password"] if len(ldapPassword) == 0 { c.SetInvalidParam("emailToLdap", "ldap_password") return } token := props["token"] c.LogAudit("attempt") var user *model.User var err *model.AppError if user, err = app.GetUserByEmail(email); err != nil { c.LogAudit("fail - couldn't get user") c.Err = err return } if err := app.CheckPasswordAndAllCriteria(user, emailPassword, token); err != nil { c.LogAuditWithUserId(user.Id, "failed - bad authentication") c.Err = err return } if err := app.RevokeAllSessions(user.Id); err != nil { c.Err = err return } c.LogAuditWithUserId(user.Id, "Revoked all sessions for user") c.RemoveSessionCookie(w, r) if c.Err != nil { return } ldapInterface := einterfaces.GetLdapInterface() if ldapInterface == nil { c.Err = model.NewLocAppError("emailToLdap", "api.user.email_to_ldap.not_available.app_error", nil, "") c.Err.StatusCode = http.StatusNotImplemented return } if err := ldapInterface.SwitchToLdap(user.Id, ldapId, ldapPassword); err != nil { c.LogAuditWithUserId(user.Id, "fail - ldap switch failed") c.Err = err return } go func() { if err := app.SendSignInChangeEmail(user.Email, "AD/LDAP", user.Locale, c.GetSiteURL()); err != nil { l4g.Error(err.Error()) } }() m := map[string]string{} m["follow_link"] = "/login?extra=signin_change" c.LogAudit("success") w.Write([]byte(model.MapToJson(m))) } func ldapToEmail(c *Context, w http.ResponseWriter, r *http.Request) { props := model.MapFromJson(r.Body) email := props["email"] if len(email) == 0 { c.SetInvalidParam("ldapToEmail", "email") return } emailPassword := props["email_password"] if err := utils.IsPasswordValid(emailPassword); err != nil { c.Err = err return } ldapPassword := props["ldap_password"] if len(ldapPassword) == 0 { c.SetInvalidParam("ldapToEmail", "ldap_password") return } token := props["token"] c.LogAudit("attempt") var user *model.User var err *model.AppError if user, err = app.GetUserByEmail(email); err != nil { c.LogAudit("fail - couldn't get user") c.Err = err return } if user.AuthService != model.USER_AUTH_SERVICE_LDAP { c.Err = model.NewLocAppError("ldapToEmail", "api.user.ldap_to_email.not_ldap_account.app_error", nil, "") return } ldapInterface := einterfaces.GetLdapInterface() if ldapInterface == nil || user.AuthData == nil { c.Err = model.NewLocAppError("ldapToEmail", "api.user.ldap_to_email.not_available.app_error", nil, "") c.Err.StatusCode = http.StatusNotImplemented return } if err := ldapInterface.CheckPassword(*user.AuthData, ldapPassword); err != nil { c.LogAuditWithUserId(user.Id, "fail - ldap authentication failed") c.Err = err return } if err := app.CheckUserMfa(user, token); err != nil { c.LogAuditWithUserId(user.Id, "fail - mfa token failed") c.Err = err return } if err := app.UpdatePassword(user, emailPassword); err != nil { c.LogAudit("fail - database issue") c.Err = err return } if err := app.RevokeAllSessions(user.Id); err != nil { c.Err = err return } c.LogAuditWithUserId(user.Id, "Revoked all sessions for user") c.RemoveSessionCookie(w, r) if c.Err != nil { return } go func() { if err := app.SendSignInChangeEmail(user.Email, c.T("api.templates.signin_change_email.body.method_email"), user.Locale, c.GetSiteURL()); err != nil { l4g.Error(err.Error()) } }() m := map[string]string{} m["follow_link"] = "/login?extra=signin_change" c.LogAudit("success") w.Write([]byte(model.MapToJson(m))) } func verifyEmail(c *Context, w http.ResponseWriter, r *http.Request) { props := model.MapFromJson(r.Body) userId := props["uid"] if len(userId) != 26 { c.SetInvalidParam("verifyEmail", "uid") return } hashedId := props["hid"] if len(hashedId) == 0 { c.SetInvalidParam("verifyEmail", "hid") return } if model.ComparePassword(hashedId, userId+utils.Cfg.EmailSettings.InviteSalt) { if c.Err = app.VerifyUserEmail(userId); c.Err != nil { return } else { c.LogAudit("Email Verified") return } } c.Err = model.NewLocAppError("verifyEmail", "api.user.verify_email.bad_link.app_error", nil, "") c.Err.StatusCode = http.StatusBadRequest } func resendVerification(c *Context, w http.ResponseWriter, r *http.Request) { props := model.MapFromJson(r.Body) email := props["email"] if len(email) == 0 { c.SetInvalidParam("resendVerification", "email") return } if user, error := app.GetUserForLogin(email, false); error != nil { c.Err = error return } else { if _, err := app.GetStatus(user.Id); err != nil { go app.SendVerifyEmail(user.Id, user.Email, user.Locale, c.GetSiteURL()) } else { go app.SendEmailChangeVerifyEmail(user.Id, user.Email, user.Locale, c.GetSiteURL()) } } } func generateMfaSecret(c *Context, w http.ResponseWriter, r *http.Request) { var user *model.User var err *model.AppError if user, err = app.GetUser(c.Session.UserId); err != nil { c.Err = err return } mfaInterface := einterfaces.GetMfaInterface() if mfaInterface == nil { c.Err = model.NewLocAppError("generateMfaSecret", "api.user.generate_mfa_qr.not_available.app_error", nil, "") c.Err.StatusCode = http.StatusNotImplemented return } secret, img, err := mfaInterface.GenerateSecret(user) if err != nil { c.Err = err return } resp := map[string]string{} resp["qr_code"] = b64.StdEncoding.EncodeToString(img) resp["secret"] = secret w.Header().Set("Cache-Control", "no-cache") w.Header().Set("Pragma", "no-cache") w.Header().Set("Expires", "0") w.Write([]byte(model.MapToJson(resp))) } func updateMfa(c *Context, w http.ResponseWriter, r *http.Request) { props := model.StringInterfaceFromJson(r.Body) activate, ok := props["activate"].(bool) if !ok { c.SetInvalidParam("updateMfa", "activate") return } token := "" if activate { token = props["token"].(string) if len(token) == 0 { c.SetInvalidParam("updateMfa", "token") return } } c.LogAudit("attempt") if activate { if err := app.ActivateMfa(c.Session.UserId, token); err != nil { c.Err = err return } c.LogAudit("success - activated") } else { if err := app.DeactivateMfa(c.Session.UserId); err != nil { c.Err = err return } c.LogAudit("success - deactivated") } go func() { var user *model.User var err *model.AppError if user, err = app.GetUser(c.Session.UserId); err != nil { l4g.Warn(err.Error()) return } if err := app.SendMfaChangeEmail(user.Email, activate, user.Locale, c.GetSiteURL()); err != nil { l4g.Error(err.Error()) } }() rdata := map[string]string{} rdata["status"] = "ok" w.Write([]byte(model.MapToJson(rdata))) } 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{} rdata["mfa_required"] = "false" w.Write([]byte(model.MapToJson(rdata))) return } props := model.MapFromJson(r.Body) loginId := props["login_id"] if len(loginId) == 0 { c.SetInvalidParam("checkMfa", "login_id") return } rdata := map[string]string{} if user, err := app.GetUserForLogin(loginId, false); err != nil { rdata["mfa_required"] = "false" } else { rdata["mfa_required"] = strconv.FormatBool(user.MfaActive) } w.Write([]byte(model.MapToJson(rdata))) } func loginWithSaml(c *Context, w http.ResponseWriter, r *http.Request) { samlInterface := einterfaces.GetSamlInterface() if samlInterface == nil { c.Err = model.NewLocAppError("loginWithSaml", "api.user.saml.not_available.app_error", nil, "") c.Err.StatusCode = http.StatusFound return } teamId, err := getTeamIdFromQuery(r.URL.Query()) if err != nil { c.Err = err return } action := r.URL.Query().Get("action") redirectTo := r.URL.Query().Get("redirect_to") relayProps := map[string]string{} relayState := "" if len(action) != 0 { relayProps["team_id"] = teamId relayProps["action"] = action if action == model.OAUTH_ACTION_EMAIL_TO_SSO { relayProps["email"] = r.URL.Query().Get("email") } } if len(redirectTo) != 0 { relayProps["redirect_to"] = redirectTo } if len(relayProps) > 0 { relayState = b64.StdEncoding.EncodeToString([]byte(model.MapToJson(relayProps))) } if data, err := samlInterface.BuildRequest(relayState); err != nil { c.Err = err return } else { w.Header().Set("Content-Type", "application/x-www-form-urlencoded") http.Redirect(w, r, data.URL, http.StatusFound) } } func completeSaml(c *Context, w http.ResponseWriter, r *http.Request) { samlInterface := einterfaces.GetSamlInterface() if samlInterface == nil { c.Err = model.NewLocAppError("completeSaml", "api.user.saml.not_available.app_error", nil, "") c.Err.StatusCode = http.StatusFound return } //Validate that the user is with SAML and all that encodedXML := r.FormValue("SAMLResponse") relayState := r.FormValue("RelayState") relayProps := make(map[string]string) if len(relayState) > 0 { stateStr := "" if b, err := b64.StdEncoding.DecodeString(relayState); err != nil { c.Err = model.NewLocAppError("completeSaml", "api.user.authorize_oauth_user.invalid_state.app_error", nil, err.Error()) c.Err.StatusCode = http.StatusFound return } else { stateStr = string(b) } relayProps = model.MapFromJson(strings.NewReader(stateStr)) } if user, err := samlInterface.DoLogin(encodedXML, relayProps); err != nil { c.Err = err c.Err.StatusCode = http.StatusFound return } else { if err := app.CheckUserAdditionalAuthenticationCriteria(user, ""); err != nil { c.Err = err c.Err.StatusCode = http.StatusFound return } action := relayProps["action"] switch action { case model.OAUTH_ACTION_SIGNUP: teamId := relayProps["team_id"] if len(teamId) > 0 { go app.AddDirectChannels(teamId, user) } break case model.OAUTH_ACTION_EMAIL_TO_SSO: if err := app.RevokeAllSessions(user.Id); err != nil { c.Err = err return } c.LogAuditWithUserId(user.Id, "Revoked all sessions for user") go func() { if err := app.SendSignInChangeEmail(user.Email, strings.Title(model.USER_AUTH_SERVICE_SAML)+" SSO", user.Locale, c.GetSiteURL()); err != nil { l4g.Error(err.Error()) } }() break } doLogin(c, w, r, user, "") if c.Err != nil { return } if val, ok := relayProps["redirect_to"]; ok { http.Redirect(w, r, c.GetSiteURL()+val, http.StatusFound) return } if action == "mobile" { w.Write([]byte("")) } else { http.Redirect(w, r, app.GetProtocol(r)+"://"+r.Host, http.StatusFound) } } } func userTyping(req *model.WebSocketRequest) (map[string]interface{}, *model.AppError) { var ok bool var channelId string if channelId, ok = req.Data["channel_id"].(string); !ok || len(channelId) != 26 { return nil, NewInvalidWebSocketParamError(req.Action, "channel_id") } var parentId string if parentId, ok = req.Data["parent_id"].(string); !ok { parentId = "" } omitUsers := make(map[string]bool, 1) omitUsers[req.Session.UserId] = true event := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_TYPING, "", channelId, "", omitUsers) event.Add("parent_id", parentId) event.Add("user_id", req.Session.UserId) go app.Publish(event) return nil, nil } func sanitizeProfile(c *Context, user *model.User) *model.User { options := utils.Cfg.GetSanitizeOptions() if app.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) { options["email"] = true options["fullname"] = true options["authservice"] = true } user.SanitizeProfile(options) return user } func searchUsers(c *Context, w http.ResponseWriter, r *http.Request) { props := model.UserSearchFromJson(r.Body) if props == nil { c.SetInvalidParam("searchUsers", "") return } if len(props.Term) == 0 { c.SetInvalidParam("searchUsers", "term") return } if props.InChannelId != "" && !app.SessionHasPermissionToChannel(c.Session, props.InChannelId, model.PERMISSION_READ_CHANNEL) { c.SetPermissionError(model.PERMISSION_READ_CHANNEL) return } if props.NotInChannelId != "" && !app.SessionHasPermissionToChannel(c.Session, props.NotInChannelId, model.PERMISSION_READ_CHANNEL) { c.SetPermissionError(model.PERMISSION_READ_CHANNEL) return } searchOptions := map[string]bool{} searchOptions[store.USER_SEARCH_OPTION_ALLOW_INACTIVE] = props.AllowInactive if !app.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) { hideFullName := !utils.Cfg.PrivacySettings.ShowFullName hideEmail := !utils.Cfg.PrivacySettings.ShowEmailAddress if hideFullName && hideEmail { searchOptions[store.USER_SEARCH_OPTION_NAMES_ONLY_NO_FULL_NAME] = true } else if hideFullName { searchOptions[store.USER_SEARCH_OPTION_ALL_NO_FULL_NAME] = true } else if hideEmail { searchOptions[store.USER_SEARCH_OPTION_NAMES_ONLY] = true } } var profiles []*model.User var err *model.AppError if props.InChannelId != "" { profiles, err = app.SearchUsersInChannel(props.InChannelId, props.Term, searchOptions, c.IsSystemAdmin()) } else if props.NotInChannelId != "" { profiles, err = app.SearchUsersNotInChannel(props.TeamId, props.NotInChannelId, props.Term, searchOptions, c.IsSystemAdmin()) } else { profiles, err = app.SearchUsersInTeam(props.TeamId, props.Term, searchOptions, c.IsSystemAdmin()) } if err != nil { c.Err = err return } w.Write([]byte(model.UserListToJson(profiles))) } func getProfilesByIds(c *Context, w http.ResponseWriter, r *http.Request) { userIds := model.ArrayFromJson(r.Body) if len(userIds) == 0 { c.SetInvalidParam("getProfilesByIds", "user_ids") return } if profiles, err := app.GetUsersByIds(userIds, c.IsSystemAdmin()); err != nil { c.Err = err return } else { profileMap := map[string]*model.User{} for _, p := range profiles { profileMap[p.Id] = p } w.Write([]byte(model.UserMapToJson(profileMap))) } } func autocompleteUsersInChannel(c *Context, w http.ResponseWriter, r *http.Request) { params := mux.Vars(r) channelId := params["channel_id"] teamId := params["team_id"] term := r.URL.Query().Get("term") if c.Session.GetTeamByTeamId(teamId) == nil { if !app.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) { return } } if !app.SessionHasPermissionToChannel(c.Session, channelId, model.PERMISSION_READ_CHANNEL) { c.SetPermissionError(model.PERMISSION_READ_CHANNEL) return } searchOptions := map[string]bool{} hideFullName := !utils.Cfg.PrivacySettings.ShowFullName if hideFullName && !app.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) { searchOptions[store.USER_SEARCH_OPTION_NAMES_ONLY_NO_FULL_NAME] = true } else { searchOptions[store.USER_SEARCH_OPTION_NAMES_ONLY] = true } autocomplete, err := app.AutocompleteUsersInChannel(teamId, channelId, term, searchOptions, c.IsSystemAdmin()) if err != nil { c.Err = err return } w.Write([]byte(autocomplete.ToJson())) } func autocompleteUsersInTeam(c *Context, w http.ResponseWriter, r *http.Request) { params := mux.Vars(r) teamId := params["team_id"] term := r.URL.Query().Get("term") if c.Session.GetTeamByTeamId(teamId) == nil { if !app.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) { return } } searchOptions := map[string]bool{} hideFullName := !utils.Cfg.PrivacySettings.ShowFullName if hideFullName && !app.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) { searchOptions[store.USER_SEARCH_OPTION_NAMES_ONLY_NO_FULL_NAME] = true } else { searchOptions[store.USER_SEARCH_OPTION_NAMES_ONLY] = true } autocomplete, err := app.AutocompleteUsersInTeam(teamId, term, searchOptions, c.IsSystemAdmin()) if err != nil { c.Err = err return } w.Write([]byte(autocomplete.ToJson())) } func autocompleteUsers(c *Context, w http.ResponseWriter, r *http.Request) { term := r.URL.Query().Get("term") searchOptions := map[string]bool{} hideFullName := !utils.Cfg.PrivacySettings.ShowFullName if hideFullName && !app.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) { searchOptions[store.USER_SEARCH_OPTION_NAMES_ONLY_NO_FULL_NAME] = true } else { searchOptions[store.USER_SEARCH_OPTION_NAMES_ONLY] = true } var profiles []*model.User var err *model.AppError if profiles, err = app.SearchUsersInTeam("", term, searchOptions, c.IsSystemAdmin()); err != nil { c.Err = err return } w.Write([]byte(model.UserListToJson(profiles))) }