mattermost-mobile/detox/utils/disable_ios_autofill.js

324 lines
11 KiB
JavaScript

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
/* eslint-disable no-console */
const os = require('os');
const path = require('path');
const shell = require('shelljs');
// Parse command line arguments
const args = process.argv.slice(2);
let simulatorId = null;
for (let i = 0; i < args.length; i++) {
if ((args[i] === '--simulator-id' || args[i] === '-s') && args[i + 1]) {
simulatorId = args[i + 1];
break;
}
}
function getSimulators() {
const result = shell.exec('xcrun simctl list devices', {silent: true});
if (result.code !== 0) {
console.error('Error: Failed to list iOS simulators');
console.error('Make sure Xcode is installed and xcrun is available');
process.exit(1);
}
const simulators = [];
const lines = result.stdout.split('\n');
let currentOS = '';
for (const line of lines) {
// Match iOS version headers like "-- iOS 17.2 --"
const osMatch = line.match(/-- (iOS [0-9.]+) --/);
if (osMatch) {
currentOS = osMatch[1];
continue;
}
// Match simulator lines like " iPhone 15 Pro (A9A6D652-D75B-4C3A-9CD4-C6BA5E76C6F4) (Shutdown)"
const simMatch = line.match(/^\s+(.+?)\s+\(([A-F0-9-]{36})\)\s+\((Booted|Shutdown|Creating|Booting)\)/);
if (simMatch && currentOS) {
simulators.push({
name: simMatch[1],
udid: simMatch[2],
state: simMatch[3],
os: currentOS,
});
}
}
return simulators;
}
function disablePasswordAutofill(udid, simulator) {
const settingsDir = path.join(
os.homedir(),
'Library/Developer/CoreSimulator/Devices',
udid,
'data/Containers/Shared/SystemGroup/systemgroup.com.apple.configurationprofiles/Library/ConfigurationProfiles',
);
const settingsPlist = path.join(settingsDir, 'UserSettings.plist');
console.log(`\nDisabling password autofill for simulator ${udid}...`);
// Check if file exists
if (!shell.test('-f', settingsPlist)) {
console.log('UserSettings.plist not found, creating it...');
// Ensure directory exists
shell.mkdir('-p', settingsDir);
// Create empty plist file
const result = shell.exec(`plutil -create xml1 "${settingsPlist}"`, {silent: true});
if (result.code !== 0) {
console.error(`Error: Failed to create UserSettings.plist at ${settingsPlist}`);
console.error(result.stderr);
return false;
}
}
// Define all keys to set
const keysToSet = [
{
path: 'restrictedBool.allowPasswordAutoFill.value',
type: 'bool',
value: 'NO',
description: 'allowPasswordAutoFill',
},
{
path: 'restrictedBool.allowPasswordAutoFillWithStrongPassword.value',
type: 'bool',
value: 'NO',
description: 'allowPasswordAutoFillWithStrongPassword (iOS 26+)',
},
{
path: 'restrictedBool.forceDisablePasswordAutoFill.value',
type: 'bool',
value: 'YES',
description: 'forceDisablePasswordAutoFill',
},
{
path: 'restrictedBool.allowiCloudKeychain.value',
type: 'bool',
value: 'NO',
description: 'allowiCloudKeychain',
},
{
path: 'restrictedValue.passwordAutoFillPasswords.value',
type: 'integer',
value: '0',
description: 'passwordAutoFillPasswords',
},
{
path: 'restrictedValue.allowAutoFillAuthenticationUI.value',
type: 'integer',
value: '0',
description: 'allowAutoFillAuthenticationUI',
},
];
let successCount = 0;
for (const key of keysToSet) {
console.log(`Setting ${key.description}...`);
// Try to replace the value directly first
let result = shell.exec(
`plutil -replace ${key.path} -${key.type} ${key.value} "${settingsPlist}"`,
{silent: true},
);
if (result.code === 0) {
successCount++;
continue;
}
// Direct replace failed, need to create the key structure
const pathParts = key.path.split('.');
const rootKey = pathParts[0]; // restrictedBool or restrictedValue
const middleKey = pathParts[1]; // e.g., allowPasswordAutoFill
// Ensure root dictionary exists (restrictedBool or restrictedValue)
const checkRoot = shell.exec(
`plutil -extract ${rootKey} xml1 -o - "${settingsPlist}" 2>/dev/null`,
{silent: true},
);
if (checkRoot.code !== 0) {
result = shell.exec(`plutil -insert ${rootKey} -dictionary "${settingsPlist}"`, {silent: true});
if (result.code !== 0) {
console.error(`⚠️ Failed to insert ${rootKey} dictionary`);
console.error(result.stderr);
continue;
}
}
// Ensure middle dictionary exists (e.g., allowPasswordAutoFill)
const checkMiddle = shell.exec(
`plutil -extract ${rootKey}.${middleKey} xml1 -o - "${settingsPlist}" 2>/dev/null`,
{silent: true},
);
if (checkMiddle.code !== 0) {
result = shell.exec(
`plutil -insert ${rootKey}.${middleKey} -dictionary "${settingsPlist}"`,
{silent: true},
);
if (result.code !== 0) {
console.error(`⚠️ Failed to insert ${rootKey}.${middleKey} dictionary`);
console.error(result.stderr);
continue;
}
}
// Set the value (insert or replace)
const checkValue = shell.exec(
`plutil -extract ${key.path} xml1 -o - "${settingsPlist}" 2>/dev/null`,
{silent: true},
);
if (checkValue.code === 0) {
// Value exists, replace it
result = shell.exec(
`plutil -replace ${key.path} -${key.type} ${key.value} "${settingsPlist}"`,
{silent: true},
);
} else {
// Value doesn't exist, insert it
result = shell.exec(
`plutil -insert ${key.path} -${key.type} ${key.value} "${settingsPlist}"`,
{silent: true},
);
}
if (result.code === 0) {
successCount++;
} else {
console.error(`⚠️ Failed to set ${key.description}`);
console.error(result.stderr);
}
}
// Verify that all keys were actually written to the plist
console.log('\nVerifying restriction keys...');
let verifiedCount = 0;
for (const key of keysToSet) {
const checkResult = shell.exec(
`plutil -extract ${key.path} xml1 -o - "${settingsPlist}" 2>/dev/null`,
{silent: true},
);
if (checkResult.code === 0) {
// Extract just the value for logging
const valueMatch = checkResult.stdout.match(/<(true|false|integer)>([^<]*)<\/(true|false|integer)>/);
if (valueMatch) {
console.log(`${key.description}: ${valueMatch[0]}`);
verifiedCount++;
}
} else {
console.log(`${key.description}: MISSING or FAILED`);
}
}
if (verifiedCount === keysToSet.length) {
console.log(`\n✅ All ${keysToSet.length} restriction keys verified in plist`);
} else {
console.error(`\n⚠️ Only ${verifiedCount}/${keysToSet.length} keys verified`);
}
if (successCount === keysToSet.length) {
console.log(`✅ Password autofill disabled successfully (${keysToSet.length} restriction keys set)`);
return true;
} else if (successCount > 0) {
console.log(`⚠️ Partially successful: ${successCount}/${keysToSet.length} keys set`);
// Check if this is iOS 26+
const isIOS26Plus = simulator && simulator.os &&
parseFloat(simulator.os.replace('iOS ', '')) >= 26;
if (isIOS26Plus) {
console.error('⚠️ CRITICAL: iOS 26+ requires ALL 6 keys to fully disable password autofill');
console.error(' Missing keys may allow "Strong Password" or iCloud Keychain prompts');
}
return false;
}
console.error('⚠️ Failed to disable password autofill');
return false;
}
async function main() {
console.log('iOS Simulator - Disable Password Autofill\n');
console.log('This tool disables password autofill on iOS simulators,');
console.log('which can interfere with Detox E2E test login flows.');
console.log('Sets 6 iOS restriction keys to fully disable autofill prompts.\n');
// Check if running on macOS
if (process.platform !== 'darwin') {
console.error('Error: This tool only works on macOS');
process.exit(1);
}
// Check if xcrun is available
if (!shell.which('xcrun')) {
console.error('Error: xcrun not found. Please install Xcode and command line tools');
process.exit(1);
}
// Get list of simulators
const simulators = getSimulators();
if (simulators.length === 0) {
console.error('Error: No iOS simulators found');
console.error('Create a simulator in Xcode first');
process.exit(1);
}
let selectedSimulator;
if (simulatorId) {
// Use provided simulator ID
selectedSimulator = simulators.find((sim) => sim.udid === simulatorId);
if (!selectedSimulator) {
console.error(`Error: Simulator with ID ${simulatorId} not found`);
process.exit(1);
}
console.log(`Target: ${selectedSimulator.name} (${selectedSimulator.os})`);
console.log(`Using simulator: ${selectedSimulator.name} (${selectedSimulator.os})`);
} else {
// Automatically select iPhone 17 Pro with iOS 26.2
selectedSimulator = simulators.find((sim) =>
sim.name === 'iPhone 17 Pro' &&
sim.os === 'iOS 26.2',
);
if (!selectedSimulator) {
console.error('Error: iPhone 17 Pro (iOS 26.2) simulator not found');
console.error('Please create this simulator in Xcode first.');
console.error('\nAvailable simulators:');
simulators.forEach((sim) => {
const stateIndicator = sim.state === 'Booted' ? '🟢' : '⚪';
console.error(` ${stateIndicator} ${sim.name} (${sim.os}) - ${sim.state}`);
});
process.exit(1);
}
console.log(`Target: ${selectedSimulator.name} (${selectedSimulator.os})`);
console.log(`Using simulator: ${selectedSimulator.name} (${selectedSimulator.os})`);
}
// Apply the setting
const success = disablePasswordAutofill(selectedSimulator.udid, selectedSimulator);
process.exit(success ? 0 : 1);
}
main();