Merge branch 'release-4.7' into icu669
Этот коммит содержится в:
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
54
app/app.go
54
app/app.go
@@ -4,6 +4,7 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"html/template"
|
||||
"net"
|
||||
"net/http"
|
||||
@@ -58,15 +59,20 @@ 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
|
||||
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
|
||||
@@ -80,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()?")
|
||||
@@ -91,11 +97,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)
|
||||
@@ -118,9 +130,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"))
|
||||
|
||||
@@ -139,6 +151,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()
|
||||
@@ -157,7 +173,7 @@ func New(options ...Option) (*App, error) {
|
||||
|
||||
func (a *App) configOrLicenseListener() {
|
||||
a.regenerateClientConfig()
|
||||
a.SetDefaultRolesBasedOnConfig()
|
||||
a.setDefaultRolesBasedOnConfig()
|
||||
}
|
||||
|
||||
func (a *App) Shutdown() {
|
||||
@@ -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,5 +466,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())
|
||||
}
|
||||
|
||||
@@ -51,7 +51,8 @@ func TestAppRace(t *testing.T) {
|
||||
a, err := New()
|
||||
require.NoError(t, err)
|
||||
a.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.ListenAddress = ":0" })
|
||||
a.StartServer()
|
||||
serverErr := a.StartServer()
|
||||
require.NoError(t, serverErr)
|
||||
a.Shutdown()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,15 +96,20 @@ func setupTestHelper(enterprise bool) *TestHelper {
|
||||
if testStore != nil {
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.ListenAddress = ":0" })
|
||||
}
|
||||
th.App.StartServer()
|
||||
serverErr := th.App.StartServer()
|
||||
if serverErr != nil {
|
||||
panic(serverErr)
|
||||
}
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.ListenAddress = prevListenAddress })
|
||||
th.App.Srv.Store.MarkSystemRanUnitTests()
|
||||
|
||||
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
|
||||
|
||||
@@ -1359,7 +1359,7 @@ func (a *App) PermanentDeleteChannel(channel *model.Channel) *model.AppError {
|
||||
|
||||
// This function is intended for use from the CLI. It is not robust against people joining the channel while the move
|
||||
// is in progress, and therefore should not be used from the API without first fixing this potential race condition.
|
||||
func (a *App) MoveChannel(team *model.Team, channel *model.Channel) *model.AppError {
|
||||
func (a *App) MoveChannel(team *model.Team, channel *model.Channel, user *model.User) *model.AppError {
|
||||
// Check that all channel members are in the destination team.
|
||||
if channelMembers, err := a.GetChannelMembersPage(channel.Id, 0, 10000000); err != nil {
|
||||
return err
|
||||
@@ -1378,11 +1378,37 @@ func (a *App) MoveChannel(team *model.Team, channel *model.Channel) *model.AppEr
|
||||
}
|
||||
}
|
||||
|
||||
// Change the Team ID of the channel.
|
||||
// keep instance of the previous team
|
||||
var previousTeam *model.Team
|
||||
if result := <-a.Srv.Store.Team().Get(channel.TeamId); result.Err != nil {
|
||||
return result.Err
|
||||
} else {
|
||||
previousTeam = result.Data.(*model.Team)
|
||||
}
|
||||
channel.TeamId = team.Id
|
||||
if result := <-a.Srv.Store.Channel().Update(channel); result.Err != nil {
|
||||
return result.Err
|
||||
}
|
||||
a.postChannelMoveMessage(user, channel, previousTeam)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) postChannelMoveMessage(user *model.User, channel *model.Channel, previousTeam *model.Team) *model.AppError {
|
||||
|
||||
post := &model.Post{
|
||||
ChannelId: channel.Id,
|
||||
Message: fmt.Sprintf(utils.T("api.team.move_channel.success"), previousTeam.Name),
|
||||
Type: model.POST_MOVE_CHANNEL,
|
||||
UserId: user.Id,
|
||||
Props: model.StringInterface{
|
||||
"username": user.Username,
|
||||
},
|
||||
}
|
||||
|
||||
if _, err := a.CreatePost(post, channel, false); err != nil {
|
||||
return model.NewAppError("postChannelMoveMessage", "api.team.move_channel.post.error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ func TestMoveChannel(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := th.App.MoveChannel(targetTeam, channel1); err == nil {
|
||||
if err := th.App.MoveChannel(targetTeam, channel1, th.BasicUser); err == nil {
|
||||
t.Fatal("Should have failed due to mismatched members.")
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ func TestMoveChannel(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := th.App.MoveChannel(targetTeam, channel1); err != nil {
|
||||
if err := th.App.MoveChannel(targetTeam, channel1, th.BasicUser); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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"])
|
||||
}
|
||||
|
||||
@@ -501,6 +501,7 @@ func (a *App) trackConfig() {
|
||||
|
||||
a.SendDiagnostic(TRACK_CONFIG_MESSAGE_EXPORT, map[string]interface{}{
|
||||
"enable_message_export": *cfg.MessageExportSettings.EnableExport,
|
||||
"export_format": *cfg.MessageExportSettings.ExportFormat,
|
||||
"daily_run_time": *cfg.MessageExportSettings.DailyRunTime,
|
||||
"default_export_from_timestamp": *cfg.MessageExportSettings.ExportFromTimestamp,
|
||||
"batch_size": *cfg.MessageExportSettings.BatchSize,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
107
app/license.go
107
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,115 @@ 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() {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -565,6 +565,7 @@ func (a *App) RegisterPluginCommand(pluginId string, command *model.Command) err
|
||||
TeamId: command.TeamId,
|
||||
AutoComplete: command.AutoComplete,
|
||||
AutoCompleteDesc: command.AutoCompleteDesc,
|
||||
AutoCompleteHint: command.AutoCompleteHint,
|
||||
DisplayName: command.DisplayName,
|
||||
}
|
||||
|
||||
|
||||
10
app/post.go
10
app/post.go
@@ -922,6 +922,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
|
||||
}
|
||||
@@ -936,14 +940,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))
|
||||
|
||||
@@ -192,6 +192,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
|
||||
@@ -213,6 +217,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",
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
l4g "github.com/alecthomas/log4go"
|
||||
"github.com/gorilla/handlers"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/crypto/acme/autocert"
|
||||
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
@@ -116,7 +117,7 @@ func redirectHTTPToHTTPS(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, url.String(), http.StatusFound)
|
||||
}
|
||||
|
||||
func (a *App) StartServer() {
|
||||
func (a *App) StartServer() error {
|
||||
l4g.Info(utils.T("api.server.start_server.starting.info"))
|
||||
|
||||
var handler http.Handler = &CorsWrapper{a.Config, a.Srv.Router}
|
||||
@@ -126,8 +127,7 @@ func (a *App) StartServer() {
|
||||
|
||||
rateLimiter, err := NewRateLimiter(&a.Config().RateLimitSettings)
|
||||
if err != nil {
|
||||
l4g.Critical(err.Error())
|
||||
return
|
||||
return err
|
||||
}
|
||||
|
||||
a.Srv.RateLimiter = rateLimiter
|
||||
@@ -151,8 +151,8 @@ func (a *App) StartServer() {
|
||||
|
||||
listener, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
l4g.Critical(utils.T("api.server.start_server.starting.critical"), err)
|
||||
return
|
||||
errors.Wrapf(err, utils.T("api.server.start_server.starting.critical"), err)
|
||||
return err
|
||||
}
|
||||
a.Srv.ListenAddr = listener.Addr().(*net.TCPAddr)
|
||||
|
||||
@@ -214,6 +214,8 @@ func (a *App) StartServer() {
|
||||
}
|
||||
close(a.Srv.didFinishListen)
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type tcpKeepAliveListener struct {
|
||||
|
||||
50
app/server_test.go
Обычный файл
50
app/server_test.go
Обычный файл
@@ -0,0 +1,50 @@
|
||||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestStartServerSuccess(t *testing.T) {
|
||||
a, err := New()
|
||||
require.NoError(t, err)
|
||||
|
||||
a.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.ListenAddress = ":0" })
|
||||
serverErr := a.StartServer()
|
||||
a.Shutdown()
|
||||
require.NoError(t, serverErr)
|
||||
}
|
||||
|
||||
func TestStartServerRateLimiterCriticalError(t *testing.T) {
|
||||
a, err := New()
|
||||
require.NoError(t, err)
|
||||
|
||||
// Attempt to use Rate Limiter with an invalid config
|
||||
a.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.RateLimitSettings.Enable = true
|
||||
*cfg.RateLimitSettings.MaxBurst = -100
|
||||
})
|
||||
|
||||
serverErr := a.StartServer()
|
||||
a.Shutdown()
|
||||
require.Error(t, serverErr)
|
||||
}
|
||||
|
||||
func TestStartServerPortUnavailable(t *testing.T) {
|
||||
a, err := New()
|
||||
require.NoError(t, err)
|
||||
|
||||
// Attempt to listen on a system-reserved port
|
||||
a.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.ServiceSettings.ListenAddress = ":21"
|
||||
})
|
||||
|
||||
serverErr := a.StartServer()
|
||||
a.Shutdown()
|
||||
require.Error(t, serverErr)
|
||||
}
|
||||
@@ -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 })
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Ссылка в новой задаче
Block a user