feat: emit an event when accessing restricted path in File System Access API (#42994)

* fix: show a dialog when accessing restricted path in File System Access API

Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>

* fix: allow overriding initial blocked paths

Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>

* docs: fix doc

Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>

* Update docs/api/session.md

Co-authored-by: Erick Zhao <erick@hotmail.ca>

Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>

* fix: change block to deny for consistency

Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>

---------

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
This commit is contained in:
trop[bot] 2024-07-24 13:23:33 -04:00 committed by GitHub
parent f797f92716
commit 9e14f8d828
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 149 additions and 18 deletions

View file

@ -143,6 +143,71 @@ Returns:
Emitted after an extension is loaded and all necessary browser state is Emitted after an extension is loaded and all necessary browser state is
initialized to support the start of the extension's background page. initialized to support the start of the extension's background page.
#### Event: 'file-system-access-restricted'
Returns:
* `event` Event
* `details` Object
* `origin` string - The origin that initiated access to the blocked path.
* `isDirectory` boolean - Whether or not the path is a directory.
* `path` string - The blocked path attempting to be accessed.
* `callback` Function
* `action` string - The action to take as a result of the restricted path access attempt.
* `allow` - This will allow `path` to be accessed despite restricted status.
* `deny` - This will block the access request and trigger an [`AbortError`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController/abort).
* `tryAgain` - This will open a new file picker and allow the user to choose another path.
```js
const { app, dialog, BrowserWindow, session } = require('electron')
async function createWindow () {
const mainWindow = new BrowserWindow()
await mainWindow.loadURL('https://buzzfeed.com')
session.defaultSession.on('file-system-access-restricted', async (e, details, callback) => {
const { origin, path } = details
const { response } = await dialog.showMessageBox({
message: `Are you sure you want ${origin} to open restricted path ${path}?`,
title: 'File System Access Restricted',
buttons: ['Choose a different folder', 'Allow', 'Cancel'],
cancelId: 2
})
if (response === 0) {
callback('tryAgain')
} else if (response === 1) {
callback('allow')
} else {
callback('deny')
}
})
mainWindow.webContents.executeJavaScript(`
window.showDirectoryPicker({
id: 'electron-demo',
mode: 'readwrite',
startIn: 'downloads',
}).catch(e => {
console.log(e)
})`, true
)
}
app.whenReady().then(() => {
createWindow()
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
app.on('window-all-closed', function () {
if (process.platform !== 'darwin') app.quit()
})
```
#### Event: 'preconnect' #### Event: 'preconnect'
Returns: Returns:

View file

@ -11,6 +11,7 @@
#include "base/files/file_path.h" #include "base/files/file_path.h"
#include "base/json/values_util.h" #include "base/json/values_util.h"
#include "base/path_service.h" #include "base/path_service.h"
#include "base/task/bind_post_task.h"
#include "base/task/thread_pool.h" #include "base/task/thread_pool.h"
#include "base/time/time.h" #include "base/time/time.h"
#include "base/timer/timer.h" #include "base/timer/timer.h"
@ -26,18 +27,52 @@
#include "content/public/browser/render_frame_host.h" #include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h" #include "content/public/browser/render_process_host.h"
#include "content/public/browser/web_contents.h" #include "content/public/browser/web_contents.h"
#include "gin/data_object_builder.h"
#include "shell/browser/api/electron_api_session.h"
#include "shell/browser/electron_permission_manager.h" #include "shell/browser/electron_permission_manager.h"
#include "shell/browser/web_contents_permission_helper.h" #include "shell/browser/web_contents_permission_helper.h"
#include "shell/common/gin_converters/callback_converter.h"
#include "shell/common/gin_converters/file_path_converter.h" #include "shell/common/gin_converters/file_path_converter.h"
#include "third_party/blink/public/mojom/file_system_access/file_system_access_manager.mojom.h" #include "third_party/blink/public/mojom/file_system_access/file_system_access_manager.mojom.h"
#include "ui/base/l10n/l10n_util.h" #include "ui/base/l10n/l10n_util.h"
#include "url/origin.h" #include "url/origin.h"
namespace gin {
template <>
struct Converter<
ChromeFileSystemAccessPermissionContext::SensitiveEntryResult> {
static bool FromV8(
v8::Isolate* isolate,
v8::Local<v8::Value> val,
ChromeFileSystemAccessPermissionContext::SensitiveEntryResult* out) {
std::string type;
if (!ConvertFromV8(isolate, val, &type))
return false;
if (type == "allow")
*out = ChromeFileSystemAccessPermissionContext::SensitiveEntryResult::
kAllowed;
else if (type == "tryAgain")
*out = ChromeFileSystemAccessPermissionContext::SensitiveEntryResult::
kTryAgain;
else if (type == "deny")
*out =
ChromeFileSystemAccessPermissionContext::SensitiveEntryResult::kAbort;
else
return false;
return true;
}
};
} // namespace gin
namespace { namespace {
using BlockType = ChromeFileSystemAccessPermissionContext::BlockType; using BlockType = ChromeFileSystemAccessPermissionContext::BlockType;
using HandleType = content::FileSystemAccessPermissionContext::HandleType; using HandleType = content::FileSystemAccessPermissionContext::HandleType;
using GrantType = electron::FileSystemAccessPermissionContext::GrantType; using GrantType = electron::FileSystemAccessPermissionContext::GrantType;
using SensitiveEntryResult =
ChromeFileSystemAccessPermissionContext::SensitiveEntryResult;
using blink::mojom::PermissionStatus; using blink::mojom::PermissionStatus;
// Dictionary keys for the FILE_SYSTEM_LAST_PICKED_DIRECTORY website setting. // Dictionary keys for the FILE_SYSTEM_LAST_PICKED_DIRECTORY website setting.
@ -527,11 +562,11 @@ void FileSystemAccessPermissionContext::ConfirmSensitiveEntryAccess(
content::GlobalRenderFrameHostId frame_id, content::GlobalRenderFrameHostId frame_id,
base::OnceCallback<void(SensitiveEntryResult)> callback) { base::OnceCallback<void(SensitiveEntryResult)> callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
callback_ = std::move(callback);
auto after_blocklist_check_callback = base::BindOnce( auto after_blocklist_check_callback = base::BindOnce(
&FileSystemAccessPermissionContext::DidCheckPathAgainstBlocklist, &FileSystemAccessPermissionContext::DidCheckPathAgainstBlocklist,
GetWeakPtr(), origin, path, handle_type, user_action, frame_id, GetWeakPtr(), origin, path, handle_type, user_action, frame_id);
std::move(callback));
CheckPathAgainstBlocklist(path_type, path, handle_type, CheckPathAgainstBlocklist(path_type, path, handle_type,
std::move(after_blocklist_check_callback)); std::move(after_blocklist_check_callback));
} }
@ -570,31 +605,54 @@ void FileSystemAccessPermissionContext::PerformAfterWriteChecks(
std::move(callback).Run(AfterWriteCheckResult::kAllow); std::move(callback).Run(AfterWriteCheckResult::kAllow);
} }
void FileSystemAccessPermissionContext::RunRestrictedPathCallback(
SensitiveEntryResult result) {
if (callback_)
std::move(callback_).Run(result);
}
void FileSystemAccessPermissionContext::OnRestrictedPathResult(
gin::Arguments* args) {
SensitiveEntryResult result = SensitiveEntryResult::kAbort;
args->GetNext(&result);
RunRestrictedPathCallback(result);
}
void FileSystemAccessPermissionContext::DidCheckPathAgainstBlocklist( void FileSystemAccessPermissionContext::DidCheckPathAgainstBlocklist(
const url::Origin& origin, const url::Origin& origin,
const base::FilePath& path, const base::FilePath& path,
HandleType handle_type, HandleType handle_type,
UserAction user_action, UserAction user_action,
content::GlobalRenderFrameHostId frame_id, content::GlobalRenderFrameHostId frame_id,
base::OnceCallback<void(SensitiveEntryResult)> callback,
bool should_block) { bool should_block) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (user_action == UserAction::kNone) { if (user_action == UserAction::kNone) {
std::move(callback).Run(should_block ? SensitiveEntryResult::kAbort RunRestrictedPathCallback(should_block ? SensitiveEntryResult::kAbort
: SensitiveEntryResult::kAllowed); : SensitiveEntryResult::kAllowed);
return; return;
} }
// Chromium opens a dialog here, but in Electron's case we log and abort.
if (should_block) { if (should_block) {
LOG(INFO) << path.value() auto* session =
<< " is blocked by the blocklis and cannot be accessed"; electron::api::Session::FromBrowserContext(browser_context());
std::move(callback).Run(SensitiveEntryResult::kAbort); v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
v8::HandleScope scope(isolate);
v8::Local<v8::Object> details =
gin::DataObjectBuilder(isolate)
.Set("origin", origin.GetURL().spec())
.Set("isDirectory", handle_type == HandleType::kDirectory)
.Set("path", path)
.Build();
session->Emit(
"file-system-access-restricted", details,
base::BindRepeating(
&FileSystemAccessPermissionContext::OnRestrictedPathResult,
weak_factory_.GetWeakPtr()));
return; return;
} }
std::move(callback).Run(SensitiveEntryResult::kAllowed); RunRestrictedPathCallback(SensitiveEntryResult::kAllowed);
} }
void FileSystemAccessPermissionContext::MaybeEvictEntries( void FileSystemAccessPermissionContext::MaybeEvictEntries(

View file

@ -21,6 +21,10 @@
class GURL; class GURL;
namespace gin {
class Arguments;
} // namespace gin
namespace base { namespace base {
class FilePath; class FilePath;
} // namespace base } // namespace base
@ -128,15 +132,17 @@ class FileSystemAccessPermissionContext
const base::FilePath& path, const base::FilePath& path,
HandleType handle_type, HandleType handle_type,
base::OnceCallback<void(bool)> callback); base::OnceCallback<void(bool)> callback);
void DidCheckPathAgainstBlocklist( void DidCheckPathAgainstBlocklist(const url::Origin& origin,
const url::Origin& origin,
const base::FilePath& path, const base::FilePath& path,
HandleType handle_type, HandleType handle_type,
UserAction user_action, UserAction user_action,
content::GlobalRenderFrameHostId frame_id, content::GlobalRenderFrameHostId frame_id,
base::OnceCallback<void(SensitiveEntryResult)> callback,
bool should_block); bool should_block);
void RunRestrictedPathCallback(SensitiveEntryResult result);
void OnRestrictedPathResult(gin::Arguments* args);
void MaybeEvictEntries(base::Value::Dict& dict); void MaybeEvictEntries(base::Value::Dict& dict);
void CleanupPermissions(const url::Origin& origin); void CleanupPermissions(const url::Origin& origin);
@ -159,6 +165,8 @@ class FileSystemAccessPermissionContext
std::map<url::Origin, base::Value::Dict> id_pathinfo_map_; std::map<url::Origin, base::Value::Dict> id_pathinfo_map_;
base::OnceCallback<void(SensitiveEntryResult)> callback_;
base::WeakPtrFactory<FileSystemAccessPermissionContext> weak_factory_{this}; base::WeakPtrFactory<FileSystemAccessPermissionContext> weak_factory_{this};
}; };