Add server settings to further lock files on mobile (#30949)

* Add server settings to further lock files on mobile

* fix format with prettier

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
Co-authored-by: Saturnino Abril <5334504+saturninoabril@users.noreply.github.com>
This commit is contained in:
Elias Nahum 2025-06-19 15:37:32 +08:00 committed by GitHub
parent b1f609e6b8
commit 731cc1fb5f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 160 additions and 9 deletions

View file

@ -566,6 +566,8 @@ const defaultServerConfig: AdminConfig = {
MobileEnableBiometrics: false,
MobilePreventScreenCapture: false,
MobileJailbreakProtection: false,
MobileEnableSecureFilePreview: false,
MobileAllowPdfLinkNavigation: false,
},
CacheSettings: {
CacheType: 'lru',

View file

@ -15,6 +15,10 @@ export default class MobileSecurity {
readonly preventScreenCaptureToggleFalse: Locator;
readonly jailbreakProtectionToggleTrue: Locator;
readonly jailbreakProtectionToggleFalse: Locator;
readonly enableSecureFilePreviewToggleTrue: Locator;
readonly enableSecureFilePreviewToggleFalse: Locator;
readonly allowPdfLinkNavigationToggleTrue: Locator;
readonly allowPdfLinkNavigationToggleFalse: Locator;
readonly saveButton: Locator;
@ -42,6 +46,27 @@ export default class MobileSecurity {
'NativeAppSettings.MobileJailbreakProtectionfalse',
);
this.jailbreakProtectionToggleTrue = this.container.getByTestId(
'NativeAppSettings.MobileJailbreakProtectiontrue',
);
this.jailbreakProtectionToggleFalse = this.container.getByTestId(
'NativeAppSettings.MobileJailbreakProtectionfalse',
);
this.enableSecureFilePreviewToggleTrue = this.container.getByTestId(
'NativeAppSettings.MobileEnableSecureFilePreviewtrue',
);
this.enableSecureFilePreviewToggleFalse = this.container.getByTestId(
'NativeAppSettings.MobileEnableSecureFilePreviewfalse',
);
this.allowPdfLinkNavigationToggleTrue = this.container.getByTestId(
'NativeAppSettings.MobileAllowPdfLinkNavigationtrue',
);
this.allowPdfLinkNavigationToggleFalse = this.container.getByTestId(
'NativeAppSettings.MobileAllowPdfLinkNavigationfalse',
);
this.saveButton = this.container.getByRole('button', {name: 'Save'});
}
@ -73,6 +98,22 @@ export default class MobileSecurity {
await this.jailbreakProtectionToggleFalse.click();
}
async clickEnableSecureFilePreviewToggleTrue() {
await this.enableSecureFilePreviewToggleTrue.click();
}
async clickEnableSecureFilePreviewToggleFalse() {
await this.enableSecureFilePreviewToggleFalse.click();
}
async clickAllowPdfLinkNavigationToggleTrue() {
await this.allowPdfLinkNavigationToggleTrue.click();
}
async clickAllowPdfLinkNavigationToggleFalse() {
await this.allowPdfLinkNavigationToggleFalse.click();
}
async clickSaveButton() {
await this.saveButton.click();
}

View file

@ -8,7 +8,10 @@ test('should be able to enable mobile security settings when licensed', async ({
const license = await adminClient.getClientLicenseOld();
test.skip(license.SkuShortName !== 'enterprise', 'Skipping test - server has no enterprise license');
test.skip(
license.SkuShortName !== 'enterprise' || license.short_sku_name !== 'advanced',
'Skipping test - server has no enterprise or enterprise advanced license',
);
if (!adminUser) {
throw new Error('Failed to create admin user');
@ -96,6 +99,62 @@ test('should be able to enable mobile security settings when licensed', async ({
expect(await systemConsolePage.mobileSecurity.enableBiometricAuthenticationToggleTrue.isChecked()).toBe(true);
expect(await systemConsolePage.mobileSecurity.preventScreenCaptureToggleTrue.isChecked()).toBe(true);
expect(await systemConsolePage.mobileSecurity.jailbreakProtectionToggleTrue.isChecked()).toBe(true);
if (license.SkuShortName === 'advanced') {
// # Enable Secure File Preview
await systemConsolePage.mobileSecurity.clickEnableSecureFilePreviewToggleTrue();
// * Verify all toggles are enabled
expect(await systemConsolePage.mobileSecurity.enableBiometricAuthenticationToggleTrue.isChecked()).toBe(true);
expect(await systemConsolePage.mobileSecurity.preventScreenCaptureToggleTrue.isChecked()).toBe(true);
expect(await systemConsolePage.mobileSecurity.jailbreakProtectionToggleTrue.isChecked()).toBe(true);
expect(await systemConsolePage.mobileSecurity.enableSecureFilePreviewToggleTrue.isChecked()).toBe(true);
expect(await systemConsolePage.mobileSecurity.allowPdfLinkNavigationToggleTrue.isChecked()).toBe(false);
// # Save settings
await systemConsolePage.mobileSecurity.clickSaveButton();
// # Wait until the save button has settled
await pw.waitUntil(async () => (await systemConsolePage.mobileSecurity.saveButton.textContent()) === 'Save');
// # Go to any other section and come back to Mobile Security
await systemConsolePage.sidebar.goToItem('Users');
await systemConsolePage.systemUsers.toBeVisible();
await systemConsolePage.sidebar.goToItem('Mobile Security');
// * Verify all toggles are still enabled
expect(await systemConsolePage.mobileSecurity.enableBiometricAuthenticationToggleTrue.isChecked()).toBe(true);
expect(await systemConsolePage.mobileSecurity.preventScreenCaptureToggleTrue.isChecked()).toBe(true);
expect(await systemConsolePage.mobileSecurity.jailbreakProtectionToggleTrue.isChecked()).toBe(true);
expect(await systemConsolePage.mobileSecurity.enableSecureFilePreviewToggleTrue.isChecked()).toBe(true);
expect(await systemConsolePage.mobileSecurity.allowPdfLinkNavigationToggleTrue.isChecked()).toBe(false);
// # Enable Allow PDF Link Navigation
await systemConsolePage.mobileSecurity.clickAllowPdfLinkNavigationToggleTrue();
// * Verify all toggles are enabled
expect(await systemConsolePage.mobileSecurity.enableBiometricAuthenticationToggleTrue.isChecked()).toBe(true);
expect(await systemConsolePage.mobileSecurity.preventScreenCaptureToggleTrue.isChecked()).toBe(true);
expect(await systemConsolePage.mobileSecurity.jailbreakProtectionToggleTrue.isChecked()).toBe(true);
expect(await systemConsolePage.mobileSecurity.enableSecureFilePreviewToggleTrue.isChecked()).toBe(true);
expect(await systemConsolePage.mobileSecurity.allowPdfLinkNavigationToggleTrue.isChecked()).toBe(true);
// # Save settings
await systemConsolePage.mobileSecurity.clickSaveButton();
// # Wait until the save button has settled
await pw.waitUntil(async () => (await systemConsolePage.mobileSecurity.saveButton.textContent()) === 'Save');
// # Go to any other section and come back to Mobile Security
await systemConsolePage.sidebar.goToItem('Users');
await systemConsolePage.systemUsers.toBeVisible();
await systemConsolePage.sidebar.goToItem('Mobile Security');
// * Verify all toggles are still enabled
expect(await systemConsolePage.mobileSecurity.enableBiometricAuthenticationToggleTrue.isChecked()).toBe(true);
expect(await systemConsolePage.mobileSecurity.preventScreenCaptureToggleTrue.isChecked()).toBe(true);
expect(await systemConsolePage.mobileSecurity.jailbreakProtectionToggleTrue.isChecked()).toBe(true);
expect(await systemConsolePage.mobileSecurity.enableSecureFilePreviewToggleTrue.isChecked()).toBe(true);
expect(await systemConsolePage.mobileSecurity.allowPdfLinkNavigationToggleTrue.isChecked()).toBe(true);
}
});
test('should show mobile security upsell when not licensed', async ({pw}) => {

View file

@ -409,6 +409,11 @@ func GenerateLimitedClientConfig(c *model.Config, telemetryID string, license *m
props["MobilePreventScreenCapture"] = strconv.FormatBool(*c.NativeAppSettings.MobilePreventScreenCapture)
props["MobileJailbreakProtection"] = strconv.FormatBool(*c.NativeAppSettings.MobileJailbreakProtection)
}
if model.MinimumEnterpriseAdvancedLicense(license) {
props["MobileEnableSecureFilePreview"] = strconv.FormatBool(*c.NativeAppSettings.MobileEnableSecureFilePreview)
props["MobileAllowPdfLinkNavigation"] = strconv.FormatBool(*c.NativeAppSettings.MobileAllowPdfLinkNavigation)
}
}
for key, value := range c.FeatureFlags.ToMap() {

View file

@ -2906,14 +2906,16 @@ func (s *SamlSettings) SetDefaults() {
}
type NativeAppSettings struct {
AppCustomURLSchemes []string `access:"site_customization,write_restrictable,cloud_restrictable"` // telemetry: none
AppDownloadLink *string `access:"site_customization,write_restrictable,cloud_restrictable"`
AndroidAppDownloadLink *string `access:"site_customization,write_restrictable,cloud_restrictable"`
IosAppDownloadLink *string `access:"site_customization,write_restrictable,cloud_restrictable"`
MobileExternalBrowser *bool `access:"site_customization,write_restrictable,cloud_restrictable"`
MobileEnableBiometrics *bool `access:"site_customization,write_restrictable"`
MobilePreventScreenCapture *bool `access:"site_customization,write_restrictable"`
MobileJailbreakProtection *bool `access:"site_customization,write_restrictable"`
AppCustomURLSchemes []string `access:"site_customization,write_restrictable,cloud_restrictable"` // telemetry: none
AppDownloadLink *string `access:"site_customization,write_restrictable,cloud_restrictable"`
AndroidAppDownloadLink *string `access:"site_customization,write_restrictable,cloud_restrictable"`
IosAppDownloadLink *string `access:"site_customization,write_restrictable,cloud_restrictable"`
MobileExternalBrowser *bool `access:"site_customization,write_restrictable,cloud_restrictable"`
MobileEnableBiometrics *bool `access:"site_customization,write_restrictable"`
MobilePreventScreenCapture *bool `access:"site_customization,write_restrictable"`
MobileJailbreakProtection *bool `access:"site_customization,write_restrictable"`
MobileEnableSecureFilePreview *bool `access:"site_customization,write_restrictable"`
MobileAllowPdfLinkNavigation *bool `access:"site_customization,write_restrictable"`
}
func (s *NativeAppSettings) SetDefaults() {
@ -2948,6 +2950,14 @@ func (s *NativeAppSettings) SetDefaults() {
if s.MobileJailbreakProtection == nil {
s.MobileJailbreakProtection = NewPointer(false)
}
if s.MobileEnableSecureFilePreview == nil {
s.MobileEnableSecureFilePreview = NewPointer(false)
}
if s.MobileAllowPdfLinkNavigation == nil {
s.MobileAllowPdfLinkNavigation = NewPointer(false)
}
}
type ElasticsearchSettings struct {

View file

@ -2130,6 +2130,33 @@ const AdminDefinition: AdminDefinitionType = {
label: defineMessage({id: 'admin.mobileSecurity.jailbreakTitle', defaultMessage: 'Enable Jailbreak/Root Protection:'}),
help_text: defineMessage({id: 'admin.mobileSecurity.jailbreakDescription', defaultMessage: 'Prevents access to the app on devices detected as jailbroken or rooted. If a device fails the security check, users will be denied access or prompted to switch to a compliant server.'}),
},
{
type: 'bool',
key: 'NativeAppSettings.MobileEnableSecureFilePreview',
label: defineMessage({id: 'admin.mobileSecurity.secureFilePreviewTitle', defaultMessage: 'Enable Secure File Preview Mode:'}),
help_text: defineMessage({id: 'admin.mobileSecurity.secureFilePreviewDescription', defaultMessage: 'Prevents file downloads, previews, and sharing for most file types, even if {mobileAllowDownloads} is enabled. Allows in-app previews for PDFs, videos, and images only. Files are stored temporarily in the apps cache and cannot be exported or shared.'}),
help_text_values: {
mobileAllowDownloads: (
<a href='../site_config/file_sharing_downloads'>
<b>
<FormattedMessage
id='admin.mobileSecurity.mobileAllowDownloads'
defaultMessage='Site Configuration > File Sharing and Downloads > Allow File Downloads on Mobile'
/>
</b>
</a>
),
},
isHidden: it.not(it.minLicenseTier(LicenseSkus.EnterpriseAdvanced)),
},
{
type: 'bool',
key: 'NativeAppSettings.MobileAllowPdfLinkNavigation',
label: defineMessage({id: 'admin.mobileSecurity.allowPdfLinkNavigationTitle', defaultMessage: 'Allow Link Navigation in Secure PDFs:'}),
help_text: defineMessage({id: 'admin.mobileSecurity.allowPdfLinkNavigationDescription', defaultMessage: 'Enables tapping links inside PDFs when Secure File Preview Mode is active. Links will open in the device browser or supported app. Has no effect when Secure File Preview Mode is disabled.'}),
isDisabled: it.stateIsFalse('NativeAppSettings.MobileEnableSecureFilePreview'),
isHidden: it.not(it.minLicenseTier(LicenseSkus.EnterpriseAdvanced)),
},
],
},
},

View file

@ -1729,12 +1729,17 @@
"admin.mfa.bannerDesc": "<link>Multi-factor authentication</link> is available for accounts with AD/LDAP or email login. If other login methods are used, MFA should be configured with the authentication provider.",
"admin.mobile_security_feature_discovery.copy": "Enable advanced security features like biometric authentication, screen capture prevention, and jailbreak/root detection for your mobile users.",
"admin.mobile_security_feature_discovery.title": "Enhance mobile app security with Mattermost Enterprise",
"admin.mobileSecurity.allowPdfLinkNavigationDescription": "Enables tapping links inside PDFs when Secure File Preview Mode is active. Links will open in the device browser or supported app. Has no effect when Secure File Preview Mode is disabled.",
"admin.mobileSecurity.allowPdfLinkNavigationTitle": "Allow Link Navigation in Secure PDFs:",
"admin.mobileSecurity.biometricsDescription": "Enforces biometric authentication (with PIN/passcode fallback) before accessing the app. Users will be prompted based on session activity and server switching rules.",
"admin.mobileSecurity.biometricsTitle": "Enable Biometric Authentication:",
"admin.mobileSecurity.jailbreakDescription": "Prevents access to the app on devices detected as jailbroken or rooted. If a device fails the security check, users will be denied access or prompted to switch to a compliant server.",
"admin.mobileSecurity.jailbreakTitle": "Enable Jailbreak/Root Protection:",
"admin.mobileSecurity.mobileAllowDownloads": "Site Configuration > File Sharing and Downloads > Allow File Downloads on Mobile",
"admin.mobileSecurity.screenCaptureDescription": "Blocks screenshots and screen recordings when using the mobile app. Screenshots will appear blank, and screen recordings will blur (iOS) or show a black screen (Android). Also applies when switching apps.",
"admin.mobileSecurity.screenCaptureTitle": "Prevent Screen Capture:",
"admin.mobileSecurity.secureFilePreviewDescription": "Prevents file downloads, previews, and sharing for most file types, even if {mobileAllowDownloads} is enabled. Allows in-app previews for PDFs, videos, and images only. Files are stored temporarily in the apps cache and cannot be exported or shared.",
"admin.mobileSecurity.secureFilePreviewTitle": "Enable Secure File Preview Mode:",
"admin.mobileSecurity.title": "Mobile Security",
"admin.nav.administratorsGuide": "Administrator's Guide",
"admin.nav.commercialSupport": "Commercial Support",

View file

@ -787,6 +787,8 @@ export type NativeAppSettings = {
MobileEnableBiometrics: boolean;
MobilePreventScreenCapture: boolean;
MobileJailbreakProtection: boolean;
MobileEnableSecureFilePreview: boolean;
MobileAllowPdfLinkNavigation: boolean;
};
export type ClusterSettings = {