From eff65aa05c74e93533c2504b8141b0474011e68c Mon Sep 17 00:00:00 2001 From: Chris Date: Wed, 7 Feb 2018 11:05:46 -0600 Subject: [PATCH 01/15] ABC-132: sign error page parameters (#8197) * sign error page parameters * add comments --- api/api.go | 6 +++- api/context.go | 6 ++-- api/file.go | 4 +-- api4/file.go | 4 +-- api4/oauth.go | 24 ++++++++----- app/app.go | 22 +++++++----- app/config.go | 88 ++++++++++++++++++++++++++++++++++++++++++++++ app/config_test.go | 9 +++++ model/system.go | 23 +++++++++--- utils/api.go | 27 ++++++++++---- utils/api_test.go | 49 ++++++++++++++++++++++++++ web/web.go | 2 +- 12 files changed, 227 insertions(+), 37 deletions(-) create mode 100644 utils/api_test.go diff --git a/api/api.go b/api/api.go index 2d65bb2166..70f36db857 100644 --- a/api/api.go +++ b/api/api.go @@ -109,7 +109,7 @@ func Init(a *app.App, root *mux.Router) *API { api.InitReaction() // 404 on any api route before web.go has a chance to serve it - root.Handle("/api/{anything:.*}", http.HandlerFunc(Handle404)) + root.Handle("/api/{anything:.*}", http.HandlerFunc(api.Handle404)) a.InitEmailBatching() @@ -120,6 +120,10 @@ func Init(a *app.App, root *mux.Router) *API { return api } +func (api *API) Handle404(w http.ResponseWriter, r *http.Request) { + Handle404(api.App, w, r) +} + func ReturnStatusOK(w http.ResponseWriter) { m := make(map[string]string) m[model.STATUS] = model.STATUS_OK diff --git a/api/context.go b/api/context.go index b28a247317..a8ff2b6940 100644 --- a/api/context.go +++ b/api/context.go @@ -229,7 +229,7 @@ func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { if c.Err.StatusCode == http.StatusUnauthorized { http.Redirect(w, r, c.GetTeamURL()+"/?redirect="+url.QueryEscape(r.URL.Path), http.StatusTemporaryRedirect) } else { - utils.RenderWebError(c.Err, w, r) + utils.RenderWebAppError(w, r, c.Err, c.App.AsymmetricSigningKey()) } } @@ -434,7 +434,7 @@ func IsApiCall(r *http.Request) bool { return strings.Index(r.URL.Path, "/api/") == 0 } -func Handle404(w http.ResponseWriter, r *http.Request) { +func Handle404(a *app.App, w http.ResponseWriter, r *http.Request) { err := model.NewAppError("Handle404", "api.context.404.app_error", nil, "", http.StatusNotFound) l4g.Debug("%v: code=404 ip=%v", r.URL.Path, utils.GetIpAddress(r)) @@ -444,7 +444,7 @@ func Handle404(w http.ResponseWriter, r *http.Request) { err.DetailedError = "There doesn't appear to be an api call for the url='" + r.URL.Path + "'. Typo? are you missing a team_id or user_id as part of the url?" w.Write([]byte(err.ToJson())) } else { - utils.RenderWebError(err, w, r) + utils.RenderWebAppError(w, r, err, a.AsymmetricSigningKey()) } } diff --git a/api/file.go b/api/file.go index 2d626304e1..3b8984816d 100644 --- a/api/file.go +++ b/api/file.go @@ -174,12 +174,12 @@ func getPublicFile(c *Context, w http.ResponseWriter, r *http.Request) { if hash != correctHash { c.Err = model.NewAppError("getPublicFile", "api.file.get_file.public_invalid.app_error", nil, "", http.StatusBadRequest) - http.Redirect(w, r, c.GetSiteURLHeader()+"/error?message="+utils.T(c.Err.Message), http.StatusTemporaryRedirect) + utils.RenderWebAppError(w, r, c.Err, c.App.AsymmetricSigningKey()) return } } else { c.Err = model.NewAppError("getPublicFile", "api.file.get_file.public_invalid.app_error", nil, "", http.StatusBadRequest) - http.Redirect(w, r, c.GetSiteURLHeader()+"/error?message="+utils.T(c.Err.Message), http.StatusTemporaryRedirect) + utils.RenderWebAppError(w, r, c.Err, c.App.AsymmetricSigningKey()) return } diff --git a/api4/file.go b/api4/file.go index 48ee281fe2..acc4c78e58 100644 --- a/api4/file.go +++ b/api4/file.go @@ -281,13 +281,13 @@ func getPublicFile(c *Context, w http.ResponseWriter, r *http.Request) { if len(hash) == 0 { c.Err = model.NewAppError("getPublicFile", "api.file.get_file.public_invalid.app_error", nil, "", http.StatusBadRequest) - http.Redirect(w, r, c.GetSiteURLHeader()+"/error?message="+utils.T(c.Err.Message), http.StatusTemporaryRedirect) + utils.RenderWebAppError(w, r, c.Err, c.App.AsymmetricSigningKey()) return } if hash != app.GeneratePublicLinkHash(info.Id, *c.App.Config().FileSettings.PublicLinkSalt) { c.Err = model.NewAppError("getPublicFile", "api.file.get_file.public_invalid.app_error", nil, "", http.StatusBadRequest) - http.Redirect(w, r, c.GetSiteURLHeader()+"/error?message="+utils.T(c.Err.Message), http.StatusTemporaryRedirect) + utils.RenderWebAppError(w, r, c.Err, c.App.AsymmetricSigningKey()) return } diff --git a/api4/oauth.go b/api4/oauth.go index 655adaaeee..d0f43256a8 100644 --- a/api4/oauth.go +++ b/api4/oauth.go @@ -313,7 +313,7 @@ func deauthorizeOAuthApp(c *Context, w http.ResponseWriter, r *http.Request) { func authorizeOAuthPage(c *Context, w http.ResponseWriter, r *http.Request) { if !c.App.Config().ServiceSettings.EnableOAuthServiceProvider { err := model.NewAppError("authorizeOAuth", "api.oauth.authorize_oauth.disabled.app_error", nil, "", http.StatusNotImplemented) - utils.RenderWebError(err, w, r) + utils.RenderWebAppError(w, r, err, c.App.AsymmetricSigningKey()) return } @@ -326,13 +326,13 @@ func authorizeOAuthPage(c *Context, w http.ResponseWriter, r *http.Request) { } if err := authRequest.IsValid(); err != nil { - utils.RenderWebError(err, w, r) + utils.RenderWebAppError(w, r, err, c.App.AsymmetricSigningKey()) return } oauthApp, err := c.App.GetOAuthApp(authRequest.ClientId) if err != nil { - utils.RenderWebError(err, w, r) + utils.RenderWebAppError(w, r, err, c.App.AsymmetricSigningKey()) return } @@ -343,7 +343,8 @@ func authorizeOAuthPage(c *Context, w http.ResponseWriter, r *http.Request) { } if !oauthApp.IsValidRedirectURL(authRequest.RedirectUri) { - utils.RenderWebError(model.NewAppError("authorizeOAuthPage", "api.oauth.allow_oauth.redirect_callback.app_error", nil, "", http.StatusBadRequest), w, r) + err := model.NewAppError("authorizeOAuthPage", "api.oauth.allow_oauth.redirect_callback.app_error", nil, "", http.StatusBadRequest) + utils.RenderWebAppError(w, r, err, c.App.AsymmetricSigningKey()) return } @@ -360,7 +361,7 @@ func authorizeOAuthPage(c *Context, w http.ResponseWriter, r *http.Request) { redirectUrl, err := c.App.AllowOAuthAppAccessToUser(c.Session.UserId, authRequest) if err != nil { - utils.RenderWebError(err, w, r) + utils.RenderWebAppError(w, r, err, c.App.AsymmetricSigningKey()) return } @@ -441,7 +442,10 @@ func completeOAuth(c *Context, w http.ResponseWriter, r *http.Request) { code := r.URL.Query().Get("code") if len(code) == 0 { - http.Redirect(w, r, c.GetSiteURLHeader()+"/error?type=oauth_missing_code&service="+strings.Title(service), http.StatusTemporaryRedirect) + utils.RenderWebError(w, r, http.StatusTemporaryRedirect, url.Values{ + "type": []string{"oauth_missing_code"}, + "service": []string{strings.Title(service)}, + }, c.App.AsymmetricSigningKey()) return } @@ -462,7 +466,7 @@ func completeOAuth(c *Context, w http.ResponseWriter, r *http.Request) { if action == model.OAUTH_ACTION_MOBILE { w.Write([]byte(err.ToJson())) } else { - http.Redirect(w, r, c.GetSiteURLHeader()+"/error?message="+url.QueryEscape(err.Message), http.StatusTemporaryRedirect) + utils.RenderWebAppError(w, r, err, c.App.AsymmetricSigningKey()) } return } @@ -474,7 +478,7 @@ func completeOAuth(c *Context, w http.ResponseWriter, r *http.Request) { if action == model.OAUTH_ACTION_MOBILE { w.Write([]byte(err.ToJson())) } else { - http.Redirect(w, r, c.GetSiteURLHeader()+"/error?message="+url.QueryEscape(err.Message), http.StatusTemporaryRedirect) + utils.RenderWebAppError(w, r, err, c.App.AsymmetricSigningKey()) } return } @@ -559,7 +563,9 @@ func signupWithOAuth(c *Context, w http.ResponseWriter, r *http.Request) { } if !c.App.Config().TeamSettings.EnableUserCreation { - http.Redirect(w, r, c.GetSiteURLHeader()+"/error?message="+url.QueryEscape(utils.T("api.oauth.singup_with_oauth.disabled.app_error")), http.StatusTemporaryRedirect) + utils.RenderWebError(w, r, http.StatusBadRequest, url.Values{ + "message": []string{utils.T("api.oauth.singup_with_oauth.disabled.app_error")}, + }, c.App.AsymmetricSigningKey()) return } diff --git a/app/app.go b/app/app.go index 1e46d29d07..0b5efa76b3 100644 --- a/app/app.go +++ b/app/app.go @@ -4,6 +4,7 @@ package app import ( + "crypto/ecdsa" "html/template" "net" "net/http" @@ -60,13 +61,14 @@ type App struct { newStore func() store.Store - htmlTemplateWatcher *utils.HTMLTemplateWatcher - sessionCache *utils.Cache - roles map[string]*model.Role - configListenerId string - licenseListenerId string - disableConfigWatch bool - configWatcher *utils.ConfigWatcher + htmlTemplateWatcher *utils.HTMLTemplateWatcher + sessionCache *utils.Cache + roles map[string]*model.Role + configListenerId string + licenseListenerId string + disableConfigWatch bool + configWatcher *utils.ConfigWatcher + asymmetricSigningKey *ecdsa.PrivateKey pluginCommands []*PluginCommand pluginCommandsLock sync.RWMutex @@ -139,6 +141,10 @@ func New(options ...Option) (*App, error) { } app.Srv.Store = app.newStore() + if err := app.ensureAsymmetricSigningKey(); err != nil { + return nil, errors.Wrapf(err, "unable to ensure asymmetric signing key") + } + app.initJobs() app.initBuiltInPlugins() @@ -448,5 +454,5 @@ func (a *App) Handle404(w http.ResponseWriter, r *http.Request) { l4g.Debug("%v: code=404 ip=%v", r.URL.Path, utils.GetIpAddress(r)) - utils.RenderWebError(err, w, r) + utils.RenderWebAppError(w, r, err, a.AsymmetricSigningKey()) } diff --git a/app/config.go b/app/config.go index a2398f9e91..46426c442a 100644 --- a/app/config.go +++ b/app/config.go @@ -4,7 +4,12 @@ package app import ( + "crypto/ecdsa" + "crypto/elliptic" "crypto/md5" + "crypto/rand" + "crypto/x509" + "encoding/base64" "encoding/json" "fmt" "runtime/debug" @@ -116,8 +121,91 @@ func (a *App) InvokeConfigListeners(old, current *model.Config) { } } +// EnsureAsymmetricSigningKey ensures that an asymmetric signing key exists and future calls to +// AsymmetricSigningKey will always return a valid signing key. +func (a *App) ensureAsymmetricSigningKey() error { + if a.asymmetricSigningKey != nil { + return nil + } + + var key *model.SystemAsymmetricSigningKey + + result := <-a.Srv.Store.System().GetByName(model.SYSTEM_ASYMMETRIC_SIGNING_KEY) + if result.Err == nil { + if err := json.Unmarshal([]byte(result.Data.(*model.System).Value), &key); err != nil { + return err + } + } + + // If we don't already have a key, try to generate one. + if key == nil { + newECDSAKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return err + } + newKey := &model.SystemAsymmetricSigningKey{ + ECDSAKey: &model.SystemECDSAKey{ + Curve: "P-256", + X: newECDSAKey.X, + Y: newECDSAKey.Y, + D: newECDSAKey.D, + }, + } + system := &model.System{ + Name: model.SYSTEM_ASYMMETRIC_SIGNING_KEY, + } + v, err := json.Marshal(newKey) + if err != nil { + return err + } + system.Value = string(v) + if result = <-a.Srv.Store.System().Save(system); result.Err == nil { + // If we were able to save the key, use it, otherwise ignore the error. + key = newKey + } + } + + // If we weren't able to save a new key above, another server must have beat us to it. Get the + // key from the database, and if that fails, error out. + if key == nil { + result := <-a.Srv.Store.System().GetByName(model.SYSTEM_ASYMMETRIC_SIGNING_KEY) + if result.Err != nil { + return result.Err + } else if err := json.Unmarshal([]byte(result.Data.(*model.System).Value), &key); err != nil { + return err + } + } + + var curve elliptic.Curve + switch key.ECDSAKey.Curve { + case "P-256": + curve = elliptic.P256() + default: + return fmt.Errorf("unknown curve: " + key.ECDSAKey.Curve) + } + a.asymmetricSigningKey = &ecdsa.PrivateKey{ + PublicKey: ecdsa.PublicKey{ + Curve: curve, + X: key.ECDSAKey.X, + Y: key.ECDSAKey.Y, + }, + D: key.ECDSAKey.D, + } + a.regenerateClientConfig() + return nil +} + +// AsymmetricSigningKey will return a private key that can be used for asymmetric signing. +func (a *App) AsymmetricSigningKey() *ecdsa.PrivateKey { + return a.asymmetricSigningKey +} + func (a *App) regenerateClientConfig() { a.clientConfig = utils.GenerateClientConfig(a.Config(), a.DiagnosticId()) + if key := a.AsymmetricSigningKey(); key != nil { + der, _ := x509.MarshalPKIXPublicKey(&key.PublicKey) + a.clientConfig["AsymmetricSigningPublicKey"] = base64.StdEncoding.EncodeToString(der) + } clientConfigJSON, _ := json.Marshal(a.clientConfig) a.clientConfigHash = fmt.Sprintf("%x", md5.Sum(clientConfigJSON)) } diff --git a/app/config_test.go b/app/config_test.go index e3d50b9583..5ee999f0fc 100644 --- a/app/config_test.go +++ b/app/config_test.go @@ -6,6 +6,8 @@ package app import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/mattermost/mattermost-server/model" ) @@ -54,3 +56,10 @@ func TestConfigListener(t *testing.T) { t.Fatal("listener 2 should've been called") } } + +func TestAsymmetricSigningKey(t *testing.T) { + th := Setup().InitBasic() + defer th.TearDown() + assert.NotNil(t, th.App.AsymmetricSigningKey()) + assert.NotEmpty(t, th.App.ClientConfig()["AsymmetricSigningPublicKey"]) +} diff --git a/model/system.go b/model/system.go index 020c50858f..2a636b14fc 100644 --- a/model/system.go +++ b/model/system.go @@ -6,14 +6,16 @@ package model import ( "encoding/json" "io" + "math/big" ) const ( - SYSTEM_DIAGNOSTIC_ID = "DiagnosticId" - SYSTEM_RAN_UNIT_TESTS = "RanUnitTests" - SYSTEM_LAST_SECURITY_TIME = "LastSecurityTime" - SYSTEM_ACTIVE_LICENSE_ID = "ActiveLicenseId" - SYSTEM_LAST_COMPLIANCE_TIME = "LastComplianceTime" + SYSTEM_DIAGNOSTIC_ID = "DiagnosticId" + SYSTEM_RAN_UNIT_TESTS = "RanUnitTests" + SYSTEM_LAST_SECURITY_TIME = "LastSecurityTime" + SYSTEM_ACTIVE_LICENSE_ID = "ActiveLicenseId" + SYSTEM_LAST_COMPLIANCE_TIME = "LastComplianceTime" + SYSTEM_ASYMMETRIC_SIGNING_KEY = "AsymmetricSigningKey" ) type System struct { @@ -31,3 +33,14 @@ func SystemFromJson(data io.Reader) *System { json.NewDecoder(data).Decode(&o) return o } + +type SystemAsymmetricSigningKey struct { + ECDSAKey *SystemECDSAKey `json:"ecdsa_key,omitempty"` +} + +type SystemECDSAKey struct { + Curve string `json:"curve"` + X *big.Int `json:"x"` + Y *big.Int `json:"y"` + D *big.Int `json:"d,omitempty"` +} diff --git a/utils/api.go b/utils/api.go index 005c3284bf..51524074d7 100644 --- a/utils/api.go +++ b/utils/api.go @@ -4,6 +4,9 @@ package utils import ( + "crypto" + "crypto/rand" + "encoding/base64" "fmt" "html/template" "net/http" @@ -32,13 +35,25 @@ func OriginChecker(allowedOrigins string) func(*http.Request) bool { } } -func RenderWebError(err *model.AppError, w http.ResponseWriter, r *http.Request) { - status := http.StatusTemporaryRedirect - if err.StatusCode != http.StatusInternalServerError { - status = err.StatusCode - } +func RenderWebAppError(w http.ResponseWriter, r *http.Request, err *model.AppError, s crypto.Signer) { + RenderWebError(w, r, err.StatusCode, url.Values{ + "message": []string{err.Message}, + }, s) +} + +func RenderWebError(w http.ResponseWriter, r *http.Request, status int, params url.Values, s crypto.Signer) { + queryString := params.Encode() + + h := crypto.SHA256 + sum := h.New() + sum.Write([]byte("/error?" + queryString)) + signature, err := s.Sign(rand.Reader, sum.Sum(nil), h) + if err != nil { + http.Error(w, "", http.StatusInternalServerError) + return + } + destination := strings.TrimRight(GetSiteURL(), "/") + "/error?" + queryString + "&s=" + base64.URLEncoding.EncodeToString(signature) - destination := strings.TrimRight(GetSiteURL(), "/") + "/error?message=" + url.QueryEscape(err.Message) if status >= 300 && status < 400 { http.Redirect(w, r, destination, status) return diff --git a/utils/api_test.go b/utils/api_test.go new file mode 100644 index 0000000000..5e41c7bfef --- /dev/null +++ b/utils/api_test.go @@ -0,0 +1,49 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package utils + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/sha256" + "encoding/asn1" + "encoding/base64" + "math/big" + "net/http" + "net/http/httptest" + "net/url" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestRenderWebError(t *testing.T) { + r := httptest.NewRequest("GET", "http://foo", nil) + w := httptest.NewRecorder() + key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + require.NoError(t, err) + RenderWebError(w, r, http.StatusTemporaryRedirect, url.Values{ + "foo": []string{"bar"}, + }, key) + + resp := w.Result() + location, err := url.Parse(resp.Header.Get("Location")) + require.NoError(t, err) + require.NotEmpty(t, location.Query().Get("s")) + + type ecdsaSignature struct { + R, S *big.Int + } + var rs ecdsaSignature + s, err := base64.URLEncoding.DecodeString(location.Query().Get("s")) + require.NoError(t, err) + _, err = asn1.Unmarshal(s, &rs) + require.NoError(t, err) + + assert.Equal(t, "bar", location.Query().Get("foo")) + h := sha256.Sum256([]byte("/error?foo=bar")) + assert.True(t, ecdsa.Verify(&key.PublicKey, h[:], rs.R, rs.S)) +} diff --git a/web/web.go b/web/web.go index 321d83a756..e0edd1b7a9 100644 --- a/web/web.go +++ b/web/web.go @@ -94,7 +94,7 @@ func root(c *api.Context, w http.ResponseWriter, r *http.Request) { } if api.IsApiCall(r) { - api.Handle404(w, r) + api.Handle404(c.App, w, r) return } From 654fc2f7494889f00c236ec3eac8d37a887b1804 Mon Sep 17 00:00:00 2001 From: enahum Date: Wed, 7 Feb 2018 16:08:42 -0300 Subject: [PATCH 02/15] translations PR 20180205 (#8200) --- i18n/de.json | 12 ++++++------ i18n/ja.json | 2 +- i18n/pt-BR.json | 10 +++++----- i18n/ru.json | 2 +- i18n/tr.json | 6 +++--- i18n/zh-TW.json | 36 ++++++++++++++++++------------------ 6 files changed, 34 insertions(+), 34 deletions(-) diff --git a/i18n/de.json b/i18n/de.json index 0f326e221a..b506e70184 100644 --- a/i18n/de.json +++ b/i18n/de.json @@ -153,7 +153,7 @@ }, { "id": "api.channel.add_member.added", - "translation": "%v wurde von %v zum Kanal hinzugefügt" + "translation": "%v wurde von %v zum Kanal hinzugefügt." }, { "id": "api.channel.add_member.find_channel.app_error", @@ -337,7 +337,7 @@ }, { "id": "api.channel.post_update_channel_header_message_and_forget.post.error", - "translation": "Fehler beim Senden der Aktualisierung der Kanalüberschrift-Mitteilung" + "translation": "Fehler bei der Aktualisierung der Kanalüberschrift" }, { "id": "api.channel.post_update_channel_header_message_and_forget.removed", @@ -4644,7 +4644,7 @@ }, { "id": "model.config.is_valid.atmos_camo_image_proxy_options.app_error", - "translation": "Invalid atmos/camo image proxy options for service settings. Must be set to your shared key." + "translation": "Ungültiger atmos-/camo-Bild-Proxy-Typ für Diensteinstellungen. Muss auf ihren Shared Key gesetzt sein." }, { "id": "model.config.is_valid.cluster_email_batching.app_error", @@ -5404,7 +5404,7 @@ }, { "id": "model.user.is_valid.position.app_error", - "translation": "Ungültige Position: Darf nicht länger als 35 Zeichen sein." + "translation": "Ungültige Position: Darf nicht länger als 128 Zeichen sein." }, { "id": "model.user.is_valid.pwd.app_error", @@ -5768,7 +5768,7 @@ }, { "id": "store.sql_channel.get_unread.app_error", - "translation": "Die ungelesenen Mitteilungen des Kanals konnten nicht abgerufen werden" + "translation": "Die ungelesenen Nachrichten des Kanals konnten nicht abgerufen werden" }, { "id": "store.sql_channel.increment_mention_count.app_error", @@ -6592,7 +6592,7 @@ }, { "id": "store.sql_team.get_unread.app_error", - "translation": "Die ungelesenen Mitteilungen des Teams konnten nicht abgerufen werden" + "translation": "Die ungelesenen Nachrichten des Teams konnten nicht abgerufen werden" }, { "id": "store.sql_team.permanent_delete.app_error", diff --git a/i18n/ja.json b/i18n/ja.json index 19a88fda28..3017f94dca 100644 --- a/i18n/ja.json +++ b/i18n/ja.json @@ -5404,7 +5404,7 @@ }, { "id": "model.user.is_valid.position.app_error", - "translation": "不正な役職: 128文字以上にはできません" + "translation": "不正な役職: 128文字以下でなければなりません。" }, { "id": "model.user.is_valid.pwd.app_error", diff --git a/i18n/pt-BR.json b/i18n/pt-BR.json index 66ff2c49ba..b07cb452e0 100644 --- a/i18n/pt-BR.json +++ b/i18n/pt-BR.json @@ -153,7 +153,7 @@ }, { "id": "api.channel.add_member.added", - "translation": "%v adicionado ao canal por %v" + "translation": "%v adicionado ao canal por %v." }, { "id": "api.channel.add_member.find_channel.app_error", @@ -2184,7 +2184,7 @@ }, { "id": "api.team.add_user_to_team.added", - "translation": "%v adicionado a equipe por %v" + "translation": "%v adicionado a equipe por %v." }, { "id": "api.team.add_user_to_team.missing_parameter.app_error", @@ -4644,7 +4644,7 @@ }, { "id": "model.config.is_valid.atmos_camo_image_proxy_options.app_error", - "translation": "Invalid atmos/camo image proxy options for service settings. Must be set to your shared key." + "translation": "Opções inválidas de proxy de imagem atmos/camo nas configurações de serviço. Deve ser configurado para sua chave compartilhada." }, { "id": "model.config.is_valid.cluster_email_batching.app_error", @@ -4760,7 +4760,7 @@ }, { "id": "model.config.is_valid.image_proxy_type.app_error", - "translation": "Invalid image proxy type for service settings." + "translation": "Tipo de proxy de imagem inválido nas configurações de serviços." }, { "id": "model.config.is_valid.ldap_basedn", @@ -5404,7 +5404,7 @@ }, { "id": "model.user.is_valid.position.app_error", - "translation": "Posição inválida: não pode ter mais que 35 caracteres." + "translation": "Posição inválida: não pode ter mais que 128 caracteres." }, { "id": "model.user.is_valid.pwd.app_error", diff --git a/i18n/ru.json b/i18n/ru.json index efa6c4111f..b1e37d1917 100644 --- a/i18n/ru.json +++ b/i18n/ru.json @@ -2320,7 +2320,7 @@ }, { "id": "api.team.remove_user_from_team.removed", - "translation": "%v удален из команды." + "translation": " %v удален из команды." }, { "id": "api.team.signup_team.email_disabled.app_error", diff --git a/i18n/tr.json b/i18n/tr.json index 45b047512c..b95c9b5428 100644 --- a/i18n/tr.json +++ b/i18n/tr.json @@ -153,7 +153,7 @@ }, { "id": "api.channel.add_member.added", - "translation": "%v kanala %v tarafından eklendi" + "translation": "%v kanala %v tarafından eklendi." }, { "id": "api.channel.add_member.find_channel.app_error", @@ -2184,7 +2184,7 @@ }, { "id": "api.team.add_user_to_team.added", - "translation": "%v takıma %v tarafından eklendi" + "translation": "%v takıma %v tarafından eklendi." }, { "id": "api.team.add_user_to_team.missing_parameter.app_error", @@ -5404,7 +5404,7 @@ }, { "id": "model.user.is_valid.position.app_error", - "translation": "Konum geçersiz: 35 karakterden uzun olmamalıdır." + "translation": "Konum geçersiz: 128 karakterden kısa olmalıdır." }, { "id": "model.user.is_valid.pwd.app_error", diff --git a/i18n/zh-TW.json b/i18n/zh-TW.json index cbd7db862e..a7387d88f7 100644 --- a/i18n/zh-TW.json +++ b/i18n/zh-TW.json @@ -153,7 +153,7 @@ }, { "id": "api.channel.add_member.added", - "translation": "%v 由 %v 邀請加入頻道" + "translation": "%v 已被 %v 加入頻道。" }, { "id": "api.channel.add_member.find_channel.app_error", @@ -201,11 +201,11 @@ }, { "id": "api.channel.change_channel_privacy.private_to_public", - "translation": "This channel has been converted to a Public Channel and can be joined by any team member." + "translation": "此頻道已轉為公開頻道,任意團隊成員將可加入。" }, { "id": "api.channel.change_channel_privacy.public_to_private", - "translation": "This channel has been converted to a Private Channel." + "translation": "此頻道已轉為私人頻道。" }, { "id": "api.channel.create_channel.direct_channel.app_error", @@ -2184,7 +2184,7 @@ }, { "id": "api.team.add_user_to_team.added", - "translation": "%v 由 %v 邀請加入頻道" + "translation": "%v 已被 %v 加入頻道。" }, { "id": "api.team.add_user_to_team.missing_parameter.app_error", @@ -2320,7 +2320,7 @@ }, { "id": "api.team.remove_user_from_team.removed", - "translation": "%v 已從頻道中移除。" + "translation": "%v 已從團隊中移除。" }, { "id": "api.team.signup_team.email_disabled.app_error", @@ -3360,7 +3360,7 @@ }, { "id": "app.import.validate_post_import_data.create_at_zero.error", - "translation": "如果有提供訊息建立時間,該值不能為 0。" + "translation": "訊息建立時間不能為 0。" }, { "id": "app.import.validate_post_import_data.message_length.error", @@ -3380,51 +3380,51 @@ }, { "id": "app.import.validate_reaction_import_data.create_at_before_parent.error", - "translation": "Reaction CreateAt property must be greater than the parent post CreateAt." + "translation": "互動建立時間必須大於隸屬訊息的建立時間。" }, { "id": "app.import.validate_reaction_import_data.create_at_missing.error", - "translation": "缺少必要的訊息屬性:建立日期。" + "translation": "缺少必要的互動屬性:建立日期。" }, { "id": "app.import.validate_reaction_import_data.create_at_zero.error", - "translation": "如果有提供訊息建立時間,該值不能為 0。" + "translation": "互動建立時間不能為 0。" }, { "id": "app.import.validate_reaction_import_data.emoji_name_length.error", - "translation": "訊息屬性長度超過允許的最大長度" + "translation": "互動屬性 繪文字名稱 長度超過允許的最大長度" }, { "id": "app.import.validate_reaction_import_data.emoji_name_missing.error", - "translation": "缺少必要的訊息屬性:使用者。" + "translation": "缺少必要的互動屬性:繪文字名稱。" }, { "id": "app.import.validate_reaction_import_data.user_missing.error", - "translation": "缺少必要的訊息屬性:使用者。" + "translation": "缺少必要的互動屬性:使用者。" }, { "id": "app.import.validate_reply_import_data.create_at_before_parent.error", - "translation": "Reply CreateAt property must be greater than the parent post CreateAt." + "translation": "回應建立時間必須大於隸屬訊息的建立時間。" }, { "id": "app.import.validate_reply_import_data.create_at_missing.error", - "translation": "缺少必要的訊息屬性:建立日期。" + "translation": "缺少必要的回應屬性:建立日期。" }, { "id": "app.import.validate_reply_import_data.create_at_zero.error", - "translation": "如果有提供訊息建立時間,該值不能為 0。" + "translation": "回應建立時間不能為 0。" }, { "id": "app.import.validate_reply_import_data.message_length.error", - "translation": "訊息屬性長度超過允許的最大長度" + "translation": "回應訊息屬性長度超過允許的最大長度" }, { "id": "app.import.validate_reply_import_data.message_missing.error", - "translation": "缺少必要的訊息屬性:訊息。" + "translation": "缺少必要的回應屬性:訊息。" }, { "id": "app.import.validate_reply_import_data.user_missing.error", - "translation": "缺少必要的訊息屬性:使用者。" + "translation": "缺少必要的回應屬性:使用者。" }, { "id": "app.import.validate_team_import_data.allowed_domains_length.error", From 840892ab887680935df516f6942eb3563b7bf96b Mon Sep 17 00:00:00 2001 From: JoramWilander Date: Wed, 7 Feb 2018 16:21:22 -0500 Subject: [PATCH 03/15] Increase OAuth2 state parameter limit --- model/authorize.go | 2 +- model/authorize_test.go | 2 +- store/sqlstore/oauth_store.go | 2 +- store/sqlstore/upgrade.go | 1 + 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/model/authorize.go b/model/authorize.go index 2296e7e22b..9fd5afa70a 100644 --- a/model/authorize.go +++ b/model/authorize.go @@ -62,7 +62,7 @@ func (ad *AuthData) IsValid() *AppError { return NewAppError("AuthData.IsValid", "model.authorize.is_valid.redirect_uri.app_error", nil, "client_id="+ad.ClientId, http.StatusBadRequest) } - if len(ad.State) > 128 { + if len(ad.State) > 1024 { return NewAppError("AuthData.IsValid", "model.authorize.is_valid.state.app_error", nil, "client_id="+ad.ClientId, http.StatusBadRequest) } diff --git a/model/authorize_test.go b/model/authorize_test.go index 3f43a4fc39..81e0593050 100644 --- a/model/authorize_test.go +++ b/model/authorize_test.go @@ -115,7 +115,7 @@ func TestAuthIsValid(t *testing.T) { t.Fatal(err) } - ad.Scope = NewRandomString(129) + ad.Scope = NewRandomString(1025) if err := ad.IsValid(); err == nil { t.Fatal("Should have failed invalid Scope") } diff --git a/store/sqlstore/oauth_store.go b/store/sqlstore/oauth_store.go index 30a44b75fd..0a9bd82666 100644 --- a/store/sqlstore/oauth_store.go +++ b/store/sqlstore/oauth_store.go @@ -35,7 +35,7 @@ func NewSqlOAuthStore(sqlStore SqlStore) store.OAuthStore { tableAuth.ColMap("ClientId").SetMaxSize(26) tableAuth.ColMap("Code").SetMaxSize(128) tableAuth.ColMap("RedirectUri").SetMaxSize(256) - tableAuth.ColMap("State").SetMaxSize(128) + tableAuth.ColMap("State").SetMaxSize(1024) tableAuth.ColMap("Scope").SetMaxSize(128) tableAccess := db.AddTableWithName(model.AccessData{}, "OAuthAccessData").SetKeys(false, "Token") diff --git a/store/sqlstore/upgrade.go b/store/sqlstore/upgrade.go index 0de91f28bc..7c1522f257 100644 --- a/store/sqlstore/upgrade.go +++ b/store/sqlstore/upgrade.go @@ -343,6 +343,7 @@ func UpgradeDatabaseToVersion46(sqlStore SqlStore) { func UpgradeDatabaseToVersion47(sqlStore SqlStore) { if shouldPerformUpgrade(sqlStore, VERSION_4_6_0, VERSION_4_7_0) { sqlStore.AlterColumnTypeIfExists("Users", "Position", "varchar(128)", "varchar(128)") + sqlStore.AlterColumnTypeIfExists("OAuthAuthData", "State", "varchar(1024)", "varchar(1024)") saveSchemaVersion(sqlStore, VERSION_4_7_0) } } From 0f703a3368a0b16fcd48b474377f0dbd2144f366 Mon Sep 17 00:00:00 2001 From: Chris Date: Wed, 7 Feb 2018 16:20:51 -0600 Subject: [PATCH 04/15] Eliminate utils.SetLicense calls (#8217) * eliminate utils.SetLicense calls * test fix * another test fix * more test fixes --- api/channel_test.go | 245 ++++--------------------------------------- api/post_test.go | 42 +------- api/team_test.go | 42 +------- api/user_test.go | 15 +-- api/webhook_test.go | 38 +------ api4/channel_test.go | 148 +++----------------------- api4/post_test.go | 43 +------- api4/team_test.go | 71 ++----------- api4/user_test.go | 53 +--------- app/app.go | 3 + app/config.go | 8 -- app/license.go | 16 ++- app/session_test.go | 13 +-- model/license.go | 19 ++++ 14 files changed, 91 insertions(+), 665 deletions(-) diff --git a/api/channel_test.go b/api/channel_test.go index 5533105c3d..9268d90717 100644 --- a/api/channel_test.go +++ b/api/channel_test.go @@ -12,7 +12,6 @@ import ( "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/store" "github.com/mattermost/mattermost-server/store/sqlstore" - "github.com/mattermost/mattermost-server/utils" ) func TestCreateChannel(t *testing.T) { @@ -97,23 +96,9 @@ func TestCreateChannel(t *testing.T) { t.Fatal("Should have errored out on direct channel type") } - isLicensed := utils.IsLicensed() - license := utils.License() - restrictPublicChannel := *th.App.Config().TeamSettings.RestrictPublicChannelManagement - restrictPrivateChannel := *th.App.Config().TeamSettings.RestrictPrivateChannelManagement - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelManagement = restrictPublicChannel }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManagement = restrictPrivateChannel }) - utils.SetIsLicensed(isLicensed) - utils.SetLicense(license) - th.App.SetDefaultRolesBasedOnConfig() - }() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelManagement = model.PERMISSIONS_ALL }) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManagement = model.PERMISSIONS_ALL }) - th.App.SetDefaultRolesBasedOnConfig() - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() + th.App.SetLicense(model.NewTestLicense()) channel2 := &model.Channel{DisplayName: "Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} channel3 := &model.Channel{DisplayName: "Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_PRIVATE, TeamId: team.Id} @@ -130,7 +115,6 @@ func TestCreateChannel(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelCreation = model.PERMISSIONS_TEAM_ADMIN }) - th.App.SetDefaultRolesBasedOnConfig() th.LoginBasic2() channel2.Name = "zz" + model.NewId() + "a" @@ -160,7 +144,6 @@ func TestCreateChannel(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelCreation = model.PERMISSIONS_SYSTEM_ADMIN }) - th.App.SetDefaultRolesBasedOnConfig() channel2.Name = "zz" + model.NewId() + "a" channel3.Name = "zz" + model.NewId() + "a" @@ -181,9 +164,7 @@ func TestCreateChannel(t *testing.T) { } // Check that if unlicensed the policy restriction is not enforced. - utils.SetIsLicensed(false) - utils.SetLicense(nil) - th.App.SetDefaultRolesBasedOnConfig() + th.App.SetLicense(nil) channel4 := model.Channel{DisplayName: "Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} channel5 := model.Channel{DisplayName: "Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_PRIVATE, TeamId: team.Id} @@ -196,7 +177,6 @@ func TestCreateChannel(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelCreation = model.PERMISSIONS_ALL }) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelCreation = model.PERMISSIONS_ALL }) - th.App.SetDefaultRolesBasedOnConfig() } func TestCreateDirectChannel(t *testing.T) { @@ -367,23 +347,9 @@ func TestUpdateChannel(t *testing.T) { t.Fatal("should have failed - channel deleted") } - isLicensed := utils.IsLicensed() - license := utils.License() - restrictPublicChannel := *th.App.Config().TeamSettings.RestrictPublicChannelManagement - restrictPrivateChannel := *th.App.Config().TeamSettings.RestrictPrivateChannelManagement - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelManagement = restrictPublicChannel }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManagement = restrictPrivateChannel }) - utils.SetIsLicensed(isLicensed) - utils.SetLicense(license) - th.App.SetDefaultRolesBasedOnConfig() - }() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelManagement = model.PERMISSIONS_ALL }) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManagement = model.PERMISSIONS_ALL }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() + th.App.SetLicense(model.NewTestLicense()) channel2 := th.CreateChannel(Client, team) channel3 := th.CreatePrivateChannel(Client, team) @@ -404,14 +370,8 @@ func TestUpdateChannel(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelManagement = model.PERMISSIONS_CHANNEL_ADMIN - }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManagement = model.PERMISSIONS_CHANNEL_ADMIN }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() th.MakeUserChannelUser(th.BasicUser, channel2) th.MakeUserChannelUser(th.BasicUser, channel3) sqlstore.ClearChannelCaches() @@ -447,14 +407,9 @@ func TestUpdateChannel(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelManagement = model.PERMISSIONS_TEAM_ADMIN - }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManagement = model.PERMISSIONS_TEAM_ADMIN }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() + th.App.SetLicense(model.NewTestLicense()) if _, err := Client.UpdateChannel(channel2); err == nil { t.Fatal("should have errored not team admin") @@ -477,14 +432,8 @@ func TestUpdateChannel(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelManagement = model.PERMISSIONS_SYSTEM_ADMIN - }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManagement = model.PERMISSIONS_SYSTEM_ADMIN }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() if _, err := Client.UpdateChannel(channel2); err == nil { t.Fatal("should have errored not system admin") @@ -503,9 +452,7 @@ func TestUpdateChannel(t *testing.T) { } // Check that if unlicensed the policy restriction is not enforced. - utils.SetIsLicensed(false) - utils.SetLicense(nil) - th.App.SetDefaultRolesBasedOnConfig() + th.App.SetLicense(nil) if _, err := Client.UpdateChannel(channel2); err != nil { t.Fatal(err) @@ -618,23 +565,9 @@ func TestUpdateChannelHeader(t *testing.T) { t.Fatal("should have errored non-channel member trying to update header") } - isLicensed := utils.IsLicensed() - license := utils.License() - restrictPublicChannel := *th.App.Config().TeamSettings.RestrictPublicChannelManagement - restrictPrivateChannel := *th.App.Config().TeamSettings.RestrictPrivateChannelManagement - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelManagement = restrictPublicChannel }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManagement = restrictPrivateChannel }) - utils.SetIsLicensed(isLicensed) - utils.SetLicense(license) - th.App.SetDefaultRolesBasedOnConfig() - }() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelManagement = model.PERMISSIONS_ALL }) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManagement = model.PERMISSIONS_ALL }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() + th.App.SetLicense(model.NewTestLicense()) th.LoginBasic() channel2 := th.CreateChannel(Client, team) @@ -659,11 +592,8 @@ func TestUpdateChannelHeader(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelManagement = model.PERMISSIONS_CHANNEL_ADMIN - }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManagement = model.PERMISSIONS_CHANNEL_ADMIN }) - th.App.SetDefaultRolesBasedOnConfig() th.MakeUserChannelUser(th.BasicUser, channel2) th.MakeUserChannelUser(th.BasicUser, channel3) sqlstore.ClearChannelCaches() @@ -688,11 +618,8 @@ func TestUpdateChannelHeader(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelManagement = model.PERMISSIONS_TEAM_ADMIN - }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManagement = model.PERMISSIONS_TEAM_ADMIN }) - th.App.SetDefaultRolesBasedOnConfig() if _, err := Client.UpdateChannelHeader(data2); err == nil { t.Fatal("should have errored not team admin") @@ -715,11 +642,8 @@ func TestUpdateChannelHeader(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelManagement = model.PERMISSIONS_SYSTEM_ADMIN - }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManagement = model.PERMISSIONS_SYSTEM_ADMIN }) - th.App.SetDefaultRolesBasedOnConfig() if _, err := Client.UpdateChannelHeader(data2); err == nil { t.Fatal("should have errored not system admin") @@ -741,9 +665,7 @@ func TestUpdateChannelHeader(t *testing.T) { } // Check that if unlicensed the policy restriction is not enforced. - utils.SetIsLicensed(false) - utils.SetLicense(nil) - th.App.SetDefaultRolesBasedOnConfig() + th.App.SetLicense(nil) if _, err := SystemAdminClient.UpdateChannelHeader(data2); err != nil { t.Fatal(err) @@ -814,23 +736,9 @@ func TestUpdateChannelPurpose(t *testing.T) { t.Fatal("should have errored non-channel member trying to update purpose") } - isLicensed := utils.IsLicensed() - license := utils.License() - restrictPublicChannel := *th.App.Config().TeamSettings.RestrictPublicChannelManagement - restrictPrivateChannel := *th.App.Config().TeamSettings.RestrictPrivateChannelManagement - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelManagement = restrictPublicChannel }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManagement = restrictPrivateChannel }) - utils.SetIsLicensed(isLicensed) - utils.SetLicense(license) - th.App.SetDefaultRolesBasedOnConfig() - }() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelManagement = model.PERMISSIONS_ALL }) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManagement = model.PERMISSIONS_ALL }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() + th.App.SetLicense(model.NewTestLicense()) th.LoginBasic() channel2 := th.CreateChannel(Client, team) @@ -855,11 +763,8 @@ func TestUpdateChannelPurpose(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelManagement = model.PERMISSIONS_CHANNEL_ADMIN - }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManagement = model.PERMISSIONS_CHANNEL_ADMIN }) - th.App.SetDefaultRolesBasedOnConfig() th.MakeUserChannelUser(th.BasicUser, channel2) th.MakeUserChannelUser(th.BasicUser, channel3) sqlstore.ClearChannelCaches() @@ -884,11 +789,8 @@ func TestUpdateChannelPurpose(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelManagement = model.PERMISSIONS_TEAM_ADMIN - }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManagement = model.PERMISSIONS_TEAM_ADMIN }) - th.App.SetDefaultRolesBasedOnConfig() if _, err := Client.UpdateChannelPurpose(data2); err == nil { t.Fatal("should have errored not team admin") @@ -911,11 +813,8 @@ func TestUpdateChannelPurpose(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelManagement = model.PERMISSIONS_SYSTEM_ADMIN - }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManagement = model.PERMISSIONS_SYSTEM_ADMIN }) - th.App.SetDefaultRolesBasedOnConfig() if _, err := Client.UpdateChannelPurpose(data2); err == nil { t.Fatal("should have errored not system admin") @@ -937,9 +836,7 @@ func TestUpdateChannelPurpose(t *testing.T) { } // Check that if unlicensed the policy restriction is not enforced. - utils.SetIsLicensed(false) - utils.SetLicense(nil) - th.App.SetDefaultRolesBasedOnConfig() + th.App.SetLicense(nil) if _, err := SystemAdminClient.UpdateChannelHeader(data2); err != nil { t.Fatal(err) } @@ -1400,23 +1297,9 @@ func TestDeleteChannel(t *testing.T) { t.Fatal("should have failed - channel already deleted") } - isLicensed := utils.IsLicensed() - license := utils.License() - restrictPublicChannel := *th.App.Config().TeamSettings.RestrictPublicChannelManagement - restrictPrivateChannel := *th.App.Config().TeamSettings.RestrictPrivateChannelManagement - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelManagement = restrictPublicChannel }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManagement = restrictPrivateChannel }) - utils.SetIsLicensed(isLicensed) - utils.SetLicense(license) - th.App.SetDefaultRolesBasedOnConfig() - }() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelManagement = model.PERMISSIONS_ALL }) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManagement = model.PERMISSIONS_ALL }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() + th.App.SetLicense(model.NewTestLicense()) th.LoginSystemAdmin() th.LinkUserToTeam(th.BasicUser, team) @@ -1438,16 +1321,10 @@ func TestDeleteChannel(t *testing.T) { t.Fatal(err) } - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelDeletion = model.PERMISSIONS_CHANNEL_ADMIN - }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelDeletion = model.PERMISSIONS_CHANNEL_ADMIN }) - th.App.SetDefaultRolesBasedOnConfig() th.LoginSystemAdmin() @@ -1498,16 +1375,11 @@ func TestDeleteChannel(t *testing.T) { th.UpdateUserToNonTeamAdmin(th.BasicUser, team) th.App.InvalidateAllCaches() - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() + th.App.SetLicense(model.NewTestLicense()) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelDeletion = model.PERMISSIONS_TEAM_ADMIN - }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelDeletion = model.PERMISSIONS_TEAM_ADMIN }) - th.App.SetDefaultRolesBasedOnConfig() th.LoginSystemAdmin() @@ -1537,16 +1409,10 @@ func TestDeleteChannel(t *testing.T) { t.Fatal(err) } - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelDeletion = model.PERMISSIONS_SYSTEM_ADMIN - }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelDeletion = model.PERMISSIONS_SYSTEM_ADMIN }) - th.App.SetDefaultRolesBasedOnConfig() th.LoginSystemAdmin() @@ -1578,9 +1444,7 @@ func TestDeleteChannel(t *testing.T) { } // Check that if unlicensed the policy restriction is not enforced. - utils.SetIsLicensed(false) - utils.SetLicense(nil) - th.App.SetDefaultRolesBasedOnConfig() + th.App.SetLicense(nil) channel2 = th.CreateChannel(Client, team) channel3 = th.CreatePrivateChannel(Client, team) @@ -1595,10 +1459,6 @@ func TestDeleteChannel(t *testing.T) { if _, err := Client.DeleteChannel(channel3.Id); err != nil { t.Fatal(err) } - - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelDeletion = model.PERMISSIONS_ALL }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelDeletion = model.PERMISSIONS_ALL }) - th.App.SetDefaultRolesBasedOnConfig() } func TestGetChannelStats(t *testing.T) { @@ -1675,16 +1535,9 @@ func TestAddChannelMember(t *testing.T) { } // Test policy does not apply to TE. - restrictPrivateChannel := *th.App.Config().TeamSettings.RestrictPrivateChannelManageMembers - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPrivateChannelManageMembers = restrictPrivateChannel - }) - }() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManageMembers = model.PERMISSIONS_CHANNEL_ADMIN }) - th.App.SetDefaultRolesBasedOnConfig() channel3 := &model.Channel{DisplayName: "A Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_PRIVATE, TeamId: team.Id} channel3 = Client.Must(th.SystemAdminClient.CreateChannel(channel3)).Data.(*model.Channel) @@ -1694,18 +1547,8 @@ func TestAddChannelMember(t *testing.T) { } // Add a license - isLicensed := utils.IsLicensed() - license := utils.License() - defer func() { - utils.SetIsLicensed(isLicensed) - utils.SetLicense(license) - th.App.SetDefaultRolesBasedOnConfig() - }() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManageMembers = model.PERMISSIONS_ALL }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() + th.App.SetLicense(model.NewTestLicense()) // Check that a regular channel user can add other users. channel4 := &model.Channel{DisplayName: "A Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_PRIVATE, TeamId: team.Id} @@ -1719,10 +1562,6 @@ func TestAddChannelMember(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManageMembers = model.PERMISSIONS_CHANNEL_ADMIN }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() channel5 := &model.Channel{DisplayName: "A Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_PRIVATE, TeamId: team.Id} channel5 = Client.Must(th.SystemAdminClient.CreateChannel(channel5)).Data.(*model.Channel) @@ -1733,10 +1572,7 @@ func TestAddChannelMember(t *testing.T) { th.MakeUserChannelAdmin(user1, channel5) th.App.InvalidateAllCaches() - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() + th.App.SetLicense(model.NewTestLicense()) if _, err := Client.AddChannelMember(channel5.Id, user2.Id); err != nil { t.Fatal(err) @@ -1746,10 +1582,6 @@ func TestAddChannelMember(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManageMembers = model.PERMISSIONS_TEAM_ADMIN }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() channel6 := &model.Channel{DisplayName: "A Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_PRIVATE, TeamId: team.Id} channel6 = Client.Must(th.SystemAdminClient.CreateChannel(channel6)).Data.(*model.Channel) @@ -1760,10 +1592,7 @@ func TestAddChannelMember(t *testing.T) { th.UpdateUserToTeamAdmin(user1, team) th.App.InvalidateAllCaches() - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() + th.App.SetLicense(model.NewTestLicense()) if _, err := Client.AddChannelMember(channel6.Id, user2.Id); err != nil { t.Fatal(err) @@ -1773,10 +1602,6 @@ func TestAddChannelMember(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManageMembers = model.PERMISSIONS_SYSTEM_ADMIN }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() channel7 := &model.Channel{DisplayName: "A Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_PRIVATE, TeamId: team.Id} channel7 = Client.Must(th.SystemAdminClient.CreateChannel(channel7)).Data.(*model.Channel) @@ -1860,16 +1685,9 @@ func TestRemoveChannelMember(t *testing.T) { th.LoginBasic() // Test policy does not apply to TE. - restrictPrivateChannel := *th.App.Config().TeamSettings.RestrictPrivateChannelManageMembers - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPrivateChannelManageMembers = restrictPrivateChannel - }) - }() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManageMembers = model.PERMISSIONS_CHANNEL_ADMIN }) - th.App.SetDefaultRolesBasedOnConfig() channel3 := &model.Channel{DisplayName: "A Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_PRIVATE, TeamId: team.Id} channel3 = Client.Must(th.SystemAdminClient.CreateChannel(channel3)).Data.(*model.Channel) @@ -1880,18 +1698,8 @@ func TestRemoveChannelMember(t *testing.T) { } // Add a license - isLicensed := utils.IsLicensed() - license := utils.License() - defer func() { - utils.SetIsLicensed(isLicensed) - utils.SetLicense(license) - th.App.SetDefaultRolesBasedOnConfig() - }() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManageMembers = model.PERMISSIONS_ALL }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() + th.App.SetLicense(model.NewTestLicense()) // Check that a regular channel user can remove other users. channel4 := &model.Channel{DisplayName: "A Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_PRIVATE, TeamId: team.Id} @@ -1906,10 +1714,6 @@ func TestRemoveChannelMember(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManageMembers = model.PERMISSIONS_CHANNEL_ADMIN }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() channel5 := &model.Channel{DisplayName: "A Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_PRIVATE, TeamId: team.Id} channel5 = Client.Must(th.SystemAdminClient.CreateChannel(channel5)).Data.(*model.Channel) @@ -1921,9 +1725,7 @@ func TestRemoveChannelMember(t *testing.T) { th.MakeUserChannelAdmin(user1, channel5) th.App.InvalidateAllCaches() - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() + th.App.SetLicense(model.NewTestLicense()) if _, err := Client.RemoveChannelMember(channel5.Id, user2.Id); err != nil { t.Fatal(err) @@ -1933,10 +1735,6 @@ func TestRemoveChannelMember(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManageMembers = model.PERMISSIONS_TEAM_ADMIN }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() channel6 := &model.Channel{DisplayName: "A Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_PRIVATE, TeamId: team.Id} channel6 = Client.Must(th.SystemAdminClient.CreateChannel(channel6)).Data.(*model.Channel) @@ -1948,10 +1746,7 @@ func TestRemoveChannelMember(t *testing.T) { th.UpdateUserToTeamAdmin(user1, team) th.App.InvalidateAllCaches() - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() + th.App.SetLicense(model.NewTestLicense()) if _, err := Client.RemoveChannelMember(channel6.Id, user2.Id); err != nil { t.Fatal(err) @@ -1961,10 +1756,6 @@ func TestRemoveChannelMember(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManageMembers = model.PERMISSIONS_SYSTEM_ADMIN }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() channel7 := &model.Channel{DisplayName: "A Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_PRIVATE, TeamId: team.Id} channel7 = Client.Must(th.SystemAdminClient.CreateChannel(channel7)).Data.(*model.Channel) diff --git a/api/post_test.go b/api/post_test.go index 299fdf0462..2fc79d9b12 100644 --- a/api/post_test.go +++ b/api/post_test.go @@ -160,20 +160,8 @@ func TestCreatePost(t *testing.T) { } } - isLicensed := utils.IsLicensed() - license := utils.License() - disableTownSquareReadOnly := th.App.Config().TeamSettings.ExperimentalTownSquareIsReadOnly - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { cfg.TeamSettings.ExperimentalTownSquareIsReadOnly = disableTownSquareReadOnly }) - utils.SetIsLicensed(isLicensed) - utils.SetLicense(license) - th.App.SetDefaultRolesBasedOnConfig() - }() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.ExperimentalTownSquareIsReadOnly = true }) - th.App.SetDefaultRolesBasedOnConfig() - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() + th.App.SetLicense(model.NewTestLicense()) defaultChannel := store.Must(th.App.Srv.Store.Channel().GetByName(team.Id, model.DEFAULT_CHANNEL, true)).(*model.Channel) defaultPost := &model.Post{ @@ -466,16 +454,7 @@ func TestUpdatePost(t *testing.T) { } // Test licensed policy controls for edit post - isLicensed := utils.IsLicensed() - license := utils.License() - defer func() { - utils.SetIsLicensed(isLicensed) - utils.SetLicense(license) - }() - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - + th.App.SetLicense(model.NewTestLicense()) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.AllowEditPost = model.ALLOW_EDIT_POST_NEVER }) post4 := &model.Post{ChannelId: channel1.Id, Message: "zz" + model.NewId() + "a", RootId: rpost1.Data.(*model.Post).Id} @@ -966,7 +945,6 @@ func TestDeletePosts(t *testing.T) { team1 := th.BasicTeam th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.RestrictPostDelete = model.PERMISSIONS_DELETE_POST_ALL }) - th.App.SetDefaultRolesBasedOnConfig() time.Sleep(10 * time.Millisecond) post1 := &model.Post{ChannelId: channel1.Id, Message: "zz" + model.NewId() + "a"} @@ -1021,15 +999,7 @@ func TestDeletePosts(t *testing.T) { } // Test licensed policy controls for delete post - isLicensed := utils.IsLicensed() - license := utils.License() - defer func() { - utils.SetIsLicensed(isLicensed) - utils.SetLicense(license) - }() - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() + th.App.SetLicense(model.NewTestLicense()) th.UpdateUserToTeamAdmin(th.BasicUser2, th.BasicTeam) @@ -1044,7 +1014,6 @@ func TestDeletePosts(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.RestrictPostDelete = model.PERMISSIONS_DELETE_POST_TEAM_ADMIN }) - th.App.SetDefaultRolesBasedOnConfig() th.LoginBasic() @@ -1069,7 +1038,6 @@ func TestDeletePosts(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.RestrictPostDelete = model.PERMISSIONS_DELETE_POST_SYSTEM_ADMIN }) - th.App.SetDefaultRolesBasedOnConfig() th.LoginBasic() @@ -1088,9 +1056,7 @@ func TestDeletePosts(t *testing.T) { } // Check that if unlicensed the policy restriction is not enforced. - utils.SetIsLicensed(false) - utils.SetLicense(nil) - th.App.SetDefaultRolesBasedOnConfig() + th.App.SetLicense(nil) time.Sleep(10 * time.Millisecond) post7 := &model.Post{ChannelId: channel1.Id, Message: "zz" + model.NewId() + "a"} diff --git a/api/team_test.go b/api/team_test.go index b1c8925445..696cf31bbd 100644 --- a/api/team_test.go +++ b/api/team_test.go @@ -139,20 +139,8 @@ func TestAddUserToTeam(t *testing.T) { t.Fatal(err) } - // Restore config/license at end of test case. - restrictTeamInvite := *th.App.Config().TeamSettings.RestrictTeamInvite - isLicensed := utils.IsLicensed() - license := utils.License() - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictTeamInvite = restrictTeamInvite }) - utils.SetIsLicensed(isLicensed) - utils.SetLicense(license) - th.App.SetDefaultRolesBasedOnConfig() - }() - // Set the config so that only team admins can add a user to a team. th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictTeamInvite = model.PERMISSIONS_TEAM_ADMIN }) - th.App.SetDefaultRolesBasedOnConfig() // Test without the EE license to see that the permission restriction is ignored. user3 := th.CreateUser(th.BasicClient) @@ -161,10 +149,7 @@ func TestAddUserToTeam(t *testing.T) { } // Add an EE license. - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() + th.App.SetLicense(model.NewTestLicense()) // Check that a regular user can't add someone to the team. user4 := th.CreateUser(th.BasicClient) @@ -175,11 +160,8 @@ func TestAddUserToTeam(t *testing.T) { // Should work as team admin. th.UpdateUserToTeamAdmin(th.BasicUser, th.BasicTeam) th.App.InvalidateAllCaches() + th.App.SetLicense(model.NewTestLicense()) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictTeamInvite = model.PERMISSIONS_TEAM_ADMIN }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() user5 := th.CreateUser(th.BasicClient) if _, err := th.BasicClient.AddUserToTeam(th.BasicTeam.Id, user5.Id); err != nil { @@ -188,7 +170,6 @@ func TestAddUserToTeam(t *testing.T) { // Change permission level to System Admin th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictTeamInvite = model.PERMISSIONS_SYSTEM_ADMIN }) - th.App.SetDefaultRolesBasedOnConfig() // Should not work as team admin. user6 := th.CreateUser(th.BasicClient) @@ -565,13 +546,7 @@ func TestInviteMembers(t *testing.T) { t.Fatal("Should have errored out on no invites to send") } - restrictTeamInvite := *th.App.Config().TeamSettings.RestrictTeamInvite - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictTeamInvite = restrictTeamInvite }) - th.App.SetDefaultRolesBasedOnConfig() - }() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictTeamInvite = model.PERMISSIONS_TEAM_ADMIN }) - th.App.SetDefaultRolesBasedOnConfig() th.LoginBasic2() th.LinkUserToTeam(th.BasicUser2, team) @@ -580,17 +555,7 @@ func TestInviteMembers(t *testing.T) { t.Fatal(err) } - isLicensed := utils.IsLicensed() - license := utils.License() - defer func() { - utils.SetIsLicensed(isLicensed) - utils.SetLicense(license) - th.App.SetDefaultRolesBasedOnConfig() - }() - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() + th.App.SetLicense(model.NewTestLicense()) if _, err := Client.InviteMembers(invites); err == nil { t.Fatal("should have errored not team admin and licensed") @@ -606,7 +571,6 @@ func TestInviteMembers(t *testing.T) { } th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictTeamInvite = model.PERMISSIONS_SYSTEM_ADMIN }) - th.App.SetDefaultRolesBasedOnConfig() if _, err := Client.InviteMembers(invites); err == nil { t.Fatal("should have errored not system admin and licensed") diff --git a/api/user_test.go b/api/user_test.go index 8d6aad22b6..f65d7c45b7 100644 --- a/api/user_test.go +++ b/api/user_test.go @@ -1889,17 +1889,7 @@ func TestUpdateMfa(t *testing.T) { Client := th.BasicClient - isLicensed := utils.IsLicensed() - license := utils.License() - defer func() { - utils.SetIsLicensed(isLicensed) - utils.SetLicense(license) - }() - utils.SetIsLicensed(false) - utils.SetLicense(&model.License{Features: &model.Features{}}) - if utils.License().Features.MFA == nil { - utils.License().Features.MFA = new(bool) - } + th.App.SetLicense(nil) team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} rteam, _ := Client.CreateTeam(&team) @@ -1925,8 +1915,7 @@ func TestUpdateMfa(t *testing.T) { t.Fatal("should have failed - not licensed") } - utils.SetIsLicensed(true) - *utils.License().Features.MFA = true + th.App.SetLicense(model.NewTestLicense("mfa")) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableMultifactorAuthentication = true }) if _, err := Client.UpdateMfa(true, "123456"); err == nil { diff --git a/api/webhook_test.go b/api/webhook_test.go index b5a8366037..f4d46496bc 100644 --- a/api/webhook_test.go +++ b/api/webhook_test.go @@ -9,7 +9,6 @@ import ( "testing" "github.com/mattermost/mattermost-server/model" - "github.com/mattermost/mattermost-server/utils" ) func TestCreateIncomingHook(t *testing.T) { @@ -93,7 +92,6 @@ func TestCreateIncomingHook(t *testing.T) { } th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = false }) - th.App.SetDefaultRolesBasedOnConfig() if _, err := Client.CreateIncomingWebhook(hook); err != nil { t.Fatal(err) @@ -133,7 +131,6 @@ func TestUpdateIncomingHook(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableIncomingWebhooks = true }) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = true }) - th.App.SetDefaultRolesBasedOnConfig() hook := createIncomingWebhook(channel1.Id, Client, t) @@ -217,7 +214,6 @@ func TestUpdateIncomingHook(t *testing.T) { t.Run("OnlyAdminIntegrationsDisabled", func(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = false }) - th.App.SetDefaultRolesBasedOnConfig() t.Run("UpdateHookOfSameUser", func(t *testing.T) { sameUserHook := &model.IncomingWebhook{ChannelId: channel1.Id, UserId: user2.Id} @@ -240,7 +236,6 @@ func TestUpdateIncomingHook(t *testing.T) { }) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = true }) - th.App.SetDefaultRolesBasedOnConfig() Client.Logout() th.UpdateUserToTeamAdmin(user2, team) @@ -324,7 +319,6 @@ func TestListIncomingHooks(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableIncomingWebhooks = true }) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = true }) - th.App.SetDefaultRolesBasedOnConfig() hook1 := &model.IncomingWebhook{ChannelId: channel1.Id} hook1 = Client.Must(Client.CreateIncomingWebhook(hook1)).Data.(*model.IncomingWebhook) @@ -351,7 +345,6 @@ func TestListIncomingHooks(t *testing.T) { } th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = false }) - th.App.SetDefaultRolesBasedOnConfig() if _, err := Client.ListIncomingWebhooks(); err != nil { t.Fatal(err) @@ -376,7 +369,6 @@ func TestDeleteIncomingHook(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableIncomingWebhooks = true }) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = true }) - th.App.SetDefaultRolesBasedOnConfig() hook := &model.IncomingWebhook{ChannelId: channel1.Id} hook = Client.Must(Client.CreateIncomingWebhook(hook)).Data.(*model.IncomingWebhook) @@ -410,7 +402,6 @@ func TestDeleteIncomingHook(t *testing.T) { } th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = false }) - th.App.SetDefaultRolesBasedOnConfig() if _, err := Client.DeleteIncomingWebhook(hook.Id); err == nil { t.Fatal("should have failed - not creator or team admin") @@ -447,7 +438,6 @@ func TestCreateOutgoingHook(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOutgoingWebhooks = true }) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = true }) - th.App.SetDefaultRolesBasedOnConfig() hook := &model.OutgoingWebhook{ChannelId: channel1.Id, CallbackURLs: []string{"http://nowhere.com"}} @@ -518,7 +508,6 @@ func TestCreateOutgoingHook(t *testing.T) { } th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = false }) - th.App.SetDefaultRolesBasedOnConfig() if _, err := Client.CreateOutgoingWebhook(hook); err != nil { t.Fatal(err) @@ -551,7 +540,6 @@ func TestListOutgoingHooks(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOutgoingWebhooks = true }) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = true }) - th.App.SetDefaultRolesBasedOnConfig() hook1 := &model.OutgoingWebhook{ChannelId: channel1.Id, CallbackURLs: []string{"http://nowhere.com"}} hook1 = Client.Must(Client.CreateOutgoingWebhook(hook1)).Data.(*model.OutgoingWebhook) @@ -578,7 +566,6 @@ func TestListOutgoingHooks(t *testing.T) { } th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = false }) - th.App.SetDefaultRolesBasedOnConfig() if _, err := Client.ListOutgoingWebhooks(); err != nil { t.Fatal(err) @@ -609,7 +596,6 @@ func TestUpdateOutgoingHook(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOutgoingWebhooks = true }) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = true }) - th.App.SetDefaultRolesBasedOnConfig() hook := createOutgoingWebhook(channel1.Id, []string{"http://nowhere.com"}, []string{"cats"}, Client, t) createOutgoingWebhook(channel1.Id, []string{"http://nowhere.com"}, []string{"dogs"}, Client, t) @@ -683,7 +669,6 @@ func TestUpdateOutgoingHook(t *testing.T) { } th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = false }) - th.App.SetDefaultRolesBasedOnConfig() hook2 := createOutgoingWebhook(channel1.Id, []string{"http://nowhereelse.com"}, []string{"dogs"}, Client, t) if _, err := Client.UpdateOutgoingWebhook(hook2); err != nil { @@ -691,7 +676,6 @@ func TestUpdateOutgoingHook(t *testing.T) { } th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = true }) - th.App.SetDefaultRolesBasedOnConfig() Client.Logout() th.LinkUserToTeam(user3, team) @@ -779,7 +763,6 @@ func TestDeleteOutgoingHook(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOutgoingWebhooks = true }) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = true }) - th.App.SetDefaultRolesBasedOnConfig() hook := &model.OutgoingWebhook{ChannelId: channel1.Id, CallbackURLs: []string{"http://nowhere.com"}} hook = Client.Must(Client.CreateOutgoingWebhook(hook)).Data.(*model.OutgoingWebhook) @@ -813,7 +796,6 @@ func TestDeleteOutgoingHook(t *testing.T) { } th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = false }) - th.App.SetDefaultRolesBasedOnConfig() if _, err := Client.DeleteOutgoingWebhook(hook.Id); err == nil { t.Fatal("should have failed - not creator or team admin") @@ -848,7 +830,6 @@ func TestRegenOutgoingHookToken(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOutgoingWebhooks = true }) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = true }) - th.App.SetDefaultRolesBasedOnConfig() hook := &model.OutgoingWebhook{ChannelId: channel1.Id, CallbackURLs: []string{"http://nowhere.com"}} hook = Client.Must(Client.CreateOutgoingWebhook(hook)).Data.(*model.OutgoingWebhook) @@ -883,7 +864,6 @@ func TestRegenOutgoingHookToken(t *testing.T) { } th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = false }) - th.App.SetDefaultRolesBasedOnConfig() hook = &model.OutgoingWebhook{ChannelId: channel1.Id, CallbackURLs: []string{"http://nowhere.com"}} hook = Client.Must(Client.CreateOutgoingWebhook(hook)).Data.(*model.OutgoingWebhook) @@ -917,10 +897,6 @@ func TestIncomingWebhooks(t *testing.T) { user2 := th.CreateUser(Client) th.LinkUserToTeam(user2, team) - enableIncomingHooks := th.App.Config().ServiceSettings.EnableIncomingWebhooks - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableIncomingWebhooks = enableIncomingHooks }) - }() th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableIncomingWebhooks = true }) hook := &model.IncomingWebhook{ChannelId: channel1.Id} @@ -962,20 +938,8 @@ func TestIncomingWebhooks(t *testing.T) { t.Fatal("should not have failed -- ExperimentalTownSquareIsReadOnly is false and it's not a read only channel") } - isLicensed := utils.IsLicensed() - license := utils.License() - disableTownSquareReadOnly := th.App.Config().TeamSettings.ExperimentalTownSquareIsReadOnly - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { cfg.TeamSettings.ExperimentalTownSquareIsReadOnly = disableTownSquareReadOnly }) - utils.SetIsLicensed(isLicensed) - utils.SetLicense(license) - th.App.SetDefaultRolesBasedOnConfig() - }() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.ExperimentalTownSquareIsReadOnly = true }) - th.App.SetDefaultRolesBasedOnConfig() - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() + th.App.SetLicense(model.NewTestLicense()) if _, err := th.BasicClient.DoPost(url, fmt.Sprintf("{\"text\":\"this is a test\", \"channel\":\"%s\"}", model.DEFAULT_CHANNEL), "application/json"); err == nil { t.Fatal("should have failed -- ExperimentalTownSquareIsReadOnly is true and it's a read only channel") diff --git a/api4/channel_test.go b/api4/channel_test.go index 724b0d84b0..e659187070 100644 --- a/api4/channel_test.go +++ b/api4/channel_test.go @@ -14,7 +14,6 @@ import ( "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/store/sqlstore" - "github.com/mattermost/mattermost-server/utils" ) func TestCreateChannel(t *testing.T) { @@ -82,23 +81,9 @@ func TestCreateChannel(t *testing.T) { th.LoginBasic() // Check permissions with policy config changes - isLicensed := utils.IsLicensed() - license := utils.License() - restrictPublicChannel := *th.App.Config().TeamSettings.RestrictPublicChannelCreation - restrictPrivateChannel := *th.App.Config().TeamSettings.RestrictPrivateChannelCreation - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelCreation = restrictPublicChannel }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelCreation = restrictPrivateChannel }) - utils.SetIsLicensed(isLicensed) - utils.SetLicense(license) - th.App.SetDefaultRolesBasedOnConfig() - }() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelCreation = model.PERMISSIONS_ALL }) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelCreation = model.PERMISSIONS_ALL }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() + th.App.SetLicense(model.NewTestLicense()) channel.Name = GenerateTestChannelName() _, resp = Client.CreateChannel(channel) @@ -110,11 +95,8 @@ func TestCreateChannel(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelCreation = model.PERMISSIONS_TEAM_ADMIN - }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelCreation = model.PERMISSIONS_TEAM_ADMIN }) - th.App.SetDefaultRolesBasedOnConfig() _, resp = Client.CreateChannel(channel) CheckForbiddenStatus(t, resp) @@ -142,11 +124,8 @@ func TestCreateChannel(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelCreation = model.PERMISSIONS_SYSTEM_ADMIN - }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelCreation = model.PERMISSIONS_SYSTEM_ADMIN }) - th.App.SetDefaultRolesBasedOnConfig() th.LoginBasic() @@ -173,9 +152,7 @@ func TestCreateChannel(t *testing.T) { CheckNoError(t, resp) // Check that if unlicensed the policy restriction is not enforced. - utils.SetIsLicensed(false) - utils.SetLicense(nil) - th.App.SetDefaultRolesBasedOnConfig() + th.App.SetLicense(nil) channel.Name = GenerateTestChannelName() _, resp = Client.CreateChannel(channel) @@ -887,23 +864,9 @@ func TestDeleteChannel(t *testing.T) { th.InitBasic().InitSystemAdmin() - isLicensed := utils.IsLicensed() - license := utils.License() - restrictPublicChannel := *th.App.Config().TeamSettings.RestrictPublicChannelManagement - restrictPrivateChannel := *th.App.Config().TeamSettings.RestrictPrivateChannelManagement - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelManagement = restrictPublicChannel }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManagement = restrictPrivateChannel }) - utils.SetIsLicensed(isLicensed) - utils.SetLicense(license) - th.App.SetDefaultRolesBasedOnConfig() - }() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelManagement = model.PERMISSIONS_ALL }) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManagement = model.PERMISSIONS_ALL }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() + th.App.SetLicense(model.NewTestLicense()) Client = th.Client team = th.BasicTeam @@ -926,11 +889,8 @@ func TestDeleteChannel(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelDeletion = model.PERMISSIONS_CHANNEL_ADMIN - }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelDeletion = model.PERMISSIONS_CHANNEL_ADMIN }) - th.App.SetDefaultRolesBasedOnConfig() // channels created by SystemAdmin publicChannel6 = th.CreateChannelWithClient(th.SystemAdminClient, model.CHANNEL_OPEN) @@ -967,9 +927,7 @@ func TestDeleteChannel(t *testing.T) { // successful delete by team admin th.UpdateUserToTeamAdmin(user, team) th.App.InvalidateAllCaches() - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() + th.App.SetLicense(model.NewTestLicense()) _, resp = Client.DeleteChannel(publicChannel6.Id) CheckNoError(t, resp) @@ -979,16 +937,11 @@ func TestDeleteChannel(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelDeletion = model.PERMISSIONS_TEAM_ADMIN - }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelDeletion = model.PERMISSIONS_TEAM_ADMIN }) - th.App.SetDefaultRolesBasedOnConfig() th.UpdateUserToNonTeamAdmin(user, team) th.App.InvalidateAllCaches() - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() + th.App.SetLicense(model.NewTestLicense()) // channels created by SystemAdmin publicChannel6 = th.CreateChannelWithClient(th.SystemAdminClient, model.CHANNEL_OPEN) @@ -1018,9 +971,7 @@ func TestDeleteChannel(t *testing.T) { // successful delete by team admin th.UpdateUserToTeamAdmin(th.BasicUser, team) th.App.InvalidateAllCaches() - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() + th.App.SetLicense(model.NewTestLicense()) _, resp = Client.DeleteChannel(publicChannel6.Id) CheckNoError(t, resp) @@ -1030,11 +981,8 @@ func TestDeleteChannel(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelDeletion = model.PERMISSIONS_SYSTEM_ADMIN - }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelDeletion = model.PERMISSIONS_SYSTEM_ADMIN }) - th.App.SetDefaultRolesBasedOnConfig() // channels created by SystemAdmin publicChannel6 = th.CreateChannelWithClient(th.SystemAdminClient, model.CHANNEL_OPEN) @@ -1064,9 +1012,7 @@ func TestDeleteChannel(t *testing.T) { // cannot delete by team admin th.UpdateUserToTeamAdmin(th.BasicUser, team) th.App.InvalidateAllCaches() - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() + th.App.SetLicense(model.NewTestLicense()) _, resp = Client.DeleteChannel(publicChannel6.Id) CheckForbiddenStatus(t, resp) @@ -1823,16 +1769,9 @@ func TestAddChannelMember(t *testing.T) { CheckNoError(t, resp) // Test policy does not apply to TE. - restrictPrivateChannel := *th.App.Config().TeamSettings.RestrictPrivateChannelManageMembers - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPrivateChannelManageMembers = restrictPrivateChannel - }) - }() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManageMembers = model.PERMISSIONS_CHANNEL_ADMIN }) - th.App.SetDefaultRolesBasedOnConfig() Client.Login(user2.Username, user2.Password) privateChannel = th.CreatePrivateChannel() @@ -1846,18 +1785,8 @@ func TestAddChannelMember(t *testing.T) { Client.Logout() // Add a license - isLicensed := utils.IsLicensed() - license := utils.License() - defer func() { - utils.SetIsLicensed(isLicensed) - utils.SetLicense(license) - th.App.SetDefaultRolesBasedOnConfig() - }() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManageMembers = model.PERMISSIONS_ALL }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() + th.App.SetLicense(model.NewTestLicense()) // Check that a regular channel user can add other users. Client.Login(user2.Username, user2.Password) @@ -1875,10 +1804,6 @@ func TestAddChannelMember(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManageMembers = model.PERMISSIONS_CHANNEL_ADMIN }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() Client.Login(user2.Username, user2.Password) privateChannel = th.CreatePrivateChannel() @@ -1893,10 +1818,7 @@ func TestAddChannelMember(t *testing.T) { th.MakeUserChannelAdmin(user, privateChannel) th.App.InvalidateAllCaches() - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() + th.App.SetLicense(model.NewTestLicense()) Client.Login(user.Username, user.Password) _, resp = Client.AddChannelMember(privateChannel.Id, user3.Id) @@ -1907,10 +1829,6 @@ func TestAddChannelMember(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManageMembers = model.PERMISSIONS_TEAM_ADMIN }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() Client.Login(user2.Username, user2.Password) privateChannel = th.CreatePrivateChannel() @@ -1925,10 +1843,7 @@ func TestAddChannelMember(t *testing.T) { th.UpdateUserToTeamAdmin(user, team) th.App.InvalidateAllCaches() - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() + th.App.SetLicense(model.NewTestLicense()) Client.Login(user.Username, user.Password) _, resp = Client.AddChannelMember(privateChannel.Id, user3.Id) @@ -1939,10 +1854,6 @@ func TestAddChannelMember(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManageMembers = model.PERMISSIONS_SYSTEM_ADMIN }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() Client.Login(user2.Username, user2.Password) privateChannel = th.CreatePrivateChannel() @@ -2019,16 +1930,9 @@ func TestRemoveChannelMember(t *testing.T) { th.App.InvalidateAllCaches() // Test policy does not apply to TE. - restrictPrivateChannel := *th.App.Config().TeamSettings.RestrictPrivateChannelManageMembers - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPrivateChannelManageMembers = restrictPrivateChannel - }) - }() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManageMembers = model.PERMISSIONS_CHANNEL_ADMIN }) - th.App.SetDefaultRolesBasedOnConfig() privateChannel := th.CreateChannelWithClient(th.SystemAdminClient, model.CHANNEL_PRIVATE) _, resp = th.SystemAdminClient.AddChannelMember(privateChannel.Id, user1.Id) @@ -2040,18 +1944,8 @@ func TestRemoveChannelMember(t *testing.T) { CheckNoError(t, resp) // Add a license - isLicensed := utils.IsLicensed() - license := utils.License() - defer func() { - utils.SetIsLicensed(isLicensed) - utils.SetLicense(license) - th.App.SetDefaultRolesBasedOnConfig() - }() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManageMembers = model.PERMISSIONS_ALL }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() + th.App.SetLicense(model.NewTestLicense()) // Check that a regular channel user can remove other users. privateChannel = th.CreateChannelWithClient(th.SystemAdminClient, model.CHANNEL_PRIVATE) @@ -2067,10 +1961,6 @@ func TestRemoveChannelMember(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManageMembers = model.PERMISSIONS_CHANNEL_ADMIN }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() privateChannel = th.CreateChannelWithClient(th.SystemAdminClient, model.CHANNEL_PRIVATE) _, resp = th.SystemAdminClient.AddChannelMember(privateChannel.Id, user1.Id) @@ -2083,9 +1973,7 @@ func TestRemoveChannelMember(t *testing.T) { th.MakeUserChannelAdmin(user1, privateChannel) th.App.InvalidateAllCaches() - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() + th.App.SetLicense(model.NewTestLicense()) _, resp = Client.RemoveUserFromChannel(privateChannel.Id, user2.Id) CheckNoError(t, resp) @@ -2094,10 +1982,6 @@ func TestRemoveChannelMember(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManageMembers = model.PERMISSIONS_TEAM_ADMIN }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() privateChannel = th.CreateChannelWithClient(th.SystemAdminClient, model.CHANNEL_PRIVATE) _, resp = th.SystemAdminClient.AddChannelMember(privateChannel.Id, user1.Id) @@ -2110,9 +1994,7 @@ func TestRemoveChannelMember(t *testing.T) { th.UpdateUserToTeamAdmin(user1, team) th.App.InvalidateAllCaches() - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() + th.App.SetLicense(model.NewTestLicense()) _, resp = Client.RemoveUserFromChannel(privateChannel.Id, user2.Id) CheckNoError(t, resp) @@ -2121,10 +2003,6 @@ func TestRemoveChannelMember(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManageMembers = model.PERMISSIONS_SYSTEM_ADMIN }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() privateChannel = th.CreateChannelWithClient(th.SystemAdminClient, model.CHANNEL_PRIVATE) _, resp = th.SystemAdminClient.AddChannelMember(privateChannel.Id, user1.Id) diff --git a/api4/post_test.go b/api4/post_test.go index 6f770b70a3..2579185259 100644 --- a/api4/post_test.go +++ b/api4/post_test.go @@ -17,7 +17,6 @@ import ( "github.com/mattermost/mattermost-server/app" "github.com/mattermost/mattermost-server/model" - "github.com/mattermost/mattermost-server/utils" ) func TestCreatePost(t *testing.T) { @@ -130,20 +129,8 @@ func testCreatePostWithOutgoingHook( team := th.BasicTeam channel := th.BasicChannel - enableOutgoingHooks := th.App.Config().ServiceSettings.EnableOutgoingWebhooks - enableAdminOnlyHooks := th.App.Config().ServiceSettings.EnableOnlyAdminIntegrations - allowedInternalConnections := *th.App.Config().ServiceSettings.AllowedUntrustedInternalConnections - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOutgoingWebhooks = enableOutgoingHooks }) - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOnlyAdminIntegrations = enableAdminOnlyHooks }) - th.App.SetDefaultRolesBasedOnConfig() - th.App.UpdateConfig(func(cfg *model.Config) { - cfg.ServiceSettings.AllowedUntrustedInternalConnections = &allowedInternalConnections - }) - }() th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOutgoingWebhooks = true }) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = true }) - th.App.SetDefaultRolesBasedOnConfig() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.AllowedUntrustedInternalConnections = "localhost 127.0.0.1" }) @@ -489,21 +476,8 @@ func TestUpdatePost(t *testing.T) { Client := th.Client channel := th.BasicChannel - isLicensed := utils.IsLicensed() - license := utils.License() - allowEditPost := *th.App.Config().ServiceSettings.AllowEditPost - defer func() { - utils.SetIsLicensed(isLicensed) - utils.SetLicense(license) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.AllowEditPost = allowEditPost }) - th.App.SetDefaultRolesBasedOnConfig() - }() - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - + th.App.SetLicense(model.NewTestLicense()) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.AllowEditPost = model.ALLOW_EDIT_POST_ALWAYS }) - th.App.SetDefaultRolesBasedOnConfig() post := &model.Post{ChannelId: channel.Id, Message: "zz" + model.NewId() + "a"} rpost, resp := Client.CreatePost(post) @@ -574,21 +548,8 @@ func TestPatchPost(t *testing.T) { Client := th.Client channel := th.BasicChannel - isLicensed := utils.IsLicensed() - license := utils.License() - allowEditPost := *th.App.Config().ServiceSettings.AllowEditPost - defer func() { - utils.SetIsLicensed(isLicensed) - utils.SetLicense(license) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.AllowEditPost = allowEditPost }) - th.App.SetDefaultRolesBasedOnConfig() - }() - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - + th.App.SetLicense(model.NewTestLicense()) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.AllowEditPost = model.ALLOW_EDIT_POST_ALWAYS }) - th.App.SetDefaultRolesBasedOnConfig() post := &model.Post{ ChannelId: channel.Id, diff --git a/api4/team_test.go b/api4/team_test.go index a8696a30b5..56e6d575de 100644 --- a/api4/team_test.go +++ b/api4/team_test.go @@ -68,13 +68,7 @@ func TestCreateTeam(t *testing.T) { CheckUnauthorizedStatus(t, resp) // Update permission - enableTeamCreation := th.App.Config().TeamSettings.EnableTeamCreation - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { cfg.TeamSettings.EnableTeamCreation = enableTeamCreation }) - th.App.SetDefaultRolesBasedOnConfig() - }() th.App.UpdateConfig(func(cfg *model.Config) { cfg.TeamSettings.EnableTeamCreation = false }) - th.App.SetDefaultRolesBasedOnConfig() th.LoginBasic() _, resp = Client.CreateTeam(team) @@ -1292,20 +1286,8 @@ func TestAddTeamMember(t *testing.T) { Client.Logout() - // Check effects of config and license changes. - restrictTeamInvite := *th.App.Config().TeamSettings.RestrictTeamInvite - isLicensed := utils.IsLicensed() - license := utils.License() - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictTeamInvite = restrictTeamInvite }) - utils.SetIsLicensed(isLicensed) - utils.SetLicense(license) - th.App.SetDefaultRolesBasedOnConfig() - }() - // Set the config so that only team admins can add a user to a team. th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictTeamInvite = model.PERMISSIONS_TEAM_ADMIN }) - th.App.SetDefaultRolesBasedOnConfig() th.LoginBasic() // Test without the EE license to see that the permission restriction is ignored. @@ -1313,10 +1295,7 @@ func TestAddTeamMember(t *testing.T) { CheckNoError(t, resp) // Add an EE license. - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() + th.App.SetLicense(model.NewTestLicense()) th.LoginBasic() // Check that a regular user can't add someone to the team. @@ -1327,10 +1306,7 @@ func TestAddTeamMember(t *testing.T) { th.UpdateUserToTeamAdmin(th.BasicUser, th.BasicTeam) th.App.InvalidateAllCaches() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictTeamInvite = model.PERMISSIONS_TEAM_ADMIN }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() + th.App.SetLicense(model.NewTestLicense()) th.LoginBasic() // Should work as a team admin. @@ -1339,7 +1315,6 @@ func TestAddTeamMember(t *testing.T) { // Change permission level to System Admin th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictTeamInvite = model.PERMISSIONS_SYSTEM_ADMIN }) - th.App.SetDefaultRolesBasedOnConfig() // Should not work as team admin. _, resp = Client.AddTeamMember(team.Id, otherUser.Id) @@ -1353,21 +1328,13 @@ func TestAddTeamMember(t *testing.T) { th.UpdateUserToNonTeamAdmin(th.BasicUser, th.BasicTeam) th.App.InvalidateAllCaches() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictTeamInvite = model.PERMISSIONS_ALL }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() + th.App.SetLicense(model.NewTestLicense()) th.LoginBasic() // Should work as a regular user. _, resp = Client.AddTeamMember(team.Id, otherUser.Id) CheckNoError(t, resp) - // Reset config and license. - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictTeamInvite = restrictTeamInvite }) - utils.SetIsLicensed(isLicensed) - utils.SetLicense(license) - th.App.SetDefaultRolesBasedOnConfig() th.LoginBasic() // by hash and data @@ -1506,20 +1473,8 @@ func TestAddTeamMembers(t *testing.T) { Client.Logout() - // Check effects of config and license changes. - restrictTeamInvite := *th.App.Config().TeamSettings.RestrictTeamInvite - isLicensed := utils.IsLicensed() - license := utils.License() - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictTeamInvite = restrictTeamInvite }) - utils.SetIsLicensed(isLicensed) - utils.SetLicense(license) - th.App.SetDefaultRolesBasedOnConfig() - }() - // Set the config so that only team admins can add a user to a team. th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictTeamInvite = model.PERMISSIONS_TEAM_ADMIN }) - th.App.SetDefaultRolesBasedOnConfig() th.LoginBasic() // Test without the EE license to see that the permission restriction is ignored. @@ -1527,10 +1482,7 @@ func TestAddTeamMembers(t *testing.T) { CheckNoError(t, resp) // Add an EE license. - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() + th.App.SetLicense(model.NewTestLicense()) th.LoginBasic() // Check that a regular user can't add someone to the team. @@ -1541,10 +1493,7 @@ func TestAddTeamMembers(t *testing.T) { th.UpdateUserToTeamAdmin(th.BasicUser, th.BasicTeam) th.App.InvalidateAllCaches() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictTeamInvite = model.PERMISSIONS_TEAM_ADMIN }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() + th.App.SetLicense(model.NewTestLicense()) th.LoginBasic() // Should work as a team admin. @@ -1553,7 +1502,6 @@ func TestAddTeamMembers(t *testing.T) { // Change permission level to System Admin th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictTeamInvite = model.PERMISSIONS_SYSTEM_ADMIN }) - th.App.SetDefaultRolesBasedOnConfig() // Should not work as team admin. _, resp = Client.AddTeamMembers(team.Id, userList) @@ -1567,10 +1515,7 @@ func TestAddTeamMembers(t *testing.T) { th.UpdateUserToNonTeamAdmin(th.BasicUser, th.BasicTeam) th.App.InvalidateAllCaches() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictTeamInvite = model.PERMISSIONS_ALL }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() + th.App.SetLicense(model.NewTestLicense()) th.LoginBasic() // Should work as a regular user. @@ -1929,10 +1874,6 @@ func TestInviteUsersToTeam(t *testing.T) { } } - restrictCreationToDomains := th.App.Config().TeamSettings.RestrictCreationToDomains - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { cfg.TeamSettings.RestrictCreationToDomains = restrictCreationToDomains }) - }() th.App.UpdateConfig(func(cfg *model.Config) { cfg.TeamSettings.RestrictCreationToDomains = "@example.com" }) err := th.App.InviteNewUsersToTeam(emailList, th.BasicTeam.Id, th.BasicUser.Id) diff --git a/api4/user_test.go b/api4/user_test.go index a7b7d297d4..4613a8ea9c 100644 --- a/api4/user_test.go +++ b/api4/user_test.go @@ -1566,18 +1566,7 @@ func TestUpdateUserMfa(t *testing.T) { defer th.TearDown() Client := th.Client - isLicensed := utils.IsLicensed() - license := utils.License() - enableMfa := *th.App.Config().ServiceSettings.EnableMultifactorAuthentication - defer func() { - utils.SetIsLicensed(isLicensed) - utils.SetLicense(license) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableMultifactorAuthentication = enableMfa }) - }() - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - *utils.License().Features.MFA = true + th.App.SetLicense(model.NewTestLicense("mfa")) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableMultifactorAuthentication = true }) session, _ := th.App.GetSession(Client.AuthToken) @@ -1612,18 +1601,7 @@ func TestCheckUserMfa(t *testing.T) { t.Fatal("should be false - mfa not active") } - isLicensed := utils.IsLicensed() - license := utils.License() - enableMfa := *th.App.Config().ServiceSettings.EnableMultifactorAuthentication - defer func() { - utils.SetIsLicensed(isLicensed) - utils.SetLicense(license) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableMultifactorAuthentication = enableMfa }) - }() - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - *utils.License().Features.MFA = true + th.App.SetLicense(model.NewTestLicense("mfa")) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableMultifactorAuthentication = true }) th.LoginBasic() @@ -1659,18 +1637,7 @@ func TestGenerateMfaSecret(t *testing.T) { _, resp = Client.GenerateMfaSecret("junk") CheckBadRequestStatus(t, resp) - isLicensed := utils.IsLicensed() - license := utils.License() - enableMfa := *th.App.Config().ServiceSettings.EnableMultifactorAuthentication - defer func() { - utils.SetIsLicensed(isLicensed) - utils.SetLicense(license) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableMultifactorAuthentication = enableMfa }) - }() - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - *utils.License().Features.MFA = true + th.App.SetLicense(model.NewTestLicense("mfa")) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableMultifactorAuthentication = true }) _, resp = Client.GenerateMfaSecret(model.NewId()) @@ -2187,19 +2154,7 @@ func TestSwitchAccount(t *testing.T) { t.Fatal("bad link") } - isLicensed := utils.IsLicensed() - license := utils.License() - enableAuthenticationTransfer := *th.App.Config().ServiceSettings.ExperimentalEnableAuthenticationTransfer - defer func() { - utils.SetIsLicensed(isLicensed) - utils.SetLicense(license) - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.ServiceSettings.ExperimentalEnableAuthenticationTransfer = enableAuthenticationTransfer - }) - }() - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() + th.App.SetLicense(model.NewTestLicense()) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.ExperimentalEnableAuthenticationTransfer = false }) sr = &model.SwitchRequest{ diff --git a/app/app.go b/app/app.go index 0b5efa76b3..3c37ec252c 100644 --- a/app/app.go +++ b/app/app.go @@ -88,6 +88,9 @@ func New(options ...Option) (*App, error) { panic("Only one App should exist at a time. Did you forget to call Shutdown()?") } + // TODO: remove this once utils global license state is eliminated + utils.SetLicense(nil) + app := &App{ goroutineExitSignal: make(chan struct{}, 1), Srv: &Server{ diff --git a/app/config.go b/app/config.go index 46426c442a..55be24352f 100644 --- a/app/config.go +++ b/app/config.go @@ -254,11 +254,3 @@ func (a *App) Desanitize(cfg *model.Config) { cfg.SqlSettings.DataSourceSearchReplicas[i] = actual.SqlSettings.DataSourceSearchReplicas[i] } } - -// License returns the currently active license or nil if the application is unlicensed. -func (a *App) License() *model.License { - if utils.IsLicensed() { - return utils.License() - } - return nil -} diff --git a/app/license.go b/app/license.go index c7fd07197d..7402b5c223 100644 --- a/app/license.go +++ b/app/license.go @@ -59,7 +59,7 @@ func (a *App) SaveLicense(licenseBytes []byte) (*model.License, *model.AppError) } } - if ok := utils.SetLicense(license); !ok { + if ok := a.SetLicense(license); !ok { return nil, model.NewAppError("addLicense", model.EXPIRED_LICENSE_ERROR, nil, "", http.StatusBadRequest) } @@ -102,6 +102,20 @@ func (a *App) SaveLicense(licenseBytes []byte) (*model.License, *model.AppError) return license, nil } +// License returns the currently active license or nil if the application is unlicensed. +func (a *App) License() *model.License { + if utils.IsLicensed() { + return utils.License() + } + return nil +} + +func (a *App) SetLicense(license *model.License) bool { + ok := utils.SetLicense(license) + a.SetDefaultRolesBasedOnConfig() + return ok +} + func (a *App) RemoveLicense() *model.AppError { utils.RemoveLicense() diff --git a/app/session_test.go b/app/session_test.go index bca3b59b7e..09d81f4ac3 100644 --- a/app/session_test.go +++ b/app/session_test.go @@ -48,18 +48,7 @@ func TestGetSessionIdleTimeoutInMinutes(t *testing.T) { session, _ = th.App.CreateSession(session) - isLicensed := utils.IsLicensed() - license := utils.License() - timeout := *th.App.Config().ServiceSettings.SessionIdleTimeoutInMinutes - defer func() { - utils.SetIsLicensed(isLicensed) - utils.SetLicense(license) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.SessionIdleTimeoutInMinutes = timeout }) - }() - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - *utils.License().Features.Compliance = true + th.App.SetLicense(model.NewTestLicense("compliance")) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.SessionIdleTimeoutInMinutes = 5 }) rsession, err := th.App.GetSession(session.Token) diff --git a/model/license.go b/model/license.go index f96cba06c5..942a18d555 100644 --- a/model/license.go +++ b/model/license.go @@ -173,6 +173,25 @@ func (l *License) ToJson() string { return string(b) } +// NewTestLicense returns a license that expires in the future and has the given features. +func NewTestLicense(features ...string) *License { + ret := &License{ + ExpiresAt: GetMillis() + 90*24*60*60*1000, + Customer: &Customer{}, + Features: &Features{}, + } + ret.Features.SetDefaults() + + featureMap := map[string]bool{} + for _, feature := range features { + featureMap[feature] = true + } + featureJson, _ := json.Marshal(featureMap) + json.Unmarshal(featureJson, &ret.Features) + + return ret +} + func LicenseFromJson(data io.Reader) *License { var o *License json.NewDecoder(data).Decode(&o) From cf929476bdaa9a388fdfde62889bbef2a4dbf1c2 Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 8 Feb 2018 10:54:45 -0600 Subject: [PATCH 05/15] fix client4 post sanitization (#8219) --- model/client4.go | 4 +-- model/client4_test.go | 58 +++++++++++++++++++++++++++++++++++++++++++ model/post.go | 13 +++++----- 3 files changed, 67 insertions(+), 8 deletions(-) create mode 100644 model/client4_test.go diff --git a/model/client4.go b/model/client4.go index 0694ecbdf0..962b816bb2 100644 --- a/model/client4.go +++ b/model/client4.go @@ -1729,7 +1729,7 @@ func (c *Client4) RemoveUserFromChannel(channelId, userId string) (bool, *Respon // CreatePost creates a post based on the provided post struct. func (c *Client4) CreatePost(post *Post) (*Post, *Response) { - if r, err := c.DoApiPost(c.GetPostsRoute(), post.ToJson()); err != nil { + if r, err := c.DoApiPost(c.GetPostsRoute(), post.ToUnsanitizedJson()); err != nil { return nil, BuildErrorResponse(r, err) } else { defer closeBody(r) @@ -1739,7 +1739,7 @@ func (c *Client4) CreatePost(post *Post) (*Post, *Response) { // UpdatePost updates a post based on the provided post struct. func (c *Client4) UpdatePost(postId string, post *Post) (*Post, *Response) { - if r, err := c.DoApiPut(c.GetPostRoute(postId), post.ToJson()); err != nil { + if r, err := c.DoApiPut(c.GetPostRoute(postId), post.ToUnsanitizedJson()); err != nil { return nil, BuildErrorResponse(r, err) } else { defer closeBody(r) diff --git a/model/client4_test.go b/model/client4_test.go new file mode 100644 index 0000000000..f7923fa8fa --- /dev/null +++ b/model/client4_test.go @@ -0,0 +1,58 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" +) + +// https://github.com/mattermost/mattermost-server/issues/8205 +func TestClient4CreatePost(t *testing.T) { + post := &Post{ + Props: map[string]interface{}{ + "attachments": []*SlackAttachment{ + &SlackAttachment{ + Actions: []*PostAction{ + &PostAction{ + Integration: &PostActionIntegration{ + Context: map[string]interface{}{ + "foo": "bar", + }, + URL: "http://foo.com", + }, + Name: "Foo", + }, + }, + }, + }, + }, + } + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + attachments := PostFromJson(r.Body).Attachments() + assert.Equal(t, []*SlackAttachment{ + &SlackAttachment{ + Actions: []*PostAction{ + &PostAction{ + Integration: &PostActionIntegration{ + Context: map[string]interface{}{ + "foo": "bar", + }, + URL: "http://foo.com", + }, + Name: "Foo", + }, + }, + }, + }, attachments) + })) + + client := NewAPIv4Client(server.URL) + _, resp := client.CreatePost(post) + assert.Equal(t, http.StatusOK, resp.StatusCode) +} diff --git a/model/post.go b/model/post.go index 7cf0f1b35b..4a774b5d4e 100644 --- a/model/post.go +++ b/model/post.go @@ -122,12 +122,13 @@ type PostActionIntegrationResponse struct { func (o *Post) ToJson() string { copy := *o copy.StripActionIntegrations() - b, err := json.Marshal(©) - if err != nil { - return "" - } else { - return string(b) - } + b, _ := json.Marshal(©) + return string(b) +} + +func (o *Post) ToUnsanitizedJson() string { + b, _ := json.Marshal(o) + return string(b) } func PostFromJson(data io.Reader) *Post { From 1b064c674a21fbee4000fdf84fbdc75da1fab1f0 Mon Sep 17 00:00:00 2001 From: Derrick Anderson Date: Thu, 8 Feb 2018 12:12:48 -0500 Subject: [PATCH 06/15] Add prepatory code for 4.8.0 (#8226) * Add prepatory code for 4.8.0 * formatting issue * build bug --- store/sqlstore/upgrade.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/store/sqlstore/upgrade.go b/store/sqlstore/upgrade.go index 0de91f28bc..bebcc262cd 100644 --- a/store/sqlstore/upgrade.go +++ b/store/sqlstore/upgrade.go @@ -15,6 +15,7 @@ import ( ) const ( + VERSION_4_8_0 = "4.8.0" VERSION_4_7_0 = "4.7.0" VERSION_4_6_0 = "4.6.0" VERSION_4_5_0 = "4.5.0" @@ -64,6 +65,7 @@ func UpgradeDatabase(sqlStore SqlStore) { UpgradeDatabaseToVersion45(sqlStore) UpgradeDatabaseToVersion46(sqlStore) UpgradeDatabaseToVersion47(sqlStore) + UpgradeDatabaseToVersion48(sqlStore) // If the SchemaVersion is empty this this is the first time it has ran // so lets set it to the current version. @@ -346,3 +348,10 @@ func UpgradeDatabaseToVersion47(sqlStore SqlStore) { saveSchemaVersion(sqlStore, VERSION_4_7_0) } } + +func UpgradeDatabaseToVersion48(sqlStore SqlStore) { + //TODO: Uncomment the following condition when version 4.8.0 is released + //if shouldPerformUpgrade(sqlStore, VERSION_4_7_0, VERSION_4_8_0) { + // saveSchemaVersion(sqlStore, VERSION_4_8_0) + //} +} From 7e8106b95c11a4187f8c00256f3067433d20b24e Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 8 Feb 2018 18:43:27 -0600 Subject: [PATCH 07/15] use mattermost fork of go-bindata (#8216) --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index ca8fafdb3e..366c27057a 100644 --- a/Makefile +++ b/Makefile @@ -276,7 +276,7 @@ store-mocks: ## Creates mock files. GOPATH=$(shell go env GOPATH) $(shell go env GOPATH)/bin/mockery -dir store -all -output store/storetest/mocks -note 'Regenerate this file using `make store-mocks`.' update-jira-plugin: ## Updates Jira plugin. - go get github.com/jteeuwen/go-bindata/... + go get github.com/mattermost/go-bindata/... curl -s https://api.github.com/repos/mattermost/mattermost-plugin-jira/releases/latest | grep browser_download_url | grep darwin-amd64 | cut -d '"' -f 4 | wget -qi - -O plugin.tar.gz $(shell go env GOPATH)/bin/go-bindata -pkg jira -o app/plugin/jira/plugin_darwin_amd64.go plugin.tar.gz curl -s https://api.github.com/repos/mattermost/mattermost-plugin-jira/releases/latest | grep browser_download_url | grep linux-amd64 | cut -d '"' -f 4 | wget -qi - -O plugin.tar.gz @@ -287,7 +287,7 @@ update-jira-plugin: ## Updates Jira plugin. gofmt -s -w ./app/plugin/jira update-zoom-plugin: ## Updates Zoom plugin. - go get github.com/jteeuwen/go-bindata/... + go get github.com/mattermost/go-bindata/... curl -s https://api.github.com/repos/mattermost/mattermost-plugin-zoom/releases/latest | grep browser_download_url | grep darwin-amd64 | cut -d '"' -f 4 | wget -qi - -O plugin.tar.gz $(shell go env GOPATH)/bin/go-bindata -pkg zoom -o app/plugin/zoom/plugin_darwin_amd64.go plugin.tar.gz curl -s https://api.github.com/repos/mattermost/mattermost-plugin-zoom/releases/latest | grep browser_download_url | grep linux-amd64 | cut -d '"' -f 4 | wget -qi - -O plugin.tar.gz From a6309aaf48e216fe5f6779188071d4b621b643b6 Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 9 Feb 2018 10:04:48 -0600 Subject: [PATCH 08/15] Remove license globals entirely (#8229) * remove license globals entirely * fix infinite recursion * test fix --- api/apitestlib.go | 5 +- api/license.go | 7 +- api/license_test.go | 8 +-- api/user.go | 4 +- api4/apitestlib.go | 5 +- api4/oauth_test.go | 62 ----------------- api4/system.go | 6 +- app/admin.go | 3 +- app/app.go | 24 ++++--- app/apptestlib.go | 5 +- app/config.go | 2 +- app/email.go | 3 +- app/file.go | 3 +- app/license.go | 105 ++++++++++++++++++++++++++--- app/license_test.go | 75 ++++++++++++++++++++- app/role.go | 6 +- app/session_test.go | 9 ++- cmd/platform/jobserver.go | 2 +- jobs/server.go | 33 --------- jobs/server_test.go | 39 ----------- utils/authorization.go | 20 +++--- utils/config.go | 31 +++++---- utils/config_test.go | 2 +- utils/file_backend.go | 4 +- utils/file_backend_test.go | 2 +- utils/license.go | 135 +------------------------------------ utils/license_test.go | 67 ------------------ utils/mail.go | 12 ++-- utils/mail_test.go | 6 +- 29 files changed, 256 insertions(+), 429 deletions(-) delete mode 100644 jobs/server_test.go diff --git a/api/apitestlib.go b/api/apitestlib.go index bae00927a0..47691b2dd0 100644 --- a/api/apitestlib.go +++ b/api/apitestlib.go @@ -118,9 +118,10 @@ func setupTestHelper(enterprise bool) *TestHelper { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.EnableOpenServer = true }) - utils.SetIsLicensed(enterprise) if enterprise { - utils.License().Features.SetDefaults() + th.App.SetLicense(model.NewTestLicense()) + } else { + th.App.SetLicense(nil) } return th diff --git a/api/license.go b/api/license.go index 8eb7803e11..432442ad65 100644 --- a/api/license.go +++ b/api/license.go @@ -9,7 +9,6 @@ import ( "net/http" "github.com/mattermost/mattermost-server/model" - "github.com/mattermost/mattermost-server/utils" ) func (api *API) InitLicense() { @@ -83,7 +82,7 @@ func removeLicense(c *Context, w http.ResponseWriter, r *http.Request) { func getClientLicenceConfig(c *Context, w http.ResponseWriter, r *http.Request) { useSanitizedLicense := !c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) - etag := utils.GetClientLicenseEtag(useSanitizedLicense) + etag := c.App.GetClientLicenseEtag(useSanitizedLicense) if c.HandleEtag(etag, "Get Client License Config", w, r) { return } @@ -91,9 +90,9 @@ func getClientLicenceConfig(c *Context, w http.ResponseWriter, r *http.Request) var clientLicense map[string]string if useSanitizedLicense { - clientLicense = utils.ClientLicense() + clientLicense = c.App.ClientLicense() } else { - clientLicense = utils.GetSanitizedClientLicense() + clientLicense = c.App.GetSanitizedClientLicense() } w.Header().Set(model.HEADER_ETAG_SERVER, etag) diff --git a/api/license_test.go b/api/license_test.go index 50d73101d8..47586151ac 100644 --- a/api/license_test.go +++ b/api/license_test.go @@ -5,8 +5,6 @@ package api import ( "testing" - - "github.com/mattermost/mattermost-server/utils" ) func TestGetLicenceConfig(t *testing.T) { @@ -32,7 +30,7 @@ func TestGetLicenceConfig(t *testing.T) { t.Fatal("cache should be empty") } - utils.SetClientLicense(map[string]string{"IsLicensed": "true"}) + th.App.SetClientLicense(map[string]string{"IsLicensed": "true"}) if cache_result, err := Client.GetClientLicenceConfig(result.Etag); err != nil { t.Fatal(err) @@ -40,7 +38,7 @@ func TestGetLicenceConfig(t *testing.T) { t.Fatal("result should not be empty") } - utils.SetClientLicense(map[string]string{"SomeFeature": "true", "IsLicensed": "true"}) + th.App.SetClientLicense(map[string]string{"SomeFeature": "true", "IsLicensed": "true"}) if cache_result, err := Client.GetClientLicenceConfig(result.Etag); err != nil { t.Fatal(err) @@ -48,6 +46,6 @@ func TestGetLicenceConfig(t *testing.T) { t.Fatal("result should not be empty") } - utils.SetClientLicense(map[string]string{"IsLicensed": "false"}) + th.App.SetClientLicense(map[string]string{"IsLicensed": "false"}) } } diff --git a/api/user.go b/api/user.go index 440ea58580..ad4f12ef3c 100644 --- a/api/user.go +++ b/api/user.go @@ -299,9 +299,9 @@ func getInitialLoad(c *Context, w http.ResponseWriter, r *http.Request) { il.ClientCfg = c.App.ClientConfig() if c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) { - il.LicenseCfg = utils.ClientLicense() + il.LicenseCfg = c.App.ClientLicense() } else { - il.LicenseCfg = utils.GetSanitizedClientLicense() + il.LicenseCfg = c.App.GetSanitizedClientLicense() } w.Write([]byte(il.ToJson())) diff --git a/api4/apitestlib.go b/api4/apitestlib.go index b55ce7cbfc..db43a65123 100644 --- a/api4/apitestlib.go +++ b/api4/apitestlib.go @@ -125,9 +125,10 @@ func setupTestHelper(enterprise bool) *TestHelper { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.EnableOpenServer = true }) - utils.SetIsLicensed(enterprise) if enterprise { - utils.License().Features.SetDefaults() + th.App.SetLicense(model.NewTestLicense()) + } else { + th.App.SetLicense(nil) } th.Client = th.CreateClient() diff --git a/api4/oauth_test.go b/api4/oauth_test.go index 8dd602456c..c871dafff7 100644 --- a/api4/oauth_test.go +++ b/api4/oauth_test.go @@ -18,14 +18,7 @@ func TestCreateOAuthApp(t *testing.T) { Client := th.Client AdminClient := th.SystemAdminClient - enableOAuth := th.App.Config().ServiceSettings.EnableOAuthServiceProvider - adminOnly := *th.App.Config().ServiceSettings.EnableOnlyAdminIntegrations - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = enableOAuth }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = adminOnly }) - }() th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = true }) - th.App.SetDefaultRolesBasedOnConfig() oapp := &model.OAuthApp{Name: GenerateTestAppName(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}, IsTrusted: true} @@ -42,12 +35,10 @@ func TestCreateOAuthApp(t *testing.T) { } th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = true }) - th.App.SetDefaultRolesBasedOnConfig() _, resp = Client.CreateOAuthApp(oapp) CheckForbiddenStatus(t, resp) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = false }) - th.App.SetDefaultRolesBasedOnConfig() rapp, resp = Client.CreateOAuthApp(oapp) CheckNoError(t, resp) CheckCreatedStatus(t, resp) @@ -86,14 +77,7 @@ func TestUpdateOAuthApp(t *testing.T) { Client := th.Client AdminClient := th.SystemAdminClient - enableOAuth := th.App.Config().ServiceSettings.EnableOAuthServiceProvider - adminOnly := *th.App.Config().ServiceSettings.EnableOnlyAdminIntegrations - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = enableOAuth }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = adminOnly }) - }() th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = true }) - th.App.SetDefaultRolesBasedOnConfig() oapp := &model.OAuthApp{ Name: "oapp", @@ -172,7 +156,6 @@ func TestUpdateOAuthApp(t *testing.T) { th.LoginBasic() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = false }) - th.App.SetDefaultRolesBasedOnConfig() _, resp = Client.UpdateOAuthApp(oapp) CheckForbiddenStatus(t, resp) @@ -199,15 +182,8 @@ func TestGetOAuthApps(t *testing.T) { Client := th.Client AdminClient := th.SystemAdminClient - enableOAuth := th.App.Config().ServiceSettings.EnableOAuthServiceProvider - adminOnly := *th.App.Config().ServiceSettings.EnableOnlyAdminIntegrations - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = enableOAuth }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = adminOnly }) - }() th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = true }) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = false }) - th.App.SetDefaultRolesBasedOnConfig() oapp := &model.OAuthApp{Name: GenerateTestAppName(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}} @@ -251,7 +227,6 @@ func TestGetOAuthApps(t *testing.T) { } th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = true }) - th.App.SetDefaultRolesBasedOnConfig() _, resp = Client.GetOAuthApps(0, 1000) CheckForbiddenStatus(t, resp) @@ -272,15 +247,8 @@ func TestGetOAuthApp(t *testing.T) { Client := th.Client AdminClient := th.SystemAdminClient - enableOAuth := th.App.Config().ServiceSettings.EnableOAuthServiceProvider - adminOnly := *th.App.Config().ServiceSettings.EnableOnlyAdminIntegrations - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = enableOAuth }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = adminOnly }) - }() th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = true }) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = false }) - th.App.SetDefaultRolesBasedOnConfig() oapp := &model.OAuthApp{Name: GenerateTestAppName(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}} @@ -320,7 +288,6 @@ func TestGetOAuthApp(t *testing.T) { CheckForbiddenStatus(t, resp) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = true }) - th.App.SetDefaultRolesBasedOnConfig() _, resp = Client.GetOAuthApp(rapp2.Id) CheckForbiddenStatus(t, resp) @@ -347,15 +314,8 @@ func TestGetOAuthAppInfo(t *testing.T) { Client := th.Client AdminClient := th.SystemAdminClient - enableOAuth := th.App.Config().ServiceSettings.EnableOAuthServiceProvider - adminOnly := *th.App.Config().ServiceSettings.EnableOnlyAdminIntegrations - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = enableOAuth }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = adminOnly }) - }() th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = true }) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = false }) - th.App.SetDefaultRolesBasedOnConfig() oapp := &model.OAuthApp{Name: GenerateTestAppName(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}} @@ -395,7 +355,6 @@ func TestGetOAuthAppInfo(t *testing.T) { CheckNoError(t, resp) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = true }) - th.App.SetDefaultRolesBasedOnConfig() _, resp = Client.GetOAuthAppInfo(rapp2.Id) CheckNoError(t, resp) @@ -422,15 +381,8 @@ func TestDeleteOAuthApp(t *testing.T) { Client := th.Client AdminClient := th.SystemAdminClient - enableOAuth := th.App.Config().ServiceSettings.EnableOAuthServiceProvider - adminOnly := *th.App.Config().ServiceSettings.EnableOnlyAdminIntegrations - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = enableOAuth }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = adminOnly }) - }() th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = true }) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = false }) - th.App.SetDefaultRolesBasedOnConfig() oapp := &model.OAuthApp{Name: GenerateTestAppName(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}} @@ -465,7 +417,6 @@ func TestDeleteOAuthApp(t *testing.T) { CheckNoError(t, resp) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = false }) - th.App.SetDefaultRolesBasedOnConfig() _, resp = Client.DeleteOAuthApp(rapp.Id) CheckForbiddenStatus(t, resp) @@ -490,15 +441,8 @@ func TestRegenerateOAuthAppSecret(t *testing.T) { Client := th.Client AdminClient := th.SystemAdminClient - enableOAuth := th.App.Config().ServiceSettings.EnableOAuthServiceProvider - adminOnly := *th.App.Config().ServiceSettings.EnableOnlyAdminIntegrations - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = enableOAuth }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = adminOnly }) - }() th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = true }) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = false }) - th.App.SetDefaultRolesBasedOnConfig() oapp := &model.OAuthApp{Name: GenerateTestAppName(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}} @@ -537,7 +481,6 @@ func TestRegenerateOAuthAppSecret(t *testing.T) { CheckNoError(t, resp) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = false }) - th.App.SetDefaultRolesBasedOnConfig() _, resp = Client.RegenerateOAuthAppSecret(rapp.Id) CheckForbiddenStatus(t, resp) @@ -622,12 +565,7 @@ func TestAuthorizeOAuthApp(t *testing.T) { Client := th.Client AdminClient := th.SystemAdminClient - enableOAuth := th.App.Config().ServiceSettings.EnableOAuthServiceProvider - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = enableOAuth }) - }() th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = true }) - th.App.SetDefaultRolesBasedOnConfig() oapp := &model.OAuthApp{Name: GenerateTestAppName(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}} diff --git a/api4/system.go b/api4/system.go index 43b941247a..061ffe0940 100644 --- a/api4/system.go +++ b/api4/system.go @@ -266,7 +266,7 @@ func getClientLicense(c *Context, w http.ResponseWriter, r *http.Request) { return } - etag := utils.GetClientLicenseEtag(true) + etag := c.App.GetClientLicenseEtag(true) if c.HandleEtag(etag, "Get Client License", w, r) { return } @@ -274,9 +274,9 @@ func getClientLicense(c *Context, w http.ResponseWriter, r *http.Request) { var clientLicense map[string]string if c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) { - clientLicense = utils.ClientLicense() + clientLicense = c.App.ClientLicense() } else { - clientLicense = utils.GetSanitizedClientLicense() + clientLicense = c.App.GetSanitizedClientLicense() } w.Header().Set(model.HEADER_ETAG_SERVER, etag) diff --git a/app/admin.go b/app/admin.go index b838ed3bde..154fa88991 100644 --- a/app/admin.go +++ b/app/admin.go @@ -237,7 +237,8 @@ func (a *App) TestEmail(userId string, cfg *model.Config) *model.AppError { return err } else { T := utils.GetUserTranslations(user.Locale) - if err := utils.SendMailUsingConfig(user.Email, T("api.admin.test_email.subject"), T("api.admin.test_email.body"), cfg); err != nil { + license := a.License() + if err := utils.SendMailUsingConfig(user.Email, T("api.admin.test_email.subject"), T("api.admin.test_email.body"), cfg, license != nil && *license.Features.Compliance); err != nil { return err } } diff --git a/app/app.go b/app/app.go index 3c37ec252c..dd5deb342d 100644 --- a/app/app.go +++ b/app/app.go @@ -59,6 +59,10 @@ type App struct { configFile string configListeners map[string]func(*model.Config, *model.Config) + licenseValue atomic.Value + clientLicenseValue atomic.Value + licenseListeners map[string]func() + newStore func() store.Store htmlTemplateWatcher *utils.HTMLTemplateWatcher @@ -88,18 +92,16 @@ func New(options ...Option) (*App, error) { panic("Only one App should exist at a time. Did you forget to call Shutdown()?") } - // TODO: remove this once utils global license state is eliminated - utils.SetLicense(nil) - app := &App{ goroutineExitSignal: make(chan struct{}, 1), Srv: &Server{ Router: mux.NewRouter(), }, - sessionCache: utils.NewLru(model.SESSION_CACHE_SIZE), - configFile: "config.json", - configListeners: make(map[string]func(*model.Config, *model.Config)), - clientConfig: make(map[string]string), + sessionCache: utils.NewLru(model.SESSION_CACHE_SIZE), + configFile: "config.json", + configListeners: make(map[string]func(*model.Config, *model.Config)), + clientConfig: make(map[string]string), + licenseListeners: map[string]func(){}, } for _, option := range options { @@ -123,9 +125,9 @@ func New(options ...Option) (*App, error) { app.configListenerId = app.AddConfigListener(func(_, _ *model.Config) { app.configOrLicenseListener() }) - app.licenseListenerId = utils.AddLicenseListener(app.configOrLicenseListener) + app.licenseListenerId = app.AddLicenseListener(app.configOrLicenseListener) app.regenerateClientConfig() - app.SetDefaultRolesBasedOnConfig() + app.setDefaultRolesBasedOnConfig() l4g.Info(utils.T("api.server.new_server.init.info")) @@ -166,7 +168,7 @@ func New(options ...Option) (*App, error) { func (a *App) configOrLicenseListener() { a.regenerateClientConfig() - a.SetDefaultRolesBasedOnConfig() + a.setDefaultRolesBasedOnConfig() } func (a *App) Shutdown() { @@ -188,7 +190,7 @@ func (a *App) Shutdown() { } a.RemoveConfigListener(a.configListenerId) - utils.RemoveLicenseListener(a.licenseListenerId) + a.RemoveLicenseListener(a.licenseListenerId) l4g.Info(utils.T("api.server.stop_server.stopped.info")) a.DisableConfigWatch() diff --git a/app/apptestlib.go b/app/apptestlib.go index 016a68becf..c7846c9b56 100644 --- a/app/apptestlib.go +++ b/app/apptestlib.go @@ -106,9 +106,10 @@ func setupTestHelper(enterprise bool) *TestHelper { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.EnableOpenServer = true }) - utils.SetIsLicensed(enterprise) if enterprise { - utils.License().Features.SetDefaults() + th.App.SetLicense(model.NewTestLicense()) + } else { + th.App.SetLicense(nil) } return th diff --git a/app/config.go b/app/config.go index 55be24352f..b4925e8fb7 100644 --- a/app/config.go +++ b/app/config.go @@ -201,7 +201,7 @@ func (a *App) AsymmetricSigningKey() *ecdsa.PrivateKey { } func (a *App) regenerateClientConfig() { - a.clientConfig = utils.GenerateClientConfig(a.Config(), a.DiagnosticId()) + a.clientConfig = utils.GenerateClientConfig(a.Config(), a.DiagnosticId(), a.License()) if key := a.AsymmetricSigningKey(); key != nil { der, _ := x509.MarshalPKIXPublicKey(&key.PublicKey) a.clientConfig["AsymmetricSigningPublicKey"] = base64.StdEncoding.EncodeToString(der) diff --git a/app/email.go b/app/email.go index b809b972d1..89de2ae657 100644 --- a/app/email.go +++ b/app/email.go @@ -316,5 +316,6 @@ func (a *App) NewEmailTemplate(name, locale string) *utils.HTMLTemplate { } func (a *App) SendMail(to, subject, htmlBody string) *model.AppError { - return utils.SendMailUsingConfig(to, subject, htmlBody, a.Config()) + license := a.License() + return utils.SendMailUsingConfig(to, subject, htmlBody, a.Config(), license != nil && *license.Features.Compliance) } diff --git a/app/file.go b/app/file.go index d66c64adbf..bb20585bb2 100644 --- a/app/file.go +++ b/app/file.go @@ -58,7 +58,8 @@ const ( ) func (a *App) FileBackend() (utils.FileBackend, *model.AppError) { - return utils.NewFileBackend(&a.Config().FileSettings) + license := a.License() + return utils.NewFileBackend(&a.Config().FileSettings, license != nil && *license.Features.Compliance) } func (a *App) ReadFile(path string) ([]byte, *model.AppError) { diff --git a/app/license.go b/app/license.go index 7402b5c223..efb725a208 100644 --- a/app/license.go +++ b/app/license.go @@ -4,16 +4,19 @@ package app import ( + "crypto/md5" + "fmt" "net/http" "strings" l4g "github.com/alecthomas/log4go" + "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/utils" ) func (a *App) LoadLicense() { - utils.RemoveLicense() + a.SetLicense(nil) licenseId := "" if result := <-a.Srv.Store.System().Get(); result.Err == nil { @@ -36,7 +39,7 @@ func (a *App) LoadLicense() { if result := <-a.Srv.Store.License().Get(licenseId); result.Err == nil { record := result.Data.(*model.LicenseRecord) - utils.LoadLicense([]byte(record.Bytes)) + a.ValidateAndSetLicenseBytes([]byte(record.Bytes)) l4g.Info("License key valid unlocking enterprise features.") } else { l4g.Info(utils.T("mattermost.load_license.find.warn")) @@ -104,33 +107,113 @@ func (a *App) SaveLicense(licenseBytes []byte) (*model.License, *model.AppError) // License returns the currently active license or nil if the application is unlicensed. func (a *App) License() *model.License { - if utils.IsLicensed() { - return utils.License() - } - return nil + license, _ := a.licenseValue.Load().(*model.License) + return license } func (a *App) SetLicense(license *model.License) bool { - ok := utils.SetLicense(license) - a.SetDefaultRolesBasedOnConfig() - return ok + defer func() { + a.setDefaultRolesBasedOnConfig() + for _, listener := range a.licenseListeners { + listener() + } + }() + + if license != nil { + license.Features.SetDefaults() + + if !license.IsExpired() { + a.licenseValue.Store(license) + a.clientLicenseValue.Store(utils.GetClientLicense(license)) + return true + } + } + + a.licenseValue.Store((*model.License)(nil)) + a.SetClientLicense(map[string]string{"IsLicensed": "false"}) + return false +} + +func (a *App) ValidateAndSetLicenseBytes(b []byte) { + if success, licenseStr := utils.ValidateLicense(b); success { + license := model.LicenseFromJson(strings.NewReader(licenseStr)) + a.SetLicense(license) + return + } + + l4g.Warn(utils.T("utils.license.load_license.invalid.warn")) +} + +func (a *App) SetClientLicense(m map[string]string) { + a.clientLicenseValue.Store(m) +} + +func (a *App) ClientLicense() map[string]string { + clientLicense, _ := a.clientLicenseValue.Load().(map[string]string) + return clientLicense } func (a *App) RemoveLicense() *model.AppError { - utils.RemoveLicense() + if license, _ := a.licenseValue.Load().(*model.License); license == nil { + return nil + } sysVar := &model.System{} sysVar.Name = model.SYSTEM_ACTIVE_LICENSE_ID sysVar.Value = "" if result := <-a.Srv.Store.System().SaveOrUpdate(sysVar); result.Err != nil { - utils.RemoveLicense() return result.Err } + a.SetLicense(nil) a.ReloadConfig() a.InvalidateAllCaches() return nil } + +func (a *App) AddLicenseListener(listener func()) string { + id := model.NewId() + a.licenseListeners[id] = listener + return id +} + +func (a *App) RemoveLicenseListener(id string) { + delete(a.licenseListeners, id) +} + +func (a *App) GetClientLicenseEtag(useSanitized bool) string { + value := "" + + lic := a.ClientLicense() + + if useSanitized { + lic = a.GetSanitizedClientLicense() + } + + for k, v := range lic { + value += fmt.Sprintf("%s:%s;", k, v) + } + + return model.Etag(fmt.Sprintf("%x", md5.Sum([]byte(value)))) +} + +func (a *App) GetSanitizedClientLicense() map[string]string { + sanitizedLicense := make(map[string]string) + + for k, v := range a.ClientLicense() { + sanitizedLicense[k] = v + } + + delete(sanitizedLicense, "Id") + delete(sanitizedLicense, "Name") + delete(sanitizedLicense, "Email") + delete(sanitizedLicense, "PhoneNumber") + delete(sanitizedLicense, "IssuedAt") + delete(sanitizedLicense, "StartsAt") + delete(sanitizedLicense, "ExpiresAt") + + return sanitizedLicense +} diff --git a/app/license_test.go b/app/license_test.go index 5b73d9d186..f86d604d10 100644 --- a/app/license_test.go +++ b/app/license_test.go @@ -4,8 +4,9 @@ package app import ( - //"github.com/mattermost/mattermost-server/model" "testing" + + "github.com/mattermost/mattermost-server/model" ) func TestLoadLicense(t *testing.T) { @@ -37,3 +38,75 @@ func TestRemoveLicense(t *testing.T) { t.Fatal("should have removed license") } } + +func TestSetLicense(t *testing.T) { + th := Setup() + defer th.TearDown() + + l1 := &model.License{} + l1.Features = &model.Features{} + l1.Customer = &model.Customer{} + l1.StartsAt = model.GetMillis() - 1000 + l1.ExpiresAt = model.GetMillis() + 100000 + if ok := th.App.SetLicense(l1); !ok { + t.Fatal("license should have worked") + } + + l2 := &model.License{} + l2.Features = &model.Features{} + l2.Customer = &model.Customer{} + l2.StartsAt = model.GetMillis() - 1000 + l2.ExpiresAt = model.GetMillis() - 100 + if ok := th.App.SetLicense(l2); ok { + t.Fatal("license should have failed") + } + + l3 := &model.License{} + l3.Features = &model.Features{} + l3.Customer = &model.Customer{} + l3.StartsAt = model.GetMillis() + 10000 + l3.ExpiresAt = model.GetMillis() + 100000 + if ok := th.App.SetLicense(l3); !ok { + t.Fatal("license should have passed") + } +} + +func TestClientLicenseEtag(t *testing.T) { + th := Setup() + defer th.TearDown() + + etag1 := th.App.GetClientLicenseEtag(false) + + th.App.SetClientLicense(map[string]string{"SomeFeature": "true", "IsLicensed": "true"}) + + etag2 := th.App.GetClientLicenseEtag(false) + if etag1 == etag2 { + t.Fatal("etags should not match") + } + + th.App.SetClientLicense(map[string]string{"SomeFeature": "true", "IsLicensed": "false"}) + + etag3 := th.App.GetClientLicenseEtag(false) + if etag2 == etag3 { + t.Fatal("etags should not match") + } +} + +func TestGetSanitizedClientLicense(t *testing.T) { + th := Setup() + defer th.TearDown() + + l1 := &model.License{} + l1.Features = &model.Features{} + l1.Customer = &model.Customer{} + l1.Customer.Name = "TestName" + l1.StartsAt = model.GetMillis() - 1000 + l1.ExpiresAt = model.GetMillis() + 100000 + th.App.SetLicense(l1) + + m := th.App.GetSanitizedClientLicense() + + if _, ok := m["Name"]; ok { + t.Fatal("should have been sanatized") + } +} diff --git a/app/role.go b/app/role.go index 5f39dd6239..9f271ea7a2 100644 --- a/app/role.go +++ b/app/role.go @@ -12,8 +12,6 @@ func (a *App) Role(id string) *model.Role { return a.roles[id] } -// Updates the roles based on the app config and the global license check. You may need to invoke -// this when license changes are made. -func (a *App) SetDefaultRolesBasedOnConfig() { - a.roles = utils.DefaultRolesBasedOnConfig(a.Config()) +func (a *App) setDefaultRolesBasedOnConfig() { + a.roles = utils.DefaultRolesBasedOnConfig(a.Config(), a.License() != nil) } diff --git a/app/session_test.go b/app/session_test.go index 09d81f4ac3..bf8198a4ed 100644 --- a/app/session_test.go +++ b/app/session_test.go @@ -6,11 +6,10 @@ package app import ( "testing" - "github.com/mattermost/mattermost-server/model" - "github.com/mattermost/mattermost-server/utils" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/mattermost/mattermost-server/model" ) func TestCache(t *testing.T) { @@ -111,7 +110,7 @@ func TestGetSessionIdleTimeoutInMinutes(t *testing.T) { assert.Nil(t, err) // Test regular session with license off, should not timeout - *utils.License().Features.Compliance = false + th.App.SetLicense(nil) session = &model.Session{ UserId: model.NewId(), @@ -125,7 +124,7 @@ func TestGetSessionIdleTimeoutInMinutes(t *testing.T) { _, err = th.App.GetSession(session.Token) assert.Nil(t, err) - *utils.License().Features.Compliance = true + th.App.SetLicense(model.NewTestLicense("compliance")) // Test regular session with timeout set to 0, should not timeout th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.SessionIdleTimeoutInMinutes = 0 }) diff --git a/cmd/platform/jobserver.go b/cmd/platform/jobserver.go index e664136c0b..044ee6b6a6 100644 --- a/cmd/platform/jobserver.go +++ b/cmd/platform/jobserver.go @@ -35,7 +35,7 @@ func jobserverCmdF(cmd *cobra.Command, args []string) { defer l4g.Close() defer a.Shutdown() - a.Jobs.LoadLicense() + a.LoadLicense() // Run jobs l4g.Info("Starting Mattermost job server") diff --git a/jobs/server.go b/jobs/server.go index 4015d581eb..01cf821dcb 100644 --- a/jobs/server.go +++ b/jobs/server.go @@ -4,12 +4,9 @@ package jobs import ( - l4g "github.com/alecthomas/log4go" - ejobs "github.com/mattermost/mattermost-server/einterfaces/jobs" "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/store" - "github.com/mattermost/mattermost-server/utils" ) type ConfigService interface { @@ -50,36 +47,6 @@ func (srv *JobServer) Config() *model.Config { return srv.ConfigService.Config() } -func (srv *JobServer) LoadLicense() { - licenseId := "" - if result := <-srv.Store.System().Get(); result.Err == nil { - props := result.Data.(model.StringMap) - licenseId = props[model.SYSTEM_ACTIVE_LICENSE_ID] - } - - var licenseBytes []byte - - if len(licenseId) != 26 { - // Lets attempt to load the file from disk since it was missing from the DB - _, licenseBytes = utils.GetAndValidateLicenseFileFromDisk(*srv.ConfigService.Config().ServiceSettings.LicenseFileLocation) - } else { - if result := <-srv.Store.License().Get(licenseId); result.Err == nil { - record := result.Data.(*model.LicenseRecord) - licenseBytes = []byte(record.Bytes) - l4g.Info("License key valid unlocking enterprise features.") - } else { - l4g.Info(utils.T("mattermost.load_license.find.warn")) - } - } - - if licenseBytes != nil { - utils.LoadLicense(licenseBytes) - l4g.Info("License key valid unlocking enterprise features.") - } else { - l4g.Info(utils.T("mattermost.load_license.find.warn")) - } -} - func (srv *JobServer) StartWorkers() { srv.Workers = srv.InitWorkers().Start() } diff --git a/jobs/server_test.go b/jobs/server_test.go deleted file mode 100644 index 3b5ef6f3df..0000000000 --- a/jobs/server_test.go +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package jobs - -import ( - "testing" - - "github.com/mattermost/mattermost-server/model" - "github.com/mattermost/mattermost-server/store" - "github.com/mattermost/mattermost-server/store/storetest" - "github.com/mattermost/mattermost-server/utils" -) - -func TestJobServer_LoadLicense(t *testing.T) { - if utils.T == nil { - utils.TranslationsPreInit() - } - - mockStore := &storetest.Store{} - defer mockStore.AssertExpectations(t) - - server := &JobServer{ - Store: mockStore, - } - - mockStore.SystemStore.On("Get").Return(storetest.NewStoreChannel(store.StoreResult{ - Data: model.StringMap{ - model.SYSTEM_ACTIVE_LICENSE_ID: "thelicenseid00000000000000", - }, - })) - mockStore.LicenseStore.On("Get", "thelicenseid00000000000000").Return(storetest.NewStoreChannel(store.StoreResult{ - Data: &model.LicenseRecord{ - Id: "thelicenseid00000000000000", - }, - })) - - server.LoadLicense() -} diff --git a/utils/authorization.go b/utils/authorization.go index 39a0d606c6..42815b807f 100644 --- a/utils/authorization.go +++ b/utils/authorization.go @@ -7,7 +7,7 @@ import ( "github.com/mattermost/mattermost-server/model" ) -func DefaultRolesBasedOnConfig(cfg *model.Config) map[string]*model.Role { +func DefaultRolesBasedOnConfig(cfg *model.Config, isLicensed bool) map[string]*model.Role { roles := make(map[string]*model.Role) for id, role := range model.DefaultRoles { copy := &model.Role{} @@ -15,7 +15,7 @@ func DefaultRolesBasedOnConfig(cfg *model.Config) map[string]*model.Role { roles[id] = copy } - if IsLicensed() { + if isLicensed { switch *cfg.TeamSettings.RestrictPublicChannelCreation { case model.PERMISSIONS_ALL: roles[model.TEAM_USER_ROLE_ID].Permissions = append( @@ -35,7 +35,7 @@ func DefaultRolesBasedOnConfig(cfg *model.Config) map[string]*model.Role { ) } - if IsLicensed() { + if isLicensed { switch *cfg.TeamSettings.RestrictPublicChannelManagement { case model.PERMISSIONS_ALL: roles[model.TEAM_USER_ROLE_ID].Permissions = append( @@ -64,7 +64,7 @@ func DefaultRolesBasedOnConfig(cfg *model.Config) map[string]*model.Role { ) } - if IsLicensed() { + if isLicensed { switch *cfg.TeamSettings.RestrictPublicChannelDeletion { case model.PERMISSIONS_ALL: roles[model.TEAM_USER_ROLE_ID].Permissions = append( @@ -93,7 +93,7 @@ func DefaultRolesBasedOnConfig(cfg *model.Config) map[string]*model.Role { ) } - if IsLicensed() { + if isLicensed { switch *cfg.TeamSettings.RestrictPrivateChannelCreation { case model.PERMISSIONS_ALL: roles[model.TEAM_USER_ROLE_ID].Permissions = append( @@ -113,7 +113,7 @@ func DefaultRolesBasedOnConfig(cfg *model.Config) map[string]*model.Role { ) } - if IsLicensed() { + if isLicensed { switch *cfg.TeamSettings.RestrictPrivateChannelManagement { case model.PERMISSIONS_ALL: roles[model.TEAM_USER_ROLE_ID].Permissions = append( @@ -142,7 +142,7 @@ func DefaultRolesBasedOnConfig(cfg *model.Config) map[string]*model.Role { ) } - if IsLicensed() { + if isLicensed { switch *cfg.TeamSettings.RestrictPrivateChannelDeletion { case model.PERMISSIONS_ALL: roles[model.TEAM_USER_ROLE_ID].Permissions = append( @@ -172,7 +172,7 @@ func DefaultRolesBasedOnConfig(cfg *model.Config) map[string]*model.Role { } // Restrict permissions for Private Channel Manage Members - if IsLicensed() { + if isLicensed { switch *cfg.TeamSettings.RestrictPrivateChannelManageMembers { case model.PERMISSIONS_ALL: roles[model.CHANNEL_USER_ROLE_ID].Permissions = append( @@ -214,7 +214,7 @@ func DefaultRolesBasedOnConfig(cfg *model.Config) map[string]*model.Role { } // Grant permissions for inviting and adding users to a team. - if IsLicensed() { + if isLicensed { if *cfg.TeamSettings.RestrictTeamInvite == model.PERMISSIONS_TEAM_ADMIN { roles[model.TEAM_ADMIN_ROLE_ID].Permissions = append( roles[model.TEAM_ADMIN_ROLE_ID].Permissions, @@ -236,7 +236,7 @@ func DefaultRolesBasedOnConfig(cfg *model.Config) map[string]*model.Role { ) } - if IsLicensed() { + if isLicensed { switch *cfg.ServiceSettings.RestrictPostDelete { case model.PERMISSIONS_DELETE_POST_ALL: roles[model.CHANNEL_USER_ROLE_ID].Permissions = append( diff --git a/utils/config.go b/utils/config.go index 9e962eef47..0669d63e41 100644 --- a/utils/config.go +++ b/utils/config.go @@ -342,7 +342,7 @@ func LoadConfig(fileName string) (config *model.Config, configPath string, appEr return config, configPath, nil } -func GenerateClientConfig(c *model.Config, diagnosticId string) map[string]string { +func GenerateClientConfig(c *model.Config, diagnosticId string, license *model.License) map[string]string { props := make(map[string]string) props["Version"] = model.CurrentVersion @@ -456,18 +456,17 @@ func GenerateClientConfig(c *model.Config, diagnosticId string) map[string]strin props["PluginsEnabled"] = strconv.FormatBool(*c.PluginSettings.Enable) - if IsLicensed() { - License := License() + if license != nil { props["ExperimentalTownSquareIsReadOnly"] = strconv.FormatBool(*c.TeamSettings.ExperimentalTownSquareIsReadOnly) props["ExperimentalEnableAuthenticationTransfer"] = strconv.FormatBool(*c.ServiceSettings.ExperimentalEnableAuthenticationTransfer) - if *License.Features.CustomBrand { + if *license.Features.CustomBrand { props["EnableCustomBrand"] = strconv.FormatBool(*c.TeamSettings.EnableCustomBrand) props["CustomBrandText"] = *c.TeamSettings.CustomBrandText props["CustomDescriptionText"] = *c.TeamSettings.CustomDescriptionText } - if *License.Features.LDAP { + if *license.Features.LDAP { props["EnableLdap"] = strconv.FormatBool(*c.LdapSettings.Enable) props["LdapLoginFieldName"] = *c.LdapSettings.LoginFieldName props["LdapNicknameAttributeSet"] = strconv.FormatBool(*c.LdapSettings.NicknameAttribute != "") @@ -478,16 +477,16 @@ func GenerateClientConfig(c *model.Config, diagnosticId string) map[string]strin props["LdapLoginButtonTextColor"] = *c.LdapSettings.LoginButtonTextColor } - if *License.Features.MFA { + if *license.Features.MFA { props["EnableMultifactorAuthentication"] = strconv.FormatBool(*c.ServiceSettings.EnableMultifactorAuthentication) props["EnforceMultifactorAuthentication"] = strconv.FormatBool(*c.ServiceSettings.EnforceMultifactorAuthentication) } - if *License.Features.Compliance { + if *license.Features.Compliance { props["EnableCompliance"] = strconv.FormatBool(*c.ComplianceSettings.Enable) } - if *License.Features.SAML { + if *license.Features.SAML { props["EnableSaml"] = strconv.FormatBool(*c.SamlSettings.Enable) props["SamlLoginButtonText"] = *c.SamlSettings.LoginButtonText props["SamlFirstNameAttributeSet"] = strconv.FormatBool(*c.SamlSettings.FirstNameAttribute != "") @@ -498,23 +497,23 @@ func GenerateClientConfig(c *model.Config, diagnosticId string) map[string]strin props["SamlLoginButtonTextColor"] = *c.SamlSettings.LoginButtonTextColor } - if *License.Features.Cluster { + if *license.Features.Cluster { props["EnableCluster"] = strconv.FormatBool(*c.ClusterSettings.Enable) } - if *License.Features.Cluster { + if *license.Features.Cluster { props["EnableMetrics"] = strconv.FormatBool(*c.MetricsSettings.Enable) } - if *License.Features.GoogleOAuth { + if *license.Features.GoogleOAuth { props["EnableSignUpWithGoogle"] = strconv.FormatBool(c.GoogleSettings.Enable) } - if *License.Features.Office365OAuth { + if *license.Features.Office365OAuth { props["EnableSignUpWithOffice365"] = strconv.FormatBool(c.Office365Settings.Enable) } - if *License.Features.PasswordRequirements { + if *license.Features.PasswordRequirements { props["PasswordMinimumLength"] = fmt.Sprintf("%v", *c.PasswordSettings.MinimumLength) props["PasswordRequireLowercase"] = strconv.FormatBool(*c.PasswordSettings.Lowercase) props["PasswordRequireUppercase"] = strconv.FormatBool(*c.PasswordSettings.Uppercase) @@ -522,7 +521,7 @@ func GenerateClientConfig(c *model.Config, diagnosticId string) map[string]strin props["PasswordRequireSymbol"] = strconv.FormatBool(*c.PasswordSettings.Symbol) } - if *License.Features.Announcement { + if *license.Features.Announcement { props["EnableBanner"] = strconv.FormatBool(*c.AnnouncementSettings.EnableBanner) props["BannerText"] = *c.AnnouncementSettings.BannerText props["BannerColor"] = *c.AnnouncementSettings.BannerColor @@ -530,14 +529,14 @@ func GenerateClientConfig(c *model.Config, diagnosticId string) map[string]strin props["AllowBannerDismissal"] = strconv.FormatBool(*c.AnnouncementSettings.AllowBannerDismissal) } - if *License.Features.ThemeManagement { + if *license.Features.ThemeManagement { props["EnableThemeSelection"] = strconv.FormatBool(*c.ThemeSettings.EnableThemeSelection) props["DefaultTheme"] = *c.ThemeSettings.DefaultTheme props["AllowCustomThemes"] = strconv.FormatBool(*c.ThemeSettings.AllowCustomThemes) props["AllowedThemes"] = strings.Join(c.ThemeSettings.AllowedThemes, ",") } - if *License.Features.DataRetention { + if *license.Features.DataRetention { props["DataRetentionEnableMessageDeletion"] = strconv.FormatBool(*c.DataRetentionSettings.EnableMessageDeletion) props["DataRetentionMessageRetentionDays"] = strconv.FormatInt(int64(*c.DataRetentionSettings.MessageRetentionDays), 10) props["DataRetentionEnableFileDeletion"] = strconv.FormatBool(*c.DataRetentionSettings.EnableFileDeletion) diff --git a/utils/config_test.go b/utils/config_test.go index 9abc56d5ef..5809422f12 100644 --- a/utils/config_test.go +++ b/utils/config_test.go @@ -197,7 +197,7 @@ func TestGetClientConfig(t *testing.T) { cfg, _, err := LoadConfig("config.json") require.Nil(t, err) - configMap := GenerateClientConfig(cfg, "") + configMap := GenerateClientConfig(cfg, "", nil) if configMap["EmailNotificationContentsType"] != *cfg.EmailSettings.EmailNotificationContentsType { t.Fatal("EmailSettings.EmailNotificationContentsType not exposed to client config") } diff --git a/utils/file_backend.go b/utils/file_backend.go index c7a6c55912..42af7f604b 100644 --- a/utils/file_backend.go +++ b/utils/file_backend.go @@ -22,7 +22,7 @@ type FileBackend interface { RemoveDirectory(path string) *model.AppError } -func NewFileBackend(settings *model.FileSettings) (FileBackend, *model.AppError) { +func NewFileBackend(settings *model.FileSettings, enableComplianceFeatures bool) (FileBackend, *model.AppError) { switch *settings.DriverName { case model.IMAGE_DRIVER_S3: return &S3FileBackend{ @@ -33,7 +33,7 @@ func NewFileBackend(settings *model.FileSettings) (FileBackend, *model.AppError) signV2: settings.AmazonS3SignV2 != nil && *settings.AmazonS3SignV2, region: settings.AmazonS3Region, bucket: settings.AmazonS3Bucket, - encrypt: settings.AmazonS3SSE != nil && *settings.AmazonS3SSE && IsLicensed() && *License().Features.Compliance, + encrypt: settings.AmazonS3SSE != nil && *settings.AmazonS3SSE && enableComplianceFeatures, trace: settings.AmazonS3Trace != nil && *settings.AmazonS3Trace, }, nil case model.IMAGE_DRIVER_LOCAL: diff --git a/utils/file_backend_test.go b/utils/file_backend_test.go index 76cd1f4a81..46f75574ef 100644 --- a/utils/file_backend_test.go +++ b/utils/file_backend_test.go @@ -63,7 +63,7 @@ func TestS3FileBackendTestSuite(t *testing.T) { func (s *FileBackendTestSuite) SetupTest() { TranslationsPreInit() - backend, err := NewFileBackend(&s.settings) + backend, err := NewFileBackend(&s.settings, true) require.Nil(s.T(), err) s.backend = backend } diff --git a/utils/license.go b/utils/license.go index 2aaa2a5492..2853a58d00 100644 --- a/utils/license.go +++ b/utils/license.go @@ -5,28 +5,21 @@ package utils import ( "crypto" - "crypto/md5" "crypto/rsa" "crypto/sha512" "crypto/x509" "encoding/base64" "encoding/pem" - "fmt" "io/ioutil" "os" "strconv" "strings" - "sync/atomic" l4g "github.com/alecthomas/log4go" "github.com/mattermost/mattermost-server/model" ) -var isLicensedInt32 int32 -var licenseValue atomic.Value -var clientLicenseValue atomic.Value - var publicKey []byte = []byte(`-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyZmShlU8Z8HdG0IWSZ8r tSyzyxrXkJjsFUf0Ke7bm/TLtIggRdqOcUF3XEWqQk5RGD5vuq7Rlg1zZqMEBk8N @@ -37,92 +30,6 @@ a0v85XL6i9ote2P+fLZ3wX9EoioHzgdgB7arOxY50QRJO7OyCqpKFKv6lRWTXuSt hwIDAQAB -----END PUBLIC KEY-----`) -func init() { - SetLicense(nil) -} - -func IsLicensed() bool { - return atomic.LoadInt32(&isLicensedInt32) == 1 -} - -func SetIsLicensed(v bool) { - if v { - atomic.StoreInt32(&isLicensedInt32, 1) - } else { - atomic.StoreInt32(&isLicensedInt32, 0) - } -} - -func License() *model.License { - return licenseValue.Load().(*model.License) -} - -func SetClientLicense(m map[string]string) { - clientLicenseValue.Store(m) -} - -func ClientLicense() map[string]string { - return clientLicenseValue.Load().(map[string]string) -} - -func LoadLicense(licenseBytes []byte) { - if success, licenseStr := ValidateLicense(licenseBytes); success { - license := model.LicenseFromJson(strings.NewReader(licenseStr)) - SetLicense(license) - return - } - - l4g.Warn(T("utils.license.load_license.invalid.warn")) -} - -var licenseListeners = map[string]func(){} - -func AddLicenseListener(listener func()) string { - id := model.NewId() - licenseListeners[id] = listener - return id -} - -func RemoveLicenseListener(id string) { - delete(licenseListeners, id) -} - -func SetLicense(license *model.License) bool { - defer func() { - for _, listener := range licenseListeners { - listener() - } - }() - - if license == nil { - SetIsLicensed(false) - license = &model.License{ - Features: new(model.Features), - } - license.Features.SetDefaults() - licenseValue.Store(license) - - SetClientLicense(map[string]string{"IsLicensed": "false"}) - - return false - } else { - license.Features.SetDefaults() - - if !license.IsExpired() { - licenseValue.Store(license) - SetIsLicensed(true) - clientLicenseValue.Store(getClientLicense(license)) - return true - } - - return false - } -} - -func RemoveLicense() { - SetLicense(nil) -} - func ValidateLicense(signed []byte) (bool, string) { decoded := make([]byte, base64.StdEncoding.DecodedLen(len(signed))) @@ -213,12 +120,12 @@ func GetLicenseFileLocation(fileLocation string) string { } } -func getClientLicense(l *model.License) map[string]string { +func GetClientLicense(l *model.License) map[string]string { props := make(map[string]string) - props["IsLicensed"] = strconv.FormatBool(IsLicensed()) + props["IsLicensed"] = strconv.FormatBool(l != nil) - if IsLicensed() { + if l != nil { props["Id"] = l.Id props["Users"] = strconv.Itoa(*l.Features.Users) props["LDAP"] = strconv.FormatBool(*l.Features.LDAP) @@ -248,39 +155,3 @@ func getClientLicense(l *model.License) map[string]string { return props } - -func GetClientLicenseEtag(useSanitized bool) string { - value := "" - - lic := ClientLicense() - - if useSanitized { - lic = GetSanitizedClientLicense() - } - - for k, v := range lic { - value += fmt.Sprintf("%s:%s;", k, v) - } - - return model.Etag(fmt.Sprintf("%x", md5.Sum([]byte(value)))) -} - -func GetSanitizedClientLicense() map[string]string { - sanitizedLicense := make(map[string]string) - - for k, v := range ClientLicense() { - sanitizedLicense[k] = v - } - - if IsLicensed() { - delete(sanitizedLicense, "Id") - delete(sanitizedLicense, "Name") - delete(sanitizedLicense, "Email") - delete(sanitizedLicense, "PhoneNumber") - delete(sanitizedLicense, "IssuedAt") - delete(sanitizedLicense, "StartsAt") - delete(sanitizedLicense, "ExpiresAt") - } - - return sanitizedLicense -} diff --git a/utils/license_test.go b/utils/license_test.go index 9771ec4977..c2d1b4c05f 100644 --- a/utils/license_test.go +++ b/utils/license_test.go @@ -5,87 +5,20 @@ package utils import ( "testing" - - "github.com/mattermost/mattermost-server/model" ) -func TestSetLicense(t *testing.T) { - l1 := &model.License{} - l1.Features = &model.Features{} - l1.Customer = &model.Customer{} - l1.StartsAt = model.GetMillis() - 1000 - l1.ExpiresAt = model.GetMillis() + 100000 - if ok := SetLicense(l1); !ok { - t.Fatal("license should have worked") - } - - l2 := &model.License{} - l2.Features = &model.Features{} - l2.Customer = &model.Customer{} - l2.StartsAt = model.GetMillis() - 1000 - l2.ExpiresAt = model.GetMillis() - 100 - if ok := SetLicense(l2); ok { - t.Fatal("license should have failed") - } - - l3 := &model.License{} - l3.Features = &model.Features{} - l3.Customer = &model.Customer{} - l3.StartsAt = model.GetMillis() + 10000 - l3.ExpiresAt = model.GetMillis() + 100000 - if ok := SetLicense(l3); !ok { - t.Fatal("license should have passed") - } -} - func TestValidateLicense(t *testing.T) { b1 := []byte("junk") if ok, _ := ValidateLicense(b1); ok { t.Fatal("should have failed - bad license") } - LoadLicense(b1) - b2 := []byte("junkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunk") if ok, _ := ValidateLicense(b2); ok { t.Fatal("should have failed - bad license") } } -func TestClientLicenseEtag(t *testing.T) { - etag1 := GetClientLicenseEtag(false) - - SetClientLicense(map[string]string{"SomeFeature": "true", "IsLicensed": "true"}) - - etag2 := GetClientLicenseEtag(false) - if etag1 == etag2 { - t.Fatal("etags should not match") - } - - SetClientLicense(map[string]string{"SomeFeature": "true", "IsLicensed": "false"}) - - etag3 := GetClientLicenseEtag(false) - if etag2 == etag3 { - t.Fatal("etags should not match") - } -} - -func TestGetSanitizedClientLicense(t *testing.T) { - l1 := &model.License{} - l1.Features = &model.Features{} - l1.Customer = &model.Customer{} - l1.Customer.Name = "TestName" - l1.StartsAt = model.GetMillis() - 1000 - l1.ExpiresAt = model.GetMillis() + 100000 - SetLicense(l1) - - m := GetSanitizedClientLicense() - - if _, ok := m["Name"]; ok { - t.Fatal("should have been sanatized") - } -} - func TestGetLicenseFileLocation(t *testing.T) { fileName := GetLicenseFileLocation("") if len(fileName) == 0 { diff --git a/utils/mail.go b/utils/mail.go index 4c8a505af8..633f978181 100644 --- a/utils/mail.go +++ b/utils/mail.go @@ -105,17 +105,17 @@ func TestConnection(config *model.Config) { defer c.Close() } -func SendMailUsingConfig(to, subject, htmlBody string, config *model.Config) *model.AppError { +func SendMailUsingConfig(to, subject, htmlBody string, config *model.Config, enableComplianceFeatures bool) *model.AppError { fromMail := mail.Address{Name: config.EmailSettings.FeedbackName, Address: config.EmailSettings.FeedbackEmail} - return sendMail(to, to, fromMail, subject, htmlBody, nil, nil, config) + return sendMail(to, to, fromMail, subject, htmlBody, nil, nil, config, enableComplianceFeatures) } // allows for sending an email with attachments and differing MIME/SMTP recipients -func SendMailUsingConfigAdvanced(mimeTo, smtpTo string, from mail.Address, subject, htmlBody string, attachments []*model.FileInfo, mimeHeaders map[string]string, config *model.Config) *model.AppError { - return sendMail(mimeTo, smtpTo, from, subject, htmlBody, attachments, mimeHeaders, config) +func SendMailUsingConfigAdvanced(mimeTo, smtpTo string, from mail.Address, subject, htmlBody string, attachments []*model.FileInfo, mimeHeaders map[string]string, config *model.Config, enableComplianceFeatures bool) *model.AppError { + return sendMail(mimeTo, smtpTo, from, subject, htmlBody, attachments, mimeHeaders, config, enableComplianceFeatures) } -func sendMail(mimeTo, smtpTo string, from mail.Address, subject, htmlBody string, attachments []*model.FileInfo, mimeHeaders map[string]string, config *model.Config) *model.AppError { +func sendMail(mimeTo, smtpTo string, from mail.Address, subject, htmlBody string, attachments []*model.FileInfo, mimeHeaders map[string]string, config *model.Config, enableComplianceFeatures bool) *model.AppError { if !config.EmailSettings.SendEmailNotifications || len(config.EmailSettings.SMTPServer) == 0 { return nil } @@ -151,7 +151,7 @@ func sendMail(mimeTo, smtpTo string, from mail.Address, subject, htmlBody string m.AddAlternative("text/html", htmlMessage) if attachments != nil { - fileBackend, err := NewFileBackend(&config.FileSettings) + fileBackend, err := NewFileBackend(&config.FileSettings, enableComplianceFeatures) if err != nil { return err } diff --git a/utils/mail_test.go b/utils/mail_test.go index 207fe32a53..7034204417 100644 --- a/utils/mail_test.go +++ b/utils/mail_test.go @@ -50,7 +50,7 @@ func TestSendMailUsingConfig(t *testing.T) { //Delete all the messages before check the sample email DeleteMailBox(emailTo) - if err := SendMailUsingConfig(emailTo, emailSubject, emailBody, cfg); err != nil { + if err := SendMailUsingConfig(emailTo, emailSubject, emailBody, cfg, true); err != nil { t.Log(err) t.Fatal("Should connect to the STMP Server") } else { @@ -95,7 +95,7 @@ func TestSendMailUsingConfigAdvanced(t *testing.T) { DeleteMailBox(smtpTo) // create a file that will be attached to the email - fileBackend, err := NewFileBackend(&cfg.FileSettings) + fileBackend, err := NewFileBackend(&cfg.FileSettings, true) assert.Nil(t, err) fileContents := []byte("hello world") fileName := "file.txt" @@ -111,7 +111,7 @@ func TestSendMailUsingConfigAdvanced(t *testing.T) { headers := make(map[string]string) headers["TestHeader"] = "TestValue" - if err := SendMailUsingConfigAdvanced(mimeTo, smtpTo, from, emailSubject, emailBody, attachments, headers, cfg); err != nil { + if err := SendMailUsingConfigAdvanced(mimeTo, smtpTo, from, emailSubject, emailBody, attachments, headers, cfg, true); err != nil { t.Log(err) t.Fatal("Should connect to the STMP Server") } else { From 0daac7e4fc05ecb64dbc162daff618569bb249cd Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 9 Feb 2018 13:56:11 -0600 Subject: [PATCH 09/15] Add /v4/image api (#8230) * add image api * i suppose i should add a test... * only redirect to image proxy --- api4/api.go | 5 +++++ api4/image.go | 22 ++++++++++++++++++++ api4/image_test.go | 52 ++++++++++++++++++++++++++++++++++++++++++++++ utils/config.go | 3 +++ 4 files changed, 82 insertions(+) create mode 100644 api4/image.go create mode 100644 api4/image_test.go diff --git a/api4/api.go b/api4/api.go index 580bd8c587..871dca0ac7 100644 --- a/api4/api.go +++ b/api4/api.go @@ -76,6 +76,8 @@ type Routes struct { Compliance *mux.Router // 'api/v4/compliance' Cluster *mux.Router // 'api/v4/cluster' + Image *mux.Router // 'api/v4/image' + LDAP *mux.Router // 'api/v4/ldap' Elasticsearch *mux.Router // 'api/v4/elasticsearch' @@ -194,6 +196,8 @@ func Init(a *app.App, root *mux.Router, full bool) *API { api.BaseRoutes.OpenGraph = api.BaseRoutes.ApiRoot.PathPrefix("/opengraph").Subrouter() + api.BaseRoutes.Image = api.BaseRoutes.ApiRoot.PathPrefix("/image").Subrouter() + api.InitUser() api.InitTeam() api.InitChannel() @@ -219,6 +223,7 @@ func Init(a *app.App, root *mux.Router, full bool) *API { api.InitWebrtc() api.InitOpenGraph() api.InitPlugin() + api.InitImage() root.Handle("/api/v4/{anything:.*}", http.HandlerFunc(Handle404)) diff --git a/api4/image.go b/api4/image.go new file mode 100644 index 0000000000..4589de2045 --- /dev/null +++ b/api4/image.go @@ -0,0 +1,22 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package api4 + +import ( + "net/http" +) + +func (api *API) InitImage() { + api.BaseRoutes.Image.Handle("", api.ApiSessionRequiredTrustRequester(getImage)).Methods("GET") +} + +func getImage(c *Context, w http.ResponseWriter, r *http.Request) { + // Only redirect to our image proxy if one is enabled. Arbitrary redirects are not allowed for + // security reasons. + if transform := c.App.ImageProxyAdder(); transform != nil { + http.Redirect(w, r, transform(r.URL.Query().Get("url")), http.StatusFound) + } else { + http.NotFound(w, r) + } +} diff --git a/api4/image_test.go b/api4/image_test.go new file mode 100644 index 0000000000..236d5785da --- /dev/null +++ b/api4/image_test.go @@ -0,0 +1,52 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package api4 + +import ( + "net/http" + "net/url" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/mattermost/mattermost-server/model" +) + +func TestGetImage(t *testing.T) { + th := Setup().InitBasic() + defer th.TearDown() + + th.Client.HttpClient.CheckRedirect = func(*http.Request, []*http.Request) error { + return http.ErrUseLastResponse + } + + originURL := "http://foo.bar/baz.gif" + + r, err := http.NewRequest("GET", th.Client.ApiUrl+"/image?url="+url.QueryEscape(originURL), nil) + require.NoError(t, err) + r.Header.Set(model.HEADER_AUTH, th.Client.AuthType+" "+th.Client.AuthToken) + + th.App.UpdateConfig(func(cfg *model.Config) { + cfg.ServiceSettings.ImageProxyType = nil + }) + + resp, err := th.Client.HttpClient.Do(r) + require.NoError(t, err) + assert.Equal(t, http.StatusNotFound, resp.StatusCode) + + th.App.UpdateConfig(func(cfg *model.Config) { + cfg.ServiceSettings.ImageProxyType = model.NewString("willnorris/imageproxy") + cfg.ServiceSettings.ImageProxyURL = model.NewString("https://proxy.foo.bar") + }) + + r, err = http.NewRequest("GET", th.Client.ApiUrl+"/image?url="+originURL, nil) + require.NoError(t, err) + r.Header.Set(model.HEADER_AUTH, th.Client.AuthType+" "+th.Client.AuthToken) + + resp, err = th.Client.HttpClient.Do(r) + require.NoError(t, err) + assert.Equal(t, http.StatusFound, resp.StatusCode) + assert.Equal(t, "https://proxy.foo.bar//"+originURL, resp.Header.Get("Location")) +} diff --git a/utils/config.go b/utils/config.go index 0669d63e41..a855733a75 100644 --- a/utils/config.go +++ b/utils/config.go @@ -456,6 +456,9 @@ func GenerateClientConfig(c *model.Config, diagnosticId string, license *model.L props["PluginsEnabled"] = strconv.FormatBool(*c.PluginSettings.Enable) + hasImageProxy := c.ServiceSettings.ImageProxyType != nil && *c.ServiceSettings.ImageProxyType != "" && c.ServiceSettings.ImageProxyURL != nil && *c.ServiceSettings.ImageProxyURL != "" + props["HasImageProxy"] = strconv.FormatBool(hasImageProxy) + if license != nil { props["ExperimentalTownSquareIsReadOnly"] = strconv.FormatBool(*c.TeamSettings.ExperimentalTownSquareIsReadOnly) props["ExperimentalEnableAuthenticationTransfer"] = strconv.FormatBool(*c.ServiceSettings.ExperimentalEnableAuthenticationTransfer) From a4e9499714999d58f26c712df02c014f1facccf7 Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 9 Feb 2018 13:56:11 -0600 Subject: [PATCH 10/15] Add /v4/image api (#8230) * add image api * i suppose i should add a test... * only redirect to image proxy --- api4/api.go | 5 +++++ api4/image.go | 22 ++++++++++++++++++++ api4/image_test.go | 52 ++++++++++++++++++++++++++++++++++++++++++++++ utils/config.go | 3 +++ 4 files changed, 82 insertions(+) create mode 100644 api4/image.go create mode 100644 api4/image_test.go diff --git a/api4/api.go b/api4/api.go index 580bd8c587..871dca0ac7 100644 --- a/api4/api.go +++ b/api4/api.go @@ -76,6 +76,8 @@ type Routes struct { Compliance *mux.Router // 'api/v4/compliance' Cluster *mux.Router // 'api/v4/cluster' + Image *mux.Router // 'api/v4/image' + LDAP *mux.Router // 'api/v4/ldap' Elasticsearch *mux.Router // 'api/v4/elasticsearch' @@ -194,6 +196,8 @@ func Init(a *app.App, root *mux.Router, full bool) *API { api.BaseRoutes.OpenGraph = api.BaseRoutes.ApiRoot.PathPrefix("/opengraph").Subrouter() + api.BaseRoutes.Image = api.BaseRoutes.ApiRoot.PathPrefix("/image").Subrouter() + api.InitUser() api.InitTeam() api.InitChannel() @@ -219,6 +223,7 @@ func Init(a *app.App, root *mux.Router, full bool) *API { api.InitWebrtc() api.InitOpenGraph() api.InitPlugin() + api.InitImage() root.Handle("/api/v4/{anything:.*}", http.HandlerFunc(Handle404)) diff --git a/api4/image.go b/api4/image.go new file mode 100644 index 0000000000..4589de2045 --- /dev/null +++ b/api4/image.go @@ -0,0 +1,22 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package api4 + +import ( + "net/http" +) + +func (api *API) InitImage() { + api.BaseRoutes.Image.Handle("", api.ApiSessionRequiredTrustRequester(getImage)).Methods("GET") +} + +func getImage(c *Context, w http.ResponseWriter, r *http.Request) { + // Only redirect to our image proxy if one is enabled. Arbitrary redirects are not allowed for + // security reasons. + if transform := c.App.ImageProxyAdder(); transform != nil { + http.Redirect(w, r, transform(r.URL.Query().Get("url")), http.StatusFound) + } else { + http.NotFound(w, r) + } +} diff --git a/api4/image_test.go b/api4/image_test.go new file mode 100644 index 0000000000..236d5785da --- /dev/null +++ b/api4/image_test.go @@ -0,0 +1,52 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package api4 + +import ( + "net/http" + "net/url" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/mattermost/mattermost-server/model" +) + +func TestGetImage(t *testing.T) { + th := Setup().InitBasic() + defer th.TearDown() + + th.Client.HttpClient.CheckRedirect = func(*http.Request, []*http.Request) error { + return http.ErrUseLastResponse + } + + originURL := "http://foo.bar/baz.gif" + + r, err := http.NewRequest("GET", th.Client.ApiUrl+"/image?url="+url.QueryEscape(originURL), nil) + require.NoError(t, err) + r.Header.Set(model.HEADER_AUTH, th.Client.AuthType+" "+th.Client.AuthToken) + + th.App.UpdateConfig(func(cfg *model.Config) { + cfg.ServiceSettings.ImageProxyType = nil + }) + + resp, err := th.Client.HttpClient.Do(r) + require.NoError(t, err) + assert.Equal(t, http.StatusNotFound, resp.StatusCode) + + th.App.UpdateConfig(func(cfg *model.Config) { + cfg.ServiceSettings.ImageProxyType = model.NewString("willnorris/imageproxy") + cfg.ServiceSettings.ImageProxyURL = model.NewString("https://proxy.foo.bar") + }) + + r, err = http.NewRequest("GET", th.Client.ApiUrl+"/image?url="+originURL, nil) + require.NoError(t, err) + r.Header.Set(model.HEADER_AUTH, th.Client.AuthType+" "+th.Client.AuthToken) + + resp, err = th.Client.HttpClient.Do(r) + require.NoError(t, err) + assert.Equal(t, http.StatusFound, resp.StatusCode) + assert.Equal(t, "https://proxy.foo.bar//"+originURL, resp.Header.Get("Location")) +} diff --git a/utils/config.go b/utils/config.go index 9e962eef47..87ebee693c 100644 --- a/utils/config.go +++ b/utils/config.go @@ -456,6 +456,9 @@ func GenerateClientConfig(c *model.Config, diagnosticId string) map[string]strin props["PluginsEnabled"] = strconv.FormatBool(*c.PluginSettings.Enable) + hasImageProxy := c.ServiceSettings.ImageProxyType != nil && *c.ServiceSettings.ImageProxyType != "" && c.ServiceSettings.ImageProxyURL != nil && *c.ServiceSettings.ImageProxyURL != "" + props["HasImageProxy"] = strconv.FormatBool(hasImageProxy) + if IsLicensed() { License := License() props["ExperimentalTownSquareIsReadOnly"] = strconv.FormatBool(*c.TeamSettings.ExperimentalTownSquareIsReadOnly) From 396e7513ecc7d86b04e56745586c802e56e5d763 Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 9 Feb 2018 15:41:06 -0600 Subject: [PATCH 11/15] Don't proxy same-site image urls (#8238) * don't proxy same-site urls * fix empty site url case --- app/post.go | 8 ++++++-- app/post_test.go | 10 ++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/app/post.go b/app/post.go index 005624605c..5b0e59b233 100644 --- a/app/post.go +++ b/app/post.go @@ -876,6 +876,10 @@ func (a *App) imageProxyConfig() (proxyType, proxyURL, options, siteURL string) proxyURL += "/" } + if siteURL == "" || siteURL[len(siteURL)-1] != '/' { + siteURL += "/" + } + if cfg.ServiceSettings.ImageProxyOptions != nil { options = *cfg.ServiceSettings.ImageProxyOptions } @@ -890,12 +894,12 @@ func (a *App) ImageProxyAdder() func(string) string { } return func(url string) string { - if url == "" || strings.HasPrefix(url, proxyURL) { + if url == "" || strings.HasPrefix(url, siteURL) || strings.HasPrefix(url, proxyURL) { return url } if url[0] == '/' { - url = siteURL + url + url = siteURL + url[1:] } switch proxyType { diff --git a/app/post_test.go b/app/post_test.go index 3f3783265f..e09d3a1982 100644 --- a/app/post_test.go +++ b/app/post_test.go @@ -190,6 +190,10 @@ func TestImageProxy(t *testing.T) { th := Setup().InitBasic() defer th.TearDown() + th.App.UpdateConfig(func(cfg *model.Config) { + *cfg.ServiceSettings.SiteURL = "http://mymattermost.com" + }) + for name, tc := range map[string]struct { ProxyType string ProxyURL string @@ -211,6 +215,12 @@ func TestImageProxy(t *testing.T) { ImageURL: "http://mydomain.com/myimage", ProxiedImageURL: "https://127.0.0.1/x1000/http://mydomain.com/myimage", }, + "willnorris/imageproxy_SameSite": { + ProxyType: "willnorris/imageproxy", + ProxyURL: "https://127.0.0.1", + ImageURL: "http://mymattermost.com/myimage", + ProxiedImageURL: "http://mymattermost.com/myimage", + }, "willnorris/imageproxy_EmptyImageURL": { ProxyType: "willnorris/imageproxy", ProxyURL: "https://127.0.0.1", From 3e0c3eff9f2ddec241cdb3f7a91230fd7c51a5f6 Mon Sep 17 00:00:00 2001 From: Joram Wilander Date: Fri, 9 Feb 2018 17:47:22 -0500 Subject: [PATCH 12/15] ABC-228 Update GetPosts caching to work for non-60 limits (#8233) * Update GetPosts caching to work for non-60 limits * Only cache on limits of 30/60 and add test * Add comments clarifying 30 and 60 limits --- store/sqlstore/post_store.go | 15 ++++++++++----- store/storetest/post_store.go | 23 +++++++++++++++++++++-- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/store/sqlstore/post_store.go b/store/sqlstore/post_store.go index bc336e70d9..25c3c49139 100644 --- a/store/sqlstore/post_store.go +++ b/store/sqlstore/post_store.go @@ -322,7 +322,10 @@ type etagPosts struct { func (s SqlPostStore) InvalidateLastPostTimeCache(channelId string) { lastPostTimeCache.Remove(channelId) - lastPostsCache.Remove(channelId) + + // Keys are "{channelid}{limit}" and caching only occurs on limits of 30 and 60 + lastPostsCache.Remove(channelId + "30") + lastPostsCache.Remove(channelId + "60") } func (s SqlPostStore) GetEtag(channelId string, allowFromCache bool) store.StoreChannel { @@ -439,8 +442,9 @@ func (s SqlPostStore) GetPosts(channelId string, offset int, limit int, allowFro return } - if allowFromCache && offset == 0 && limit == 60 { - if cacheItem, ok := lastPostsCache.Get(channelId); ok { + // Caching only occurs on limits of 30 and 60, the common limits requested by MM clients + if allowFromCache && offset == 0 && (limit == 60 || limit == 30) { + if cacheItem, ok := lastPostsCache.Get(fmt.Sprintf("%s%v", channelId, limit)); ok { if s.metrics != nil { s.metrics.IncrementMemCacheHitCounter("Last Posts Cache") } @@ -482,8 +486,9 @@ func (s SqlPostStore) GetPosts(channelId string, offset int, limit int, allowFro list.MakeNonNil() - if offset == 0 && limit == 60 { - lastPostsCache.AddWithExpiresInSecs(channelId, list, LAST_POSTS_CACHE_SEC) + // Caching only occurs on limits of 30 and 60, the common limits requested by MM clients + if offset == 0 && (limit == 60 || limit == 30) { + lastPostsCache.AddWithExpiresInSecs(fmt.Sprintf("%s%v", channelId, limit), list, LAST_POSTS_CACHE_SEC) } result.Data = list diff --git a/store/storetest/post_store.go b/store/storetest/post_store.go index 4deb7f8d4e..e663d5a41f 100644 --- a/store/storetest/post_store.go +++ b/store/storetest/post_store.go @@ -27,7 +27,7 @@ func TestPostStore(t *testing.T, ss store.Store) { t.Run("PermDelete1Level", func(t *testing.T) { testPostStorePermDelete1Level(t, ss) }) t.Run("PermDelete1Level2", func(t *testing.T) { testPostStorePermDelete1Level2(t, ss) }) t.Run("GetWithChildren", func(t *testing.T) { testPostStoreGetWithChildren(t, ss) }) - t.Run("GetPostsWtihDetails", func(t *testing.T) { testPostStoreGetPostsWtihDetails(t, ss) }) + t.Run("GetPostsWithDetails", func(t *testing.T) { testPostStoreGetPostsWithDetails(t, ss) }) t.Run("GetPostsBeforeAfter", func(t *testing.T) { testPostStoreGetPostsBeforeAfter(t, ss) }) t.Run("GetPostsSince", func(t *testing.T) { testPostStoreGetPostsSince(t, ss) }) t.Run("Search", func(t *testing.T) { testPostStoreSearch(t, ss) }) @@ -490,7 +490,7 @@ func testPostStoreGetWithChildren(t *testing.T, ss store.Store) { } } -func testPostStoreGetPostsWtihDetails(t *testing.T, ss store.Store) { +func testPostStoreGetPostsWithDetails(t *testing.T, ss store.Store) { o1 := &model.Post{} o1.ChannelId = model.NewId() o1.UserId = model.NewId() @@ -591,6 +591,25 @@ func testPostStoreGetPostsWtihDetails(t *testing.T, ss store.Store) { if r2.Posts[o1.Id].Message != o1.Message { t.Fatal("Missing parent") } + + // Run once to fill cache + <-ss.Post().GetPosts(o1.ChannelId, 0, 30, true) + + o6 := &model.Post{} + o6.ChannelId = o1.ChannelId + o6.UserId = model.NewId() + o6.Message = "zz" + model.NewId() + "b" + o6 = (<-ss.Post().Save(o6)).Data.(*model.Post) + + // Should only be 6 since we hit the cache + r3 := (<-ss.Post().GetPosts(o1.ChannelId, 0, 30, true)).Data.(*model.PostList) + assert.Equal(t, 6, len(r3.Order)) + + ss.Post().InvalidateLastPostTimeCache(o1.ChannelId) + + // Cache was invalidated, we should get all the posts + r4 := (<-ss.Post().GetPosts(o1.ChannelId, 0, 30, true)).Data.(*model.PostList) + assert.Equal(t, 7, len(r4.Order)) } func testPostStoreGetPostsBeforeAfter(t *testing.T, ss store.Store) { From c1b6e8792c9f91c66c35737438c20757ef43066f Mon Sep 17 00:00:00 2001 From: Christopher Brown Date: Fri, 9 Feb 2018 20:08:39 -0600 Subject: [PATCH 13/15] minor addition to #8238 --- app/post.go | 6 +----- app/post_test.go | 6 ++++++ 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/app/post.go b/app/post.go index 5b0e59b233..be9374e10a 100644 --- a/app/post.go +++ b/app/post.go @@ -894,14 +894,10 @@ func (a *App) ImageProxyAdder() func(string) string { } return func(url string) string { - if url == "" || strings.HasPrefix(url, siteURL) || strings.HasPrefix(url, proxyURL) { + if url == "" || url[0] == '/' || strings.HasPrefix(url, siteURL) || strings.HasPrefix(url, proxyURL) { return url } - if url[0] == '/' { - url = siteURL + url[1:] - } - switch proxyType { case "atmos/camo": mac := hmac.New(sha1.New, []byte(options)) diff --git a/app/post_test.go b/app/post_test.go index e09d3a1982..409bc043d6 100644 --- a/app/post_test.go +++ b/app/post_test.go @@ -221,6 +221,12 @@ func TestImageProxy(t *testing.T) { ImageURL: "http://mymattermost.com/myimage", ProxiedImageURL: "http://mymattermost.com/myimage", }, + "willnorris/imageproxy_PathOnly": { + ProxyType: "willnorris/imageproxy", + ProxyURL: "https://127.0.0.1", + ImageURL: "/myimage", + ProxiedImageURL: "/myimage", + }, "willnorris/imageproxy_EmptyImageURL": { ProxyType: "willnorris/imageproxy", ProxyURL: "https://127.0.0.1", From 9707ac3aaf2cb4352c573aadf54b8535e237dd9e Mon Sep 17 00:00:00 2001 From: Jonathan Date: Mon, 12 Feb 2018 09:16:17 -0500 Subject: [PATCH 14/15] Added invite_id field to email invite url, along with validation of this field on the server (#8235) --- api4/team_test.go | 3 ++- app/email.go | 1 + app/team.go | 5 ++++ app/team_test.go | 67 +++++++++++++++++++++++++++++++++++++++++++++++ i18n/en.json | 16 +++-------- 5 files changed, 79 insertions(+), 13 deletions(-) diff --git a/api4/team_test.go b/api4/team_test.go index a8696a30b5..d365fd6865 100644 --- a/api4/team_test.go +++ b/api4/team_test.go @@ -1250,7 +1250,7 @@ func TestAddTeamMember(t *testing.T) { tm, resp := Client.AddTeamMember(team.Id, otherUser.Id) CheckForbiddenStatus(t, resp) if resp.Error == nil { - t.Fatalf("Error is nhul") + t.Fatalf("Error is nil") } Client.Logout() @@ -1376,6 +1376,7 @@ func TestAddTeamMember(t *testing.T) { dataObject := make(map[string]string) dataObject["time"] = fmt.Sprintf("%v", model.GetMillis()) dataObject["id"] = team.Id + dataObject["invite_id"] = team.InviteId data := model.MapToJson(dataObject) hashed := utils.HashSha256(fmt.Sprintf("%v:%v", data, th.App.Config().EmailSettings.InviteSalt)) diff --git a/app/email.go b/app/email.go index b809b972d1..764dc017ab 100644 --- a/app/email.go +++ b/app/email.go @@ -276,6 +276,7 @@ func (a *App) SendInviteEmails(team *model.Team, senderName string, invites []st props["display_name"] = team.DisplayName props["name"] = team.Name props["time"] = fmt.Sprintf("%v", model.GetMillis()) + props["invite_id"] = team.InviteId data := model.MapToJson(props) hash := utils.HashSha256(fmt.Sprintf("%v:%v", data, a.Config().EmailSettings.InviteSalt)) bodyPage.Props["Link"] = fmt.Sprintf("%s/signup_user_complete/?d=%s&h=%s", siteURL, url.QueryEscape(data), url.QueryEscape(hash)) diff --git a/app/team.go b/app/team.go index 21b8e5879e..8e8c29e2ab 100644 --- a/app/team.go +++ b/app/team.go @@ -234,6 +234,11 @@ func (a *App) AddUserToTeamByHash(userId string, hash string, data string) (*mod team = result.Data.(*model.Team) } + // verify that the team's invite id hasn't been changed since the invite was sent + if team.InviteId != props["invite_id"] { + return nil, model.NewAppError("JoinUserToTeamByHash", "api.user.create_user.signup_link_mismatched_invite_id.app_error", nil, "", http.StatusBadRequest) + } + var user *model.User if result := <-uchan; result.Err != nil { return nil, result.Err diff --git a/app/team_test.go b/app/team_test.go index 084558fb44..7cb20b6f69 100644 --- a/app/team_test.go +++ b/app/team_test.go @@ -7,7 +7,15 @@ import ( "strings" "testing" + "fmt" + + "sync/atomic" + "github.com/mattermost/mattermost-server/model" + "github.com/mattermost/mattermost-server/store" + "github.com/mattermost/mattermost-server/store/storetest" + "github.com/mattermost/mattermost-server/utils" + "github.com/stretchr/testify/assert" ) func TestCreateTeam(t *testing.T) { @@ -393,3 +401,62 @@ func TestSanitizeTeams(t *testing.T) { } }) } + +func TestAddUserToTeamByHashMismatchedInviteId(t *testing.T) { + mockStore := &storetest.Store{} + defer mockStore.AssertExpectations(t) + + teamId := model.NewId() + userId := model.NewId() + inviteSalt := model.NewId() + + inviteId := model.NewId() + teamInviteId := model.NewId() + + // generate a fake email invite - stolen from SendInviteEmails() in email.go + props := make(map[string]string) + props["email"] = model.NewId() + "@mattermost.com" + props["id"] = teamId + props["display_name"] = model.NewId() + props["name"] = model.NewId() + props["time"] = fmt.Sprintf("%v", model.GetMillis()) + props["invite_id"] = inviteId + data := model.MapToJson(props) + hash := utils.HashSha256(fmt.Sprintf("%v:%v", data, inviteSalt)) + + // when the server tries to validate the invite, it will pull the user from our mock store + // this can return nil, because we'll fail before we get to trying to use it + mockStore.UserStore.On("Get", userId).Return( + storetest.NewStoreChannel(store.StoreResult{ + Data: nil, + Err: nil, + }), + ) + + // the server will also pull the team. the one we return has a different invite id than the one in the email invite we made above + mockStore.TeamStore.On("Get", teamId).Return( + storetest.NewStoreChannel(store.StoreResult{ + Data: &model.Team{ + InviteId: teamInviteId, + }, + Err: nil, + }), + ) + + app := App{ + Srv: &Server{ + Store: mockStore, + }, + config: atomic.Value{}, + } + app.config.Store(&model.Config{ + EmailSettings: model.EmailSettings{ + InviteSalt: inviteSalt, + }, + }) + + // this should fail because the invite ids are mismatched + team, err := app.AddUserToTeamByHash(userId, hash, data) + assert.Nil(t, team) + assert.Equal(t, "api.user.create_user.signup_link_mismatched_invite_id.app_error", err.Id) +} diff --git a/i18n/en.json b/i18n/en.json index d983e88556..4365a44fb6 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -2202,10 +2202,6 @@ "id": "api.team.create_team_from_signup.expired_link.app_error", "translation": "The signup link has expired" }, - { - "id": "api.team.create_team_from_signup.invalid_link.app_error", - "translation": "The signup link does not appear to be valid" - }, { "id": "api.team.create_team_from_signup.unavailable.app_error", "translation": "This URL is unavailable. Please try another." @@ -2706,6 +2702,10 @@ "id": "api.user.create_user.signup_link_expired.app_error", "translation": "The signup link has expired" }, + { + "id": "api.user.create_user.signup_link_mismatched_invite_id.app_error", + "translation": "The signup link does not appear to be valid" + }, { "id": "api.user.create_user.signup_link_invalid.app_error", "translation": "The signup link does not appear to be valid" @@ -7294,10 +7294,6 @@ "id": "web.root.singup_title", "translation": "Signup" }, - { - "id": "web.signup_team_complete.invalid_link.app_error", - "translation": "The signup link does not appear to be valid" - }, { "id": "web.signup_team_complete.link_expired.app_error", "translation": "The signup link has expired" @@ -7314,10 +7310,6 @@ "id": "web.signup_user_complete.link_expired.app_error", "translation": "The signup link has expired" }, - { - "id": "web.signup_user_complete.link_invalid.app_error", - "translation": "The signup link does not appear to be valid" - }, { "id": "web.signup_user_complete.no_invites.app_error", "translation": "The team type doesn't allow open invites" From 07fd7aeeb8eb2b198b01b713a4ab57f6352faef2 Mon Sep 17 00:00:00 2001 From: Pierre de La Morinerie Date: Mon, 12 Feb 2018 22:16:32 +0530 Subject: [PATCH 15/15] Add tests for the `platform server` command (#8231) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Cleanup app state on initialization error When returning an initialization error, the app state was not cleaned up. This is especially visible during tests, as `appCount` is not decremented, and makes the new app initialization fail. * Test the `platform server` command As the `platform server` command only exits when interrupted by a signal, it is not possible to test it as the other cobra commands. Instead we directly test the actual command function. The internal command handler is slighly refactored to take a channel in argument, and registers it as the signal handler. Nothing very different—except than controlling this channel from the outside allows the test to send the system signal itself, thus preventing the server to run forever. --- app/app.go | 11 ++++-- cmd/platform/server.go | 10 +++--- cmd/platform/server_test.go | 72 +++++++++++++++++++++++++++++++++++++ jobs/jobs_watcher.go | 6 ++-- 4 files changed, 89 insertions(+), 10 deletions(-) create mode 100644 cmd/platform/server_test.go diff --git a/app/app.go b/app/app.go index dd5deb342d..636f0a4285 100644 --- a/app/app.go +++ b/app/app.go @@ -86,7 +86,7 @@ var appCount = 0 // New creates a new App. You must call Shutdown when you're done with it. // XXX: For now, only one at a time is allowed as some resources are still shared. -func New(options ...Option) (*App, error) { +func New(options ...Option) (outApp *App, outErr error) { appCount++ if appCount > 1 { panic("Only one App should exist at a time. Did you forget to call Shutdown()?") @@ -103,6 +103,11 @@ func New(options ...Option) (*App, error) { clientConfig: make(map[string]string), licenseListeners: map[string]func(){}, } + defer func() { + if outErr != nil { + app.Shutdown() + } + }() for _, option := range options { option(app) @@ -182,7 +187,9 @@ func (a *App) Shutdown() { a.ShutDownPlugins() a.WaitForGoroutines() - a.Srv.Store.Close() + if a.Srv.Store != nil { + a.Srv.Store.Close() + } a.Srv = nil if a.htmlTemplateWatcher != nil { diff --git a/cmd/platform/server.go b/cmd/platform/server.go index a8a6e8923f..1b411cf202 100644 --- a/cmd/platform/server.go +++ b/cmd/platform/server.go @@ -42,10 +42,11 @@ func runServerCmd(cmd *cobra.Command, args []string) error { disableConfigWatch, _ := cmd.Flags().GetBool("disableconfigwatch") - return runServer(config, disableConfigWatch) + interruptChan := make(chan os.Signal, 1) + return runServer(config, disableConfigWatch, interruptChan) } -func runServer(configFileLocation string, disableConfigWatch bool) error { +func runServer(configFileLocation string, disableConfigWatch bool, interruptChan chan os.Signal) error { options := []app.Option{app.ConfigFile(configFileLocation)} if disableConfigWatch { options = append(options, app.DisableConfigWatch) @@ -165,9 +166,8 @@ func runServer(configFileLocation string, disableConfigWatch bool) error { // wait for kill signal before attempting to gracefully shutdown // the running service - c := make(chan os.Signal, 1) - signal.Notify(c, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) - <-c + signal.Notify(interruptChan, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) + <-interruptChan if a.Cluster != nil { a.Cluster.StopInterNodeCommunication() diff --git a/cmd/platform/server_test.go b/cmd/platform/server_test.go new file mode 100644 index 0000000000..15f9a357a3 --- /dev/null +++ b/cmd/platform/server_test.go @@ -0,0 +1,72 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package main + +import ( + "io/ioutil" + "os" + "syscall" + "testing" + + "github.com/mattermost/mattermost-server/jobs" + "github.com/mattermost/mattermost-server/utils" + "github.com/stretchr/testify/require" +) + +type ServerTestHelper struct { + configPath string + disableConfigWatch bool + interruptChan chan os.Signal + originalInterval int +} + +func SetupServerTest() *ServerTestHelper { + // Build a channel that will be used by the server to receive system signals… + interruptChan := make(chan os.Signal, 1) + // …and sent it immediately a SIGINT value. + // This will make the server loop stop as soon as it started successfully. + interruptChan <- syscall.SIGINT + + // Let jobs poll for termination every 0.2s (instead of every 15s by default) + // Otherwise we would have to wait the whole polling duration before the test + // terminates. + originalInterval := jobs.DEFAULT_WATCHER_POLLING_INTERVAL + jobs.DEFAULT_WATCHER_POLLING_INTERVAL = 200 + + th := &ServerTestHelper{ + configPath: utils.FindConfigFile("config.json"), + disableConfigWatch: true, + interruptChan: interruptChan, + originalInterval: originalInterval, + } + return th +} + +func (th *ServerTestHelper) TearDownServerTest() { + jobs.DEFAULT_WATCHER_POLLING_INTERVAL = th.originalInterval +} + +func TestRunServerSuccess(t *testing.T) { + th := SetupServerTest() + defer th.TearDownServerTest() + + err := runServer(th.configPath, th.disableConfigWatch, th.interruptChan) + require.NoError(t, err) +} + +func TestRunServerInvalidConfigFile(t *testing.T) { + th := SetupServerTest() + defer th.TearDownServerTest() + + // Start the server with an unreadable config file + unreadableConfigFile, err := ioutil.TempFile("", "mattermost-unreadable-config-file-") + if err != nil { + panic(err) + } + os.Chmod(unreadableConfigFile.Name(), 0200) + defer os.Remove(unreadableConfigFile.Name()) + + err = runServer(unreadableConfigFile.Name(), th.disableConfigWatch, th.interruptChan) + require.Error(t, err) +} diff --git a/jobs/jobs_watcher.go b/jobs/jobs_watcher.go index f519e7ccae..eaa3a4e737 100644 --- a/jobs/jobs_watcher.go +++ b/jobs/jobs_watcher.go @@ -11,9 +11,9 @@ import ( "github.com/mattermost/mattermost-server/model" ) -const ( - DEFAULT_WATCHER_POLLING_INTERVAL = 15000 -) +// Default polling interval for jobs termination. +// (Defining as `var` rather than `const` allows tests to lower the interval.) +var DEFAULT_WATCHER_POLLING_INTERVAL = 15000 type Watcher struct { srv *JobServer