Этот коммит содержится в:
JoramWilander
2017-07-06 17:28:38 -04:00
родитель 3d382cfa0e
Коммит 15ad24d160
8 изменённых файлов: 114 добавлений и 20 удалений

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

@@ -157,7 +157,7 @@ func loginWithOAuth(c *Context, w http.ResponseWriter, r *http.Request) {
return return
} }
if authUrl, err := app.GetOAuthLoginEndpoint(service, teamId, model.OAUTH_ACTION_LOGIN, redirectTo, loginHint); err != nil { if authUrl, err := app.GetOAuthLoginEndpoint(w, r, service, teamId, model.OAUTH_ACTION_LOGIN, redirectTo, loginHint); err != nil {
c.Err = err c.Err = err
return return
} else { } else {
@@ -180,7 +180,7 @@ func signupWithOAuth(c *Context, w http.ResponseWriter, r *http.Request) {
return return
} }
if authUrl, err := app.GetOAuthSignupEndpoint(service, teamId); err != nil { if authUrl, err := app.GetOAuthSignupEndpoint(w, r, service, teamId); err != nil {
c.Err = err c.Err = err
return return
} else { } else {

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

@@ -866,7 +866,7 @@ func emailToOAuth(c *Context, w http.ResponseWriter, r *http.Request) {
return return
} }
link, err := app.SwitchEmailToOAuth(email, password, mfaToken, service) link, err := app.SwitchEmailToOAuth(w, r, email, password, mfaToken, service)
if err != nil { if err != nil {
c.Err = err c.Err = err
return return

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

@@ -400,7 +400,7 @@ func completeOAuth(c *Context, w http.ResponseWriter, r *http.Request) {
uri := c.GetSiteURLHeader() + "/signup/" + service + "/complete" uri := c.GetSiteURLHeader() + "/signup/" + service + "/complete"
body, teamId, props, err := app.AuthorizeOAuthUser(service, code, state, uri) body, teamId, props, err := app.AuthorizeOAuthUser(w, r, service, code, state, uri)
if err != nil { if err != nil {
c.Err = err c.Err = err
return return
@@ -455,7 +455,7 @@ func loginWithOAuth(c *Context, w http.ResponseWriter, r *http.Request) {
return return
} }
if authUrl, err := app.GetOAuthLoginEndpoint(c.Params.Service, teamId, model.OAUTH_ACTION_LOGIN, redirectTo, loginHint); err != nil { if authUrl, err := app.GetOAuthLoginEndpoint(w, r, c.Params.Service, teamId, model.OAUTH_ACTION_LOGIN, redirectTo, loginHint); err != nil {
c.Err = err c.Err = err
return return
} else { } else {
@@ -475,7 +475,7 @@ func mobileLoginWithOAuth(c *Context, w http.ResponseWriter, r *http.Request) {
return return
} }
if authUrl, err := app.GetOAuthLoginEndpoint(c.Params.Service, teamId, model.OAUTH_ACTION_MOBILE, "", ""); err != nil { if authUrl, err := app.GetOAuthLoginEndpoint(w, r, c.Params.Service, teamId, model.OAUTH_ACTION_MOBILE, "", ""); err != nil {
c.Err = err c.Err = err
return return
} else { } else {
@@ -500,7 +500,7 @@ func signupWithOAuth(c *Context, w http.ResponseWriter, r *http.Request) {
return return
} }
if authUrl, err := app.GetOAuthSignupEndpoint(c.Params.Service, teamId); err != nil { if authUrl, err := app.GetOAuthSignupEndpoint(w, r, c.Params.Service, teamId); err != nil {
c.Err = err c.Err = err
return return
} else { } else {

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

@@ -1056,7 +1056,7 @@ func switchAccountType(c *Context, w http.ResponseWriter, r *http.Request) {
var err *model.AppError var err *model.AppError
if switchRequest.EmailToOAuth() { if switchRequest.EmailToOAuth() {
link, err = app.SwitchEmailToOAuth(switchRequest.Email, switchRequest.Password, switchRequest.MfaCode, switchRequest.NewService) link, err = app.SwitchEmailToOAuth(w, r, switchRequest.Email, switchRequest.Password, switchRequest.MfaCode, switchRequest.NewService)
} else if switchRequest.OAuthToEmail() { } else if switchRequest.OAuthToEmail() {
c.SessionRequired() c.SessionRequired()
if c.Err != nil { if c.Err != nil {

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

@@ -12,6 +12,7 @@ import (
"net/http" "net/http"
"net/url" "net/url"
"strings" "strings"
"time"
l4g "github.com/alecthomas/log4go" l4g "github.com/alecthomas/log4go"
"github.com/mattermost/platform/einterfaces" "github.com/mattermost/platform/einterfaces"
@@ -20,6 +21,11 @@ import (
"github.com/mattermost/platform/utils" "github.com/mattermost/platform/utils"
) )
const (
OAUTH_COOKIE_MAX_AGE_SECONDS = 30 * 60 // 30 minutes
COOKIE_OAUTH = "MMOAUTH"
)
func CreateOAuthApp(app *model.OAuthApp) (*model.OAuthApp, *model.AppError) { func CreateOAuthApp(app *model.OAuthApp) (*model.OAuthApp, *model.AppError) {
if !utils.Cfg.ServiceSettings.EnableOAuthServiceProvider { if !utils.Cfg.ServiceSettings.EnableOAuthServiceProvider {
return nil, model.NewAppError("CreateOAuthApp", "api.oauth.register_oauth_app.turn_off.app_error", nil, "", http.StatusNotImplemented) return nil, model.NewAppError("CreateOAuthApp", "api.oauth.register_oauth_app.turn_off.app_error", nil, "", http.StatusNotImplemented)
@@ -289,7 +295,7 @@ func newSessionUpdateToken(appName string, accessData *model.AccessData, user *m
return accessRsp, nil return accessRsp, nil
} }
func GetOAuthLoginEndpoint(service, teamId, action, redirectTo, loginHint string) (string, *model.AppError) { func GetOAuthLoginEndpoint(w http.ResponseWriter, r *http.Request, service, teamId, action, redirectTo, loginHint string) (string, *model.AppError) {
stateProps := map[string]string{} stateProps := map[string]string{}
stateProps["action"] = action stateProps["action"] = action
if len(teamId) != 0 { if len(teamId) != 0 {
@@ -300,21 +306,21 @@ func GetOAuthLoginEndpoint(service, teamId, action, redirectTo, loginHint string
stateProps["redirect_to"] = redirectTo stateProps["redirect_to"] = redirectTo
} }
if authUrl, err := GetAuthorizationCode(service, stateProps, loginHint); err != nil { if authUrl, err := GetAuthorizationCode(w, r, service, stateProps, loginHint); err != nil {
return "", err return "", err
} else { } else {
return authUrl, nil return authUrl, nil
} }
} }
func GetOAuthSignupEndpoint(service, teamId string) (string, *model.AppError) { func GetOAuthSignupEndpoint(w http.ResponseWriter, r *http.Request, service, teamId string) (string, *model.AppError) {
stateProps := map[string]string{} stateProps := map[string]string{}
stateProps["action"] = model.OAUTH_ACTION_SIGNUP stateProps["action"] = model.OAUTH_ACTION_SIGNUP
if len(teamId) != 0 { if len(teamId) != 0 {
stateProps["team_id"] = teamId stateProps["team_id"] = teamId
} }
if authUrl, err := GetAuthorizationCode(service, stateProps, ""); err != nil { if authUrl, err := GetAuthorizationCode(w, r, service, stateProps, ""); err != nil {
return "", err return "", err
} else { } else {
return authUrl, nil return authUrl, nil
@@ -519,17 +525,69 @@ func CompleteSwitchWithOAuth(service string, userData io.ReadCloser, email strin
return user, nil return user, nil
} }
func GetAuthorizationCode(service string, props map[string]string, loginHint string) (string, *model.AppError) { func CreateOAuthStateToken(extra string) (*model.Token, *model.AppError) {
token := model.NewToken(model.TOKEN_TYPE_OAUTH, extra)
if result := <-Srv.Store.Token().Save(token); result.Err != nil {
return nil, result.Err
}
return token, nil
}
func GetOAuthStateToken(token string) (*model.Token, *model.AppError) {
if result := <-Srv.Store.Token().GetByToken(token); result.Err != nil {
return nil, model.NewAppError("GetOAuthStateToken", "api.oauth.invalid_state_token.app_error", nil, result.Err.Error(), http.StatusBadRequest)
} else {
token := result.Data.(*model.Token)
if token.Type != model.TOKEN_TYPE_OAUTH {
return nil, model.NewAppError("GetOAuthStateToken", "api.oauth.invalid_state_token.app_error", nil, "", http.StatusBadRequest)
}
return token, nil
}
}
func generateOAuthStateTokenExtra(email, action, cookie string) string {
return email + ":" + action + ":" + cookie
}
func GetAuthorizationCode(w http.ResponseWriter, r *http.Request, service string, props map[string]string, loginHint string) (string, *model.AppError) {
sso := utils.Cfg.GetSSOService(service) sso := utils.Cfg.GetSSOService(service)
if sso != nil && !sso.Enable { if sso != nil && !sso.Enable {
return "", model.NewAppError("GetAuthorizationCode", "api.user.get_authorization_code.unsupported.app_error", nil, "service="+service, http.StatusNotImplemented) return "", model.NewAppError("GetAuthorizationCode", "api.user.get_authorization_code.unsupported.app_error", nil, "service="+service, http.StatusNotImplemented)
} }
secure := false
if GetProtocol(r) == "https" {
secure = true
}
cookieValue := model.NewId()
expiresAt := time.Unix(model.GetMillis()/1000+int64(OAUTH_COOKIE_MAX_AGE_SECONDS), 0)
oauthCookie := &http.Cookie{
Name: COOKIE_OAUTH,
Value: cookieValue,
Path: "/",
MaxAge: OAUTH_COOKIE_MAX_AGE_SECONDS,
Expires: expiresAt,
HttpOnly: true,
Secure: secure,
}
http.SetCookie(w, oauthCookie)
clientId := sso.Id clientId := sso.Id
endpoint := sso.AuthEndpoint endpoint := sso.AuthEndpoint
scope := sso.Scope scope := sso.Scope
props["hash"] = utils.HashSha256(clientId) tokenExtra := generateOAuthStateTokenExtra(props["email"], props["action"], cookieValue)
stateToken, err := CreateOAuthStateToken(tokenExtra)
if err != nil {
return "", err
}
props["token"] = stateToken.Token
state := b64.StdEncoding.EncodeToString([]byte(model.MapToJson(props))) state := b64.StdEncoding.EncodeToString([]byte(model.MapToJson(props)))
redirectUri := utils.GetSiteURL() + "/signup/" + service + "/complete" redirectUri := utils.GetSiteURL() + "/signup/" + service + "/complete"
@@ -547,7 +605,7 @@ func GetAuthorizationCode(service string, props map[string]string, loginHint str
return authUrl, nil return authUrl, nil
} }
func AuthorizeOAuthUser(service, code, state, redirectUri string) (io.ReadCloser, string, map[string]string, *model.AppError) { func AuthorizeOAuthUser(w http.ResponseWriter, r *http.Request, service, code, state, redirectUri string) (io.ReadCloser, string, map[string]string, *model.AppError) {
sso := utils.Cfg.GetSSOService(service) sso := utils.Cfg.GetSSOService(service)
if sso == nil || !sso.Enable { if sso == nil || !sso.Enable {
return nil, "", nil, model.NewLocAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.unsupported.app_error", nil, "service="+service) return nil, "", nil, model.NewLocAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.unsupported.app_error", nil, "service="+service)
@@ -562,10 +620,41 @@ func AuthorizeOAuthUser(service, code, state, redirectUri string) (io.ReadCloser
stateProps := model.MapFromJson(strings.NewReader(stateStr)) stateProps := model.MapFromJson(strings.NewReader(stateStr))
if stateProps["hash"] != utils.HashSha256(sso.Id) { expectedToken, err := GetOAuthStateToken(stateProps["token"])
return nil, "", nil, model.NewLocAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.invalid_state.app_error", nil, "") if err != nil {
return nil, "", nil, err
} }
stateEmail := stateProps["email"]
stateAction := stateProps["action"]
if stateAction == model.OAUTH_ACTION_EMAIL_TO_SSO && stateEmail == "" {
return nil, "", nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.invalid_state.app_error", nil, "", http.StatusBadRequest)
}
cookieValue := ""
if cookie, err := r.Cookie(COOKIE_OAUTH); err != nil {
return nil, "", nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.invalid_state.app_error", nil, "", http.StatusBadRequest)
} else {
cookieValue = cookie.Value
}
expectedTokenExtra := generateOAuthStateTokenExtra(stateEmail, stateAction, cookieValue)
if expectedTokenExtra != expectedToken.Extra {
return nil, "", nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.invalid_state.app_error", nil, "", http.StatusBadRequest)
}
DeleteToken(expectedToken)
cookie := &http.Cookie{
Name: COOKIE_OAUTH,
Value: "",
Path: "/",
MaxAge: -1,
HttpOnly: true,
}
http.SetCookie(w, cookie)
teamId := stateProps["team_id"] teamId := stateProps["team_id"]
p := url.Values{} p := url.Values{}
@@ -617,7 +706,7 @@ func AuthorizeOAuthUser(service, code, state, redirectUri string) (io.ReadCloser
} }
func SwitchEmailToOAuth(email, password, code, service string) (string, *model.AppError) { func SwitchEmailToOAuth(w http.ResponseWriter, r *http.Request, email, password, code, service string) (string, *model.AppError) {
var user *model.User var user *model.User
var err *model.AppError var err *model.AppError
if user, err = GetUserByEmail(email); err != nil { if user, err = GetUserByEmail(email); err != nil {
@@ -635,7 +724,7 @@ func SwitchEmailToOAuth(email, password, code, service string) (string, *model.A
if service == model.USER_AUTH_SERVICE_SAML { if service == model.USER_AUTH_SERVICE_SAML {
return utils.GetSiteURL() + "/login/sso/saml?action=" + model.OAUTH_ACTION_EMAIL_TO_SSO + "&email=" + email, nil return utils.GetSiteURL() + "/login/sso/saml?action=" + model.OAUTH_ACTION_EMAIL_TO_SSO + "&email=" + email, nil
} else { } else {
if authUrl, err := GetAuthorizationCode(service, stateProps, ""); err != nil { if authUrl, err := GetAuthorizationCode(w, r, service, stateProps, ""); err != nil {
return "", err return "", err
} else { } else {
return authUrl, nil return authUrl, nil

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

@@ -2699,6 +2699,10 @@
"id": "api.user.permanent_delete_user.system_admin.warn", "id": "api.user.permanent_delete_user.system_admin.warn",
"translation": "You are deleting %v that is a system administrator. You may need to set another account as the system administrator using the command line tools." "translation": "You are deleting %v that is a system administrator. You may need to set another account as the system administrator using the command line tools."
}, },
{
"id": "api.oauth.invalid_state_token.app_error",
"translation": "Invalid state token"
},
{ {
"id": "api.user.reset_password.invalid_link.app_error", "id": "api.user.reset_password.invalid_link.app_error",
"translation": "The reset password link does not appear to be valid" "translation": "The reset password link does not appear to be valid"

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

@@ -8,6 +8,7 @@ import "net/http"
const ( const (
TOKEN_SIZE = 64 TOKEN_SIZE = 64
MAX_TOKEN_EXIPRY_TIME = 1000 * 60 * 60 * 24 // 24 hour MAX_TOKEN_EXIPRY_TIME = 1000 * 60 * 60 * 24 // 24 hour
TOKEN_TYPE_OAUTH = "oauth"
) )
type Token struct { type Token struct {

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

@@ -5043,7 +5043,7 @@ math-expression-evaluator@^1.2.14:
mattermost-redux@mattermost/mattermost-redux#webapp-master: mattermost-redux@mattermost/mattermost-redux#webapp-master:
version "0.0.1" version "0.0.1"
resolved "https://codeload.github.com/mattermost/mattermost-redux/tar.gz/dd48556075c8be41aa5ac4a0165bbe830d496875" resolved "https://codeload.github.com/mattermost/mattermost-redux/tar.gz/b4bab66d36f10ace06bcd3243d68807bfbca9c48"
dependencies: dependencies:
deep-equal "1.0.1" deep-equal "1.0.1"
harmony-reflect "1.5.1" harmony-reflect "1.5.1"