Skip to content

Commit

Permalink
use wix method for some services
Browse files Browse the repository at this point in the history
  • Loading branch information
KazuCocoa committed Nov 25, 2023
1 parent 2eff9b6 commit c6aa895
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 30 deletions.
6 changes: 5 additions & 1 deletion .github/workflows/functional-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,11 @@ jobs:
- uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: "${{ matrix.xcodeVersion }}"
- run: xcrun simctl list devices available
- run: |
brew update
brew tap wix/brew
brew install applesimutils
xcrun simctl list devices available
name: Install Utilities
- run: npm install
name: Install dev dependencies
Expand Down
3 changes: 3 additions & 0 deletions NOTICE.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
This product uses [applesimutils](https://github.com/wix/AppleSimulatorUtils) software written by
Copyright 2017 Wix Engineering <https://wix.com> under MIT license

This product uses [idb](https://github.com/facebook/idb) software written by
Copyright (c) 2019-present, Facebook, Inc. <https://facebook.com> under MIT License
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ The following tools and utilities are not mandatory, but could be used by the ap

- [Mobile Native Foundation](https://github.com/MobileNativeFoundation)
- [IDB](https://github.com/facebook/idb)
- [AppleSimulatorUtils](https://github.com/wix/AppleSimulatorUtils)
- For `contacts`, `camera`, `faceid`, `health`, `homekit`, `notifications`, `speech` and `userTracking` permissions

### Xcode and iOS versions

Expand Down
132 changes: 104 additions & 28 deletions lib/extensions/permissions.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import log from '../logger';
import _ from 'lodash';
import { fs, util } from '@appium/support';
import { exec } from 'teen_process';
import path from 'path';
import B from 'bluebird';

const STATUS = Object.freeze({
UNSET: 'unset',
Expand All @@ -10,6 +12,22 @@ const STATUS = Object.freeze({
LIMITED: 'limited',
});

const WIX_SIM_UTILS = 'applesimutils';

const WIX_SERVICE_NAMES = [
// 'contacts' permission does not work well with the privacy command while the help description addresses the command support 'contacts'
// Confirmed up to with Xcode 15 and iOS 17
`contacts`,

// Below services have not supported by the privacy command by Apple yet.
'camera',
'faceid',
'health',
'homekit',
'notifications',
'speech',
'userTracking'
];

const SERVICES = Object.freeze({
calendar: 'kTCCServiceCalendar',
Expand All @@ -35,6 +53,10 @@ function toInternalServiceName (serviceName) {
);
}

function formatStatus (status) {
return [STATUS.UNSET, STATUS.NO].includes(status) ? _.toUpper(status) : status;
}

/**
* Runs a command line sqlite3 query
*
Expand All @@ -53,51 +75,104 @@ async function execSQLiteQuery (db, query) {
}
}

async function execWix (args) {
try {
await fs.which(WIX_SIM_UTILS);
} catch (e) {
throw new Error(
`${WIX_SIM_UTILS} binary has not been found in your PATH. ` +
`Please install it ('brew tap wix/brew && brew install wix/brew/applesimutils') to ` +
`be able to change application permissions`
);
}

log.debug(`Executing: ${WIX_SIM_UTILS} ${util.quote(args)}`);
try {
const {stdout} = await exec(WIX_SIM_UTILS, args);
log.debug(`Command output: ${stdout}`);
return stdout;
} catch (e) {
throw new Error(`Cannot execute "${WIX_SIM_UTILS} ${util.quote(args)}". Original error: ${e.stderr || e.message}`);
}
}

/**
* Sets permissions for the given application
*
* @param {import('node-simctl').Simctl} simctl - node-simctl object.
* @param {string} udid - udid of the target simulator device.
* @param {string} bundleId - bundle identifier of the target application.
* @param {Object} permissionsMapping - An object, where keys are service names
* and values are corresponding state values. See the result of `xcrun simctl privacy`
* and values are corresponding state values. Services listed in WIX_SERVICE_NAMES
* will be set by WIX_SIM_UTILS otherwise via `xcrun simctl privacy` command by Apple.
* See the result of `xcrun simctl privacy` and https://github.com/wix/AppleSimulatorUtils
* for more details on available service names and statuses.
* @throws {Error} If there was an error while changing permissions.
*/
async function setAccess (simctl, bundleId, permissionsMapping) {
async function setAccess (simctl, udid, bundleId, permissionsMapping) {
const wixPermissions = {};

const grantPermissions = [];
const revokePermissions = [];
const resetPermissions = [];

for (const serviceName in permissionsMapping) {
switch (permissionsMapping[serviceName]) {
case STATUS.YES:
grantPermissions.push(serviceName);
break;
case STATUS.NO:
revokePermissions.push(serviceName);
break;
case STATUS.UNSET:
resetPermissions.push(serviceName);
break;
default:
log.warn(`${serviceName} does not support ${permissionsMapping[serviceName]}. Please specify yes, no or reset.`);
};
if (WIX_SERVICE_NAMES.includes(serviceName)) {
wixPermissions[serviceName] = permissionsMapping[serviceName];
} else {
switch (permissionsMapping[serviceName]) {
case STATUS.YES:
grantPermissions.push(serviceName);
break;
case STATUS.NO:
revokePermissions.push(serviceName);
break;
case STATUS.UNSET:
resetPermissions.push(serviceName);
break;
default:
log.warn(`${serviceName} does not support ${permissionsMapping[serviceName]}. Please specify yes, no or unset.`);
};
}
}

log.debug(`Granting permissions for ${bundleId} as ${JSON.stringify(grantPermissions)}`);
log.debug(`Revoking permissions for ${bundleId} as ${JSON.stringify(revokePermissions)}`);
log.debug(`Resetting permissions for ${bundleId} as ${JSON.stringify(resetPermissions)}`);
let permissionPromises = [];

for (const action of grantPermissions) {
await simctl.grantPermission(bundleId, action);
if (!_.isEmpty(grantPermissions)) {
log.debug(`Granting permissions for ${bundleId} as ${JSON.stringify(grantPermissions)}`);
for (const action of grantPermissions) {
permissionPromises.push(simctl.grantPermission(bundleId, action));
}
}

for (const action of revokePermissions) {
await simctl.revokePermission(bundleId, action);
if (!_.isEmpty(revokePermissions)) {
log.debug(`Revoking permissions for ${bundleId} as ${JSON.stringify(grantPermissions)}`);
for (const action of revokePermissions) {
permissionPromises.push(simctl.revokePermission(bundleId, action));
}
}

for (const action of resetPermissions) {
await simctl.resetPermission(bundleId, action);
if (!_.isEmpty(resetPermissions)) {
log.debug(`Revoking permissions for ${bundleId} as ${JSON.stringify(resetPermissions)}`);
for (const action of resetPermissions) {
permissionPromises.push(simctl.resetPermission(bundleId, action));
}
}

if (!_.isEmpty(permissionPromises)) {
B.all(permissionPromises);
}

if (!_.isEmpty(wixPermissions)) {
log.debug(`Setting permissions for ${bundleId} wit ${WIX_SIM_UTILS} as ${JSON.stringify(wixPermissions)}`);
const permissionsArg = _.toPairs(wixPermissions)
.map((x) => `${x[0]}=${formatStatus(x[1])}`)
.join(',');
await execWix([
'--byId', udid,
'--bundle', bundleId,
'--setPermissions', permissionsArg,
]);
}

return true;
Expand Down Expand Up @@ -147,8 +222,8 @@ async function getAccess (bundleId, serviceName, simDataRoot) {
const extensions = {};

/**
* Sets the particular permission to the application bundle. See
* xcrun simctl privacy for more details on the available service names and statuses.
* Sets the particular permission to the application bundle. See https://github.com/wix/AppleSimulatorUtils
* or `xcrun simctl privacy` for more details on the available service names and statuses.
*
* @param {string} bundleId - Application bundle identifier.
* @param {string} permission - Service name to be set.
Expand All @@ -165,12 +240,13 @@ extensions.setPermission = async function setPermission (bundleId, permission, v
* @param {string} bundleId - Application bundle identifier.
* @param {Object} permissionsMapping - A mapping where kays
* are service names and values are their corresponding status values.
* See `xcrun simctl privacy` for more details on available service names and statuses.
* See https://github.com/wix/AppleSimulatorUtils or `xcrun simctl privacy`
* for more details on available service names and statuses.
* @throws {Error} If there was an error while changing permissions.
*/
extensions.setPermissions = async function setPermissions (bundleId, permissionsMapping) {
log.debug(`Setting access for '${bundleId}': ${JSON.stringify(permissionsMapping, null, 2)}`);
await setAccess(this.simctl, bundleId, permissionsMapping);
await setAccess(this.simctl, this.udid, bundleId, permissionsMapping);
};

/**
Expand Down
15 changes: 14 additions & 1 deletion test/functional/simulator-e2e-specs.js
Original file line number Diff line number Diff line change
Expand Up @@ -351,14 +351,27 @@ describe('advanced features', function () {
});

describe('Permission', function () {
it('should set and get', async function () {
it('should set and get with simctrl privacy command', async function () {
await sim.setPermission('com.apple.Maps', 'microphone', 'yes');
await sim.getPermission('com.apple.Maps', 'microphone').should.eventually.eql('yes');
await sim.setPermission('com.apple.Maps', 'microphone', 'no');
await sim.getPermission('com.apple.Maps', 'microphone').should.eventually.eql('no');
await sim.setPermission('com.apple.Maps', 'microphone', 'unset');
await sim.getPermission('com.apple.Maps', 'microphone').should.eventually.eql('unset');
});

it('should set and get with wix command', async function () {
await sim.setPermission('com.apple.Maps', 'contacts', 'yes');
await sim.getPermission('com.apple.Maps', 'contacts').should.eventually.eql('yes');
await sim.setPermission('com.apple.Maps', 'contacts', 'no');
await sim.getPermission('com.apple.Maps', 'contacts').should.eventually.eql('no');

// unset sets as 'no'
await sim.setPermission('com.apple.Maps', 'contacts', 'yes');
await sim.getPermission('com.apple.Maps', 'contacts').should.eventually.eql('yes');
await sim.setPermission('com.apple.Maps', 'contacts', 'unset');
await sim.getPermission('com.apple.Maps', 'contacts').should.eventually.eql('no');
});
});
});

Expand Down

0 comments on commit c6aa895

Please sign in to comment.