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:
parent
f797f92716
commit
9e14f8d828
3 changed files with 149 additions and 18 deletions
|
@ -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:
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue