Do you know of any tutorial or sample code for a recent swift version
that covers this API, AuthorizationCreate, or something like it?
No. I’m certain that Apple hasn’t published anything like that — this is a very dark and dusty area of the system — and I’m no better at finding tutorials on the Internet than you are )-:
Having said that, I’m
very good at calling C APIs from Swift, so let’s take a stab at that…
The first thing to note is that an
AuthorizationRef is not reference counted, which makes it a challenge to use from Swift (if you follow
Swift Evolution you’ll see that move-only types are a commonly-requested feature, but we’re not there yet). So, for the moment, it’s best to wrap the type in a class:
Code Block final class QAuthorization { |
var ref: AuthorizationRef |
|
init(ref: AuthorizationRef) { |
self.ref = ref |
} |
} |
You can then add a convenience initialiser wrapped around
AuthorizationCreate:
Code Block extension QAuthorization { |
|
convenience init() throws { |
var refQ: AuthorizationRef? = nil |
let err = AuthorizationCreate(nil, nil, [], &refQ) |
guard err == errSecSuccess else { |
… throw an error here … |
} |
self.init(ref: refQ!) |
} |
} |
Now add a method to call
AuthorizationCopyRights:
Code Block extension QAuthorization { |
|
func checkRight(_ name: String, flags: AuthorizationFlags = [.interactionAllowed, .extendRights]) throws { |
let err = name.withCString { namePtr -> OSStatus in |
var item = AuthorizationItem(name: namePtr, valueLength: 0, value: nil, flags: 0) |
return withUnsafeMutablePointer(to: &item) { itemPtr -> OSStatus in |
var rights = AuthorizationRights(count: 1, items: itemPtr) |
return AuthorizationCopyRights(self.ref, &rights, nil, flags, nil) |
} |
} |
guard err == errSecSuccess else { |
… throw an error here … |
} |
} |
} |
Oh yeah, that’s scary! All this complexity is caused by Swift’s strict rules about pointer lifetimes. Specifically, lines 4 through 7 are there purely to build an
AuthorizationRights structure containing the right name string.
Finally, wrapping
AuthorizationRightSet is pretty straightforward:
Code Block extension QAuthorization { |
|
func setRight( |
name: String, |
definition: String, |
descriptionKey: String? = nil, |
bundle: Bundle? = nil, |
localeTableName: String? = nil |
) throws { |
// `NSBundle` and `CFBundle` are not toll-free bridged, so we have to |
// bounce through the URL. |
// |
// <https://developer.apple.com/library/archive/documentation/CoreFoundation/Conceptual/CFDesignConcepts/Articles/tollFreeBridgedTypes.html#//apple_ref/doc/uid/TP40010677-SW1> |
let bundleCF = bundle.map { |
CFBundleCreate(nil, $0.bundleURL as NSURL)! |
} |
let err = AuthorizationRightSet( |
self.ref, |
name, |
definition as NSString, |
descriptionKey.map { $0 as NSString }, |
bundleCF, |
localeTableName.map { $0 as NSString } |
) |
guard err == errSecSuccess else { |
… throw an error here … |
} |
} |
} |
This assumes that you want to map your right to an existing rule. If not, you’ll need a version that passes a dictionary to the
rightDefinition parameter of
AuthorizationRightSet.
And with that out of the way, let’s return to your specific questions:
I'm not trying to actually escalate permissions, or run a script as
root, or anything like that, I just want to force the entry of an
admin user/pass to protect an area of my app. Do I need to create my
own custom policy right for that?
That’s what I recommend. If you piggyback on top of an existing right then there’s no way for a sys admin to separate the two operations. For example, the parent might want to allow an older child to do your operation but block a younger child, and if you piggyback on
system.privelege.admin they can’t do that without given the older child full admin access.
If I'm testing while logged in as an admin, will the attempt to
authorize immediately succeed? Do I have to build my app and install
it on a non-admin user to see if it's working?
That very much depends on your right specification.
I'd like to prompt for the admin user/pass every time even if the
admin is logged in... Is this possible?
Yes. The trick here is to not include
shared in your right definition, so that your attempt to get the right won’t rely on credentials acquire via other means (such as the admin user being logged in).
Would you be willing to write up a snippet or two of Swift to cover
this use case?
I think the above qualifies (-:
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"