I'm using macOS sandboxing and file path bookmarks for the first time in an app.
One thing that's impeding my understanding is that my app seems to be able to open any folder path, regardless of sandboxing.
For example, I can access the file structure and file contents of the launch daemons path (which I assume would be off limits to a sandboxed app).
This is my app delegate that I've modified from a clean Swift UI / AppKit App Delegate project to showcase this.
import Cocoa
import SwiftUI
#main
class AppDelegate: NSObject, NSApplicationDelegate {
var window: NSWindow!
func applicationDidFinishLaunching(_ aNotification: Notification) {
let url = URL(fileURLWithPath: "/Library/LaunchDaemons")
if let enumerator = FileManager.default.enumerator(at: url, includingPropertiesForKeys: [.isRegularFileKey, .isDirectoryKey], options: [.skipsHiddenFiles, .skipsPackageDescendants]) {
for case let fileURL as URL in enumerator {
print(fileURL);
do {
let contents = try String(contentsOf: fileURL, encoding: .utf8)
print(contents)
} catch {
}
}
}
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
}
I have set "User Selected File" to none in the app's sandbox entitlements.
The file path and file contents are successfully printed to the console.
What am I missing here? Why is this app able to get access to this path?
FYI - I'm just running this in the Xcode debugger.
Related
I have built a very simple iOS app which should create a sample text file called test.txt in my Documents folder on my iCloud Drive but when I run the app on an iPhone 12 Pro simulator and check my iCloud Drive on my mac it isn't there.
Here is my Capabilities Tab;
Capabilities tab
Here is my Info.plist file;
Info.plist file
Here is my ViewController code;
//
// ViewController.swift
// InfoPill
//
// Created by Stephen Learmonth on 01/08/2022.
//
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let driveURL = FileManager.default.url(forUbiquityContainerIdentifier: nil)?.appendingPathComponent("Documents")
if driveURL != nil {
print("iCloud available")
let fileURL = driveURL!.appendingPathComponent("test.txt")
try? "Hello world".data(using: .utf8)?.write(to: fileURL)
} else {
print("iCloud not available")
}
}
}
my code prints iCloud available
I have an untrusted app on macOS that I want to run, but without allowing it to connect to the internet.
What is the best way to achieve this?
My best idea was to build a simplistic launch app in swift in xcode, and sandbox this launcher. From what I read, apps launched from sandboxed apps should themselves be sandboxed.
So my launcher app looks like this:
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
#IBOutlet weak var window: NSWindow!
func applicationDidFinishLaunching(_ aNotification: Notification) {
NSWorkspace.shared.open("/path/inside/bundle/to/untrustedApp.app")
print ("after")
}
func applicationWillTerminate(_ aNotification: Notification) {
}
}
(Note that I used NSWorkspace.shared.open because NSWorkspace.shared.openApp didn't do anything, not even call the completion handler.)
And I added the sandbox capability in xcode, and made sure all boxes where unchecked. Am I doing anything wrong? Or is my understanding off?
One way is to use NSTask to directly launch the Mach-O executable inside the untrusted app:
NSTask inherits the sandbox of the parent app (See here)
NSTask lets you specify the Mach-O executable, which is necessary because untrusted.app has it's own code signature, plist files, and entitlements (or lack thereof)
For example, the following trivial application will get the name of the Mach-O executable from:
/path/to/your/ParentApp.app/relative/path/to/Restricted.app
For example, for firefox:
/path/to/your/ParentApp.app/relative/path/to/Restricted.app/Contents/MacOS/firefox
Swift:
func applicationDidFinishLaunching(_ aNotification: Notification) {
let theMainAppBundle = Bundle.main.bundlePath
let theChildAppBundle = theMainAppBundle + ("/relative/path/to/RestrictedApp.app")
let childBundleExecutable = Bundle(path: theChildAppBundle)?.executablePath
Process.launchedProcess(launchPath: childBundleExecutable ?? "", arguments: [""])
NSApp.terminate(self)
}
Objective C:
(void)applicationDidFinishLaunching:(NSNotification *)aNotification {
NSString *theMainAppBundle = [[NSBundle mainBundle] bundlePath];
NSString *theChildAppBundle = [theMainAppBundle stringByAppendingString:#"/relative/path/to/RestrictedApp.app"];
NSString *childBundleExecutable = [NSBundle bundleWithPath:theChildAppBundle].executablePath;
[NSTask launchedTaskWithLaunchPath:childBundleExecutable arguments:[NSArray arrayWithObjects:#"", nil]];
[NSApp terminate:self];
}
I am trying to create a very basic Swift command-line application that signals to another application using a WebSocket when the macOS UI changes to/from light/dark mode.
For some reason, the command-line tool is not receiving any notifications from DistributedNotificationCenter, in particular, AppleInterfaceThemeChangedNotification. However, running the exact same code in a Cocoa UI app on applicationDidFinishLaunching works perfectly fine.
I found an old Obj-C CLI project on Github that is meant to print out every notification, but that doesn't do anything either. It makes me suspect Apple perhaps changed something, but I cannot seem to find anything online about it. Are there certain Xcode project settings I need to set?
// main.swift
import Foundation
class DarkModeObserver {
func observe() {
print("Observing")
DistributedNotificationCenter.default.addObserver(
forName: Notification.Name("AppleInterfaceThemeChangedNotification"),
object: nil,
queue: nil,
using: self.interfaceModeChanged(notification:)
)
}
func interfaceModeChanged(notification: Notification) {
print("Notification", notification)
}
}
let observer = DarkModeObserver.init()
observer.observe()
RunLoop.main.run()
I managed to get iTunes notifications working, so it was just the theme change notifications that weren't working. Given this, I suspect Apple only sends the notifications to UI/NSApplication applications. As such, replacing the last 3 lines from above with the following works:
let app = NSApplication.shared
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ notification: Notification) {
let observer = DarkModeObserver.init()
observer.observe()
}
}
let delegate = AppDelegate()
app.delegate = delegate
app.run()
I'm trying to implement a deep link into an macOS application, but nothing seems to work.
So far, my AppDelegate.swift contains the following
func application(app: NSApplication, openURL url: NSURL, options: [String : AnyObject]) -> Bool {
print("opened by link");
return true
}
I also configured the info.plist with URLSchemes beeing my bundle identifier and URLIdentifier beeing simply zst
In a simple html-file I use the following code to test the deep link
Test
My app gets opened (or becomes active when already running), but the print statement is not executed.
What am I doing wrong here?
Thanks to #Ssswift I found a solution.
Used this code:
How do you set your Cocoa application as the default web browser?
and converted it to swift with: https://objectivec2swift.com
works with Swift 3
in AppDelegate.swift added these lines
func applicationDidFinishLaunching(_ aNotification: Notification) {
var em = NSAppleEventManager.shared()
em.setEventHandler(self, andSelector: #selector(self.getUrl(_:withReplyEvent:)), forEventClass: AEEventClass(kInternetEventClass), andEventID: AEEventID(kAEGetURL))
}
func getUrl(_ event: NSAppleEventDescriptor, withReplyEvent replyEvent: NSAppleEventDescriptor) {
// Get the URL
var urlStr: String = event.paramDescriptor(forKeyword: keyDirectObject)!.stringValue!
print(urlStr);
}
I’m relatively new to Realm. My task is to bundle a RealmDB and make it writable. Thus far I have copied the bundled realm file into the project and implemented the following code in the app delegate. Above the "func application(application: UIApplication, didFinishLaunchingWithOptions” I used the following function:
func bundleURL(name: String) -> NSURL? {
return NSBundle.mainBundle().URLForResource("data", withExtension: "realm") }
And below didFinishLaunchingWithOptions, I used the following:
if let v0URL = bundleURL("data.realm") {
do {
try NSFileManager.defaultManager().removeItemAtURL(defaultURL)
try NSFileManager.defaultManager().copyItemAtURL(v0URL, toURL: defaultURL)
} catch {}
The issue is that I have to load the app twice to get the data to show up in a MapViewController, which is the first controller upon launch. In this case, I want map pins in the MapViewController to automatically appear upon build. I tried to implement a notification in the MapViewController using the following:
let results = try! Realm().objects(Spaces)
notificationToken = results.addNotificationBlock {[weak self](changes: RealmCollectionChange<Results<Sapces>>) in
self!.populateMap()
I also tried to implement a Database Manager:
func getDBItems() -> [Spaces] {
let dbItemsFromRealm = try! Realm().objects(Spaces)
var bathroom = [Spaces]()
if dbItemsFromRealm.count > 0 {
for dbItemsInRealm in dbItemsFromRealm {
let spaces = dbItemsInRealm as Spaces
space.append(space)
}
}
return space
}
}
However, I can’t get the pins to load upon launch. Any help would be much appreciated.
The behavior you describe is what I'd expect to see if you've already opened the Realm at the target path prior to copying the bundled Realm over to that location. You can confirm this by putting a breakpoint on the Realm initializer and on your code that calls removeItemAtURL and seeing which is hit first.