Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[macOS, sandbox] Add files.user-selected entitlement support for arbitrary folder access. #47056

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions doc/classes/OS.xml
Original file line number Diff line number Diff line change
Expand Up @@ -225,8 +225,8 @@
<return type="PackedStringArray">
</return>
<description>
With this function you can get the list of dangerous permissions that have been granted to the Android application.
[b]Note:[/b] This method is implemented on Android.
On Android devices, this function you can get the list of dangerous permissions that have been granted.
On macOS (sandboxed applications only), this function you can get the list of user selected folders accessible to the application. Use [code]request_permission("APP_SANDBOX_FILE_ACCESS")[/code] to request folder access permission.
</description>
</method>
<method name="get_keycode_string" qualifiers="const">
Expand Down Expand Up @@ -493,7 +493,10 @@
<argument index="0" name="name" type="String">
</argument>
<description>
At the moment this function is only used by [code]AudioDriverOpenSL[/code] to request permission for [code]RECORD_AUDIO[/code] on Android.
On Android devices, [code]request_permission("RECORD_AUDIO")[/code] is used by [code]AudioDriverOpenSL[/code] to request permission for audio recording (function is called automatically).

On macOS (sandboxed applications only), [code]request_permission("APP_SANDBOX_FILE_ACCESS")[/code] can be used to request access to the user selected folder. Granted permissions are saved as security-scoped bookmark and automatically granted in subsequent sessions.
[b]Note:[/b] To use this function, sandboxed applications must have [code]codesign/entitlements/app_sandbox/files_user_selected[/code] entitlement enabled.
</description>
</method>
<method name="request_permissions">
Expand Down
9 changes: 9 additions & 0 deletions platform/osx/export/export.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ void EditorExportPlatformOSX::get_export_options(List<ExportOption> *r_options)
r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/entitlements/app_sandbox/files_pictures", PROPERTY_HINT_ENUM, "No,Read-only,Read-write"), 0));
r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/entitlements/app_sandbox/files_music", PROPERTY_HINT_ENUM, "No,Read-only,Read-write"), 0));
r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/entitlements/app_sandbox/files_movies", PROPERTY_HINT_ENUM, "No,Read-only,Read-write"), 0));
r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/entitlements/app_sandbox/files_user_selected", PROPERTY_HINT_ENUM, "No,Read-only,Read-write"), 0));

r_options->push_back(ExportOption(PropertyInfo(Variant::PACKED_STRING_ARRAY, "codesign/custom_options"), PackedStringArray()));

Expand Down Expand Up @@ -870,6 +871,14 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
ent_f->store_line("<key>com.apple.security.files.movies.read-write</key>");
ent_f->store_line("<true/>");
}
if ((int)p_preset->get("codesign/entitlements/app_sandbox/files_user_selected") == 1) {
ent_f->store_line("<key>ccom.apple.security.files.user-selected.read-only</key>");
ent_f->store_line("<true/>");
}
if ((int)p_preset->get("codesign/entitlements/app_sandbox/files_user_selected") == 2) {
ent_f->store_line("<key>com.apple.security.files.user-selected.read-write</key>");
ent_f->store_line("<true/>");
}
}

ent_f->store_line("</dict>");
Expand Down
3 changes: 3 additions & 0 deletions platform/osx/os_osx.h
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,9 @@ class OS_OSX : public OS_Unix {

virtual Error move_to_trash(const String &p_path) override;

virtual bool request_permission(const String &p_name) override;
virtual Vector<String> get_granted_permissions() const override;

OS_OSX();
};

Expand Down
68 changes: 68 additions & 0 deletions platform/osx/os_osx.mm
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,16 @@ virtual void log_error(const char *p_function, const char *p_file, int p_line, c
}

void OS_OSX::finalize() {
NSArray *bookmarks = [[NSUserDefaults standardUserDefaults] arrayForKey:@"sec_bookmarks"];
for (id bookmark in bookmarks) {
NSError *error = nil;
BOOL isStale = NO;
NSURL *url = [NSURL URLByResolvingBookmarkData:bookmark options:NSURLBookmarkResolutionWithSecurityScope relativeToURL:nil bookmarkDataIsStale:&isStale error:&error];
if (!error && !isStale) {
[url stopAccessingSecurityScopedResource];
}
}

#ifdef COREMIDI_ENABLED
midi_driver.close();
#endif
Expand Down Expand Up @@ -345,10 +355,68 @@ virtual void log_error(const char *p_function, const char *p_file, int p_line, c
return OK;
}

bool OS_OSX::request_permission(const String &p_name) {
if (p_name.to_upper() == "APP_SANDBOX_FILE_ACCESS") {
NSOpenPanel *openPanel = [NSOpenPanel openPanel];
[openPanel setAllowsMultipleSelection:NO];
[openPanel setCanChooseDirectories:YES];
[openPanel setCanCreateDirectories:YES];
[openPanel setCanChooseFiles:NO];
[openPanel beginWithCompletionHandler:^(NSInteger result) {
if (result == NSFileHandlingPanelOKButton) {
NSArray *bookmarks = [[NSUserDefaults standardUserDefaults] arrayForKey:@"sec_bookmarks"];

NSError *error = nil;
NSData *bookmark = [[openPanel URL] bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope includingResourceValuesForKeys:nil relativeToURL:nil error:&error];
if (!error) {
NSArray *new_bookmarks = [bookmarks arrayByAddingObject:bookmark];
[[NSUserDefaults standardUserDefaults] setObject:new_bookmarks forKey:@"sec_bookmarks"];
}
}
}];
return true;
} else {
return false;
}
}

Vector<String> OS_OSX::get_granted_permissions() const {
Vector<String> ret;

NSArray *bookmarks = [[NSUserDefaults standardUserDefaults] arrayForKey:@"sec_bookmarks"];
for (id bookmark in bookmarks) {
NSError *error = nil;
BOOL isStale = NO;
NSURL *url = [NSURL URLByResolvingBookmarkData:bookmark options:NSURLBookmarkResolutionWithSecurityScope relativeToURL:nil bookmarkDataIsStale:&isStale error:&error];
if (!error && !isStale) {
String url_string;
url_string.parse_utf8([[url path] UTF8String]);
ret.push_back(url_string);
}
}

return ret;
}

OS_OSX::OS_OSX() {
main_loop = NULL;
force_quit = false;

// Load security-scoped bookmarks, request access, remove stale or invalid bookmarks.
NSArray *bookmarks = [[NSUserDefaults standardUserDefaults] arrayForKey:@"sec_bookmarks"];
NSMutableArray *new_bookmarks = [[NSMutableArray alloc] init];
for (id bookmark in bookmarks) {
NSError *error = nil;
BOOL isStale = NO;
NSURL *url = [NSURL URLByResolvingBookmarkData:bookmark options:NSURLBookmarkResolutionWithSecurityScope relativeToURL:nil bookmarkDataIsStale:&isStale error:&error];
if (!error && !isStale) {
if ([url startAccessingSecurityScopedResource]) {
[new_bookmarks addObject:bookmark];
}
}
}
[[NSUserDefaults standardUserDefaults] setObject:new_bookmarks forKey:@"sec_bookmarks"];

Vector<Logger *> loggers;
loggers.push_back(memnew(OSXTerminalLogger));
_set_logger(memnew(CompositeLogger(loggers)));
Expand Down