Change Authorization Dialog shown by AuthorizationCreate() - objective-c

Looking through Apples BetterAuthorizationSample and further Derivatives( http://www.stevestreeting.com/2011/11/25/escalating-privileges-on-mac-os-x-securely-and-without-using-deprecated-methods/ )
I am trying to make a small change to the application and gain better understanding of the whole Security & ServiceManagement framework.. Therefore I proceeded to add an a button which removes the installed Job through the inverse of SMJobBless - SMJobRemove(). Straightforward however the AuthorizationCreate() call displays a dialog that states and requests permission to install a helper and not remove it.
That's the dialog I get (by using kSMRightModifySystemDaemons). As you can see it says that my app tries to add a new helper tool. Which will confuse my users because the app actually tries to remove the installed helper tool.
I'm seeking to find knowledge on how this dialog is changed to reflect my actual action (Job Removal), There are also several other apps which seem to completely customize the dialog - showing their own Custom Label and Buttons..
BOOL doRemoveSystemTool(NSString* label, NSError** error)
{
BOOL result = NO;
AuthorizationItem authItem = { kSMRightModifySystemDaemons, 0, NULL, 0 };
AuthorizationRights authRights = { 1, &authItem };
AuthorizationFlags flags = kAuthorizationFlagDefaults |
kAuthorizationFlagInteractionAllowed |
kAuthorizationFlagPreAuthorize |
kAuthorizationFlagExtendRights;
AuthorizationRef authRef = NULL;
//Obtain authorization
OSStatus status = AuthorizationCreate(&authRights, kAuthorizationEmptyEnvironment, flags, &authRef);
if (status != errAuthorizationSuccess)
{
NSLog(#"Failed to create AuthorizationRef, return code %ld", (long)status);
} else
{
//We have authorization so proceed with removing the Job via SMJobRemove
result = SMJobRemove(kSMDomainSystemLaunchd, (CFStringRef)label, authRef, YES, (CFErrorRef *)error);
}
AuthorizationFree(authRef, kAuthorizationFlagDefaults);
return result;
}
I have experimented with the authItem changing to kSMRightModifySystemDaemons from kSMRightBlessPrivilegedHelper but all this did was change the dialogue to display 'Add' instead of 'Install'
Would greatly appreciate some assistance here...

I haven't used this before but found your question interesting so I did a little reading of Apple's documentation and based on that I wonder if setting up the environment with a kAuthorizationEnvironmentPrompt would do what you want?
From AuthorizationTags.h:
The name of the AuthorizationItem that should be passed into the environment
when specifying a invocation specific additional text. The value should be a
localized UTF8 string.
You'd create an AuthorizationItem with this and then an AuthorizationItemSet containing that, and then pass the set into the AuthorizationCreate call for the environment: parameter.
I'd try that.
The other idea I had reading the documentation was to have a command line tool that does the remove and authorize the execution of the command line tool ("SomethingSomethingHelper") which might be less confusing to the user (so using AuthorizationExecuteWithPrivileges or kAuthorizationRightExecute or whatever).

Related

Location authorization error even though I've asked for authorization in iOS 8 using swift

I have an app using the location services. If the app first starts, it ask the user for permission.
For some reason, if I tap on "Allow" I'll get this message:
Trying to start MapKit location updates without prompting for location authorization.
I know what this means, but I've set breakpoints all over my code and I am SURE that nothing tries to read the user location before it is allowed to do so.
Anyway, I seem to be missing something.
1) Is there a "common mistake" which one could do, something within the storyboard or so?
2) Will Apple reject an app that has such an error?
Thing is that the app works perfectly well, The only thing is that I see this message within the console. I don't know whether Apple will see this message too and if this would be a reason to reject an app..
if ([self.locationManager respondsToSelector:#selector(requestWhenInUseAuthorization)]) { // iOS8+
// Sending a message to avoid compile time error
[[UIApplication sharedApplication] sendAction:#selector(requestWhenInUseAuthorization)
to:self.locationManager
from:self
forEvent:nil];
} else {
[self.locationManager startUpdatingLocation];
}
}
I think you may need to include something like this for the requestWhenInUseAuthorization
Assume that you are using CLLocationManager. So did you make a strong reference to your locationManager object?
It seems to be a case when you requested location in local scope (variable) inside a function. Then trying to use MapKit, but locationManager object is already deallocated.
To solve that case, you should declare...
var locationManager = CLLocationManager()
... as an instance variable, then request authorization, and then using location services.
The problem was I have asked for authorization to use location services if the app is in use and I have determined the authorization like this:
func locationManager(manager: CLLocationManager!,
didChangeAuthorizationStatus status: CLAuthorizationStatus) {
switch status {
case CLAuthorizationStatus.Restricted:
locationStatus = GpsStatus.restricted
break
case CLAuthorizationStatus.Denied:
locationStatus = GpsStatus.denied
break
case CLAuthorizationStatus.NotDetermined:
locationStatus = GpsStatus.notDeterminded
break
default:
locationStatus = GpsStatus.allowed
break
}
}
This seems to be wrong, the error is gone if I explicitly check for CLAuthorizationStatus.AuthorizedWhenInUse:
func locationManager(manager: CLLocationManager!,
didChangeAuthorizationStatus status: CLAuthorizationStatus) {
switch status {
case CLAuthorizationStatus.Restricted:
locationStatus = GpsStatus.restricted
break
case CLAuthorizationStatus.Denied:
locationStatus = GpsStatus.denied
break
case CLAuthorizationStatus.NotDetermined:
locationStatus = GpsStatus.notDeterminded
break
case CLAuthorizationStatus.AuthorizedWhenInUse:
locationStatus = GpsStatus.allowed
default:
locationStatus = GpsStatus.notDeterminded
break
}
}
EDIT:
Seems like it's also a problem to add a TrackingLocationButton before I have the permission to use location services.
So do this
var userTrackingButton = MKUserTrackingBarButtonItem(mapView: self.mapView);
self.toolBar.items?.insert(userTrackingButton, atIndex: 0)
only if you have the permission
For iOS 8 you need to define the "Privacy - Location Usage Description" in the Info.plist.
Eg. NSLocationWhenInUseUsageDescription = "Use your location to show near by stores".
or
NSLocationAlwaysUsageDescription = "Use your location to show near by stores".
This key specifies the reason for accessing the user’s location information.

How can I suppress the autosave “The file has been changed by another application” alert?

I have a NSDocument subclass that presents a text document from disk. I’m trying to make it refresh automatically on detecting file changes on disk. I’ve overridden -presentedItemDidChange like this:
- (void)presentedItemDidChange
{
[super presentedItemDidChange];
// Ignoring bundles and error-handling for the moment.
NSData *newData = [NSData dataWithContentsOfURL:self.presentedItemURL];
self.textView.string = [[NSString alloc] initWithData:newData encoding:NSUTF8StringEncoding];
}
The UI refreshes fine when the file is changed in another application. The problem is, I get this dialog when I try to save the document in my application after it is modified by another app:
I kind of have an idea why this happens (not sure whether it’s correct): The modification time of the document is later (because it’s modified by another application) than the latest saved version in my app. But can I notify the autosaving system that I have done something with it and let it go away? Or am I doing things wrong when I refresh the document, and I should do it some other way to handle document versions correctly? I need to consider both external applications support or do not support autosave.
Thanks in advance.
#uranusjr's answer pointed me in the right direction -- only revertDocumentToSaved: wasn't exactly the right place.
override func presentedItemDidChange() {
dispatch_async(dispatch_get_main_queue()) {
self.reloadFromFile()
}
}
func reloadFromFile() {
guard let fileURL = self.fileURL else { return }
do {
try revertToContentsOfURL(fileURL, ofType: "YOUR TYPE HERE IF NECESSARY")
} catch {
// TODO handle error
print(error)
}
}
This simply reloads the file. readFromURL(url:, ofType:) (or the NSData/file wrapper based variants) is called and you can re-create your data structures from there.
Stumbled across the solution today (finally). You can “cheat” OS X into not warning about this by reverting the document (but not the file itself) before actually updating the internal data structure:
// Somehow read the updated data.
NSString *content = ...;
// Revert the document.
// This will discard any user input after the last document save,
// so you might want to present some UI here, like an NSAlert.
[self revertDocumentToSaved:self];
// Update the internal state.
self.content = content;
Now OS X will be happy when you save the document.

Firebase OSX simple login with email/pass oddity

I'm setting up a very basic OSX app for an existing firebase with SimpleLogin and email/password authentication.
Here's the code I'm using.
- (void) applicationDidFinishLaunching:(NSNotification *)notification {
Firebase* ref = [[Firebase alloc] initWithUrl:#"https://myapp.firebaseio.com"];
FirebaseSimpleLogin* authClient = [[FirebaseSimpleLogin alloc] initWithRef:ref];
[authClient loginWithEmail:#"myemail#mydomain.com" andPassword:#"mypassword"
withCompletionBlock:^(NSError* error, FAUser* user) {
if (error != nil) {
// There was an error logging in to this account
NSLog(#"authClient login error: %#", error);
} else {
NSLog(#"Login success.");
}
}];
}
Login is successful, and I see the log output. However, "FAUser* user" is nil. How? Why?
Online search / existing SO questions haven't helped..
Any ideas?
** UPDATE **
Same code in iOS works as expected. Is this just an OSX issue?
** UPDATE 2 **
I compiled the source code from the Firebase/Objective-C Simple Login Service (which seems to only reference iOS) directly in my OSX project and found that there is a "duplicate item" error code when the login service tries to store Keychain data on OSX.
The source code after the keychain save operation then proceeds to return a null user. I believe there is a logic error here because the if statement evaluates to true whenever it is not a success code (skipping the special case for duplicate item):
if (status != noErr) {
user = nil;
}
else if (status == errSecDuplicateItem) {
// TODO: log an error?
user = nil;
}
Anyway, I am able to continue working by modifying this small chunk of code to fit my needs.
Yeah, you're right. It's an error with accessing Keychain.
To clear the error on your own machine, you can go to Keychain Access, search for 'firebase' in the upper right. You should get an item with the name https://auth.firebase.com. Click it. Check that it says Firebase_<YOUR_FIREBASE_NAME> for the Account field, and go ahead and delete it. The next time you spin up your app, you'll have to log in again.
This seems to show up when you try to hit the same keychain item many times at once. Accessing the keychain slowly multiple times seems to be fine, so unless someone is authenticating many of the same app on the same machine, you shouldn't get the error. (If you can hit it some other way, please share!)

Enable access for assistive devices programmatically on 10.9

I want to enable access for assistive devices programatically on 10.9. On 10.8 and lower I was using following Applescript to enable access for assistive devices:
tell application "System Events"
if UI elements enabled is false then
set UI elements enabled to true
end if
end tell
With 10.9, Apple has moved the accessibility options to System Preferences ➞ Security & Privacy ➞ Privacy ➞ Accessibility. Unlike previous versions of OS X, which used a universal checkbox for all applications, the new functionality in 10.9 allows users to individually choose which apps can gain control of the system to perform their various scripted functions.
Apple has NOT provided any API to developers to programmatically enable accessibility for an app. So Mac OS 10.9 will prompt a dialog for end user permission to enable Accessibility when application uses accessibility APIs. Additionally User has to Relaunch the application after enabling Accessibility.
Can we enable access for assistive devices programmatically on 10.9 using Applescript or any other APIs? Any help to fix this issue would be greatly appreciated.
This doesn’t answer your question, but it’s good to know about a new API call that appeared in 10.9 and lets you display the authorization screen or bypass it:
NSDictionary *options = #{(id)kAXTrustedCheckOptionPrompt: #YES};
BOOL accessibilityEnabled = AXIsProcessTrustedWithOptions((CFDictionaryRef)options);
Passing YES will force the authorization screen to appear, passing NO will silently skip it. The return value is the same as the one returned by AXAPIEnabled(), which is getting deprecated in 10.9. To make sure that the function is available on your system, just compare it to NULL:
if (AXIsProcessTrustedWithOptions != NULL) {
// 10.9 and later
} else {
// 10.8 and older
}
You'll need to add ApplicationServices.framework to your project, and import to your .m or .h file:
#import <ApplicationServices/ApplicationServices.h>
It’s quite a pity that the authorization screen doesn’t let the user to authorize the app directly, it just opens the right part of the System Preferences. Which, by the way, you can do directly without going through the useless system dialogue:
tell application "System Preferences"
set securityPane to pane id "com.apple.preference.security"
tell securityPane to reveal anchor "Privacy_Accessibility"
activate
end tell
or using Objective C:
NSString *urlString = #"x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility";
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:urlString]];
This can be paired with the first code snippet to test whether accessibilityEnabled by passing #NO to kAXTrustedCheckOptionPrompt while preventing the system pop-up to appear and instead opening the Accessibility preferences pane directly:
NSDictionary *options = #{(id)kAXTrustedCheckOptionPrompt: #NO};
BOOL accessibilityEnabled = AXIsProcessTrustedWithOptions((CFDictionaryRef)options);
if (!accessibilityEnabled) {
NSString *urlString = #"x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility";
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:urlString]];
}
For a native approach I'd recommend against using all the sqlite3 and AppleScript hacks as they might stop working in the future, there's also just a proper api for this.
To add on to this, you can actually monitor if the user clicks the accessibility setting for your app so you can do some actions when the user grants the permission.
(Swift 5, tested on Mojave, Catalina, Big Sur)
reading privileges:
private func readPrivileges(prompt: Bool) -> Bool {
let options: NSDictionary = [kAXTrustedCheckOptionPrompt.takeRetainedValue() as NSString: prompt]
let status = AXIsProcessTrustedWithOptions(options)
return status
}
Monitoring for changes in accessibility:
DistributedNotificationCenter.default().addObserver(forName: NSNotification.Name("com.apple.accessibility.api"), object: nil, queue: nil) { _ in
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
self.updatePrivileges()
}
}
It is best to read the privileges again after getting the notification as the notification itself doesn't really work in my experience. So inside the updatePrivileges(), run readPrivileges() to get the new status.
You need the delay because it takes some time for the changes to be reflected.
Another thing you need to keep in mind while monitoring is that a notification will be fired for any app that gets different permissions, so if the user grants or revokes a different app you'll still get a notification.
Also, don't forget to remove the observer when you don't need it anymore.
Source: Accessbility Testbench by Piddlesoft
While #user2865860's answer works well, I though I'd post the entire code sample that works perfectly on 10.9 to save others some time. You need to get root privileges, so it will prompt a user to enter the password.
char *command= "/usr/bin/sqlite3";
char *args[] = {"/Library/Application Support/com.apple.TCC/TCC.db", "INSERT or REPLACE INTO access VALUES('kTCCServiceAccessibility','com.yourapp',0,1,0,NULL);", nil};
AuthorizationRef authRef;
OSStatus status = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, kAuthorizationFlagDefaults, &authRef);
if (status == errAuthorizationSuccess) {
status = AuthorizationExecuteWithPrivileges(authRef, command, kAuthorizationFlagDefaults, args, NULL);
AuthorizationFree(authRef, kAuthorizationFlagDestroyRights);
if(status != 0){
//handle errors...
}
}
You can edit the TCC.db file in directly. I had to do this in order to make Divvy install without user interaction. Just replace com.mizage.divvy with your program.
sudo sqlite3 /Library/Application\ Support/com.apple.TCC/TCC.db "INSERT INTO access VALUES('kTCCServiceAccessibility','com.mizage.divvy',0,1,1,NULL);"
To remove the entry:
sudo sqlite3 /Library/Application\ Support/com.apple.TCC/TCC.db "delete from access where client='com.mizage.divvy';"
I have found the following code snippet which properly requests Accessibility permissions in OS X 10.9:
if (AXIsProcessTrustedWithOptions != NULL) {
// 10.9 and later
const void * keys[] = { kAXTrustedCheckOptionPrompt };
const void * values[] = { kCFBooleanTrue };
CFDictionaryRef options = CFDictionaryCreate(
kCFAllocatorDefault,
keys,
values,
sizeof(keys) / sizeof(*keys),
&kCFCopyStringDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
return AXIsProcessTrustedWithOptions(options);
}
// OS X 10.8 and older
I was struggling with this myself and after a bit of a research I found the following:
Hacking the sqlite DB has the major drawback in using authorization services. First this will pop-up a dialog telling user that an application wants to install a utility helper (even though it is just one off launchd submission using SMJobSubmit). Second, it does not work for sandboxed apps and thus no app store.
#Max Al Faeakh uses AuthorizationExecuteWithPrivileges which is deprecated. You need to use launchd with the above SMJobSubmit. Anyway, this still requires authorization. It also requires an auxiliary application like this one.
I guess the best is to use either:
NSDictionary *options = #{(id)kAXTrustedCheckOptionPrompt: #YES};
BOOL accessibilityEnabled = AXIsProcessTrustedWithOptions((CFDictionaryRef)options);
or
NSDictionary *options = #{(id)kAXTrustedCheckOptionPrompt: #NO};
BOOL accessibilityEnabled = AXIsProcessTrustedWithOptions((CFDictionaryRef)options);
and open preference pane manually using for example scripting bridge framework:
SBSystemPreferencesApplication *prefs = [SBApplication applicationWithBundleIdentifier:#"com.apple.systempreferences"];
[prefs activate];
SBSystemPreferencesPane *pane = [[prefs panes] find:^BOOL(SBSystemPreferencesPane *elem) {
return [[elem id] isEqualToString:#"com.apple.preference.security"];
}];
SBSystemPreferencesAnchor *anchor = [[pane anchors] find:^BOOL(SBSystemPreferencesAnchor *elem) {
return [[elem name] isEqualToString:#"Privacy_Accessibility"];
}];
[anchor reveal];
The SBSystemPreferencesPane class comes form a SBSystemPreferences.h file which can be generated:
sdef "/Applications/System Preferences.app" | sdp -fh --basename SBSystemPreferences -o SBSystemPreferences.h
Thanks for this shell script samples from #NightFlight, which are really helpful. I used this with AppleScript in a Python application, like the following:
set sh to "touch /private/var/db/.AccessibilityAPIEnabled && sqlite3 \\"/Library/Application Support/com.apple.TCC/TCC.db\\" \\"INSERT or REPLACE INTO access VALUES('kTCCServiceAccessibility','com.godevnode',0,1,0,NULL);\\""
do shell script sh with administrator privileges
It worked well for me in Python code as a string.
Edit (Nov 7, 2014):
If you want to try this in AppleScript Editor, use a slightly different character escape as below:
set sh to "touch /private/var/db/.AccessibilityAPIEnabled && sqlite3 \"/Library/Application Support/com.apple.TCC/TCC.db\" \"INSERT or REPLACE INTO access VALUES('kTCCServiceAccessibility','com.godevnode',0,1,0,NULL);\""
do shell script sh with administrator privileges
For Mac OS X before 10.9, it's even simpler:
accessibility_api_file = "/private/var/db/.AccessibilityAPIEnabled"
def __enable_accessibility_api():
try:
script = 'do shell script "touch %s" with administrator ' \
'privileges' % accessibility_api_file
result = applescript.AppleScript(script).run()
log.debug("Tried to enable accessibility api, result=" + result)
return True
except applescript.ScriptError as err:
log.error(str(err))
return False
Just need to touch one file. The AppleScript mentioned in the Python code above can also be used in other languages.
Thanks everyone.
I issue the following triggered from the login window to ensure control is given only to the items we want every session:
# Enable Service Accessibility for Textpander and others
# Clear the acess table.
sqlite3 /Library/Application\ Support/com.apple.TCC/TCC.db "DELETE FROM access"
# Enter the access we wish to have.
sqlite3 /Library/Application\ Support/com.apple.TCC/TCC.db "INSERT INTO access VALUES ('kTCCServiceAccessibility','com.apple.systempreferences',0,1,1,NULL)"
sqlite3 /Library/Application\ Support/com.apple.TCC/TCC.db "INSERT INTO access VALUES ('kTCCServiceAccessibility','de.petermaurer.textpanderdaemon',0,1,1,NULL)"
The sqlite3 "hack" is great.
I had to use permissions "1,1,1" (whatever that means) to make this work.
Note that the permission combination, not the client (ie. program name) is the unique database key.

How can I get a console readout at runtime in an application?

For debugging purposes, I'd like to access console printouts at runtime in a way similar to the Console app current on the App Store (that can be found here).
I did some searching of the docs and I can't find anything that's provided by Apple, but I feel like I'm missing something important. Any insight?
Thanks.
You can do so using <asl.h>. Here is an example that I threw together to create an array of console messages.
-(NSArray*)console
{
NSMutableArray *consoleLog = [NSMutableArray array];
aslclient client = asl_open(NULL, NULL, ASL_OPT_STDERR);
aslmsg query = asl_new(ASL_TYPE_QUERY);
asl_set_query(query, ASL_KEY_MSG, NULL, ASL_QUERY_OP_NOT_EQUAL);
aslresponse response = asl_search(client, query);
asl_free(query);
aslmsg message;
while((message = asl_next(response)) != NULL)
{
const char *msg = asl_get(message, ASL_KEY_MSG);
[consoleLog addObject:[NSString stringWithCString:msg encoding:NSUTF8StringEncoding]];
}
if (message != NULL) {
asl_free(message);
}
asl_free(response);
asl_close(client);
return consoleLog;
}
If your device is attached to Xcode, you can see console output (NSLogs and such) in the debug area:
If you're running the app and connecting to Xcode later, I believe you can get console logs in the Organizer.
Edit: to access the log file at runtime, you should try /var/log/system.log — but even better I recommend using a custom debug function, which would write to the system log and/or a text view in your app. (Check out NSLogv, which will be useful when writing a wrapper function.) This also has the advantage of letting you disable all debug logs from one place (just change your debug function).