nsurlxamarin.macappstore-sandboxsecurity-scoped-bookmarks

Xamarin.Mac Using security-scoped bookmarks


Microsoft's official documentation says:

https://learn.microsoft.com/en-us/xamarin/mac/app-fundamentals/sandboxing#using-security-scoped-bookmarks

to try to access persistent outside of SandBox, but it doesn't work.

First, I use NSSavePanel.SavePanel according to the SandBox security policy and bookmark the URL that I can access.

using (var dlg = NSSavePanel.SavePanel)
{
  dlg.AllowedFileTypes = new[] { "zip" };
  dlg.Prompt = "Authenticate";

  if (dlg.RunModal() > 0)
  {
    NSError error;
    NSData url = dlg.Url.CreateBookmarkData(NSUrlBookmarkCreationOptions.WithSecurityScope, null, null, out error);
  }
}

And then I retrieve the access-authenticated bookmarks and make them accessible.

NSData data = new NSData();
NSUrl url = NSUrl.FromBookmarkData(data, NSUrlBookmarkResolutionOptions.WithSecurityScope, null, out bool isStale, out NSError error);

url.StartAccessingSecurityScopedResource();

//...

url.StopAccessingSecurityScopedResource();

However, the above url will only contain null, which will result in an error.

How can I make it work?

Do we need to put anything in the value of NSData data in the first place?


Solution

  • You are confusing some things. Your code

    using (var dlg = NSSavePanel.SavePanel)
    {
      dlg.Message = AppResources.DialogMessageSaveEncryptedFileName;
      dlg.AllowedFileTypes = new[] { "zip" };
      dlg.Prompt = "Authenticate";
    
      if (dlg.RunModal() > 0)
      {
        NSError error;
        //NSData url = dlg.Url.CreateBookmarkData(NSUrlBookmarkCreationOptions.WithSecurityScope, null, null, out error);
        NSData bookmark = dlg.Url.CreateBookmarkData(NSUrlBookmarkCreationOptions.WithSecurityScope, null, null, out error);
      }
    }
    

    The dlg.Url is the url you want to access. What you get back from dlg.Url.CreateBookmarkData() is the encoded bookmark data. You can store this data anyway you like to persist it across app launches. For example in UserDefaults Storing:

    NSUserDefaults.StandardUserDefaults["bookmark"] = bookmark; 
    NSUserDefaults.StandardUserDefaults.Synchronize();
    

    Later retrieving:

    NSData bookmark = NSUserDefaults.StandardUserDefaults.DataForKey("bookmark");
    

    This is the data you put into NSUrl.FromBookmarkData to get the url back.

    //NSData data = new NSData();
    //NSUrl url = NSUrl.FromBookmarkData(data, NSUrlBookmarkResolutionOptions.WithSecurityScope, null, out bool isStale, out NSError error);
    NSUrl url = NSUrl.FromBookmarkData(bookmark, NSUrlBookmarkResolutionOptions.WithSecurityScope, null, out bool isStale, out NSError error);    
    url.StartAccessingSecurityScopedResource();
    
    //...
    
    url.StopAccessingSecurityScopedResource();
    

    Also if you need to keep using the access pay attention to the isStale return value from CreateBookmarkData. If it return true you need to refresh the bookmark from the url with url.CreateBookMarkData and replace the stored bookmark with the new one.