『入る学科間違えた高専生』の日記

プログラミングのコードを書いたりする予定です。あとは日記等。あといつまで高専生やねん

iOS Simulatorの写真ライブラリとかの権限を自動で与えたい人々

はじめに

タイトルなんか意味わからんことになったけど、

import Photos

PHPhotoLibrary.requestAuthorization {
    if case .authorized = $0 {
        print("アクセスが許可された!")
    } else {
        print("アクセスを許可してね!")
    }
}

みたいなコード書いて、 UT とかでテストしたいときとかあるじゃないですか?
そういうときに GUI のポップアップだされても人間がテストしてるときはある程度勝手に許容する動作をしてくれると思うのですが、ロボットはそんなことしてくれないのでそこをやってあげることできないかと思って調べたらできそうだったのでやってみてできたので記事にまとめるやつ。

ちなみに Xcode 11.4 からは今から紹介するまどろっこしい操作をしなくてもよくなったのでありがたい!!後で紹介します。

もう Xcode 11.3.x は触らないぜ!ヒャッハー!!な方はこちらを押すと Xcode 11.4 の話まですっ飛びます

環境

TCC との出会い

https://www.victorsigler.com/2018/01/29/simulator-permissions.htmlwww.victorsigler.com

こちらの記事で TCC というものに出会いました。

TCC ってそもそもなんだ?

blog.lufia.org

TCC: A Quick Primerによると、Transparency, Consent, and Controlのことで、アプリケーションがユーザデータへ無制限にアクセスさせないための保護機構のようです。

上の記事を引用しました。なるほど。

実際に組み込み

先程の記事を参考にさせていただいて Unit Test Bundle の Build Phase にスクリプトを追加しました。
今回は、 はじめに で紹介した写真ライブラリへのアクセス権限を付与します。

  • Name: Disable Permission
  • Shell: /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)"`;
}

ちなみに XcodeGen でプロジェクトを構成させている場合は以下に配置しておけば問題ありませんでした。

  • project.yml
targets:

  # somethings

  AppTests:
    preBuildScripts:
      - script: |
          $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)"`;
          }
        name: Disable Permission
        shell: /usr/bin/perl

コード見た感じ、あれって SQLite3 で管理されてるのかって思って確認してみました。

$ cd ~/Library/Developer/CoreSimulator/Devices/{DEVICE_ID}/data/Library/TCC
$ sqlite3 TCC.db ".tables"
access            active_policy     expired
access_overrides  admin             policies
$ sqlite3 TCC.db "SELECT service, client FROM access;"
kTCCServiceLiverpool|com.apple.VoiceShortcuts
kTCCServiceLiverpool|com.apple.accessibility.AccessibilityUIServer
kTCCServiceUbiquity|com.apple.PassKitCore

という感じで始めから3つ権限が与えられているようです。

ちなみに {DEVICE_ID} は以下の画像のように Xcode から参照してください!

f:id:nanashinodonbee:20200401235052p:plain

テストコードを書いてみよう

本当にめっちゃくちゃ簡単にテストコード書いてみました。

import Photos
import XCTest
@testable import App

class AppTests: XCTestCase {

    func testAuthorization() {
        let status = PHPhotoLibrary.authorizationStatus()
        XCTAssert(status == .authorized)
    }
}

さて、

f:id:nanashinodonbee:20200402000609p:plain

失敗!!!

は?

$ sqlite3 TCC.db "SELECT client FROM access;"
com.apple.VoiceShortcuts
com.apple.accessibility.AccessibilityUIServer
com.apple.PassKitCore

増えてない。(ぶっちゃけ解決してないとこの記事書いてない)

github.com

調べてると GitHub でこんな Issue が見つかりました。 entitlements に何やら怪しいキーバリューを追加しろというらしい。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>com.apple.private.tcc.allow.overridable</key>
    <array>
        <string>kTCCServicePhotos</string>
    </array>
</dict>
</plist>

とりあえずこんな感じで追加した。 Build SettingsCode Signing Entitlements に作成した entitlements のファイルパスを設定することを忘れずに。今回は App/App.entitlements に作成しました。

f:id:nanashinodonbee:20200402003404p:plain

やったぜ!!

DB にも追加されているか確認

$ sqlite3 TCC.db "SELECT service, client FROM access;"
kTCCServiceLiverpool|com.apple.VoiceShortcuts
kTCCServiceLiverpool|com.apple.accessibility.AccessibilityUIServer
kTCCServicePhotos|moe.nnsnodnb.App
kTCCServiceUbiquity|com.apple.PassKitCore

ちなみに com.apple.private.tcc.allow.overridable のキーバリューが入った entitlements を設定していると以下のようにビルドはおろか Provisioning Profile すら生成してくれないので気をつける必要があります!

f:id:nanashinodonbee:20200402002821p:plain

CI 環境で UT が始まる前に置き換えるとかで対応しています。

Xcode 11.4 からの操作

Xcode 11.4 Release Notes | Apple Developer Documentation

まず Release Notes を読んでたらなんか追加されてた。

$ xcrun simctl privacy <device> <action> <service> [<bundle identifier>]

なんか意味のわからんコード書かずに済むらしい。これは熱い!!

ここからの操作は Xcode 11.4 で行っています。 Command Line Tools もそれに合わせています。

先程までに紹介した、 Build PhaseRun Script (Disable Permission) コードを以下に変更しました。

$ xcrun simctl privacy <device> grant photos moe.nnsnodnb.App

ちなみに App/App.entitlements は今回このためだけに作っているので削除しました。

f:id:nanashinodonbee:20200402004516p:plain

画像の変更が App.entitlements がないだけでちょっとわかりづらいですが、ちゃんと動きました!やった!!

こちらも DB にも追加されているか確認

$ sqlite3 TCC.db "SELECT service, client FROM access;"
kTCCServiceLiverpool|com.apple.VoiceShortcuts
kTCCServiceLiverpool|com.apple.accessibility.AccessibilityUIServer
kTCCServicePhotos|moe.nnsnodnb.App
kTCCServiceUbiquity|com.apple.PassKitCore

この service *1 に当てはまるものは --help を実行することで閲覧可能でした。

Grant, revoke, or reset privacy and permissions
Usage: simctl privacy <device> <action> <service> [<bundle identifier>]

    action
         The action to take:
             grant - Grant access without prompting. Requires bundle identifier.
             revoke - Revoke access, denying all use of the service. Requires bundle identifier.
             reset - Reset access, prompting on next use. Bundle identifier optional.
         Some permission changes will terminate the application if running.
    service
         The service:
             all - Apply the action to all services.
             calendar - Allow access to calendar.
             contacts-limited - Allow access to basic contact info.
             contacts - Allow access to full contact details.
             location - Allow access to location services when app is in use.
             location-always - Allow access to location services at all times.
             photos-add - Allow adding photos to the photo library.
             photos - Allow full access to the photo library.
             media-library - Allow access to the media library.
             microphone - Allow access to audio input.
             motion - Allow access to motion and fitness data.
             reminders - Allow access to reminders.
             siri - Allow use of the app with Siri.
    bundle identifier
         The bundle identifier of the target application.

Examples:
    reset all permissions: privacy <device> reset all
    grant test host photo permissions: privacy <device> grant photos com.example.app.test-host

Warning:
Normally applications must have valid Info.plist usage description keys and follow the API guidelines to request access to services. Using this command to bypass those requirements can mask bugs.

all , calender , contacts-limited , contacts , location , location-always , photos-add , photos , media-library , microphone , motion , reminders , siri

以上です。

ヘルプの最後にも書かれているように Info.plist にそれぞれの権限の Usage description を書いてそれがちゃんと動くか後で確かめることを忘れないことも大切です。書いてないと問答無用でクラッシュしますからね。

あとは、 Beta のときにちょっと話題になってたプッシュ通知のテストがシミュレータでできるようになったよっていうやつとか熱いですよね。

qiita.com

最後に

今回は、写真ライブラリへのフルアクセス権限を iOS Simulator に与えるを題材に記事を書きました。(完全にメモ書き)

Xcode 11.4 になってから Simulator に対して熱い機能がリリースされてしまったので手に汗握ります。

あと、 Xcode 11.4 を入れてしまってからか Xcode 11.3.1 でよくわからんスクリプトを実行した場合 TCC.db に行が追加されずにそのままちゃんと動くという謎挙動をしたのでここは気が向いたら調べて見るかもしれません。(多分しない)

追記

そういえば、 Command Line からどうやって device_id を設定しておくんだ!?ってなったので、ついでなので simctliPhone 11 Pro Max の UDID を取り出してみました。
jq コマンドが入っていること前提ですが。

$ xcrun simctl list devices "iPhone 11 Pro Max" available -j | jq ".devices" | jq '.["com.apple.CoreSimulator.SimRuntime.iOS-13-4"][0].udid'

ちなみに Bitrse を使用する場合は GitHub に公式がシステムレポート出してくれてるのでこれをいい感じにやれば良さそうですね。
iOS13.4 の Simulator のレポートがハイライトされます↓(2020/5/8 13:30現在)

https://github.com/bitrise-io/bitrise.io/blob/master/system_reports/osx-xcode-11.4.x.log#L1114-L1125github.com

おまけ

TCC ってどんな種類があるんだろうって思ったので参考までに。

$ otool -V -s __TEXT __cstring /System/Library/PrivateFrameworks/TCC.framework/Versions/A/Resources/tccd | awk '{ print $2 }' | grep -E '^kTCCService.+' | sort | uniq
kTCCServiceAccessibility
kTCCServiceAddressBook
kTCCServiceAll
kTCCServiceAppleEvents
kTCCServiceBluetoothAlways
kTCCServiceBluetoothPeripheral
kTCCServiceBluetoothWhileInUse
kTCCServiceCalendar
kTCCServiceCalls
kTCCServiceCamera
kTCCServiceContactsFull
kTCCServiceContactsLimited
kTCCServiceDeveloperTool
kTCCServiceFaceID
kTCCServiceFacebook
kTCCServiceFileProviderDomain
kTCCServiceFileProviderPresence
kTCCServiceKeyboardNetwork
kTCCServiceLinkedIn
kTCCServiceListenEvent
kTCCServiceLiverpool
kTCCServiceMSO
kTCCServiceMediaLibrary
kTCCServiceMicrophone
kTCCServiceMotion
kTCCServicePhotos
kTCCServicePhotosAdd
kTCCServicePostEvent
kTCCServiceReminders
kTCCServiceScreenCapture
kTCCServiceSensorKitAmbientLightSensor
kTCCServiceSensorKitDeviceUsage
kTCCServiceSensorKitElevation
kTCCServiceSensorKitForegroundAppCategory
kTCCServiceSensorKitKeyboardMetrics
kTCCServiceSensorKitLocationMetrics
kTCCServiceSensorKitMessageUsage
kTCCServiceSensorKitMotion
kTCCServiceSensorKitMotionHeartRate
kTCCServiceSensorKitOdometer
kTCCServiceSensorKitPedometer
kTCCServiceSensorKitPhoneUsage
kTCCServiceSensorKitSpeechMetrics
kTCCServiceSensorKitStrideCalibration
kTCCServiceSensorKitWatchAmbientLightSensor
kTCCServiceSensorKitWatchFallStats
kTCCServiceSensorKitWatchForegroundAppCategory
kTCCServiceSensorKitWatchHeartRate
kTCCServiceSensorKitWatchMotion
kTCCServiceSensorKitWatchOnWristState
kTCCServiceSensorKitWatchPedometer
kTCCServiceSensorKitWatchSpeechMetrics
kTCCServiceShareKit
kTCCServiceSinaWeibo
kTCCServiceSiri
kTCCServiceSpeechRecognition
kTCCServiceSystemPolicyAllFiles
kTCCServiceSystemPolicyDesktopFolder
kTCCServiceSystemPolicyDeveloperFiles
kTCCServiceSystemPolicyDocumentsFolder
kTCCServiceSystemPolicyDownloadsFolder
kTCCServiceSystemPolicyNetworkVolumes
kTCCServiceSystemPolicyRemovableVolumes
kTCCServiceSystemPolicySysAdminFiles
kTCCServiceTencentWeibo
kTCCServiceTwitter
kTCCServiceUbiquity
kTCCServiceWillow

*1:今回試した photos の部分は service として CLI のサブコマンドとしては認識されているようなので service として話します