324 lines
11 KiB
JavaScript
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();
|