summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--api/oauth.go114
-rw-r--r--api/user.go168
-rw-r--r--api4/context.go3
-rw-r--r--api4/user.go38
-rw-r--r--api4/user_test.go93
-rw-r--r--app/authentication.go21
-rw-r--r--app/ldap.go78
-rw-r--r--app/oauth.go172
-rw-r--r--model/client4.go10
-rw-r--r--model/switch_request.go62
-rw-r--r--model/switch_request_test.go19
11 files changed, 494 insertions, 284 deletions
diff --git a/api/oauth.go b/api/oauth.go
index 4a71500c4..059cac29b 100644
--- a/api/oauth.go
+++ b/api/oauth.go
@@ -4,8 +4,6 @@
package api
import (
- "crypto/tls"
- b64 "encoding/base64"
"fmt"
"io"
"io/ioutil"
@@ -273,7 +271,7 @@ func completeOAuth(c *Context, w http.ResponseWriter, r *http.Request) {
uri := c.GetSiteURLHeader() + "/signup/" + service + "/complete"
- if body, teamId, props, err := AuthorizeOAuthUser(service, code, state, uri); err != nil {
+ if body, teamId, props, err := app.AuthorizeOAuthUser(service, code, state, uri); err != nil {
c.Err = err
return
} else {
@@ -620,7 +618,7 @@ func loginWithOAuth(c *Context, w http.ResponseWriter, r *http.Request) {
stateProps["redirect_to"] = redirectTo
}
- if authUrl, err := GetAuthorizationCode(c, service, stateProps, loginHint); err != nil {
+ if authUrl, err := app.GetAuthorizationCode(service, stateProps, loginHint); err != nil {
c.Err = err
return
} else {
@@ -680,7 +678,7 @@ func signupWithOAuth(c *Context, w http.ResponseWriter, r *http.Request) {
stateProps["team_id"] = teamId
}
- if authUrl, err := GetAuthorizationCode(c, service, stateProps, ""); err != nil {
+ if authUrl, err := app.GetAuthorizationCode(service, stateProps, ""); err != nil {
c.Err = err
return
} else {
@@ -688,112 +686,6 @@ func signupWithOAuth(c *Context, w http.ResponseWriter, r *http.Request) {
}
}
-func GetAuthorizationCode(c *Context, service 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)
- state := b64.StdEncoding.EncodeToString([]byte(model.MapToJson(props)))
-
- redirectUri := c.GetSiteURLHeader() + "/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, string, map[string]string, *model.AppError) {
- sso := utils.Cfg.GetSSOService(service)
- if sso == nil || !sso.Enable {
- return 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, 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, model.NewLocAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.invalid_state.app_error", nil, "")
- }
-
- teamId := stateProps["team_id"]
-
- 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
- var respBody []byte
- if resp, err := client.Do(req); err != nil {
- return nil, "", nil, model.NewLocAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.token_failed.app_error", nil, err.Error())
- } else {
- ar = model.AccessResponseFromJson(resp.Body)
- defer func() {
- ioutil.ReadAll(resp.Body)
- resp.Body.Close()
- }()
- if ar == nil {
- return 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, model.NewLocAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.bad_token.app_error", nil, "token_type="+ar.TokenType+", response_body="+string(respBody))
- }
-
- if len(ar.AccessToken) == 0 {
- return 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, model.NewLocAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.service.app_error",
- map[string]interface{}{"Service": service}, err.Error())
- } else {
- return resp.Body, teamId, stateProps, nil
- }
-
-}
-
func CompleteSwitchWithOAuth(c *Context, w http.ResponseWriter, r *http.Request, service string, userData io.ReadCloser, email string) {
authData := ""
ssoEmail := ""
diff --git a/api/user.go b/api/user.go
index 7f422b355..99f751ee0 100644
--- a/api/user.go
+++ b/api/user.go
@@ -914,41 +914,14 @@ func emailToOAuth(c *Context, w http.ResponseWriter, r *http.Request) {
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")
+ link, err := app.SwitchEmailToOAuth(email, password, mfaToken, service)
+ if err != nil {
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.GetSiteURLHeader() + "/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)))
+ c.LogAudit("success for email=" + email)
+ w.Write([]byte(model.MapToJson(map[string]string{"follow_link": link})))
}
func oauthToEmail(c *Context, w http.ResponseWriter, r *http.Request) {
@@ -966,51 +939,19 @@ func oauthToEmail(c *Context, w http.ResponseWriter, r *http.Request) {
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, utils.GetSiteURL()); err != nil {
- l4g.Error(err.Error())
- }
- }()
-
- if err := app.RevokeAllSessions(c.Session.UserId); err != nil {
+ link, err := app.SwitchOAuthToEmail(email, password, c.Session.UserId)
+ if 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)))
+ w.Write([]byte(model.MapToJson(map[string]string{"follow_link": link})))
}
func emailToLdap(c *Context, w http.ResponseWriter, r *http.Request) {
@@ -1044,55 +985,19 @@ func emailToLdap(c *Context, w http.ResponseWriter, r *http.Request) {
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 {
+ link, err := app.SwitchEmailToLdap(email, emailPassword, token, ldapId, ldapPassword)
+ if 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, utils.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)))
+ w.Write([]byte(model.MapToJson(map[string]string{"follow_link": link})))
}
func ldapToEmail(c *Context, w http.ResponseWriter, r *http.Request) {
@@ -1120,66 +1025,19 @@ func ldapToEmail(c *Context, w http.ResponseWriter, r *http.Request) {
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 {
+ link, err := app.SwitchLdapToEmail(ldapPassword, token, email, emailPassword)
+ if 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, utils.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)))
+ w.Write([]byte(model.MapToJson(map[string]string{"follow_link": link})))
}
func verifyEmail(c *Context, w http.ResponseWriter, r *http.Request) {
diff --git a/api4/context.go b/api4/context.go
index 9c27ec9c1..cd1137508 100644
--- a/api4/context.go
+++ b/api4/context.go
@@ -242,8 +242,7 @@ func (c *Context) IsSystemAdmin() bool {
func (c *Context) SessionRequired() {
if len(c.Session.UserId) == 0 {
- c.Err = model.NewLocAppError("", "api.context.session_expired.app_error", nil, "UserRequired")
- c.Err.StatusCode = http.StatusUnauthorized
+ c.Err = model.NewAppError("", "api.context.session_expired.app_error", nil, "UserRequired", http.StatusUnauthorized)
return
}
}
diff --git a/api4/user.go b/api4/user.go
index b30d066ab..5880370c6 100644
--- a/api4/user.go
+++ b/api4/user.go
@@ -43,6 +43,7 @@ func InitUser() {
BaseRoutes.User.Handle("/mfa/generate", ApiSessionRequired(generateMfaSecret)).Methods("POST")
BaseRoutes.Users.Handle("/login", ApiHandler(login)).Methods("POST")
+ BaseRoutes.Users.Handle("/login/switch", ApiHandler(switchAccountType)).Methods("POST")
BaseRoutes.Users.Handle("/logout", ApiHandler(logout)).Methods("POST")
BaseRoutes.UserByUsername.Handle("", ApiSessionRequired(getUserByUsername)).Methods("GET")
@@ -981,3 +982,40 @@ func sendVerificationEmail(c *Context, w http.ResponseWriter, r *http.Request) {
ReturnStatusOK(w)
}
+
+func switchAccountType(c *Context, w http.ResponseWriter, r *http.Request) {
+ switchRequest := model.SwitchRequestFromJson(r.Body)
+ if switchRequest == nil {
+ c.SetInvalidParam("switch_request")
+ return
+ }
+
+ link := ""
+ var err *model.AppError
+
+ if switchRequest.EmailToOAuth() {
+ link, err = app.SwitchEmailToOAuth(switchRequest.Email, switchRequest.Password, switchRequest.MfaCode, switchRequest.NewService)
+ } else if switchRequest.OAuthToEmail() {
+ c.SessionRequired()
+ if c.Err != nil {
+ return
+ }
+
+ link, err = app.SwitchOAuthToEmail(switchRequest.Email, switchRequest.NewPassword, c.Session.UserId)
+ } else if switchRequest.EmailToLdap() {
+ link, err = app.SwitchEmailToLdap(switchRequest.Email, switchRequest.Password, switchRequest.MfaCode, switchRequest.LdapId, switchRequest.NewPassword)
+ } else if switchRequest.LdapToEmail() {
+ link, err = app.SwitchLdapToEmail(switchRequest.Password, switchRequest.MfaCode, switchRequest.Email, switchRequest.NewPassword)
+ } else {
+ c.SetInvalidParam("switch_request")
+ return
+ }
+
+ if err != nil {
+ c.Err = err
+ return
+ }
+
+ c.LogAudit("success")
+ w.Write([]byte(model.MapToJson(map[string]string{"follow_link": link})))
+}
diff --git a/api4/user_test.go b/api4/user_test.go
index b3e4edc3d..f904bd460 100644
--- a/api4/user_test.go
+++ b/api4/user_test.go
@@ -1297,7 +1297,7 @@ func TestUpdateUserPassword(t *testing.T) {
// Should fail because account is locked out
_, resp = Client.UpdateUserPassword(th.BasicUser.Id, th.BasicUser.Password, "newpwd")
CheckErrorMessage(t, resp, "api.user.check_user_login_attempts.too_many.app_error")
- CheckForbiddenStatus(t, resp)
+ CheckUnauthorizedStatus(t, resp)
// System admin can update another user's password
adminSetPassword := "pwdsetbyadmin"
@@ -1651,3 +1651,94 @@ func TestSetProfileImage(t *testing.T) {
t.Fatal(err)
}
}
+
+func TestSwitchAccount(t *testing.T) {
+ th := Setup().InitBasic().InitSystemAdmin()
+ defer TearDown()
+ Client := th.Client
+
+ enableGitLab := utils.Cfg.GitLabSettings.Enable
+ defer func() {
+ utils.Cfg.GitLabSettings.Enable = enableGitLab
+ }()
+ utils.Cfg.GitLabSettings.Enable = true
+
+ Client.Logout()
+
+ sr := &model.SwitchRequest{
+ CurrentService: model.USER_AUTH_SERVICE_EMAIL,
+ NewService: model.USER_AUTH_SERVICE_GITLAB,
+ Email: th.BasicUser.Email,
+ Password: th.BasicUser.Password,
+ }
+
+ link, resp := Client.SwitchAccountType(sr)
+ CheckNoError(t, resp)
+
+ if link == "" {
+ t.Fatal("bad link")
+ }
+
+ th.LoginBasic()
+
+ fakeAuthData := "1"
+ if result := <-app.Srv.Store.User().UpdateAuthData(th.BasicUser.Id, model.USER_AUTH_SERVICE_GITLAB, &fakeAuthData, th.BasicUser.Email, true); result.Err != nil {
+ t.Fatal(result.Err)
+ }
+
+ sr = &model.SwitchRequest{
+ CurrentService: model.USER_AUTH_SERVICE_GITLAB,
+ NewService: model.USER_AUTH_SERVICE_EMAIL,
+ Email: th.BasicUser.Email,
+ NewPassword: th.BasicUser.Password,
+ }
+
+ link, resp = Client.SwitchAccountType(sr)
+ CheckNoError(t, resp)
+
+ if link != "/login?extra=signin_change" {
+ t.Log(link)
+ t.Fatal("bad link")
+ }
+
+ Client.Logout()
+ _, resp = Client.Login(th.BasicUser.Email, th.BasicUser.Password)
+ CheckNoError(t, resp)
+ Client.Logout()
+
+ sr = &model.SwitchRequest{
+ CurrentService: model.USER_AUTH_SERVICE_GITLAB,
+ NewService: model.SERVICE_GOOGLE,
+ }
+
+ _, resp = Client.SwitchAccountType(sr)
+ CheckBadRequestStatus(t, resp)
+
+ sr = &model.SwitchRequest{
+ CurrentService: model.USER_AUTH_SERVICE_EMAIL,
+ NewService: model.USER_AUTH_SERVICE_GITLAB,
+ Password: th.BasicUser.Password,
+ }
+
+ _, resp = Client.SwitchAccountType(sr)
+ CheckNotFoundStatus(t, resp)
+
+ sr = &model.SwitchRequest{
+ CurrentService: model.USER_AUTH_SERVICE_EMAIL,
+ NewService: model.USER_AUTH_SERVICE_GITLAB,
+ Email: th.BasicUser.Email,
+ }
+
+ _, resp = Client.SwitchAccountType(sr)
+ CheckUnauthorizedStatus(t, resp)
+
+ sr = &model.SwitchRequest{
+ CurrentService: model.USER_AUTH_SERVICE_GITLAB,
+ NewService: model.USER_AUTH_SERVICE_EMAIL,
+ Email: th.BasicUser.Email,
+ NewPassword: th.BasicUser.Password,
+ }
+
+ _, resp = Client.SwitchAccountType(sr)
+ CheckUnauthorizedStatus(t, resp)
+}
diff --git a/app/authentication.go b/app/authentication.go
index 369458527..8ea3f5fc4 100644
--- a/app/authentication.go
+++ b/app/authentication.go
@@ -43,7 +43,7 @@ func checkUserPassword(user *model.User, password string) *model.AppError {
return result.Err
}
- return model.NewLocAppError("checkUserPassword", "api.user.check_user_password.invalid.app_error", nil, "user_id="+user.Id)
+ return model.NewAppError("checkUserPassword", "api.user.check_user_password.invalid.app_error", nil, "user_id="+user.Id, http.StatusUnauthorized)
} else {
if result := <-Srv.Store.User().UpdateFailedPasswordAttempts(user.Id, 0); result.Err != nil {
return result.Err
@@ -57,8 +57,7 @@ func checkLdapUserPasswordAndAllCriteria(ldapId *string, password string, mfaTok
ldapInterface := einterfaces.GetLdapInterface()
if ldapInterface == nil || ldapId == nil {
- err := model.NewLocAppError("doLdapAuthentication", "api.user.login_ldap.not_available.app_error", nil, "")
- err.StatusCode = http.StatusNotImplemented
+ err := model.NewAppError("doLdapAuthentication", "api.user.login_ldap.not_available.app_error", nil, "", http.StatusNotImplemented)
return nil, err
}
@@ -109,13 +108,13 @@ func CheckUserMfa(user *model.User, token string) *model.AppError {
mfaInterface := einterfaces.GetMfaInterface()
if mfaInterface == nil {
- return model.NewLocAppError("checkUserMfa", "api.user.check_user_mfa.not_available.app_error", nil, "")
+ return model.NewAppError("checkUserMfa", "api.user.check_user_mfa.not_available.app_error", nil, "", http.StatusNotImplemented)
}
if ok, err := mfaInterface.ValidateToken(user.MfaSecret, token); err != nil {
return err
} else if !ok {
- return model.NewLocAppError("checkUserMfa", "api.user.check_user_mfa.bad_code.app_error", nil, "")
+ return model.NewAppError("checkUserMfa", "api.user.check_user_mfa.bad_code.app_error", nil, "", http.StatusUnauthorized)
}
return nil
@@ -123,7 +122,7 @@ func CheckUserMfa(user *model.User, token string) *model.AppError {
func checkUserLoginAttempts(user *model.User) *model.AppError {
if user.FailedAttempts >= utils.Cfg.ServiceSettings.MaximumLoginAttempts {
- return model.NewAppError("checkUserLoginAttempts", "api.user.check_user_login_attempts.too_many.app_error", nil, "user_id="+user.Id, http.StatusForbidden)
+ return model.NewAppError("checkUserLoginAttempts", "api.user.check_user_login_attempts.too_many.app_error", nil, "user_id="+user.Id, http.StatusUnauthorized)
}
return nil
@@ -131,14 +130,14 @@ func checkUserLoginAttempts(user *model.User) *model.AppError {
func checkEmailVerified(user *model.User) *model.AppError {
if !user.EmailVerified && utils.Cfg.EmailSettings.RequireEmailVerification {
- return model.NewLocAppError("Login", "api.user.login.not_verified.app_error", nil, "user_id="+user.Id)
+ return model.NewAppError("Login", "api.user.login.not_verified.app_error", nil, "user_id="+user.Id, http.StatusUnauthorized)
}
return nil
}
func checkUserNotDisabled(user *model.User) *model.AppError {
if user.DeleteAt > 0 {
- return model.NewLocAppError("Login", "api.user.login.inactive.app_error", nil, "user_id="+user.Id)
+ return model.NewAppError("Login", "api.user.login.inactive.app_error", nil, "user_id="+user.Id, http.StatusUnauthorized)
}
return nil
}
@@ -148,8 +147,7 @@ func authenticateUser(user *model.User, password, mfaToken string) (*model.User,
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
+ err := model.NewAppError("login", "api.user.login_ldap.not_available.app_error", nil, "", http.StatusNotImplemented)
return user, err
} else if ldapUser, err := checkLdapUserPasswordAndAllCriteria(user.AuthData, password, mfaToken); err != nil {
err.StatusCode = http.StatusUnauthorized
@@ -163,8 +161,7 @@ func authenticateUser(user *model.User, password, mfaToken string) (*model.User,
if authService == model.USER_AUTH_SERVICE_SAML {
authService = strings.ToUpper(authService)
}
- err := model.NewLocAppError("login", "api.user.login.use_auth_service.app_error", map[string]interface{}{"AuthService": authService}, "")
- err.StatusCode = http.StatusBadRequest
+ err := model.NewAppError("login", "api.user.login.use_auth_service.app_error", map[string]interface{}{"AuthService": authService}, "", http.StatusBadRequest)
return user, err
} else {
if err := CheckPasswordAndAllCriteria(user, password, mfaToken); err != nil {
diff --git a/app/ldap.go b/app/ldap.go
index fe68dfa81..94b328177 100644
--- a/app/ldap.go
+++ b/app/ldap.go
@@ -18,7 +18,7 @@ func SyncLdap() {
if ldapI := einterfaces.GetLdapInterface(); ldapI != nil {
ldapI.SyncNow()
} else {
- l4g.Error("%v", model.NewLocAppError("ldapSyncNow", "ent.ldap.disabled.app_error", nil, "").Error())
+ l4g.Error("%v", model.NewLocAppError("SyncLdap", "ent.ldap.disabled.app_error", nil, "").Error())
}
}
}()
@@ -31,10 +31,84 @@ func TestLdap() *model.AppError {
return err
}
} else {
- err := model.NewLocAppError("ldapTest", "ent.ldap.disabled.app_error", nil, "")
+ err := model.NewLocAppError("TestLdap", "ent.ldap.disabled.app_error", nil, "")
err.StatusCode = http.StatusNotImplemented
return err
}
return nil
}
+
+func SwitchEmailToLdap(email, password, code, ldapId, ldapPassword string) (string, *model.AppError) {
+ user, err := GetUserByEmail(email)
+ if err != nil {
+ return "", err
+ }
+
+ if err := CheckPasswordAndAllCriteria(user, password, code); err != nil {
+ return "", err
+ }
+
+ if err := RevokeAllSessions(user.Id); err != nil {
+ return "", err
+ }
+
+ ldapInterface := einterfaces.GetLdapInterface()
+ if ldapInterface == nil {
+ return "", model.NewAppError("SwitchEmailToLdap", "api.user.email_to_ldap.not_available.app_error", nil, "", http.StatusNotImplemented)
+ }
+
+ if err := ldapInterface.SwitchToLdap(user.Id, ldapId, ldapPassword); err != nil {
+ return "", err
+ }
+
+ go func() {
+ if err := SendSignInChangeEmail(user.Email, "AD/LDAP", user.Locale, utils.GetSiteURL()); err != nil {
+ l4g.Error(err.Error())
+ }
+ }()
+
+ return "/login?extra=signin_change", nil
+}
+
+func SwitchLdapToEmail(ldapPassword, code, email, newPassword string) (string, *model.AppError) {
+ user, err := GetUserByEmail(email)
+ if err != nil {
+ return "", err
+ }
+
+ if user.AuthService != model.USER_AUTH_SERVICE_LDAP {
+ return "", model.NewAppError("SwitchLdapToEmail", "api.user.ldap_to_email.not_ldap_account.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ ldapInterface := einterfaces.GetLdapInterface()
+ if ldapInterface == nil || user.AuthData == nil {
+ return "", model.NewAppError("SwitchLdapToEmail", "api.user.ldap_to_email.not_available.app_error", nil, "", http.StatusNotImplemented)
+ }
+
+ if err := ldapInterface.CheckPassword(*user.AuthData, ldapPassword); err != nil {
+ return "", err
+ }
+
+ if err := CheckUserMfa(user, code); err != nil {
+ return "", err
+ }
+
+ if err := UpdatePassword(user, newPassword); err != nil {
+ return "", err
+ }
+
+ if err := RevokeAllSessions(user.Id); err != nil {
+ return "", err
+ }
+
+ T := utils.GetUserTranslations(user.Locale)
+
+ go func() {
+ if err := SendSignInChangeEmail(user.Email, T("api.templates.signin_change_email.body.method_email"), user.Locale, utils.GetSiteURL()); err != nil {
+ l4g.Error(err.Error())
+ }
+ }()
+
+ return "/login?extra=signin_change", nil
+}
diff --git a/app/oauth.go b/app/oauth.go
index 3e8b0b8d2..c357d10c8 100644
--- a/app/oauth.go
+++ b/app/oauth.go
@@ -4,11 +4,20 @@
package app
import (
+ "crypto/tls"
+ b64 "encoding/base64"
+ "io"
+ "io/ioutil"
+ "net/http"
+ "net/url"
+ "strings"
+
+ l4g "github.com/alecthomas/log4go"
"github.com/mattermost/platform/model"
+ "github.com/mattermost/platform/utils"
)
func RevokeAccessToken(token string) *model.AppError {
-
session, _ := GetSession(token)
schan := Srv.Store.Session().Remove(token)
@@ -32,3 +41,164 @@ func RevokeAccessToken(token string) *model.AppError {
return nil
}
+
+func GetAuthorizationCode(service 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)
+ state := b64.StdEncoding.EncodeToString([]byte(model.MapToJson(props)))
+
+ redirectUri := utils.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, string, map[string]string, *model.AppError) {
+ sso := utils.Cfg.GetSSOService(service)
+ if sso == nil || !sso.Enable {
+ return 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, 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, model.NewLocAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.invalid_state.app_error", nil, "")
+ }
+
+ teamId := stateProps["team_id"]
+
+ 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
+ var respBody []byte
+ if resp, err := client.Do(req); err != nil {
+ return nil, "", nil, model.NewLocAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.token_failed.app_error", nil, err.Error())
+ } else {
+ ar = model.AccessResponseFromJson(resp.Body)
+ defer func() {
+ ioutil.ReadAll(resp.Body)
+ resp.Body.Close()
+ }()
+ if ar == nil {
+ return 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, model.NewLocAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.bad_token.app_error", nil, "token_type="+ar.TokenType+", response_body="+string(respBody))
+ }
+
+ if len(ar.AccessToken) == 0 {
+ return 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, model.NewLocAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.service.app_error",
+ map[string]interface{}{"Service": service}, err.Error())
+ } else {
+ return resp.Body, teamId, stateProps, nil
+ }
+
+}
+
+func SwitchEmailToOAuth(email, password, code, service string) (string, *model.AppError) {
+ var user *model.User
+ var err *model.AppError
+ if user, err = GetUserByEmail(email); err != nil {
+ return "", err
+ }
+
+ if err := CheckPasswordAndAllCriteria(user, password, code); err != nil {
+ return "", err
+ }
+
+ stateProps := map[string]string{}
+ stateProps["action"] = model.OAUTH_ACTION_EMAIL_TO_SSO
+ stateProps["email"] = email
+
+ if service == model.USER_AUTH_SERVICE_SAML {
+ return utils.GetSiteURL() + "/login/sso/saml?action=" + model.OAUTH_ACTION_EMAIL_TO_SSO + "&email=" + email, nil
+ } else {
+ if authUrl, err := GetAuthorizationCode(service, stateProps, ""); err != nil {
+ return "", err
+ } else {
+ return authUrl, nil
+ }
+ }
+}
+
+func SwitchOAuthToEmail(email, password, requesterId string) (string, *model.AppError) {
+ var user *model.User
+ var err *model.AppError
+ if user, err = GetUserByEmail(email); err != nil {
+ return "", err
+ }
+
+ if user.Id != requesterId {
+ return "", model.NewAppError("SwitchOAuthToEmail", "api.user.oauth_to_email.context.app_error", nil, "", http.StatusForbidden)
+ }
+
+ if err := UpdatePassword(user, password); err != nil {
+ return "", err
+ }
+
+ T := utils.GetUserTranslations(user.Locale)
+
+ go func() {
+ if err := SendSignInChangeEmail(user.Email, T("api.templates.signin_change_email.body.method_email"), user.Locale, utils.GetSiteURL()); err != nil {
+ l4g.Error(err.Error())
+ }
+ }()
+
+ if err := RevokeAllSessions(requesterId); err != nil {
+ return "", err
+ }
+
+ return "/login?extra=signin_change", nil
+}
diff --git a/model/client4.go b/model/client4.go
index 634c477e2..6d4772431 100644
--- a/model/client4.go
+++ b/model/client4.go
@@ -409,6 +409,16 @@ func (c *Client4) Logout() (bool, *Response) {
}
}
+// SwitchAccountType changes a user's login type from one type to another.
+func (c *Client4) SwitchAccountType(switchRequest *SwitchRequest) (string, *Response) {
+ if r, err := c.DoApiPost(c.GetUsersRoute()+"/login/switch", switchRequest.ToJson()); err != nil {
+ return "", &Response{StatusCode: r.StatusCode, Error: err}
+ } else {
+ defer closeBody(r)
+ return MapFromJson(r.Body)["follow_link"], BuildResponse(r)
+ }
+}
+
// User Section
// CreateUser creates a user in the system based on the provided user struct.
diff --git a/model/switch_request.go b/model/switch_request.go
new file mode 100644
index 000000000..44fbb27a9
--- /dev/null
+++ b/model/switch_request.go
@@ -0,0 +1,62 @@
+// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package model
+
+import (
+ "encoding/json"
+ "io"
+)
+
+type SwitchRequest struct {
+ CurrentService string `json:"current_service"`
+ NewService string `json:"new_service"`
+ Email string `json:"email"`
+ Password string `json:"current_password"`
+ NewPassword string `json:"new_password"`
+ MfaCode string `json:"mfa_code"`
+ LdapId string `json:"ldap_id"`
+}
+
+func (o *SwitchRequest) ToJson() string {
+ b, err := json.Marshal(o)
+ if err != nil {
+ return ""
+ } else {
+ return string(b)
+ }
+}
+
+func SwitchRequestFromJson(data io.Reader) *SwitchRequest {
+ decoder := json.NewDecoder(data)
+ var o SwitchRequest
+ err := decoder.Decode(&o)
+ if err == nil {
+ return &o
+ } else {
+ return nil
+ }
+}
+
+func (o *SwitchRequest) EmailToOAuth() bool {
+ return o.CurrentService == USER_AUTH_SERVICE_EMAIL &&
+ (o.NewService == USER_AUTH_SERVICE_SAML ||
+ o.NewService == USER_AUTH_SERVICE_GITLAB ||
+ o.NewService == SERVICE_GOOGLE ||
+ o.NewService == SERVICE_OFFICE365)
+}
+
+func (o *SwitchRequest) OAuthToEmail() bool {
+ return (o.CurrentService == USER_AUTH_SERVICE_SAML ||
+ o.CurrentService == USER_AUTH_SERVICE_GITLAB ||
+ o.CurrentService == SERVICE_GOOGLE ||
+ o.CurrentService == SERVICE_OFFICE365) && o.NewService == USER_AUTH_SERVICE_EMAIL
+}
+
+func (o *SwitchRequest) EmailToLdap() bool {
+ return o.CurrentService == USER_AUTH_SERVICE_EMAIL && o.NewService == USER_AUTH_SERVICE_LDAP
+}
+
+func (o *SwitchRequest) LdapToEmail() bool {
+ return o.CurrentService == USER_AUTH_SERVICE_LDAP && o.NewService == USER_AUTH_SERVICE_EMAIL
+}
diff --git a/model/switch_request_test.go b/model/switch_request_test.go
new file mode 100644
index 000000000..b85846f78
--- /dev/null
+++ b/model/switch_request_test.go
@@ -0,0 +1,19 @@
+// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package model
+
+import (
+ "strings"
+ "testing"
+)
+
+func TestSwitchRequestJson(t *testing.T) {
+ o := SwitchRequest{Email: NewId(), Password: NewId()}
+ json := o.ToJson()
+ ro := SwitchRequestFromJson(strings.NewReader(json))
+
+ if o.Email != ro.Email {
+ t.Fatal("Emails do not match")
+ }
+}