You don't have permission to save file in Mac Mojave - macos

In my macOS app I'm trying to create a directory using the below extension
extension URL {
static func createFolder(folderName: String, folderPath:URL) -> URL? {
let fileManager = FileManager.default
let folderURL = folderPath.appendingPathComponent(folderName)
// If folder URL does not exist, create it
if !fileManager.fileExists(atPath: folderURL.path) {
do {
// Attempt to create folder
// try fileManager.createDirectory(at: folderURL, withIntermediateDirectories: true, attributes: nil)
// try fileManager.createDirectory(atPath: folderURL.path, withIntermediateDirectories: true, attributes: nil)
try fileManager.createDirectory(atPath: folderURL.relativePath, withIntermediateDirectories: true, attributes: nil)
} catch {
print(error.localizedDescription + ":\(folderURL.path)")
return nil
}
}
return folderURL
}
}
When I invoke this call its giving me error
You don’t have permission to save the file “FolderName” in the folder
“SelectedFolder”.:/Users/USERNAME/Workspace/SelectedFolder/FolderName
I have taken look at a similar post and have tried all methods but its still giving me the error, am I missing something here? Any help is appreciated

I am Assuming that your app is sandboxed. So you don't have permission to write folder for location where you are trying to.
If it not intended for Sandboxed you can disable the App Sandbox, it can be turned off by clicking on your project file > target name, selecting the capabilities tab and switching the App Sandbox off.
File System Programming Guide: https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/FileSystemOverview/FileSystemOverview.html
App Sandbox documentation here: https://developer.apple.com/library/archive/documentation/Security/Conceptual/AppSandboxDesignGuide/AppSandboxInDepth/AppSandboxInDepth.html
You can also look security scoped bookmark for persistent resource access.
Documentation here:
https://developer.apple.com/library/archive/documentation/Security/Conceptual/AppSandboxDesignGuide/AppSandboxInDepth/AppSandboxInDepth.html#//apple_ref/doc/uid/TP40011183-CH3-SW16
https://developer.apple.com/library/archive/documentation/Miscellaneous/Reference/EntitlementKeyReference/Chapters/EnablingAppSandbox.html#//apple_ref/doc/uid/TP40011195-CH4-SW18

Related

Ask for the Documents permission in a sandboxed app

I am writing a Mac app in SwiftUI and would like to display a live-updated list of documents and folders with the ability to edit the files.
First, users selects any folder with Open File Dialog and then I save the URL into UserDefaults and attempt to read list of files and folders when the app launches.
Assuming I have a valid URL then I do the following:
// Open the folder referenced by URL for monitoring only.
monitoredFolderFileDescriptor = open(url.path, O_EVTONLY)
// Define a dispatch source monitoring the folder for additions, deletions, and renamings.
folderMonitorSource = DispatchSource.makeFileSystemObjectSource(fileDescriptor: monitoredFolderFileDescriptor, eventMask: .write, queue: folderMonitorQueue)
App is crashes when I call DispatchSource.makeFileSystemObjectSource with the EXC_BREAKPOINT exception.
FileManager.default.isReadableFile(atPath: url.path) returns false which tells me I don't have permissions to access this folder.
The URL path is /Users/username/Documents/Folder
I have added NSDocumentsFolderUsageDescription into the info plist.
It's not clear how can I ask for permission programmatically.
Theoretically my URL can point to any folder on File System that the user selects in the Open Dialog. It's unclear what is the best practice to request permission only when necessary. Should I parse the URL for the "Documents" or "Downloads" string?
I also have watched this WWDC video.
Thanks for reading, here's what example what I am trying to show.
Like #vadian said, this needs Secure Scoped Bookmark. If you user picks a folder from NSOpenPanel, permission dialog not necessary. This answer helped me a lot.
I create new NSOpenPanel which gives me URL?, I pass this URL to the saveAccess() function below:
let bookmarksPath = "bookmarksPath"
var bookmarks: [URL: Data] = [:]
func saveAccess(url: URL) {
do {
let data = try url.bookmarkData(options: .withSecurityScope, includingResourceValuesForKeys: nil, relativeTo: nil)
bookmarks[url] = data
NSKeyedArchiver.archiveRootObject(bookmarks, toFile: bookmarksPath)
} catch {
print(error)
}
}
After you save bookmark, you can access when you app launches.
func getAccess() {
guard let bookmarks = NSKeyedUnarchiver.unarchiveObject(withFile: bookmarksPath) as? [URL: Data] else {
print("Nothing here")
return
}
guard let fileURL = bookmarks.first?.key else {
print("No bookmarks found")
return
}
guard let data = bookmarks.first?.value else {
print("No data found")
return
}
var isStale = false
do {
let newURL = try URL(resolvingBookmarkData: data, options: .withSecurityScope, relativeTo: nil, bookmarkDataIsStale: &isStale)
newURL.startAccessingSecurityScopedResource()
print("Start access")
} catch {
print(error)
return
}
}
The code is very rough, but I hope it can help someone. After acquiring the newURL, you can print content of a folder.
let files = try FileManager.default.contentsOfDirectory(at: newURL, includingPropertiesForKeys: [.localizedNameKey, .creationDateKey], options: .skipsHiddenFiles)
And don't forget to call .stopAccessingSecurityScopedResource() when you done.

Why is UIDocumentPickerViewController working on iOS, but showing the wrong folder on Mac Catalyst?

The code below works fine on an iPad or iPhone -- saveDocument() (see below) writes to /Users/me/Library/Containers/My-App/Data/Documents, and openDocument() shows me the content of that folder.
On macOS, however, openDocument() shows the Documents folder at the root level of iCloud Drive, not the sandboxed version, which is local to my computer
It's like the documentPickerVC.directoryURL is being ignored. Why is that?
If it helps, the documentURL looks like this:
file:///Users/me/Library/Containers/org.me.My-App/Data/Documents/
Any help would be really, really appreciated!
func saveDocument(){
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
do {
let puzzleData = try? encoder.encode(puzzle)
print("Saving: \(documentURL)")
let saveableURL = documentURL!.appendingPathComponent("\( .puzzle.date)")
try puzzleData!.write(to: saveableURL)
} catch {
displayError(title: "Error while saving document", message: "There was an error: \(error)")
}
}
// when the user taps on the Open button
#objc func openDocument(){
documentPickerVC = UIDocumentPickerViewController(forOpeningContentTypes: [UTType("public.item")!, UTType("public.data")!], asCopy: true )
documentPickerVC.delegate = self
documentPickerVC.modalPresentationStyle = .formSheet
documentPickerVC.directoryURL = documentURL
self.present(documentPickerVC, animated: true)
}
So, the reason documentURL isn't working is because Apple doesn't support it: specifically, “ This property has no effect in Mac apps built with Mac Catalyst.”

How to select a file in finder in Xamarin?

I am trying to select a file in the Finder, that is not happening the method works well in Obj-C but in Xamarin Finder gets activated but the file is not getting selected.
Whereas if I use Xamarin command to open the file it opens.
Here is the snippet of code that I wrote. Guide me if I am doing something wrong or missing anything?
public override void DidFinishLaunching(NSNotification notification)
{
string fullPath = "/Users/anoopvaidya/Desktop/A/ClientInfoConfig.cs";
NSUrl url = NSUrl.FromString(fullPath);
NSUrl[] urls = { url };
NSWorkspace.SharedWorkspace.OpenFile(fullPath);
//works. But I only want to select in Finder
NSWorkspace.SharedWorkspace.ActivateFileViewer(urls);
//doesn't work
}
Is it related to OS version? I am using 10.13.6.
You need to use a file:// based url:
Example:
using (var url = new NSUrl("file", "localhost", "/Users/Sushi/Desktop/StackOverflow.png"))
{
NSWorkspace.SharedWorkspace.ActivateFileViewer(new NSUrl[] { url });
}

Facebook SDK inside iOS app extension cannot find logged in user?

I've created a sample app Share extension. Followed the Apple guides, which means my project consists of a main app and a "share extension" target.
I've setup my Facebook SDK inside the main app, since the app's settings has some FB login/status functionality. It works well according to expectation: users can login and do some shares.
But I also want the logged-in user to be available to the extension target itself. When my extension comes up (in any app), I check Facebook's login status in viewDidLoad:, and it outputs "not logged in":
if ([FBSDKAccessToken currentAccessToken]) {
NSLog(#"logged in");
} else {
NSLog(#"not logged in");
}
The same code outputs "logged in" if called from within the main app. I suspect it has something to do with the fact that the extension target has a different bundle ID that looks like this: .suffix and I guess FB SDK is trying to read the user ID off the keychain cache, but maybe it's reading it off the wrong keychain due to the different bundle IDs... But it could be other reasons as well I guess.
Any idea how to keep Facebook SDK "logged in" inside the extension after the login itself occurred in the containing main app?
I have the exact same problem, as I develop a Message Extension App with a Facebook Login.
To solve the problem, I put the Login Button in the App and use the shared UserDefaults (with a group name) to store a property :
/*
* Get / sets if is FB connected (because we can't use FB SDK in Extension)
*/
public var isFacebookConnected : Bool {
get {
// Getting Bool from Shared UserDefaults
let defaults : NSUserDefaults = NSUserDefaults.init(suiteName: "MY_GROUP_NAME")!
let isFBConnected : Bool? = defaults.boolForKey("IsFacebookConnected")
if isFBConnected == nil {
return false
} else {
return isFBConnected!
}
}
set {
// Setting in UserDefaults
let defaults : NSUserDefaults = NSUserDefaults.init(suiteName: "MY_GROUP_NAME")!
defaults.setBool(newValue, forKey: "IsFacebookConnected")
defaults.synchronize()
}
}
This property is accessed from the App or from the Extension.
Then, in my Login / Logout methods I add :
//FBLogin
func loginButton(loginButton: FBSDKLoginButton!, didCompleteWithResult result: FBSDKLoginManagerLoginResult!, error: NSError!) {
if let error = error {
print(error.localizedDescription)
return
}
if result.isCancelled
{
return
}
// Set in Shared UserDefaults if connected / not connected
ConfigManager.sharedInstance.isFacebookConnected = true
...
}
func loginButtonDidLogOut(loginButton: FBSDKLoginButton!) {
// OK, keeping track
ConfigManager.sharedInstance.isFacebookConnected = false
}
Then, I keep track of my flag when login / logout.
Warning : this method is not bullet proof as I can't know if Facebook has been uninstalled, but it is robust enough for me.
I believe you'll need to take advantage of Keychain sharing capability in the app and extension to solve this.
https://developer.apple.com/documentation/security/keychain_services/keychain_items/sharing_access_to_keychain_items_among_a_collection_of_apps

Security Scoped Bookmark in App Extension

I am creating a TodayWidget app extension which displays information about user selected folders outside the application directory.
In my main application I am able to use powerbox via NSOpenPanel to select the folder. I can then save a security scoped bookmark to the user defaults of the app group container accessible by my TodayWidget.
The TodayWidget can read in the bookmark data, but when it calls URLByResolvingBookmarkData, it errors out with:
The file couldn’t be opened because it isn’t in the correct format.
Both my main application and the TodayWidget have the below entitlements:
com.apple.security.files.bookmarks.app-scope
com.apple.security.files.user-selected.read-only
From Apple's documentation, only the application that created the security scoped bookmark can use it. I guess these means embedded applications aren't allowed?
I've looked in to using XPC, but that doesn't really help the problem, as XPC can't use security scoped bookmark either, only a normal bookmark. As soon as the computer is restarted, the XPC process will lose access to the directories.
Really all I need is a way for the XPC process to get read access to user specified directories. Is there a way without having to relaunch my main application every restart of the computer?
You have probably already solved this or moved on. But for all those that are attempting something similar I will leave this here for them. In order to access security scoped bookmarks in a different app they have to be transferred as NSData and re-resolved in the other application.
In my case I show an open dialog in the main application and then save the scoped bookmark into a shared NSUserDefaults suite. The other applications are also part of that suite and then access the container of NSData's and resolve them into usable NSURL's
Here are the relevant bits of code:
//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
}
}
So then in my extension (Today view in my case) I do something like this...
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
}
And the relevant bookmark looks something like:
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
}
This solution works for me. Once you resolve the shared URL you can then create a bookmark for that application and save it for later if so desired.There may be better ways out there, hopefully Apple works on this as it is currently painful to share permissions with extensions.
Actually you don't need to startAccessingSecureFileURL and it will return fail to start. just transform bookmark data to url will gain access.

Resources