// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // See License.txt for license information. 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) if result := <-Srv.Store.OAuth().GetAccessData(token); result.Err != nil { return model.NewLocAppError("RevokeAccessToken", "api.oauth.revoke_access_token.get.app_error", nil, "") } tchan := Srv.Store.OAuth().RemoveAccessData(token) if result := <-tchan; result.Err != nil { return model.NewLocAppError("RevokeAccessToken", "api.oauth.revoke_access_token.del_token.app_error", nil, "") } if result := <-schan; result.Err != nil { return model.NewLocAppError("RevokeAccessToken", "api.oauth.revoke_access_token.del_session.app_error", nil, "") } if session != nil { ClearSessionCacheForUser(session.UserId) } 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 }