mattermost-community-enterp.../channels/app/config.go
Claude ec1f89217a Merge: Complete Mattermost Server with Community Enterprise
Full Mattermost server source with integrated Community Enterprise features.
Includes vendor directory for offline/air-gapped builds.

Structure:
- enterprise-impl/: Enterprise feature implementations
- enterprise-community/: Init files that register implementations
- enterprise/: Bridge imports (community_imports.go)
- vendor/: All dependencies for offline builds

Build (online):
  go build ./cmd/mattermost

Build (offline/air-gapped):
  go build -mod=vendor ./cmd/mattermost

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-17 23:59:07 +09:00

284 lines
8.9 KiB
Go

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package app
import (
"crypto/ecdsa"
"crypto/rand"
"encoding/json"
"net/url"
"reflect"
"strconv"
"time"
"github.com/pkg/errors"
"github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/public/shared/mlog"
"github.com/mattermost/mattermost/server/v8/channels/utils"
"github.com/mattermost/mattermost/server/v8/platform/shared/mail"
)
const (
ErrorTermsOfServiceNoRowsFound = "app.terms_of_service.get.no_rows.app_error"
)
func (s *Server) Config() *model.Config {
return s.platform.Config()
}
func (a *App) Config() *model.Config {
return a.ch.cfgSvc.Config()
}
func (a *App) EnvironmentConfig(filter func(reflect.StructField) bool) map[string]any {
return a.Srv().platform.GetEnvironmentOverridesWithFilter(filter)
}
func (a *App) UpdateConfig(f func(*model.Config)) {
a.Srv().platform.UpdateConfig(f)
}
func (a *App) IsConfigReadOnly() bool {
return a.Srv().platform.IsConfigReadOnly()
}
func (a *App) ReloadConfig() error {
return a.Srv().platform.ReloadConfig()
}
func (a *App) ClientConfig() map[string]string {
return a.ch.srv.platform.ClientConfig()
}
func (a *App) ClientConfigHash() string {
return a.ch.ClientConfigHash()
}
func (a *App) LimitedClientConfig() map[string]string {
return a.ch.srv.platform.LimitedClientConfig()
}
func (a *App) AddConfigListener(listener func(*model.Config, *model.Config)) string {
return a.Srv().platform.AddConfigListener(listener)
}
func (a *App) AddLicenseListener(listener func(oldLicense, newLicense *model.License)) string {
return a.Srv().platform.AddLicenseListener(listener)
}
// Removes a listener function by the unique ID returned when AddConfigListener was called
func (a *App) RemoveConfigListener(id string) {
a.Srv().platform.RemoveConfigListener(id)
}
// ensurePostActionCookieSecret ensures that the key for encrypting PostActionCookie exists
// and future calls to PostActionCookieSecret will always return a valid key, same on all
// servers in the cluster
func (ch *Channels) ensurePostActionCookieSecret() error {
if ch.postActionCookieSecret != nil {
return nil
}
var secret *model.SystemPostActionCookieSecret
value, err := ch.srv.Store().System().GetByName(model.SystemPostActionCookieSecretKey)
if err == nil {
if err := json.Unmarshal([]byte(value.Value), &secret); err != nil {
return err
}
}
// If we don't already have a key, try to generate one.
if secret == nil {
newSecret := &model.SystemPostActionCookieSecret{
Secret: make([]byte, 32),
}
_, err := rand.Reader.Read(newSecret.Secret)
if err != nil {
return err
}
system := &model.System{
Name: model.SystemPostActionCookieSecretKey,
}
v, err := json.Marshal(newSecret)
if err != nil {
return err
}
system.Value = string(v)
// If we were able to save the key, use it, otherwise log the error.
if err = ch.srv.Store().System().Save(system); err != nil {
mlog.Warn("Failed to save PostActionCookieSecret", mlog.Err(err))
} else {
secret = newSecret
}
}
// If we weren't able to save a new key above, another server must have beat us to it. Get the
// key from the database, and if that fails, error out.
if secret == nil {
value, err := ch.srv.Store().System().GetByName(model.SystemPostActionCookieSecretKey)
if err != nil {
return err
}
if err := json.Unmarshal([]byte(value.Value), &secret); err != nil {
return err
}
}
ch.postActionCookieSecret = secret.Secret
return nil
}
func (s *Server) ensureInstallationDate() error {
_, appErr := s.platform.GetSystemInstallDate()
if appErr == nil {
return nil
}
installDate, nErr := s.Store().User().InferSystemInstallDate()
var installationDate int64
if nErr == nil && installDate > 0 {
installationDate = installDate
} else {
installationDate = utils.MillisFromTime(time.Now())
}
if err := s.Store().System().SaveOrUpdate(&model.System{
Name: model.SystemInstallationDateKey,
Value: strconv.FormatInt(installationDate, 10),
}); err != nil {
return err
}
return nil
}
func (s *Server) ensureFirstServerRunTimestamp() error {
_, appErr := s.getFirstServerRunTimestamp()
if appErr == nil {
return nil
}
if err := s.Store().System().SaveOrUpdate(&model.System{
Name: model.SystemFirstServerRunTimestampKey,
Value: strconv.FormatInt(utils.MillisFromTime(time.Now()), 10),
}); err != nil {
return err
}
return nil
}
// AsymmetricSigningKey will return a private key that can be used for asymmetric signing.
func (ch *Channels) AsymmetricSigningKey() *ecdsa.PrivateKey {
return ch.srv.platform.AsymmetricSigningKey()
}
func (a *App) AsymmetricSigningKey() *ecdsa.PrivateKey {
return a.ch.AsymmetricSigningKey()
}
func (ch *Channels) PostActionCookieSecret() []byte {
return ch.postActionCookieSecret
}
func (a *App) PostActionCookieSecret() []byte {
return a.ch.PostActionCookieSecret()
}
func (a *App) GetCookieDomain() string {
if *a.Config().ServiceSettings.AllowCookiesForSubdomains {
if siteURL, err := url.Parse(*a.Config().ServiceSettings.SiteURL); err == nil {
return siteURL.Hostname()
}
}
return ""
}
func (a *App) GetSiteURL() string {
return *a.Config().ServiceSettings.SiteURL
}
// GetConfigFile proxies access to the given configuration file to the underlying config store.
func (a *App) GetConfigFile(name string) ([]byte, error) {
data, err := a.Srv().platform.GetConfigFile(name)
if err != nil {
return nil, errors.Wrapf(err, "failed to get config file %s", name)
}
return data, nil
}
// GetSanitizedConfig gets the configuration for a system admin without any secrets.
func (a *App) GetSanitizedConfig() *model.Config {
cfg := a.Config().Clone()
a.SanitizedConfig(cfg)
return cfg
}
// SanitizedConfig sanitizes a given configuration for a system admin without any secrets.
func (a *App) SanitizedConfig(cfg *model.Config) {
manifests, err := a.getPluginManifests()
if err != nil {
// GetPluginManifests might error, e.g. when plugins are disabled.
// Sanitize all plugin settings in this case.
cfg.Sanitize(nil, nil)
return
}
cfg.Sanitize(manifests, nil)
}
// GetEnvironmentConfig returns a map of configuration keys whose values have been overridden by an environment variable.
// If filter is not nil and returns false for a struct field, that field will be omitted.
func (a *App) GetEnvironmentConfig(filter func(reflect.StructField) bool) map[string]any {
return a.EnvironmentConfig(filter)
}
// SaveConfig replaces the active configuration, optionally notifying cluster peers.
func (a *App) SaveConfig(newCfg *model.Config, sendConfigChangeClusterMessage bool) (*model.Config, *model.Config, *model.AppError) {
return a.Srv().platform.SaveConfig(newCfg, sendConfigChangeClusterMessage)
}
func (a *App) HandleMessageExportConfig(cfg *model.Config, appCfg *model.Config) {
// If the Message Export feature has been toggled in the System Console, rewrite the ExportFromTimestamp field to an
// appropriate value. The rewriting occurs here to ensure it doesn't affect values written to the config file
// directly and not through the System Console UI.
if *cfg.MessageExportSettings.EnableExport != *appCfg.MessageExportSettings.EnableExport {
if *cfg.MessageExportSettings.EnableExport && model.SafeDereference(cfg.MessageExportSettings.ExportFromTimestamp) == int64(0) {
// When the feature is toggled on, use the current timestamp as the start time for future exports.
cfg.MessageExportSettings.ExportFromTimestamp = model.NewPointer(model.GetMillis())
} else if !*cfg.MessageExportSettings.EnableExport {
// When the feature is disabled, reset the timestamp so that the timestamp will be set if
// the feature is re-enabled from the System Console in future.
cfg.MessageExportSettings.ExportFromTimestamp = model.NewPointer(int64(0))
}
}
}
func (s *Server) MailServiceConfig() *mail.SMTPConfig {
emailSettings := s.platform.Config().EmailSettings
hostname := utils.GetHostnameFromSiteURL(*s.platform.Config().ServiceSettings.SiteURL)
cfg := mail.SMTPConfig{
Hostname: hostname,
ConnectionSecurity: *emailSettings.ConnectionSecurity,
SkipServerCertificateVerification: *emailSettings.SkipServerCertificateVerification,
ServerName: *emailSettings.SMTPServer,
Server: *emailSettings.SMTPServer,
Port: *emailSettings.SMTPPort,
ServerTimeout: *emailSettings.SMTPServerTimeout,
Username: *emailSettings.SMTPUsername,
Password: *emailSettings.SMTPPassword,
EnableSMTPAuth: *emailSettings.EnableSMTPAuth,
SendEmailNotifications: *emailSettings.SendEmailNotifications,
FeedbackName: *emailSettings.FeedbackName,
FeedbackEmail: *emailSettings.FeedbackEmail,
ReplyToAddress: *emailSettings.ReplyToAddress,
}
return &cfg
}