Merge branch 'release-4.0'
Этот коммит содержится в:
113
app/oauth.go
113
app/oauth.go
@@ -12,6 +12,7 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
l4g "github.com/alecthomas/log4go"
|
||||
"github.com/mattermost/platform/einterfaces"
|
||||
@@ -20,6 +21,11 @@ import (
|
||||
"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) {
|
||||
if !utils.Cfg.ServiceSettings.EnableOAuthServiceProvider {
|
||||
return nil, model.NewAppError("CreateOAuthApp", "api.oauth.register_oauth_app.turn_off.app_error", nil, "", http.StatusNotImplemented)
|
||||
@@ -56,6 +62,8 @@ func DeleteOAuthApp(appId string) *model.AppError {
|
||||
return err
|
||||
}
|
||||
|
||||
InvalidateAllCaches()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -289,7 +297,7 @@ func newSessionUpdateToken(appName string, accessData *model.AccessData, user *m
|
||||
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["action"] = action
|
||||
if len(teamId) != 0 {
|
||||
@@ -300,21 +308,21 @@ func GetOAuthLoginEndpoint(service, teamId, action, redirectTo, loginHint string
|
||||
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
|
||||
} else {
|
||||
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["action"] = model.OAUTH_ACTION_SIGNUP
|
||||
if len(teamId) != 0 {
|
||||
stateProps["team_id"] = teamId
|
||||
}
|
||||
|
||||
if authUrl, err := GetAuthorizationCode(service, stateProps, ""); err != nil {
|
||||
if authUrl, err := GetAuthorizationCode(w, r, service, stateProps, ""); err != nil {
|
||||
return "", err
|
||||
} else {
|
||||
return authUrl, nil
|
||||
@@ -519,17 +527,69 @@ func CompleteSwitchWithOAuth(service string, userData io.ReadCloser, email strin
|
||||
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)
|
||||
if sso != nil && !sso.Enable {
|
||||
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
|
||||
endpoint := sso.AuthEndpoint
|
||||
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)))
|
||||
|
||||
redirectUri := utils.GetSiteURL() + "/signup/" + service + "/complete"
|
||||
@@ -547,7 +607,7 @@ func GetAuthorizationCode(service string, props map[string]string, loginHint str
|
||||
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)
|
||||
if sso == nil || !sso.Enable {
|
||||
return nil, "", nil, model.NewLocAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.unsupported.app_error", nil, "service="+service)
|
||||
@@ -562,10 +622,41 @@ func AuthorizeOAuthUser(service, code, state, redirectUri string) (io.ReadCloser
|
||||
|
||||
stateProps := model.MapFromJson(strings.NewReader(stateStr))
|
||||
|
||||
if stateProps["hash"] != utils.HashSha256(sso.Id) {
|
||||
return nil, "", nil, model.NewLocAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.invalid_state.app_error", nil, "")
|
||||
expectedToken, err := GetOAuthStateToken(stateProps["token"])
|
||||
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"]
|
||||
|
||||
p := url.Values{}
|
||||
@@ -617,7 +708,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 err *model.AppError
|
||||
if user, err = GetUserByEmail(email); err != nil {
|
||||
@@ -635,7 +726,7 @@ func SwitchEmailToOAuth(email, password, code, service string) (string, *model.A
|
||||
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 {
|
||||
if authUrl, err := GetAuthorizationCode(w, r, service, stateProps, ""); err != nil {
|
||||
return "", err
|
||||
} else {
|
||||
return authUrl, nil
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/platform/model"
|
||||
"github.com/mattermost/platform/utils"
|
||||
)
|
||||
|
||||
func TestOAuthRevokeAccessToken(t *testing.T) {
|
||||
@@ -42,3 +43,54 @@ func TestOAuthRevokeAccessToken(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOAuthDeleteApp(t *testing.T) {
|
||||
Setup()
|
||||
|
||||
oldSetting := utils.Cfg.ServiceSettings.EnableOAuthServiceProvider
|
||||
defer func() {
|
||||
utils.Cfg.ServiceSettings.EnableOAuthServiceProvider = oldSetting
|
||||
}()
|
||||
utils.Cfg.ServiceSettings.EnableOAuthServiceProvider = true
|
||||
|
||||
a1 := &model.OAuthApp{}
|
||||
a1.CreatorId = model.NewId()
|
||||
a1.Name = "TestApp" + model.NewId()
|
||||
a1.CallbackUrls = []string{"https://nowhere.com"}
|
||||
a1.Homepage = "https://nowhere.com"
|
||||
|
||||
var err *model.AppError
|
||||
a1, err = CreateOAuthApp(a1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
session := &model.Session{}
|
||||
session.CreateAt = model.GetMillis()
|
||||
session.UserId = model.NewId()
|
||||
session.Token = model.NewId()
|
||||
session.Roles = model.ROLE_SYSTEM_USER.Id
|
||||
session.IsOAuth = true
|
||||
session.SetExpireInDays(1)
|
||||
|
||||
session, _ = CreateSession(session)
|
||||
|
||||
accessData := &model.AccessData{}
|
||||
accessData.Token = session.Token
|
||||
accessData.UserId = session.UserId
|
||||
accessData.RedirectUri = "http://example.com"
|
||||
accessData.ClientId = a1.Id
|
||||
accessData.ExpiresAt = session.ExpiresAt
|
||||
|
||||
if result := <-Srv.Store.OAuth().SaveAccessData(accessData); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
}
|
||||
|
||||
if err := DeleteOAuthApp(a1.Id); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := GetSession(session.Token); err == nil {
|
||||
t.Fatal("should not get session from cache or db")
|
||||
}
|
||||
}
|
||||
|
||||
16
app/post.go
16
app/post.go
@@ -227,19 +227,19 @@ func SendEphemeralPost(teamId, userId string, post *model.Post) *model.Post {
|
||||
}
|
||||
|
||||
func UpdatePost(post *model.Post, safeUpdate bool) (*model.Post, *model.AppError) {
|
||||
if utils.IsLicensed {
|
||||
if *utils.Cfg.ServiceSettings.AllowEditPost == model.ALLOW_EDIT_POST_NEVER {
|
||||
err := model.NewAppError("UpdatePost", "api.post.update_post.permissions_denied.app_error", nil, "", http.StatusForbidden)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var oldPost *model.Post
|
||||
if result := <-Srv.Store.Post().Get(post.Id); result.Err != nil {
|
||||
return nil, result.Err
|
||||
} else {
|
||||
oldPost = result.Data.(*model.PostList).Posts[post.Id]
|
||||
|
||||
if utils.IsLicensed {
|
||||
if *utils.Cfg.ServiceSettings.AllowEditPost == model.ALLOW_EDIT_POST_NEVER && post.Message != oldPost.Message {
|
||||
err := model.NewAppError("UpdatePost", "api.post.update_post.permissions_denied.app_error", nil, "", http.StatusForbidden)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if oldPost == nil {
|
||||
err := model.NewAppError("UpdatePost", "api.post.update_post.find.app_error", nil, "id="+post.Id, http.StatusBadRequest)
|
||||
return nil, err
|
||||
@@ -256,7 +256,7 @@ func UpdatePost(post *model.Post, safeUpdate bool) (*model.Post, *model.AppError
|
||||
}
|
||||
|
||||
if utils.IsLicensed {
|
||||
if *utils.Cfg.ServiceSettings.AllowEditPost == model.ALLOW_EDIT_POST_TIME_LIMIT && model.GetMillis() > oldPost.CreateAt+int64(*utils.Cfg.ServiceSettings.PostEditTimeLimit*1000) {
|
||||
if *utils.Cfg.ServiceSettings.AllowEditPost == model.ALLOW_EDIT_POST_TIME_LIMIT && model.GetMillis() > oldPost.CreateAt+int64(*utils.Cfg.ServiceSettings.PostEditTimeLimit*1000) && post.Message != oldPost.Message {
|
||||
err := model.NewAppError("UpdatePost", "api.post.update_post.permissions_time_limit.app_error", map[string]interface{}{"timeLimit": *utils.Cfg.ServiceSettings.PostEditTimeLimit}, "", http.StatusBadRequest)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -68,7 +68,6 @@ func AddSamlPublicCertificate(fileData *multipart.FileHeader) *model.AppError {
|
||||
}
|
||||
|
||||
utils.SaveConfig(utils.CfgFileName, cfg)
|
||||
utils.LoadConfig(utils.CfgFileName)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -88,7 +87,6 @@ func AddSamlPrivateCertificate(fileData *multipart.FileHeader) *model.AppError {
|
||||
}
|
||||
|
||||
utils.SaveConfig(utils.CfgFileName, cfg)
|
||||
utils.LoadConfig(utils.CfgFileName)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -108,7 +106,6 @@ func AddSamlIdpCertificate(fileData *multipart.FileHeader) *model.AppError {
|
||||
}
|
||||
|
||||
utils.SaveConfig(utils.CfgFileName, cfg)
|
||||
utils.LoadConfig(utils.CfgFileName)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -144,7 +141,6 @@ func RemoveSamlPublicCertificate() *model.AppError {
|
||||
}
|
||||
|
||||
utils.SaveConfig(utils.CfgFileName, cfg)
|
||||
utils.LoadConfig(utils.CfgFileName)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -165,7 +161,6 @@ func RemoveSamlPrivateCertificate() *model.AppError {
|
||||
}
|
||||
|
||||
utils.SaveConfig(utils.CfgFileName, cfg)
|
||||
utils.LoadConfig(utils.CfgFileName)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -186,7 +181,6 @@ func RemoveSamlIdpCertificate() *model.AppError {
|
||||
}
|
||||
|
||||
utils.SaveConfig(utils.CfgFileName, cfg)
|
||||
utils.LoadConfig(utils.CfgFileName)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
Ссылка в новой задаче
Block a user