From 8b0eedbbcd47ba09142c72a71969840aa6e121d2 Mon Sep 17 00:00:00 2001 From: Joram Wilander Date: Mon, 13 Mar 2017 08:29:56 -0400 Subject: Implement PUT /users/{user_id}/mfa endpoint for APIv4 (#5743) --- api4/user.go | 40 ++++++++++++++++++++++++++++++++++++++++ api4/user_test.go | 43 +++++++++++++++++++++++++++++++++++++++++++ app/user.go | 28 ++++++++++++++++++++++++++++ model/client4.go | 16 ++++++++++++++++ 4 files changed, 127 insertions(+) diff --git a/api4/user.go b/api4/user.go index b0063c657..7b8bfe65e 100644 --- a/api4/user.go +++ b/api4/user.go @@ -28,6 +28,7 @@ func InitUser() { BaseRoutes.User.Handle("/image", ApiSessionRequired(setProfileImage)).Methods("POST") BaseRoutes.User.Handle("", ApiSessionRequired(updateUser)).Methods("PUT") BaseRoutes.User.Handle("/patch", ApiSessionRequired(patchUser)).Methods("PUT") + BaseRoutes.User.Handle("/mfa", ApiSessionRequired(updateUserMfa)).Methods("PUT") BaseRoutes.User.Handle("", ApiSessionRequired(deleteUser)).Methods("DELETE") BaseRoutes.User.Handle("/roles", ApiSessionRequired(updateUserRoles)).Methods("PUT") BaseRoutes.User.Handle("/password", ApiSessionRequired(updatePassword)).Methods("PUT") @@ -493,6 +494,45 @@ func updateUserRoles(c *Context, w http.ResponseWriter, r *http.Request) { ReturnStatusOK(w) } +func updateUserMfa(c *Context, w http.ResponseWriter, r *http.Request) { + c.RequireUserId() + if c.Err != nil { + return + } + + if !app.SessionHasPermissionToUser(c.Session, c.Params.UserId) { + c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS) + return + } + + props := model.StringInterfaceFromJson(r.Body) + + activate, ok := props["activate"].(bool) + if !ok { + c.SetInvalidParam("activate") + return + } + + code := "" + if activate { + code, ok = props["code"].(string) + if !ok || len(code) == 0 { + c.SetInvalidParam("code") + return + } + } + + c.LogAudit("attempt") + + if err := app.UpdateMfa(activate, c.Params.UserId, code, c.GetSiteURL()); err != nil { + c.Err = err + return + } + + c.LogAudit("success - mfa updated") + ReturnStatusOK(w) +} + func updatePassword(c *Context, w http.ResponseWriter, r *http.Request) { c.RequireUserId() if c.Err != nil { diff --git a/api4/user_test.go b/api4/user_test.go index fd555fe42..87e1dd64f 100644 --- a/api4/user_test.go +++ b/api4/user_test.go @@ -11,6 +11,7 @@ import ( "github.com/mattermost/platform/app" "github.com/mattermost/platform/model" + "github.com/mattermost/platform/store" "github.com/mattermost/platform/utils" ) @@ -803,6 +804,48 @@ func TestGetUsersNotInChannel(t *testing.T) { CheckNoError(t, resp) } +func TestUpdateUserMfa(t *testing.T) { + th := Setup().InitBasic().InitSystemAdmin() + defer TearDown() + Client := th.Client + + isLicensed := utils.IsLicensed + license := utils.License + enableMfa := *utils.Cfg.ServiceSettings.EnableMultifactorAuthentication + defer func() { + utils.IsLicensed = isLicensed + utils.License = license + *utils.Cfg.ServiceSettings.EnableMultifactorAuthentication = enableMfa + }() + utils.IsLicensed = true + utils.License = &model.License{Features: &model.Features{}} + utils.License.Features.SetDefaults() + + team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + rteam, _ := Client.CreateTeam(&team) + + user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "passwd1"} + ruser, _ := Client.CreateUser(&user) + LinkUserToTeam(ruser, rteam) + store.Must(app.Srv.Store.User().VerifyEmail(ruser.Id)) + + Client.Logout() + _, resp := Client.UpdateUserMfa(ruser.Id, "12334", true) + CheckUnauthorizedStatus(t, resp) + + Client.Login(user.Email, user.Password) + _, resp = Client.UpdateUserMfa("fail", "56789", false) + CheckBadRequestStatus(t, resp) + + _, resp = Client.UpdateUserMfa(ruser.Id, "", true) + CheckErrorMessage(t, resp, "api.context.invalid_body_param.app_error") + + *utils.Cfg.ServiceSettings.EnableMultifactorAuthentication = true + + _, resp = Client.UpdateUserMfa(ruser.Id, "123456", false) + CheckNotImplementedStatus(t, resp) +} + func TestUpdateUserPassword(t *testing.T) { th := Setup().InitBasic().InitSystemAdmin() defer TearDown() diff --git a/app/user.go b/app/user.go index d01ce7a51..c872a7fa1 100644 --- a/app/user.go +++ b/app/user.go @@ -949,6 +949,34 @@ func UpdateUserNotifyProps(userId string, props map[string]string, siteURL strin return ruser, nil } +func UpdateMfa(activate bool, userId, token, siteUrl string) *model.AppError { + if activate { + if err := ActivateMfa(userId, token); err != nil { + return err + } + } else { + if err := DeactivateMfa(userId); err != nil { + return err + } + } + + go func() { + var user *model.User + var err *model.AppError + + if user, err = GetUser(userId); err != nil { + l4g.Error(err.Error()) + return + } + + if err := SendMfaChangeEmail(user.Email, activate, user.Locale, siteUrl); err != nil { + l4g.Error(err.Error()) + } + }() + + return nil +} + func UpdatePasswordByUserIdSendEmail(userId, newPassword, method, siteURL string) *model.AppError { var user *model.User var err *model.AppError diff --git a/model/client4.go b/model/client4.go index 9a1d6e1cf..70466ec59 100644 --- a/model/client4.go +++ b/model/client4.go @@ -457,6 +457,22 @@ func (c *Client4) PatchUser(userId string, patch *UserPatch) (*User, *Response) } } +// UpdateUserMfa activates multi-factor authentication for a user if activate +// is true and a valid code is provided. If activate is false, then code is not +// required and multi-factor authentication is disabled for the user. +func (c *Client4) UpdateUserMfa(userId, code string, activate bool) (bool, *Response) { + requestBody := make(map[string]interface{}) + requestBody["activate"] = activate + requestBody["code"] = code + + if r, err := c.DoApiPut(c.GetUserRoute(userId)+"/mfa", StringInterfaceToJson(requestBody)); err != nil { + return false, &Response{StatusCode: r.StatusCode, Error: err} + } else { + defer closeBody(r) + return CheckStatusOK(r), BuildResponse(r) + } +} + // UpdateUserPassword updates a user's password. Must be logged in as the user or be a system administrator. func (c *Client4) UpdateUserPassword(userId, currentPassword, newPassword string) (bool, *Response) { requestBody := map[string]string{"current_password": currentPassword, "new_password": newPassword} -- cgit v1.2.3-1-g7c22