应用程序扩展中的安全范围书签

dun*_*nc4 6 macos cocoa xpc security-scoped-bookmarks

我正在创建一个 TodayWidget 应用程序扩展,它显示有关用户在应用程序目录之外选择的文件夹的信息。

在我的主应用程序中,我可以通过 NSOpenPanel 使用 powerbox 来选择文件夹。然后,我可以将安全范围书签保存到我的 TodayWidget 可访问的应用程序组容器的用户默认值。

TodayWidget 可以读取书签数据,但是当它调用URLByResolvingBookmarkData 时,它会出错:

The file couldn’t be opened because it isn’t in the correct format.

我的主应用程序和 TodayWidget 都具有以下权利:

  • com.apple.security.files.bookmarks.app-scope
  • com.apple.security.files.user-selected.read-only

根据 Apple 的文档,只有创建安全范围书签的应用程序才能使用它。我猜这些意味着不允许嵌入式应用程序?

我已经考虑使用 XPC,但这并不能真正解决问题,因为 XPC 也不能使用安全范围的书签,只能使用普通书签。一旦计算机重新启动,XPC 进程将失去对目录的访问权限。

我真正需要的只是一种让 XPC 进程获得对用户指定目录的读取访问权限的方法。有没有办法不必每次重新启动计算机都重新启动我的主应用程序?

uta*_*hak 5

您可能已经解决了这个问题或继续前进。但是对于所有尝试类似事情的人,我将把这个留给他们。为了访问不同应用程序中的安全范围书签,它们必须作为 NSData 传输并在其他应用程序中重新解析。

就我而言,我在主应用程序中显示一个打开的对话框,然后将范围书签保存到共享NSUserDefaults套件中。其他应用程序也是该套件的一部分,然后访问NSData's的容器并将它们解析为可用的NSURL's 。

以下是相关的代码位:

//Inside my main application's open function 
... get url from NSOpenPanel
BookmarkUtils.saveURLForOtherApplications(openPanel.URL!)


//Inside BookmarkUtils.swift
static func saveURLForOtherApplications(url:NSURL)->Bool{
    let defaults  = NSUserDefaults(suiteName: <#Suite-Name#>)!
    //I store them as a dictionary of path->encoded URL
    let sandboxedBookmarks:NSMutableDictionary
    if let prevBookmarks =  defaults.objectForKey(kSandboxKey) as? NSDictionary{
       sandboxedBookmarks = NSMutableDictionary(dictionary:prevBookmarks)
    }
    else{
        sandboxedBookmarks = NSMutableDictionary()
    }
    if let shareData = BookmarkUtils.transportDataForSecureFileURL(url){
        sandboxedBookmarks.setObject(shareData, forKey:url.path!)
        defaults.setObject(sandboxedBookmarks, forKey:kSandboxKey)
        defaults.synchronize()
        return true
    }
    else{
        println("Failed to save URL Data");
        return false
    }
}

static func transportDataForSecureFileURL(fileURL:NSURL)->NSData?{
    // use normal bookmark for encoding security-scoped URLs for transport between applications
    var error:NSError? = nil
    if let data =  fileURL.bookmarkDataWithOptions(NSURLBookmarkCreationOptions.allZeros, includingResourceValuesForKeys:nil, relativeToURL:nil, error:&error){
        return data;
    }
    else{
        println("Error creating transport data!\(error)")
        return nil
    }
}
Run Code Online (Sandbox Code Playgroud)

那么在我的扩展程序中(在我的情况下为今天视图)我做这样的事情......

class TodayViewController: ...
    ...
    override func viewDidLoad() {
        super.viewDidLoad()

        var status = [MyCoolObjects]()
        for url in BookmarkUtils.sharedURLSFromApp(){
            BookmarkUtils.startAccessingSecureFileURL(url)
            status.append(statusOfURL(url))
            BookmarkUtils.stopAccessingSecureFileURL(url)
        }

    self.listViewController.contents = status
}
Run Code Online (Sandbox Code Playgroud)

相关书签如下所示:

static func sharedURLSFromApp()->[NSURL]{
    var urls = [NSURL]()
    if let defaults  = NSUserDefaults(suiteName: <#Suite-Name#>){
        if let prevBookmarks =  defaults.objectForKey(kSandboxKey) as? NSDictionary{
            for key in prevBookmarks.allKeys{
                if let transportData = prevBookmarks[key as! NSString] as? NSData{
                    if let url = secureFileURLFromTransportData(transportData){
                        urls.append(url)
                    }
                }
            }
        }
    }
    return urls
}



static func secureFileURLFromTransportData(data:NSData)->NSURL?{
    // use normal bookmark for decoding security-scoped URLs received from another application
    var bookmarkIsStale:ObjCBool = false;
    var error:NSError? = nil;
    if let fileURL = NSURL(byResolvingBookmarkData: data, options: NSURLBookmarkResolutionOptions.WithoutUI, relativeToURL: nil, bookmarkDataIsStale: &bookmarkIsStale, error: &error){
        return fileURL
    }
    else if(bookmarkIsStale){
        println("Bookmark was stale....")
    }
    else if let resolveError = error{
        println("Error resolving from transport data:\(resolveError)")
    }
    return nil
}
Run Code Online (Sandbox Code Playgroud)

这个解决方案对我有用。解析共享 URL 后,您可以为该应用程序创建一个书签,并在需要时将其保存以备后用。可能有更好的方法,希望 Apple 能够解决此问题,因为目前与扩展程序共享权限很痛苦。

  • 如果我理解代码和解释,您是在您的应用程序和今天的扩展之间共享一个普通书签,而不是一个安全范围的书签?在 `transportDataForSecureFileURL` 中,书签是在没有安全范围选项的情况下创建的,在 `secureFileURLFromTransportData` 中也是如此,书签在没有安全范围选项的情况下被解析。如果是这种情况,您可以使用这个普通书签做什么,您是否具有对该书签的文件/文件夹的读取权限? (2认同)
  • 是的,您可以访问该网址。这是魔法。解析该 url 后,如果要在重新启动后访问它,则需要将普通 url 编码为辅助应用程序中的安全范围书签。请参阅此处:https://devforums.apple.com/thread/142513?start=0&amp;tstart=0 和 http://stackoverflow.com/questions/11361043/app-sandbox-how-to-allow-xpc-service-to -read-file-that-user-opened-in-parent-ap (2认同)
  • 刚遇到这个并发现此解决方案不起作用的情况:(1)用户在主机应用程序中选择一个文件,(2)普通 url(非安全范围)的数据保存到共享 UserDefaults,(3)系统重新启动⚡️, (4) 扩展现在无法解析书签数据。重启是问题。之后,无论是主机应用程序还是扩展程序都无法再解析普通书签。 (2认同)