file access and sandbox

Hello


I had an application which access pdf files in some locations and launch acrobatread by opening the file:

workSpace().openFile(filename)

this worked fine


I had to sandbox my application, in order to access contacts and the pdf fiels cannot be opened anymore, because they are not readable

bRes = manager.fileExists(atPath: filename) returns true

but

bRes = manager.isReadableFile(atPath: filename) return false


when I inspect my file, it has read/write privileges for everyoneI don't undesrtand what I am missing?

You miss the explicit authorization from user to access this file !

It is pretty tricky, and I needed a lot of help from this forum to finally make it work. So, I'll try to speed this up for you. Hope the following is complete.


To do it, you have to call NSOpenPanel and ask there user to select file ; that will authorize access ; if you save the bookmarks, and load them when launching app, that will work next time automatically.


Here is some extract of one of my app to show how to do it :


    if let _ = NSMutableData(contentsOf: fileToReadURL) { // based on your fileName ; that is the test I use, may probably use another test
          //        print("sandbox OK")
    } else {          // This will occur only first time
        let openPanel = NSOpenPanel()
        openPanel.message = "Authorize access to file"
        openPanel.prompt = "Authorize"
        openPanel.canChooseFiles = true
        openPanel.canChooseDirectories = false
        openPanel.canCreateDirectories = false
        openPanel.directoryURL = keyURL
        openPanel.begin() {
            (result) -> Void in
            if (result.rawValue == NSFileHandlingPanelOKButton) {
                storeBookmark(openPanel.url!)
                saveBookmarks() 
            }
        }
    }

    if let data = NSMutableData(contentsOf: fileToReadURL) {
     // Now you can proceed as with non sandboxed
}


Utility functions needed

I declared a global var :

globalBookmarks = [URL: Data]()



func bookmarkPath() -> String {
     // bookmarks saved in document directory
    var url = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] as URL
    url = url.appendingPathComponent("Bookmarks.dict")
    return url.path
}


func loadBookmarks() {

    let path = bookmarkPath()
    globalBookmarks = NSKeyedUnarchiver.unarchiveObject(withFile: path) as? [URL: Data] ?? [:]  /
    for bookmark in globalBookmarks {
        restoreBookmark(bookmark)
    }

}


func saveBookmarks() {

    let path = bookmarkPath()
    NSKeyedArchiver.archiveRootObject(globalBookmarks, toFile: path)
}


func storeBookmark(url: URL) {

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


func restoreBookmark(_ bookmark: (key: URL, value: Data)) {

    let restoredUrl: URL?
    var isStale = false     // parameter for URL.init

    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 {
               // handle if needed
       } else  {
            if !url.startAccessingSecurityScopedResource() {
                Swift.print ("Couldn't access: \(url.path)")
            } else {
                // handle if needed
            }
        }
    }
}


You call loadBookmarks() in applicationDidFinishLaunching

OK I understand and it works

BUT


first question:

Is it possible to hilight the file I want the user to choose in the open Panel? necause he doesn't know wich file in the directory the application will open.


second question (it is a remark in fact)

My system is an accounting system who archives thousand of files. So the bookmark files will grow, and when the user will start the application, it will take times and ressources. Isn'it a mean to force thses bookmarks, withour opening control panel and without storing them in a file?


Anyway, thank's for your answer and your time

1. You can use NSOpenPanel to ask for a directory rather than a specific file (canChooseFiles = false, canChooseDirectories = true). Your app will then be able to read any file in the directory or its subdirectories.


2. You need to consider whether your app needs redesigning for sandboxing. You certainly should not try to have thousands of security-scoped URLs active simultaneously, and even a design that requires thousands of bookmarks stored (somewhere) seems like the wrong approach.


In a sandboxed app, file system locations are either user-visible ("outside" your sandbox container) or app-private ("inside" your sandbox container). The first set of locations require explicit user permission for your app to access. The second set are freely accessible to you app, but must not be directly exposed to the user in your app's UI. (It is grounds for app-store rejection to break these conventions.)


I suggest you spend some time absorbing the information here:


https://developer.apple.com/library/content/documentation/Security/Conceptual/AppSandboxDesignGuide/AboutAppSandbox/AboutAppSandbox.html

1. There was a call :


- (NSInteger)runModalForDirectory:(NSString *)path file:(NSString *)filename

But it has been deprecated (see documentation) :

runModalForDirectory:file:types:

Displays the panel and begins a modal event loop that is terminated when the user clicks either OK or Cancel.

Deprecated

Use runModal instead. You can set path using directoryURL, and you can set fileTypes using allowedFileTypes.

They state you can set directory, allowed file type, but not preselect the file anymore

A solution would be to name the file to select in openPanel.message


2. Yes bookmark will grow, but having a hundred of bookmarks is no problem per se (but see Quincey comment on architecture of your app).

In fact, my problem is I need to access contacts ad calendar, so i have to sandbox my App

If there is a mean to grant access to directory, it will be the solution

Which directory do you need to grant access ?


If you change


        openPanel.canChooseDirectories =true


instead of false, then you can select a directory and give access to it.

Finally I change my point of view


I need my application to be in a sandbox, and I don't want the users to interact width authorization of directories or files. It is too heavy

So I wrote a download manager, with the help of internet tutorials.

It's a little bit longer for the user, but he does'nt have to deal with security access


thank's to all of you

(now I have to write an upload manager for the users who will arhive their documents)

I am not sure this new approach will be simpler for user.


But wish you success.

file access and sandbox
 
 
Q