diff --git a/api4/config.go b/api4/config.go index 52a0df9e12..38d068e0d0 100644 --- a/api4/config.go +++ b/api4/config.go @@ -65,7 +65,11 @@ func getConfig(c *Context, w http.ResponseWriter, r *http.Request) { auditRec.Success() w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate") - w.Write([]byte(cfg.ToJson())) + if c.App.Srv().License() != nil && *c.App.Srv().License().Features.Cloud && *cfg.ExperimentalSettings.RestrictSystemAdmin { + w.Write([]byte(cfg.ToJsonFiltered(model.ConfigAccessTagType, model.ConfigAccessTagCloudRestrictable))) + } else { + w.Write([]byte(cfg.ToJson())) + } } func configReload(c *Context, w http.ResponseWriter, r *http.Request) { @@ -156,7 +160,11 @@ func updateConfig(c *Context, w http.ResponseWriter, r *http.Request) { c.LogAudit("updateConfig") w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate") - w.Write([]byte(cfg.ToJson())) + if c.App.Srv().License() != nil && *c.App.Srv().License().Features.Cloud && *cfg.ExperimentalSettings.RestrictSystemAdmin { + w.Write([]byte(cfg.ToJsonFiltered(model.ConfigAccessTagType, model.ConfigAccessTagCloudRestrictable))) + } else { + w.Write([]byte(cfg.ToJson())) + } } func getClientConfig(c *Context, w http.ResponseWriter, r *http.Request) { @@ -259,7 +267,11 @@ func patchConfig(c *Context, w http.ResponseWriter, r *http.Request) { } w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate") - w.Write([]byte(cfg.ToJson())) + if c.App.Srv().License() != nil && *c.App.Srv().License().Features.Cloud && *cfg.ExperimentalSettings.RestrictSystemAdmin { + w.Write([]byte(cfg.ToJsonFiltered(model.ConfigAccessTagType, model.ConfigAccessTagCloudRestrictable))) + } else { + w.Write([]byte(cfg.ToJson())) + } } func makeFilterConfigByPermission(accessType filterType) func(c *Context, structField reflect.StructField) bool { @@ -285,7 +297,7 @@ func makeFilterConfigByPermission(accessType filterType) func(c *Context, struct continue } // ConfigAccessTagWriteRestrictable trumps all other permissions - if tagValue == model.ConfigAccessTagWriteRestrictable { + if tagValue == model.ConfigAccessTagWriteRestrictable || tagValue == model.ConfigAccessTagCloudRestrictable { if *c.App.Config().ExperimentalSettings.RestrictSystemAdmin && accessType == filterTypeWrite { return false } @@ -302,6 +314,9 @@ func makeFilterConfigByPermission(accessType filterType) func(c *Context, struct if tagValue == model.ConfigAccessTagWriteRestrictable { continue } + if tagValue == model.ConfigAccessTagCloudRestrictable { + continue + } permissionID := fmt.Sprintf("sysconsole_%s_%s", accessType, tagValue) if permission, ok := permissionMap[permissionID]; ok { if c.App.SessionHasPermissionTo(*c.App.Session(), permission) { diff --git a/model/config.go b/model/config.go index 69b31eb8a6..ea578e5910 100644 --- a/model/config.go +++ b/model/config.go @@ -12,12 +12,14 @@ import ( "net/http" "net/url" "os" + "reflect" "regexp" "strconv" "strings" "time" "github.com/mattermost/ldap" + "github.com/mattermost/mattermost-server/v5/mlog" ) const ( @@ -252,26 +254,26 @@ var ServerTLSSupportedCiphers = map[string]uint16{ type ServiceSettings struct { SiteURL *string `access:"environment,authentication,write_restrictable"` - WebsocketURL *string `access:"write_restrictable"` - LicenseFileLocation *string `access:"write_restrictable"` - ListenAddress *string `access:"environment,write_restrictable"` - ConnectionSecurity *string `access:"environment,write_restrictable"` - TLSCertFile *string `access:"environment,write_restrictable"` - TLSKeyFile *string `access:"environment,write_restrictable"` - TLSMinVer *string `access:"write_restrictable"` - TLSStrictTransport *bool `access:"write_restrictable"` - TLSStrictTransportMaxAge *int64 `access:"write_restrictable"` - TLSOverwriteCiphers []string `access:"write_restrictable"` - UseLetsEncrypt *bool `access:"environment,write_restrictable"` - LetsEncryptCertificateCacheFile *string `access:"environment,write_restrictable"` - Forward80To443 *bool `access:"environment,write_restrictable"` - TrustedProxyIPHeader []string `access:"write_restrictable"` - ReadTimeout *int `access:"environment,write_restrictable"` - WriteTimeout *int `access:"environment,write_restrictable"` - IdleTimeout *int `access:"write_restrictable"` - MaximumLoginAttempts *int `access:"authentication,write_restrictable"` - GoroutineHealthThreshold *int `access:"write_restrictable"` - GoogleDeveloperKey *string `access:"site,write_restrictable"` + WebsocketURL *string `access:"write_restrictable,cloud_restrictable"` + LicenseFileLocation *string `access:"write_restrictable,cloud_restrictable"` + ListenAddress *string `access:"environment,write_restrictable,cloud_restrictable"` + ConnectionSecurity *string `access:"environment,write_restrictable,cloud_restrictable"` + TLSCertFile *string `access:"environment,write_restrictable,cloud_restrictable"` + TLSKeyFile *string `access:"environment,write_restrictable,cloud_restrictable"` + TLSMinVer *string `access:"write_restrictable,cloud_restrictable"` + TLSStrictTransport *bool `access:"write_restrictable,cloud_restrictable"` + TLSStrictTransportMaxAge *int64 `access:"write_restrictable,cloud_restrictable"` + TLSOverwriteCiphers []string `access:"write_restrictable,cloud_restrictable"` + UseLetsEncrypt *bool `access:"environment,write_restrictable,cloud_restrictable"` + LetsEncryptCertificateCacheFile *string `access:"environment,write_restrictable,cloud_restrictable"` + Forward80To443 *bool `access:"environment,write_restrictable,cloud_restrictable"` + TrustedProxyIPHeader []string `access:"write_restrictable,cloud_restrictable"` + ReadTimeout *int `access:"environment,write_restrictable,cloud_restrictable"` + WriteTimeout *int `access:"environment,write_restrictable,cloud_restrictable"` + IdleTimeout *int `access:"write_restrictable,cloud_restrictable"` + MaximumLoginAttempts *int `access:"authentication,write_restrictable,cloud_restrictable"` + GoroutineHealthThreshold *int `access:"write_restrictable,cloud_restrictable"` + GoogleDeveloperKey *string `access:"site,write_restrictable,cloud_restrictable"` EnableOAuthServiceProvider *bool `access:"integrations"` EnableIncomingWebhooks *bool `access:"integrations"` EnableOutgoingWebhooks *bool `access:"integrations"` @@ -280,29 +282,29 @@ type ServiceSettings struct { EnablePostUsernameOverride *bool `access:"integrations"` EnablePostIconOverride *bool `access:"integrations"` EnableLinkPreviews *bool `access:"site"` - EnableTesting *bool `access:"environment,write_restrictable"` - EnableDeveloper *bool `access:"environment,write_restrictable"` - EnableOpenTracing *bool `access:"write_restrictable"` - EnableSecurityFixAlert *bool `access:"environment,write_restrictable"` - EnableInsecureOutgoingConnections *bool `access:"environment,write_restrictable"` - AllowedUntrustedInternalConnections *string `access:"environment,write_restrictable"` + EnableTesting *bool `access:"environment,write_restrictable,cloud_restrictable"` + EnableDeveloper *bool `access:"environment,write_restrictable,cloud_restrictable"` + EnableOpenTracing *bool `access:"write_restrictable,cloud_restrictable"` + EnableSecurityFixAlert *bool `access:"environment,write_restrictable,cloud_restrictable"` + EnableInsecureOutgoingConnections *bool `access:"environment,write_restrictable,cloud_restrictable"` + AllowedUntrustedInternalConnections *string `access:"environment,write_restrictable,cloud_restrictable"` EnableMultifactorAuthentication *bool `access:"authentication"` EnforceMultifactorAuthentication *bool `access:"authentication"` EnableUserAccessTokens *bool `access:"integrations"` - AllowCorsFrom *string `access:"integrations,write_restrictable"` - CorsExposedHeaders *string `access:"integrations,write_restrictable"` - CorsAllowCredentials *bool `access:"integrations,write_restrictable"` - CorsDebug *bool `access:"integrations,write_restrictable"` - AllowCookiesForSubdomains *bool `access:"write_restrictable"` - ExtendSessionLengthWithActivity *bool `access:"environment,write_restrictable"` - SessionLengthWebInDays *int `access:"environment,write_restrictable"` - SessionLengthMobileInDays *int `access:"environment,write_restrictable"` - SessionLengthSSOInDays *int `access:"environment,write_restrictable"` - SessionCacheInMinutes *int `access:"environment,write_restrictable"` - SessionIdleTimeoutInMinutes *int `access:"environment,write_restrictable"` - WebsocketSecurePort *int `access:"write_restrictable"` - WebsocketPort *int `access:"write_restrictable"` - WebserverMode *string `access:"environment,write_restrictable"` + AllowCorsFrom *string `access:"integrations,write_restrictable,cloud_restrictable"` + CorsExposedHeaders *string `access:"integrations,write_restrictable,cloud_restrictable"` + CorsAllowCredentials *bool `access:"integrations,write_restrictable,cloud_restrictable"` + CorsDebug *bool `access:"integrations,write_restrictable,cloud_restrictable"` + AllowCookiesForSubdomains *bool `access:"write_restrictable,cloud_restrictable"` + ExtendSessionLengthWithActivity *bool `access:"environment,write_restrictable,cloud_restrictable"` + SessionLengthWebInDays *int `access:"environment,write_restrictable,cloud_restrictable"` + SessionLengthMobileInDays *int `access:"environment,write_restrictable,cloud_restrictable"` + SessionLengthSSOInDays *int `access:"environment,write_restrictable,cloud_restrictable"` + SessionCacheInMinutes *int `access:"environment,write_restrictable,cloud_restrictable"` + SessionIdleTimeoutInMinutes *int `access:"environment,write_restrictable,cloud_restrictable"` + WebsocketSecurePort *int `access:"write_restrictable,cloud_restrictable"` + WebsocketPort *int `access:"write_restrictable,cloud_restrictable"` + WebserverMode *string `access:"environment,write_restrictable,cloud_restrictable"` EnableCustomEmoji *bool `access:"site"` EnableEmojiPicker *bool `access:"site"` EnableGifPicker *bool `access:"integrations"` @@ -312,14 +314,14 @@ type ServiceSettings struct { DEPRECATED_DO_NOT_USE_RestrictPostDelete *string `json:"RestrictPostDelete" mapstructure:"RestrictPostDelete"` // This field is deprecated and must not be used. DEPRECATED_DO_NOT_USE_AllowEditPost *string `json:"AllowEditPost" mapstructure:"AllowEditPost"` // This field is deprecated and must not be used. PostEditTimeLimit *int `access:"user_management_permissions"` - TimeBetweenUserTypingUpdatesMilliseconds *int64 `access:"experimental,write_restrictable"` - EnablePostSearch *bool `access:"write_restrictable"` - MinimumHashtagLength *int `access:"environment,write_restrictable"` - EnableUserTypingMessages *bool `access:"experimental,write_restrictable"` - EnableChannelViewedMessages *bool `access:"experimental,write_restrictable"` - EnableUserStatuses *bool `access:"write_restrictable"` - ExperimentalEnableAuthenticationTransfer *bool `access:"experimental,write_restrictable"` - ClusterLogTimeoutMilliseconds *int `access:"write_restrictable"` + TimeBetweenUserTypingUpdatesMilliseconds *int64 `access:"experimental,write_restrictable,cloud_restrictable"` + EnablePostSearch *bool `access:"write_restrictable,cloud_restrictable"` + MinimumHashtagLength *int `access:"environment,write_restrictable,cloud_restrictable"` + EnableUserTypingMessages *bool `access:"experimental,write_restrictable,cloud_restrictable"` + EnableChannelViewedMessages *bool `access:"experimental,write_restrictable,cloud_restrictable"` + EnableUserStatuses *bool `access:"write_restrictable,cloud_restrictable"` + ExperimentalEnableAuthenticationTransfer *bool `access:"experimental,write_restrictable,cloud_restrictable"` + ClusterLogTimeoutMilliseconds *int `access:"write_restrictable,cloud_restrictable"` CloseUnusedDirectMessages *bool `access:"experimental"` EnablePreviewFeatures *bool `access:"experimental"` EnableTutorial *bool `access:"experimental"` @@ -334,10 +336,10 @@ type ServiceSettings struct { EnableAPITeamDeletion *bool EnableAPIUserDeletion *bool ExperimentalEnableHardenedMode *bool `access:"experimental"` - DisableLegacyMFA *bool `access:"write_restrictable"` - ExperimentalStrictCSRFEnforcement *bool `access:"experimental,write_restrictable"` + DisableLegacyMFA *bool `access:"write_restrictable,cloud_restrictable"` + ExperimentalStrictCSRFEnforcement *bool `access:"experimental,write_restrictable,cloud_restrictable"` EnableEmailInvitations *bool `access:"authentication"` - DisableBotsWhenOwnerIsDeactivated *bool `access:"integrations,write_restrictable"` + DisableBotsWhenOwnerIsDeactivated *bool `access:"integrations,write_restrictable,cloud_restrictable"` EnableBotAccountCreation *bool `access:"integrations"` EnableSVGs *bool `access:"site"` EnableLatex *bool `access:"site"` @@ -766,20 +768,20 @@ func (s *ServiceSettings) SetDefaults(isUpdate bool) { type ClusterSettings struct { Enable *bool `access:"environment,write_restrictable"` - ClusterName *string `access:"environment,write_restrictable"` - OverrideHostname *string `access:"environment,write_restrictable"` - NetworkInterface *string `access:"environment,write_restrictable"` - BindAddress *string `access:"environment,write_restrictable"` - AdvertiseAddress *string `access:"environment,write_restrictable"` - UseIpAddress *bool `access:"environment,write_restrictable"` - UseExperimentalGossip *bool `access:"environment,write_restrictable"` - EnableExperimentalGossipEncryption *bool `access:"environment,write_restrictable"` - ReadOnlyConfig *bool `access:"environment,write_restrictable"` - GossipPort *int `access:"environment,write_restrictable"` - StreamingPort *int `access:"environment,write_restrictable"` - MaxIdleConns *int `access:"environment,write_restrictable"` - MaxIdleConnsPerHost *int `access:"environment,write_restrictable"` - IdleConnTimeoutMilliseconds *int `access:"environment,write_restrictable"` + ClusterName *string `access:"environment,write_restrictable,cloud_restrictable"` + OverrideHostname *string `access:"environment,write_restrictable,cloud_restrictable"` + NetworkInterface *string `access:"environment,write_restrictable,cloud_restrictable"` + BindAddress *string `access:"environment,write_restrictable,cloud_restrictable"` + AdvertiseAddress *string `access:"environment,write_restrictable,cloud_restrictable"` + UseIpAddress *bool `access:"environment,write_restrictable,cloud_restrictable"` + UseExperimentalGossip *bool `access:"environment,write_restrictable,cloud_restrictable"` + EnableExperimentalGossipEncryption *bool `access:"environment,write_restrictable,cloud_restrictable"` + ReadOnlyConfig *bool `access:"environment,write_restrictable,cloud_restrictable"` + GossipPort *int `access:"environment,write_restrictable,cloud_restrictable"` + StreamingPort *int `access:"environment,write_restrictable,cloud_restrictable"` + MaxIdleConns *int `access:"environment,write_restrictable,cloud_restrictable"` + MaxIdleConnsPerHost *int `access:"environment,write_restrictable,cloud_restrictable"` + IdleConnTimeoutMilliseconds *int `access:"environment,write_restrictable,cloud_restrictable"` } func (s *ClusterSettings) SetDefaults() { @@ -845,9 +847,9 @@ func (s *ClusterSettings) SetDefaults() { } type MetricsSettings struct { - Enable *bool `access:"environment,write_restrictable"` - BlockProfileRate *int `access:"environment,write_restrictable"` - ListenAddress *string `access:"environment,write_restrictable"` + Enable *bool `access:"environment,write_restrictable,cloud_restrictable"` + BlockProfileRate *int `access:"environment,write_restrictable,cloud_restrictable"` + ListenAddress *string `access:"environment,write_restrictable,cloud_restrictable"` } func (s *MetricsSettings) SetDefaults() { @@ -865,14 +867,14 @@ func (s *MetricsSettings) SetDefaults() { } type ExperimentalSettings struct { - ClientSideCertEnable *bool `access:"experimental"` - ClientSideCertCheck *string `access:"experimental"` - EnableClickToReply *bool `access:"experimental,write_restrictable"` - LinkMetadataTimeoutMilliseconds *int64 `access:"experimental,write_restrictable"` + ClientSideCertEnable *bool `access:"experimental,cloud_restrictable"` + ClientSideCertCheck *string `access:"experimental,cloud_restrictable"` + EnableClickToReply *bool `access:"experimental,write_restrictable,cloud_restrictable"` + LinkMetadataTimeoutMilliseconds *int64 `access:"experimental,write_restrictable,cloud_restrictable"` RestrictSystemAdmin *bool `access:"experimental,write_restrictable"` - UseNewSAMLLibrary *bool `access:"experimental"` - CloudUserLimit *int64 `access:"experimental,write_restrictable"` - CloudBilling *bool `access:"experimental,write_restrictable"` + UseNewSAMLLibrary *bool `access:"experimental,cloud_restrictable"` + CloudUserLimit *int64 `access:"experimental,write_restrictable,cloud_restrictable"` + CloudBilling *bool `access:"experimental,write_restrictable,cloud_restrictable"` } func (s *ExperimentalSettings) SetDefaults() { @@ -911,7 +913,7 @@ func (s *ExperimentalSettings) SetDefaults() { } type AnalyticsSettings struct { - MaxUsersForStatistics *int `access:"write_restrictable"` + MaxUsersForStatistics *int `access:"write_restrictable,cloud_restrictable"` } func (s *AnalyticsSettings) SetDefaults() { @@ -1018,17 +1020,17 @@ func (s *Office365Settings) SSOSettings() *SSOSettings { } type SqlSettings struct { - DriverName *string `access:"environment,write_restrictable"` - DataSource *string `access:"environment,write_restrictable"` - DataSourceReplicas []string `access:"environment,write_restrictable"` - DataSourceSearchReplicas []string `access:"environment,write_restrictable"` - MaxIdleConns *int `access:"environment,write_restrictable"` - ConnMaxLifetimeMilliseconds *int `access:"environment,write_restrictable"` - MaxOpenConns *int `access:"environment,write_restrictable"` - Trace *bool `access:"environment,write_restrictable"` - AtRestEncryptKey *string `access:"environment,write_restrictable"` - QueryTimeout *int `access:"environment,write_restrictable"` - DisableDatabaseSearch *bool `access:"environment,write_restrictable"` + DriverName *string `access:"environment,write_restrictable,cloud_restrictable"` + DataSource *string `access:"environment,write_restrictable,cloud_restrictable"` + DataSourceReplicas []string `access:"environment,write_restrictable,cloud_restrictable"` + DataSourceSearchReplicas []string `access:"environment,write_restrictable,cloud_restrictable"` + MaxIdleConns *int `access:"environment,write_restrictable,cloud_restrictable"` + ConnMaxLifetimeMilliseconds *int `access:"environment,write_restrictable,cloud_restrictable"` + MaxOpenConns *int `access:"environment,write_restrictable,cloud_restrictable"` + Trace *bool `access:"environment,write_restrictable,cloud_restrictable"` + AtRestEncryptKey *string `access:"environment,write_restrictable,cloud_restrictable"` + QueryTimeout *int `access:"environment,write_restrictable,cloud_restrictable"` + DisableDatabaseSearch *bool `access:"environment,write_restrictable,cloud_restrictable"` } func (s *SqlSettings) SetDefaults(isUpdate bool) { @@ -1084,17 +1086,17 @@ func (s *SqlSettings) SetDefaults(isUpdate bool) { } type LogSettings struct { - EnableConsole *bool `access:"environment,write_restrictable"` - ConsoleLevel *string `access:"environment,write_restrictable"` - ConsoleJson *bool `access:"environment,write_restrictable"` - EnableFile *bool `access:"environment,write_restrictable"` - FileLevel *string `access:"environment,write_restrictable"` - FileJson *bool `access:"environment,write_restrictable"` - FileLocation *string `access:"environment,write_restrictable"` - EnableWebhookDebugging *bool `access:"environment,write_restrictable"` - EnableDiagnostics *bool `access:"environment,write_restrictable"` - EnableSentry *bool `access:"environment,write_restrictable"` - AdvancedLoggingConfig *string `access:"environment,write_restrictable"` + EnableConsole *bool `access:"environment,write_restrictable,cloud_restrictable"` + ConsoleLevel *string `access:"environment,write_restrictable,cloud_restrictable"` + ConsoleJson *bool `access:"environment,write_restrictable,cloud_restrictable"` + EnableFile *bool `access:"environment,write_restrictable,cloud_restrictable"` + FileLevel *string `access:"environment,write_restrictable,cloud_restrictable"` + FileJson *bool `access:"environment,write_restrictable,cloud_restrictable"` + FileLocation *string `access:"environment,write_restrictable,cloud_restrictable"` + EnableWebhookDebugging *bool `access:"environment,write_restrictable,cloud_restrictable"` + EnableDiagnostics *bool `access:"environment,write_restrictable,cloud_restrictable"` + EnableSentry *bool `access:"environment,write_restrictable,cloud_restrictable"` + AdvancedLoggingConfig *string `access:"environment,write_restrictable,cloud_restrictable"` } func (s *LogSettings) SetDefaults() { @@ -1144,14 +1146,14 @@ func (s *LogSettings) SetDefaults() { } type ExperimentalAuditSettings struct { - FileEnabled *bool `access:"experimental,write_restrictable"` - FileName *string `access:"experimental,write_restrictable"` - FileMaxSizeMB *int `access:"experimental,write_restrictable"` - FileMaxAgeDays *int `access:"experimental,write_restrictable"` - FileMaxBackups *int `access:"experimental,write_restrictable"` - FileCompress *bool `access:"experimental,write_restrictable"` - FileMaxQueueSize *int `access:"experimental,write_restrictable"` - AdvancedLoggingConfig *string `access:"experimental,write_restrictable"` + FileEnabled *bool `access:"experimental,write_restrictable,cloud_restrictable"` + FileName *string `access:"experimental,write_restrictable,cloud_restrictable"` + FileMaxSizeMB *int `access:"experimental,write_restrictable,cloud_restrictable"` + FileMaxAgeDays *int `access:"experimental,write_restrictable,cloud_restrictable"` + FileMaxBackups *int `access:"experimental,write_restrictable,cloud_restrictable"` + FileCompress *bool `access:"experimental,write_restrictable,cloud_restrictable"` + FileMaxQueueSize *int `access:"experimental,write_restrictable,cloud_restrictable"` + AdvancedLoggingConfig *string `access:"experimental,write_restrictable,cloud_restrictable"` } func (s *ExperimentalAuditSettings) SetDefaults() { @@ -1189,14 +1191,14 @@ func (s *ExperimentalAuditSettings) SetDefaults() { } type NotificationLogSettings struct { - EnableConsole *bool `access:"write_restrictable"` - ConsoleLevel *string `access:"write_restrictable"` - ConsoleJson *bool `access:"write_restrictable"` - EnableFile *bool `access:"write_restrictable"` - FileLevel *string `access:"write_restrictable"` - FileJson *bool `access:"write_restrictable"` - FileLocation *string `access:"write_restrictable"` - AdvancedLoggingConfig *string `access:"write_restrictable"` + EnableConsole *bool `access:"write_restrictable,cloud_restrictable"` + ConsoleLevel *string `access:"write_restrictable,cloud_restrictable"` + ConsoleJson *bool `access:"write_restrictable,cloud_restrictable"` + EnableFile *bool `access:"write_restrictable,cloud_restrictable"` + FileLevel *string `access:"write_restrictable,cloud_restrictable"` + FileJson *bool `access:"write_restrictable,cloud_restrictable"` + FileLocation *string `access:"write_restrictable,cloud_restrictable"` + AdvancedLoggingConfig *string `access:"write_restrictable,cloud_restrictable"` } func (s *NotificationLogSettings) SetDefaults() { @@ -1264,25 +1266,25 @@ func (s *PasswordSettings) SetDefaults() { } type FileSettings struct { - EnableFileAttachments *bool `access:"site"` - EnableMobileUpload *bool `access:"site"` - EnableMobileDownload *bool `access:"site"` - MaxFileSize *int64 `access:"environment"` - DriverName *string `access:"environment,write_restrictable"` - Directory *string `access:"environment,write_restrictable"` - EnablePublicLink *bool `access:"site"` - PublicLinkSalt *string `access:"site"` - InitialFont *string `access:"environment"` - AmazonS3AccessKeyId *string `access:"environment,write_restrictable"` - AmazonS3SecretAccessKey *string `access:"environment,write_restrictable"` - AmazonS3Bucket *string `access:"environment,write_restrictable"` - AmazonS3PathPrefix *string `access:"environment,write_restrictable"` - AmazonS3Region *string `access:"environment,write_restrictable"` - AmazonS3Endpoint *string `access:"environment,write_restrictable"` - AmazonS3SSL *bool `access:"environment,write_restrictable"` - AmazonS3SignV2 *bool `access:"environment,write_restrictable"` - AmazonS3SSE *bool `access:"environment,write_restrictable"` - AmazonS3Trace *bool `access:"environment,write_restrictable"` + EnableFileAttachments *bool `access:"site,cloud_restrictable"` + EnableMobileUpload *bool `access:"site,cloud_restrictable"` + EnableMobileDownload *bool `access:"site,cloud_restrictable"` + MaxFileSize *int64 `access:"environment,cloud_restrictable"` + DriverName *string `access:"environment,write_restrictable,cloud_restrictable"` + Directory *string `access:"environment,write_restrictable,cloud_restrictable"` + EnablePublicLink *bool `access:"site,cloud_restrictable"` + PublicLinkSalt *string `access:"site,cloud_restrictable"` + InitialFont *string `access:"environment,cloud_restrictable"` + AmazonS3AccessKeyId *string `access:"environment,write_restrictable,cloud_restrictable"` + AmazonS3SecretAccessKey *string `access:"environment,write_restrictable,cloud_restrictable"` + AmazonS3Bucket *string `access:"environment,write_restrictable,cloud_restrictable"` + AmazonS3PathPrefix *string `access:"environment,write_restrictable,cloud_restrictable"` + AmazonS3Region *string `access:"environment,write_restrictable,cloud_restrictable"` + AmazonS3Endpoint *string `access:"environment,write_restrictable,cloud_restrictable"` + AmazonS3SSL *bool `access:"environment,write_restrictable,cloud_restrictable"` + AmazonS3SignV2 *bool `access:"environment,write_restrictable,cloud_restrictable"` + AmazonS3SSE *bool `access:"environment,write_restrictable,cloud_restrictable"` + AmazonS3Trace *bool `access:"environment,write_restrictable,cloud_restrictable"` } func (s *FileSettings) SetDefaults(isUpdate bool) { @@ -1380,16 +1382,16 @@ type EmailSettings struct { UseChannelInEmailNotifications *bool `access:"experimental"` RequireEmailVerification *bool `access:"authentication"` FeedbackName *string `access:"site"` - FeedbackEmail *string `access:"site"` - ReplyToAddress *string `access:"site"` + FeedbackEmail *string `access:"site,cloud_restrictable"` + ReplyToAddress *string `access:"site,cloud_restrictable"` FeedbackOrganization *string `access:"site"` - EnableSMTPAuth *bool `access:"environment,write_restrictable"` - SMTPUsername *string `access:"environment,write_restrictable"` - SMTPPassword *string `access:"environment,write_restrictable"` - SMTPServer *string `access:"environment,write_restrictable"` - SMTPPort *string `access:"environment,write_restrictable"` - SMTPServerTimeout *int - ConnectionSecurity *string `access:"environment,write_restrictable"` + EnableSMTPAuth *bool `access:"environment,write_restrictable,cloud_restrictable"` + SMTPUsername *string `access:"environment,write_restrictable,cloud_restrictable"` + SMTPPassword *string `access:"environment,write_restrictable,cloud_restrictable"` + SMTPServer *string `access:"environment,write_restrictable,cloud_restrictable"` + SMTPPort *string `access:"environment,write_restrictable,cloud_restrictable"` + SMTPServerTimeout *int `access:"cloud_restrictable"` + ConnectionSecurity *string `access:"environment,write_restrictable,cloud_restrictable"` SendPushNotifications *bool `access:"environment"` PushNotificationServer *string `access:"environment"` PushNotificationContents *string `access:"site"` @@ -1398,7 +1400,7 @@ type EmailSettings struct { EmailBatchingBufferSize *int `access:"experimental"` EmailBatchingInterval *int `access:"experimental"` EnablePreviewModeBanner *bool `access:"site"` - SkipServerCertificateVerification *bool `access:"environment,write_restrictable"` + SkipServerCertificateVerification *bool `access:"environment,write_restrictable,cloud_restrictable"` EmailNotificationContentsType *string `access:"site"` LoginButtonColor *string `access:"experimental"` LoginButtonBorderColor *string `access:"experimental"` @@ -1548,13 +1550,13 @@ func (s *EmailSettings) SetDefaults(isUpdate bool) { } type RateLimitSettings struct { - Enable *bool `access:"environment,write_restrictable"` - PerSec *int `access:"environment,write_restrictable"` - MaxBurst *int `access:"environment,write_restrictable"` - MemoryStoreSize *int `access:"environment,write_restrictable"` - VaryByRemoteAddr *bool `access:"environment,write_restrictable"` - VaryByUser *bool `access:"environment,write_restrictable"` - VaryByHeader string `access:"environment,write_restrictable"` + Enable *bool `access:"environment,write_restrictable,cloud_restrictable"` + PerSec *int `access:"environment,write_restrictable,cloud_restrictable"` + MaxBurst *int `access:"environment,write_restrictable,cloud_restrictable"` + MemoryStoreSize *int `access:"environment,write_restrictable,cloud_restrictable"` + VaryByRemoteAddr *bool `access:"environment,write_restrictable,cloud_restrictable"` + VaryByUser *bool `access:"environment,write_restrictable,cloud_restrictable"` + VaryByHeader string `access:"environment,write_restrictable,cloud_restrictable"` } func (s *RateLimitSettings) SetDefaults() { @@ -1599,11 +1601,11 @@ func (s *PrivacySettings) setDefaults() { } type SupportSettings struct { - TermsOfServiceLink *string `access:"site,write_restrictable"` - PrivacyPolicyLink *string `access:"site,write_restrictable"` - AboutLink *string `access:"site,write_restrictable"` - HelpLink *string `access:"site,write_restrictable"` - ReportAProblemLink *string `access:"site,write_restrictable"` + TermsOfServiceLink *string `access:"site,write_restrictable,cloud_restrictable"` + PrivacyPolicyLink *string `access:"site,write_restrictable,cloud_restrictable"` + AboutLink *string `access:"site,write_restrictable,cloud_restrictable"` + HelpLink *string `access:"site,write_restrictable,cloud_restrictable"` + ReportAProblemLink *string `access:"site,write_restrictable,cloud_restrictable"` SupportEmail *string `access:"site"` CustomTermsOfServiceEnabled *bool `access:"compliance"` CustomTermsOfServiceReAcceptancePeriod *int `access:"compliance"` @@ -1930,12 +1932,12 @@ func (s *TeamSettings) SetDefaults() { } type ClientRequirements struct { - AndroidLatestVersion string `access:"write_restrictable"` - AndroidMinVersion string `access:"write_restrictable"` - DesktopLatestVersion string `access:"write_restrictable"` - DesktopMinVersion string `access:"write_restrictable"` - IosLatestVersion string `access:"write_restrictable"` - IosMinVersion string `access:"write_restrictable"` + AndroidLatestVersion string `access:"write_restrictable,cloud_restrictable"` + AndroidMinVersion string `access:"write_restrictable,cloud_restrictable"` + DesktopLatestVersion string `access:"write_restrictable,cloud_restrictable"` + DesktopMinVersion string `access:"write_restrictable,cloud_restrictable"` + IosLatestVersion string `access:"write_restrictable,cloud_restrictable"` + IosMinVersion string `access:"write_restrictable,cloud_restrictable"` } type LdapSettings struct { @@ -2359,9 +2361,9 @@ func (s *SamlSettings) SetDefaults() { } type NativeAppSettings struct { - AppDownloadLink *string `access:"site,write_restrictable"` - AndroidAppDownloadLink *string `access:"site,write_restrictable"` - IosAppDownloadLink *string `access:"site,write_restrictable"` + AppDownloadLink *string `access:"site,write_restrictable,cloud_restrictable"` + AndroidAppDownloadLink *string `access:"site,write_restrictable,cloud_restrictable"` + IosAppDownloadLink *string `access:"site,write_restrictable,cloud_restrictable"` } func (s *NativeAppSettings) SetDefaults() { @@ -2379,27 +2381,27 @@ func (s *NativeAppSettings) SetDefaults() { } type ElasticsearchSettings struct { - ConnectionUrl *string `access:"environment,write_restrictable"` - Username *string `access:"environment,write_restrictable"` - Password *string `access:"environment,write_restrictable"` - EnableIndexing *bool `access:"environment,write_restrictable"` - EnableSearching *bool `access:"environment,write_restrictable"` - EnableAutocomplete *bool `access:"environment,write_restrictable"` - Sniff *bool `access:"environment,write_restrictable"` - PostIndexReplicas *int `access:"environment,write_restrictable"` - PostIndexShards *int `access:"environment,write_restrictable"` - ChannelIndexReplicas *int `access:"environment,write_restrictable"` - ChannelIndexShards *int `access:"environment,write_restrictable"` - UserIndexReplicas *int `access:"environment,write_restrictable"` - UserIndexShards *int `access:"environment,write_restrictable"` - AggregatePostsAfterDays *int `access:"environment,write_restrictable"` - PostsAggregatorJobStartTime *string `access:"environment,write_restrictable"` - IndexPrefix *string `access:"environment,write_restrictable"` - LiveIndexingBatchSize *int `access:"environment,write_restrictable"` - BulkIndexingTimeWindowSeconds *int `access:"environment,write_restrictable"` - RequestTimeoutSeconds *int `access:"environment,write_restrictable"` - SkipTLSVerification *bool `access:"environment,write_restrictable"` - Trace *string `access:"environment,write_restrictable"` + ConnectionUrl *string `access:"environment,write_restrictable,cloud_restrictable"` + Username *string `access:"environment,write_restrictable,cloud_restrictable"` + Password *string `access:"environment,write_restrictable,cloud_restrictable"` + EnableIndexing *bool `access:"environment,write_restrictable,cloud_restrictable"` + EnableSearching *bool `access:"environment,write_restrictable,cloud_restrictable"` + EnableAutocomplete *bool `access:"environment,write_restrictable,cloud_restrictable"` + Sniff *bool `access:"environment,write_restrictable,cloud_restrictable"` + PostIndexReplicas *int `access:"environment,write_restrictable,cloud_restrictable"` + PostIndexShards *int `access:"environment,write_restrictable,cloud_restrictable"` + ChannelIndexReplicas *int `access:"environment,write_restrictable,cloud_restrictable"` + ChannelIndexShards *int `access:"environment,write_restrictable,cloud_restrictable"` + UserIndexReplicas *int `access:"environment,write_restrictable,cloud_restrictable"` + UserIndexShards *int `access:"environment,write_restrictable,cloud_restrictable"` + AggregatePostsAfterDays *int `access:"environment,write_restrictable,cloud_restrictable"` + PostsAggregatorJobStartTime *string `access:"environment,write_restrictable,cloud_restrictable"` + IndexPrefix *string `access:"environment,write_restrictable,cloud_restrictable"` + LiveIndexingBatchSize *int `access:"environment,write_restrictable,cloud_restrictable"` + BulkIndexingTimeWindowSeconds *int `access:"environment,write_restrictable,cloud_restrictable"` + RequestTimeoutSeconds *int `access:"environment,write_restrictable,cloud_restrictable"` + SkipTLSVerification *bool `access:"environment,write_restrictable,cloud_restrictable"` + Trace *string `access:"environment,write_restrictable,cloud_restrictable"` } func (s *ElasticsearchSettings) SetDefaults() { @@ -2549,8 +2551,8 @@ func (s *DataRetentionSettings) SetDefaults() { } type JobSettings struct { - RunJobs *bool `access:"write_restrictable"` - RunScheduler *bool `access:"write_restrictable"` + RunJobs *bool `access:"write_restrictable,cloud_restrictable"` + RunScheduler *bool `access:"write_restrictable,cloud_restrictable"` } func (s *JobSettings) SetDefaults() { @@ -2578,20 +2580,20 @@ type PluginState struct { } type PluginSettings struct { - Enable *bool `access:"plugins,write_restrictable"` - EnableUploads *bool `access:"plugins,write_restrictable"` - AllowInsecureDownloadUrl *bool `access:"plugins,write_restrictable"` - EnableHealthCheck *bool `access:"plugins,write_restrictable"` - Directory *string `access:"plugins,write_restrictable"` - ClientDirectory *string `access:"plugins,write_restrictable"` + Enable *bool `access:"plugins,write_restrictable,cloud_restrictable"` + EnableUploads *bool `access:"plugins,write_restrictable,cloud_restrictable"` + AllowInsecureDownloadUrl *bool `access:"plugins,write_restrictable,cloud_restrictable"` + EnableHealthCheck *bool `access:"plugins,write_restrictable,cloud_restrictable"` + Directory *string `access:"plugins,write_restrictable,cloud_restrictable"` + ClientDirectory *string `access:"plugins,write_restrictable,cloud_restrictable"` Plugins map[string]map[string]interface{} `access:"plugins"` PluginStates map[string]*PluginState `access:"plugins"` - EnableMarketplace *bool `access:"plugins,write_restrictable"` - EnableRemoteMarketplace *bool `access:"plugins,write_restrictable"` - AutomaticPrepackagedPlugins *bool `access:"plugins,write_restrictable"` - RequirePluginSignature *bool `access:"plugins,write_restrictable"` - MarketplaceUrl *string `access:"plugins,write_restrictable"` - SignaturePublicKeyFiles []string `access:"plugins,write_restrictable"` + EnableMarketplace *bool `access:"plugins,write_restrictable,cloud_restrictable"` + EnableRemoteMarketplace *bool `access:"plugins,write_restrictable,cloud_restrictable"` + AutomaticPrepackagedPlugins *bool `access:"plugins,write_restrictable,cloud_restrictable"` + RequirePluginSignature *bool `access:"plugins,write_restrictable,cloud_restrictable"` + MarketplaceUrl *string `access:"plugins,write_restrictable,cloud_restrictable"` + SignaturePublicKeyFiles []string `access:"plugins,write_restrictable,cloud_restrictable"` } func (s *PluginSettings) SetDefaults(ls LogSettings) { @@ -2815,7 +2817,9 @@ func (s *ImageProxySettings) SetDefaults(ss ServiceSettings) { type ConfigFunc func() *Config +const ConfigAccessTagType = "access" const ConfigAccessTagWriteRestrictable = "write_restrictable" +const ConfigAccessTagCloudRestrictable = "cloud_restrictable" // Config fields support the 'access' tag with the following values corresponding to the suffix of the associated // PERMISSION_SYSCONSOLE_*_* permission Id: 'about', 'reporting', 'user_management_users', @@ -2829,6 +2833,9 @@ const ConfigAccessTagWriteRestrictable = "write_restrictable" // // PERMISSION_MANAGE_SYSTEM always grants read access. // +// Config values with the access tag 'cloud_restrictable' mean that are marked to be filtered when it's used in a cloud licensed +// environment with ExperimentalSettings.RestrictedSystemAdmin set to true. +// // Example: // type HairSettings struct { // // Colour is writeable with either PERMISSION_SYSCONSOLE_WRITE_REPORTING or PERMISSION_SYSCONSOLE_WRITE_USER_MANAGEMENT_GROUPS. @@ -2898,6 +2905,18 @@ func (o *Config) ToJson() string { return string(b) } +func (o *Config) ToJsonFiltered(tagType, tagValue string) string { + filteredConfigMap := structToMapFilteredByTag(*o, tagType, tagValue) + for key, value := range filteredConfigMap { + v, ok := value.(map[string]interface{}) + if ok && len(v) == 0 { + delete(filteredConfigMap, key) + } + } + b, _ := json.Marshal(filteredConfigMap) + return string(b) +} + func (o *Config) GetSSOService(service string) *SSOSettings { switch service { case SERVICE_GITLAB: @@ -3603,3 +3622,63 @@ func (o *Config) Sanitize() { *o.ServiceSettings.GfycatApiSecret = FAKE_SETTING } } + +// structToMapFilteredByTag converts a struct into a map removing those fields that has the tag passed +// as argument +func structToMapFilteredByTag(t interface{}, typeOfTag, filterTag string) map[string]interface{} { + defer func() { + if r := recover(); r != nil { + mlog.Error("Panicked in structToMapFilteredByTag. This should never happen.", mlog.Any("recover", r)) + } + }() + + val := reflect.ValueOf(t) + elemField := reflect.TypeOf(t) + + if val.Kind() != reflect.Struct { + return nil + } + + out := map[string]interface{}{} + + for i := 0; i < val.NumField(); i++ { + field := val.Field(i) + + structField := elemField.Field(i) + tagPermissions := strings.Split(structField.Tag.Get(typeOfTag), ",") + if isTagPresent(filterTag, tagPermissions) { + continue + } + + var value interface{} + + switch field.Kind() { + case reflect.Struct: + value = structToMapFilteredByTag(field.Interface(), typeOfTag, filterTag) + case reflect.Ptr: + indirectType := field.Elem() + if indirectType.Kind() == reflect.Struct { + value = structToMapFilteredByTag(indirectType.Interface(), typeOfTag, filterTag) + } else if indirectType.Kind() != reflect.Invalid { + value = indirectType.Interface() + } + default: + value = field.Interface() + } + + out[val.Type().Field(i).Name] = value + } + + return out +} + +func isTagPresent(tag string, tags []string) bool { + for _, val := range tags { + tagValue := strings.TrimSpace(val) + if tagValue != "" && tagValue == tag { + return true + } + } + + return false +} diff --git a/model/config_test.go b/model/config_test.go index 41c580f3fc..48a8d8013b 100644 --- a/model/config_test.go +++ b/model/config_test.go @@ -4,6 +4,7 @@ package model import ( + "encoding/json" "fmt" "reflect" "testing" @@ -1293,6 +1294,50 @@ func TestConfigSanitize(t *testing.T) { assert.Equal(t, FAKE_SETTING, c.SqlSettings.DataSourceSearchReplicas[0]) } +func TestConfigFilteredByTag(t *testing.T) { + c := Config{} + c.SetDefaults() + + cfgMap := structToMapFilteredByTag(c, ConfigAccessTagType, ConfigAccessTagCloudRestrictable) + + // Remove entire sections but the map is still there + clusterSettings, ok := cfgMap["SqlSettings"].(map[string]interface{}) + require.True(t, ok) + require.Equal(t, 0, len(clusterSettings)) + + // Some fields are removed if they have the filtering tag + serviceSettings, ok := cfgMap["ServiceSettings"].(map[string]interface{}) + require.True(t, ok) + _, ok = serviceSettings["ListenAddress"] + require.False(t, ok) +} + +func TestConfigToJSONFiltered(t *testing.T) { + c := Config{} + c.SetDefaults() + + jsonCfgFiltered := c.ToJsonFiltered(ConfigAccessTagType, ConfigAccessTagCloudRestrictable) + + unmarshaledCfg := make(map[string]json.RawMessage) + err := json.Unmarshal([]byte(jsonCfgFiltered), &unmarshaledCfg) + require.NoError(t, err) + + _, ok := unmarshaledCfg["SqlSettings"] + require.False(t, ok) + + serviceSettingsRaw, ok := unmarshaledCfg["ServiceSettings"] + require.True(t, ok) + + unmarshaledServiceSettings := make(map[string]json.RawMessage) + err = json.Unmarshal([]byte(serviceSettingsRaw), &unmarshaledServiceSettings) + require.NoError(t, err) + + _, ok = unmarshaledServiceSettings["ListenAddress"] + require.False(t, ok) + _, ok = unmarshaledServiceSettings["SiteURL"] + require.True(t, ok) +} + func TestConfigMarketplaceDefaults(t *testing.T) { t.Parallel()