[MM-69229] Promote CloudIPFiltering: remove the feature flag (#37150)

The CloudIPFiltering flag (default false, shipped v9.4) gated the
cloud-only IP filtering admin feature. The feature stays cloud-only
independent of the flag:

- Server: ensureIPFilteringInterface still requires license.IsCloud()
  and MinimumEnterpriseLicense, so non-cloud servers continue to get
  501 Not Implemented. Added not-cloud coverage to applyIPFilters and
  getMyIP to match the existing getIPFilters case.
- Webapp: the admin console section now gates on a Cloud license +
  Enterprise tier (it.licensedForFeature('Cloud')) instead of the flag,
  preserving cloud-only visibility.

Drops the struct field, its SetDefaults entry, the e2e default_config
mirror, the admin_sidebar test fixtures, and the flag-toggling test
cases.
This commit is contained in:
Jesse Hallam
2026-06-23 15:26:28 -03:00
committed by GitHub
parent 1bfb13dbc0
commit dbcd904cc2
6 changed files with 31 additions and 54 deletions
@@ -767,7 +767,6 @@ const defaultServerConfig: AdminConfig = {
EnableExportDirectDownload: false,
MoveThreadsEnabled: false,
StreamlinedMarketplace: true,
CloudIPFiltering: false,
ConsumePostHook: false,
CloudAnnualRenewals: false,
CloudDedicatedExportUI: false,
+1 -2
View File
@@ -21,8 +21,7 @@ func (api *API) InitIPFiltering() {
func ensureIPFilteringInterface(c *Context, where string) (einterfaces.IPFilteringInterface, bool) {
license := c.App.License()
ipFilteringFeatureFlag := c.App.Config().FeatureFlags.CloudIPFiltering
if c.App.IPFiltering() == nil || !ipFilteringFeatureFlag || license == nil || !license.IsCloud() || !model.MinimumEnterpriseLicense(license) {
if c.App.IPFiltering() == nil || license == nil || !license.IsCloud() || !model.MinimumEnterpriseLicense(license) {
c.Err = model.NewAppError(where, "api.context.ip_filtering.not_available.app_error", nil, "", http.StatusNotImplemented)
return nil, false
}
+28 -44
View File
@@ -4,7 +4,6 @@ package api4
import (
"context"
"os"
"testing"
"github.com/mattermost/mattermost/server/public/model"
@@ -30,7 +29,6 @@ func Test_getIPFilters(t *testing.T) {
}
t.Run("No license returns 501", func(t *testing.T) {
t.Setenv("MM_FEATUREFLAGS_CLOUDIPFILTERING", "true")
th := Setup(t).InitBasic(t)
ipFiltering := &mocks.IPFilteringInterface{}
@@ -48,27 +46,7 @@ func Test_getIPFilters(t *testing.T) {
require.Equal(t, 501, r.StatusCode)
})
t.Run("No feature flag returns 501", func(t *testing.T) {
os.Setenv("MM_FEATUREFLAGS_CLOUDIPFILTERING", "false")
defer os.Unsetenv("MM_FEATUREFLAGS_CLOUDIPFILTERING")
th := Setup(t).InitBasic(t)
ipFiltering := &mocks.IPFilteringInterface{}
th.App.Srv().IPFiltering = ipFiltering
th.App.Srv().SetLicense(lic)
_, _, err := th.Client.Login(context.Background(), th.BasicUser.Email, th.BasicUser.Password)
require.NoError(t, err)
ipFilters, r, err := th.Client.GetIPFilters(context.Background())
require.Error(t, err)
require.Nil(t, ipFilters)
require.Equal(t, 501, r.StatusCode)
})
t.Run("Feature flag and license but no permission", func(t *testing.T) {
t.Setenv("MM_FEATUREFLAGS_CLOUDIPFILTERING", "true")
t.Run("License but no permission", func(t *testing.T) {
th := Setup(t).InitBasic(t)
ipFiltering := &mocks.IPFilteringInterface{}
@@ -85,8 +63,7 @@ func Test_getIPFilters(t *testing.T) {
require.Equal(t, 403, r.StatusCode)
})
t.Run("Feature flag and license and permission", func(t *testing.T) {
t.Setenv("MM_FEATUREFLAGS_CLOUDIPFILTERING", "true")
t.Run("License and permission", func(t *testing.T) {
th := Setup(t).InitBasic(t)
ipFiltering := &mocks.IPFilteringInterface{}
@@ -109,8 +86,7 @@ func Test_getIPFilters(t *testing.T) {
require.Equal(t, 200, r.StatusCode)
})
t.Run("Feature flag and license and permission but not cloud returns 503", func(t *testing.T) {
t.Setenv("MM_FEATUREFLAGS_CLOUDIPFILTERING", "true")
t.Run("License and permission but not cloud returns 501", func(t *testing.T) {
th := Setup(t).InitBasic(t)
ipFiltering := &mocks.IPFilteringInterface{}
@@ -149,7 +125,6 @@ func Test_applyIPFilters(t *testing.T) {
// Initialize the allowedRanges variable
t.Run("No license returns 501", func(t *testing.T) {
t.Setenv("MM_FEATUREFLAGS_CLOUDIPFILTERING", "true")
th := Setup(t).InitBasic(t)
ipFiltering := &mocks.IPFilteringInterface{}
@@ -167,16 +142,14 @@ func Test_applyIPFilters(t *testing.T) {
require.Equal(t, 501, r.StatusCode)
})
t.Run("License but no feature flag returns 501", func(t *testing.T) {
os.Setenv("MM_FEATUREFLAGS_CLOUDIPFILTERING", "false")
defer os.Unsetenv("MM_FEATUREFLAGS_CLOUDIPFILTERING")
t.Run("License and permission but not cloud returns 501", func(t *testing.T) {
th := Setup(t).InitBasic(t)
ipFiltering := &mocks.IPFilteringInterface{}
th.App.Srv().IPFiltering = ipFiltering
th.App.Srv().SetLicense(lic)
th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterprise))
_, _, err := th.Client.Login(context.Background(), th.BasicUser.Email, th.BasicUser.Password)
_, _, err := th.Client.Login(context.Background(), th.SystemAdminUser.Email, th.SystemAdminUser.Password)
require.NoError(t, err)
ipFilters, r, err := th.Client.ApplyIPFilters(context.Background(), allowedRanges)
@@ -185,8 +158,7 @@ func Test_applyIPFilters(t *testing.T) {
require.Equal(t, 501, r.StatusCode)
})
t.Run("feature flag and license but no permission", func(t *testing.T) {
t.Setenv("MM_FEATUREFLAGS_CLOUDIPFILTERING", "true")
t.Run("License but no permission", func(t *testing.T) {
th := Setup(t).InitBasic(t)
_, _, err := th.Client.Login(context.Background(), th.BasicUser.Email, th.BasicUser.Password)
@@ -202,8 +174,7 @@ func Test_applyIPFilters(t *testing.T) {
require.Equal(t, 403, r.StatusCode)
})
t.Run("Feature flag and license and permission", func(t *testing.T) {
t.Setenv("MM_FEATUREFLAGS_CLOUDIPFILTERING", "true")
t.Run("License and permission", func(t *testing.T) {
th := Setup(t).InitBasic(t)
th.App.Srv().SetLicense(lic)
@@ -250,7 +221,6 @@ func Test_getMyIP(t *testing.T) {
ExpiresAt: model.GetMillis() + 100000,
}
t.Run("No license returns 501", func(t *testing.T) {
t.Setenv("MM_FEATUREFLAGS_CLOUDIPFILTERING", "true")
th := Setup(t).InitBasic(t)
ipFiltering := &mocks.IPFilteringInterface{}
@@ -268,9 +238,23 @@ func Test_getMyIP(t *testing.T) {
require.Equal(t, 501, r.StatusCode)
})
t.Run("Licensed, but no feature flag returns 501", func(t *testing.T) {
os.Setenv("MM_FEATUREFLAGS_CLOUDIPFILTERING", "false")
defer os.Unsetenv("MM_FEATUREFLAGS_CLOUDIPFILTERING")
t.Run("Licensed but not cloud returns 501", func(t *testing.T) {
th := Setup(t).InitBasic(t)
_, _, err := th.Client.Login(context.Background(), th.BasicUser.Email, th.BasicUser.Password)
require.NoError(t, err)
ipFiltering := &mocks.IPFilteringInterface{}
th.App.Srv().IPFiltering = ipFiltering
th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterprise))
myIP, r, err := th.Client.GetMyIP(context.Background())
require.Error(t, err)
require.Nil(t, myIP)
require.Equal(t, 501, r.StatusCode)
})
t.Run("Licensed and cloud returns 200", func(t *testing.T) {
th := Setup(t).InitBasic(t)
_, _, err := th.Client.Login(context.Background(), th.BasicUser.Email, th.BasicUser.Password)
@@ -281,8 +265,8 @@ func Test_getMyIP(t *testing.T) {
th.App.Srv().SetLicense(lic)
myIP, r, err := th.Client.GetMyIP(context.Background())
require.Error(t, err)
require.Nil(t, myIP)
require.Equal(t, 501, r.StatusCode)
require.NoError(t, err)
require.NotNil(t, myIP)
require.Equal(t, 200, r.StatusCode)
})
}
+1 -4
View File
@@ -47,9 +47,7 @@ type FeatureFlags struct {
StreamlinedMarketplace bool
CloudIPFiltering bool
ConsumePostHook bool
ConsumePostHook bool
CloudAnnualRenewals bool
CloudDedicatedExportUI bool
@@ -172,7 +170,6 @@ func (f *FeatureFlags) SetDefaults() {
f.EnableExportDirectDownload = false
f.MoveThreadsEnabled = false
f.StreamlinedMarketplace = true
f.CloudIPFiltering = false
f.ConsumePostHook = false
f.CloudAnnualRenewals = false
f.CloudDedicatedExportUI = false
@@ -4294,7 +4294,7 @@ const AdminDefinition: AdminDefinitionType = {
ip_filtering: {
url: 'site_config/ip_filtering',
title: adminDefinitionMessages.ip_filtering_title,
isHidden: it.not(it.all(it.configIsTrue('FeatureFlags', 'CloudIPFiltering'), it.minLicenseTier(LicenseSkus.Enterprise))),
isHidden: it.not(it.all(it.licensedForFeature('Cloud'), it.minLicenseTier(LicenseSkus.Enterprise))),
isDisabled: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.SITE.IP_FILTERING)),
searchableStrings: [adminDefinitionMessages.ip_filtering_title],
schema: {
@@ -401,7 +401,6 @@ describe('components/AdminSidebar', () => {
AttributeBasedAccessControl: true,
CustomProfileAttributes: true,
CloudDedicatedExportUI: true,
CloudIPFiltering: true,
ExperimentalAuditSettingsSystemConsoleUI: true,
},
},
@@ -537,7 +536,6 @@ describe('components/AdminSidebar', () => {
AttributeBasedAccessControl: true,
CustomProfileAttributes: true,
CloudDedicatedExportUI: true,
CloudIPFiltering: true,
ExperimentalAuditSettingsSystemConsoleUI: true,
},
},