Note: This article is a work-in-progress. I was battling this issue and wanted to get some thoughts written down, and encourage Apple to run with this. Submitted as FB13584965.
Apple desperately needs a unified permissions API. An API that allows applications to check and request permission to access sensitive services in the OS, like photos or location. Right now it’s a hodgepodge of APIs, wherein some service have explicit methods for testing requesting access status, and others implicitly prompt the user and give little to no feedback to the caller. Often, the app must be relaunched after permissions have been granted.
This is a proposal for an extensible, pluggable API that all appleOS systems should adopt (well, after Apple implements it, of course).
High-Level Structure
I have a very limited knowledge of how Apple maintains a record of the user’s permission-granting
intent. This post describes the Transparency, Consent,
and Control utilities and database file. Not mentioned in that article is the tccd
dæmon. It is
probably the right entity to handle requests through this API, and to record the interactions in
its DB.
For the purposes of this discussion, we’ll refer to tccd
is the implementing process, despite
the fact that today it does nothing of the sort.
Each service with privacy or security concerns has a plug-in module that’s queried by the proposed permissions manager to define the permissions is needs in order to operate. It seems to me that this plug-in can be no more than a configuration file, and likely not require code.
Client API
The client API provides a shared manager, means for enumerating serices and their permissions, testing specific permissions, and requesting permissions.
A singleton TCCManager
object is available:
TCCManager.shared
Service Identifier
struct
TCCServiceIdentifier
{
public static let location = TCCServiceIdentifier("com.apple.locationservices")
public static let photos = TCCServiceIdentifier("com.apple.photos")
}
Each Apple framework declares its own service identifier:
extension
TCCServiceIdentifier
{
public static let eventTap = TCCServiceIdentifier("com.apple.eventTap")
}
The string value must match the string value in the Service Plug-In.
Permission
struct
TCCPermission
{
private(set) public let identifier : String
private(set) public let localizedName : String
private(set) public let granted : Bool
public static let photosAddMedia = TCCPermission("addMedia", service: .photos)
public static let photosReadMedia = TCCPermission("readMedia", service: .photos)
public static let photosDeleteMedia = TCCPermission("deleteMedia", service: .photos)
}
- How can we namespace the permissions under each Service?
Testing Permission
extension
TCCManager
{
public var services : [TCCService]
}
struct
TCCService
{
private(set) public let identifier : TCCServiceIdentifier
private(set) public var permissionsAvailable : [TCCPermission]
private(set) public var permissionsGranted : [TCCPermission]
}
Permissions need to work as an OptionSet, but only within a Service.
let mgr = TCCManager.shared
let photosService = mgr.get(service: .photos)
let canOpenPhotos = try photosService.hasPermission(.photosReadMedia)
How to describe a service that’s for a specific item, like a single photo? A URL? A resource UUID?
Notification
Clients can also be notified when permsissions are granted or revoked:
let mgr = TCCManager.shared
for await change: TCCPermissionChange in mgr.permissionsChanges
{
}
or
mgr.delegate = self
protocol
TCCManagerDelegate
{
func permissionChanged(change: TCCPermissionChange)
}
struct
TCCPermisisonChange
{
var granted: [TCCPermission]
var revoked: [TCCPermission]
}
Requesting Permission
let mgr = TCCManager.shared
let photosService = mgr.get(service: .photos)
let (grantedPermissions, disposition) = try await photosService.request(permissions: [.photosReadMedia, .photosWriteMedia])
or
try photosService.request(permissions: [.photosReadMedia, .photosWriteMedia])
{ inGrantedPermissions, inDisposition in // An option set of permissions that were granted.
}
Disposition
Generally right now the system only presents the permissions request UI to the user one time for any given
client or system, so as to not constantly pester the user. Instead the user should be given “Grant,” “Deny,”
and “Deny and Don’t Ask Again” choices. The disposition
result will indicate this.
Generally, a client should not pester the user with the modal system request dialog. But it can be very inconventient for the user to have to visit System Settings and root around for the specific permission they need to grant if they decide to do so after they’ve already denied it. So generally, apps should be able trigger the system UI whenver they need to (generally in response to a direct user action in the client’s UI).
An app that abuses this will get perma-banned by the user.
Service API
For now, I think a service can get by with a configuration file that defines the permissions it grants, constituting the service’s plug-in. This example shows multiple services being defined, but usually it’s one per file.
services:
- service: com.apple.eventTap
permissions:
- permission: com.apple.eventTap.listen
requiresAdmin: true
localizedName:
- en.US: "Listen to events"
- service: com.apple.photos
permissions:
- permission: com.apple.photos.readMedia
requiresAdmin: false
localizedName:
- en.US: "Access photos and videos"
- permission: com.apple.photos.addMedia
requiresAdmin: false
localizedName:
- en.US: "Add photos and videos"
- permission: com.apple.photos.deleteMedia
requiresAdmin: false
localizedName:
- en.US: "Delete photos and videos"
- Is yaml the best thing here? Most stuff like this from Apple is PLists. And there’s the newly=opensourced Pikl.
- How is this localized? Multiple files? Strings files from Xcode would smart.
- Many permissions are grouped under one name in the UI. For example, under Privacy & Security -> Accessibility (“Allow the applications below to control this computer”), a number of permissions are granted by turning the switch on for a given app that has made a request. The various APIs that make the request record different permissions in the TCC DB, but the UI facing the user is all under this Settings panel.
- How to signal current or recent use? E.g. location has an indicator next to each app that is using or recently used location data.