Refactor switching login type code into app layer and add v4 endpoint (#6000)

* Refactor switching login type code into app layer and add v4 endpoint

* Fix unit test
Этот коммит содержится в:
Joram Wilander
2017-04-10 08:19:49 -04:00
коммит произвёл Christopher Speller
родитель 7b77bcf87e
Коммит dfc6db7374
11 изменённых файлов: 494 добавлений и 284 удалений

Просмотреть файл

@@ -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 {

Просмотреть файл

@@ -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
}

Просмотреть файл

@@ -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
}