How to detect OSX admin password prompt? - objective-c

I'm trying to (programmatically) detect the OSX administrator password prompt that appears when changing system security settings. Ideally the solutions would work for C++ or Objective-C. I've looked at various NSDistributedNotificationCenters that provide OS notifications, but none of them seem to be specific to the password prompt. I've tried registering for all notifications that the OS can provide, but these notifications seem to stop once I've entered the System Preferences window.
I've also looked into the SFAuthorizationPlugin concept, but it seems like that's more for logging into the system from a cold boot.
I know it is possible, as I've seen other applications detect the password prompt and display something on the screen whenever it appears.
So how can I programmatically detect the OSX administrator password prompt?

You can listen for SecurityAgent notifications from the workspace.
Subscribe to application activation notifications like so:
#interface notificationHandler: NSObject {}
#end
#implementation notificationHandler
-(id)init
{
[[[NSWorkspace sharedWorkspace] notificationCenter]
addObserver:self
selector :#selector(handleNotification)
name :NSWorkspaceDidActivateApplicationNotification
object :nil];
} // init
-(void)handleNotification:(NSNotification *) notification
{
NSDictionary info = [notification userInfo];
NSString *appName = [[info objectForKey:NSWorkspaceApplicationKey] localizedName];
if ([appName isEqualToString:#"SecurityAgent"]) {
// You have found the administrator password prompt!
}
} // handleNotification
#end

Related

Grab audioCDPlaylist via ScriptingBridge does not work with macOS Mojave Beta

I have a application which gets audioCDPlayList from iTunes. This app works fine up to macOS High Sierra, but does not work correctly on macOS Mojave Beta 3 (18A326h).
I have investigated the reason and then found that the following strange behavior:
GetAudioCDInfoFromiTunes.h
#import <Foundation/Foundation.h>
#import <ScriptingBridge/ScriptingBridge.h>
#import "iTunes.h"
#interface GetAudioCDInfoFromiTunes : NSObject
- (NSMutableDictionary *)getAudioCDInfoFromiTunes;
#end
GetAudioCDInfoFromiTunes.m
- (NSMutableDictionary *)getAudioCDInfoFromiTunes {
// Declear iTunes scripting bridge variable
iTunesApplication *iTunesApp = [SBApplication applicationWithBundleIdentifier:#"com.apple.iTunes"];
SBElementArray *sources = [iTunesApp sources];
NSLog(#"sources=%#", sources);
NSLog(#"count=%ld", [sources count]);
iTunesPlaylist *aAudioCDPlayList = nil;
for (iTunesSource *src in sources) {
NSLog(#"src=%#", src);
SBElementArray *playlists = [src audioCDPlaylists];
NSLog(#"playlists=%#", playlists);
for (iTunesPlaylist *aPlaylist in playlists) {
NSLog(#"aplaylist=%#", aPlaylist);
if ([aPlaylist isKindOfClass:[NSClassFromString(#"ITunesAudioCDPlaylist") class]]) {
aAudioCDPlayList = [aPlaylist get];
break;
}
}
}
... SNIP ...
}
Executing the above code, NSLog of Line.8, count of sources is 0. And therefore for loop of Line.12 don't work. Then the result [aPlaylist get] is null.
Does anyone know the reason why the count of sources is 0?
Plase let me know how can I run my ScriptingBridge code on Mojave Beta...
Mojave has tightened data security and privacy and that includes scripting. See WWDC 2018 session 702.
The first time your app tries to control iTunes, Mojave will prompt to get your confirmation to allow that. It will remember your choice so it doesn't ask again.
I guess you must have denied it permission once. After that, it just always prevented your app from controlling iTunes.
Since developers need to test their app's behavior when this prompt is displayed, when permission is denied, and when it's granted, Apple has included a command-line utility, tccutil, to reset the remembered user choices. The command to reset permissions regarding which apps may control other apps is tccutil reset AppleEvents.

Issue retrieving auth with Google API Client for Mac OS X app

I’m working on a Mac OS X app, where the user will need to access their Google Calendar. However, I can’t get the authentication to work and it doesn’t really make any sense to me why it’s not working.
Google API is installed via Cocoapods: pod ‘Google-API-Client/Calendar'
I’ve got a NSTabViewController inside a NSWindow, when a user clicks a button I called the following:
#property (nonatomic, strong)GTMOAuth2WindowController *windowController;
static NSString *const scope = #"https://www.googleapis.com/auth/calendar";
- (void)startAuthentication {
GTMOAuth2Authentication *auth = [GTMOAuth2WindowController authForGoogleFromKeychainForName:kKeychainItemName
clientID:kClientID clientSecret:kClientSecret];
if ( auth.canAuthorize) {
return;
}
self.windowController = [[GTMOAuth2WindowController alloc] initWithScope:scope
clientID:kClientID
clientSecret:kClientSecret
keychainItemName:kKeychainItemName
resourceBundle:nil];
}
- (void)windowController:(GTMOAuth2WindowController *)windowController
finishedWithAuth:(GTMOAuth2Authentication *)auth
error:(NSError *)error {}
Don’t know why, but the selector is never called. From debug I noticed the Fetcher starts, and kGTMOAuth2UserSignedIn gets posted, but it never ends.
Any ideas why this isn’t working?
Had nothing to do with the Google code, it was an issue with the window containing the viewController.

Developing a non-GUI user agent in Objective-C using NSDistributedNotificationCenter

I would like to create a user agent in Objective-C that listens for notifications from the default NSDistributedNotificationCenter. The agent will not have a GUI. When I create a Cocoa application (I will also be using Distributed Objects, which I think is only in Cocoa) in Xcode, however, Xcode sets the project as a GUI application.
In the main function, I remove the NSApplicationMain(...) function call to remove the GUI elements from the application. However, now I can't get the thread to wait (listen for) notifications coming in from the NSDistributedNotificationCenter. The app just starts and quits immediately.
I looked into using the NSRunLoop from the current NSThread, however, it seems that NSRunLoops only wait on NSPorts. There's no mention of waiting on NSNotifications.
NSDistributedNotificationCenter is Foundation, so you don't need to create a GUI app. You can create a command line template, for example, and run it from terminal. As a very simple example, you could create an example that just prints out every distributed notification it receives below.
To build, copy into an Xcode template for a Foundation command line app, or simply copy into a text file named something like test_note.m and build according to the comments. In this example, the application will never end (CFRunLoopRun() never returns) and you will have to kill it by hitting CTRL+C from the terminal or killing it with something like kill or the activity monitor.
// test_build.m
// to build: clang -o test_build test_build.m -framework foundation
#import <Foundation/Foundation.h>
#interface Observer : NSObject
- (void)observeNotification:(NSNotification*)note;
#end
#implementation Observer
- (void)observeNotification:(NSNotification*)note
{
NSLog(#"Got Notification: %#", note);
}
#end
int main (int argc, char const *argv[])
{
#autoreleasepool {
Observer* myObserver = [[Observer alloc] init];
[[NSDistributedNotificationCenter defaultCenter] addObserver:myObserver selector:#selector(observeNotification:) name:nil object:nil];
CFRunLoopRun();
}
return 0;
}

What's the correct way to stop a background process on Mac OS X?

I have an application with 2 components: a desktop application that users interact with, and a background process that can be enabled from the desktop application. Once the background process is enabled, it will run as a user launch agent independently of the desktop app.
However, what I'm wondering is what to do when the user disables the background process. At this point I want to stop the background process but I'm not sure what the best approach is. The 3 options that I see are:
Use the 'kill' command.
Direct, but not reliable and just seems somewhat "wrong".
Use an NSMachPort to send an exit request from the desktop app to the background process.
This is the best approach I've thought of but I've run into an implementation problem (I'll be posting this in a separate query) and I'd like to be sure that the approach is right before going much further.
Something else???
Thank you in advance for any help/insight that you can offer.
The daemon could handle quit apple events or listen on a CFMessagePort.
If you use signals you should handle the signal, probably SIG_QUIT, that is sent instead of just letting the system kill your process.
If you have cleanup that may take a while, use something other than signals. If you are basically just calling exit, then signals are fine.
If you already have a CFRunLoop going then use CFMessagePort. If you are already handling apple events than handle quit.
CFMessagePort is a wrapper around CFMachPort that provides a name and some other conveniences. You can also use the NS wrappers for either.
I found an easier way to do this using an NSConnection object. I created a very simple ExitListener object with this header:
#interface ExitListener : NSObject {
BOOL _exitRequested;
NSConnection *_connection;
}
#property (nonatomic, assign) BOOL exitRequested;
- (void)requestExit;
#end
and this implementation:
#implementation ExitListener
#synthesize exitRequested = _exitRequested;
// On init we set ourselves up to listen for an exit
- (id)init {
if ((self = [super init]) != nil) {
_connection = [[NSConnection alloc] init];
[_connection setRootObject:self];
[_connection registerName:#"com.blahblah.exitport"];
}
return self;
}
- (void)dealloc {
[_connection release];
[super dealloc];
}
- (void)requestExit {
[self setExitRequested:YES];
}
#end
To setup the listener, the background process simply allocates and inits an instance of the ExitListener. The desktop application then asks the background process to exit by making this call:
- (void)stopBackgroundProcess {
// Get a connection to the background process and ask it to exit
NSConnection *connection = [NSConnection connectionWithRegisteredName:#"com.blahblah.exitport" host:nil];
NSProxy *proxy = [connection rootProxy];
if ([proxy respondsToSelector:#selector(requestExit)]) {
[proxy performSelector:#selector(requestExit)];
}
}
Using NSMachPorts directly seemed to lead to far more problems in registering and obtaining references. I found that NSConnection is the simplest way to create a basic communication channel for the sort of situation that I needed to solve.

Skype Mac API - Use AppleScript or 5 year old API?

I have a x86_64 app that I would like to have optionally read Skype status messages. However, the 5 year old skype mac framework is 32-bit, and if there is a way to have that compile within a 64-bit app, I haven't found it.
My question is, basically, how should I go about doing this? I really only need to get and set the USERSTATUS AWAY/ONLINE string.
Using AppleScript, a "Should Skype allow this" dialog pops up... every time. This is highly inefficient and downright irritating.
Advice?
I'm considering writing a 32-bit CLI wrapper, but that seems like overkill.
I used Notification Watcher to learn that Skype's API just works with NSDistributedNotifications. Repeating those notifications worked like a charm for a 64bit app.
Check out Scripting Bridge: Introduction to Scripting Bridge Programming Guide for Cocoa
If I remember right, the permission dialog does not come up once you allow permission.
I my Skype Apple Scripts I have to GUI to click them. If they come up.
tell application "Skype" to launch
delay 15
(* this part if the security API window comes up*)
tell application "System Events"
tell application process "Skype"
if exists (radio button "Allow this application to use Skype" of radio group 1 of window "Skype API Security") then
click
delay 0.5
click button "OK" of window "Skype API Security"
end if
end tell
end tell
delay 5
I've found out that if you open "Skype.app" by viewing bundle contents -> Frameworks you'll find a 64bit and 32bit skype.framework
This is an answer in reply to a request from twitter. I used this code after asking this question way back when. I have not needed to look into the Skype API since this works just fine, but I imagine that its been updated since I last tried to use it. Anyhow...
Here's a list of the NSDistributedNotifications that I use when communicating to skype:
SKSkypeAPINotification
SKSkypeAttachResponse
SKSkypeBecameAvailable
SKAvailabilityUpdate
SKSkypeWillQuit
Just like any other kind of NSDistributedNotification, you simply register and process the results:
[[NSDistributedNotificationCenter defaultCenter]
addObserver:self selector:#selector(setStatusAfterQuit:)
name:#"SKSkypeWillQuit"
object:nil
suspensionBehavior:NSNotificationSuspensionBehaviorDeliverImmediately];
These are the iVars that I keep to sync with Skype:
NSString *applicationName;
NSString *mostRecentStatus;
NSString *mostRecentStatusMessage;
NSString *mostRecentUsername;
int APIClientID;
BOOL isConnected;
BOOL needToSetMessage;
NSString *nextMessage;
NSString *nextStatus;
Here's an example of how to connect to skype:
-(void) skypeConnect{
if (!isConnected){
[[NSDistributedNotificationCenter defaultCenter] postNotificationName:#"SKSkypeAPIAvailabilityRequest"
object:nil
userInfo:nil
deliverImmediately:YES];
[[NSDistributedNotificationCenter defaultCenter] postNotificationName:#"SKSkypeAPIAttachRequest"
object:applicationName
userInfo:nil
deliverImmediately:YES];
}
}
Here's an example of getting the status message (after you've registered with Skype):
-(void) processNotification:(NSNotification *) note{
if ([[note name] isEqualToString:#"SKSkypeAttachResponse"]){
if([[[note userInfo] objectForKey:#"SKYPE_API_ATTACH_RESPONSE"] intValue] == 0){
NSLog(#"Failed to connect to Skype.");
isConnected = NO;
}else {
NSLog(#"Connected to Skype.");
APIClientID = [[[note userInfo] objectForKey:#"SKYPE_API_ATTACH_RESPONSE"] intValue];
isConnected = YES;
[self sendCommand:#"GET PROFILE MOOD_TEXT"];
if (needToSetMessage){
[self sendCommand:[NSString stringWithFormat:#"SET USERSTATUS %#",nextStatus]];
[self sendCommand:[NSString stringWithFormat:#"SET PROFILE MOOD_TEXT %#",nextMessage]];
needToSetMessage = NO;
nextMessage = #"";
nextStatus = #"";
}
}
}
}