Merge remote-tracking branch 'origin/master' into advanced-permissions-phase-1
Этот коммит содержится в:
@@ -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
Обычный файл
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": ""
|
||||||
|
|||||||
40
i18n/en.json
40
i18n/en.json
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
91
templates/globalrelay_compliance_export.html
Обычный файл
91
templates/globalrelay_compliance_export.html
Обычный файл
@@ -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
Обычный файл
8
templates/globalrelay_compliance_export_message.html
Обычный файл
@@ -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
Обычный файл
10
templates/globalrelay_compliance_export_participant_row.html
Обычный файл
@@ -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)
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user