summaryrefslogtreecommitdiffstats
path: root/app
diff options
context:
space:
mode:
authorGeorge Goldberg <george@gberg.me>2018-02-13 13:35:52 +0000
committerGeorge Goldberg <george@gberg.me>2018-02-13 13:46:01 +0000
commit5c101253c5987743cf1b3a8fe68814d748070622 (patch)
treee2632505d051e7d15d6daf974ed8ac92095f82fe /app
parentb7fc3d7d35ca4dd16097715a66463392a1dfaf0a (diff)
parentd88d2bc2ed3aefa68b5ed2942f493ae42bb40bfa (diff)
downloadchat-5c101253c5987743cf1b3a8fe68814d748070622.tar.gz
chat-5c101253c5987743cf1b3a8fe68814d748070622.tar.bz2
chat-5c101253c5987743cf1b3a8fe68814d748070622.zip
Merge branch 'master' into advanced-permissions-phase-1
Diffstat (limited to 'app')
-rw-r--r--app/admin.go3
-rw-r--r--app/app.go52
-rw-r--r--app/app_test.go10
-rw-r--r--app/apptestlib.go5
-rw-r--r--app/config.go98
-rw-r--r--app/config_test.go9
-rw-r--r--app/email.go4
-rw-r--r--app/file.go3
-rw-r--r--app/license.go106
-rw-r--r--app/license_test.go75
-rw-r--r--app/post.go10
-rw-r--r--app/post_test.go27
-rw-r--r--app/session_test.go22
-rw-r--r--app/team.go5
-rw-r--r--app/team_test.go67
15 files changed, 419 insertions, 77 deletions
diff --git a/app/admin.go b/app/admin.go
index b838ed3bd..154fa8899 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 ffb903b57..9d44c358c 100644
--- a/app/app.go
+++ b/app/app.go
@@ -4,6 +4,7 @@
package app
import (
+ "crypto/ecdsa"
"html/template"
"net"
"net/http"
@@ -61,14 +62,19 @@ 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
- sessionCache *utils.Cache
- configListenerId string
- licenseListenerId string
- disableConfigWatch bool
- configWatcher *utils.ConfigWatcher
+ htmlTemplateWatcher *utils.HTMLTemplateWatcher
+ sessionCache *utils.Cache
+ configListenerId string
+ licenseListenerId string
+ disableConfigWatch bool
+ configWatcher *utils.ConfigWatcher
+ asymmetricSigningKey *ecdsa.PrivateKey
pluginCommands []*PluginCommand
pluginCommandsLock sync.RWMutex
@@ -82,7 +88,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()?")
@@ -93,11 +99,17 @@ func New(options ...Option) (*App, error) {
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(){},
+ }
+ defer func() {
+ if outErr != nil {
+ app.Shutdown()
+ }
+ }()
for _, option := range options {
option(app)
@@ -120,7 +132,7 @@ 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()
l4g.Info(utils.T("api.server.new_server.init.info"))
@@ -140,6 +152,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()
@@ -171,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 {
@@ -179,7 +197,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()
@@ -448,7 +466,7 @@ 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())
}
// This function migrates the default built in roles from code/config to the database.
@@ -460,7 +478,7 @@ func (a *App) DoAdvancedPermissionsMigration() {
l4g.Info("Migrating roles to database.")
roles := model.MakeDefaultRoles()
- roles = utils.SetRolePermissionsFromConfig(roles, a.Config(), utils.IsLicensed())
+ roles = utils.SetRolePermissionsFromConfig(roles, a.Config(), a.License() != nil)
allSucceeded := true
diff --git a/app/app_test.go b/app/app_test.go
index 4a4238197..f31e0332c 100644
--- a/app/app_test.go
+++ b/app/app_test.go
@@ -226,16 +226,12 @@ func TestDoAdvancedPermissionsMigration(t *testing.T) {
}
// Add a license and change the policy config.
- 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.UpdateConfig(func(cfg *model.Config) {
@@ -244,9 +240,7 @@ func TestDoAdvancedPermissionsMigration(t *testing.T) {
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.SetLicense(model.NewTestLicense())
// Check the migration doesn't change anything if run again.
th.App.DoAdvancedPermissionsMigration()
@@ -394,7 +388,7 @@ func TestDoAdvancedPermissionsMigration(t *testing.T) {
}
// Remove the license.
- utils.SetIsLicensed(false)
+ th.App.SetLicense(nil)
// Do the migration again.
th.ResetRoleMigration()
diff --git a/app/apptestlib.go b/app/apptestlib.go
index 7a2d7157d..3402f1f79 100644
--- a/app/apptestlib.go
+++ b/app/apptestlib.go
@@ -114,9 +114,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 a2398f9e9..b4925e8fb 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())
+ 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)
+ }
clientConfigJSON, _ := json.Marshal(a.clientConfig)
a.clientConfigHash = fmt.Sprintf("%x", md5.Sum(clientConfigJSON))
}
@@ -166,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/config_test.go b/app/config_test.go
index e3d50b958..5ee999f0f 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/app/email.go b/app/email.go
index b809b972d..54a272a3b 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))
@@ -316,5 +317,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 d66c64adb..bb20585bb 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 c7fd07197..6a2206747 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"))
@@ -59,7 +62,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,21 +105,114 @@ 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 {
+ license, _ := a.licenseValue.Load().(*model.License)
+ return license
+}
+
+func (a *App) SetLicense(license *model.License) bool {
+ defer func() {
+ 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 5b73d9d18..f86d604d1 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/post.go b/app/post.go
index 843319082..3f6252faa 100644
--- a/app/post.go
+++ b/app/post.go
@@ -869,6 +869,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
}
@@ -883,14 +887,10 @@ func (a *App) ImageProxyAdder() func(string) string {
}
return func(url string) string {
- if url == "" || strings.HasPrefix(url, proxyURL) {
+ if url == "" || url[0] == '/' || strings.HasPrefix(url, siteURL) || strings.HasPrefix(url, proxyURL) {
return url
}
- if url[0] == '/' {
- url = siteURL + url
- }
-
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 049d3ff92..207935d58 100644
--- a/app/post_test.go
+++ b/app/post_test.go
@@ -15,7 +15,6 @@ import (
"github.com/stretchr/testify/require"
"github.com/mattermost/mattermost-server/model"
- "github.com/mattermost/mattermost-server/utils"
)
func TestUpdatePostEditAt(t *testing.T) {
@@ -51,15 +50,7 @@ func TestUpdatePostTimeLimit(t *testing.T) {
post := &model.Post{}
*post = *th.BasicPost
- 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.PostEditTimeLimit = -1
@@ -236,6 +227,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
@@ -257,6 +252,18 @@ 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_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",
diff --git a/app/session_test.go b/app/session_test.go
index bca3b59b7..bf8198a4e 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) {
@@ -48,18 +47,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)
@@ -122,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(),
@@ -136,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/app/team.go b/app/team.go
index 5a81bd220..dc10760f8 100644
--- a/app/team.go
+++ b/app/team.go
@@ -238,6 +238,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 084558fb4..7cb20b6f6 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)
+}