From dfc6db737411bd4ad68a803be5182f06055a1769 Mon Sep 17 00:00:00 2001 From: Joram Wilander Date: Mon, 10 Apr 2017 08:19:49 -0400 Subject: 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 --- app/oauth.go | 172 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 171 insertions(+), 1 deletion(-) (limited to 'app/oauth.go') 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 +} -- cgit v1.2.3-1-g7c22