mirror of
https://github.com/mattermost/mattermost.git
synced 2025-02-25 18:55:24 -06:00
Merge remote-tracking branch 'origin/master' into advanced-permissions-phase-1
This commit is contained in:
@@ -105,7 +105,11 @@ 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 })
|
||||
api4.Init(th.App, th.App.Srv.Router, false)
|
||||
Init(th.App, th.App.Srv.Router)
|
||||
|
||||
@@ -127,7 +127,7 @@ func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
c.SetSiteURLHeader(app.GetProtocol(r) + "://" + r.Host)
|
||||
|
||||
w.Header().Set(model.HEADER_REQUEST_ID, c.RequestId)
|
||||
w.Header().Set(model.HEADER_VERSION_ID, fmt.Sprintf("%v.%v.%v.%v", model.CurrentVersion, model.BuildNumber, c.App.ClientConfigHash(), utils.IsLicensed()))
|
||||
w.Header().Set(model.HEADER_VERSION_ID, fmt.Sprintf("%v.%v.%v.%v", model.CurrentVersion, model.BuildNumber, c.App.ClientConfigHash(), c.App.License() != nil))
|
||||
|
||||
// Instruct the browser not to display us in an iframe unless is the same origin for anti-clickjacking
|
||||
if !h.isApi {
|
||||
@@ -292,7 +292,7 @@ func (c *Context) UserRequired() {
|
||||
|
||||
func (c *Context) MfaRequired() {
|
||||
// Must be licensed for MFA and have it configured for enforcement
|
||||
if !utils.IsLicensed() || !*utils.License().Features.MFA || !*c.App.Config().ServiceSettings.EnableMultifactorAuthentication || !*c.App.Config().ServiceSettings.EnforceMultifactorAuthentication {
|
||||
if license := c.App.License(); license == nil || !*license.Features.MFA || !*c.App.Config().ServiceSettings.EnableMultifactorAuthentication || !*c.App.Config().ServiceSettings.EnforceMultifactorAuthentication {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -1057,7 +1057,7 @@ func updateMfa(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func checkMfa(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
if !utils.IsLicensed() || !*utils.License().Features.MFA || !*c.App.Config().ServiceSettings.EnableMultifactorAuthentication {
|
||||
if license := c.App.License(); license == nil || !*license.Features.MFA || !*c.App.Config().ServiceSettings.EnableMultifactorAuthentication {
|
||||
rdata := map[string]string{}
|
||||
rdata["mfa_required"] = "false"
|
||||
w.Write([]byte(model.MapToJson(rdata)))
|
||||
|
||||
@@ -113,7 +113,11 @@ 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 })
|
||||
Init(th.App, th.App.Srv.Router, true)
|
||||
wsapi.Init(th.App, th.App.Srv.WebSocketRouter)
|
||||
@@ -299,8 +303,7 @@ func (me *TestHelper) CreateUserWithClient(client *model.Client4) *model.User {
|
||||
}
|
||||
|
||||
utils.DisableDebugLogForTest()
|
||||
ruser, r := client.CreateUser(user)
|
||||
fmt.Println(r)
|
||||
ruser, _ := client.CreateUser(user)
|
||||
ruser.Password = "Password1"
|
||||
store.Must(me.App.Srv.Store.User().VerifyEmail(ruser.Id))
|
||||
utils.EnableDebugLogForTest()
|
||||
|
||||
@@ -112,7 +112,7 @@ func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
c.SetSiteURLHeader(app.GetProtocol(r) + "://" + r.Host)
|
||||
|
||||
w.Header().Set(model.HEADER_REQUEST_ID, c.RequestId)
|
||||
w.Header().Set(model.HEADER_VERSION_ID, fmt.Sprintf("%v.%v.%v.%v", model.CurrentVersion, model.BuildNumber, c.App.ClientConfigHash(), utils.IsLicensed()))
|
||||
w.Header().Set(model.HEADER_VERSION_ID, fmt.Sprintf("%v.%v.%v.%v", model.CurrentVersion, model.BuildNumber, c.App.ClientConfigHash(), c.App.License() != nil))
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
@@ -249,7 +249,7 @@ func (c *Context) SessionRequired() {
|
||||
|
||||
func (c *Context) MfaRequired() {
|
||||
// Must be licensed for MFA and have it configured for enforcement
|
||||
if !utils.IsLicensed() || !*utils.License().Features.MFA || !*c.App.Config().ServiceSettings.EnableMultifactorAuthentication || !*c.App.Config().ServiceSettings.EnforceMultifactorAuthentication {
|
||||
if license := c.App.License(); license == nil || !*license.Features.MFA || !*c.App.Config().ServiceSettings.EnableMultifactorAuthentication || !*c.App.Config().ServiceSettings.EnforceMultifactorAuthentication {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -738,7 +738,7 @@ func checkUserMfa(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
resp := map[string]interface{}{}
|
||||
resp["mfa_required"] = false
|
||||
|
||||
if !utils.IsLicensed() || !*utils.License().Features.MFA || !*c.App.Config().ServiceSettings.EnableMultifactorAuthentication {
|
||||
if license := c.App.License(); license == nil || !*license.Features.MFA || !*c.App.Config().ServiceSettings.EnableMultifactorAuthentication {
|
||||
w.Write([]byte(model.StringInterfaceToJson(resp)))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -50,7 +50,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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,7 +101,11 @@ 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.DoAdvancedPermissionsMigration()
|
||||
|
||||
@@ -36,7 +36,7 @@ func (tl TokenLocation) String() string {
|
||||
}
|
||||
|
||||
func (a *App) IsPasswordValid(password string) *model.AppError {
|
||||
if utils.IsLicensed() && *utils.License().Features.PasswordRequirements {
|
||||
if license := a.License(); license != nil && *license.Features.PasswordRequirements {
|
||||
return utils.IsPasswordValidWithSettings(password, &a.Config().PasswordSettings)
|
||||
}
|
||||
return utils.IsPasswordValid(password)
|
||||
@@ -150,7 +150,7 @@ func (a *App) CheckUserPostflightAuthenticationCriteria(user *model.User) *model
|
||||
}
|
||||
|
||||
func (a *App) CheckUserMfa(user *model.User, token string) *model.AppError {
|
||||
if !user.MfaActive || !utils.IsLicensed() || !*utils.License().Features.MFA || !*a.Config().ServiceSettings.EnableMultifactorAuthentication {
|
||||
if license := a.License(); !user.MfaActive || license == nil || !*license.Features.MFA || !*a.Config().ServiceSettings.EnableMultifactorAuthentication {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -183,7 +183,8 @@ func checkUserNotDisabled(user *model.User) *model.AppError {
|
||||
}
|
||||
|
||||
func (a *App) authenticateUser(user *model.User, password, mfaToken string) (*model.User, *model.AppError) {
|
||||
ldapAvailable := *a.Config().LdapSettings.Enable && a.Ldap != nil && utils.IsLicensed() && *utils.License().Features.LDAP
|
||||
license := a.License()
|
||||
ldapAvailable := *a.Config().LdapSettings.Enable && a.Ldap != nil && license != nil && *license.Features.LDAP
|
||||
|
||||
if user.AuthService == model.USER_AUTH_SERVICE_LDAP {
|
||||
if !ldapAvailable {
|
||||
|
||||
@@ -1363,7 +1363,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
|
||||
@@ -1382,11 +1382,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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
|
||||
l4g "github.com/alecthomas/log4go"
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
"github.com/mattermost/mattermost-server/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -80,7 +79,7 @@ func (me *ClusterDiscoveryService) Stop() {
|
||||
}
|
||||
|
||||
func (a *App) IsLeader() bool {
|
||||
if utils.IsLicensed() && *a.Config().ClusterSettings.Enable && a.Cluster != nil {
|
||||
if a.License() != nil && *a.Config().ClusterSettings.Enable && a.Cluster != nil {
|
||||
return a.Cluster.IsLeader()
|
||||
} else {
|
||||
return true
|
||||
|
||||
@@ -9,11 +9,10 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
"github.com/mattermost/mattermost-server/utils"
|
||||
)
|
||||
|
||||
func (a *App) GetComplianceReports(page, perPage int) (model.Compliances, *model.AppError) {
|
||||
if !*a.Config().ComplianceSettings.Enable || !utils.IsLicensed() || !*utils.License().Features.Compliance {
|
||||
if license := a.License(); !*a.Config().ComplianceSettings.Enable || license == nil || !*license.Features.Compliance {
|
||||
return nil, model.NewAppError("GetComplianceReports", "ent.compliance.licence_disable.app_error", nil, "", http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
@@ -25,7 +24,7 @@ func (a *App) GetComplianceReports(page, perPage int) (model.Compliances, *model
|
||||
}
|
||||
|
||||
func (a *App) SaveComplianceReport(job *model.Compliance) (*model.Compliance, *model.AppError) {
|
||||
if !*a.Config().ComplianceSettings.Enable || !utils.IsLicensed() || !*utils.License().Features.Compliance || a.Compliance == nil {
|
||||
if license := a.License(); !*a.Config().ComplianceSettings.Enable || license == nil || !*license.Features.Compliance || a.Compliance == nil {
|
||||
return nil, model.NewAppError("saveComplianceReport", "ent.compliance.licence_disable.app_error", nil, "", http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
@@ -44,7 +43,7 @@ func (a *App) SaveComplianceReport(job *model.Compliance) (*model.Compliance, *m
|
||||
}
|
||||
|
||||
func (a *App) GetComplianceReport(reportId string) (*model.Compliance, *model.AppError) {
|
||||
if !*a.Config().ComplianceSettings.Enable || !utils.IsLicensed() || !*utils.License().Features.Compliance || a.Compliance == nil {
|
||||
if license := a.License(); !*a.Config().ComplianceSettings.Enable || license == nil || !*license.Features.Compliance || a.Compliance == nil {
|
||||
return nil, model.NewAppError("downloadComplianceReport", "ent.compliance.licence_disable.app_error", nil, "", http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
|
||||
@@ -166,3 +166,11 @@ 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
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
"github.com/mattermost/mattermost-server/utils"
|
||||
"github.com/segmentio/analytics-go"
|
||||
)
|
||||
|
||||
@@ -502,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,
|
||||
@@ -509,17 +509,17 @@ func (a *App) trackConfig() {
|
||||
}
|
||||
|
||||
func (a *App) trackLicense() {
|
||||
if utils.IsLicensed() {
|
||||
if license := a.License(); license != nil {
|
||||
data := map[string]interface{}{
|
||||
"customer_id": utils.License().Customer.Id,
|
||||
"license_id": utils.License().Id,
|
||||
"issued": utils.License().IssuedAt,
|
||||
"start": utils.License().StartsAt,
|
||||
"expire": utils.License().ExpiresAt,
|
||||
"users": *utils.License().Features.Users,
|
||||
"customer_id": license.Customer.Id,
|
||||
"license_id": license.Id,
|
||||
"issued": license.IssuedAt,
|
||||
"start": license.StartsAt,
|
||||
"expire": license.ExpiresAt,
|
||||
"users": *license.Features.Users,
|
||||
}
|
||||
|
||||
features := utils.License().Features.ToMap()
|
||||
features := license.Features.ToMap()
|
||||
for featureName, featureValue := range features {
|
||||
data["feature_"+featureName] = featureValue
|
||||
}
|
||||
|
||||
@@ -220,7 +220,7 @@ func (a *App) sendBatchedEmailNotification(userId string, notifications []*batch
|
||||
}
|
||||
|
||||
emailNotificationContentsType := model.EMAIL_NOTIFICATION_CONTENTS_FULL
|
||||
if utils.IsLicensed() && *utils.License().Features.EmailNotificationContents {
|
||||
if license := a.License(); license != nil && *license.Features.EmailNotificationContents {
|
||||
emailNotificationContentsType = *a.Config().EmailSettings.EmailNotificationContentsType
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
func (a *App) SyncLdap() {
|
||||
a.Go(func() {
|
||||
|
||||
if utils.IsLicensed() && *utils.License().Features.LDAP && *a.Config().LdapSettings.EnableSync {
|
||||
if license := a.License(); license != nil && *license.Features.LDAP && *a.Config().LdapSettings.EnableSync {
|
||||
if ldapI := a.Ldap; ldapI != nil {
|
||||
ldapI.StartSynchronizeJob(false)
|
||||
} else {
|
||||
@@ -25,7 +25,8 @@ func (a *App) SyncLdap() {
|
||||
}
|
||||
|
||||
func (a *App) TestLdap() *model.AppError {
|
||||
if ldapI := a.Ldap; ldapI != nil && utils.IsLicensed() && *utils.License().Features.LDAP && (*a.Config().LdapSettings.Enable || *a.Config().LdapSettings.EnableSync) {
|
||||
license := a.License()
|
||||
if ldapI := a.Ldap; ldapI != nil && license != nil && *license.Features.LDAP && (*a.Config().LdapSettings.Enable || *a.Config().LdapSettings.EnableSync) {
|
||||
if err := ldapI.RunTest(); err != nil {
|
||||
err.StatusCode = 500
|
||||
return err
|
||||
@@ -39,7 +40,7 @@ func (a *App) TestLdap() *model.AppError {
|
||||
}
|
||||
|
||||
func (a *App) SwitchEmailToLdap(email, password, code, ldapId, ldapPassword string) (string, *model.AppError) {
|
||||
if utils.IsLicensed() && !*a.Config().ServiceSettings.ExperimentalEnableAuthenticationTransfer {
|
||||
if a.License() != nil && !*a.Config().ServiceSettings.ExperimentalEnableAuthenticationTransfer {
|
||||
return "", model.NewAppError("emailToLdap", "api.user.email_to_ldap.not_available.app_error", nil, "", http.StatusForbidden)
|
||||
}
|
||||
|
||||
@@ -75,7 +76,7 @@ func (a *App) SwitchEmailToLdap(email, password, code, ldapId, ldapPassword stri
|
||||
}
|
||||
|
||||
func (a *App) SwitchLdapToEmail(ldapPassword, code, email, newPassword string) (string, *model.AppError) {
|
||||
if utils.IsLicensed() && !*a.Config().ServiceSettings.ExperimentalEnableAuthenticationTransfer {
|
||||
if a.License() != nil && !*a.Config().ServiceSettings.ExperimentalEnableAuthenticationTransfer {
|
||||
return "", model.NewAppError("ldapToEmail", "api.user.ldap_to_email.not_available.app_error", nil, "", http.StatusForbidden)
|
||||
}
|
||||
|
||||
|
||||
@@ -6,8 +6,6 @@ package app
|
||||
import (
|
||||
//"github.com/mattermost/mattermost-server/model"
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/mattermost-server/utils"
|
||||
)
|
||||
|
||||
func TestLoadLicense(t *testing.T) {
|
||||
@@ -15,7 +13,7 @@ func TestLoadLicense(t *testing.T) {
|
||||
defer th.TearDown()
|
||||
|
||||
th.App.LoadLicense()
|
||||
if utils.IsLicensed() {
|
||||
if th.App.License() != nil {
|
||||
t.Fatal("shouldn't have a valid license")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,7 +233,7 @@ func (a *App) SendNotifications(post *model.Post, team *model.Team, channel *mod
|
||||
sendPushNotifications := false
|
||||
if *a.Config().EmailSettings.SendPushNotifications {
|
||||
pushServer := *a.Config().EmailSettings.PushNotificationServer
|
||||
if pushServer == model.MHPNS && (!utils.IsLicensed() || !*utils.License().Features.MHPNS) {
|
||||
if license := a.License(); pushServer == model.MHPNS && (license == nil || !*license.Features.MHPNS) {
|
||||
l4g.Warn(utils.T("api.post.send_notifications_and_forget.push_notification.mhpnsWarn"))
|
||||
sendPushNotifications = false
|
||||
} else {
|
||||
@@ -358,7 +358,7 @@ func (a *App) sendNotificationEmail(post *model.Post, user *model.User, channel
|
||||
}
|
||||
|
||||
emailNotificationContentsType := model.EMAIL_NOTIFICATION_CONTENTS_FULL
|
||||
if utils.IsLicensed() && *utils.License().Features.EmailNotificationContents {
|
||||
if license := a.License(); license != nil && *license.Features.EmailNotificationContents {
|
||||
emailNotificationContentsType = *a.Config().EmailSettings.EmailNotificationContentsType
|
||||
}
|
||||
|
||||
|
||||
@@ -717,7 +717,7 @@ func (a *App) AuthorizeOAuthUser(w http.ResponseWriter, r *http.Request, service
|
||||
}
|
||||
|
||||
func (a *App) SwitchEmailToOAuth(w http.ResponseWriter, r *http.Request, email, password, code, service string) (string, *model.AppError) {
|
||||
if utils.IsLicensed() && !*a.Config().ServiceSettings.ExperimentalEnableAuthenticationTransfer {
|
||||
if a.License() != nil && !*a.Config().ServiceSettings.ExperimentalEnableAuthenticationTransfer {
|
||||
return "", model.NewAppError("emailToOAuth", "api.user.email_to_oauth.not_available.app_error", nil, "", http.StatusForbidden)
|
||||
}
|
||||
|
||||
@@ -747,7 +747,7 @@ func (a *App) SwitchEmailToOAuth(w http.ResponseWriter, r *http.Request, email,
|
||||
}
|
||||
|
||||
func (a *App) SwitchOAuthToEmail(email, password, requesterId string) (string, *model.AppError) {
|
||||
if utils.IsLicensed() && !*a.Config().ServiceSettings.ExperimentalEnableAuthenticationTransfer {
|
||||
if a.License() != nil && !*a.Config().ServiceSettings.ExperimentalEnableAuthenticationTransfer {
|
||||
return "", model.NewAppError("oauthToEmail", "api.user.oauth_to_email.not_available.app_error", nil, "", http.StatusForbidden)
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
|
||||
@@ -124,7 +124,7 @@ func (a *App) CreatePost(post *model.Post, channel *model.Channel, triggerWebhoo
|
||||
user = result.Data.(*model.User)
|
||||
}
|
||||
|
||||
if utils.IsLicensed() && *a.Config().TeamSettings.ExperimentalTownSquareIsReadOnly &&
|
||||
if a.License() != nil && *a.Config().TeamSettings.ExperimentalTownSquareIsReadOnly &&
|
||||
!post.IsSystemMessage() &&
|
||||
channel.Name == model.DEFAULT_CHANNEL &&
|
||||
!a.RolesGrantPermission(user.GetRoles(), model.PERMISSION_MANAGE_SYSTEM.Id) {
|
||||
@@ -332,7 +332,7 @@ func (a *App) UpdatePost(post *model.Post, safeUpdate bool) (*model.Post, *model
|
||||
} else {
|
||||
oldPost = result.Data.(*model.PostList).Posts[post.Id]
|
||||
|
||||
if utils.IsLicensed() {
|
||||
if a.License() != nil {
|
||||
if *a.Config().ServiceSettings.AllowEditPost == model.ALLOW_EDIT_POST_NEVER && post.Message != oldPost.Message {
|
||||
err := model.NewAppError("UpdatePost", "api.post.update_post.permissions_denied.app_error", nil, "", http.StatusForbidden)
|
||||
return nil, err
|
||||
@@ -354,7 +354,7 @@ func (a *App) UpdatePost(post *model.Post, safeUpdate bool) (*model.Post, *model
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if utils.IsLicensed() {
|
||||
if a.License() != nil {
|
||||
if *a.Config().ServiceSettings.AllowEditPost == model.ALLOW_EDIT_POST_TIME_LIMIT && model.GetMillis() > oldPost.CreateAt+int64(*a.Config().ServiceSettings.PostEditTimeLimit*1000) && post.Message != oldPost.Message {
|
||||
err := model.NewAppError("UpdatePost", "api.post.update_post.permissions_time_limit.app_error", map[string]interface{}{"timeLimit": *a.Config().ServiceSettings.PostEditTimeLimit}, "", http.StatusBadRequest)
|
||||
return nil, err
|
||||
@@ -613,7 +613,7 @@ func (a *App) SearchPostsInTeam(terms string, userId string, teamId string, isOr
|
||||
paramsList := model.ParseSearchParams(terms)
|
||||
|
||||
esInterface := a.Elasticsearch
|
||||
if esInterface != nil && *a.Config().ElasticsearchSettings.EnableSearching && utils.IsLicensed() && *utils.License().Features.Elasticsearch {
|
||||
if license := a.License(); esInterface != nil && *a.Config().ElasticsearchSettings.EnableSearching && license != nil && *license.Features.Elasticsearch {
|
||||
finalParamsList := []*model.SearchParams{}
|
||||
|
||||
for _, params := range paramsList {
|
||||
|
||||
@@ -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
Normal file
50
app/server_test.go
Normal file
@@ -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)
|
||||
}
|
||||
@@ -69,8 +69,9 @@ func (a *App) GetSession(token string) (*model.Session, *model.AppError) {
|
||||
return nil, model.NewAppError("GetSession", "api.context.invalid_token.error", map[string]interface{}{"Token": token}, "", http.StatusUnauthorized)
|
||||
}
|
||||
|
||||
license := a.License()
|
||||
if *a.Config().ServiceSettings.SessionIdleTimeoutInMinutes > 0 &&
|
||||
utils.IsLicensed() && *utils.License().Features.Compliance &&
|
||||
license != nil && *license.Features.Compliance &&
|
||||
session != nil && !session.IsOAuth && !session.IsMobileApp() &&
|
||||
session.Props[model.SESSION_PROP_TYPE] != model.SESSION_TYPE_USER_ACCESS_TOKEN {
|
||||
|
||||
|
||||
@@ -374,7 +374,8 @@ func (a *App) GetUserByAuth(authData *string, authService string) (*model.User,
|
||||
}
|
||||
|
||||
func (a *App) GetUserForLogin(loginId string, onlyLdap bool) (*model.User, *model.AppError) {
|
||||
ldapAvailable := *a.Config().LdapSettings.Enable && a.Ldap != nil && utils.IsLicensed() && *utils.License().Features.LDAP
|
||||
license := a.License()
|
||||
ldapAvailable := *a.Config().LdapSettings.Enable && a.Ldap != nil && license != nil && *license.Features.LDAP
|
||||
|
||||
if result := <-a.Srv.Store.User().GetForLogin(
|
||||
loginId,
|
||||
|
||||
@@ -277,7 +277,7 @@ func (webCon *WebConn) IsAuthenticated() bool {
|
||||
|
||||
func (webCon *WebConn) SendHello() {
|
||||
msg := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_HELLO, "", "", webCon.UserId, nil)
|
||||
msg.Add("server_version", fmt.Sprintf("%v.%v.%v.%v", model.CurrentVersion, model.BuildNumber, webCon.App.ClientConfigHash(), utils.IsLicensed()))
|
||||
msg.Add("server_version", fmt.Sprintf("%v.%v.%v.%v", model.CurrentVersion, model.BuildNumber, webCon.App.ClientConfigHash(), webCon.App.License() != nil))
|
||||
webCon.Send <- msg
|
||||
}
|
||||
|
||||
|
||||
@@ -632,7 +632,7 @@ func (a *App) HandleIncomingWebhook(hookId string, req *model.IncomingWebhookReq
|
||||
}
|
||||
}
|
||||
|
||||
if utils.IsLicensed() && *a.Config().TeamSettings.ExperimentalTownSquareIsReadOnly &&
|
||||
if a.License() != nil && *a.Config().TeamSettings.ExperimentalTownSquareIsReadOnly &&
|
||||
channel.Name == model.DEFAULT_CHANNEL {
|
||||
return model.NewAppError("HandleIncomingWebhook", "api.post.create_post.town_square_read_only", nil, "", http.StatusForbidden)
|
||||
}
|
||||
|
||||
@@ -106,6 +106,8 @@ func init() {
|
||||
channelCreateCmd.Flags().String("purpose", "", "Channel purpose")
|
||||
channelCreateCmd.Flags().Bool("private", false, "Create a private channel.")
|
||||
|
||||
moveChannelsCmd.Flags().String("username", "", "Required. Username who is moving the channel.")
|
||||
|
||||
deleteChannelsCmd.Flags().Bool("confirm", false, "Confirm you really want to delete the channels.")
|
||||
|
||||
modifyChannelCmd.Flags().Bool("private", false, "Convert the channel to a private channel")
|
||||
@@ -319,26 +321,33 @@ func moveChannelsCmdF(cmd *cobra.Command, args []string) error {
|
||||
return errors.New("Unable to find destination team '" + args[0] + "'")
|
||||
}
|
||||
|
||||
username, erru := cmd.Flags().GetString("username")
|
||||
if erru != nil || username == "" {
|
||||
return errors.New("Username is required")
|
||||
}
|
||||
user := getUserFromUserArg(a, username)
|
||||
|
||||
channels := getChannelsFromChannelArgs(a, args[1:])
|
||||
for i, channel := range channels {
|
||||
if channel == nil {
|
||||
CommandPrintErrorln("Unable to find channel '" + args[i] + "'")
|
||||
continue
|
||||
}
|
||||
if err := moveChannel(a, team, channel); err != nil {
|
||||
originTeamID := channel.TeamId
|
||||
if err := moveChannel(a, team, channel, user); err != nil {
|
||||
CommandPrintErrorln("Unable to move channel '" + channel.Name + "' error: " + err.Error())
|
||||
} else {
|
||||
CommandPrettyPrintln("Moved channel '" + channel.Name + "'")
|
||||
CommandPrettyPrintln("Moved channel '" + channel.Name + "' to " + team.Name + "(" + team.Id + ") from " + originTeamID + ".")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func moveChannel(a *app.App, team *model.Team, channel *model.Channel) *model.AppError {
|
||||
func moveChannel(a *app.App, team *model.Team, channel *model.Channel, user *model.User) *model.AppError {
|
||||
oldTeamId := channel.TeamId
|
||||
|
||||
if err := a.MoveChannel(team, channel); err != nil {
|
||||
if err := a.MoveChannel(team, channel, user); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -44,6 +44,33 @@ func TestRemoveChannel(t *testing.T) {
|
||||
checkCommand(t, "channel", "remove", th.BasicTeam.Name+":"+channel.Name, th.BasicUser2.Email)
|
||||
}
|
||||
|
||||
func TestMoveChannel(t *testing.T) {
|
||||
th := api.Setup().InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
client := th.BasicClient
|
||||
team1 := th.BasicTeam
|
||||
team2 := th.CreateTeam(client)
|
||||
user1 := th.BasicUser
|
||||
th.LinkUserToTeam(user1, team2)
|
||||
channel := th.BasicChannel
|
||||
|
||||
th.LinkUserToTeam(user1, team1)
|
||||
th.LinkUserToTeam(user1, team2)
|
||||
|
||||
adminEmail := user1.Email
|
||||
adminUsername := user1.Username
|
||||
origin := team1.Name + ":" + channel.Name
|
||||
dest := team2.Name
|
||||
|
||||
checkCommand(t, "channel", "add", origin, adminEmail)
|
||||
|
||||
// should fail with nill because errors are logged instead of returned when a channel does not exist
|
||||
require.Nil(t, runCommand(t, "channel", "move", dest, team1.Name+":doesnotexist", "--username", adminUsername))
|
||||
|
||||
checkCommand(t, "channel", "move", dest, origin, "--username", adminUsername)
|
||||
}
|
||||
|
||||
func TestListChannels(t *testing.T) {
|
||||
th := api.Setup().InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
@@ -53,7 +53,7 @@ func runServer(configFileLocation string, disableConfigWatch bool) error {
|
||||
|
||||
a, err := app.New(options...)
|
||||
if err != nil {
|
||||
l4g.Error(err.Error())
|
||||
l4g.Critical(err.Error())
|
||||
return err
|
||||
}
|
||||
defer a.Shutdown()
|
||||
@@ -89,20 +89,27 @@ func runServer(configFileLocation string, disableConfigWatch bool) error {
|
||||
}
|
||||
})
|
||||
|
||||
a.StartServer()
|
||||
serverErr := a.StartServer()
|
||||
if serverErr != nil {
|
||||
l4g.Critical(serverErr.Error())
|
||||
return serverErr
|
||||
}
|
||||
|
||||
api4.Init(a, a.Srv.Router, false)
|
||||
api3 := api.Init(a, a.Srv.Router)
|
||||
wsapi.Init(a, a.Srv.WebSocketRouter)
|
||||
web.Init(api3)
|
||||
|
||||
if !utils.IsLicensed() && len(a.Config().SqlSettings.DataSourceReplicas) > 1 {
|
||||
license := a.License()
|
||||
|
||||
if license == nil && len(a.Config().SqlSettings.DataSourceReplicas) > 1 {
|
||||
l4g.Warn(utils.T("store.sql.read_replicas_not_licensed.critical"))
|
||||
a.UpdateConfig(func(cfg *model.Config) {
|
||||
cfg.SqlSettings.DataSourceReplicas = cfg.SqlSettings.DataSourceReplicas[:1]
|
||||
})
|
||||
}
|
||||
|
||||
if !utils.IsLicensed() {
|
||||
if license == nil {
|
||||
a.UpdateConfig(func(cfg *model.Config) {
|
||||
cfg.TeamSettings.MaxNotificationsPerChannel = &MaxNotificationsPerChannelDefault
|
||||
})
|
||||
|
||||
@@ -53,7 +53,11 @@ func webClientTestsCmdF(cmd *cobra.Command, args []string) error {
|
||||
defer a.Shutdown()
|
||||
|
||||
utils.InitTranslations(a.Config().LocalizationSettings)
|
||||
a.StartServer()
|
||||
serverErr := a.StartServer()
|
||||
if serverErr != nil {
|
||||
return serverErr
|
||||
}
|
||||
|
||||
api4.Init(a, a.Srv.Router, false)
|
||||
api.Init(a, a.Srv.Router)
|
||||
wsapi.Init(a, a.Srv.WebSocketRouter)
|
||||
@@ -71,7 +75,11 @@ func serverForWebClientTestsCmdF(cmd *cobra.Command, args []string) error {
|
||||
defer a.Shutdown()
|
||||
|
||||
utils.InitTranslations(a.Config().LocalizationSettings)
|
||||
a.StartServer()
|
||||
serverErr := a.StartServer()
|
||||
if serverErr != nil {
|
||||
return serverErr
|
||||
}
|
||||
|
||||
api4.Init(a, a.Srv.Router, false)
|
||||
api.Init(a, a.Srv.Router)
|
||||
wsapi.Init(a, a.Srv.WebSocketRouter)
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
"CloseUnusedDirectMessages": false,
|
||||
"EnableTutorial": true,
|
||||
"ExperimentalEnableDefaultChannelLeaveJoinMessages": true,
|
||||
"ExperimentalGroupUnreadChannels": false,
|
||||
"ExperimentalGroupUnreadChannels": "disabled",
|
||||
"ImageProxyType": "",
|
||||
"ImageProxyOptions": "",
|
||||
"ImageProxyURL": ""
|
||||
|
||||
40
i18n/en.json
40
i18n/en.json
@@ -131,10 +131,6 @@
|
||||
"id": "api.admin.upload_brand_image.too_large.app_error",
|
||||
"translation": "Unable to upload file. File is too large."
|
||||
},
|
||||
{
|
||||
"id": "api.api.init.parsing_templates.debug",
|
||||
"translation": "Parsing server templates at %v"
|
||||
},
|
||||
{
|
||||
"id": "api.api.init.parsing_templates.error",
|
||||
"translation": "Failed to parse server templates %v"
|
||||
@@ -3142,6 +3138,14 @@
|
||||
"id": "app.channel.move_channel.members_do_not_match.error",
|
||||
"translation": "Cannot move a channel unless all its members are already members of the destination team."
|
||||
},
|
||||
{
|
||||
"id": "api.team.move_channel.success",
|
||||
"translation": "This channel has been moved to this team from %v."
|
||||
},
|
||||
{
|
||||
"id": "api.team.move_channel.post.error",
|
||||
"translation": "Failed to post channel move message."
|
||||
},
|
||||
{
|
||||
"id": "app.channel.post_update_channel_purpose_message.post.error",
|
||||
"translation": "Failed to post channel purpose message"
|
||||
@@ -4766,6 +4770,10 @@
|
||||
"id": "model.config.is_valid.file_thumb_width.app_error",
|
||||
"translation": "Invalid thumbnail width for file settings. Must be a positive number."
|
||||
},
|
||||
{
|
||||
"id": "model.config.is_valid.group_unread_channels.app_error",
|
||||
"translation": "Invalid group unread channels for service settings. Must be 'disabled', 'default_on', or 'default_off'."
|
||||
},
|
||||
{
|
||||
"id": "model.config.is_valid.image_proxy_type.app_error",
|
||||
"translation": "Invalid image proxy type for service settings."
|
||||
@@ -4862,6 +4870,14 @@
|
||||
"id": "model.config.is_valid.message_export.batch_size.app_error",
|
||||
"translation": "Message export job BatchSize must be a positive integer"
|
||||
},
|
||||
{
|
||||
"id": "model.config.is_valid.message_export.export_type.app_error",
|
||||
"translation": "Message export job ExportFormat must be one of either 'actiance' or 'globalrelay'"
|
||||
},
|
||||
{
|
||||
"id": "model.config.is_valid.message_export.global_relay_email_address.app_error",
|
||||
"translation": "Message export job GlobalRelayEmailAddress must be set to a valid email address"
|
||||
},
|
||||
{
|
||||
"id": "model.config.is_valid.message_export.daily_runtime.app_error",
|
||||
"translation": "Message export job DailyRuntime must be a 24-hour time stamp in the form HH:MM."
|
||||
@@ -7106,6 +7122,10 @@
|
||||
"id": "utils.mail.new_client.auth.app_error",
|
||||
"translation": "Failed to authenticate on SMTP server"
|
||||
},
|
||||
{
|
||||
"id": "utils.mail.sendMail.attachments.write_error",
|
||||
"translation": "Failed to write attachment to email"
|
||||
},
|
||||
{
|
||||
"id": "utils.mail.new_client.helo.error",
|
||||
"translation": "Failed to to set the HELO to SMTP server %v"
|
||||
@@ -7182,10 +7202,6 @@
|
||||
"id": "web.create_dir.error",
|
||||
"translation": "Failed to create directory watcher %v"
|
||||
},
|
||||
{
|
||||
"id": "web.dir_fail.error",
|
||||
"translation": "Failed in directory watcher %v"
|
||||
},
|
||||
{
|
||||
"id": "web.do_load_channel.error",
|
||||
"translation": "Error in getting users profile for id=%v forcing logout"
|
||||
@@ -7290,18 +7306,10 @@
|
||||
"id": "web.parsing_templates.debug",
|
||||
"translation": "Parsing templates at %v"
|
||||
},
|
||||
{
|
||||
"id": "web.parsing_templates.error",
|
||||
"translation": "Failed to parse templates %v"
|
||||
},
|
||||
{
|
||||
"id": "web.post_permalink.app_error",
|
||||
"translation": "Invalid Post ID"
|
||||
},
|
||||
{
|
||||
"id": "web.reparse_templates.info",
|
||||
"translation": "Re-parsing templates because of modified file %v"
|
||||
},
|
||||
{
|
||||
"id": "web.reset_password.expired_link.app_error",
|
||||
"translation": "The password reset link has expired"
|
||||
|
||||
@@ -6,7 +6,10 @@ package model
|
||||
type ChannelMemberHistory struct {
|
||||
ChannelId string
|
||||
UserId string
|
||||
UserEmail string `db:"Email"`
|
||||
JoinTime int64
|
||||
LeaveTime *int64
|
||||
|
||||
// these two fields are never set in the database - when we SELECT, we join on Users to get them
|
||||
UserEmail string `db:"Email"`
|
||||
Username string
|
||||
}
|
||||
|
||||
@@ -69,6 +69,10 @@ const (
|
||||
ALLOW_EDIT_POST_NEVER = "never"
|
||||
ALLOW_EDIT_POST_TIME_LIMIT = "time_limit"
|
||||
|
||||
GROUP_UNREAD_CHANNELS_DISABLED = "disabled"
|
||||
GROUP_UNREAD_CHANNELS_DEFAULT_ON = "default_on"
|
||||
GROUP_UNREAD_CHANNELS_DEFAULT_OFF = "default_off"
|
||||
|
||||
EMAIL_BATCHING_BUFFER_SIZE = 256
|
||||
EMAIL_BATCHING_INTERVAL = 30
|
||||
|
||||
@@ -154,6 +158,9 @@ const (
|
||||
|
||||
PLUGIN_SETTINGS_DEFAULT_DIRECTORY = "./plugins"
|
||||
PLUGIN_SETTINGS_DEFAULT_CLIENT_DIRECTORY = "./client/plugins"
|
||||
|
||||
COMPLIANCE_EXPORT_TYPE_ACTIANCE = "actiance"
|
||||
COMPLIANCE_EXPORT_TYPE_GLOBALRELAY = "globalrelay"
|
||||
)
|
||||
|
||||
type ServiceSettings struct {
|
||||
@@ -214,7 +221,7 @@ type ServiceSettings struct {
|
||||
EnablePreviewFeatures *bool
|
||||
EnableTutorial *bool
|
||||
ExperimentalEnableDefaultChannelLeaveJoinMessages *bool
|
||||
ExperimentalGroupUnreadChannels *bool
|
||||
ExperimentalGroupUnreadChannels *string
|
||||
ImageProxyType *string
|
||||
ImageProxyURL *string
|
||||
ImageProxyOptions *string
|
||||
@@ -424,7 +431,11 @@ func (s *ServiceSettings) SetDefaults() {
|
||||
}
|
||||
|
||||
if s.ExperimentalGroupUnreadChannels == nil {
|
||||
s.ExperimentalGroupUnreadChannels = NewBool(false)
|
||||
s.ExperimentalGroupUnreadChannels = NewString(GROUP_UNREAD_CHANNELS_DISABLED)
|
||||
} else if *s.ExperimentalGroupUnreadChannels == "0" {
|
||||
s.ExperimentalGroupUnreadChannels = NewString(GROUP_UNREAD_CHANNELS_DISABLED)
|
||||
} else if *s.ExperimentalGroupUnreadChannels == "1" {
|
||||
s.ExperimentalGroupUnreadChannels = NewString(GROUP_UNREAD_CHANNELS_DEFAULT_ON)
|
||||
}
|
||||
|
||||
if s.ImageProxyType == nil {
|
||||
@@ -1615,9 +1626,13 @@ func (s *PluginSettings) SetDefaults() {
|
||||
|
||||
type MessageExportSettings struct {
|
||||
EnableExport *bool
|
||||
ExportFormat *string
|
||||
DailyRunTime *string
|
||||
ExportFromTimestamp *int64
|
||||
BatchSize *int
|
||||
|
||||
// formatter-specific settings - these are only expected to be non-nil if ExportFormat is set to the associated format
|
||||
GlobalRelayEmailAddress *string
|
||||
}
|
||||
|
||||
func (s *MessageExportSettings) SetDefaults() {
|
||||
@@ -1625,6 +1640,10 @@ func (s *MessageExportSettings) SetDefaults() {
|
||||
s.EnableExport = NewBool(false)
|
||||
}
|
||||
|
||||
if s.ExportFormat == nil {
|
||||
s.ExportFormat = NewString(COMPLIANCE_EXPORT_TYPE_ACTIANCE)
|
||||
}
|
||||
|
||||
if s.DailyRunTime == nil {
|
||||
s.DailyRunTime = NewString("01:00")
|
||||
}
|
||||
@@ -2070,6 +2089,12 @@ func (ss *ServiceSettings) isValid() *AppError {
|
||||
return NewAppError("Config.IsValid", "model.config.is_valid.listen_address.app_error", nil, "", http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if *ss.ExperimentalGroupUnreadChannels != GROUP_UNREAD_CHANNELS_DISABLED &&
|
||||
*ss.ExperimentalGroupUnreadChannels != GROUP_UNREAD_CHANNELS_DEFAULT_ON &&
|
||||
*ss.ExperimentalGroupUnreadChannels != GROUP_UNREAD_CHANNELS_DEFAULT_OFF {
|
||||
return NewAppError("Config.IsValid", "model.config.is_valid.group_unread_channels.app_error", nil, "", http.StatusBadRequest)
|
||||
}
|
||||
|
||||
switch *ss.ImageProxyType {
|
||||
case "", "willnorris/imageproxy":
|
||||
case "atmos/camo":
|
||||
@@ -2156,6 +2181,16 @@ func (mes *MessageExportSettings) isValid(fs FileSettings) *AppError {
|
||||
return NewAppError("Config.IsValid", "model.config.is_valid.message_export.daily_runtime.app_error", nil, err.Error(), http.StatusBadRequest)
|
||||
} else if mes.BatchSize == nil || *mes.BatchSize < 0 {
|
||||
return NewAppError("Config.IsValid", "model.config.is_valid.message_export.batch_size.app_error", nil, "", http.StatusBadRequest)
|
||||
} else if mes.ExportFormat == nil || (*mes.ExportFormat != COMPLIANCE_EXPORT_TYPE_ACTIANCE && *mes.ExportFormat != COMPLIANCE_EXPORT_TYPE_GLOBALRELAY) {
|
||||
return NewAppError("Config.IsValid", "model.config.is_valid.message_export.export_type.app_error", nil, "", http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if *mes.ExportFormat == COMPLIANCE_EXPORT_TYPE_GLOBALRELAY {
|
||||
// validating email addresses is hard - just make sure it contains an '@' sign
|
||||
// see https://stackoverflow.com/questions/201323/using-a-regular-expression-to-validate-an-email-address
|
||||
if mes.GlobalRelayEmailAddress == nil || !strings.Contains(*mes.GlobalRelayEmailAddress, "@") {
|
||||
return NewAppError("Config.IsValid", "model.config.is_valid.message_export.global_relay_email_address.app_error", nil, "", http.StatusBadRequest)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -36,6 +36,38 @@ func TestConfigDefaultFileSettingsS3SSE(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigDefaultServiceSettingsExperimentalGroupUnreadChannels(t *testing.T) {
|
||||
c1 := Config{}
|
||||
c1.SetDefaults()
|
||||
|
||||
if *c1.ServiceSettings.ExperimentalGroupUnreadChannels != GROUP_UNREAD_CHANNELS_DISABLED {
|
||||
t.Fatal("ServiceSettings.ExperimentalGroupUnreadChannels should default to 'disabled'")
|
||||
}
|
||||
|
||||
// This setting was briefly a boolean, so ensure that those values still work as expected
|
||||
c1 = Config{
|
||||
ServiceSettings: ServiceSettings{
|
||||
ExperimentalGroupUnreadChannels: NewString("1"),
|
||||
},
|
||||
}
|
||||
c1.SetDefaults()
|
||||
|
||||
if *c1.ServiceSettings.ExperimentalGroupUnreadChannels != GROUP_UNREAD_CHANNELS_DEFAULT_ON {
|
||||
t.Fatal("ServiceSettings.ExperimentalGroupUnreadChannels should set true to 'default on'")
|
||||
}
|
||||
|
||||
c1 = Config{
|
||||
ServiceSettings: ServiceSettings{
|
||||
ExperimentalGroupUnreadChannels: NewString("0"),
|
||||
},
|
||||
}
|
||||
c1.SetDefaults()
|
||||
|
||||
if *c1.ServiceSettings.ExperimentalGroupUnreadChannels != GROUP_UNREAD_CHANNELS_DISABLED {
|
||||
t.Fatal("ServiceSettings.ExperimentalGroupUnreadChannels should set false to 'disabled'")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMessageExportSettingsIsValidEnableExportNotSet(t *testing.T) {
|
||||
fs := &FileSettings{}
|
||||
mes := &MessageExportSettings{}
|
||||
@@ -104,7 +136,7 @@ func TestMessageExportSettingsIsValidBatchSizeInvalid(t *testing.T) {
|
||||
require.Error(t, mes.isValid(*fs))
|
||||
}
|
||||
|
||||
func TestMessageExportSettingsIsValid(t *testing.T) {
|
||||
func TestMessageExportSettingsIsValidExportFormatInvalid(t *testing.T) {
|
||||
fs := &FileSettings{
|
||||
DriverName: NewString("foo"), // bypass file location check
|
||||
}
|
||||
@@ -115,6 +147,55 @@ func TestMessageExportSettingsIsValid(t *testing.T) {
|
||||
BatchSize: NewInt(100),
|
||||
}
|
||||
|
||||
// should fail fast because export format isn't set
|
||||
require.Error(t, mes.isValid(*fs))
|
||||
}
|
||||
|
||||
func TestMessageExportSettingsIsValidGlobalRelayEmailAddressInvalid(t *testing.T) {
|
||||
fs := &FileSettings{
|
||||
DriverName: NewString("foo"), // bypass file location check
|
||||
}
|
||||
mes := &MessageExportSettings{
|
||||
EnableExport: NewBool(true),
|
||||
ExportFormat: NewString(COMPLIANCE_EXPORT_TYPE_GLOBALRELAY),
|
||||
ExportFromTimestamp: NewInt64(0),
|
||||
DailyRunTime: NewString("15:04"),
|
||||
BatchSize: NewInt(100),
|
||||
}
|
||||
|
||||
// should fail fast because global relay email address isn't set
|
||||
require.Error(t, mes.isValid(*fs))
|
||||
}
|
||||
|
||||
func TestMessageExportSettingsIsValidActiance(t *testing.T) {
|
||||
fs := &FileSettings{
|
||||
DriverName: NewString("foo"), // bypass file location check
|
||||
}
|
||||
mes := &MessageExportSettings{
|
||||
EnableExport: NewBool(true),
|
||||
ExportFormat: NewString(COMPLIANCE_EXPORT_TYPE_ACTIANCE),
|
||||
ExportFromTimestamp: NewInt64(0),
|
||||
DailyRunTime: NewString("15:04"),
|
||||
BatchSize: NewInt(100),
|
||||
}
|
||||
|
||||
// should pass because everything is valid
|
||||
require.Nil(t, mes.isValid(*fs))
|
||||
}
|
||||
|
||||
func TestMessageExportSettingsIsValidGlobalRelay(t *testing.T) {
|
||||
fs := &FileSettings{
|
||||
DriverName: NewString("foo"), // bypass file location check
|
||||
}
|
||||
mes := &MessageExportSettings{
|
||||
EnableExport: NewBool(true),
|
||||
ExportFormat: NewString(COMPLIANCE_EXPORT_TYPE_GLOBALRELAY),
|
||||
ExportFromTimestamp: NewInt64(0),
|
||||
DailyRunTime: NewString("15:04"),
|
||||
BatchSize: NewInt(100),
|
||||
GlobalRelayEmailAddress: NewString("test@mattermost.com"),
|
||||
}
|
||||
|
||||
// should pass because everything is valid
|
||||
require.Nil(t, mes.isValid(*fs))
|
||||
}
|
||||
@@ -127,6 +208,7 @@ func TestMessageExportSetDefaults(t *testing.T) {
|
||||
require.Equal(t, "01:00", *mes.DailyRunTime)
|
||||
require.Equal(t, int64(0), *mes.ExportFromTimestamp)
|
||||
require.Equal(t, 10000, *mes.BatchSize)
|
||||
require.Equal(t, COMPLIANCE_EXPORT_TYPE_ACTIANCE, *mes.ExportFormat)
|
||||
}
|
||||
|
||||
func TestMessageExportSetDefaultsExportEnabledExportFromTimestampNil(t *testing.T) {
|
||||
|
||||
@@ -9,6 +9,7 @@ type MessageExport struct {
|
||||
|
||||
UserId *string
|
||||
UserEmail *string
|
||||
Username *string
|
||||
|
||||
PostId *string
|
||||
PostCreateAt *int64
|
||||
|
||||
@@ -28,6 +28,7 @@ const (
|
||||
POST_ADD_REMOVE = "system_add_remove" // Deprecated, use POST_ADD_TO_CHANNEL or POST_REMOVE_FROM_CHANNEL instead
|
||||
POST_ADD_TO_CHANNEL = "system_add_to_channel"
|
||||
POST_REMOVE_FROM_CHANNEL = "system_remove_from_channel"
|
||||
POST_MOVE_CHANNEL = "system_move_channel"
|
||||
POST_ADD_TO_TEAM = "system_add_to_team"
|
||||
POST_REMOVE_FROM_TEAM = "system_remove_from_team"
|
||||
POST_HEADER_CHANGE = "system_header_change"
|
||||
@@ -196,6 +197,7 @@ func (o *Post) IsValid() *AppError {
|
||||
POST_LEAVE_TEAM,
|
||||
POST_ADD_TO_CHANNEL,
|
||||
POST_REMOVE_FROM_CHANNEL,
|
||||
POST_MOVE_CHANNEL,
|
||||
POST_ADD_TO_TEAM,
|
||||
POST_REMOVE_FROM_TEAM,
|
||||
POST_SLACK_ATTACHMENT,
|
||||
|
||||
@@ -110,7 +110,8 @@ func (s SqlChannelMemberHistoryStore) getFromChannelMemberHistoryTable(startTime
|
||||
query := `
|
||||
SELECT
|
||||
cmh.*,
|
||||
u.Email
|
||||
u.Email,
|
||||
u.Username
|
||||
FROM ChannelMemberHistory cmh
|
||||
INNER JOIN Users u ON cmh.UserId = u.Id
|
||||
WHERE cmh.ChannelId = :ChannelId
|
||||
@@ -130,9 +131,10 @@ func (s SqlChannelMemberHistoryStore) getFromChannelMemberHistoryTable(startTime
|
||||
func (s SqlChannelMemberHistoryStore) getFromChannelMembersTable(startTime int64, endTime int64, channelId string) ([]*model.ChannelMemberHistory, error) {
|
||||
query := `
|
||||
SELECT DISTINCT
|
||||
ch.ChannelId,
|
||||
ch.UserId,
|
||||
u.email
|
||||
ch.ChannelId,
|
||||
ch.UserId,
|
||||
u.Email,
|
||||
u.Username
|
||||
FROM ChannelMembers AS ch
|
||||
INNER JOIN Users AS u ON ch.UserId = u.id
|
||||
WHERE ch.ChannelId = :ChannelId`
|
||||
@@ -158,7 +160,7 @@ func (s SqlChannelMemberHistoryStore) PermanentDeleteBatch(endTime int64, limit
|
||||
query =
|
||||
`DELETE FROM ChannelMemberHistory
|
||||
WHERE ctid IN (
|
||||
SELECT ctid FROM ChannelMemberHistory
|
||||
SELECT ctid FROM ChannelMemberHistory
|
||||
WHERE LeaveTime IS NOT NULL
|
||||
AND LeaveTime <= :EndTime
|
||||
LIMIT :Limit
|
||||
|
||||
@@ -225,7 +225,8 @@ func (s SqlComplianceStore) MessageExport(after int64, limit int) store.StoreCha
|
||||
Channels.Id AS ChannelId,
|
||||
Channels.DisplayName AS ChannelDisplayName,
|
||||
Users.Id AS UserId,
|
||||
Users.Email AS UserEmail
|
||||
Users.Email AS UserEmail,
|
||||
Users.Username
|
||||
FROM
|
||||
Posts
|
||||
LEFT OUTER JOIN Channels ON Posts.ChannelId = Channels.Id
|
||||
|
||||
@@ -35,6 +35,7 @@ func testLogJoinEvent(t *testing.T, ss store.Store) {
|
||||
user := model.User{
|
||||
Email: model.NewId() + "@mattermost.com",
|
||||
Nickname: model.NewId(),
|
||||
Username: model.NewId(),
|
||||
}
|
||||
user = *store.Must(ss.User().Save(&user)).(*model.User)
|
||||
|
||||
@@ -57,6 +58,7 @@ func testLogLeaveEvent(t *testing.T, ss store.Store) {
|
||||
user := model.User{
|
||||
Email: model.NewId() + "@mattermost.com",
|
||||
Nickname: model.NewId(),
|
||||
Username: model.NewId(),
|
||||
}
|
||||
user = *store.Must(ss.User().Save(&user)).(*model.User)
|
||||
|
||||
@@ -82,6 +84,7 @@ func testGetUsersInChannelAtChannelMemberHistory(t *testing.T, ss store.Store) {
|
||||
user := model.User{
|
||||
Email: model.NewId() + "@mattermost.com",
|
||||
Nickname: model.NewId(),
|
||||
Username: model.NewId(),
|
||||
}
|
||||
user = *store.Must(ss.User().Save(&user)).(*model.User)
|
||||
|
||||
@@ -108,6 +111,7 @@ func testGetUsersInChannelAtChannelMemberHistory(t *testing.T, ss store.Store) {
|
||||
assert.Equal(t, channel.Id, channelMembers[0].ChannelId)
|
||||
assert.Equal(t, user.Id, channelMembers[0].UserId)
|
||||
assert.Equal(t, user.Email, channelMembers[0].UserEmail)
|
||||
assert.Equal(t, user.Username, channelMembers[0].Username)
|
||||
assert.Equal(t, joinTime, channelMembers[0].JoinTime)
|
||||
assert.Nil(t, channelMembers[0].LeaveTime)
|
||||
|
||||
@@ -117,6 +121,7 @@ func testGetUsersInChannelAtChannelMemberHistory(t *testing.T, ss store.Store) {
|
||||
assert.Equal(t, channel.Id, channelMembers[0].ChannelId)
|
||||
assert.Equal(t, user.Id, channelMembers[0].UserId)
|
||||
assert.Equal(t, user.Email, channelMembers[0].UserEmail)
|
||||
assert.Equal(t, user.Username, channelMembers[0].Username)
|
||||
assert.Equal(t, joinTime, channelMembers[0].JoinTime)
|
||||
assert.Nil(t, channelMembers[0].LeaveTime)
|
||||
|
||||
@@ -129,6 +134,7 @@ func testGetUsersInChannelAtChannelMemberHistory(t *testing.T, ss store.Store) {
|
||||
assert.Equal(t, channel.Id, channelMembers[0].ChannelId)
|
||||
assert.Equal(t, user.Id, channelMembers[0].UserId)
|
||||
assert.Equal(t, user.Email, channelMembers[0].UserEmail)
|
||||
assert.Equal(t, user.Username, channelMembers[0].Username)
|
||||
assert.Equal(t, joinTime, channelMembers[0].JoinTime)
|
||||
assert.Equal(t, leaveTime, *channelMembers[0].LeaveTime)
|
||||
|
||||
@@ -138,6 +144,7 @@ func testGetUsersInChannelAtChannelMemberHistory(t *testing.T, ss store.Store) {
|
||||
assert.Equal(t, channel.Id, channelMembers[0].ChannelId)
|
||||
assert.Equal(t, user.Id, channelMembers[0].UserId)
|
||||
assert.Equal(t, user.Email, channelMembers[0].UserEmail)
|
||||
assert.Equal(t, user.Username, channelMembers[0].Username)
|
||||
assert.Equal(t, joinTime, channelMembers[0].JoinTime)
|
||||
assert.Equal(t, leaveTime, *channelMembers[0].LeaveTime)
|
||||
|
||||
@@ -160,6 +167,7 @@ func testGetUsersInChannelAtChannelMembers(t *testing.T, ss store.Store) {
|
||||
user := model.User{
|
||||
Email: model.NewId() + "@mattermost.com",
|
||||
Nickname: model.NewId(),
|
||||
Username: model.NewId(),
|
||||
}
|
||||
user = *store.Must(ss.User().Save(&user)).(*model.User)
|
||||
|
||||
@@ -192,6 +200,7 @@ func testGetUsersInChannelAtChannelMembers(t *testing.T, ss store.Store) {
|
||||
assert.Equal(t, channel.Id, channelMembers[0].ChannelId)
|
||||
assert.Equal(t, user.Id, channelMembers[0].UserId)
|
||||
assert.Equal(t, user.Email, channelMembers[0].UserEmail)
|
||||
assert.Equal(t, user.Username, channelMembers[0].Username)
|
||||
assert.Equal(t, joinTime-500, channelMembers[0].JoinTime)
|
||||
assert.Equal(t, joinTime-100, *channelMembers[0].LeaveTime)
|
||||
|
||||
@@ -201,6 +210,7 @@ func testGetUsersInChannelAtChannelMembers(t *testing.T, ss store.Store) {
|
||||
assert.Equal(t, channel.Id, channelMembers[0].ChannelId)
|
||||
assert.Equal(t, user.Id, channelMembers[0].UserId)
|
||||
assert.Equal(t, user.Email, channelMembers[0].UserEmail)
|
||||
assert.Equal(t, user.Username, channelMembers[0].Username)
|
||||
assert.Equal(t, joinTime-100, channelMembers[0].JoinTime)
|
||||
assert.Equal(t, joinTime+500, *channelMembers[0].LeaveTime)
|
||||
|
||||
@@ -210,6 +220,7 @@ func testGetUsersInChannelAtChannelMembers(t *testing.T, ss store.Store) {
|
||||
assert.Equal(t, channel.Id, channelMembers[0].ChannelId)
|
||||
assert.Equal(t, user.Id, channelMembers[0].UserId)
|
||||
assert.Equal(t, user.Email, channelMembers[0].UserEmail)
|
||||
assert.Equal(t, user.Username, channelMembers[0].Username)
|
||||
assert.Equal(t, joinTime+100, channelMembers[0].JoinTime)
|
||||
assert.Equal(t, joinTime+500, *channelMembers[0].LeaveTime)
|
||||
|
||||
@@ -219,6 +230,7 @@ func testGetUsersInChannelAtChannelMembers(t *testing.T, ss store.Store) {
|
||||
assert.Equal(t, channel.Id, channelMembers[0].ChannelId)
|
||||
assert.Equal(t, user.Id, channelMembers[0].UserId)
|
||||
assert.Equal(t, user.Email, channelMembers[0].UserEmail)
|
||||
assert.Equal(t, user.Username, channelMembers[0].Username)
|
||||
assert.Equal(t, joinTime+100, channelMembers[0].JoinTime)
|
||||
assert.Equal(t, leaveTime-100, *channelMembers[0].LeaveTime)
|
||||
|
||||
@@ -228,6 +240,7 @@ func testGetUsersInChannelAtChannelMembers(t *testing.T, ss store.Store) {
|
||||
assert.Equal(t, channel.Id, channelMembers[0].ChannelId)
|
||||
assert.Equal(t, user.Id, channelMembers[0].UserId)
|
||||
assert.Equal(t, user.Email, channelMembers[0].UserEmail)
|
||||
assert.Equal(t, user.Username, channelMembers[0].Username)
|
||||
assert.Equal(t, joinTime-100, channelMembers[0].JoinTime)
|
||||
assert.Equal(t, leaveTime+100, *channelMembers[0].LeaveTime)
|
||||
|
||||
@@ -237,6 +250,7 @@ func testGetUsersInChannelAtChannelMembers(t *testing.T, ss store.Store) {
|
||||
assert.Equal(t, channel.Id, channelMembers[0].ChannelId)
|
||||
assert.Equal(t, user.Id, channelMembers[0].UserId)
|
||||
assert.Equal(t, user.Email, channelMembers[0].UserEmail)
|
||||
assert.Equal(t, user.Username, channelMembers[0].Username)
|
||||
assert.Equal(t, leaveTime+100, channelMembers[0].JoinTime)
|
||||
assert.Equal(t, leaveTime+200, *channelMembers[0].LeaveTime)
|
||||
}
|
||||
@@ -255,12 +269,14 @@ func testPermanentDeleteBatch(t *testing.T, ss store.Store) {
|
||||
user := model.User{
|
||||
Email: model.NewId() + "@mattermost.com",
|
||||
Nickname: model.NewId(),
|
||||
Username: model.NewId(),
|
||||
}
|
||||
user = *store.Must(ss.User().Save(&user)).(*model.User)
|
||||
|
||||
user2 := model.User{
|
||||
Email: model.NewId() + "@mattermost.com",
|
||||
Nickname: model.NewId(),
|
||||
Username: model.NewId(),
|
||||
}
|
||||
user2 = *store.Must(ss.User().Save(&user2)).(*model.User)
|
||||
|
||||
|
||||
@@ -341,7 +341,8 @@ func testComplianceMessageExport(t *testing.T, ss store.Store) {
|
||||
|
||||
// and two users that are a part of that team
|
||||
user1 := &model.User{
|
||||
Email: model.NewId(),
|
||||
Email: model.NewId(),
|
||||
Username: model.NewId(),
|
||||
}
|
||||
user1 = store.Must(ss.User().Save(user1)).(*model.User)
|
||||
store.Must(ss.Team().SaveMember(&model.TeamMember{
|
||||
@@ -350,7 +351,8 @@ func testComplianceMessageExport(t *testing.T, ss store.Store) {
|
||||
}, -1))
|
||||
|
||||
user2 := &model.User{
|
||||
Email: model.NewId(),
|
||||
Email: model.NewId(),
|
||||
Username: model.NewId(),
|
||||
}
|
||||
user2 = store.Must(ss.User().Save(user2)).(*model.User)
|
||||
store.Must(ss.Team().SaveMember(&model.TeamMember{
|
||||
@@ -415,6 +417,7 @@ func testComplianceMessageExport(t *testing.T, ss store.Store) {
|
||||
assert.Equal(t, channel.DisplayName, *messageExportMap[post1.Id].ChannelDisplayName)
|
||||
assert.Equal(t, user1.Id, *messageExportMap[post1.Id].UserId)
|
||||
assert.Equal(t, user1.Email, *messageExportMap[post1.Id].UserEmail)
|
||||
assert.Equal(t, user1.Username, *messageExportMap[post1.Id].Username)
|
||||
|
||||
// post2 was made by user1 in channel1 and team1
|
||||
assert.Equal(t, post2.Id, *messageExportMap[post2.Id].PostId)
|
||||
@@ -424,6 +427,7 @@ func testComplianceMessageExport(t *testing.T, ss store.Store) {
|
||||
assert.Equal(t, channel.DisplayName, *messageExportMap[post2.Id].ChannelDisplayName)
|
||||
assert.Equal(t, user1.Id, *messageExportMap[post2.Id].UserId)
|
||||
assert.Equal(t, user1.Email, *messageExportMap[post2.Id].UserEmail)
|
||||
assert.Equal(t, user1.Username, *messageExportMap[post2.Id].Username)
|
||||
|
||||
// post3 is a DM between user1 and user2
|
||||
assert.Equal(t, post3.Id, *messageExportMap[post3.Id].PostId)
|
||||
@@ -432,4 +436,5 @@ func testComplianceMessageExport(t *testing.T, ss store.Store) {
|
||||
assert.Equal(t, directMessageChannel.Id, *messageExportMap[post3.Id].ChannelId)
|
||||
assert.Equal(t, user1.Id, *messageExportMap[post3.Id].UserId)
|
||||
assert.Equal(t, user1.Email, *messageExportMap[post3.Id].UserEmail)
|
||||
assert.Equal(t, user1.Username, *messageExportMap[post3.Id].Username)
|
||||
}
|
||||
|
||||
91
templates/globalrelay_compliance_export.html
Normal file
91
templates/globalrelay_compliance_export.html
Normal file
@@ -0,0 +1,91 @@
|
||||
{{define "globalrelay_compliance_export"}}
|
||||
<style type="text/css">
|
||||
body {
|
||||
font-family:Arial, sans-serif;
|
||||
font-size:14px;
|
||||
font-weight:normal;
|
||||
}
|
||||
|
||||
.summary-list ul {
|
||||
padding: 0px;
|
||||
list-style:none;
|
||||
}
|
||||
.summary-list li {
|
||||
display: inline;
|
||||
padding: 0 1em 0 0;
|
||||
}
|
||||
.summary-list .bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.participants {
|
||||
border-collapse:collapse;
|
||||
border-spacing:0;
|
||||
}
|
||||
.participants td {
|
||||
padding:10px 5px;
|
||||
border:1px solid black;
|
||||
overflow:hidden;
|
||||
text-align: center;
|
||||
word-break:normal;
|
||||
}
|
||||
.participants th {
|
||||
padding:10px 5px;
|
||||
border:1px solid black;
|
||||
overflow:hidden;
|
||||
word-break:normal;
|
||||
}
|
||||
.participants th,td {
|
||||
vertical-align:top
|
||||
}
|
||||
|
||||
.message-list ul {
|
||||
list-style:none;
|
||||
padding: 0;
|
||||
}
|
||||
.message-list li {
|
||||
padding: 0 0 1em 0;
|
||||
}
|
||||
.message .sent_time {
|
||||
font-weight:bold;
|
||||
}
|
||||
.message .username {
|
||||
font-weight:bold;
|
||||
}
|
||||
.message .email {
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
|
||||
<h1>Mattermost Compliance Export</h1>
|
||||
|
||||
<h2>Conversation Summary</h2>
|
||||
<div class="summary-list">
|
||||
<ul>
|
||||
<li><span class="bold">Channel: </span>{{.Props.ChannelName}}</li>
|
||||
<li><span class="bold">Started: </span>{{.Props.Started}}</li>
|
||||
<li><span class="bold">Ended: </span>{{.Props.Ended}}</li>
|
||||
<li><span class="bold">Duration: </span>{{.Props.Duration}} Minutes</li>
|
||||
</ul>
|
||||
</div>
|
||||
<table class="participants">
|
||||
<tr>
|
||||
<th class="username">Username<br></th>
|
||||
<th class="email">Email</th>
|
||||
<th class="joined">Joined</th>
|
||||
<th class="left">Left</th>
|
||||
<th class="duration">Duration</th>
|
||||
<th class="messages">Messages</th>
|
||||
</tr>
|
||||
{{.Props.ParticipantRows}}
|
||||
</table>
|
||||
|
||||
<h2>Messages</h2>
|
||||
<div class="message-list">
|
||||
<ul>
|
||||
{{.Props.Messages}}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<p>Exported on {{.Props.ExportDate}}</p>
|
||||
{{end}}
|
||||
8
templates/globalrelay_compliance_export_message.html
Normal file
8
templates/globalrelay_compliance_export_message.html
Normal file
@@ -0,0 +1,8 @@
|
||||
{{define "globalrelay_compliance_export_message"}}
|
||||
<li class="message">
|
||||
<span class="sent_time">{{.Props.SentTime}}</span>
|
||||
<span class="username">@{{.Props.Username}}</span>
|
||||
<span class="email">({{.Props.Email}}):</span>
|
||||
<span class="message">{{.Props.Message}}</span>
|
||||
</li>
|
||||
{{end}}
|
||||
10
templates/globalrelay_compliance_export_participant_row.html
Normal file
10
templates/globalrelay_compliance_export_participant_row.html
Normal file
@@ -0,0 +1,10 @@
|
||||
{{define "globalrelay_compliance_export_participant_row"}}
|
||||
<tr>
|
||||
<td class="username">@{{.Props.Username}}</td>
|
||||
<td class="email">{{.Props.Email}}</td>
|
||||
<td class="joined">{{.Props.Joined}}</td>
|
||||
<td class="left">{{.Props.Left}}</td>
|
||||
<td class="duration">{{.Props.DurationMinutes}} Minutes</td>
|
||||
<td class="messages">{{.Props.NumMessages}}</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
@@ -397,7 +397,7 @@ func GenerateClientConfig(c *model.Config, diagnosticId string) map[string]strin
|
||||
props["EnablePreviewFeatures"] = strconv.FormatBool(*c.ServiceSettings.EnablePreviewFeatures)
|
||||
props["EnableTutorial"] = strconv.FormatBool(*c.ServiceSettings.EnableTutorial)
|
||||
props["ExperimentalEnableDefaultChannelLeaveJoinMessages"] = strconv.FormatBool(*c.ServiceSettings.ExperimentalEnableDefaultChannelLeaveJoinMessages)
|
||||
props["ExperimentalGroupUnreadChannels"] = strconv.FormatBool(*c.ServiceSettings.ExperimentalGroupUnreadChannels)
|
||||
props["ExperimentalGroupUnreadChannels"] = *c.ServiceSettings.ExperimentalGroupUnreadChannels
|
||||
|
||||
props["SendEmailNotifications"] = strconv.FormatBool(c.EmailSettings.SendEmailNotifications)
|
||||
props["SendPushNotifications"] = strconv.FormatBool(*c.EmailSettings.SendPushNotifications)
|
||||
|
||||
@@ -23,7 +23,7 @@ type HTMLTemplateWatcher struct {
|
||||
|
||||
func NewHTMLTemplateWatcher(directory string) (*HTMLTemplateWatcher, error) {
|
||||
templatesDir, _ := FindDir(directory)
|
||||
l4g.Debug(T("api.api.init.parsing_templates.debug"), templatesDir)
|
||||
l4g.Debug("Parsing server templates at %v", templatesDir)
|
||||
|
||||
ret := &HTMLTemplateWatcher{
|
||||
stop: make(chan struct{}),
|
||||
@@ -55,15 +55,15 @@ func NewHTMLTemplateWatcher(directory string) (*HTMLTemplateWatcher, error) {
|
||||
return
|
||||
case event := <-watcher.Events:
|
||||
if event.Op&fsnotify.Write == fsnotify.Write {
|
||||
l4g.Info(T("web.reparse_templates.info"), event.Name)
|
||||
l4g.Info("Re-parsing templates because of modified file %v", event.Name)
|
||||
if htmlTemplates, err := template.ParseGlob(templatesDir + "*.html"); err != nil {
|
||||
l4g.Error(T("web.parsing_templates.error"), err)
|
||||
l4g.Error("Failed to parse templates %v", err)
|
||||
} else {
|
||||
ret.templates.Store(htmlTemplates)
|
||||
}
|
||||
}
|
||||
case err := <-watcher.Errors:
|
||||
l4g.Error(T("web.dir_fail.error"), err)
|
||||
l4g.Error("Failed in directory watcher %s", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -37,6 +38,12 @@ type JSONMessageInbucket struct {
|
||||
Text string
|
||||
HTML string `json:"Html"`
|
||||
}
|
||||
Attachments []struct {
|
||||
Filename string
|
||||
ContentType string `json:"content-type"`
|
||||
DownloadLink string `json:"download-link"`
|
||||
Bytes []byte `json:"-"`
|
||||
}
|
||||
}
|
||||
|
||||
func ParseEmail(email string) string {
|
||||
@@ -89,21 +96,54 @@ func GetMessageFromMailbox(email, id string) (results JSONMessageInbucket, err e
|
||||
var record JSONMessageInbucket
|
||||
|
||||
url := fmt.Sprintf("%s%s%s/%s", getInbucketHost(), INBUCKET_API, parsedEmail, id)
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
emailResponse, err := get(url)
|
||||
if err != nil {
|
||||
return record, err
|
||||
}
|
||||
defer emailResponse.Body.Close()
|
||||
|
||||
err = json.NewDecoder(emailResponse.Body).Decode(&record)
|
||||
|
||||
// download attachments
|
||||
if record.Attachments != nil && len(record.Attachments) > 0 {
|
||||
for i := range record.Attachments {
|
||||
if bytes, err := downloadAttachment(record.Attachments[i].DownloadLink); err != nil {
|
||||
return record, err
|
||||
} else {
|
||||
record.Attachments[i].Bytes = make([]byte, len(bytes))
|
||||
copy(record.Attachments[i].Bytes, bytes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return record, err
|
||||
}
|
||||
|
||||
func downloadAttachment(url string) ([]byte, error) {
|
||||
attachmentResponse, err := get(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer attachmentResponse.Body.Close()
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
io.Copy(buf, attachmentResponse.Body)
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func get(url string) (*http.Response, error) {
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client := &http.Client{}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return record, err
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
err = json.NewDecoder(resp.Body).Decode(&record)
|
||||
return record, err
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func DeleteMailBox(email string) (err error) {
|
||||
|
||||
@@ -15,6 +15,8 @@ import (
|
||||
|
||||
"net/http"
|
||||
|
||||
"io"
|
||||
|
||||
l4g "github.com/alecthomas/log4go"
|
||||
"github.com/mattermost/html2text"
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
@@ -104,36 +106,72 @@ func TestConnection(config *model.Config) {
|
||||
}
|
||||
|
||||
func SendMailUsingConfig(to, subject, htmlBody string, config *model.Config) *model.AppError {
|
||||
fromMail := mail.Address{Name: config.EmailSettings.FeedbackName, Address: config.EmailSettings.FeedbackEmail}
|
||||
return sendMail(to, to, fromMail, subject, htmlBody, nil, nil, config)
|
||||
}
|
||||
|
||||
// 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 sendMail(mimeTo, smtpTo string, from mail.Address, subject, htmlBody string, attachments []*model.FileInfo, mimeHeaders map[string]string, config *model.Config) *model.AppError {
|
||||
if !config.EmailSettings.SendEmailNotifications || len(config.EmailSettings.SMTPServer) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
l4g.Debug(T("utils.mail.send_mail.sending.debug"), to, subject)
|
||||
l4g.Debug(T("utils.mail.send_mail.sending.debug"), mimeTo, subject)
|
||||
|
||||
htmlMessage := "\r\n<html><body>" + htmlBody + "</body></html>"
|
||||
|
||||
fromMail := mail.Address{Name: config.EmailSettings.FeedbackName, Address: config.EmailSettings.FeedbackEmail}
|
||||
|
||||
txtBody, err := html2text.FromString(htmlBody)
|
||||
if err != nil {
|
||||
l4g.Warn(err)
|
||||
txtBody = ""
|
||||
}
|
||||
|
||||
m := gomail.NewMessage(gomail.SetCharset("UTF-8"))
|
||||
m.SetHeaders(map[string][]string{
|
||||
"From": {fromMail.String()},
|
||||
"To": {to},
|
||||
headers := map[string][]string{
|
||||
"From": {from.String()},
|
||||
"To": {mimeTo},
|
||||
"Subject": {encodeRFC2047Word(subject)},
|
||||
"Content-Transfer-Encoding": {"8bit"},
|
||||
"Auto-Submitted": {"auto-generated"},
|
||||
"Precedence": {"bulk"},
|
||||
})
|
||||
m.SetDateHeader("Date", time.Now())
|
||||
}
|
||||
if mimeHeaders != nil {
|
||||
for k, v := range mimeHeaders {
|
||||
headers[k] = []string{encodeRFC2047Word(v)}
|
||||
}
|
||||
}
|
||||
|
||||
m := gomail.NewMessage(gomail.SetCharset("UTF-8"))
|
||||
m.SetHeaders(headers)
|
||||
m.SetDateHeader("Date", time.Now())
|
||||
m.SetBody("text/plain", txtBody)
|
||||
m.AddAlternative("text/html", htmlMessage)
|
||||
|
||||
if attachments != nil {
|
||||
fileBackend, err := NewFileBackend(&config.FileSettings)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, fileInfo := range attachments {
|
||||
m.Attach(fileInfo.Name, gomail.SetCopyFunc(func(writer io.Writer) error {
|
||||
bytes, err := fileBackend.ReadFile(fileInfo.Path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := writer.Write(bytes); err != nil {
|
||||
return model.NewAppError("SendMail", "utils.mail.sendMail.attachments.write_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
return nil
|
||||
}))
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
conn, err1 := connectToSMTPServer(config)
|
||||
if err1 != nil {
|
||||
return err1
|
||||
@@ -147,11 +185,11 @@ func SendMailUsingConfig(to, subject, htmlBody string, config *model.Config) *mo
|
||||
defer c.Quit()
|
||||
defer c.Close()
|
||||
|
||||
if err := c.Mail(fromMail.Address); err != nil {
|
||||
if err := c.Mail(from.Address); err != nil {
|
||||
return model.NewAppError("SendMail", "utils.mail.send_mail.from_address.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
if err := c.Rcpt(to); err != nil {
|
||||
if err := c.Rcpt(smtpTo); err != nil {
|
||||
return model.NewAppError("SendMail", "utils.mail.send_mail.to_address.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,10 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"net/mail"
|
||||
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@@ -39,9 +43,9 @@ func TestSendMailUsingConfig(t *testing.T) {
|
||||
require.Nil(t, err)
|
||||
T = GetUserTranslations("en")
|
||||
|
||||
var emailTo string = "test@example.com"
|
||||
var emailSubject string = "Testing this email"
|
||||
var emailBody string = "This is a test from autobot"
|
||||
var emailTo = "test@example.com"
|
||||
var emailSubject = "Testing this email"
|
||||
var emailBody = "This is a test from autobot"
|
||||
|
||||
//Delete all the messages before check the sample email
|
||||
DeleteMailBox(emailTo)
|
||||
@@ -50,7 +54,7 @@ func TestSendMailUsingConfig(t *testing.T) {
|
||||
t.Log(err)
|
||||
t.Fatal("Should connect to the STMP Server")
|
||||
} else {
|
||||
//Check if the email was send to the rigth email address
|
||||
//Check if the email was send to the right email address
|
||||
var resultsMailbox JSONMessageHeaderInbucket
|
||||
err := RetryInbucket(5, func() error {
|
||||
var err error
|
||||
@@ -75,3 +79,78 @@ func TestSendMailUsingConfig(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSendMailUsingConfigAdvanced(t *testing.T) {
|
||||
cfg, _, err := LoadConfig("config.json")
|
||||
require.Nil(t, err)
|
||||
T = GetUserTranslations("en")
|
||||
|
||||
var mimeTo = "test@example.com"
|
||||
var smtpTo = "test2@example.com"
|
||||
var from = mail.Address{Name: "Nobody", Address: "nobody@mattermost.com"}
|
||||
var emailSubject = "Testing this email"
|
||||
var emailBody = "This is a test from autobot"
|
||||
|
||||
//Delete all the messages before check the sample email
|
||||
DeleteMailBox(smtpTo)
|
||||
|
||||
// create a file that will be attached to the email
|
||||
fileBackend, err := NewFileBackend(&cfg.FileSettings)
|
||||
assert.Nil(t, err)
|
||||
fileContents := []byte("hello world")
|
||||
fileName := "file.txt"
|
||||
assert.Nil(t, fileBackend.WriteFile(fileContents, fileName))
|
||||
defer fileBackend.RemoveFile(fileName)
|
||||
|
||||
attachments := make([]*model.FileInfo, 1)
|
||||
attachments[0] = &model.FileInfo{
|
||||
Name: fileName,
|
||||
Path: fileName,
|
||||
}
|
||||
|
||||
headers := make(map[string]string)
|
||||
headers["TestHeader"] = "TestValue"
|
||||
|
||||
if err := SendMailUsingConfigAdvanced(mimeTo, smtpTo, from, emailSubject, emailBody, attachments, headers, cfg); err != nil {
|
||||
t.Log(err)
|
||||
t.Fatal("Should connect to the STMP Server")
|
||||
} else {
|
||||
//Check if the email was send to the right email address
|
||||
var resultsMailbox JSONMessageHeaderInbucket
|
||||
err := RetryInbucket(5, func() error {
|
||||
var err error
|
||||
resultsMailbox, err = GetMailBox(smtpTo)
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
t.Fatal("No emails found for address " + smtpTo)
|
||||
}
|
||||
if err == nil && len(resultsMailbox) > 0 {
|
||||
if !strings.ContainsAny(resultsMailbox[0].To[0], smtpTo) {
|
||||
t.Fatal("Wrong To recipient")
|
||||
} else {
|
||||
if resultsEmail, err := GetMessageFromMailbox(smtpTo, resultsMailbox[0].ID); err == nil {
|
||||
if !strings.Contains(resultsEmail.Body.Text, emailBody) {
|
||||
t.Log(resultsEmail.Body.Text)
|
||||
t.Fatal("Received message")
|
||||
}
|
||||
|
||||
// verify that the To header of the email message is set to the MIME recipient, even though we got it out of the SMTP recipient's email inbox
|
||||
assert.Equal(t, mimeTo, resultsEmail.Header["To"][0])
|
||||
|
||||
// verify that the MIME from address is correct - unfortunately, we can't verify the SMTP from address
|
||||
assert.Equal(t, from.String(), resultsEmail.Header["From"][0])
|
||||
|
||||
// check that the custom mime headers came through - header case seems to get mutated
|
||||
assert.Equal(t, "TestValue", resultsEmail.Header["Testheader"][0])
|
||||
|
||||
// ensure that the attachment was successfully sent
|
||||
assert.Len(t, resultsEmail.Attachments, 1)
|
||||
assert.Equal(t, fileName, resultsEmail.Attachments[0].Filename)
|
||||
assert.Equal(t, fileContents, resultsEmail.Attachments[0].Bytes)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,10 @@ func Setup() *app.App {
|
||||
}
|
||||
prevListenAddress := *a.Config().ServiceSettings.ListenAddress
|
||||
a.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.ListenAddress = ":0" })
|
||||
a.StartServer()
|
||||
serverErr := a.StartServer()
|
||||
if serverErr != nil {
|
||||
panic(serverErr)
|
||||
}
|
||||
a.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.ListenAddress = prevListenAddress })
|
||||
api4.Init(a, a.Srv.Router, false)
|
||||
api3 := api.Init(a, a.Srv.Router)
|
||||
|
||||
Reference in New Issue
Block a user