summaryrefslogtreecommitdiffstats
path: root/utils
diff options
context:
space:
mode:
Diffstat (limited to 'utils')
-rw-r--r--utils/api.go25
-rw-r--r--utils/api_test.go49
-rw-r--r--utils/authorization.go20
-rw-r--r--utils/config.go31
-rw-r--r--utils/config_test.go2
-rw-r--r--utils/file_backend.go4
-rw-r--r--utils/file_backend_test.go2
-rw-r--r--utils/html.go8
-rw-r--r--utils/inbucket.go52
-rw-r--r--utils/license.go135
-rw-r--r--utils/license_test.go67
-rw-r--r--utils/mail.go62
-rw-r--r--utils/mail_test.go89
13 files changed, 261 insertions, 285 deletions
diff --git a/utils/api.go b/utils/api.go
index 51524074d..005c3284b 100644
--- a/utils/api.go
+++ b/utils/api.go
@@ -4,9 +4,6 @@
package utils
import (
- "crypto"
- "crypto/rand"
- "encoding/base64"
"fmt"
"html/template"
"net/http"
@@ -35,25 +32,13 @@ func OriginChecker(allowedOrigins string) func(*http.Request) bool {
}
}
-func RenderWebAppError(w http.ResponseWriter, r *http.Request, err *model.AppError, s crypto.Signer) {
- RenderWebError(w, r, err.StatusCode, url.Values{
- "message": []string{err.Message},
- }, s)
-}
-
-func RenderWebError(w http.ResponseWriter, r *http.Request, status int, params url.Values, s crypto.Signer) {
- queryString := params.Encode()
-
- h := crypto.SHA256
- sum := h.New()
- sum.Write([]byte("/error?" + queryString))
- signature, err := s.Sign(rand.Reader, sum.Sum(nil), h)
- if err != nil {
- http.Error(w, "", http.StatusInternalServerError)
- return
+func RenderWebError(err *model.AppError, w http.ResponseWriter, r *http.Request) {
+ status := http.StatusTemporaryRedirect
+ if err.StatusCode != http.StatusInternalServerError {
+ status = err.StatusCode
}
- destination := strings.TrimRight(GetSiteURL(), "/") + "/error?" + queryString + "&s=" + base64.URLEncoding.EncodeToString(signature)
+ destination := strings.TrimRight(GetSiteURL(), "/") + "/error?message=" + url.QueryEscape(err.Message)
if status >= 300 && status < 400 {
http.Redirect(w, r, destination, status)
return
diff --git a/utils/api_test.go b/utils/api_test.go
deleted file mode 100644
index 5e41c7bfe..000000000
--- a/utils/api_test.go
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-package utils
-
-import (
- "crypto/ecdsa"
- "crypto/elliptic"
- "crypto/rand"
- "crypto/sha256"
- "encoding/asn1"
- "encoding/base64"
- "math/big"
- "net/http"
- "net/http/httptest"
- "net/url"
- "testing"
-
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
-)
-
-func TestRenderWebError(t *testing.T) {
- r := httptest.NewRequest("GET", "http://foo", nil)
- w := httptest.NewRecorder()
- key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
- require.NoError(t, err)
- RenderWebError(w, r, http.StatusTemporaryRedirect, url.Values{
- "foo": []string{"bar"},
- }, key)
-
- resp := w.Result()
- location, err := url.Parse(resp.Header.Get("Location"))
- require.NoError(t, err)
- require.NotEmpty(t, location.Query().Get("s"))
-
- type ecdsaSignature struct {
- R, S *big.Int
- }
- var rs ecdsaSignature
- s, err := base64.URLEncoding.DecodeString(location.Query().Get("s"))
- require.NoError(t, err)
- _, err = asn1.Unmarshal(s, &rs)
- require.NoError(t, err)
-
- assert.Equal(t, "bar", location.Query().Get("foo"))
- h := sha256.Sum256([]byte("/error?foo=bar"))
- assert.True(t, ecdsa.Verify(&key.PublicKey, h[:], rs.R, rs.S))
-}
diff --git a/utils/authorization.go b/utils/authorization.go
index 42815b807..39a0d606c 100644
--- a/utils/authorization.go
+++ b/utils/authorization.go
@@ -7,7 +7,7 @@ import (
"github.com/mattermost/mattermost-server/model"
)
-func DefaultRolesBasedOnConfig(cfg *model.Config, isLicensed bool) map[string]*model.Role {
+func DefaultRolesBasedOnConfig(cfg *model.Config) map[string]*model.Role {
roles := make(map[string]*model.Role)
for id, role := range model.DefaultRoles {
copy := &model.Role{}
@@ -15,7 +15,7 @@ func DefaultRolesBasedOnConfig(cfg *model.Config, isLicensed bool) map[string]*m
roles[id] = copy
}
- if isLicensed {
+ if IsLicensed() {
switch *cfg.TeamSettings.RestrictPublicChannelCreation {
case model.PERMISSIONS_ALL:
roles[model.TEAM_USER_ROLE_ID].Permissions = append(
@@ -35,7 +35,7 @@ func DefaultRolesBasedOnConfig(cfg *model.Config, isLicensed bool) map[string]*m
)
}
- if isLicensed {
+ if IsLicensed() {
switch *cfg.TeamSettings.RestrictPublicChannelManagement {
case model.PERMISSIONS_ALL:
roles[model.TEAM_USER_ROLE_ID].Permissions = append(
@@ -64,7 +64,7 @@ func DefaultRolesBasedOnConfig(cfg *model.Config, isLicensed bool) map[string]*m
)
}
- if isLicensed {
+ if IsLicensed() {
switch *cfg.TeamSettings.RestrictPublicChannelDeletion {
case model.PERMISSIONS_ALL:
roles[model.TEAM_USER_ROLE_ID].Permissions = append(
@@ -93,7 +93,7 @@ func DefaultRolesBasedOnConfig(cfg *model.Config, isLicensed bool) map[string]*m
)
}
- if isLicensed {
+ if IsLicensed() {
switch *cfg.TeamSettings.RestrictPrivateChannelCreation {
case model.PERMISSIONS_ALL:
roles[model.TEAM_USER_ROLE_ID].Permissions = append(
@@ -113,7 +113,7 @@ func DefaultRolesBasedOnConfig(cfg *model.Config, isLicensed bool) map[string]*m
)
}
- if isLicensed {
+ if IsLicensed() {
switch *cfg.TeamSettings.RestrictPrivateChannelManagement {
case model.PERMISSIONS_ALL:
roles[model.TEAM_USER_ROLE_ID].Permissions = append(
@@ -142,7 +142,7 @@ func DefaultRolesBasedOnConfig(cfg *model.Config, isLicensed bool) map[string]*m
)
}
- if isLicensed {
+ if IsLicensed() {
switch *cfg.TeamSettings.RestrictPrivateChannelDeletion {
case model.PERMISSIONS_ALL:
roles[model.TEAM_USER_ROLE_ID].Permissions = append(
@@ -172,7 +172,7 @@ func DefaultRolesBasedOnConfig(cfg *model.Config, isLicensed bool) map[string]*m
}
// Restrict permissions for Private Channel Manage Members
- if isLicensed {
+ if IsLicensed() {
switch *cfg.TeamSettings.RestrictPrivateChannelManageMembers {
case model.PERMISSIONS_ALL:
roles[model.CHANNEL_USER_ROLE_ID].Permissions = append(
@@ -214,7 +214,7 @@ func DefaultRolesBasedOnConfig(cfg *model.Config, isLicensed bool) map[string]*m
}
// Grant permissions for inviting and adding users to a team.
- if isLicensed {
+ if IsLicensed() {
if *cfg.TeamSettings.RestrictTeamInvite == model.PERMISSIONS_TEAM_ADMIN {
roles[model.TEAM_ADMIN_ROLE_ID].Permissions = append(
roles[model.TEAM_ADMIN_ROLE_ID].Permissions,
@@ -236,7 +236,7 @@ func DefaultRolesBasedOnConfig(cfg *model.Config, isLicensed bool) map[string]*m
)
}
- if isLicensed {
+ if IsLicensed() {
switch *cfg.ServiceSettings.RestrictPostDelete {
case model.PERMISSIONS_DELETE_POST_ALL:
roles[model.CHANNEL_USER_ROLE_ID].Permissions = append(
diff --git a/utils/config.go b/utils/config.go
index a855733a7..87ebee693 100644
--- a/utils/config.go
+++ b/utils/config.go
@@ -342,7 +342,7 @@ func LoadConfig(fileName string) (config *model.Config, configPath string, appEr
return config, configPath, nil
}
-func GenerateClientConfig(c *model.Config, diagnosticId string, license *model.License) map[string]string {
+func GenerateClientConfig(c *model.Config, diagnosticId string) map[string]string {
props := make(map[string]string)
props["Version"] = model.CurrentVersion
@@ -459,17 +459,18 @@ func GenerateClientConfig(c *model.Config, diagnosticId string, license *model.L
hasImageProxy := c.ServiceSettings.ImageProxyType != nil && *c.ServiceSettings.ImageProxyType != "" && c.ServiceSettings.ImageProxyURL != nil && *c.ServiceSettings.ImageProxyURL != ""
props["HasImageProxy"] = strconv.FormatBool(hasImageProxy)
- if license != nil {
+ if IsLicensed() {
+ License := License()
props["ExperimentalTownSquareIsReadOnly"] = strconv.FormatBool(*c.TeamSettings.ExperimentalTownSquareIsReadOnly)
props["ExperimentalEnableAuthenticationTransfer"] = strconv.FormatBool(*c.ServiceSettings.ExperimentalEnableAuthenticationTransfer)
- if *license.Features.CustomBrand {
+ if *License.Features.CustomBrand {
props["EnableCustomBrand"] = strconv.FormatBool(*c.TeamSettings.EnableCustomBrand)
props["CustomBrandText"] = *c.TeamSettings.CustomBrandText
props["CustomDescriptionText"] = *c.TeamSettings.CustomDescriptionText
}
- if *license.Features.LDAP {
+ if *License.Features.LDAP {
props["EnableLdap"] = strconv.FormatBool(*c.LdapSettings.Enable)
props["LdapLoginFieldName"] = *c.LdapSettings.LoginFieldName
props["LdapNicknameAttributeSet"] = strconv.FormatBool(*c.LdapSettings.NicknameAttribute != "")
@@ -480,16 +481,16 @@ func GenerateClientConfig(c *model.Config, diagnosticId string, license *model.L
props["LdapLoginButtonTextColor"] = *c.LdapSettings.LoginButtonTextColor
}
- if *license.Features.MFA {
+ if *License.Features.MFA {
props["EnableMultifactorAuthentication"] = strconv.FormatBool(*c.ServiceSettings.EnableMultifactorAuthentication)
props["EnforceMultifactorAuthentication"] = strconv.FormatBool(*c.ServiceSettings.EnforceMultifactorAuthentication)
}
- if *license.Features.Compliance {
+ if *License.Features.Compliance {
props["EnableCompliance"] = strconv.FormatBool(*c.ComplianceSettings.Enable)
}
- if *license.Features.SAML {
+ if *License.Features.SAML {
props["EnableSaml"] = strconv.FormatBool(*c.SamlSettings.Enable)
props["SamlLoginButtonText"] = *c.SamlSettings.LoginButtonText
props["SamlFirstNameAttributeSet"] = strconv.FormatBool(*c.SamlSettings.FirstNameAttribute != "")
@@ -500,23 +501,23 @@ func GenerateClientConfig(c *model.Config, diagnosticId string, license *model.L
props["SamlLoginButtonTextColor"] = *c.SamlSettings.LoginButtonTextColor
}
- if *license.Features.Cluster {
+ if *License.Features.Cluster {
props["EnableCluster"] = strconv.FormatBool(*c.ClusterSettings.Enable)
}
- if *license.Features.Cluster {
+ if *License.Features.Cluster {
props["EnableMetrics"] = strconv.FormatBool(*c.MetricsSettings.Enable)
}
- if *license.Features.GoogleOAuth {
+ if *License.Features.GoogleOAuth {
props["EnableSignUpWithGoogle"] = strconv.FormatBool(c.GoogleSettings.Enable)
}
- if *license.Features.Office365OAuth {
+ if *License.Features.Office365OAuth {
props["EnableSignUpWithOffice365"] = strconv.FormatBool(c.Office365Settings.Enable)
}
- if *license.Features.PasswordRequirements {
+ if *License.Features.PasswordRequirements {
props["PasswordMinimumLength"] = fmt.Sprintf("%v", *c.PasswordSettings.MinimumLength)
props["PasswordRequireLowercase"] = strconv.FormatBool(*c.PasswordSettings.Lowercase)
props["PasswordRequireUppercase"] = strconv.FormatBool(*c.PasswordSettings.Uppercase)
@@ -524,7 +525,7 @@ func GenerateClientConfig(c *model.Config, diagnosticId string, license *model.L
props["PasswordRequireSymbol"] = strconv.FormatBool(*c.PasswordSettings.Symbol)
}
- if *license.Features.Announcement {
+ if *License.Features.Announcement {
props["EnableBanner"] = strconv.FormatBool(*c.AnnouncementSettings.EnableBanner)
props["BannerText"] = *c.AnnouncementSettings.BannerText
props["BannerColor"] = *c.AnnouncementSettings.BannerColor
@@ -532,14 +533,14 @@ func GenerateClientConfig(c *model.Config, diagnosticId string, license *model.L
props["AllowBannerDismissal"] = strconv.FormatBool(*c.AnnouncementSettings.AllowBannerDismissal)
}
- if *license.Features.ThemeManagement {
+ if *License.Features.ThemeManagement {
props["EnableThemeSelection"] = strconv.FormatBool(*c.ThemeSettings.EnableThemeSelection)
props["DefaultTheme"] = *c.ThemeSettings.DefaultTheme
props["AllowCustomThemes"] = strconv.FormatBool(*c.ThemeSettings.AllowCustomThemes)
props["AllowedThemes"] = strings.Join(c.ThemeSettings.AllowedThemes, ",")
}
- if *license.Features.DataRetention {
+ if *License.Features.DataRetention {
props["DataRetentionEnableMessageDeletion"] = strconv.FormatBool(*c.DataRetentionSettings.EnableMessageDeletion)
props["DataRetentionMessageRetentionDays"] = strconv.FormatInt(int64(*c.DataRetentionSettings.MessageRetentionDays), 10)
props["DataRetentionEnableFileDeletion"] = strconv.FormatBool(*c.DataRetentionSettings.EnableFileDeletion)
diff --git a/utils/config_test.go b/utils/config_test.go
index 5809422f1..9abc56d5e 100644
--- a/utils/config_test.go
+++ b/utils/config_test.go
@@ -197,7 +197,7 @@ func TestGetClientConfig(t *testing.T) {
cfg, _, err := LoadConfig("config.json")
require.Nil(t, err)
- configMap := GenerateClientConfig(cfg, "", nil)
+ configMap := GenerateClientConfig(cfg, "")
if configMap["EmailNotificationContentsType"] != *cfg.EmailSettings.EmailNotificationContentsType {
t.Fatal("EmailSettings.EmailNotificationContentsType not exposed to client config")
}
diff --git a/utils/file_backend.go b/utils/file_backend.go
index 42af7f604..c7a6c5591 100644
--- a/utils/file_backend.go
+++ b/utils/file_backend.go
@@ -22,7 +22,7 @@ type FileBackend interface {
RemoveDirectory(path string) *model.AppError
}
-func NewFileBackend(settings *model.FileSettings, enableComplianceFeatures bool) (FileBackend, *model.AppError) {
+func NewFileBackend(settings *model.FileSettings) (FileBackend, *model.AppError) {
switch *settings.DriverName {
case model.IMAGE_DRIVER_S3:
return &S3FileBackend{
@@ -33,7 +33,7 @@ func NewFileBackend(settings *model.FileSettings, enableComplianceFeatures bool)
signV2: settings.AmazonS3SignV2 != nil && *settings.AmazonS3SignV2,
region: settings.AmazonS3Region,
bucket: settings.AmazonS3Bucket,
- encrypt: settings.AmazonS3SSE != nil && *settings.AmazonS3SSE && enableComplianceFeatures,
+ encrypt: settings.AmazonS3SSE != nil && *settings.AmazonS3SSE && IsLicensed() && *License().Features.Compliance,
trace: settings.AmazonS3Trace != nil && *settings.AmazonS3Trace,
}, nil
case model.IMAGE_DRIVER_LOCAL:
diff --git a/utils/file_backend_test.go b/utils/file_backend_test.go
index 46f75574e..76cd1f4a8 100644
--- a/utils/file_backend_test.go
+++ b/utils/file_backend_test.go
@@ -63,7 +63,7 @@ func TestS3FileBackendTestSuite(t *testing.T) {
func (s *FileBackendTestSuite) SetupTest() {
TranslationsPreInit()
- backend, err := NewFileBackend(&s.settings, true)
+ backend, err := NewFileBackend(&s.settings)
require.Nil(s.T(), err)
s.backend = backend
}
diff --git a/utils/html.go b/utils/html.go
index 6bbe55c6d..02db8c97a 100644
--- a/utils/html.go
+++ b/utils/html.go
@@ -23,7 +23,7 @@ type HTMLTemplateWatcher struct {
func NewHTMLTemplateWatcher(directory string) (*HTMLTemplateWatcher, error) {
templatesDir, _ := FindDir(directory)
- l4g.Debug("Parsing server templates at %v", templatesDir)
+ l4g.Debug(T("api.api.init.parsing_templates.debug"), templatesDir)
ret := &HTMLTemplateWatcher{
stop: make(chan struct{}),
@@ -55,15 +55,15 @@ func NewHTMLTemplateWatcher(directory string) (*HTMLTemplateWatcher, error) {
return
case event := <-watcher.Events:
if event.Op&fsnotify.Write == fsnotify.Write {
- l4g.Info("Re-parsing templates because of modified file %v", event.Name)
+ l4g.Info(T("web.reparse_templates.info"), event.Name)
if htmlTemplates, err := template.ParseGlob(templatesDir + "*.html"); err != nil {
- l4g.Error("Failed to parse templates %v", err)
+ l4g.Error(T("web.parsing_templates.error"), err)
} else {
ret.templates.Store(htmlTemplates)
}
}
case err := <-watcher.Errors:
- l4g.Error("Failed in directory watcher %s", err)
+ l4g.Error(T("web.dir_fail.error"), err)
}
}
}()
diff --git a/utils/inbucket.go b/utils/inbucket.go
index 5c40d5757..46011989b 100644
--- a/utils/inbucket.go
+++ b/utils/inbucket.go
@@ -4,7 +4,6 @@
package utils
import (
- "bytes"
"encoding/json"
"fmt"
"io"
@@ -38,12 +37,6 @@ type JSONMessageInbucket struct {
Text string
HTML string `json:"Html"`
}
- Attachments []struct {
- Filename string
- ContentType string `json:"content-type"`
- DownloadLink string `json:"download-link"`
- Bytes []byte `json:"-"`
- }
}
func ParseEmail(email string) string {
@@ -96,54 +89,21 @@ func GetMessageFromMailbox(email, id string) (results JSONMessageInbucket, err e
var record JSONMessageInbucket
url := fmt.Sprintf("%s%s%s/%s", getInbucketHost(), INBUCKET_API, parsedEmail, id)
- emailResponse, err := get(url)
- if err != nil {
- return record, err
- }
- defer emailResponse.Body.Close()
-
- err = json.NewDecoder(emailResponse.Body).Decode(&record)
-
- // download attachments
- if record.Attachments != nil && len(record.Attachments) > 0 {
- for i := range record.Attachments {
- if bytes, err := downloadAttachment(record.Attachments[i].DownloadLink); err != nil {
- return record, err
- } else {
- record.Attachments[i].Bytes = make([]byte, len(bytes))
- copy(record.Attachments[i].Bytes, bytes)
- }
- }
- }
-
- return record, err
-}
-
-func downloadAttachment(url string) ([]byte, error) {
- attachmentResponse, err := get(url)
- if err != nil {
- return nil, err
- }
- defer attachmentResponse.Body.Close()
-
- buf := new(bytes.Buffer)
- io.Copy(buf, attachmentResponse.Body)
- return buf.Bytes(), nil
-}
-
-func get(url string) (*http.Response, error) {
req, err := http.NewRequest("GET", url, nil)
if err != nil {
- return nil, err
+ return record, err
}
client := &http.Client{}
+
resp, err := client.Do(req)
if err != nil {
- return nil, err
+ return record, err
}
+ defer resp.Body.Close()
- return resp, nil
+ err = json.NewDecoder(resp.Body).Decode(&record)
+ return record, err
}
func DeleteMailBox(email string) (err error) {
diff --git a/utils/license.go b/utils/license.go
index 2853a58d0..2aaa2a549 100644
--- a/utils/license.go
+++ b/utils/license.go
@@ -5,21 +5,28 @@ package utils
import (
"crypto"
+ "crypto/md5"
"crypto/rsa"
"crypto/sha512"
"crypto/x509"
"encoding/base64"
"encoding/pem"
+ "fmt"
"io/ioutil"
"os"
"strconv"
"strings"
+ "sync/atomic"
l4g "github.com/alecthomas/log4go"
"github.com/mattermost/mattermost-server/model"
)
+var isLicensedInt32 int32
+var licenseValue atomic.Value
+var clientLicenseValue atomic.Value
+
var publicKey []byte = []byte(`-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyZmShlU8Z8HdG0IWSZ8r
tSyzyxrXkJjsFUf0Ke7bm/TLtIggRdqOcUF3XEWqQk5RGD5vuq7Rlg1zZqMEBk8N
@@ -30,6 +37,92 @@ a0v85XL6i9ote2P+fLZ3wX9EoioHzgdgB7arOxY50QRJO7OyCqpKFKv6lRWTXuSt
hwIDAQAB
-----END PUBLIC KEY-----`)
+func init() {
+ SetLicense(nil)
+}
+
+func IsLicensed() bool {
+ return atomic.LoadInt32(&isLicensedInt32) == 1
+}
+
+func SetIsLicensed(v bool) {
+ if v {
+ atomic.StoreInt32(&isLicensedInt32, 1)
+ } else {
+ atomic.StoreInt32(&isLicensedInt32, 0)
+ }
+}
+
+func License() *model.License {
+ return licenseValue.Load().(*model.License)
+}
+
+func SetClientLicense(m map[string]string) {
+ clientLicenseValue.Store(m)
+}
+
+func ClientLicense() map[string]string {
+ return clientLicenseValue.Load().(map[string]string)
+}
+
+func LoadLicense(licenseBytes []byte) {
+ if success, licenseStr := ValidateLicense(licenseBytes); success {
+ license := model.LicenseFromJson(strings.NewReader(licenseStr))
+ SetLicense(license)
+ return
+ }
+
+ l4g.Warn(T("utils.license.load_license.invalid.warn"))
+}
+
+var licenseListeners = map[string]func(){}
+
+func AddLicenseListener(listener func()) string {
+ id := model.NewId()
+ licenseListeners[id] = listener
+ return id
+}
+
+func RemoveLicenseListener(id string) {
+ delete(licenseListeners, id)
+}
+
+func SetLicense(license *model.License) bool {
+ defer func() {
+ for _, listener := range licenseListeners {
+ listener()
+ }
+ }()
+
+ if license == nil {
+ SetIsLicensed(false)
+ license = &model.License{
+ Features: new(model.Features),
+ }
+ license.Features.SetDefaults()
+ licenseValue.Store(license)
+
+ SetClientLicense(map[string]string{"IsLicensed": "false"})
+
+ return false
+ } else {
+ license.Features.SetDefaults()
+
+ if !license.IsExpired() {
+ licenseValue.Store(license)
+ SetIsLicensed(true)
+ clientLicenseValue.Store(getClientLicense(license))
+ return true
+ }
+
+ return false
+ }
+}
+
+func RemoveLicense() {
+ SetLicense(nil)
+}
+
func ValidateLicense(signed []byte) (bool, string) {
decoded := make([]byte, base64.StdEncoding.DecodedLen(len(signed)))
@@ -120,12 +213,12 @@ func GetLicenseFileLocation(fileLocation string) string {
}
}
-func GetClientLicense(l *model.License) map[string]string {
+func getClientLicense(l *model.License) map[string]string {
props := make(map[string]string)
- props["IsLicensed"] = strconv.FormatBool(l != nil)
+ props["IsLicensed"] = strconv.FormatBool(IsLicensed())
- if l != nil {
+ if IsLicensed() {
props["Id"] = l.Id
props["Users"] = strconv.Itoa(*l.Features.Users)
props["LDAP"] = strconv.FormatBool(*l.Features.LDAP)
@@ -155,3 +248,39 @@ func GetClientLicense(l *model.License) map[string]string {
return props
}
+
+func GetClientLicenseEtag(useSanitized bool) string {
+ value := ""
+
+ lic := ClientLicense()
+
+ if useSanitized {
+ lic = GetSanitizedClientLicense()
+ }
+
+ for k, v := range lic {
+ value += fmt.Sprintf("%s:%s;", k, v)
+ }
+
+ return model.Etag(fmt.Sprintf("%x", md5.Sum([]byte(value))))
+}
+
+func GetSanitizedClientLicense() map[string]string {
+ sanitizedLicense := make(map[string]string)
+
+ for k, v := range ClientLicense() {
+ sanitizedLicense[k] = v
+ }
+
+ if IsLicensed() {
+ delete(sanitizedLicense, "Id")
+ delete(sanitizedLicense, "Name")
+ delete(sanitizedLicense, "Email")
+ delete(sanitizedLicense, "PhoneNumber")
+ delete(sanitizedLicense, "IssuedAt")
+ delete(sanitizedLicense, "StartsAt")
+ delete(sanitizedLicense, "ExpiresAt")
+ }
+
+ return sanitizedLicense
+}
diff --git a/utils/license_test.go b/utils/license_test.go
index c2d1b4c05..9771ec497 100644
--- a/utils/license_test.go
+++ b/utils/license_test.go
@@ -5,20 +5,87 @@ package utils
import (
"testing"
+
+ "github.com/mattermost/mattermost-server/model"
)
+func TestSetLicense(t *testing.T) {
+ l1 := &model.License{}
+ l1.Features = &model.Features{}
+ l1.Customer = &model.Customer{}
+ l1.StartsAt = model.GetMillis() - 1000
+ l1.ExpiresAt = model.GetMillis() + 100000
+ if ok := SetLicense(l1); !ok {
+ t.Fatal("license should have worked")
+ }
+
+ l2 := &model.License{}
+ l2.Features = &model.Features{}
+ l2.Customer = &model.Customer{}
+ l2.StartsAt = model.GetMillis() - 1000
+ l2.ExpiresAt = model.GetMillis() - 100
+ if ok := SetLicense(l2); ok {
+ t.Fatal("license should have failed")
+ }
+
+ l3 := &model.License{}
+ l3.Features = &model.Features{}
+ l3.Customer = &model.Customer{}
+ l3.StartsAt = model.GetMillis() + 10000
+ l3.ExpiresAt = model.GetMillis() + 100000
+ if ok := SetLicense(l3); !ok {
+ t.Fatal("license should have passed")
+ }
+}
+
func TestValidateLicense(t *testing.T) {
b1 := []byte("junk")
if ok, _ := ValidateLicense(b1); ok {
t.Fatal("should have failed - bad license")
}
+ LoadLicense(b1)
+
b2 := []byte("junkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunk")
if ok, _ := ValidateLicense(b2); ok {
t.Fatal("should have failed - bad license")
}
}
+func TestClientLicenseEtag(t *testing.T) {
+ etag1 := GetClientLicenseEtag(false)
+
+ SetClientLicense(map[string]string{"SomeFeature": "true", "IsLicensed": "true"})
+
+ etag2 := GetClientLicenseEtag(false)
+ if etag1 == etag2 {
+ t.Fatal("etags should not match")
+ }
+
+ SetClientLicense(map[string]string{"SomeFeature": "true", "IsLicensed": "false"})
+
+ etag3 := GetClientLicenseEtag(false)
+ if etag2 == etag3 {
+ t.Fatal("etags should not match")
+ }
+}
+
+func TestGetSanitizedClientLicense(t *testing.T) {
+ l1 := &model.License{}
+ l1.Features = &model.Features{}
+ l1.Customer = &model.Customer{}
+ l1.Customer.Name = "TestName"
+ l1.StartsAt = model.GetMillis() - 1000
+ l1.ExpiresAt = model.GetMillis() + 100000
+ SetLicense(l1)
+
+ m := GetSanitizedClientLicense()
+
+ if _, ok := m["Name"]; ok {
+ t.Fatal("should have been sanatized")
+ }
+}
+
func TestGetLicenseFileLocation(t *testing.T) {
fileName := GetLicenseFileLocation("")
if len(fileName) == 0 {
diff --git a/utils/mail.go b/utils/mail.go
index 633f97818..b0289da5e 100644
--- a/utils/mail.go
+++ b/utils/mail.go
@@ -15,8 +15,6 @@ import (
"net/http"
- "io"
-
l4g "github.com/alecthomas/log4go"
"github.com/mattermost/html2text"
"github.com/mattermost/mattermost-server/model"
@@ -105,73 +103,37 @@ func TestConnection(config *model.Config) {
defer c.Close()
}
-func SendMailUsingConfig(to, subject, htmlBody string, config *model.Config, enableComplianceFeatures bool) *model.AppError {
- fromMail := mail.Address{Name: config.EmailSettings.FeedbackName, Address: config.EmailSettings.FeedbackEmail}
- return sendMail(to, to, fromMail, subject, htmlBody, nil, nil, config, enableComplianceFeatures)
-}
-
-// 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, enableComplianceFeatures bool) *model.AppError {
- return sendMail(mimeTo, smtpTo, from, subject, htmlBody, attachments, mimeHeaders, config, enableComplianceFeatures)
-}
-
-func sendMail(mimeTo, smtpTo string, from mail.Address, subject, htmlBody string, attachments []*model.FileInfo, mimeHeaders map[string]string, config *model.Config, enableComplianceFeatures bool) *model.AppError {
+func SendMailUsingConfig(to, subject, htmlBody string, config *model.Config) *model.AppError {
if !config.EmailSettings.SendEmailNotifications || len(config.EmailSettings.SMTPServer) == 0 {
return nil
}
- l4g.Debug(T("utils.mail.send_mail.sending.debug"), mimeTo, subject)
+ l4g.Debug(T("utils.mail.send_mail.sending.debug"), to, subject)
htmlMessage := "\r\n<html><body>" + htmlBody + "</body></html>"
+ fromMail := mail.Address{Name: config.EmailSettings.FeedbackName, Address: config.EmailSettings.FeedbackEmail}
+
txtBody, err := html2text.FromString(htmlBody)
if err != nil {
l4g.Warn(err)
txtBody = ""
}
- headers := map[string][]string{
- "From": {from.String()},
- "To": {mimeTo},
+ m := gomail.NewMessage(gomail.SetCharset("UTF-8"))
+ m.SetHeaders(map[string][]string{
+ "From": {fromMail.String()},
+ "To": {to},
"Subject": {encodeRFC2047Word(subject)},
"Content-Transfer-Encoding": {"8bit"},
"Auto-Submitted": {"auto-generated"},
"Precedence": {"bulk"},
- }
- if mimeHeaders != nil {
- for k, v := range mimeHeaders {
- headers[k] = []string{encodeRFC2047Word(v)}
- }
- }
-
- m := gomail.NewMessage(gomail.SetCharset("UTF-8"))
- m.SetHeaders(headers)
+ })
m.SetDateHeader("Date", time.Now())
+
m.SetBody("text/plain", txtBody)
m.AddAlternative("text/html", htmlMessage)
- if attachments != nil {
- fileBackend, err := NewFileBackend(&config.FileSettings, enableComplianceFeatures)
- if err != nil {
- return err
- }
-
- for _, fileInfo := range attachments {
- m.Attach(fileInfo.Name, gomail.SetCopyFunc(func(writer io.Writer) error {
- bytes, err := fileBackend.ReadFile(fileInfo.Path)
- if err != nil {
- return err
- }
- if _, err := writer.Write(bytes); err != nil {
- return model.NewAppError("SendMail", "utils.mail.sendMail.attachments.write_error", nil, err.Error(), http.StatusInternalServerError)
- }
- return nil
- }))
-
- }
-
- }
-
conn, err1 := connectToSMTPServer(config)
if err1 != nil {
return err1
@@ -185,11 +147,11 @@ func sendMail(mimeTo, smtpTo string, from mail.Address, subject, htmlBody string
defer c.Quit()
defer c.Close()
- if err := c.Mail(from.Address); err != nil {
+ if err := c.Mail(fromMail.Address); err != nil {
return model.NewAppError("SendMail", "utils.mail.send_mail.from_address.app_error", nil, err.Error(), http.StatusInternalServerError)
}
- if err := c.Rcpt(smtpTo); err != nil {
+ if err := c.Rcpt(to); err != nil {
return model.NewAppError("SendMail", "utils.mail.send_mail.to_address.app_error", nil, err.Error(), http.StatusInternalServerError)
}
diff --git a/utils/mail_test.go b/utils/mail_test.go
index 703420441..574f71f46 100644
--- a/utils/mail_test.go
+++ b/utils/mail_test.go
@@ -7,10 +7,6 @@ import (
"strings"
"testing"
- "net/mail"
-
- "github.com/mattermost/mattermost-server/model"
- "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -43,18 +39,18 @@ func TestSendMailUsingConfig(t *testing.T) {
require.Nil(t, err)
T = GetUserTranslations("en")
- var emailTo = "test@example.com"
- var emailSubject = "Testing this email"
- var emailBody = "This is a test from autobot"
+ var emailTo string = "test@example.com"
+ var emailSubject string = "Testing this email"
+ var emailBody string = "This is a test from autobot"
//Delete all the messages before check the sample email
DeleteMailBox(emailTo)
- if err := SendMailUsingConfig(emailTo, emailSubject, emailBody, cfg, true); err != nil {
+ if err := SendMailUsingConfig(emailTo, emailSubject, emailBody, 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
+ //Check if the email was send to the rigth email address
var resultsMailbox JSONMessageHeaderInbucket
err := RetryInbucket(5, func() error {
var err error
@@ -79,78 +75,3 @@ 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, true)
- 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, true); 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)
- }
- }
- }
- }
-}