revert master changes
Этот коммит содержится в:
25
utils/api.go
25
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
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -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)
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return record, err
|
||||
}
|
||||
defer emailResponse.Body.Close()
|
||||
|
||||
err = json.NewDecoder(emailResponse.Body).Decode(&record)
|
||||
|
||||
// download attachments
|
||||
if record.Attachments != nil && len(record.Attachments) > 0 {
|
||||
for i := range record.Attachments {
|
||||
if bytes, err := downloadAttachment(record.Attachments[i].DownloadLink); err != nil {
|
||||
return record, err
|
||||
} else {
|
||||
record.Attachments[i].Bytes = make([]byte, len(bytes))
|
||||
copy(record.Attachments[i].Bytes, bytes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return record, err
|
||||
}
|
||||
|
||||
func downloadAttachment(url string) ([]byte, error) {
|
||||
attachmentResponse, err := get(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer attachmentResponse.Body.Close()
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
io.Copy(buf, attachmentResponse.Body)
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func get(url string) (*http.Response, error) {
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client := &http.Client{}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return 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) {
|
||||
|
||||
135
utils/license.go
135
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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ссылка в новой задаче
Block a user