Simulator's permissions

Enabling the permissions to the PhotoLibrary.framework, Photo.framework or any other framework which need authorization for the user can be problematic during running your unit tests. It might be nice to be able to pre-allow or disallow access for applications without user intervention to run the tests.

There are several ways in which this problem can be solved. One of the most simple is stubbing the access to these frameworks using dependency injection. I’m not going to go further in that aspect as isn’t the purpose of this article.

In this article, I’ll cover how we can disable the permissions when we run our test in the simulator in a completely different way.

TCC.db

TCC.db is an SQLite3 database, located in the user’s developer library in the simulator. This database contains between other information the allowance/disallowance to these frameworks.

~/Library/Developer/CoreSimulator/Devices/$DEVICE_ID/data/Library/

Let’s take a look with the built-in sqlite3 command-line tool inside the database:

> sqlite3 TCC.db
SQLite version 3.19.3 2017-06-27 16:48:08
Enter ".help" for usage hints.

sqlite> .tables
access            access_times      admin
access_overrides  active_policy     policies

There are several tables inside the database, but we are going to focus specifically on the access table. Let’s make a query to the table to see its content:

sqlite> .dump access
PRAGMA foreign_keys=OFF;
BEGIN TRANSACTION;
CREATE TABLE access (service TEXT NOT NULL, client TEXT NOT NULL, client_type INTEGER NOT NULL, allowed	INTEGER	NOT NULL, prompt_count INTEGER NOT NULL, 	csreq BLOB, policy_id INTEGER, 	PRIMARY KEY (service, client, client_type), FOREIGN KEY (policy_id) REFERENCES policies(id) ON DELETE CASCADE ON UPDATE CASCADE);
INSERT INTO access VALUES('kTCCServiceUbiquity','com.apple.PassKitCore',0,1,1,NULL,NULL);
INSERT INTO access VALUES('kTCCServiceUbiquity','com.apple.CloudDocs.MobileDocumentsFileProvider',0,1,1,NULL,NULL);
INSERT INTO access VALUES('kTCCServiceAddressBook','de.socialbit.SwiftAddressBookExample',0,1,0,0,0);
INSERT INTO access VALUES('kTCCServicePhotos','com.ExampleCompany.company',0,1,0,0,0);
INSERT INTO access VALUES('kTCCServiceCamera','com.ExampleCompany.company',0,1,0,0,0);
COMMIT;

There are several columns in that table but let’s pay attention specifically to three of them:

It’s pretty trivial to update this database ourselves directly if we want to enable the access to any of these services, but we will talk about that later.

Services

In the service column we can find several services that normally we use daily:

As you might be already figured out the names are pretty intuitive and represent the name of the most used services in any iOS app. I didn’t list all the available services above, there are several more 🧐 out there.

Allowing access to the services.

To be able to modify the access table I modified a small script in this SO question.

#!/usr/bin/perl

$currentUserID = `id -un`;
chomp($currentUserID);
$folderLocations = `find "/Users/$currentUserID/Library/Developer/CoreSimulator/Devices" -name TCC`;

while($folderLocations =~ /(..*)/g) {
    `sqlite3 "$1/TCC.db" "insert or replace into access values('kTCCServicePhotos','BundlerIdentifier', 0, 1, 0, 0, 0)"`;
}

Let’s go through the steps in the script:

  1. We get the current logged username with id -un.
  2. We remove any trailing string in the saved $currentUserID.
  3. We get all the folder locations in which exist a TCC.db
  4. We apply regex to match the database name and then we modify the database table access to allow access to the Photos.framework.

To run the script from the console you should make it executable running:

chmod a+x disable-permissions.pl

I used the name disable-permissions, but you can feel free to put anyone you want.

Run Script Phase

Now we have the script we can run it from the console and it will enable/disable any permission we want. But would be cool if this script could run as part of our build process but only when we run our tests 🤔 ?. Guess what? We can do it using the Run Script Phase in Xcode.

In my previous article, I explained how to set different configurations for your testing environment. Once we have defined the Testing configuration in our project we can create a new Run Script Phase to run our defined code as part of the build process.

Go to the Project’s Navigator and select your project target. Select Build Phases and click the + button to add a new Run Script Phase:

As we want to disable the permissions only for the testing configuration we need to include the following before running the script:

if [ "${CONFIGURATION}" = "Testing" ]; then
    ./scripts/disable-permissions.pl
fi

I keep my scripts in a scripts folder in the root directory of the app to keep everything organized, pretty much you need to change the locations regarding the one you created. Now every time we build our project before running our unit tests the script would be executed and the permissions enabled for each service we defined in the script.

Libraries

While I was researching I found two good libraries using similar approaches. There are both written in Objective-C, but who knows maybe soon we can see a Swift port for any of these:

Conclusion

We covered how to pre-allow the permissions in an app to be able to run the unit tests and how to execute it automatically as part of a Run Script Phase.

Thanks for reading! 🎉.

· swift, ios, simulator