mirror of
https://github.com/hanzoai/team.git
synced 2026-09-30 01:57:35 +00:00
Improve license error message for wrong service environment (#37198)
* [MM-64634] Improve license error for wrong service environment When a cryptographically valid license is uploaded to a server running a different service environment (e.g. a production license on a test/dev server, or vice versa), ValidateLicense now retries verification against the alternate environment's public key. On success it returns a distinct error that callers surface as the new WrongEnvironmentLicenseError, giving admins an actionable message instead of the generic "Invalid license file." Genuinely corrupt licenses still return the existing InvalidLicenseError. Co-authored-by: mattermost-code <[email protected]> * [MM-64634] Add tests for wrong-environment license error mapping Cover SaveLicense (the System Console upload entry point) returning WrongEnvironmentLicenseError for a license signed for another service environment, and add a direct unit test for NewLicenseValidationAppError. Co-authored-by: mattermost-code <[email protected]> * Add audit logging for wrong-environment license upload failures Handle WrongEnvironmentLicenseError in addLicense and localAddLicense so environment mismatches are logged distinctly instead of falling through to the generic unable-to-save message. Co-authored-by: mattermost-code <[email protected]> * Log wrong-environment audit in LicenseFromBytes error path addLicense validates the license before SaveLicense, so WrongEnvironmentLicenseError is returned from LicenseFromBytes and never reaches the SaveLicense audit handler. Co-authored-by: mattermost-code <[email protected]> * Address PR feedback: remove license validation audit logging from addLicense Per review feedback, the per-error-id audit logging added to the LicenseFromBytes failure path in addLicense doesn't belong in the audit logs. Remove it to clean up the diff; the AppError is still returned to the caller unchanged. Resolves: 1 change request * Remove wrong-environment license audit logging Per review feedback, the wrong-environment license classification does not belong in the audit logs. Revert the audit logging additions in both the cloud and local license upload handlers. * Split wrong-environment license error by direction Per review feedback, distinguish between a production license uploaded to a test/dev server and a test/dev license uploaded to a production server. Each case now maps to its own AppError ID and user-facing message instead of a single generic wrong-environment error. * Restrict wrong-environment license fallback to signature failures Only treat alternate-key verification success as a wrong-environment mismatch when the primary key failed with rsa.ErrVerification. Other primary-key failures (e.g. malformed key material) are now surfaced unchanged instead of being masked as a wrong-environment license, and non-verification errors from the alternate-key check are no longer discarded. * Regenerate store layers and mocks for SessionStore --------- Co-authored-by: mattermost-code <[email protected]> Co-authored-by: Mattermost Build <[email protected]> Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
committed by
GitHub
co-authored by
Hanzo Dev
parent
d908576206
commit
b1799e1882
@@ -132,7 +132,7 @@ func (ps *PlatformService) LoadLicense() {
|
||||
func (ps *PlatformService) SaveLicense(licenseBytes []byte) (*model.License, *model.AppError) {
|
||||
licenseStr, err := utils.LicenseValidator.ValidateLicense(licenseBytes)
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("addLicense", model.InvalidLicenseError, nil, "", http.StatusBadRequest).Wrap(err)
|
||||
return nil, utils.NewLicenseValidationAppError("addLicense", err)
|
||||
}
|
||||
|
||||
var license model.License
|
||||
|
||||
@@ -5,6 +5,7 @@ package platform
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -35,6 +36,21 @@ func TestSaveLicense(t *testing.T) {
|
||||
require.NotNil(t, err, "shouldn't have saved license")
|
||||
}
|
||||
|
||||
func TestSaveLicenseWrongEnvironment(t *testing.T) {
|
||||
// t.Setenv prevents t.Parallel — the service environment has no config equivalent.
|
||||
th := Setup(t)
|
||||
|
||||
t.Setenv("MM_SERVICEENVIRONMENT", model.ServiceEnvironmentProduction)
|
||||
|
||||
// validTestLicense is signed with the test key, so uploading it to a server running
|
||||
// in the production service environment must report an environment mismatch rather
|
||||
// than the generic invalid-license error.
|
||||
_, appErr := th.Service.SaveLicense(validTestLicense)
|
||||
require.NotNil(t, appErr, "shouldn't have saved a license signed for another environment")
|
||||
require.Equal(t, model.WrongEnvironmentTestLicenseError, appErr.Id)
|
||||
require.Equal(t, http.StatusBadRequest, appErr.StatusCode)
|
||||
}
|
||||
|
||||
func TestSaveEnterpriseAdvancedLicense(t *testing.T) {
|
||||
th := Setup(t)
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"maps"
|
||||
@@ -24,6 +25,16 @@ import (
|
||||
"github.com/mattermost/mattermost/server/v8/channels/utils/fileutils"
|
||||
)
|
||||
|
||||
// ErrLicenseProductionInTestEnvironment indicates the license signature is valid against
|
||||
// the production key, but this server is running in a test or development service
|
||||
// environment (i.e. a production license uploaded to a test/dev server).
|
||||
var ErrLicenseProductionInTestEnvironment = errors.New("license is a production license but the server is running in a test or development service environment")
|
||||
|
||||
// ErrLicenseTestInProductionEnvironment indicates the license signature is valid against
|
||||
// the test key, but this server is running in a production service environment (i.e. a
|
||||
// test/dev license uploaded to a production server).
|
||||
var ErrLicenseTestInProductionEnvironment = errors.New("license is a test or development license but the server is running in a production service environment")
|
||||
|
||||
var LicenseValidator LicenseValidatorIface
|
||||
|
||||
func init() {
|
||||
@@ -43,7 +54,7 @@ type LicenseValidatorImpl struct {
|
||||
func (l *LicenseValidatorImpl) LicenseFromBytes(licenseBytes []byte) (*model.License, *model.AppError) {
|
||||
licenseStr, err := l.ValidateLicense(licenseBytes)
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("LicenseFromBytes", model.InvalidLicenseError, nil, "", http.StatusBadRequest).Wrap(err)
|
||||
return nil, NewLicenseValidationAppError("LicenseFromBytes", err)
|
||||
}
|
||||
|
||||
var license model.License
|
||||
@@ -74,37 +85,92 @@ func (l *LicenseValidatorImpl) ValidateLicense(signed []byte) (string, error) {
|
||||
plaintext := decoded[:len(decoded)-256]
|
||||
signature := decoded[len(decoded)-256:]
|
||||
|
||||
var publicKey []byte
|
||||
switch model.GetServiceEnvironment() {
|
||||
case model.ServiceEnvironmentProduction:
|
||||
publicKey = productionPublicKey
|
||||
case model.ServiceEnvironmentTest, model.ServiceEnvironmentDev:
|
||||
publicKey = testPublicKey
|
||||
}
|
||||
block, _ := pem.Decode(publicKey)
|
||||
if block == nil {
|
||||
return "", fmt.Errorf("failed to decode public key PEM block for environment %q", model.GetServiceEnvironment())
|
||||
}
|
||||
|
||||
public, err := x509.ParsePKIXPublicKey(block.Bytes)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Encountered error signing license: %w", err)
|
||||
}
|
||||
|
||||
rsaPublic := public.(*rsa.PublicKey)
|
||||
primaryKey, alternateKey := licenseKeysForEnvironment(model.GetServiceEnvironment())
|
||||
|
||||
h := sha512.New()
|
||||
h.Write(plaintext)
|
||||
d := h.Sum(nil)
|
||||
|
||||
err = rsa.VerifyPKCS1v15(rsaPublic, crypto.SHA512, d, signature)
|
||||
if err != nil {
|
||||
if err := verifyLicenseSignature(primaryKey, d, signature); err != nil {
|
||||
// Only a genuine signature mismatch should trigger the wrong-environment
|
||||
// fallback. Any other failure (e.g. malformed key material) means the
|
||||
// current environment's key is broken, so surface it unchanged.
|
||||
if !errors.Is(err, rsa.ErrVerification) {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// The license did not verify against this environment's key. If it verifies
|
||||
// against the other environment's key, the license is genuine but was signed
|
||||
// for a different service environment, so report which way the mismatch goes.
|
||||
if altErr := verifyLicenseSignature(alternateKey, d, signature); altErr == nil {
|
||||
return "", wrongEnvironmentError(model.GetServiceEnvironment())
|
||||
} else if !errors.Is(altErr, rsa.ErrVerification) {
|
||||
return "", altErr
|
||||
}
|
||||
return "", fmt.Errorf("Invalid signature: %w", err)
|
||||
}
|
||||
|
||||
return string(plaintext), nil
|
||||
}
|
||||
|
||||
// wrongEnvironmentError returns the sentinel describing a license that is genuine but
|
||||
// signed for the opposite service environment from the one this server runs in.
|
||||
func wrongEnvironmentError(environment string) error {
|
||||
if environment == model.ServiceEnvironmentProduction {
|
||||
// Running production, but the license verified against the test key.
|
||||
return ErrLicenseTestInProductionEnvironment
|
||||
}
|
||||
// Running test/dev, but the license verified against the production key.
|
||||
return ErrLicenseProductionInTestEnvironment
|
||||
}
|
||||
|
||||
// licenseKeysForEnvironment returns the public key for the given service environment
|
||||
// (primary) along with the public key for the other environment (alternate).
|
||||
func licenseKeysForEnvironment(environment string) (primary, alternate []byte) {
|
||||
switch environment {
|
||||
case model.ServiceEnvironmentProduction:
|
||||
return productionPublicKey, testPublicKey
|
||||
case model.ServiceEnvironmentTest, model.ServiceEnvironmentDev:
|
||||
return testPublicKey, productionPublicKey
|
||||
default:
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
// verifyLicenseSignature verifies the RSA PKCS1v15 signature of a license digest against
|
||||
// the provided PEM-encoded public key.
|
||||
func verifyLicenseSignature(publicKey, digest, signature []byte) error {
|
||||
block, _ := pem.Decode(publicKey)
|
||||
if block == nil {
|
||||
return fmt.Errorf("failed to decode public key PEM block")
|
||||
}
|
||||
|
||||
public, err := x509.ParsePKIXPublicKey(block.Bytes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("encountered error parsing public key: %w", err)
|
||||
}
|
||||
|
||||
rsaPublic, ok := public.(*rsa.PublicKey)
|
||||
if !ok {
|
||||
return fmt.Errorf("public key is not an RSA key")
|
||||
}
|
||||
|
||||
return rsa.VerifyPKCS1v15(rsaPublic, crypto.SHA512, digest, signature)
|
||||
}
|
||||
|
||||
// NewLicenseValidationAppError converts a license validation error returned by
|
||||
// ValidateLicense into a user-facing AppError, distinguishing a license signed for the
|
||||
// wrong service environment from a generally invalid license.
|
||||
func NewLicenseValidationAppError(where string, err error) *model.AppError {
|
||||
switch {
|
||||
case errors.Is(err, ErrLicenseProductionInTestEnvironment):
|
||||
return model.NewAppError(where, model.WrongEnvironmentProductionLicenseError, nil, "", http.StatusBadRequest).Wrap(err)
|
||||
case errors.Is(err, ErrLicenseTestInProductionEnvironment):
|
||||
return model.NewAppError(where, model.WrongEnvironmentTestLicenseError, nil, "", http.StatusBadRequest).Wrap(err)
|
||||
}
|
||||
return model.NewAppError(where, model.InvalidLicenseError, nil, "", http.StatusBadRequest).Wrap(err)
|
||||
}
|
||||
|
||||
func GetAndValidateLicenseFileFromDisk(location string) (*model.License, []byte, error) {
|
||||
fileName := GetLicenseFileLocation(location)
|
||||
|
||||
|
||||
@@ -5,7 +5,15 @@ package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha512"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
@@ -90,6 +98,7 @@ func TestValidateLicense(t *testing.T) {
|
||||
|
||||
str, err := LicenseValidator.ValidateLicense(validTestLicense)
|
||||
require.Error(t, err)
|
||||
require.ErrorIs(t, err, ErrLicenseTestInProductionEnvironment)
|
||||
require.Empty(t, str)
|
||||
})
|
||||
|
||||
@@ -109,6 +118,131 @@ func TestValidateLicense(t *testing.T) {
|
||||
require.Empty(t, str)
|
||||
require.Contains(t, err.Error(), "failed to decode public key PEM block")
|
||||
})
|
||||
|
||||
t.Run("broken primary key is surfaced and not misreported as a wrong-environment license", func(t *testing.T) {
|
||||
// t.Setenv prevents t.Parallel — env var has no config equivalent
|
||||
t.Setenv("MM_SERVICEENVIRONMENT", model.ServiceEnvironmentTest)
|
||||
|
||||
// Corrupt the primary (test) key so its verification fails for a non-signature
|
||||
// reason, while standing in a generated key as the alternate (production) key
|
||||
// and signing a license with it. The broken primary key must be surfaced
|
||||
// rather than the license being misreported as wrong-environment.
|
||||
priv, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
require.NoError(t, err)
|
||||
|
||||
originalTestKey := testPublicKey
|
||||
originalProductionKey := productionPublicKey
|
||||
defer func() {
|
||||
testPublicKey = originalTestKey
|
||||
productionPublicKey = originalProductionKey
|
||||
}()
|
||||
testPublicKey = []byte("not a valid PEM block")
|
||||
productionPublicKey = marshalPublicKeyPEM(t, &priv.PublicKey)
|
||||
|
||||
signed := signLicense(t, priv, []byte(`{"id":"emulated-production-license"}`))
|
||||
|
||||
str, err := LicenseValidator.ValidateLicense(signed)
|
||||
require.Error(t, err)
|
||||
require.Empty(t, str)
|
||||
require.NotErrorIs(t, err, ErrLicenseProductionInTestEnvironment)
|
||||
require.NotErrorIs(t, err, ErrLicenseTestInProductionEnvironment)
|
||||
require.Contains(t, err.Error(), "failed to decode public key PEM block")
|
||||
})
|
||||
}
|
||||
|
||||
func TestLicenseFromBytesEnvironmentMismatch(t *testing.T) {
|
||||
t.Run("test license uploaded to a production server returns the wrong-environment error", func(t *testing.T) {
|
||||
t.Setenv("MM_SERVICEENVIRONMENT", model.ServiceEnvironmentProduction)
|
||||
|
||||
license, appErr := LicenseValidator.LicenseFromBytes(validTestLicense)
|
||||
require.Nil(t, license)
|
||||
require.NotNil(t, appErr)
|
||||
require.Equal(t, model.WrongEnvironmentTestLicenseError, appErr.Id)
|
||||
require.Equal(t, http.StatusBadRequest, appErr.StatusCode)
|
||||
})
|
||||
|
||||
t.Run("production license uploaded to a test/dev server returns the wrong-environment error", func(t *testing.T) {
|
||||
t.Setenv("MM_SERVICEENVIRONMENT", model.ServiceEnvironmentTest)
|
||||
|
||||
// We cannot sign with the real production key, so stand in a generated key as
|
||||
// the production key and sign a license with it to emulate a production license
|
||||
// arriving at a test server.
|
||||
priv, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
require.NoError(t, err)
|
||||
|
||||
originalProductionKey := productionPublicKey
|
||||
defer func() { productionPublicKey = originalProductionKey }()
|
||||
productionPublicKey = marshalPublicKeyPEM(t, &priv.PublicKey)
|
||||
|
||||
signed := signLicense(t, priv, []byte(`{"id":"emulated-production-license"}`))
|
||||
|
||||
license, appErr := LicenseValidator.LicenseFromBytes(signed)
|
||||
require.Nil(t, license)
|
||||
require.NotNil(t, appErr)
|
||||
require.Equal(t, model.WrongEnvironmentProductionLicenseError, appErr.Id)
|
||||
require.Equal(t, http.StatusBadRequest, appErr.StatusCode)
|
||||
})
|
||||
|
||||
t.Run("genuinely corrupt license returns the generic invalid error", func(t *testing.T) {
|
||||
t.Setenv("MM_SERVICEENVIRONMENT", model.ServiceEnvironmentTest)
|
||||
|
||||
// A blob long enough to pass the length check but signed by no known key.
|
||||
priv, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
require.NoError(t, err)
|
||||
corrupt := signLicense(t, priv, []byte(`{"id":"corrupt-license"}`))
|
||||
|
||||
license, appErr := LicenseValidator.LicenseFromBytes(corrupt)
|
||||
require.Nil(t, license)
|
||||
require.NotNil(t, appErr)
|
||||
require.Equal(t, model.InvalidLicenseError, appErr.Id)
|
||||
require.Equal(t, http.StatusBadRequest, appErr.StatusCode)
|
||||
})
|
||||
}
|
||||
|
||||
func TestNewLicenseValidationAppError(t *testing.T) {
|
||||
t.Run("maps the production-in-test sentinel to WrongEnvironmentProductionLicenseError", func(t *testing.T) {
|
||||
appErr := NewLicenseValidationAppError("Test", fmt.Errorf("wrap: %w", ErrLicenseProductionInTestEnvironment))
|
||||
require.Equal(t, model.WrongEnvironmentProductionLicenseError, appErr.Id)
|
||||
require.Equal(t, http.StatusBadRequest, appErr.StatusCode)
|
||||
require.ErrorIs(t, appErr, ErrLicenseProductionInTestEnvironment)
|
||||
})
|
||||
|
||||
t.Run("maps the test-in-production sentinel to WrongEnvironmentTestLicenseError", func(t *testing.T) {
|
||||
appErr := NewLicenseValidationAppError("Test", fmt.Errorf("wrap: %w", ErrLicenseTestInProductionEnvironment))
|
||||
require.Equal(t, model.WrongEnvironmentTestLicenseError, appErr.Id)
|
||||
require.Equal(t, http.StatusBadRequest, appErr.StatusCode)
|
||||
require.ErrorIs(t, appErr, ErrLicenseTestInProductionEnvironment)
|
||||
})
|
||||
|
||||
t.Run("maps any other error to InvalidLicenseError", func(t *testing.T) {
|
||||
appErr := NewLicenseValidationAppError("Test", fmt.Errorf("Invalid signature"))
|
||||
require.Equal(t, model.InvalidLicenseError, appErr.Id)
|
||||
require.Equal(t, http.StatusBadRequest, appErr.StatusCode)
|
||||
require.NotErrorIs(t, appErr, ErrLicenseProductionInTestEnvironment)
|
||||
require.NotErrorIs(t, appErr, ErrLicenseTestInProductionEnvironment)
|
||||
})
|
||||
}
|
||||
|
||||
func signLicense(t *testing.T, priv *rsa.PrivateKey, payload []byte) []byte {
|
||||
t.Helper()
|
||||
|
||||
h := sha512.New()
|
||||
h.Write(payload)
|
||||
digest := h.Sum(nil)
|
||||
|
||||
signature, err := rsa.SignPKCS1v15(rand.Reader, priv, crypto.SHA512, digest)
|
||||
require.NoError(t, err)
|
||||
|
||||
raw := append(append([]byte{}, payload...), signature...)
|
||||
return []byte(base64.StdEncoding.EncodeToString(raw))
|
||||
}
|
||||
|
||||
func marshalPublicKeyPEM(t *testing.T, pub *rsa.PublicKey) []byte {
|
||||
t.Helper()
|
||||
|
||||
der, err := x509.MarshalPKIXPublicKey(pub)
|
||||
require.NoError(t, err)
|
||||
return pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: der})
|
||||
}
|
||||
|
||||
func TestGetLicenseFileLocation(t *testing.T) {
|
||||
|
||||
@@ -2680,6 +2680,14 @@
|
||||
"id": "api.license.add_license.unique_users.app_error",
|
||||
"translation": "This license only supports {{.Users}} users, when your system has {{.Count}} unique users. Unique users are counted distinctly by email address. You can see total user count under Site Reports -> View Statistics."
|
||||
},
|
||||
{
|
||||
"id": "api.license.add_license.wrong_environment_production.app_error",
|
||||
"translation": "This is a production license, but this server is running in a test or development environment. Production licenses can only be used on production servers."
|
||||
},
|
||||
{
|
||||
"id": "api.license.add_license.wrong_environment_test.app_error",
|
||||
"translation": "This is a test or development license, but this server is running in a production environment. Test or development licenses can only be used on test or development servers."
|
||||
},
|
||||
{
|
||||
"id": "api.license.client.old_format.app_error",
|
||||
"translation": "New format for the client license is not supported yet. Please specify format=old in the query string."
|
||||
|
||||
@@ -16,8 +16,14 @@ const (
|
||||
|
||||
ExpiredLicenseError = "api.license.add_license.expired.app_error"
|
||||
InvalidLicenseError = "api.license.add_license.invalid.app_error"
|
||||
LicenseGracePeriod = DayInMilliseconds * 10 //10 days
|
||||
LicenseRenewalLink = "https://mattermost.com/renew/"
|
||||
// WrongEnvironmentProductionLicenseError is returned when a production license is
|
||||
// uploaded to a server running in a test or development service environment.
|
||||
WrongEnvironmentProductionLicenseError = "api.license.add_license.wrong_environment_production.app_error"
|
||||
// WrongEnvironmentTestLicenseError is returned when a test or development license is
|
||||
// uploaded to a server running in a production service environment.
|
||||
WrongEnvironmentTestLicenseError = "api.license.add_license.wrong_environment_test.app_error"
|
||||
LicenseGracePeriod = DayInMilliseconds * 10 //10 days
|
||||
LicenseRenewalLink = "https://mattermost.com/renew/"
|
||||
|
||||
LicenseShortSkuE10 = "E10"
|
||||
LicenseShortSkuE20 = "E20"
|
||||
|
||||
@@ -467,26 +467,28 @@ func extractByFuncName(name string, args []ast.Expr) *string {
|
||||
|
||||
func extractForConstants(name string, valueNode ast.Expr) *string {
|
||||
validConstants := map[string]bool{
|
||||
"MISSING_CHANNEL_ERROR": true,
|
||||
"MISSING_CHANNEL_MEMBER_ERROR": true,
|
||||
"CHANNEL_EXISTS_ERROR": true,
|
||||
"MISSING_STATUS_ERROR": true,
|
||||
"TEAM_MEMBER_EXISTS_ERROR": true,
|
||||
"MISSING_AUTH_ACCOUNT_ERROR": true,
|
||||
"MISSING_ACCOUNT_ERROR": true,
|
||||
"EXPIRED_LICENSE_ERROR": true,
|
||||
"INVALID_LICENSE_ERROR": true,
|
||||
"MissingChannelError": true,
|
||||
"MissingChannelMemberError": true,
|
||||
"ChannelExistsError": true,
|
||||
"MissingStatusError": true,
|
||||
"TeamMemberExistsError": true,
|
||||
"MissingAuthAccountError": true,
|
||||
"MissingAccountError": true,
|
||||
"ExpiredLicenseError": true,
|
||||
"InvalidLicenseError": true,
|
||||
"NoTranslation": true,
|
||||
"PayloadParseError": true,
|
||||
"MISSING_CHANNEL_ERROR": true,
|
||||
"MISSING_CHANNEL_MEMBER_ERROR": true,
|
||||
"CHANNEL_EXISTS_ERROR": true,
|
||||
"MISSING_STATUS_ERROR": true,
|
||||
"TEAM_MEMBER_EXISTS_ERROR": true,
|
||||
"MISSING_AUTH_ACCOUNT_ERROR": true,
|
||||
"MISSING_ACCOUNT_ERROR": true,
|
||||
"EXPIRED_LICENSE_ERROR": true,
|
||||
"INVALID_LICENSE_ERROR": true,
|
||||
"MissingChannelError": true,
|
||||
"MissingChannelMemberError": true,
|
||||
"ChannelExistsError": true,
|
||||
"MissingStatusError": true,
|
||||
"TeamMemberExistsError": true,
|
||||
"MissingAuthAccountError": true,
|
||||
"MissingAccountError": true,
|
||||
"ExpiredLicenseError": true,
|
||||
"InvalidLicenseError": true,
|
||||
"WrongEnvironmentProductionLicenseError": true,
|
||||
"WrongEnvironmentTestLicenseError": true,
|
||||
"NoTranslation": true,
|
||||
"PayloadParseError": true,
|
||||
}
|
||||
|
||||
if _, ok := validConstants[name]; !ok {
|
||||
|
||||
Reference in New Issue
Block a user