From 87989b8afd4666a72940389db716b6500d0a9ec3 Mon Sep 17 00:00:00 2001 From: Harrison Healey Date: Tue, 3 May 2016 14:10:36 -0400 Subject: PLT-2258 Unified login screen and related APIs (#2820) * Unified login screen and related APIs * Refactored login API call to be less convoluted * Removed LDAP login prompt from invite process * Fixed existing LDAP users being able to log in if LDAP was configured, but disabled * Gofmt * Future proofed login API * Updated login APIs based on feedback * Added additional auditing to login API * Actually removed loginById --- api/apitestlib.go | 6 +- api/authentication.go | 57 ++++++++++ api/channel_benchmark_test.go | 2 +- api/channel_test.go | 14 +-- api/command_loadtest.go | 2 +- api/file_test.go | 2 +- api/post_test.go | 6 +- api/team_test.go | 22 ++-- api/user.go | 236 ++++++++++++------------------------------ api/user_test.go | 113 +++++++++++++------- 10 files changed, 224 insertions(+), 236 deletions(-) (limited to 'api') diff --git a/api/apitestlib.go b/api/apitestlib.go index d82dc30be..a7c2a9406 100644 --- a/api/apitestlib.go +++ b/api/apitestlib.go @@ -200,19 +200,19 @@ func (me *TestHelper) CreatePost(client *model.Client, channel *model.Channel) * func (me *TestHelper) LoginBasic() { utils.DisableDebugLogForTest() - me.BasicClient.Must(me.BasicClient.LoginByEmail(me.BasicTeam.Name, me.BasicUser.Email, me.BasicUser.Password)) + me.BasicClient.Must(me.BasicClient.Login(me.BasicUser.Email, me.BasicUser.Password)) utils.EnableDebugLogForTest() } func (me *TestHelper) LoginBasic2() { utils.DisableDebugLogForTest() - me.BasicClient.Must(me.BasicClient.LoginByEmail(me.BasicTeam.Name, me.BasicUser2.Email, me.BasicUser2.Password)) + me.BasicClient.Must(me.BasicClient.Login(me.BasicUser2.Email, me.BasicUser2.Password)) utils.EnableDebugLogForTest() } func (me *TestHelper) LoginSystemAdmin() { utils.DisableDebugLogForTest() - me.SystemAdminClient.Must(me.SystemAdminClient.LoginByEmail(me.SystemAdminTeam.Name, me.SystemAdminUser.Email, me.SystemAdminUser.Password)) + me.SystemAdminClient.Must(me.SystemAdminClient.Login(me.SystemAdminUser.Email, me.SystemAdminUser.Password)) utils.EnableDebugLogForTest() } diff --git a/api/authentication.go b/api/authentication.go index bab83a720..10ed578e1 100644 --- a/api/authentication.go +++ b/api/authentication.go @@ -7,6 +7,8 @@ import ( "github.com/mattermost/platform/einterfaces" "github.com/mattermost/platform/model" "github.com/mattermost/platform/utils" + + "net/http" ) func checkPasswordAndAllCriteria(user *model.User, password string, mfaToken string) *model.AppError { @@ -37,6 +39,32 @@ func checkUserPassword(user *model.User, password string) *model.AppError { } } +func checkLdapUserPasswordAndAllCriteria(ldapId, password, mfaToken string) (*model.User, *model.AppError) { + ldapInterface := einterfaces.GetLdapInterface() + + if ldapInterface == nil { + err := model.NewLocAppError("doLdapAuthentication", "api.user.login_ldap.not_available.app_error", nil, "") + err.StatusCode = http.StatusNotImplemented + return nil, err + } + + var user *model.User + if ldapUser, err := ldapInterface.DoLogin(ldapId, password); err != nil { + err.StatusCode = http.StatusUnauthorized + return nil, err + } else { + user = ldapUser + } + + if err := checkUserAdditionalAuthenticationCriteria(user, mfaToken); err != nil { + err.StatusCode = http.StatusUnauthorized + return user, err + } + + // user successfully authenticated + return user, nil +} + func checkUserAdditionalAuthenticationCriteria(user *model.User, mfaToken string) *model.AppError { if err := checkUserMfa(user, mfaToken); err != nil { return err @@ -97,3 +125,32 @@ func checkUserNotDisabled(user *model.User) *model.AppError { } return nil } + +func authenticateUser(user *model.User, password, mfaToken string) (*model.User, *model.AppError) { + ldapAvailable := *utils.Cfg.LdapSettings.Enable && einterfaces.GetLdapInterface() != nil + + if user.AuthService == model.USER_AUTH_SERVICE_LDAP { + if !ldapAvailable { + err := model.NewLocAppError("login", "api.user.login_ldap.not_available.app_error", nil, "") + err.StatusCode = http.StatusNotImplemented + return user, err + } else if ldapUser, err := checkLdapUserPasswordAndAllCriteria(user.AuthData, password, mfaToken); err != nil { + err.StatusCode = http.StatusUnauthorized + return user, err + } else { + // slightly redundant to get the user again, but we need to get it from the LDAP server + return ldapUser, nil + } + } else if user.AuthService != "" { + err := model.NewLocAppError("login", "api.user.login.use_auth_service.app_error", map[string]interface{}{"AuthService": user.AuthService}, "") + err.StatusCode = http.StatusBadRequest + return user, err + } else { + if err := checkPasswordAndAllCriteria(user, password, mfaToken); err != nil { + err.StatusCode = http.StatusUnauthorized + return user, err + } else { + return user, nil + } + } +} diff --git a/api/channel_benchmark_test.go b/api/channel_benchmark_test.go index 3e7c2882c..569c2dcc0 100644 --- a/api/channel_benchmark_test.go +++ b/api/channel_benchmark_test.go @@ -131,7 +131,7 @@ func BenchmarkJoinChannel(b *testing.B) { user = th.BasicClient.Must(th.BasicClient.CreateUser(user, "")).Data.(*model.User) LinkUserToTeam(user, th.BasicTeam) store.Must(Srv.Store.User().VerifyEmail(user.Id)) - th.BasicClient.LoginByEmail(th.BasicTeam.Name, user.Email, "pwd") + th.BasicClient.Login(user.Email, "pwd") // Benchmark Start b.ResetTimer() diff --git a/api/channel_test.go b/api/channel_test.go index 4c8462e6a..31b201346 100644 --- a/api/channel_test.go +++ b/api/channel_test.go @@ -169,7 +169,7 @@ func TestUpdateChannel(t *testing.T) { } } - Client.LoginByEmail(team.Name, user2.Email, user2.Password) + Client.Login(user2.Email, user2.Password) if _, err := Client.UpdateChannel(upChannel1); err == nil { t.Fatal("Standard User should have failed to update") @@ -432,7 +432,7 @@ func TestJoinChannelById(t *testing.T) { user3 := th.CreateUser(th.BasicClient) LinkUserToTeam(user3, team) - Client.LoginByEmail(team.Name, user3.Email, "pwd") + Client.Login(user3.Email, "pwd") if _, err := Client.JoinChannel(rchannel.Id); err == nil { t.Fatal("shoudn't be able to join direct channel") @@ -462,7 +462,7 @@ func TestJoinChannelByName(t *testing.T) { user3 := th.CreateUser(th.BasicClient) LinkUserToTeam(user3, team) - Client.LoginByEmail(team.Name, user3.Email, "pwd") + Client.Login(user3.Email, "pwd") if _, err := Client.JoinChannelByName(rchannel.Name); err == nil { t.Fatal("shoudn't be able to join direct channel") @@ -518,7 +518,7 @@ func TestDeleteChannel(t *testing.T) { Client.AddChannelMember(channelMadeByCA.Id, userTeamAdmin.Id) - Client.LoginByEmail(team.Name, userTeamAdmin.Email, "pwd") + Client.Login(userTeamAdmin.Email, "pwd") channel1 := &model.Channel{DisplayName: "A Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) @@ -541,7 +541,7 @@ func TestDeleteChannel(t *testing.T) { userStd := th.CreateUser(th.BasicClient) LinkUserToTeam(userStd, team) - Client.LoginByEmail(team.Name, userStd.Email, userStd.Password) + Client.Login(userStd.Email, userStd.Password) if _, err := Client.JoinChannel(channel1.Id); err == nil { t.Fatal("should have failed to join deleted channel") @@ -606,7 +606,7 @@ func TestGetChannelExtraInfo(t *testing.T) { Client2.SetTeamId(team.Id) store.Must(Srv.Store.User().VerifyEmail(user2.Id)) - Client2.LoginByEmail(team.Name, user2.Email, "pwd") + Client2.Login(user2.Email, "pwd") Client2.Must(Client2.JoinChannel(channel1.Id)) if cache_result, err := Client.GetChannelExtraInfo(channel1.Id, -1, currentEtag); err != nil { @@ -775,7 +775,7 @@ func TestRemoveChannelMember(t *testing.T) { channel2 := &model.Channel{DisplayName: "A Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} channel2 = Client.Must(Client.CreateChannel(channel2)).Data.(*model.Channel) - Client.LoginByEmail(team.Name, userStd.Email, userStd.Password) + Client.Login(userStd.Email, userStd.Password) if _, err := Client.RemoveChannelMember(channel2.Id, userStd.Id); err == nil { t.Fatal("Should have errored, user not channel admin") diff --git a/api/command_loadtest.go b/api/command_loadtest.go index b76187960..26306440f 100644 --- a/api/command_loadtest.go +++ b/api/command_loadtest.go @@ -164,7 +164,7 @@ func (me *LoadTestProvider) SetupCommand(c *Context, channelId string, message s if err := CreateBasicUser(client); err != nil { return &model.CommandResponse{Text: "Failed to create testing environment", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} } - client.LoginByEmail(BTEST_TEAM_NAME, BTEST_USER_EMAIL, BTEST_USER_PASSWORD) + client.Login(BTEST_USER_EMAIL, BTEST_USER_PASSWORD) environment, err := CreateTestEnvironmentWithTeams( client, utils.Range{numTeams, numTeams}, diff --git a/api/file_test.go b/api/file_test.go index dd4a8520b..015048ec4 100644 --- a/api/file_test.go +++ b/api/file_test.go @@ -217,7 +217,7 @@ func TestGetFile(t *testing.T) { data := model.MapToJson(newProps) hash := model.HashPassword(fmt.Sprintf("%v:%v", data, utils.Cfg.FileSettings.PublicLinkSalt)) - Client.LoginByEmail(team2.Name, user2.Email, "pwd") + Client.Login(user2.Email, "pwd") Client.SetTeamId(team2.Id) if _, downErr := Client.GetFile(filenames[0]+"?d="+url.QueryEscape(data)+"&h="+url.QueryEscape(hash)+"&t="+team.Id, false); downErr != nil { diff --git a/api/post_test.go b/api/post_test.go index 8556d66b6..529cc6e4d 100644 --- a/api/post_test.go +++ b/api/post_test.go @@ -91,7 +91,7 @@ func TestCreatePost(t *testing.T) { t.Fatal("Should have been forbidden") } - Client.LoginByEmail(team2.Name, user3.Email, user3.Password) + Client.Login(user3.Email, user3.Password) Client.SetTeamId(team2.Id) channel3 := th.CreateChannel(Client, team2) @@ -512,7 +512,7 @@ func TestSearchPostsFromUser(t *testing.T) { t.Fatalf("wrong number of posts returned %v", len(result.Order)) } - Client.LoginByEmail(team.Name, user3.Email, user3.Password) + Client.Login(user3.Email, user3.Password) // wait for the join/leave messages to be created for user3 since they're done asynchronously time.Sleep(100 * time.Millisecond) @@ -741,7 +741,7 @@ func TestGetOutOfChannelMentions(t *testing.T) { user4 := th.CreateUser(Client) LinkUserToTeam(user4, team2) - Client.Must(Client.LoginByEmail(team2.Name, user4.Email, user4.Password)) + Client.Must(Client.Login(user4.Email, user4.Password)) Client.SetTeamId(team2.Id) channel2 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team2.Id} diff --git a/api/team_test.go b/api/team_test.go index 161c7e620..a58260fd2 100644 --- a/api/team_test.go +++ b/api/team_test.go @@ -90,7 +90,7 @@ func TestCreateTeam(t *testing.T) { LinkUserToTeam(user, rteam.Data.(*model.Team)) store.Must(Srv.Store.User().VerifyEmail(user.Id)) - Client.LoginByEmail(team.Name, user.Email, "pwd") + Client.Login(user.Email, "pwd") Client.SetTeamId(rteam.Data.(*model.Team).Id) c1 := Client.Must(Client.GetChannels("")).Data.(*model.ChannelList) @@ -209,7 +209,7 @@ func TestAddUserToTeamFromInvite(t *testing.T) { user2 := th.CreateUser(th.BasicClient) Client.Must(Client.Logout()) - Client.Must(Client.LoginByEmail("", user2.Email, user2.Password)) + Client.Must(Client.Login(user2.Email, user2.Password)) if result, err := th.BasicClient.AddUserToTeamFromInvite("", "", rteam.InviteId); err != nil { t.Fatal(err) @@ -234,7 +234,7 @@ func TestGetAllTeams(t *testing.T) { LinkUserToTeam(user, team) store.Must(Srv.Store.User().VerifyEmail(user.Id)) - Client.LoginByEmail(team.Name, user.Email, "pwd") + Client.Login(user.Email, "pwd") Client.SetTeamId(team.Id) if r1, err := Client.GetAllTeams(); err != nil { @@ -254,7 +254,7 @@ func TestGetAllTeams(t *testing.T) { c.IpAddress = "cmd_line" UpdateRoles(c, user, model.ROLE_SYSTEM_ADMIN) - Client.LoginByEmail(team.Name, user.Email, "pwd") + Client.Login(user.Email, "pwd") Client.SetTeamId(team.Id) if r1, err := Client.GetAllTeams(); err != nil { @@ -283,7 +283,7 @@ func TestGetAllTeamListings(t *testing.T) { LinkUserToTeam(user, team) store.Must(Srv.Store.User().VerifyEmail(user.Id)) - Client.LoginByEmail(team.Name, user.Email, "pwd") + Client.Login(user.Email, "pwd") Client.SetTeamId(team.Id) if r1, err := Client.GetAllTeamListings(); err != nil { @@ -303,7 +303,7 @@ func TestGetAllTeamListings(t *testing.T) { c.IpAddress = "cmd_line" UpdateRoles(c, user, model.ROLE_SYSTEM_ADMIN) - Client.LoginByEmail(team.Name, user.Email, "pwd") + Client.Login(user.Email, "pwd") Client.SetTeamId(team.Id) if r1, err := Client.GetAllTeams(); err != nil { @@ -332,7 +332,7 @@ func TestTeamPermDelete(t *testing.T) { LinkUserToTeam(user1, team) store.Must(Srv.Store.User().VerifyEmail(user1.Id)) - Client.LoginByEmail(team.Name, user1.Email, "pwd") + Client.Login(user1.Email, "pwd") Client.SetTeamId(team.Id) channel1 := &model.Channel{DisplayName: "TestGetPosts", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} @@ -375,7 +375,7 @@ func TestInviteMembers(t *testing.T) { LinkUserToTeam(user, team) store.Must(Srv.Store.User().VerifyEmail(user.Id)) - Client.LoginByEmail(team.Name, user.Email, "pwd") + Client.Login(user.Email, "pwd") Client.SetTeamId(team.Id) invite := make(map[string]string) @@ -413,7 +413,7 @@ func TestUpdateTeamDisplayName(t *testing.T) { LinkUserToTeam(user2, team) store.Must(Srv.Store.User().VerifyEmail(user2.Id)) - Client.LoginByEmail(team.Name, user2.Email, "pwd") + Client.Login(user2.Email, "pwd") Client.SetTeamId(team.Id) vteam := &model.Team{DisplayName: team.DisplayName, Name: team.Name, Email: team.Email, Type: team.Type} @@ -422,7 +422,7 @@ func TestUpdateTeamDisplayName(t *testing.T) { t.Fatal("Should have errored, not admin") } - Client.LoginByEmail(team.Name, user.Email, "pwd") + Client.Login(user.Email, "pwd") vteam.DisplayName = "" if _, err := Client.UpdateTeam(vteam); err == nil { @@ -474,7 +474,7 @@ func TestGetMyTeam(t *testing.T) { LinkUserToTeam(ruser.Data.(*model.User), rteam.Data.(*model.Team)) store.Must(Srv.Store.User().VerifyEmail(ruser.Data.(*model.User).Id)) - Client.LoginByEmail(team.Name, user.Email, user.Password) + Client.Login(user.Email, user.Password) Client.SetTeamId(team.Id) if result, err := Client.GetMyTeam(""); err != nil { diff --git a/api/user.go b/api/user.go index d8e2e6623..aee4dab61 100644 --- a/api/user.go +++ b/api/user.go @@ -45,7 +45,6 @@ func InitUser() { BaseRoutes.Users.Handle("/reset_password", ApiAppHandler(resetPassword)).Methods("POST") BaseRoutes.Users.Handle("/login", ApiAppHandler(login)).Methods("POST") BaseRoutes.Users.Handle("/logout", ApiAppHandler(logout)).Methods("POST") - BaseRoutes.Users.Handle("/login_ldap", ApiAppHandler(loginLdap)).Methods("POST") BaseRoutes.Users.Handle("/revoke_session", ApiUserRequired(revokeSession)).Methods("POST") BaseRoutes.Users.Handle("/attach_device", ApiUserRequired(attachDeviceId)).Methods("POST") BaseRoutes.Users.Handle("/verify_email", ApiAppHandler(verifyEmail)).Methods("POST") @@ -333,7 +332,7 @@ func CreateOAuthUser(c *Context, w http.ResponseWriter, r *http.Request, service } } - Login(c, w, r, ruser, "") + doLogin(c, w, r, ruser, "") if c.Err != nil { return nil } @@ -431,114 +430,86 @@ func SendVerifyEmailAndForget(c *Context, userId, userEmail, siteURL string) { func login(c *Context, w http.ResponseWriter, r *http.Request) { props := model.MapFromJson(r.Body) - if len(props["password"]) == 0 { + id := props["id"] + loginId := props["login_id"] + password := props["password"] + mfaToken := props["token"] + deviceId := props["device_id"] + + if len(password) == 0 { c.Err = model.NewLocAppError("login", "api.user.login.blank_pwd.app_error", nil, "") c.Err.StatusCode = http.StatusBadRequest return } var user *model.User - if len(props["id"]) != 0 { - user = LoginById(c, w, r, props["id"], props["password"], props["token"], props["device_id"]) - } else if len(props["email"]) != 0 { - user = LoginByEmail(c, w, r, props["email"], props["name"], props["password"], props["token"], props["device_id"]) - } else if len(props["username"]) != 0 { - user = LoginByUsername(c, w, r, props["username"], props["name"], props["password"], props["token"], props["device_id"]) - } else { - c.Err = model.NewLocAppError("login", "api.user.login.not_provided.app_error", nil, "") - c.Err.StatusCode = http.StatusBadRequest - return - } + var err *model.AppError - if c.Err != nil { - return - } + if len(id) != 0 { + c.LogAuditWithUserId(id, "attempt") - if user != nil { - user.Sanitize(map[string]bool{}) - } else { - user = &model.User{} - } - w.Write([]byte(user.ToJson())) -} - -func doUserPasswordAuthenticationAndLogin(c *Context, w http.ResponseWriter, r *http.Request, user *model.User, password string, mfaToken string, deviceId string) bool { - c.LogAuditWithUserId(user.Id, "attempt") - if err := checkPasswordAndAllCriteria(user, password, mfaToken); err != nil { - c.LogAuditWithUserId(user.Id, "fail") - c.Err = err - c.Err.StatusCode = http.StatusUnauthorized - return false - } else { - Login(c, w, r, user, deviceId) - c.LogAuditWithUserId(user.Id, "success") - return true - } -} - -func LoginById(c *Context, w http.ResponseWriter, r *http.Request, userId, password, mfaToken, deviceId string) *model.User { - if result := <-Srv.Store.User().Get(userId); result.Err != nil { - c.Err = result.Err - return nil + if result := <-Srv.Store.User().Get(id); result.Err != nil { + c.LogAuditWithUserId(user.Id, "failure") + c.Err = result.Err + c.Err.StatusCode = http.StatusBadRequest + return + } else { + user = result.Data.(*model.User) + } } else { - user := result.Data.(*model.User) + c.LogAudit("attempt") - if len(user.AuthData) != 0 { - c.Err = model.NewLocAppError("LoginById", "api.user.login_by_email.sign_in.app_error", - map[string]interface{}{"AuthService": user.AuthService}, "") - return nil + if user, err = getUserForLogin(loginId); err != nil { + c.LogAudit("failure") + c.Err = err + return } - if doUserPasswordAuthenticationAndLogin(c, w, r, user, password, mfaToken, deviceId) { - return user - } + c.LogAuditWithUserId(user.Id, "attempt") } - return nil -} + // and then authenticate them + if user, err = authenticateUser(user, password, mfaToken); err != nil { + c.LogAuditWithUserId(user.Id, "failure") + c.Err = err + return + } -func LoginByEmail(c *Context, w http.ResponseWriter, r *http.Request, email, name, password, mfaToken, deviceId string) *model.User { - if result := <-Srv.Store.User().GetByEmail(email); result.Err != nil { - c.Err = result.Err - c.Err.StatusCode = http.StatusUnauthorized - return nil - } else { - user := result.Data.(*model.User) + c.LogAuditWithUserId(user.Id, "success") - if len(user.AuthData) != 0 { - c.Err = model.NewLocAppError("LoginByEmail", "api.user.login_by_email.sign_in.app_error", - map[string]interface{}{"AuthService": user.AuthService}, "") - return nil - } + doLogin(c, w, r, user, deviceId) - if doUserPasswordAuthenticationAndLogin(c, w, r, user, password, mfaToken, deviceId) { - return user - } - } + user.Sanitize(map[string]bool{}) - return nil + w.Write([]byte(user.ToJson())) } -func LoginByUsername(c *Context, w http.ResponseWriter, r *http.Request, username, name, password, mfaToken, deviceId string) *model.User { - if result := <-Srv.Store.User().GetByUsername(username); result.Err != nil { - c.Err = result.Err - c.Err.StatusCode = http.StatusUnauthorized - return nil - } else { - user := result.Data.(*model.User) +func getUserForLogin(loginId string) (*model.User, *model.AppError) { + ldapAvailable := *utils.Cfg.LdapSettings.Enable && einterfaces.GetLdapInterface() != nil - if len(user.AuthData) != 0 { - c.Err = model.NewLocAppError("LoginByUsername", "api.user.login_by_email.sign_in.app_error", - map[string]interface{}{"AuthService": user.AuthService}, "") - return nil + if result := <-Srv.Store.User().GetForLogin( + loginId, + *utils.Cfg.EmailSettings.EnableSignInWithUsername, + *utils.Cfg.EmailSettings.EnableSignInWithEmail, + ldapAvailable, + ); result.Err != nil { + + if !ldapAvailable { + // failed to find user and no LDAP server to fall back on + result.Err.StatusCode = http.StatusBadRequest + return nil, result.Err } - if doUserPasswordAuthenticationAndLogin(c, w, r, user, password, mfaToken, deviceId) { - return user + // fall back to LDAP server to see if we can find a user + if ldapUser, ldapErr := einterfaces.GetLdapInterface().GetUser(loginId); ldapErr != nil { + ldapErr.StatusCode = http.StatusBadRequest + return nil, ldapErr + } else { + return ldapUser, nil } + } else { + return result.Data.(*model.User), nil } - - return nil } func LoginByOAuth(c *Context, w http.ResponseWriter, r *http.Request, service string, userData io.Reader) *model.User { @@ -570,80 +541,13 @@ func LoginByOAuth(c *Context, w http.ResponseWriter, r *http.Request, service st return nil } else { user = result.Data.(*model.User) - Login(c, w, r, user, "") + doLogin(c, w, r, user, "") return user } } -func loginLdap(c *Context, w http.ResponseWriter, r *http.Request) { - if !*utils.Cfg.LdapSettings.Enable { - c.Err = model.NewLocAppError("loginLdap", "api.user.login_ldap.disabled.app_error", nil, "") - c.Err.StatusCode = http.StatusNotImplemented - return - } - - props := model.MapFromJson(r.Body) - - password := props["password"] - id := props["id"] - mfaToken := props["token"] - - if len(password) == 0 { - c.Err = model.NewLocAppError("loginLdap", "api.user.login_ldap.blank_pwd.app_error", nil, "") - c.Err.StatusCode = http.StatusBadRequest - return - } - - if len(id) == 0 { - c.Err = model.NewLocAppError("loginLdap", "api.user.login_ldap.need_id.app_error", nil, "") - c.Err.StatusCode = http.StatusBadRequest - return - } - - ldapInterface := einterfaces.GetLdapInterface() - if ldapInterface == nil { - c.Err = model.NewLocAppError("loginLdap", "api.user.login_ldap.not_available.app_error", nil, "") - c.Err.StatusCode = http.StatusNotImplemented - return - } - - user, err := ldapInterface.DoLogin(id, password) - if err != nil { - if user != nil { - c.LogAuditWithUserId(user.Id, "attempt") - c.LogAuditWithUserId(user.Id, "fail") - } else { - c.LogAudit("attempt") - c.LogAudit("fail") - } - c.Err = err - c.Err.StatusCode = http.StatusUnauthorized - return - } - c.LogAuditWithUserId(user.Id, "attempt") - - if err = checkUserAdditionalAuthenticationCriteria(user, mfaToken); err != nil { - c.LogAuditWithUserId(user.Id, "fail") - c.Err = err - c.Err.StatusCode = http.StatusUnauthorized - return - } - - // User is authenticated at this point - - Login(c, w, r, user, props["device_id"]) - c.LogAuditWithUserId(user.Id, "success") - - if user != nil { - user.Sanitize(map[string]bool{}) - } else { - user = &model.User{} - } - w.Write([]byte(user.ToJson())) -} - // User MUST be authenticated completely before calling Login -func Login(c *Context, w http.ResponseWriter, r *http.Request, user *model.User, deviceId string) { +func doLogin(c *Context, w http.ResponseWriter, r *http.Request, user *model.User, deviceId string) { session := &model.Session{UserId: user.Id, Roles: user.Roles, DeviceId: deviceId, IsOAuth: false} @@ -2371,28 +2275,20 @@ func checkMfa(c *Context, w http.ResponseWriter, r *http.Request) { props := model.MapFromJson(r.Body) - method := props["method"] - if method != model.USER_AUTH_SERVICE_EMAIL && - method != model.USER_AUTH_SERVICE_USERNAME && - method != model.USER_AUTH_SERVICE_LDAP { - c.SetInvalidParam("checkMfa", "method") - return - } - loginId := props["login_id"] if len(loginId) == 0 { c.SetInvalidParam("checkMfa", "login_id") return } - var uchan store.StoreChannel - if method == model.USER_AUTH_SERVICE_EMAIL { - uchan = Srv.Store.User().GetByEmail(loginId) - } else if method == model.USER_AUTH_SERVICE_USERNAME { - uchan = Srv.Store.User().GetByUsername(loginId) - } else if method == model.USER_AUTH_SERVICE_LDAP { - uchan = Srv.Store.User().GetByAuth(loginId, model.USER_AUTH_SERVICE_LDAP) - } + // we don't need to worry about contacting the ldap server to get this user because + // only users already in the system could have MFA enabled + uchan := Srv.Store.User().GetForLogin( + loginId, + *utils.Cfg.EmailSettings.EnableSignInWithUsername, + *utils.Cfg.EmailSettings.EnableSignInWithEmail, + *utils.Cfg.LdapSettings.Enable, + ) rdata := map[string]string{} if result := <-uchan; result.Err != nil { diff --git a/api/user_test.go b/api/user_test.go index 3c744120c..ee13f43f2 100644 --- a/api/user_test.go +++ b/api/user_test.go @@ -84,6 +84,19 @@ func TestLogin(t *testing.T) { th := Setup() Client := th.CreateClient() + enableSignInWithEmail := *utils.Cfg.EmailSettings.EnableSignInWithEmail + enableSignInWithUsername := *utils.Cfg.EmailSettings.EnableSignInWithUsername + enableLdap := *utils.Cfg.LdapSettings.Enable + defer func() { + *utils.Cfg.EmailSettings.EnableSignInWithEmail = enableSignInWithEmail + *utils.Cfg.EmailSettings.EnableSignInWithUsername = enableSignInWithUsername + *utils.Cfg.LdapSettings.Enable = enableLdap + }() + + *utils.Cfg.EmailSettings.EnableSignInWithEmail = false + *utils.Cfg.EmailSettings.EnableSignInWithUsername = false + *utils.Cfg.LdapSettings.Enable = false + team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} rteam, _ := Client.CreateTeam(&team) @@ -100,7 +113,12 @@ func TestLogin(t *testing.T) { } } - if result, err := Client.LoginByEmail(team.Name, user.Email, user.Password); err != nil { + if _, err := Client.Login(user.Email, user.Password); err == nil { + t.Fatal("shouldn't be able to log in by email when disabled") + } + + *utils.Cfg.EmailSettings.EnableSignInWithEmail = true + if result, err := Client.Login(user.Email, user.Password); err != nil { t.Fatal(err) } else { if result.Data.(*model.User).Email != user.Email { @@ -108,7 +126,12 @@ func TestLogin(t *testing.T) { } } - if result, err := Client.LoginByUsername(team.Name, user.Username, user.Password); err != nil { + if _, err := Client.Login(user.Username, user.Password); err == nil { + t.Fatal("shouldn't be able to log in by username when disabled") + } + + *utils.Cfg.EmailSettings.EnableSignInWithUsername = true + if result, err := Client.Login(user.Username, user.Password); err != nil { t.Fatal(err) } else { if result.Data.(*model.User).Email != user.Email { @@ -116,19 +139,19 @@ func TestLogin(t *testing.T) { } } - if _, err := Client.LoginByEmail(team.Name, user.Email, user.Password+"invalid"); err == nil { + if _, err := Client.Login(user.Email, user.Password+"invalid"); err == nil { t.Fatal("Invalid Password") } - if _, err := Client.LoginByUsername(team.Name, user.Username, user.Password+"invalid"); err == nil { + if _, err := Client.Login(user.Username, user.Password+"invalid"); err == nil { t.Fatal("Invalid Password") } - if _, err := Client.LoginByEmail(team.Name, "", user.Password); err == nil { + if _, err := Client.Login("", user.Password); err == nil { t.Fatal("should have failed") } - if _, err := Client.LoginByUsername(team.Name, "", user.Password); err == nil { + if _, err := Client.Login("", user.Password); err == nil { t.Fatal("should have failed") } @@ -160,22 +183,35 @@ func TestLogin(t *testing.T) { ruser2, _ := Client.CreateUserFromSignup(&user2, data, hash) - if _, err := Client.LoginByEmail(team2.Name, ruser2.Data.(*model.User).Email, user2.Password); err != nil { + if _, err := Client.Login(ruser2.Data.(*model.User).Email, user2.Password); err != nil { t.Fatal("From verfied hash") } Client.AuthToken = authToken + + user3 := &model.User{ + Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", + Nickname: "Corey Hulen", + Username: "corey" + model.NewId(), + Password: "pwd", + AuthService: model.USER_AUTH_SERVICE_LDAP, + } + user3 = Client.Must(Client.CreateUser(user3, "")).Data.(*model.User) + store.Must(Srv.Store.User().VerifyEmail(user3.Id)) + + if _, err := Client.Login(user3.Id, user3.Password); err == nil { + t.Fatal("LDAP user should not be able to log in with LDAP disabled") + } } func TestLoginWithDeviceId(t *testing.T) { th := Setup().InitBasic() Client := th.BasicClient - team := th.BasicTeam user := th.BasicUser Client.Must(Client.Logout()) deviceId := model.NewId() - if result, err := Client.LoginByEmailWithDevice(team.Name, user.Email, user.Password, deviceId); err != nil { + if result, err := Client.LoginWithDevice(user.Email, user.Password, deviceId); err != nil { t.Fatal(err) } else { ruser := result.Data.(*model.User) @@ -184,7 +220,7 @@ func TestLoginWithDeviceId(t *testing.T) { t.Fatal(err) } else { sessions := ssresult.Data.([]*model.Session) - if _, err := Client.LoginByEmailWithDevice(team.Name, user.Email, user.Password, deviceId); err != nil { + if _, err := Client.LoginWithDevice(user.Email, user.Password, deviceId); err != nil { t.Fatal(err) } @@ -198,13 +234,12 @@ func TestLoginWithDeviceId(t *testing.T) { func TestSessions(t *testing.T) { th := Setup().InitBasic() Client := th.BasicClient - team := th.BasicTeam user := th.BasicUser Client.Must(Client.Logout()) deviceId := model.NewId() - Client.LoginByEmailWithDevice(team.Name, user.Email, user.Password, deviceId) - Client.LoginByEmail(team.Name, user.Email, user.Password) + Client.LoginWithDevice(user.Email, user.Password, deviceId) + Client.Login(user.Email, user.Password) r1, err := Client.GetSessions(user.Id) if err != nil { @@ -269,7 +304,7 @@ func TestGetUser(t *testing.T) { LinkUserToTeam(ruser3.Data.(*model.User), rteam2.Data.(*model.Team)) store.Must(Srv.Store.User().VerifyEmail(ruser3.Data.(*model.User).Id)) - Client.LoginByEmail(team.Name, user.Email, user.Password) + Client.Login(user.Email, user.Password) rId := ruser.Data.(*model.User).Id if result, err := Client.GetUser(rId, ""); err != nil { @@ -333,7 +368,7 @@ func TestGetUser(t *testing.T) { c.IpAddress = "cmd_line" UpdateRoles(c, ruser.Data.(*model.User), model.ROLE_SYSTEM_ADMIN) - Client.LoginByEmail(team.Name, user.Email, "pwd") + Client.Login(user.Email, "pwd") if _, err := Client.GetProfiles(rteam2.Data.(*model.Team).Id, ""); err != nil { t.Fatal(err) @@ -374,7 +409,7 @@ func TestGetAudits(t *testing.T) { time.Sleep(100 * time.Millisecond) - Client.LoginByEmail(team.Name, user.Email, user.Password) + Client.Login(user.Email, user.Password) time.Sleep(100 * time.Millisecond) @@ -427,7 +462,7 @@ func TestUserCreateImage(t *testing.T) { LinkUserToTeam(user, team) store.Must(Srv.Store.User().VerifyEmail(user.Id)) - Client.LoginByEmail(team.Name, user.Email, "pwd") + Client.Login(user.Email, "pwd") Client.DoApiGet("/users/"+user.Id+"/image", "", "") @@ -471,7 +506,7 @@ func TestUserUploadProfileImage(t *testing.T) { t.Fatal("Should have errored") } - Client.LoginByEmail(team.Name, user.Email, "pwd") + Client.Login(user.Email, "pwd") Client.SetTeamId(team.Id) if _, upErr := Client.UploadProfileFile(body.Bytes(), writer.FormDataContentType()); upErr == nil { @@ -575,7 +610,7 @@ func TestUserUpdate(t *testing.T) { t.Fatal("Should have errored") } - Client.LoginByEmail(team.Name, user.Email, "pwd") + Client.Login(user.Email, "pwd") Client.SetTeamId(team.Id) time.Sleep(100 * time.Millisecond) @@ -615,7 +650,7 @@ func TestUserUpdate(t *testing.T) { LinkUserToTeam(user2, team) store.Must(Srv.Store.User().VerifyEmail(user2.Id)) - Client.LoginByEmail(team.Name, user2.Email, "pwd") + Client.Login(user2.Email, "pwd") Client.SetTeamId(team.Id) user.Nickname = "Tim Timmy" @@ -642,7 +677,7 @@ func TestUserUpdatePassword(t *testing.T) { t.Fatal("Should have errored") } - Client.LoginByEmail(team.Name, user.Email, "pwd") + Client.Login(user.Email, "pwd") if _, err := Client.UpdateUserPassword("123", "pwd", "newpwd"); err == nil { t.Fatal("Should have errored") @@ -673,7 +708,7 @@ func TestUserUpdatePassword(t *testing.T) { t.Fatal("LastPasswordUpdate should have changed") } - if _, err := Client.LoginByEmail(team.Name, user.Email, "newpwd"); err != nil { + if _, err := Client.Login(user.Email, "newpwd"); err != nil { t.Fatal(err) } @@ -681,7 +716,7 @@ func TestUserUpdatePassword(t *testing.T) { user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User) LinkUserToTeam(user2, team) - Client.LoginByEmail(team.Name, user2.Email, "pwd") + Client.Login(user2.Email, "pwd") if _, err := Client.UpdateUserPassword(user.Id, "pwd", "newpwd"); err == nil { t.Fatal("Should have errored") @@ -713,7 +748,7 @@ func TestUserUpdateRoles(t *testing.T) { t.Fatal("Should have errored, not logged in") } - Client.LoginByEmail(team.Name, user2.Email, "pwd") + Client.Login(user2.Email, "pwd") Client.SetTeamId(team.Id) if _, err := Client.UpdateUserRoles(data); err == nil { @@ -728,7 +763,7 @@ func TestUserUpdateRoles(t *testing.T) { LinkUserToTeam(user3, team2) store.Must(Srv.Store.User().VerifyEmail(user3.Id)) - Client.LoginByEmail(team2.Name, user3.Email, "pwd") + Client.Login(user3.Email, "pwd") Client.SetTeamId(team2.Id) data["user_id"] = user2.Id @@ -737,7 +772,7 @@ func TestUserUpdateRoles(t *testing.T) { t.Fatal("Should have errored, wrong team") } - Client.LoginByEmail(team.Name, user.Email, "pwd") + Client.Login(user.Email, "pwd") data["user_id"] = "junk" data["new_roles"] = "admin" @@ -771,7 +806,7 @@ func TestUserUpdateDeviceId(t *testing.T) { LinkUserToTeam(user, team) store.Must(Srv.Store.User().VerifyEmail(user.Id)) - Client.LoginByEmail(team.Name, user.Email, "pwd") + Client.Login(user.Email, "pwd") Client.SetTeamId(team.Id) deviceId := model.PUSH_NOTIFY_APPLE + ":1234567890" @@ -811,7 +846,7 @@ func TestUserUpdateActive(t *testing.T) { t.Fatal("Should have errored, not logged in") } - Client.LoginByEmail(team.Name, user2.Email, "pwd") + Client.Login(user2.Email, "pwd") Client.SetTeamId(team.Id) if _, err := Client.UpdateActive(user.Id, false); err == nil { @@ -828,14 +863,14 @@ func TestUserUpdateActive(t *testing.T) { LinkUserToTeam(user2, team2) store.Must(Srv.Store.User().VerifyEmail(user3.Id)) - Client.LoginByEmail(team2.Name, user3.Email, "pwd") + Client.Login(user3.Email, "pwd") Client.SetTeamId(team2.Id) if _, err := Client.UpdateActive(user.Id, false); err == nil { t.Fatal("Should have errored, not yourself") } - Client.LoginByEmail(team.Name, user.Email, "pwd") + Client.Login(user.Email, "pwd") Client.SetTeamId(team.Id) if _, err := Client.UpdateActive("junk", false); err == nil { @@ -859,7 +894,7 @@ func TestUserPermDelete(t *testing.T) { LinkUserToTeam(user1, team) store.Must(Srv.Store.User().VerifyEmail(user1.Id)) - Client.LoginByEmail(team.Name, user1.Email, "pwd") + Client.Login(user1.Email, "pwd") Client.SetTeamId(team.Id) channel1 := &model.Channel{DisplayName: "TestGetPosts", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} @@ -1013,7 +1048,7 @@ func TestUserUpdateNotify(t *testing.T) { t.Fatal("Should have errored - not logged in") } - Client.LoginByEmail(team.Name, user.Email, "pwd") + Client.Login(user.Email, "pwd") Client.SetTeamId(team.Id) if result, err := Client.UpdateUserNotify(data); err != nil { @@ -1109,7 +1144,7 @@ func TestStatuses(t *testing.T) { LinkUserToTeam(ruser2, rteam.Data.(*model.Team)) store.Must(Srv.Store.User().VerifyEmail(ruser2.Id)) - Client.LoginByEmail(team.Name, user.Email, user.Password) + Client.Login(user.Email, user.Password) Client.SetTeamId(team.Id) userIds := []string{ruser2.Id} @@ -1207,7 +1242,7 @@ func TestOAuthToEmail(t *testing.T) { t.Fatal("should have failed - not logged in") } - Client.LoginByEmail(team.Name, user.Email, user.Password) + Client.Login(user.Email, user.Password) if _, err := Client.OAuthToEmail(m); err == nil { t.Fatal("should have failed - empty data") @@ -1248,7 +1283,7 @@ func TestLDAPToEmail(t *testing.T) { LinkUserToTeam(ruser, rteam.Data.(*model.Team)) store.Must(Srv.Store.User().VerifyEmail(ruser.Id)) - Client.LoginByEmail(team.Name, user.Email, user.Password) + Client.Login(user.Email, user.Password) m := map[string]string{} if _, err := Client.LDAPToEmail(m); err == nil { @@ -1301,7 +1336,7 @@ func TestEmailToLDAP(t *testing.T) { LinkUserToTeam(ruser, rteam.Data.(*model.Team)) store.Must(Srv.Store.User().VerifyEmail(ruser.Id)) - Client.LoginByEmail(team.Name, user.Email, user.Password) + Client.Login(user.Email, user.Password) m := map[string]string{} if _, err := Client.EmailToLDAP(m); err == nil { @@ -1438,7 +1473,7 @@ func TestGenerateMfaQrCode(t *testing.T) { t.Fatal("should have failed - not logged in") } - Client.LoginByEmail(team.Name, user.Email, user.Password) + Client.Login(user.Email, user.Password) if _, err := Client.GenerateMfaQrCode(); err == nil { t.Fatal("should have failed - not licensed") @@ -1476,7 +1511,7 @@ func TestUpdateMfa(t *testing.T) { t.Fatal("should have failed - not logged in") } - Client.LoginByEmail(team.Name, user.Email, user.Password) + Client.Login(user.Email, user.Password) if _, err := Client.UpdateMfa(true, ""); err == nil { t.Fatal("should have failed - no token") @@ -1509,7 +1544,7 @@ func TestCheckMfa(t *testing.T) { LinkUserToTeam(ruser.Data.(*model.User), rteam.Data.(*model.Team)) store.Must(Srv.Store.User().VerifyEmail(ruser.Data.(*model.User).Id)) - if result, err := Client.CheckMfa(model.USER_AUTH_SERVICE_EMAIL, user.Email); err != nil { + if result, err := Client.CheckMfa(user.Email); err != nil { t.Fatal(err) } else { resp := result.Data.(map[string]string) -- cgit v1.2.3-1-g7c22