Know if MainApp is launched by HelperApp - cocoa

I am really stuck here with a simple task, but still cannot get it working.
I've managed to implement 'LaunchAtLogin' feature with the HelperApp as described in this article.
But my application should react differently on launch-by-helper-app and launch-by-user actions. So my task now is to have some kind of flag indicating that MainApp was launched by HelperApp.
I know there are many similar questions, but still none of the solutions works for me. Sandbox seems to cut all the parameters I am trying to send to the MainApp.
Here what I have tried:
- [NSWorkspace - launchApplicationAtURL: options: configuration: error:]
- [NSWorkspace - openURLs: withAppBundleIdentifier: options: additionalEventParamDescriptor: launchIdentifiers:]
- LSOpenApplication()
Until recently I thought I can rely on -psn argument sent by Finder when user manually launches the application. But this argument is sent on 10.8 even when MainApp is launched by HelperApp.
In the article mentioned above, author suggests using [NSWorkspace - launchApplicationAtURL: options: configuration: error:]. Parameters are sent, but nothing arrives to the MainApp.
Has anyone succeeded to accomplish this (or similar) task?
Need help!
Thanks in advance!

After hell of searching and experimenting I am ready to answer my own question, so others can save their time and efforts.
My conclusion is that for now there is no way HelperApp can launch MainApp with some arguments under the Sandbox. At least I have not found any way to do that.
Launch MainApp like this:
[[NSWorkspace sharedWorkspace] launchApplication:newPath];
In MainApp add the following:
Application_IsLaunchedByHelperApp = YES;
ProcessSerialNumber currPSN;
OSStatus err = GetCurrentProcess(&currPSN);
if (!err)
{
// Get information about our process
NSDictionary * currDict = [(NSDictionary *)ProcessInformationCopyDictionary(&currPSN,
kProcessDictionaryIncludeAllInformationMask) autorelease];
// Get the PSN of the app that launched us. Its not really the parent app, in the unix sense.
long long temp = [[currDict objectForKey:#"ParentPSN"] longLongValue];
long long hi = (temp >> 32) & 0x00000000FFFFFFFFLL;
long long lo = (temp >> 0) & 0x00000000FFFFFFFFLL;
ProcessSerialNumber parentPSN = {(UInt32)hi, (UInt32)lo};
// Get info on the launching process
NSDictionary * parentDict = [(NSDictionary*)ProcessInformationCopyDictionary(&parentPSN,
kProcessDictionaryIncludeAllInformationMask) autorelease];
// analyze
// parent app info is not null ?
if (parentDict && parentDict.count > 0)
{
NSString * launchedByAppBundleId = [parentDict objectForKey:#"CFBundleIdentifier"];
if (![launchedByAppBundleId isEqualToString:HELPER_APP_BUNDLE_ID])
{
Application_IsLaunchedByHelperApp = NO;
}
}
}
That's it. Application_IsLaunchedByHelperApp now has correct value.
The solution is not mine. I've found it on the web (cocoabuilder, I guess). Good luck to everyone! And thanks for your attention to my questions.
UPDATE
Looks like there are cases when launched at login app shows launchedByAppBundleId = #"com.apple.loginwindow". So the last part of code will look like this:
//
// analyze
//
// parent app info is not null ?
if (parentDict && parentDict.count > 0)
{
NSString * launchedByAppBundleId = [parentDict objectForKey:#"CFBundleIdentifier"];
if (![launchedByAppBundleId isEqualToString:HELPER_APP_BUNDLE_ID] &&
![launchedByAppBundleId isEqualToString:#"com.apple.loginwindow"])
{
Application_IsLaunchedByHelperApp = NO;
}
}

The place to seek an answer is the Apple Developer Forums - folk there have dealt with all sorts of issues around helper apps and the sandbox. Searching for application groups and custom URL schemes going back, say, 1-2 years may turn up a solution that meets your needs. If you are still stuck post a question on those forums, someone will probably know what you need.
If you are not an Apple Developer so have no access to those forums, or do not intend to distribute via the Mac App Store, then just turn off the sandbox - in its current state this isn't technology you choose to use... HTH.

Related

Swift 2, warning: could not load any Objective-C class information from the dyld shared cache

I have found a few questions regarding this issue, yet none of them were helping with my problem. I am trying to save an object to core data using this code (which worked perfectly fine in Xcode 6 and Simulator...):
let fetchRequest = NSFetchRequest(entityName: "Patient")
let fetchedResults : [NSManagedObject]!
do {
fetchedResults = try managedContext.executeFetchRequest(fetchRequest) as! [NSManagedObject]
patienten = fetchedResults
} catch {
print("error")
}
I added the do-try-catch once I started working on this project in the Xcode 7 beta and a physical device.
Now, when I hit the Save button, this piece of code is called, the app freezes and I get the following:
warning: could not load any Objective-C class information from the dyld shared cache. This will significantly reduce the quality of type information available.
Does anybody know where I went wrong?
For anyone coming across this in the future, I just ran into this problem myself and it turned out that I was actually getting a stack overflow from a recursive function.
Apparently calling setValue:forKey: on an NSObject calls the respective set[Key] function, where [Key] is the (capitalized) name you gave to the forKey section.
So, if like me, you have code that looks like the following, it will cause an infinite loop and crash.
func setName(name: String) {
self.setValue(name, forKey: "name")
}
Choose Product > Clean
I had similar issue. I deleted the app from the device. Then "Product->Clean" in the XCode menu. When I ran the app again, the issue got resolved.
Swift 3:
Actually this problem happened often when you have any property in input declared as type NSError and the compiler expect an Error output, so change the input type to Error usually solve this issue.
What helped me in similar problem (xCode 7, Swift 2):
reading this question
Or more quickly without explaining the reason of solution: just comment #objc(className) in your NSManagedObjectSubclass , that was generated from your CoreData Entity (#objc(Patient) - in your case ).
This solution (if issue still appears) does not applicable to xCode 7.1/Swift 2.1, as the way of generating NSManagedObjectSubclasses was changed.
Don't forget about cleaning your project (Product > Clean) and deleting the app from your device/simulator to replace CoreData storage on it.
let fetchRequest = NSFetchRequest(entityName: "Patient")
do {
let fetchedResults = try managedObjectContext!.executeFetchRequest(fetchRequest)
print("\(fetchedResults)")
} catch {
print("error")
}
The above code worked for me.
Maybe the issue maybe with how your core data is managed.
Check if your managedObjectContext is actually getting created.
Check the modelling of your core data

Can't register subscriptions; CKError 0x19030580, "Service Unavailable"

Every time when I try to register the subscription, I get the error: CKError 0x19030580: "Service Unavailable" (6/2022); server message = "refused to install an older schema (68f93710-7456-11e4-b13e-008cfac0c800) since we already have 693359e0-7456-11e4-8e42-008cfac03128"; uuid = 42F42F6B-98FB-4774-B735-271C1AEF07F1; container ID = "iCloud.com.*.*". And when I try to get all subscriptions that are on the server, I receive nothing.
Why am I receiving CKError 0x19030580? Why can't I retrieve subscriptions? How should I fix them?
Code:
NSPredicate *truePredicate = [NSPredicate predicateWithValue:YES];
CKSubscription *itemSubscription = [[CKSubscription alloc] initWithRecordType:ItemAssetRecordType
predicate:truePredicate
options:CKSubscriptionOptionsFiresOnRecordCreation | CKSubscriptionOptionsFiresOnRecordUpdate];
CKNotificationInfo *notification = [[CKNotificationInfo alloc] init];
notification.shouldSendContentAvailable = YES;
notification.alertLocalizationKey = #"New Item available.";
notification.shouldBadge = NO;
itemSubscription.notificationInfo = notification;
[self.privateDatabase saveSubscription:itemSubscription completionHandler:^(CKSubscription *subscription, NSError *error) {
if (error) {
} else {
self.subscribedItems = subscription.subscriptionID;
}
}];
I have similar issue. One of my record keep refused to upload. Similar "refused to install an older schema" error came up. First, I thought it's in my code, 2-3 days I try to solve it, but nothing.
Then I try to reset iCloud development container (via iCloud dashboard). Make again the records. Then try to run it again. And it works.
I think it's bug on Apple side. Btw, my affected records not subscription though. But you may want to try it.
Tips, you may want to check if your app already subscribed first before subscribing. I save subscriptionID while subscribe method called in NSUserDefaults. Then I remove it if unsubscribe method called.
I had the same "refused to install an older schema" error but with simple writes, not subscriptions. In my case I was writing a record with a field that did not exist. Now I thought that when under the development environment the schema automatically added new field types - but anyway, when I added it manually using the dashboard it worked.
CloudKit sends CKError.serviceUnavailable when the user has disabled iCloud Drive on their device.

Cocoa App that deletes itself

I'm writing a Mac OSX application using Cocoa that is designed to stop working after a specified date, to avoid the user simply changing their system clock and then re-running the app I would like the program to close and delete itself if it is loaded after the expiry date. Is this possible?
I am distributing the application directly not through the app store. Also, checking the date using the internet isn't really an option because the app needs to be useable offline.
Thanks,
Matthew
It's possible, but not reliably so. To delete your app, just get your main bundle's URL and tell NSFileManager to delete it. But your app bundle may not be writable — and thus not deletable either — and the user may have any number of backups even if you do manage to delete it. I would not write anything that depends on this being possible unless I had tight control over the systems the program will run on. (I mean, I probably wouldn't write something that does this anyway, because it's a little crazy. But if I were going to write something like this, it would have to be something that only runs on my own systems.)
You could perform some sanity checks in the system to get an idea whether the user manually set the clock back to the past.
Note that I still don't think the plan of (maliciously) deleting user files is a great idea in general and the following approach in particular will certainly break under Sandboxing..
..but out of curiosity: Here's a snippet that will check all files in /var/log and return whether some of them have been modified in the future (= the system is quite likely running "in the past")
- (bool)isFakeSystemTime
{
int futureFileCount = 0;
// let's check against 1 day from now in the future to be safe
NSTimeInterval secondsPerDay = 24 * 60 * 60;
NSDate *tomorrow = [[[NSDate alloc] initWithTimeIntervalSinceNow:secondsPerDay] autorelease];
NSString *directoryPath = #"/var/log";
NSArray *filesInDirectory = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:directoryPath error:nil];
for (NSString* fileName in filesInDirectory)
{
NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:[directoryPath stringByAppendingPathComponent:fileName] error:nil];
NSDate *date = [attributes valueForKey:#"NSFileModificationDate"];
if (!date)
continue;
if ([date compare:tomorrow] == NSOrderedDescending)
{
NSLog(#"File '%#' modified >=1 day in the future", fileName);
futureFileCount++;
}
}
// again, some heuristic to be (more) on the safe side
return futureFileCount > 5;
}

Obtaining admin privileges to delete files using rm from a Cocoa app

I am making a small app that deletes log files. I am using an NSTask instance which runs rm and srm (secure rm) to delete files.
I want to be able to delete files in:
/Library/Logs
~/Library/Logs
The issue is that the user account does not have permissions to access some files in the system library folder, such as the Adobe logs subfolder and others. For example, only the "system" user (group?) has r/w permissions for the Adobe logs folder and its contents, and the current user doesn't even have an entry in the permissions shown in the Get Info window for the folder.
What I want to be able to do exactly:
Obtain admin privileges.
Store the password in the Keychain so the app doesn't have to nag the user each time (Is the storage of the password a bad idea? Is it possible?)
Delete a file whatever the file permissions may be.
I am using NSTask because it offers notifications for task completion, getting text output from the task itself, etc. Would I need to use something else? If so, how could I replicate NSTask's completion notifications and output file handle while running rm and srm with admin privileges?
I am looking for the most secure way to handle the situation. i.e. I don't want my application to become a doorway for privilege escalation attacks.
I looked at the Authorization Services Programming Guide but I am not sure which case fits. At first I thought that AuthorizationExecuteWithPrivileges would be a good idea but after reading more on the subject it looks like this method is not recommended for security reasons.
A detailed answer would be very welcome. I'm sure some of you already had to do something similar and have some code and knowledge to share.
Thanks in advance!
Update:
I am now able to make the authentication dialog pop up and obtain privileges, like so:
OSStatus status;
AuthorizationRef authRef;
status = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, kAuthorizationFlagDefaults, &authRef);
AuthorizationRights authRights;
AuthorizationItem authItems[1];
authItems[0].name = kAuthorizationRightExecute;
authRights.count = sizeof(authItems) / sizeof(authItems[0]);
authRights.items = authItems;
AuthorizationFlags authFlags = kAuthorizationFlagDefaults | kAuthorizationFlagExtendRights | kAuthorizationFlagInteractionAllowed;
status = AuthorizationCopyRights(authRef, &authRights, kAuthorizationEmptyEnvironment, authFlags, NULL);
From the looks of it, it seems that the "Factored Application" method looks the most appropriate. The thing is that, to me, rm already seems like an external helper tool. I'm not sure I get the setuid alternative suggested in the documentation. Could I set the setuid bit on rm and run it using the NSTask method I already implemented? This would mean that I wouldn't need to create my own helper tool. Could somebody elaborate on this subject?
I also looked at the BetterAuthorizationSample which is suggested as a more secure and recent alternative to the setuid bit method, but found it awfully complex for such as simple behavior. Any hints?
Thanks in advance for any help!
Perhaps a tad late, but this might be useful for future reference for other people. Most of the code is from this person.
Basically, it has a lot to do with Authorization on the Mac. You can read more about that here and here.
The code, which uses the rm tool:
+ (BOOL)removeFileWithElevatedPrivilegesFromLocation:(NSString *)location
{
// Create authorization reference
OSStatus status;
AuthorizationRef authorizationRef;
// AuthorizationCreate and pass NULL as the initial
// AuthorizationRights set so that the AuthorizationRef gets created
// successfully, and then later call AuthorizationCopyRights to
// determine or extend the allowable rights.
// http://developer.apple.com/qa/qa2001/qa1172.html
status = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, kAuthorizationFlagDefaults, &authorizationRef);
if (status != errAuthorizationSuccess)
{
NSLog(#"Error Creating Initial Authorization: %d", status);
return NO;
}
// kAuthorizationRightExecute == "system.privilege.admin"
AuthorizationItem right = {kAuthorizationRightExecute, 0, NULL, 0};
AuthorizationRights rights = {1, &right};
AuthorizationFlags flags = kAuthorizationFlagDefaults | kAuthorizationFlagInteractionAllowed |
kAuthorizationFlagPreAuthorize | kAuthorizationFlagExtendRights;
// Call AuthorizationCopyRights to determine or extend the allowable rights.
status = AuthorizationCopyRights(authorizationRef, &rights, NULL, flags, NULL);
if (status != errAuthorizationSuccess)
{
NSLog(#"Copy Rights Unsuccessful: %d", status);
return NO;
}
// use rm tool with -rf
char *tool = "/bin/rm";
char *args[] = {"-rf", (char *)[location UTF8String], NULL};
FILE *pipe = NULL;
status = AuthorizationExecuteWithPrivileges(authorizationRef, tool, kAuthorizationFlagDefaults, args, &pipe);
if (status != errAuthorizationSuccess)
{
NSLog(#"Error: %d", status);
return NO;
}
// The only way to guarantee that a credential acquired when you
// request a right is not shared with other authorization instances is
// to destroy the credential. To do so, call the AuthorizationFree
// function with the flag kAuthorizationFlagDestroyRights.
// http://developer.apple.com/documentation/Security/Conceptual/authorization_concepts/02authconcepts/chapter_2_section_7.html
status = AuthorizationFree(authorizationRef, kAuthorizationFlagDestroyRights);
return YES;
}
I had this headache a few months ago. I was trying to get a shell script running with admin privileges that shutdown my computer at a certain time. I feel your pain.
I used the BetterAuthorizationSample which was a total nightmare to wade through. But I took the most pragmatic route - I didn't bother trying to understand everything that was going on, I just grabbed the guts of the code.
It didn't take me that long to get it doing what I wanted. I can't remember exactly what I altered, but you're welcome to check out my code:
http://github.com/johngallagher/TurnItOff
I hope this helps on your quest for a secure application!

How do I share a Core Data store between processes using NSDistributedNotifications?

Background
I've already posted a question about the basics of sharing a Core Data store between processes.
I'm trying to implement the recommendations given and I'm running into problems.
My Goal
I have two processes - the Helper App and the UI. They both share a single data store. I want the UI to update it's NSManagedObjectContext when the Helper App has saved new data to the store.
Current Program Flow
The Helper App Process writes data to the Store.
In the Helper App, I listen for NSManagedObjectContextDidSaveNotification notifications.
When the context is saved, I encode the inserted, deleted and updated objects using their URI representations and NSArchiver.
I send an NSNotification to the NSDistributedNotificationCenter with this encoded dictionary as the userInfo.
The UI Process is listening for the save notification. When it receives the notification, it unarchives the userInfo using NSUnarchiver.
It looks up all the updated/inserted/deleted objects from the URIs given and replaces them with NSManagedObjects.
It constructs an NSNotification with the updated/inserted/deleted objects.
I call mergeChangesFromContextDidSaveNotification: on the Managed Object Context of the UI Process, passing in the NSNotification I constructed in the previous step.
The Problem
Inserted objects are faulted into the UI Managed Object Context fine and they appear in the UI. The problem comes with updated objects. They just don't update.
What I've tried
The most obvious thing to try would
be to pass the save Notification
from the Helper App process to the
UI process. Easy, right? Well, no.
Distributed Notifications won't
allow me to do that as the userInfo
dictionary is not in the right
format. That's why I'm doing all the
NSArchiving stuff.
I've tried calling
refreshObject:mergeChanges:YES on
the NSManagedObjects to be updated,
but this doesn't seem to have any
effect.
I've tried performing the
mergeChangesFromContextDidSaveNotification:
selector on the main thread and the
current thread. Neither seems to
affect the result.
I've tried using
mergeChangesFromContextDidSaveNotification:
before between threads, which of
course is much simpler and it worked
perfectly. But I need this same
functionality between processes.
Alternatives?
Am I missing something here? I'm consistently getting the feeling I'm making this much more complex than it needs to be, but after reading the documentation several times and spending a few solid days on this, I can't see any other way of refreshing the MOC of the UI.
Is there a more elegant way of doing this? Or am I just making a silly mistake somewhere in my code?
The Code
I've tried to make it as readable as possible, but it's still a mess. Sorry.
Helper App Code
-(void)workerThreadObjectContextDidSave:(NSNotification *)saveNotification {
NSMutableDictionary *savedObjectsEncodedURIs = [NSMutableDictionary dictionary];
NSArray *savedObjectKeys = [[saveNotification userInfo] allKeys];
for(NSString *thisSavedObjectKey in savedObjectKeys) {
// This is the set of updated/inserted/deleted NSManagedObjects.
NSSet *thisSavedObjectSet = [[saveNotification userInfo] objectForKey:thisSavedObjectKey];
NSMutableSet *thisSavedObjectSetEncoded = [NSMutableSet set];
for(id thisSavedObject in [thisSavedObjectSet allObjects]) {
// Construct a set of URIs that will be encoded as NSData
NSURL *thisSavedObjectURI = [[(NSManagedObject *)thisSavedObject objectID] URIRepresentation];
[thisSavedObjectSetEncoded addObject:thisSavedObjectURI];
}
// Archive the set of URIs.
[savedObjectsEncodedURIs setObject:[NSArchiver archivedDataWithRootObject:thisSavedObjectSetEncoded] forKey:thisSavedObjectKey];
}
if ([[savedObjectsEncodedURIs allValues] count] > 0) {
// Tell UI process there are new objects that need merging into it's MOC
[[NSDistributedNotificationCenter defaultCenter] postNotificationName:#"com.synapticmishap.lapsus.save" object:#"HelperApp" userInfo:(NSDictionary *)savedObjectsEncodedURIs];
}
}
UI Code
-(void)mergeSavesIntoMOC:(NSNotification *)notification {
NSDictionary *objectsToRefresh = [notification userInfo];
NSMutableDictionary *notificationUserInfo = [NSMutableDictionary dictionary];
NSArray *savedObjectKeys = [[notification userInfo] allKeys];
for(NSString *thisSavedObjectKey in savedObjectKeys) {
// Iterate through all the URIs in the decoded set. For each URI, get the NSManagedObject and add it to a set.
NSSet *thisSavedObjectSetDecoded = [NSUnarchiver unarchiveObjectWithData:[[notification userInfo] objectForKey:thisSavedObjectKey]];
NSMutableSet *savedManagedObjectSet = [NSMutableSet set];
for(NSURL *thisSavedObjectURI in thisSavedObjectSetDecoded) {
NSManagedObject *thisSavedManagedObject = [managedObjectContext objectWithID:[persistentStoreCoordinator managedObjectIDForURIRepresentation:thisSavedObjectURI]];
[savedManagedObjectSet addObject:thisSavedManagedObject];
// If the object is to be updated, refresh the object and merge in changes.
// This doesn't work!
if ([thisSavedObjectKey isEqualToString:NSUpdatedObjectsKey]) {
[managedObjectContext refreshObject:thisSavedManagedObject mergeChanges:YES];
[managedObjectContext save:nil];
}
}
[notificationUserInfo setObject:savedManagedObjectSet forKey:thisSavedObjectKey];
}
// Build a notification suitable for merging changes into MOC.
NSNotification *saveNotification = [NSNotification notificationWithName:#"" object:nil userInfo:(NSDictionary *)notificationUserInfo];
[managedObjectContext performSelectorOnMainThread:#selector(mergeChangesFromContextDidSaveNotification:)
withObject:saveNotification
waitUntilDone:YES];
}
I used the method in
http://www.mlsite.net/blog/?p=518
then every object is correctly faulted but the faults are fetch in cache so still no update
I had to do
[moc stalenessInterval = 0];
And it finally worked, with relationship.
You're looking for - (void)refreshObject:(NSManagedObject *)object mergeChanges:(BOOL)flag I believe.
This will refresh the object with the info in the persistent store, merging changes if you want.
I'd go with Mike's suggestion and just watch the store file for changes.
Though it may not be the most efficient, I've had success using - [NSManagedObjectContext reset] from a second process when there's a change to a store. In my case case, the code is fairly linear — all I do is run a fetch request for some data after resetting. I don't know how this will work with bindings and a complicated UI, but you may be able to post a notification to manually update things if it's not handled automatically.
I had this exact same issue with an iPhone app that I've been working on. In my case, the solution involved setting the Context's stalenessInterval to something suitably infinitesimal (e.g., 0.5 seconds).
This works, except for sandboxes apps. You can't send a notification with a user info dict. Instead consider some other IPC like XPC or DO.
On a side note, using NSDustributedNotificationCenter is not always 100% if the system is busy.
Setting stalenessInterval of managed object context works. My case involves multiple threads instead of process though.
Starting with iOS 9, you should now use mergeChangesFromRemoteContextSave:intoContexts:. See this for an explanation: https://www.innoq.com/en/blog/ios-writing-core-data-in-today-extension/

Resources