Merge remote-tracking branch 'origin/master' into advanced-permissions-phase-1

Этот коммит содержится в:
Jesús Espino
2018-02-07 18:05:23 +01:00
родитель 7941c30117 7bd298ceaa
Коммит a04b02081a
52 изменённых файлов: 699 добавлений и 125 удалений

Просмотреть файл

@@ -105,7 +105,11 @@ func setupTestHelper(enterprise bool) *TestHelper {
if testStore != nil { if testStore != nil {
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.ListenAddress = ":0" }) 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.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.ListenAddress = prevListenAddress })
api4.Init(th.App, th.App.Srv.Router, false) api4.Init(th.App, th.App.Srv.Router, false)
Init(th.App, th.App.Srv.Router) 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) c.SetSiteURLHeader(app.GetProtocol(r) + "://" + r.Host)
w.Header().Set(model.HEADER_REQUEST_ID, c.RequestId) 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 // Instruct the browser not to display us in an iframe unless is the same origin for anti-clickjacking
if !h.isApi { if !h.isApi {
@@ -292,7 +292,7 @@ func (c *Context) UserRequired() {
func (c *Context) MfaRequired() { func (c *Context) MfaRequired() {
// Must be licensed for MFA and have it configured for enforcement // 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 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) { 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 := map[string]string{}
rdata["mfa_required"] = "false" rdata["mfa_required"] = "false"
w.Write([]byte(model.MapToJson(rdata))) w.Write([]byte(model.MapToJson(rdata)))

Просмотреть файл

@@ -113,7 +113,11 @@ func setupTestHelper(enterprise bool) *TestHelper {
if testStore != nil { if testStore != nil {
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.ListenAddress = ":0" }) 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.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.ListenAddress = prevListenAddress })
Init(th.App, th.App.Srv.Router, true) Init(th.App, th.App.Srv.Router, true)
wsapi.Init(th.App, th.App.Srv.WebSocketRouter) wsapi.Init(th.App, th.App.Srv.WebSocketRouter)
@@ -299,8 +303,7 @@ func (me *TestHelper) CreateUserWithClient(client *model.Client4) *model.User {
} }
utils.DisableDebugLogForTest() utils.DisableDebugLogForTest()
ruser, r := client.CreateUser(user) ruser, _ := client.CreateUser(user)
fmt.Println(r)
ruser.Password = "Password1" ruser.Password = "Password1"
store.Must(me.App.Srv.Store.User().VerifyEmail(ruser.Id)) store.Must(me.App.Srv.Store.User().VerifyEmail(ruser.Id))
utils.EnableDebugLogForTest() utils.EnableDebugLogForTest()

Просмотреть файл

@@ -112,7 +112,7 @@ func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
c.SetSiteURLHeader(app.GetProtocol(r) + "://" + r.Host) c.SetSiteURLHeader(app.GetProtocol(r) + "://" + r.Host)
w.Header().Set(model.HEADER_REQUEST_ID, c.RequestId) 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") w.Header().Set("Content-Type", "application/json")
@@ -249,7 +249,7 @@ func (c *Context) SessionRequired() {
func (c *Context) MfaRequired() { func (c *Context) MfaRequired() {
// Must be licensed for MFA and have it configured for enforcement // 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 return
} }

Просмотреть файл

@@ -738,7 +738,7 @@ func checkUserMfa(c *Context, w http.ResponseWriter, r *http.Request) {
resp := map[string]interface{}{} resp := map[string]interface{}{}
resp["mfa_required"] = false 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))) w.Write([]byte(model.StringInterfaceToJson(resp)))
return return
} }

Просмотреть файл

@@ -50,7 +50,8 @@ func TestAppRace(t *testing.T) {
a, err := New() a, err := New()
require.NoError(t, err) require.NoError(t, err)
a.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.ListenAddress = ":0" }) a.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.ListenAddress = ":0" })
a.StartServer() serverErr := a.StartServer()
require.NoError(t, serverErr)
a.Shutdown() a.Shutdown()
} }
} }

Просмотреть файл

@@ -101,7 +101,11 @@ func setupTestHelper(enterprise bool) *TestHelper {
if testStore != nil { if testStore != nil {
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.ListenAddress = ":0" }) 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.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.ListenAddress = prevListenAddress })
th.App.DoAdvancedPermissionsMigration() th.App.DoAdvancedPermissionsMigration()

Просмотреть файл

@@ -36,7 +36,7 @@ func (tl TokenLocation) String() string {
} }
func (a *App) IsPasswordValid(password string) *model.AppError { 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.IsPasswordValidWithSettings(password, &a.Config().PasswordSettings)
} }
return utils.IsPasswordValid(password) 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 { 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 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) { 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 user.AuthService == model.USER_AUTH_SERVICE_LDAP {
if !ldapAvailable { 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 // 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. // 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. // Check that all channel members are in the destination team.
if channelMembers, err := a.GetChannelMembersPage(channel.Id, 0, 10000000); err != nil { if channelMembers, err := a.GetChannelMembersPage(channel.Id, 0, 10000000); err != nil {
return err 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 channel.TeamId = team.Id
if result := <-a.Srv.Store.Channel().Update(channel); result.Err != nil { if result := <-a.Srv.Store.Channel().Update(channel); result.Err != nil {
return result.Err 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 return nil
} }

Просмотреть файл

@@ -97,7 +97,7 @@ func TestMoveChannel(t *testing.T) {
t.Fatal(err) 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.") t.Fatal("Should have failed due to mismatched members.")
} }
@@ -105,7 +105,7 @@ func TestMoveChannel(t *testing.T) {
t.Fatal(err) 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) t.Fatal(err)
} }
} }

Просмотреть файл

@@ -9,7 +9,6 @@ import (
l4g "github.com/alecthomas/log4go" l4g "github.com/alecthomas/log4go"
"github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/model"
"github.com/mattermost/mattermost-server/utils"
) )
const ( const (
@@ -80,7 +79,7 @@ func (me *ClusterDiscoveryService) Stop() {
} }
func (a *App) IsLeader() bool { 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() return a.Cluster.IsLeader()
} else { } else {
return true return true

Просмотреть файл

@@ -9,11 +9,10 @@ import (
"net/http" "net/http"
"github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/model"
"github.com/mattermost/mattermost-server/utils"
) )
func (a *App) GetComplianceReports(page, perPage int) (model.Compliances, *model.AppError) { 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) 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) { 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) 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) { 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) 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] 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" "sync/atomic"
"github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/model"
"github.com/mattermost/mattermost-server/utils"
"github.com/segmentio/analytics-go" "github.com/segmentio/analytics-go"
) )
@@ -502,6 +501,7 @@ func (a *App) trackConfig() {
a.SendDiagnostic(TRACK_CONFIG_MESSAGE_EXPORT, map[string]interface{}{ a.SendDiagnostic(TRACK_CONFIG_MESSAGE_EXPORT, map[string]interface{}{
"enable_message_export": *cfg.MessageExportSettings.EnableExport, "enable_message_export": *cfg.MessageExportSettings.EnableExport,
"export_format": *cfg.MessageExportSettings.ExportFormat,
"daily_run_time": *cfg.MessageExportSettings.DailyRunTime, "daily_run_time": *cfg.MessageExportSettings.DailyRunTime,
"default_export_from_timestamp": *cfg.MessageExportSettings.ExportFromTimestamp, "default_export_from_timestamp": *cfg.MessageExportSettings.ExportFromTimestamp,
"batch_size": *cfg.MessageExportSettings.BatchSize, "batch_size": *cfg.MessageExportSettings.BatchSize,
@@ -509,17 +509,17 @@ func (a *App) trackConfig() {
} }
func (a *App) trackLicense() { func (a *App) trackLicense() {
if utils.IsLicensed() { if license := a.License(); license != nil {
data := map[string]interface{}{ data := map[string]interface{}{
"customer_id": utils.License().Customer.Id, "customer_id": license.Customer.Id,
"license_id": utils.License().Id, "license_id": license.Id,
"issued": utils.License().IssuedAt, "issued": license.IssuedAt,
"start": utils.License().StartsAt, "start": license.StartsAt,
"expire": utils.License().ExpiresAt, "expire": license.ExpiresAt,
"users": *utils.License().Features.Users, "users": *license.Features.Users,
} }
features := utils.License().Features.ToMap() features := license.Features.ToMap()
for featureName, featureValue := range features { for featureName, featureValue := range features {
data["feature_"+featureName] = featureValue data["feature_"+featureName] = featureValue
} }

Просмотреть файл

@@ -220,7 +220,7 @@ func (a *App) sendBatchedEmailNotification(userId string, notifications []*batch
} }
emailNotificationContentsType := model.EMAIL_NOTIFICATION_CONTENTS_FULL 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 emailNotificationContentsType = *a.Config().EmailSettings.EmailNotificationContentsType
} }

Просмотреть файл

@@ -14,7 +14,7 @@ import (
func (a *App) SyncLdap() { func (a *App) SyncLdap() {
a.Go(func() { 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 { if ldapI := a.Ldap; ldapI != nil {
ldapI.StartSynchronizeJob(false) ldapI.StartSynchronizeJob(false)
} else { } else {
@@ -25,7 +25,8 @@ func (a *App) SyncLdap() {
} }
func (a *App) TestLdap() *model.AppError { 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 { if err := ldapI.RunTest(); err != nil {
err.StatusCode = 500 err.StatusCode = 500
return err 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) { 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) 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) { 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) return "", model.NewAppError("ldapToEmail", "api.user.ldap_to_email.not_available.app_error", nil, "", http.StatusForbidden)
} }

Просмотреть файл

@@ -6,8 +6,6 @@ package app
import ( import (
//"github.com/mattermost/mattermost-server/model" //"github.com/mattermost/mattermost-server/model"
"testing" "testing"
"github.com/mattermost/mattermost-server/utils"
) )
func TestLoadLicense(t *testing.T) { func TestLoadLicense(t *testing.T) {
@@ -15,7 +13,7 @@ func TestLoadLicense(t *testing.T) {
defer th.TearDown() defer th.TearDown()
th.App.LoadLicense() th.App.LoadLicense()
if utils.IsLicensed() { if th.App.License() != nil {
t.Fatal("shouldn't have a valid license") 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 sendPushNotifications := false
if *a.Config().EmailSettings.SendPushNotifications { if *a.Config().EmailSettings.SendPushNotifications {
pushServer := *a.Config().EmailSettings.PushNotificationServer 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")) l4g.Warn(utils.T("api.post.send_notifications_and_forget.push_notification.mhpnsWarn"))
sendPushNotifications = false sendPushNotifications = false
} else { } else {
@@ -358,7 +358,7 @@ func (a *App) sendNotificationEmail(post *model.Post, user *model.User, channel
} }
emailNotificationContentsType := model.EMAIL_NOTIFICATION_CONTENTS_FULL 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 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) { 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) 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) { 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) 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, TeamId: command.TeamId,
AutoComplete: command.AutoComplete, AutoComplete: command.AutoComplete,
AutoCompleteDesc: command.AutoCompleteDesc, AutoCompleteDesc: command.AutoCompleteDesc,
AutoCompleteHint: command.AutoCompleteHint,
DisplayName: command.DisplayName, DisplayName: command.DisplayName,
} }

Просмотреть файл

@@ -124,7 +124,7 @@ func (a *App) CreatePost(post *model.Post, channel *model.Channel, triggerWebhoo
user = result.Data.(*model.User) user = result.Data.(*model.User)
} }
if utils.IsLicensed() && *a.Config().TeamSettings.ExperimentalTownSquareIsReadOnly && if a.License() != nil && *a.Config().TeamSettings.ExperimentalTownSquareIsReadOnly &&
!post.IsSystemMessage() && !post.IsSystemMessage() &&
channel.Name == model.DEFAULT_CHANNEL && channel.Name == model.DEFAULT_CHANNEL &&
!a.RolesGrantPermission(user.GetRoles(), model.PERMISSION_MANAGE_SYSTEM.Id) { !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 { } else {
oldPost = result.Data.(*model.PostList).Posts[post.Id] 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 { 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) err := model.NewAppError("UpdatePost", "api.post.update_post.permissions_denied.app_error", nil, "", http.StatusForbidden)
return nil, err return nil, err
@@ -354,7 +354,7 @@ func (a *App) UpdatePost(post *model.Post, safeUpdate bool) (*model.Post, *model
return nil, err 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 { 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) 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 return nil, err
@@ -613,7 +613,7 @@ func (a *App) SearchPostsInTeam(terms string, userId string, teamId string, isOr
paramsList := model.ParseSearchParams(terms) paramsList := model.ParseSearchParams(terms)
esInterface := a.Elasticsearch 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{} finalParamsList := []*model.SearchParams{}
for _, params := range paramsList { for _, params := range paramsList {

Просмотреть файл

@@ -17,6 +17,7 @@ import (
l4g "github.com/alecthomas/log4go" l4g "github.com/alecthomas/log4go"
"github.com/gorilla/handlers" "github.com/gorilla/handlers"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/pkg/errors"
"golang.org/x/crypto/acme/autocert" "golang.org/x/crypto/acme/autocert"
"github.com/mattermost/mattermost-server/model" "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) 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")) l4g.Info(utils.T("api.server.start_server.starting.info"))
var handler http.Handler = &CorsWrapper{a.Config, a.Srv.Router} var handler http.Handler = &CorsWrapper{a.Config, a.Srv.Router}
@@ -126,8 +127,7 @@ func (a *App) StartServer() {
rateLimiter, err := NewRateLimiter(&a.Config().RateLimitSettings) rateLimiter, err := NewRateLimiter(&a.Config().RateLimitSettings)
if err != nil { if err != nil {
l4g.Critical(err.Error()) return err
return
} }
a.Srv.RateLimiter = rateLimiter a.Srv.RateLimiter = rateLimiter
@@ -151,8 +151,8 @@ func (a *App) StartServer() {
listener, err := net.Listen("tcp", addr) listener, err := net.Listen("tcp", addr)
if err != nil { if err != nil {
l4g.Critical(utils.T("api.server.start_server.starting.critical"), err) errors.Wrapf(err, utils.T("api.server.start_server.starting.critical"), err)
return return err
} }
a.Srv.ListenAddr = listener.Addr().(*net.TCPAddr) a.Srv.ListenAddr = listener.Addr().(*net.TCPAddr)
@@ -214,6 +214,8 @@ func (a *App) StartServer() {
} }
close(a.Srv.didFinishListen) close(a.Srv.didFinishListen)
}() }()
return nil
} }
type tcpKeepAliveListener struct { type tcpKeepAliveListener struct {

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)
}

Просмотреть файл

@@ -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) 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 && if *a.Config().ServiceSettings.SessionIdleTimeoutInMinutes > 0 &&
utils.IsLicensed() && *utils.License().Features.Compliance && license != nil && *license.Features.Compliance &&
session != nil && !session.IsOAuth && !session.IsMobileApp() && session != nil && !session.IsOAuth && !session.IsMobileApp() &&
session.Props[model.SESSION_PROP_TYPE] != model.SESSION_TYPE_USER_ACCESS_TOKEN { 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) { 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( if result := <-a.Srv.Store.User().GetForLogin(
loginId, loginId,

Просмотреть файл

@@ -277,7 +277,7 @@ func (webCon *WebConn) IsAuthenticated() bool {
func (webCon *WebConn) SendHello() { func (webCon *WebConn) SendHello() {
msg := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_HELLO, "", "", webCon.UserId, nil) 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 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 { channel.Name == model.DEFAULT_CHANNEL {
return model.NewAppError("HandleIncomingWebhook", "api.post.create_post.town_square_read_only", nil, "", http.StatusForbidden) 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().String("purpose", "", "Channel purpose")
channelCreateCmd.Flags().Bool("private", false, "Create a private channel.") 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.") 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") 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] + "'") 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:]) channels := getChannelsFromChannelArgs(a, args[1:])
for i, channel := range channels { for i, channel := range channels {
if channel == nil { if channel == nil {
CommandPrintErrorln("Unable to find channel '" + args[i] + "'") CommandPrintErrorln("Unable to find channel '" + args[i] + "'")
continue 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()) CommandPrintErrorln("Unable to move channel '" + channel.Name + "' error: " + err.Error())
} else { } else {
CommandPrettyPrintln("Moved channel '" + channel.Name + "'") CommandPrettyPrintln("Moved channel '" + channel.Name + "' to " + team.Name + "(" + team.Id + ") from " + originTeamID + ".")
} }
} }
return nil 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 oldTeamId := channel.TeamId
if err := a.MoveChannel(team, channel); err != nil { if err := a.MoveChannel(team, channel, user); err != nil {
return err return err
} }

Просмотреть файл

@@ -44,6 +44,33 @@ func TestRemoveChannel(t *testing.T) {
checkCommand(t, "channel", "remove", th.BasicTeam.Name+":"+channel.Name, th.BasicUser2.Email) 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) { func TestListChannels(t *testing.T) {
th := api.Setup().InitBasic() th := api.Setup().InitBasic()
defer th.TearDown() defer th.TearDown()

Просмотреть файл

@@ -53,7 +53,7 @@ func runServer(configFileLocation string, disableConfigWatch bool) error {
a, err := app.New(options...) a, err := app.New(options...)
if err != nil { if err != nil {
l4g.Error(err.Error()) l4g.Critical(err.Error())
return err return err
} }
defer a.Shutdown() 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) api4.Init(a, a.Srv.Router, false)
api3 := api.Init(a, a.Srv.Router) api3 := api.Init(a, a.Srv.Router)
wsapi.Init(a, a.Srv.WebSocketRouter) wsapi.Init(a, a.Srv.WebSocketRouter)
web.Init(api3) 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")) l4g.Warn(utils.T("store.sql.read_replicas_not_licensed.critical"))
a.UpdateConfig(func(cfg *model.Config) { a.UpdateConfig(func(cfg *model.Config) {
cfg.SqlSettings.DataSourceReplicas = cfg.SqlSettings.DataSourceReplicas[:1] cfg.SqlSettings.DataSourceReplicas = cfg.SqlSettings.DataSourceReplicas[:1]
}) })
} }
if !utils.IsLicensed() { if license == nil {
a.UpdateConfig(func(cfg *model.Config) { a.UpdateConfig(func(cfg *model.Config) {
cfg.TeamSettings.MaxNotificationsPerChannel = &MaxNotificationsPerChannelDefault cfg.TeamSettings.MaxNotificationsPerChannel = &MaxNotificationsPerChannelDefault
}) })

Просмотреть файл

@@ -53,7 +53,11 @@ func webClientTestsCmdF(cmd *cobra.Command, args []string) error {
defer a.Shutdown() defer a.Shutdown()
utils.InitTranslations(a.Config().LocalizationSettings) utils.InitTranslations(a.Config().LocalizationSettings)
a.StartServer() serverErr := a.StartServer()
if serverErr != nil {
return serverErr
}
api4.Init(a, a.Srv.Router, false) api4.Init(a, a.Srv.Router, false)
api.Init(a, a.Srv.Router) api.Init(a, a.Srv.Router)
wsapi.Init(a, a.Srv.WebSocketRouter) wsapi.Init(a, a.Srv.WebSocketRouter)
@@ -71,7 +75,11 @@ func serverForWebClientTestsCmdF(cmd *cobra.Command, args []string) error {
defer a.Shutdown() defer a.Shutdown()
utils.InitTranslations(a.Config().LocalizationSettings) utils.InitTranslations(a.Config().LocalizationSettings)
a.StartServer() serverErr := a.StartServer()
if serverErr != nil {
return serverErr
}
api4.Init(a, a.Srv.Router, false) api4.Init(a, a.Srv.Router, false)
api.Init(a, a.Srv.Router) api.Init(a, a.Srv.Router)
wsapi.Init(a, a.Srv.WebSocketRouter) wsapi.Init(a, a.Srv.WebSocketRouter)

Просмотреть файл

@@ -57,7 +57,7 @@
"CloseUnusedDirectMessages": false, "CloseUnusedDirectMessages": false,
"EnableTutorial": true, "EnableTutorial": true,
"ExperimentalEnableDefaultChannelLeaveJoinMessages": true, "ExperimentalEnableDefaultChannelLeaveJoinMessages": true,
"ExperimentalGroupUnreadChannels": false, "ExperimentalGroupUnreadChannels": "disabled",
"ImageProxyType": "", "ImageProxyType": "",
"ImageProxyOptions": "", "ImageProxyOptions": "",
"ImageProxyURL": "" "ImageProxyURL": ""

Просмотреть файл

@@ -131,10 +131,6 @@
"id": "api.admin.upload_brand_image.too_large.app_error", "id": "api.admin.upload_brand_image.too_large.app_error",
"translation": "Unable to upload file. File is too large." "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", "id": "api.api.init.parsing_templates.error",
"translation": "Failed to parse server templates %v" "translation": "Failed to parse server templates %v"
@@ -3142,6 +3138,14 @@
"id": "app.channel.move_channel.members_do_not_match.error", "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." "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", "id": "app.channel.post_update_channel_purpose_message.post.error",
"translation": "Failed to post channel purpose message" "translation": "Failed to post channel purpose message"
@@ -4766,6 +4770,10 @@
"id": "model.config.is_valid.file_thumb_width.app_error", "id": "model.config.is_valid.file_thumb_width.app_error",
"translation": "Invalid thumbnail width for file settings. Must be a positive number." "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", "id": "model.config.is_valid.image_proxy_type.app_error",
"translation": "Invalid image proxy type for service settings." "translation": "Invalid image proxy type for service settings."
@@ -4862,6 +4870,14 @@
"id": "model.config.is_valid.message_export.batch_size.app_error", "id": "model.config.is_valid.message_export.batch_size.app_error",
"translation": "Message export job BatchSize must be a positive integer" "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", "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." "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", "id": "utils.mail.new_client.auth.app_error",
"translation": "Failed to authenticate on SMTP server" "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", "id": "utils.mail.new_client.helo.error",
"translation": "Failed to to set the HELO to SMTP server %v" "translation": "Failed to to set the HELO to SMTP server %v"
@@ -7182,10 +7202,6 @@
"id": "web.create_dir.error", "id": "web.create_dir.error",
"translation": "Failed to create directory watcher %v" "translation": "Failed to create directory watcher %v"
}, },
{
"id": "web.dir_fail.error",
"translation": "Failed in directory watcher %v"
},
{ {
"id": "web.do_load_channel.error", "id": "web.do_load_channel.error",
"translation": "Error in getting users profile for id=%v forcing logout" "translation": "Error in getting users profile for id=%v forcing logout"
@@ -7290,18 +7306,10 @@
"id": "web.parsing_templates.debug", "id": "web.parsing_templates.debug",
"translation": "Parsing templates at %v" "translation": "Parsing templates at %v"
}, },
{
"id": "web.parsing_templates.error",
"translation": "Failed to parse templates %v"
},
{ {
"id": "web.post_permalink.app_error", "id": "web.post_permalink.app_error",
"translation": "Invalid Post ID" "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", "id": "web.reset_password.expired_link.app_error",
"translation": "The password reset link has expired" "translation": "The password reset link has expired"

Просмотреть файл

@@ -6,7 +6,10 @@ package model
type ChannelMemberHistory struct { type ChannelMemberHistory struct {
ChannelId string ChannelId string
UserId string UserId string
UserEmail string `db:"Email"`
JoinTime int64 JoinTime int64
LeaveTime *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_NEVER = "never"
ALLOW_EDIT_POST_TIME_LIMIT = "time_limit" 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_BUFFER_SIZE = 256
EMAIL_BATCHING_INTERVAL = 30 EMAIL_BATCHING_INTERVAL = 30
@@ -154,6 +158,9 @@ const (
PLUGIN_SETTINGS_DEFAULT_DIRECTORY = "./plugins" PLUGIN_SETTINGS_DEFAULT_DIRECTORY = "./plugins"
PLUGIN_SETTINGS_DEFAULT_CLIENT_DIRECTORY = "./client/plugins" PLUGIN_SETTINGS_DEFAULT_CLIENT_DIRECTORY = "./client/plugins"
COMPLIANCE_EXPORT_TYPE_ACTIANCE = "actiance"
COMPLIANCE_EXPORT_TYPE_GLOBALRELAY = "globalrelay"
) )
type ServiceSettings struct { type ServiceSettings struct {
@@ -214,7 +221,7 @@ type ServiceSettings struct {
EnablePreviewFeatures *bool EnablePreviewFeatures *bool
EnableTutorial *bool EnableTutorial *bool
ExperimentalEnableDefaultChannelLeaveJoinMessages *bool ExperimentalEnableDefaultChannelLeaveJoinMessages *bool
ExperimentalGroupUnreadChannels *bool ExperimentalGroupUnreadChannels *string
ImageProxyType *string ImageProxyType *string
ImageProxyURL *string ImageProxyURL *string
ImageProxyOptions *string ImageProxyOptions *string
@@ -424,7 +431,11 @@ func (s *ServiceSettings) SetDefaults() {
} }
if s.ExperimentalGroupUnreadChannels == nil { 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 { if s.ImageProxyType == nil {
@@ -1615,9 +1626,13 @@ func (s *PluginSettings) SetDefaults() {
type MessageExportSettings struct { type MessageExportSettings struct {
EnableExport *bool EnableExport *bool
ExportFormat *string
DailyRunTime *string DailyRunTime *string
ExportFromTimestamp *int64 ExportFromTimestamp *int64
BatchSize *int 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() { func (s *MessageExportSettings) SetDefaults() {
@@ -1625,6 +1640,10 @@ func (s *MessageExportSettings) SetDefaults() {
s.EnableExport = NewBool(false) s.EnableExport = NewBool(false)
} }
if s.ExportFormat == nil {
s.ExportFormat = NewString(COMPLIANCE_EXPORT_TYPE_ACTIANCE)
}
if s.DailyRunTime == nil { if s.DailyRunTime == nil {
s.DailyRunTime = NewString("01:00") 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) 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 { switch *ss.ImageProxyType {
case "", "willnorris/imageproxy": case "", "willnorris/imageproxy":
case "atmos/camo": 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) 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 { } 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) 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 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) { func TestMessageExportSettingsIsValidEnableExportNotSet(t *testing.T) {
fs := &FileSettings{} fs := &FileSettings{}
mes := &MessageExportSettings{} mes := &MessageExportSettings{}
@@ -104,7 +136,7 @@ func TestMessageExportSettingsIsValidBatchSizeInvalid(t *testing.T) {
require.Error(t, mes.isValid(*fs)) require.Error(t, mes.isValid(*fs))
} }
func TestMessageExportSettingsIsValid(t *testing.T) { func TestMessageExportSettingsIsValidExportFormatInvalid(t *testing.T) {
fs := &FileSettings{ fs := &FileSettings{
DriverName: NewString("foo"), // bypass file location check DriverName: NewString("foo"), // bypass file location check
} }
@@ -115,6 +147,55 @@ func TestMessageExportSettingsIsValid(t *testing.T) {
BatchSize: NewInt(100), 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 // should pass because everything is valid
require.Nil(t, mes.isValid(*fs)) 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, "01:00", *mes.DailyRunTime)
require.Equal(t, int64(0), *mes.ExportFromTimestamp) require.Equal(t, int64(0), *mes.ExportFromTimestamp)
require.Equal(t, 10000, *mes.BatchSize) require.Equal(t, 10000, *mes.BatchSize)
require.Equal(t, COMPLIANCE_EXPORT_TYPE_ACTIANCE, *mes.ExportFormat)
} }
func TestMessageExportSetDefaultsExportEnabledExportFromTimestampNil(t *testing.T) { func TestMessageExportSetDefaultsExportEnabledExportFromTimestampNil(t *testing.T) {

Просмотреть файл

@@ -9,6 +9,7 @@ type MessageExport struct {
UserId *string UserId *string
UserEmail *string UserEmail *string
Username *string
PostId *string PostId *string
PostCreateAt *int64 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_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_ADD_TO_CHANNEL = "system_add_to_channel"
POST_REMOVE_FROM_CHANNEL = "system_remove_from_channel" POST_REMOVE_FROM_CHANNEL = "system_remove_from_channel"
POST_MOVE_CHANNEL = "system_move_channel"
POST_ADD_TO_TEAM = "system_add_to_team" POST_ADD_TO_TEAM = "system_add_to_team"
POST_REMOVE_FROM_TEAM = "system_remove_from_team" POST_REMOVE_FROM_TEAM = "system_remove_from_team"
POST_HEADER_CHANGE = "system_header_change" POST_HEADER_CHANGE = "system_header_change"
@@ -196,6 +197,7 @@ func (o *Post) IsValid() *AppError {
POST_LEAVE_TEAM, POST_LEAVE_TEAM,
POST_ADD_TO_CHANNEL, POST_ADD_TO_CHANNEL,
POST_REMOVE_FROM_CHANNEL, POST_REMOVE_FROM_CHANNEL,
POST_MOVE_CHANNEL,
POST_ADD_TO_TEAM, POST_ADD_TO_TEAM,
POST_REMOVE_FROM_TEAM, POST_REMOVE_FROM_TEAM,
POST_SLACK_ATTACHMENT, POST_SLACK_ATTACHMENT,

Просмотреть файл

@@ -110,7 +110,8 @@ func (s SqlChannelMemberHistoryStore) getFromChannelMemberHistoryTable(startTime
query := ` query := `
SELECT SELECT
cmh.*, cmh.*,
u.Email u.Email,
u.Username
FROM ChannelMemberHistory cmh FROM ChannelMemberHistory cmh
INNER JOIN Users u ON cmh.UserId = u.Id INNER JOIN Users u ON cmh.UserId = u.Id
WHERE cmh.ChannelId = :ChannelId 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) { func (s SqlChannelMemberHistoryStore) getFromChannelMembersTable(startTime int64, endTime int64, channelId string) ([]*model.ChannelMemberHistory, error) {
query := ` query := `
SELECT DISTINCT SELECT DISTINCT
ch.ChannelId, ch.ChannelId,
ch.UserId, ch.UserId,
u.email u.Email,
u.Username
FROM ChannelMembers AS ch FROM ChannelMembers AS ch
INNER JOIN Users AS u ON ch.UserId = u.id INNER JOIN Users AS u ON ch.UserId = u.id
WHERE ch.ChannelId = :ChannelId` WHERE ch.ChannelId = :ChannelId`
@@ -158,7 +160,7 @@ func (s SqlChannelMemberHistoryStore) PermanentDeleteBatch(endTime int64, limit
query = query =
`DELETE FROM ChannelMemberHistory `DELETE FROM ChannelMemberHistory
WHERE ctid IN ( WHERE ctid IN (
SELECT ctid FROM ChannelMemberHistory SELECT ctid FROM ChannelMemberHistory
WHERE LeaveTime IS NOT NULL WHERE LeaveTime IS NOT NULL
AND LeaveTime <= :EndTime AND LeaveTime <= :EndTime
LIMIT :Limit LIMIT :Limit

Просмотреть файл

@@ -225,7 +225,8 @@ func (s SqlComplianceStore) MessageExport(after int64, limit int) store.StoreCha
Channels.Id AS ChannelId, Channels.Id AS ChannelId,
Channels.DisplayName AS ChannelDisplayName, Channels.DisplayName AS ChannelDisplayName,
Users.Id AS UserId, Users.Id AS UserId,
Users.Email AS UserEmail Users.Email AS UserEmail,
Users.Username
FROM FROM
Posts Posts
LEFT OUTER JOIN Channels ON Posts.ChannelId = Channels.Id LEFT OUTER JOIN Channels ON Posts.ChannelId = Channels.Id

Просмотреть файл

@@ -35,6 +35,7 @@ func testLogJoinEvent(t *testing.T, ss store.Store) {
user := model.User{ user := model.User{
Email: model.NewId() + "@mattermost.com", Email: model.NewId() + "@mattermost.com",
Nickname: model.NewId(), Nickname: model.NewId(),
Username: model.NewId(),
} }
user = *store.Must(ss.User().Save(&user)).(*model.User) user = *store.Must(ss.User().Save(&user)).(*model.User)
@@ -57,6 +58,7 @@ func testLogLeaveEvent(t *testing.T, ss store.Store) {
user := model.User{ user := model.User{
Email: model.NewId() + "@mattermost.com", Email: model.NewId() + "@mattermost.com",
Nickname: model.NewId(), Nickname: model.NewId(),
Username: model.NewId(),
} }
user = *store.Must(ss.User().Save(&user)).(*model.User) user = *store.Must(ss.User().Save(&user)).(*model.User)
@@ -82,6 +84,7 @@ func testGetUsersInChannelAtChannelMemberHistory(t *testing.T, ss store.Store) {
user := model.User{ user := model.User{
Email: model.NewId() + "@mattermost.com", Email: model.NewId() + "@mattermost.com",
Nickname: model.NewId(), Nickname: model.NewId(),
Username: model.NewId(),
} }
user = *store.Must(ss.User().Save(&user)).(*model.User) 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, channel.Id, channelMembers[0].ChannelId)
assert.Equal(t, user.Id, channelMembers[0].UserId) assert.Equal(t, user.Id, channelMembers[0].UserId)
assert.Equal(t, user.Email, channelMembers[0].UserEmail) 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, joinTime, channelMembers[0].JoinTime)
assert.Nil(t, channelMembers[0].LeaveTime) 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, channel.Id, channelMembers[0].ChannelId)
assert.Equal(t, user.Id, channelMembers[0].UserId) assert.Equal(t, user.Id, channelMembers[0].UserId)
assert.Equal(t, user.Email, channelMembers[0].UserEmail) 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, joinTime, channelMembers[0].JoinTime)
assert.Nil(t, channelMembers[0].LeaveTime) 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, channel.Id, channelMembers[0].ChannelId)
assert.Equal(t, user.Id, channelMembers[0].UserId) assert.Equal(t, user.Id, channelMembers[0].UserId)
assert.Equal(t, user.Email, channelMembers[0].UserEmail) 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, joinTime, channelMembers[0].JoinTime)
assert.Equal(t, leaveTime, *channelMembers[0].LeaveTime) 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, channel.Id, channelMembers[0].ChannelId)
assert.Equal(t, user.Id, channelMembers[0].UserId) assert.Equal(t, user.Id, channelMembers[0].UserId)
assert.Equal(t, user.Email, channelMembers[0].UserEmail) 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, joinTime, channelMembers[0].JoinTime)
assert.Equal(t, leaveTime, *channelMembers[0].LeaveTime) assert.Equal(t, leaveTime, *channelMembers[0].LeaveTime)
@@ -160,6 +167,7 @@ func testGetUsersInChannelAtChannelMembers(t *testing.T, ss store.Store) {
user := model.User{ user := model.User{
Email: model.NewId() + "@mattermost.com", Email: model.NewId() + "@mattermost.com",
Nickname: model.NewId(), Nickname: model.NewId(),
Username: model.NewId(),
} }
user = *store.Must(ss.User().Save(&user)).(*model.User) 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, channel.Id, channelMembers[0].ChannelId)
assert.Equal(t, user.Id, channelMembers[0].UserId) assert.Equal(t, user.Id, channelMembers[0].UserId)
assert.Equal(t, user.Email, channelMembers[0].UserEmail) 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-500, channelMembers[0].JoinTime)
assert.Equal(t, joinTime-100, *channelMembers[0].LeaveTime) 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, channel.Id, channelMembers[0].ChannelId)
assert.Equal(t, user.Id, channelMembers[0].UserId) assert.Equal(t, user.Id, channelMembers[0].UserId)
assert.Equal(t, user.Email, channelMembers[0].UserEmail) 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-100, channelMembers[0].JoinTime)
assert.Equal(t, joinTime+500, *channelMembers[0].LeaveTime) 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, channel.Id, channelMembers[0].ChannelId)
assert.Equal(t, user.Id, channelMembers[0].UserId) assert.Equal(t, user.Id, channelMembers[0].UserId)
assert.Equal(t, user.Email, channelMembers[0].UserEmail) 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+100, channelMembers[0].JoinTime)
assert.Equal(t, joinTime+500, *channelMembers[0].LeaveTime) 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, channel.Id, channelMembers[0].ChannelId)
assert.Equal(t, user.Id, channelMembers[0].UserId) assert.Equal(t, user.Id, channelMembers[0].UserId)
assert.Equal(t, user.Email, channelMembers[0].UserEmail) 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+100, channelMembers[0].JoinTime)
assert.Equal(t, leaveTime-100, *channelMembers[0].LeaveTime) 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, channel.Id, channelMembers[0].ChannelId)
assert.Equal(t, user.Id, channelMembers[0].UserId) assert.Equal(t, user.Id, channelMembers[0].UserId)
assert.Equal(t, user.Email, channelMembers[0].UserEmail) 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-100, channelMembers[0].JoinTime)
assert.Equal(t, leaveTime+100, *channelMembers[0].LeaveTime) 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, channel.Id, channelMembers[0].ChannelId)
assert.Equal(t, user.Id, channelMembers[0].UserId) assert.Equal(t, user.Id, channelMembers[0].UserId)
assert.Equal(t, user.Email, channelMembers[0].UserEmail) 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+100, channelMembers[0].JoinTime)
assert.Equal(t, leaveTime+200, *channelMembers[0].LeaveTime) assert.Equal(t, leaveTime+200, *channelMembers[0].LeaveTime)
} }
@@ -255,12 +269,14 @@ func testPermanentDeleteBatch(t *testing.T, ss store.Store) {
user := model.User{ user := model.User{
Email: model.NewId() + "@mattermost.com", Email: model.NewId() + "@mattermost.com",
Nickname: model.NewId(), Nickname: model.NewId(),
Username: model.NewId(),
} }
user = *store.Must(ss.User().Save(&user)).(*model.User) user = *store.Must(ss.User().Save(&user)).(*model.User)
user2 := model.User{ user2 := model.User{
Email: model.NewId() + "@mattermost.com", Email: model.NewId() + "@mattermost.com",
Nickname: model.NewId(), Nickname: model.NewId(),
Username: model.NewId(),
} }
user2 = *store.Must(ss.User().Save(&user2)).(*model.User) 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 // and two users that are a part of that team
user1 := &model.User{ user1 := &model.User{
Email: model.NewId(), Email: model.NewId(),
Username: model.NewId(),
} }
user1 = store.Must(ss.User().Save(user1)).(*model.User) user1 = store.Must(ss.User().Save(user1)).(*model.User)
store.Must(ss.Team().SaveMember(&model.TeamMember{ store.Must(ss.Team().SaveMember(&model.TeamMember{
@@ -350,7 +351,8 @@ func testComplianceMessageExport(t *testing.T, ss store.Store) {
}, -1)) }, -1))
user2 := &model.User{ user2 := &model.User{
Email: model.NewId(), Email: model.NewId(),
Username: model.NewId(),
} }
user2 = store.Must(ss.User().Save(user2)).(*model.User) user2 = store.Must(ss.User().Save(user2)).(*model.User)
store.Must(ss.Team().SaveMember(&model.TeamMember{ 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, channel.DisplayName, *messageExportMap[post1.Id].ChannelDisplayName)
assert.Equal(t, user1.Id, *messageExportMap[post1.Id].UserId) assert.Equal(t, user1.Id, *messageExportMap[post1.Id].UserId)
assert.Equal(t, user1.Email, *messageExportMap[post1.Id].UserEmail) 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 // post2 was made by user1 in channel1 and team1
assert.Equal(t, post2.Id, *messageExportMap[post2.Id].PostId) 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, channel.DisplayName, *messageExportMap[post2.Id].ChannelDisplayName)
assert.Equal(t, user1.Id, *messageExportMap[post2.Id].UserId) assert.Equal(t, user1.Id, *messageExportMap[post2.Id].UserId)
assert.Equal(t, user1.Email, *messageExportMap[post2.Id].UserEmail) 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 // post3 is a DM between user1 and user2
assert.Equal(t, post3.Id, *messageExportMap[post3.Id].PostId) 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, directMessageChannel.Id, *messageExportMap[post3.Id].ChannelId)
assert.Equal(t, user1.Id, *messageExportMap[post3.Id].UserId) assert.Equal(t, user1.Id, *messageExportMap[post3.Id].UserId)
assert.Equal(t, user1.Email, *messageExportMap[post3.Id].UserEmail) assert.Equal(t, user1.Email, *messageExportMap[post3.Id].UserEmail)
assert.Equal(t, user1.Username, *messageExportMap[post3.Id].Username)
} }

Просмотреть файл

@@ -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:&nbsp;</span>{{.Props.ChannelName}}</li>
<li><span class="bold">Started:&nbsp;</span>{{.Props.Started}}</li>
<li><span class="bold">Ended:&nbsp;</span>{{.Props.Ended}}</li>
<li><span class="bold">Duration:&nbsp;</span>{{.Props.Duration}}&nbsp;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}}

Просмотреть файл

@@ -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}}

Просмотреть файл

@@ -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["EnablePreviewFeatures"] = strconv.FormatBool(*c.ServiceSettings.EnablePreviewFeatures)
props["EnableTutorial"] = strconv.FormatBool(*c.ServiceSettings.EnableTutorial) props["EnableTutorial"] = strconv.FormatBool(*c.ServiceSettings.EnableTutorial)
props["ExperimentalEnableDefaultChannelLeaveJoinMessages"] = strconv.FormatBool(*c.ServiceSettings.ExperimentalEnableDefaultChannelLeaveJoinMessages) 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["SendEmailNotifications"] = strconv.FormatBool(c.EmailSettings.SendEmailNotifications)
props["SendPushNotifications"] = strconv.FormatBool(*c.EmailSettings.SendPushNotifications) props["SendPushNotifications"] = strconv.FormatBool(*c.EmailSettings.SendPushNotifications)

Просмотреть файл

@@ -23,7 +23,7 @@ type HTMLTemplateWatcher struct {
func NewHTMLTemplateWatcher(directory string) (*HTMLTemplateWatcher, error) { func NewHTMLTemplateWatcher(directory string) (*HTMLTemplateWatcher, error) {
templatesDir, _ := FindDir(directory) templatesDir, _ := FindDir(directory)
l4g.Debug(T("api.api.init.parsing_templates.debug"), templatesDir) l4g.Debug("Parsing server templates at %v", templatesDir)
ret := &HTMLTemplateWatcher{ ret := &HTMLTemplateWatcher{
stop: make(chan struct{}), stop: make(chan struct{}),
@@ -55,15 +55,15 @@ func NewHTMLTemplateWatcher(directory string) (*HTMLTemplateWatcher, error) {
return return
case event := <-watcher.Events: case event := <-watcher.Events:
if event.Op&fsnotify.Write == fsnotify.Write { 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 { 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 { } else {
ret.templates.Store(htmlTemplates) ret.templates.Store(htmlTemplates)
} }
} }
case err := <-watcher.Errors: 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 package utils
import ( import (
"bytes"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
@@ -37,6 +38,12 @@ type JSONMessageInbucket struct {
Text string Text string
HTML string `json:"Html"` 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 { func ParseEmail(email string) string {
@@ -89,21 +96,54 @@ func GetMessageFromMailbox(email, id string) (results JSONMessageInbucket, err e
var record JSONMessageInbucket var record JSONMessageInbucket
url := fmt.Sprintf("%s%s%s/%s", getInbucketHost(), INBUCKET_API, parsedEmail, id) 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 { if err != nil {
return record, err 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{} client := &http.Client{}
resp, err := client.Do(req) resp, err := client.Do(req)
if err != nil { if err != nil {
return record, err return nil, err
} }
defer resp.Body.Close()
err = json.NewDecoder(resp.Body).Decode(&record) return resp, nil
return record, err
} }
func DeleteMailBox(email string) (err error) { func DeleteMailBox(email string) (err error) {

Просмотреть файл

@@ -15,6 +15,8 @@ import (
"net/http" "net/http"
"io"
l4g "github.com/alecthomas/log4go" l4g "github.com/alecthomas/log4go"
"github.com/mattermost/html2text" "github.com/mattermost/html2text"
"github.com/mattermost/mattermost-server/model" "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 { 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 { if !config.EmailSettings.SendEmailNotifications || len(config.EmailSettings.SMTPServer) == 0 {
return nil 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>" htmlMessage := "\r\n<html><body>" + htmlBody + "</body></html>"
fromMail := mail.Address{Name: config.EmailSettings.FeedbackName, Address: config.EmailSettings.FeedbackEmail}
txtBody, err := html2text.FromString(htmlBody) txtBody, err := html2text.FromString(htmlBody)
if err != nil { if err != nil {
l4g.Warn(err) l4g.Warn(err)
txtBody = "" txtBody = ""
} }
m := gomail.NewMessage(gomail.SetCharset("UTF-8")) headers := map[string][]string{
m.SetHeaders(map[string][]string{ "From": {from.String()},
"From": {fromMail.String()}, "To": {mimeTo},
"To": {to},
"Subject": {encodeRFC2047Word(subject)}, "Subject": {encodeRFC2047Word(subject)},
"Content-Transfer-Encoding": {"8bit"}, "Content-Transfer-Encoding": {"8bit"},
"Auto-Submitted": {"auto-generated"}, "Auto-Submitted": {"auto-generated"},
"Precedence": {"bulk"}, "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.SetBody("text/plain", txtBody)
m.AddAlternative("text/html", htmlMessage) 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) conn, err1 := connectToSMTPServer(config)
if err1 != nil { if err1 != nil {
return err1 return err1
@@ -147,11 +185,11 @@ func SendMailUsingConfig(to, subject, htmlBody string, config *model.Config) *mo
defer c.Quit() defer c.Quit()
defer c.Close() 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) 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) return model.NewAppError("SendMail", "utils.mail.send_mail.to_address.app_error", nil, err.Error(), http.StatusInternalServerError)
} }

Просмотреть файл

@@ -7,6 +7,10 @@ import (
"strings" "strings"
"testing" "testing"
"net/mail"
"github.com/mattermost/mattermost-server/model"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@@ -39,9 +43,9 @@ func TestSendMailUsingConfig(t *testing.T) {
require.Nil(t, err) require.Nil(t, err)
T = GetUserTranslations("en") T = GetUserTranslations("en")
var emailTo string = "test@example.com" var emailTo = "test@example.com"
var emailSubject string = "Testing this email" var emailSubject = "Testing this email"
var emailBody string = "This is a test from autobot" var emailBody = "This is a test from autobot"
//Delete all the messages before check the sample email //Delete all the messages before check the sample email
DeleteMailBox(emailTo) DeleteMailBox(emailTo)
@@ -50,7 +54,7 @@ func TestSendMailUsingConfig(t *testing.T) {
t.Log(err) t.Log(err)
t.Fatal("Should connect to the STMP Server") t.Fatal("Should connect to the STMP Server")
} else { } 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 var resultsMailbox JSONMessageHeaderInbucket
err := RetryInbucket(5, func() error { err := RetryInbucket(5, func() error {
var err 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 prevListenAddress := *a.Config().ServiceSettings.ListenAddress
a.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.ListenAddress = ":0" }) 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 }) a.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.ListenAddress = prevListenAddress })
api4.Init(a, a.Srv.Router, false) api4.Init(a, a.Srv.Router, false)
api3 := api.Init(a, a.Srv.Router) api3 := api.Init(a, a.Srv.Router)