* failing to find i18n shouldn't segfault The server was trying to handle the fact that it couldn't find the i18n directory, by emitting a translated log message... * fix utils.FindDir The attempts to find the directory in the parent or grandparent directory don't work if the current working directory was inside `enterprise`, with `enterprise` itself being a symlink as per the usual developer setup. Recurse to the root of the filesystem, cleaning the path along the way to work around this limitation (and allow tests to be run from an arbitrarily deep nesting level.) Fix corresponding usages to employ filepath.Join. * failing to find html templates shouldn't segfault * fail fast if the test user cannot be created * rework utils.FindDir to retain backwards compatibility
159 строки
4.9 KiB
Go
159 строки
4.9 KiB
Go
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
|
// See License.txt for license information.
|
|
|
|
package utils
|
|
|
|
import (
|
|
"crypto"
|
|
"crypto/rsa"
|
|
"crypto/sha512"
|
|
"crypto/x509"
|
|
"encoding/base64"
|
|
"encoding/pem"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
|
|
l4g "github.com/alecthomas/log4go"
|
|
|
|
"github.com/mattermost/mattermost-server/model"
|
|
)
|
|
|
|
var publicKey []byte = []byte(`-----BEGIN PUBLIC KEY-----
|
|
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyZmShlU8Z8HdG0IWSZ8r
|
|
tSyzyxrXkJjsFUf0Ke7bm/TLtIggRdqOcUF3XEWqQk5RGD5vuq7Rlg1zZqMEBk8N
|
|
EZeRhkxyaZW8pLjxwuBUOnXfJew31+gsTNdKZzRjrvPumKr3EtkleuoxNdoatu4E
|
|
HrKmR/4Yi71EqAvkhk7ZjQFuF0osSWJMEEGGCSUYQnTEqUzcZSh1BhVpkIkeu8Kk
|
|
1wCtptODixvEujgqVe+SrE3UlZjBmPjC/CL+3cYmufpSNgcEJm2mwsdaXp2OPpfn
|
|
a0v85XL6i9ote2P+fLZ3wX9EoioHzgdgB7arOxY50QRJO7OyCqpKFKv6lRWTXuSt
|
|
hwIDAQAB
|
|
-----END PUBLIC KEY-----`)
|
|
|
|
func ValidateLicense(signed []byte) (bool, string) {
|
|
decoded := make([]byte, base64.StdEncoding.DecodedLen(len(signed)))
|
|
|
|
_, err := base64.StdEncoding.Decode(decoded, signed)
|
|
if err != nil {
|
|
l4g.Error(T("utils.license.validate_license.decode.error"), err.Error())
|
|
return false, ""
|
|
}
|
|
|
|
if len(decoded) <= 256 {
|
|
l4g.Error(T("utils.license.validate_license.not_long.error"))
|
|
return false, ""
|
|
}
|
|
|
|
// remove null terminator
|
|
for decoded[len(decoded)-1] == byte(0) {
|
|
decoded = decoded[:len(decoded)-1]
|
|
}
|
|
|
|
plaintext := decoded[:len(decoded)-256]
|
|
signature := decoded[len(decoded)-256:]
|
|
|
|
block, _ := pem.Decode(publicKey)
|
|
|
|
public, err := x509.ParsePKIXPublicKey(block.Bytes)
|
|
if err != nil {
|
|
l4g.Error(T("utils.license.validate_license.signing.error"), err.Error())
|
|
return false, ""
|
|
}
|
|
|
|
rsaPublic := public.(*rsa.PublicKey)
|
|
|
|
h := sha512.New()
|
|
h.Write(plaintext)
|
|
d := h.Sum(nil)
|
|
|
|
err = rsa.VerifyPKCS1v15(rsaPublic, crypto.SHA512, d, signature)
|
|
if err != nil {
|
|
l4g.Error(T("utils.license.validate_license.invalid.error"), err.Error())
|
|
return false, ""
|
|
}
|
|
|
|
return true, string(plaintext)
|
|
}
|
|
|
|
func GetAndValidateLicenseFileFromDisk(location string) (*model.License, []byte) {
|
|
fileName := GetLicenseFileLocation(location)
|
|
|
|
if _, err := os.Stat(fileName); err != nil {
|
|
l4g.Debug("We could not find the license key in the database or on disk at %v", fileName)
|
|
return nil, nil
|
|
}
|
|
|
|
l4g.Info("License key has not been uploaded. Loading license key from disk at %v", fileName)
|
|
licenseBytes := GetLicenseFileFromDisk(fileName)
|
|
|
|
if success, licenseStr := ValidateLicense(licenseBytes); !success {
|
|
l4g.Error("Found license key at %v but it appears to be invalid.", fileName)
|
|
return nil, nil
|
|
} else {
|
|
return model.LicenseFromJson(strings.NewReader(licenseStr)), licenseBytes
|
|
}
|
|
}
|
|
|
|
func GetLicenseFileFromDisk(fileName string) []byte {
|
|
file, err := os.Open(fileName)
|
|
if err != nil {
|
|
l4g.Error("Failed to open license key from disk at %v err=%v", fileName, err.Error())
|
|
return nil
|
|
}
|
|
defer file.Close()
|
|
|
|
licenseBytes, err := ioutil.ReadAll(file)
|
|
if err != nil {
|
|
l4g.Error("Failed to read license key from disk at %v err=%v", fileName, err.Error())
|
|
return nil
|
|
}
|
|
|
|
return licenseBytes
|
|
}
|
|
|
|
func GetLicenseFileLocation(fileLocation string) string {
|
|
if fileLocation == "" {
|
|
configDir, _ := FindDir("config")
|
|
return filepath.Join(configDir, "mattermost.mattermost-license")
|
|
} else {
|
|
return fileLocation
|
|
}
|
|
}
|
|
|
|
func GetClientLicense(l *model.License) map[string]string {
|
|
props := make(map[string]string)
|
|
|
|
props["IsLicensed"] = strconv.FormatBool(l != nil)
|
|
|
|
if l != nil {
|
|
props["Id"] = l.Id
|
|
props["Users"] = strconv.Itoa(*l.Features.Users)
|
|
props["LDAP"] = strconv.FormatBool(*l.Features.LDAP)
|
|
props["MFA"] = strconv.FormatBool(*l.Features.MFA)
|
|
props["SAML"] = strconv.FormatBool(*l.Features.SAML)
|
|
props["Cluster"] = strconv.FormatBool(*l.Features.Cluster)
|
|
props["Metrics"] = strconv.FormatBool(*l.Features.Metrics)
|
|
props["GoogleOAuth"] = strconv.FormatBool(*l.Features.GoogleOAuth)
|
|
props["Office365OAuth"] = strconv.FormatBool(*l.Features.Office365OAuth)
|
|
props["Compliance"] = strconv.FormatBool(*l.Features.Compliance)
|
|
props["CustomBrand"] = strconv.FormatBool(*l.Features.CustomBrand)
|
|
props["MHPNS"] = strconv.FormatBool(*l.Features.MHPNS)
|
|
props["PasswordRequirements"] = strconv.FormatBool(*l.Features.PasswordRequirements)
|
|
props["Announcement"] = strconv.FormatBool(*l.Features.Announcement)
|
|
props["Elasticsearch"] = strconv.FormatBool(*l.Features.Elasticsearch)
|
|
props["DataRetention"] = strconv.FormatBool(*l.Features.DataRetention)
|
|
props["IssuedAt"] = strconv.FormatInt(l.IssuedAt, 10)
|
|
props["StartsAt"] = strconv.FormatInt(l.StartsAt, 10)
|
|
props["ExpiresAt"] = strconv.FormatInt(l.ExpiresAt, 10)
|
|
props["Name"] = l.Customer.Name
|
|
props["Email"] = l.Customer.Email
|
|
props["Company"] = l.Customer.Company
|
|
props["PhoneNumber"] = l.Customer.PhoneNumber
|
|
props["EmailNotificationContents"] = strconv.FormatBool(*l.Features.EmailNotificationContents)
|
|
props["MessageExport"] = strconv.FormatBool(*l.Features.MessageExport)
|
|
}
|
|
|
|
return props
|
|
}
|