Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

File access in a sandboxed Mac app with swift

I am working on an app for OS X 10.9 with swift, sandboxed.

The app needs access to a SQLite database file. I let the user choose/open a file with NSOpenPanel. I then save the file path with NSUserDefaults for later use.

I want this file to be opened automatically every time when the app is started again. I get the stored path from the NSUserDefault, but when I open the file with this path I get an error, saying I have no permission to access the file.

(it is working without sandboxing)

It looks like bookmark's are the solution to my problem.

Is there a good tutorial how to use bookmark's with swift for an osx app? Any other suggestion?

like image 473
Hanspeter Stocker Avatar asked Jul 07 '15 20:07

Hanspeter Stocker


People also ask

Can you use sandbox on a Mac?

Overview. The App Sandbox is an access control technology that macOS provides and enforces at the kernel level. The sandbox's primary function is to contain damage to the system and the user's data if the user executes a compromised app.

What is Sandboxing in Swift?

Sandboxing is designed to prevent apps from gathering or modifying information stored by other apps. Each app has a unique home directory for its files, which is randomly assigned when the app is installed.

What is sandbox folder Mac?

This folder holds all local information for the app, like your app preferences. If you don't ever give a sandboxed app access to additional files or folders (eg: by choosing extra locations in file handling dialogs), then you can be sure that everything the app stores on your Mac is kept in its sandbox.


2 Answers

Here's more clean solution with SWIFT 5.0:

import Foundation
import Cocoa


    class BookmarkManager {
        static let manager = BookmarkManager()
        // Save bookmark for URL. Use this inside the NSOpenPanel `begin` closure
        func saveBookmark(for url: URL){

            guard let bookmarkDic = self.getBookmarkData(url: url),
                let bookmarkURL = getBookmarkURL() else{
                    print("Error getting data or bookmarkURL")
                    return
            }

            do
            {
                let data = try NSKeyedArchiver.archivedData(withRootObject: bookmarkDic, requiringSecureCoding: false)
                try data.write(to: bookmarkURL)
                print("Did save data to url")
            }
            catch
            {
                print("Couldn't save bookmarks")
            }
        }

        // Load bookmarks when your app launch for example
        func loadBookmarks()
        {

            guard let url = self.getBookmarkURL() else {
                return
            }

            if self.fileExists(url)
            {
                do
                {
                    let fileData = try Data(contentsOf: url)
                    if let fileBookmarks = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(fileData) as! [URL: Data]?
                    {
                        for bookmark in fileBookmarks{
                            self.restoreBookmark(key: bookmark.key, value: bookmark.value)
                        }

                    }
                }
                catch
                {
                    print ("Couldn't load bookmarks")
                }

            }
        }

        private func restoreBookmark(key: URL, value: Data){
            let restoredUrl: URL?
            var isStale = false

            Swift.print ("Restoring \(key)")
            do
            {
                restoredUrl = try URL.init(resolvingBookmarkData: value, options: NSURL.BookmarkResolutionOptions.withSecurityScope, relativeTo: nil, bookmarkDataIsStale: &isStale)
            }
            catch
            {
                Swift.print ("Error restoring bookmarks")
                restoredUrl = nil
            }

            if let url = restoredUrl
            {
                if isStale
                {
                    Swift.print ("URL is stale")
                }
                else
                {
                    if !url.startAccessingSecurityScopedResource()
                    {
                        Swift.print ("Couldn't access: \(url.path)")
                    }
                }
            }
        }
        private func getBookmarkData(url: URL) -> [URL: Data]?{
            let data = try? url.bookmarkData(options: NSURL.BookmarkCreationOptions.withSecurityScope, includingResourceValuesForKeys: nil, relativeTo: nil)
            if let data = data{
                return [url: data]
            }
            return nil
        }

        private func getBookmarkURL() -> URL? {
            let urls = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)
            if let appSupportURL = urls.last{
                let url = appSupportURL.appendingPathComponent("Bookmarks.dict")
                return url
            }
            return nil
        }

        private func fileExists(_ url: URL) -> Bool
        {
            var isDir = ObjCBool(false)
            let exists = FileManager.default.fileExists(atPath: url.path, isDirectory: &isDir)

            return exists
        }

    }
like image 80
Jad Avatar answered Oct 14 '22 21:10

Jad


Here is my answer that I've just got working in Swift 3 with a little help from http://swiftrien.blogspot.com/2015/07/persisting-file-access-rights-between.html

import Foundation
import Cocoa

var bookmarks = [URL: Data]()

func bookmarkPath() -> String
{
    var url = app.applicationDocumentsDirectory
    url = url.appendingPathComponent("Bookmarks.dict")
    return url.path
}

func loadBookmarks()
{
    let path = bookmarkPath()
    bookmarks = NSKeyedUnarchiver.unarchiveObject(withFile: path) as! [URL: Data]
    for bookmark in bookmarks
    {
        restoreBookmark(bookmark)
    }
}

func saveBookmarks()
{
    let path = bookmarkPath()
    NSKeyedArchiver.archiveRootObject(bookmarks, toFile: path)
}

func storeBookmark(url: URL)
{
    do
    {
        let data = try url.bookmarkData(options: NSURL.BookmarkCreationOptions.withSecurityScope, includingResourceValuesForKeys: nil, relativeTo: nil)
        bookmarks[url] = data
    }
    catch
    {
        Swift.print ("Error storing bookmarks")
    }

}

func restoreBookmark(_ bookmark: (key: URL, value: Data))
{
    let restoredUrl: URL?
    var isStale = false

    Swift.print ("Restoring \(bookmark.key)")
    do
    {
        restoredUrl = try URL.init(resolvingBookmarkData: bookmark.value, options: NSURL.BookmarkResolutionOptions.withSecurityScope, relativeTo: nil, bookmarkDataIsStale: &isStale)
    }
    catch
    {
        Swift.print ("Error restoring bookmarks")
        restoredUrl = nil
    }

    if let url = restoredUrl
    {
        if isStale
        {
            Swift.print ("URL is stale")
        }
        else
        {
            if !url.startAccessingSecurityScopedResource()
            {
                Swift.print ("Couldn't access: \(url.path)")
            }
        }
    }

}

func allowFolder() -> URL?
{
    let openPanel = NSOpenPanel()
    openPanel.allowsMultipleSelection = false
    openPanel.canChooseDirectories = true
    openPanel.canCreateDirectories = true
    openPanel.canChooseFiles = false
    openPanel.begin
        { (result) -> Void in
            if result == NSFileHandlingPanelOKButton
            {
                let url = openPanel.url
                storeBookmark(url: url!)
            }
    }
    return openPanel.url
}

Swift 4 (update):

import Foundation
import Cocoa

var bookmarks = [URL: Data]()

func fileExists(_ url: URL) -> Bool
{
    var isDir = ObjCBool(false)
    let exists = FileManager.default.fileExists(atPath: url.path, isDirectory: &isDir)

    return exists
}

func bookmarkURL() -> URL
{
    let urls = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)
    let appSupportURL = urls[urls.count - 1]
    let url = appSupportURL.appendingPathComponent("Bookmarks.dict")
    return url
}

func loadBookmarks()
{

    let url = bookmarkURL()
    if fileExists(url)
    {
        do
        {
            let fileData = try Data(contentsOf: url)
            if let fileBookmarks = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(fileData) as! [URL: Data]?
            {
                bookmarks = fileBookmarks
                for bookmark in bookmarks
                {
                    restoreBookmark(bookmark)
                }
            }
        }
        catch
        {
            print ("Couldn't load bookmarks")
        }

    }
}

func saveBookmarks()
{
    let url = bookmarkURL()
    do
    {
        let data = try NSKeyedArchiver.archivedData(withRootObject: bookmarks, requiringSecureCoding: false)
        try data.write(to: url)
    }
    catch
    {
        print("Couldn't save bookmarks")
    }
}    

func storeBookmark(url: URL)
{
    do
    {
        let data = try url.bookmarkData(options: NSURL.BookmarkCreationOptions.withSecurityScope, includingResourceValuesForKeys: nil, relativeTo: nil)
        bookmarks[url] = data
    }
    catch
    {
        Swift.print ("Error storing bookmarks")
    }

}

func restoreBookmark(_ bookmark: (key: URL, value: Data))
{
    let restoredUrl: URL?
    var isStale = false

    Swift.print ("Restoring \(bookmark.key)")
    do
    {
        restoredUrl = try URL.init(resolvingBookmarkData: bookmark.value, options: NSURL.BookmarkResolutionOptions.withSecurityScope, relativeTo: nil, bookmarkDataIsStale: &isStale)
    }
    catch
    {
        Swift.print ("Error restoring bookmarks")
        restoredUrl = nil
    }

    if let url = restoredUrl
    {
        if isStale
        {
            Swift.print ("URL is stale")
        }
        else
        {
            if !url.startAccessingSecurityScopedResource()
            {
                Swift.print ("Couldn't access: \(url.path)")
            }
        }
    }

}

func allowFolder() -> URL?
{
    let openPanel = NSOpenPanel()
    openPanel.allowsMultipleSelection = false
    openPanel.canChooseDirectories = true
    openPanel.canCreateDirectories = true
    openPanel.canChooseFiles = false
    openPanel.begin
        { (result) -> Void in
            if result == NSFileHandlingPanelOKButton
            {
                let url = openPanel.url
                storeBookmark(url: url!)
            }
    }
    return openPanel.url
}

To use this code you must first call NSOpenPanel so the user can select which folders to give you access to. The NSOpenPanel must be stored as a bookmark and saved to disk.

let url = allowFolder()
saveBookmarks()

When you restart the application you must call

loadBookmarks()

then your app will have the same level of access as it did when the user selected the folder. Hope this helps someone.

like image 31
iphaaw Avatar answered Oct 14 '22 21:10

iphaaw