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

feat: add session.setCorsOriginAccessList API #24849

Closed
wants to merge 1 commit into from
Closed
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -662,6 +662,29 @@ event. The [DownloadItem](download-item.md) will not have any `WebContents` asso
the initial state will be `interrupted`. The download will start only when the
`resume` API is called on the [DownloadItem](download-item.md).

#### `ses.setCorsOriginAccessList(origin, allowPatterns, blockPatterns)`

* `origin` String - The Origin URL for which to set the CORS policy.
* `allowPatterns` String[] - An array of URL patterns to allow.
* `blockPatterns` String[] - An array of URL patterns to block.

Returns `Promise<void>` - Resolves when the operation is complete.

Adds additional URL patterns to the allow and block list of the CORS policy.
This is often used to securely permit pages from your custom protocol to access resources that would otherwise be blocked by CORS, such as `file:///` URLs, without having to use `webSecurity: false`.
This can also be used to block access to certain resources.

```javascript
const { app, session } = require('electron')
app.whenReady().then(async () => {
await session.defaultSession.setCorsOriginAccessList(
'custom://abc/hello.html',
['https://*.github.com/*', '*://electron.github.io', 'file'], /* allow list */
['http://*/*'] /* block list */)
})
```

#### `ses.clearAuthCache()`

Returns `Promise<void>` - resolves when the session’s HTTP authentication cache has been cleared.
@@ -292,7 +292,9 @@ Do not disable `webSecurity` in production applications.

Disabling `webSecurity` will disable the same-origin policy and set
`allowRunningInsecureContent` property to `true`. In other words, it allows
the execution of insecure code from different domains.
the execution of insecure code from different domains. It also allows any
website from any URL to access files on the local computer using
the `file:///` protocol.

### How?

@@ -318,6 +320,24 @@ const mainWindow = new BrowserWindow()
<webview src="page.html"></webview>
```

### Allow custom protocol to access `file:///` resources

Instead of disabling `webSecurity`, consider using the
[`session.setCorsOriginAccessList`][set-cors-origin-access-list] API
to allow cross-origin resource access for your custom protocol.

```javascript
const { app, session } = require('electron')
app.whenReady().then(async () => {
await session.defaultSession.setCorsOriginAccessList(
'my-custom-protocol://abc/index.html',
['file'], /* allow list */
[] /* block list */
)
})
```

## 6) Define a Content Security Policy

A Content Security Policy (CSP) is an additional layer of protection against
@@ -694,3 +714,4 @@ which potential security issues are not as widely known.
[open-external]: ../api/shell.md#shellopenexternalurl-options
[sandbox]: ../api/sandbox-option.md
[responsible-disclosure]: https://en.wikipedia.org/wiki/Responsible_disclosure
[set-cors-origin-access-list]: ../api/session.md#sessetcorsoriginaccesslistorigin-allowpatterns-blockpatterns
@@ -318,6 +318,38 @@ class DictionaryObserver final : public SpellcheckCustomDictionary::Observer {
};
#endif // BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER)

uint16_t GetEffectivePort(const std::string& port_string) {
int port_int = 0;
bool success = base::StringToInt(port_string, &port_int);
// The URLPattern should verify that |port| is a number or "*", so conversion
// should never fail.
DCHECK(success) << port_string;
return port_int;
}

void AddPatternSetToAccessList(
const std::set<URLPattern>& pattern_set,
std::vector<network::mojom::CorsOriginPatternPtr>* list,
network::mojom::CorsOriginAccessMatchPriority priority) {
for (const URLPattern& pattern : pattern_set) {
network::mojom::CorsDomainMatchMode domain_match_mode =
pattern.match_subdomains()
? network::mojom::CorsDomainMatchMode::kAllowSubdomains
: network::mojom::CorsDomainMatchMode::kDisallowSubdomains;
network::mojom::CorsPortMatchMode port_match_mode =
(pattern.port() == "*")
? network::mojom::CorsPortMatchMode::kAllowAnyPort
: network::mojom::CorsPortMatchMode::kAllowOnlySpecifiedPort;
uint16_t port = (port_match_mode ==
network::mojom::CorsPortMatchMode::kAllowOnlySpecifiedPort)
? GetEffectivePort(pattern.port())
: 0u;
list->push_back(network::mojom::CorsOriginPattern::New(
pattern.scheme(), pattern.host(), port, domain_match_mode,
port_match_mode, priority));
}
}

struct UserDataLink : base::SupportsUserData::Data {
explicit UserDataLink(Session* ses) : session(ses) {}

@@ -749,6 +781,32 @@ void Session::DownloadURL(const GURL& url) {
download_manager->DownloadUrl(std::move(download_params));
}

v8::Local<v8::Promise> Session::SetCorsOriginAccessList(
const GURL& url,
const std::set<URLPattern>& allow_pattern_set,
const std::set<URLPattern>& block_pattern_set) {
auto* isolate = JavascriptEnvironment::GetIsolate();
gin_helper::Promise<void> promise(isolate);
v8::Local<v8::Promise> handle = promise.GetHandle();

std::vector<network::mojom::CorsOriginPatternPtr> allow_list;
std::vector<network::mojom::CorsOriginPatternPtr> block_list;
AddPatternSetToAccessList(
allow_pattern_set, &allow_list,
network::mojom::CorsOriginAccessMatchPriority::kDefaultPriority);
AddPatternSetToAccessList(
block_pattern_set, &block_list,
network::mojom::CorsOriginAccessMatchPriority::kDefaultPriority);

browser_context_->SetCorsOriginAccessListForOrigin(
content::BrowserContext::TargetBrowserContexts::kSingleContext,
url::Origin::Create(url), std::move(allow_list), std::move(block_list),
base::BindOnce(gin_helper::Promise<void>::ResolvePromise,
std::move(promise)));

return handle;
}

void Session::CreateInterruptedDownload(const gin_helper::Dictionary& options) {
int64_t offset = 0, length = 0;
double start_time = base::Time::Now().ToDoubleT();
@@ -1174,6 +1232,7 @@ gin::ObjectTemplateBuilder Session::GetObjectTemplateBuilder(
.SetMethod("setSSLConfig", &Session::SetSSLConfig)
.SetMethod("getBlobData", &Session::GetBlobData)
.SetMethod("downloadURL", &Session::DownloadURL)
.SetMethod("setCorsOriginAccessList", &Session::SetCorsOriginAccessList)
.SetMethod("createInterruptedDownload",
&Session::CreateInterruptedDownload)
.SetMethod("setPreloads", &Session::SetPreloads)
@@ -5,6 +5,7 @@
#ifndef SHELL_BROWSER_API_ELECTRON_API_SESSION_H_
#define SHELL_BROWSER_API_ELECTRON_API_SESSION_H_

#include <set>
#include <string>
#include <vector>

@@ -124,6 +125,10 @@ class Session : public gin::Wrappable<Session>,
v8::Local<v8::Value> NetLog(v8::Isolate* isolate);
void Preconnect(const gin_helper::Dictionary& options, gin::Arguments* args);
v8::Local<v8::Promise> CloseAllConnections();
v8::Local<v8::Promise> SetCorsOriginAccessList(
const GURL& url,
const std::set<URLPattern>& allow_pattern_set,
const std::set<URLPattern>& block_pattern_set);
#if BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER)
base::Value GetSpellCheckerLanguages();
void SetSpellCheckerLanguages(gin_helper::ErrorThrower thrower,
@@ -29,19 +29,6 @@

namespace gin {

template <>
struct Converter<URLPattern> {
static bool FromV8(v8::Isolate* isolate,
v8::Local<v8::Value> val,
URLPattern* out) {
std::string pattern;
if (!ConvertFromV8(isolate, val, &pattern))
return false;
*out = URLPattern(URLPattern::SCHEME_ALL);
return out->Parse(pattern) == URLPattern::ParseResult::kSuccess;
}
};

template <>
struct Converter<extensions::WebRequestResourceType> {
static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
@@ -87,6 +87,11 @@ void NetworkContextService::ConfigureNetworkContextParams(
network_context_params->enable_ftp_url_support = true;
#endif // !BUILDFLAG(DISABLE_FTP_SUPPORT)

network_context_params->cors_origin_access_list =
browser_context_->GetSharedCorsOriginAccessList()
->GetOriginAccessList()
.CreateCorsOriginAccessPatternsList();

proxy_config_monitor_.AddToNetworkContextParams(network_context_params);

BrowserProcessImpl::ApplyProxyModeFromCommandLine(
@@ -12,6 +12,7 @@
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/values.h"
#include "extensions/common/url_pattern.h"
#include "gin/converter.h"
#include "gin/dictionary.h"
#include "net/cert/x509_certificate.h"
@@ -400,4 +401,15 @@ v8::Local<v8::Value> Converter<net::RedirectInfo>::ToV8(
return ConvertToV8(isolate, dict);
}

// static
bool Converter<URLPattern>::FromV8(v8::Isolate* isolate,
v8::Local<v8::Value> val,
URLPattern* out) {
std::string pattern;
if (!ConvertFromV8(isolate, val, &pattern))
return false;
*out = URLPattern(URLPattern::SCHEME_ALL);
return out->Parse(pattern) == URLPattern::ParseResult::kSuccess;
}

} // namespace gin
@@ -13,6 +13,8 @@
#include "services/network/public/mojom/fetch_api.mojom.h"
#include "shell/browser/net/cert_verifier_client.h"

class URLPattern;

namespace base {
class DictionaryValue;
class ListValue;
@@ -114,6 +116,13 @@ struct Converter<net::RedirectInfo> {
const net::RedirectInfo& val);
};

template <>
struct Converter<URLPattern> {
static bool FromV8(v8::Isolate* isolate,
v8::Local<v8::Value> val,
URLPattern* out);
};

template <typename K, typename V>
struct Converter<std::vector<std::pair<K, V>>> {
static bool FromV8(v8::Isolate* isolate,