In my main app bundle I have supporting XPC service included. App is sandboxed and everything works quite fine, except that when I call [[NSUserDefaults standardUserDefault] setObject:forKey:] method and than - synchronize method from the XPC service app, preferences are not written and data cannot be retrieved next time I need it.
I didn't find anything related to this problem in Apple's documentation, except that the sandboxed app cannot access preferences of other apps. That's all right, I don't need it. XPC service has its own container in ~/Library/Containers, where it should be able to store its own data, I'd suppose. But obviously it's not the case for some reason.
I probably missed something, but cannot find what. Is there anything special which needs to be done (adding some entitlement or so) in order to make this work?
Thanks for any tips.
I believe you'll need to use Group Containers to share the preferences and I have achieved something similar (a non-UI LSUIElement app sharing preferences with its conventional Preferences app countpart) using RMSharedPreferences.
Related
I'm modernizing and sandboxing an old Cocoa utility and considering approaches. The app lives in the menu bar and works in the background, but shows a Dock icon and a configuration window when the icon is clicked.
There are two approaches:
A. A single binary with LSUIElement=YES, using TransformProcessType to show and hide the dock icon as necessary.
B. A helper tool performs the actual app functionality, manages the menu icon and always runs in LSUIElement mode in background. The main app presents the configuration UI and is only launched when necessary.
The app currently does A. I've noticed that many long-running utility apps have separate helper binaries and basically do B. Examples on my Mac include Paste Helper, TimingHelper, Discord Helper, CCC Helper (for Carbon Copy Cloner), 1Password Extension Helper.
I understand that having a separate helper app is conceptually more pure and potentially allows for better separation of sandbox privileges, but it's also harder to implement, so I doubt that's the sole reason why all these apps opted to have a separate helper binary.
So:
What are the pros and cons of A and B, i.e. why do some choose B over A? Is it required to get some functionality these days?
Is it even possible to have a helper tool outlive the main app in a sandboxed Mac App Store app?
What API does one use to make such a helper? The old-style authorisation APIs seem deprecated, and XPC does not seem like it allows a helper app to launch at startup (and even outliving the main app may be hacky)?
I suspect the reason so many developers choose option B is because this arrangement is now baked into macOS via the "Login Items" facility.
In a nutshell, your main application embeds a second (helper) app and that app is configured as a "Login Item". macOS detects this and automatically adds your helper app to the user's login items. You can control this, programmatically, using SMLoginItemSetEnabled(...).
You end up with a regular app users are familiar with, and a helper app that automatically starts at login and can run in the background. I'm also pretty sure this includes a free XPC connection you can take advantage of.
Read all about it in the Adding Login Items section of the infamous Daemons and Services Programming Guide.
I've never done this myself (I currently install background apps as user agents, which I can do because I don't have a sandboxed app), but I did research it for another project and I know a lot of apps do this.
One disadvantage of option A (based on user feedback from my own apps) is that the main app won't act like a regular app. Using the A approach, your users either can't quit the app (because it will need to automatically restart) or you need a way to hide it in the dock, and then there's no (obvious) way to launch it again. It just gets confusing. If you do let your users quit the app, then the background functionality goes away, and that creates other problems.
I have a problem with the apple sandbox and the functionality of my app. The app should put my mac into sleep, or shutdown or restart it. I am using "com.apple.security.temporary-exception.apple-events" as the entitlement for sandbox with the value "com.apple.finder". I have imported ScriptingBridge and generated a Finder.h file which i have also included in my project like its proposed in https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ScriptingBridgeConcepts/UsingScriptingBridge/UsingScriptingBridge.html#//apple_ref/doc/uid/TP40006104-CH4-SW12
Then i tried to use the provided methods from Finder.h like shutDown, restart or sleep but they just don't work.
FinderApplication *theFinder = [SBApplication applicationWithBundleIdentifier:#"com.apple.finder"];
[theFinder shutDown];
Can anyone tell me how i can implement these functionalities in a sandboxed app?
Many Thanks!
Checkout https://developer.apple.com/library/mac/qa/qa1134/_index.html
This guide provides the exact code you are looking for. I have tried this and it works perfectly.
I have a status bar application from which I can launch a helper application for preferences. All preferences are stored in NSUserDefaults in the helper app and are read using addSuiteNamed: in the main app.
I would like to notify the main app when preferences are changed in the helper app. What is the best way to do this?
I have tried using NSDistributedNotificationCenter but this is too slow and not very reliable.
RMSharedPreferences by Realmac Software seems to be what I was looking for. It allows communicating between multiple apps and it is very easy to setup and seems to be way more reliable than NSDistributedNotificationCenter.
I'm having some architectural doubts about a project (Mac OS X app) I'm working on. It basically consists of two elements: a daemon that runs in the background gathering some data and a viewer used to represent the gathered data.
The daemon should be visible in the status bar (no dock icon) and includes a small menu accessible via the status bar. It saves data in a core data store. One of the menu items is a link which opens the viewer. When this viewer is opened, a normal GUI application should start including a dock icon and menubar. The viewer is also opened when opening the application itself (by double-clicking on the icon).
After some experimenting, I figured out the best way to achieve this functionality is by creating two applications, the main application representing the viewer and a helper utility representing the daemon. One of the reasons I did it this way is that it isn't possible to switch between LSUIElement values instantly to force the daemon/viewer state.
Now I have some questions about this architecture:
Both the daemon and viewer application uses the same core data store to save and retrieve data. When having a multi-threaded application I know multiple NSManagedObjectContext objects are needed to correctly synchronize data. What about having multiple applications using the same core data store simultaneously? Is this even possible without having the risk of conflicts, locks, etc.? How do I guarantee consistency?
The daemon should always start when the viewer starts. I achieved this by simply looping through all open processes and checking if the bundle identifier of the daemon is listed. If not, the daemon is started using NSWorkspace's launchApplication. This works fine. Now when the user quits the daemon, the viewer should also stop. What is the best way for the viewer to be notified of the daemon stopping? I can periodically check active processes and quit the viewer if the daemon is gone but that sounds a bit odd. I would rather choose some kind of notification that I'll send when the viewer is about to close. But since this notification should be sent and captured between apps I don't know which simple notification service is available. Any thoughts?
The application is sandboxed as it will be distributed on the Mac App Store. Starting apps with NSWorkspace's launchApplication causes the target app to run in the same sandboxed environment as the source which I think is not a problem at all because running both applications in the same sandbox feels better and probably is. But imagine this scenario: the daemon is started automatically at login (using SMLoginItemSetEnabled) and the user double-clicks Viewer.app. As the daemon is already running (again, this is checked by looping through active processes) it won't be started. Now we have the daemon and the viewer running in different sandboxes right? Will this cause any problems regarding preferences, core data store, etc.?
I would like to use NSUserDefaults for basic configuration, can I somehow interchange this data between the daemon and the viewer? Again, both applications will have different bundle identifiers.
Thanks in advance for your help, appreciated!
There isn't one right answer to this problem, but here's how I'd approach it:
Both the daemon and viewer application uses the same core data store to save and retrieve data.
Because sharing a Core Data store between processes isn't supported (as far as I know), I'd have the daemon expose an XPC Service. Instead of opening the Core Data store itself, the viewer would use an NSXPCConnection to access the data via the daemon.
Assuming the viewer never runs without the daemon, it can use SMLoginItemSetEnabled, like you mentioned in the question, to register a mach service for the daemon, and then connect to that service.
There's sample code that goes over the details of setting that up here on Apple's website (summary: the daemon needs to be at App.app/Contents/Library/LoginItems/daemon.bundle.id.app), and you might also want to read this blog post that discusses some extra requirements imposed by sandboxing (summary: make extra sure that your Team ID is in the daemon's bundle identifier).
The daemon should always start when the viewer starts.
All set: once you register the daemon with SMLoginItemSetEnabled, launchd will start it (if necessary) when the viewer connects to its XPC Service.
Now when the user quits the daemon, the viewer should also stop.
The viewer can use the NSXPCConnection to find out when the daemon quits. The daemon can also use SMLoginItemSetEnabled to un-register itself before it quits, so that it doesn't get relaunched.
I would like to use NSUserDefaults for basic configuration, can I somehow interchange this data between the daemon and the viewer? Again, both applications will have different bundle identifiers.
Use a suite for this:
// To read or write:
NSUserDefaults* suiteDefaults = [[NSUserDefaults alloc] initWithSuiteName:#"com.example.app.shared"];
[suiteDefaults setObject:[NSDate date] forKey:#"launchTime"];
// Add the suite to -standardUserDefaults to make reading easier:
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
[defaults addSuiteNamed:#"com.example.app.shared"];
To work with sandboxing, the viewer and the daemon must share an App Group. You can even use KVO to observe changes to shared keys.
I am wondering what limitations are imposed on the Mac app store. Can someone point me in the right direction? For instance, lets say I wanted to write an app that does incremental auto-backups of files on the Mac file system, is that possible with an app in the Mac app store, or would my only option be a standalone mac app?
On the iPhone, apps are self contained in their own "sandbox"? Does this same principal apply to mac app store apps?
Fellows, what the OP is really referring to is described here:
http://developer.apple.com/library/mac/#documentation/Security/Conceptual/CodeSigningGuide/Introduction/Introduction.html
It is all about
Containers
Entitlements
PowerBox (daemon)
Once an application is "sandboxed" its view of the ~ "home" directory is that of a Container (which happens to be created upon first start in $HOME/Library/Containers/appBundleID/Data). Therein it finds its "private copy" of config, cache and data files. And off course it can't read anything else, unless...
... you give the application the appropriate Entitlements in the form of a codesign-ed property file (in fact, codesigning is integral part of the sandboxing concept).
One of these Entitlements is the "com.apple.security.files.user-selected.read-write" which allows the application to read and write exactly those files which the user has explicitly chosen via the standard save and open dialog respectively.
The PowerBox (pboxd) daemon then renders the appropriate file dialog in its own process space and adds the selected file paths to the list of allowed files. This is transparent to the application, that is no code changes are required - as long as the application uses the standard NSOpenPanel or NSSavePanel dialogs.
"To facilitate application sandboxing, Mac OS X v10.7 provides a trusted system daemon that is tasked with presenting open and save panels on behalf of applications running in an application sandbox. That daemon is called Powerbox (its process name is pboxd). These Powerbox-presented remote panels appear fully indistinguishable from in-process panels in terms of user experience.
Any time an application running inside a sandbox invokes an NSOpenPanel or NSSavePanel dialog, rather than showing the panels directly, AppKit automatically asks the Powerbox to present the dialog. From a developer perspective, there are no code changes required in terms of how these panels are used; this process is fully transparent." [end quote from Apple docs]
Given all that, what the OP would need (for a backup solution) are "unmediated read/writes":
"If an application chooses to derive the user's home directory in a way that bypasses Cocoa APIs (by directly invoking getpwent, for example), the application sandbox prohibits it from writing to the paths it receives (unless the application has the unmediated write entitlement, which is strongly discouraged for obvious security reasons." [end quote from Apple docs]
However the closest Entitlements which would give "free access to the file system" I could fine would be:
"
Absolute file read-only—The ability to read the files or directories at the specified absolute paths. (com.apple.security.temporary-exception.files.absolute-path.read-only)
Absolute file read/write—The ability to read or write the files or directories at the specified absolute paths. (com.apple.security.temporary-exception.files.absolute-path.read-write)
" [end quote from Apple docs]
And I am not sure whether an application could simply provide the root directory "/"
Anyway, carefully note that these entitlements are marked "temporary": Apple might remove/deprecate those entitlements at seen fit!
I believe that starting in November, Mac App Store submissions must adopt the App Sandbox. There are specific entitlements that you can request when your app is submitted to the app store, along with an explanation of why you need those entitlements. More information can be found in WWDC Session 204 video on the Developer site.
Technically, there is sandboxing on the OS X. However, applicants started manually by the user bypass those sandboxing restrictions (sometimes requiring escalation / admin verification screens).
Take a look at:
http://techjournal.318.com/security/a-brief-introduction-to-mac-os-x-sandbox-technology/
The are changing or have changed with the Lion release. Mac Store apps used to be able to do anything within the filesystem that the logged in user privs could allow but I think you are now required to use the entitlements system and hence sandbox your App.
Read this for more ideas
https://developer.apple.com/library/mac/#releasenotes/General/SubmittingToMacAppStore/_index.html#//apple_ref/doc/uid/TP40010572
This will help. Take a good look at it.
http://developer.apple.com/library/mac/#documentation/Security/Conceptual/AppSandboxDesignGuide/AppSandboxInDepth/AppSandboxInDepth.html#//apple_ref/doc/uid/TP40011183-CH3-SW16
You can currently find Hype or Pixelmator on the Mac App Store.
This proves evidently that you can save to disk and read from disk, which seems a basic feature of any serious application. Moreover, Apple is pushing developers to start using incremental auto-backups of files, it would therefore be very surprising if they forbade that in the App Store, wouldn't it?