Sonoma: Detecting NSAppDataUsageDescription denial

MacOS Sonoma has a new privacy/security feature. If an app tries to access data in another app’s container, it triggers a warning message. You can customize the error message by setting a plist in your app bundle. See NSAppDataUsageDescription for details.

My question: how can a Xojo app detect when the user has denied permission?

The code below, when run on Sonoma, always returns “Success”, regardless of whether the user grants or denies permission.

The only difference is that if the user denies permission, the plist is not written to the container. However, it appears as if CFPreferences is caching the data in RAM, so from the app’s point of view the write/read is successful.

dim key as string = "foobar"
dim value as string = "success" + str(Microseconds,"#")
dim result as string = "failure"



dim f as FolderItem = SpecialFolder.UserLibrary
dim path as string  = f.ShellPath + "/Containers/com.apple.ScreenSaver.Engine.legacyScreenSaver.x86-64/Data/Library/Preferences/"
dim bundleID as string = "com.myapp.xyz"


dim cfp as new CFPreferencesMBS

dim cfbundleID as CFStringMBS = New CFStringMBS(path + bundleID)
dim cfkey as New CFStringMBS(key)
dim cfvalue as new CFStringMBS(value)


// try to set the value, this will trigger the NSAppDataUsageDescription warning
cfp.SetValue(cfkey,cfvalue,cfbundleID,cfp.kCFPreferencesCurrentUser, cfp.kCFPreferencesAnyHost)



// this always succeds, even if the user denies permission
if not cfp.AppSynchronize(bundleID) then
  MsgBox "syncrhonize failed"
end if

// get a new CFPreferencesMBS object
cfp = nil

cfp = new CFPreferencesMBS

dim o as CFObjectMBS = cfp.CopyValue(cfkey, cfbundleID, cfp.kCFPreferencesCurrentUser, cfp.kCFPreferencesAnyHost)
if o isa CFStringMBS then
  dim t as CFStringMBS=CFStringMBS(o)
  if t <> nil then 
    result = t.str
  end if
end if

if result = value then
  MsgBox "Success! value=" + value
else
  MsgBox "Failure! value=" + value
end if


1 Like

I found a way to do it by using a different way of reading back the key/value pair (using the plutil utility).

This code seems to work:

Private Function IsCFPreferencesReadable() as boolean

// example code for detecting when the user denies permission 
// to access data in a container in macOS Sonoma

  dim key as string = "foobar"
  dim value as string = "success" + str(Microseconds,"#")
  dim result as string = "failure"
  
  
  
  dim f as FolderItem = SpecialFolder.UserLibrary
  dim path as string  = f.ShellPath + "/Containers/com.apple.ScreenSaver.Engine.legacyScreenSaver.x86-64/Data/Library/Preferences/"
  dim bundleID as string = "com.myapp.xyz"
  
  
  dim cfp as new CFPreferencesMBS
  
  dim cfbundleID as CFStringMBS = New CFStringMBS(path + bundleID)
  dim cfkey as New CFStringMBS(key)
  dim cfvalue as new CFStringMBS(value)
  
  
  // try to set the value, this will trigger the NSAppDataUsageDescription warning
  cfp.SetValue(cfkey,cfvalue,cfbundleID,cfp.kCFPreferencesCurrentUser, cfp.kCFPreferencesAnyHost)
   
  
  // this always succeds, even if the user denies permission
  if not cfp.AppSynchronize(bundleID) then
      return false
  end if
  
    
  // use plutil to see if it succeeded
  dim sh as new shell
  dim command as string = "plutil -extract '" + key + "' raw " + path + bundleID + ".plist"
  sh.Execute command
  dim r as string = sh.Result
  if r.InStr(value) > 0 then
    msgBox "plutil success:" + r
    return true
  else
    MsgBox "plutil failure:" + r
    return false
  end if
 
  
End Function