From c209e4457457edc042f063390c9a222a694f3a6d Mon Sep 17 00:00:00 2001 From: Derrick Anderson Date: Mon, 12 Feb 2018 16:01:02 -0500 Subject: revert master changes --- utils/api.go | 25 ++------- utils/api_test.go | 49 ---------------- utils/authorization.go | 20 +++---- utils/config.go | 31 ++++++----- utils/config_test.go | 2 +- utils/file_backend.go | 4 +- utils/file_backend_test.go | 2 +- utils/html.go | 8 +-- utils/inbucket.go | 52 ++--------------- utils/license.go | 135 ++++++++++++++++++++++++++++++++++++++++++++- utils/license_test.go | 67 ++++++++++++++++++++++ utils/mail.go | 62 ++++----------------- utils/mail_test.go | 89 ++---------------------------- 13 files changed, 261 insertions(+), 285 deletions(-) delete mode 100644 utils/api_test.go (limited to 'utils') 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" + htmlBody + "" + 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) - } - } - } - } -} -- cgit v1.2.3-1-g7c22