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
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'
Returns:

View file

@ -11,6 +11,7 @@
#include "base/files/file_path.h"
#include "base/json/values_util.h"
#include "base/path_service.h"
#include "base/task/bind_post_task.h"
#include "base/task/thread_pool.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
@ -26,18 +27,52 @@
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.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/web_contents_permission_helper.h"
#include "shell/common/gin_converters/callback_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 "ui/base/l10n/l10n_util.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 {
using BlockType = ChromeFileSystemAccessPermissionContext::BlockType;
using HandleType = content::FileSystemAccessPermissionContext::HandleType;
using GrantType = electron::FileSystemAccessPermissionContext::GrantType;
using SensitiveEntryResult =
ChromeFileSystemAccessPermissionContext::SensitiveEntryResult;
using blink::mojom::PermissionStatus;
// Dictionary keys for the FILE_SYSTEM_LAST_PICKED_DIRECTORY website setting.
@ -527,11 +562,11 @@ void FileSystemAccessPermissionContext::ConfirmSensitiveEntryAccess(
content::GlobalRenderFrameHostId frame_id,
base::OnceCallback<void(SensitiveEntryResult)> callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
callback_ = std::move(callback);
auto after_blocklist_check_callback = base::BindOnce(
&FileSystemAccessPermissionContext::DidCheckPathAgainstBlocklist,
GetWeakPtr(), origin, path, handle_type, user_action, frame_id,
std::move(callback));
GetWeakPtr(), origin, path, handle_type, user_action, frame_id);
CheckPathAgainstBlocklist(path_type, path, handle_type,
std::move(after_blocklist_check_callback));
}
@ -570,31 +605,54 @@ void FileSystemAccessPermissionContext::PerformAfterWriteChecks(
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(
const url::Origin& origin,
const base::FilePath& path,
HandleType handle_type,
UserAction user_action,
content::GlobalRenderFrameHostId frame_id,
base::OnceCallback<void(SensitiveEntryResult)> callback,
bool should_block) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (user_action == UserAction::kNone) {
std::move(callback).Run(should_block ? SensitiveEntryResult::kAbort
: SensitiveEntryResult::kAllowed);
RunRestrictedPathCallback(should_block ? SensitiveEntryResult::kAbort
: SensitiveEntryResult::kAllowed);
return;
}
// Chromium opens a dialog here, but in Electron's case we log and abort.
if (should_block) {
LOG(INFO) << path.value()
<< " is blocked by the blocklis and cannot be accessed";
std::move(callback).Run(SensitiveEntryResult::kAbort);
auto* session =
electron::api::Session::FromBrowserContext(browser_context());
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;
}
std::move(callback).Run(SensitiveEntryResult::kAllowed);
RunRestrictedPathCallback(SensitiveEntryResult::kAllowed);
}
void FileSystemAccessPermissionContext::MaybeEvictEntries(

View file

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