feat: add session.setPermissionCheckHandler (#13925)
* feat: add session.setPermissionCheckHandler to handle syncornous permission checks vs requests * spec: add tests for session.setPermissionCheckHandler * docs: add docs for session.setPermissionCheckHandler * feat: add mediaType to media permission checks * chore: cleanup check impl
This commit is contained in:
parent
afdb6c5f90
commit
68da311ed1
10 changed files with 142 additions and 1 deletions
|
@ -627,6 +627,18 @@ void Session::SetPermissionRequestHandler(v8::Local<v8::Value> val,
|
||||||
permission_manager->SetPermissionRequestHandler(handler);
|
permission_manager->SetPermissionRequestHandler(handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Session::SetPermissionCheckHandler(v8::Local<v8::Value> val,
|
||||||
|
mate::Arguments* args) {
|
||||||
|
AtomPermissionManager::CheckHandler handler;
|
||||||
|
if (!(val->IsNull() || mate::ConvertFromV8(args->isolate(), val, &handler))) {
|
||||||
|
args->ThrowError("Must pass null or function");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto* permission_manager = static_cast<AtomPermissionManager*>(
|
||||||
|
browser_context()->GetPermissionManager());
|
||||||
|
permission_manager->SetPermissionCheckHandler(handler);
|
||||||
|
}
|
||||||
|
|
||||||
void Session::ClearHostResolverCache(mate::Arguments* args) {
|
void Session::ClearHostResolverCache(mate::Arguments* args) {
|
||||||
base::Closure callback;
|
base::Closure callback;
|
||||||
args->GetNext(&callback);
|
args->GetNext(&callback);
|
||||||
|
@ -814,6 +826,8 @@ void Session::BuildPrototype(v8::Isolate* isolate,
|
||||||
.SetMethod("setCertificateVerifyProc", &Session::SetCertVerifyProc)
|
.SetMethod("setCertificateVerifyProc", &Session::SetCertVerifyProc)
|
||||||
.SetMethod("setPermissionRequestHandler",
|
.SetMethod("setPermissionRequestHandler",
|
||||||
&Session::SetPermissionRequestHandler)
|
&Session::SetPermissionRequestHandler)
|
||||||
|
.SetMethod("setPermissionCheckHandler",
|
||||||
|
&Session::SetPermissionCheckHandler)
|
||||||
.SetMethod("clearHostResolverCache", &Session::ClearHostResolverCache)
|
.SetMethod("clearHostResolverCache", &Session::ClearHostResolverCache)
|
||||||
.SetMethod("clearAuthCache", &Session::ClearAuthCache)
|
.SetMethod("clearAuthCache", &Session::ClearAuthCache)
|
||||||
.SetMethod("allowNTLMCredentialsForDomains",
|
.SetMethod("allowNTLMCredentialsForDomains",
|
||||||
|
|
|
@ -75,6 +75,8 @@ class Session : public mate::TrackableObject<Session>,
|
||||||
void SetCertVerifyProc(v8::Local<v8::Value> proc, mate::Arguments* args);
|
void SetCertVerifyProc(v8::Local<v8::Value> proc, mate::Arguments* args);
|
||||||
void SetPermissionRequestHandler(v8::Local<v8::Value> val,
|
void SetPermissionRequestHandler(v8::Local<v8::Value> val,
|
||||||
mate::Arguments* args);
|
mate::Arguments* args);
|
||||||
|
void SetPermissionCheckHandler(v8::Local<v8::Value> val,
|
||||||
|
mate::Arguments* args);
|
||||||
void ClearHostResolverCache(mate::Arguments* args);
|
void ClearHostResolverCache(mate::Arguments* args);
|
||||||
void ClearAuthCache(mate::Arguments* args);
|
void ClearAuthCache(mate::Arguments* args);
|
||||||
void AllowNTLMCredentialsForDomains(const std::string& domains);
|
void AllowNTLMCredentialsForDomains(const std::string& domains);
|
||||||
|
|
|
@ -718,7 +718,9 @@ void WebContents::FindReply(content::WebContents* web_contents,
|
||||||
bool WebContents::CheckMediaAccessPermission(content::WebContents* web_contents,
|
bool WebContents::CheckMediaAccessPermission(content::WebContents* web_contents,
|
||||||
const GURL& security_origin,
|
const GURL& security_origin,
|
||||||
content::MediaStreamType type) {
|
content::MediaStreamType type) {
|
||||||
return true;
|
auto* permission_helper =
|
||||||
|
WebContentsPermissionHelper::FromWebContents(web_contents);
|
||||||
|
return permission_helper->CheckMediaAccessPermission(security_origin, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebContents::RequestMediaAccessPermission(
|
void WebContents::RequestMediaAccessPermission(
|
||||||
|
|
|
@ -100,6 +100,11 @@ void AtomPermissionManager::SetPermissionRequestHandler(
|
||||||
request_handler_ = handler;
|
request_handler_ = handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AtomPermissionManager::SetPermissionCheckHandler(
|
||||||
|
const CheckHandler& handler) {
|
||||||
|
check_handler_ = handler;
|
||||||
|
}
|
||||||
|
|
||||||
int AtomPermissionManager::RequestPermission(
|
int AtomPermissionManager::RequestPermission(
|
||||||
content::PermissionType permission,
|
content::PermissionType permission,
|
||||||
content::RenderFrameHost* render_frame_host,
|
content::RenderFrameHost* render_frame_host,
|
||||||
|
@ -223,4 +228,18 @@ int AtomPermissionManager::SubscribePermissionStatusChange(
|
||||||
void AtomPermissionManager::UnsubscribePermissionStatusChange(
|
void AtomPermissionManager::UnsubscribePermissionStatusChange(
|
||||||
int subscription_id) {}
|
int subscription_id) {}
|
||||||
|
|
||||||
|
bool AtomPermissionManager::CheckPermissionWithDetails(
|
||||||
|
content::PermissionType permission,
|
||||||
|
content::RenderFrameHost* render_frame_host,
|
||||||
|
const GURL& requesting_origin,
|
||||||
|
const base::DictionaryValue* details) const {
|
||||||
|
if (check_handler_.is_null()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
auto* web_contents =
|
||||||
|
content::WebContents::FromRenderFrameHost(render_frame_host);
|
||||||
|
return check_handler_.Run(web_contents, permission, requesting_origin,
|
||||||
|
*details);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace atom
|
} // namespace atom
|
||||||
|
|
|
@ -31,9 +31,14 @@ class AtomPermissionManager : public content::PermissionManager {
|
||||||
content::PermissionType,
|
content::PermissionType,
|
||||||
const StatusCallback&,
|
const StatusCallback&,
|
||||||
const base::DictionaryValue&)>;
|
const base::DictionaryValue&)>;
|
||||||
|
using CheckHandler = base::Callback<bool(content::WebContents*,
|
||||||
|
content::PermissionType,
|
||||||
|
const GURL& requesting_origin,
|
||||||
|
const base::DictionaryValue&)>;
|
||||||
|
|
||||||
// Handler to dispatch permission requests in JS.
|
// Handler to dispatch permission requests in JS.
|
||||||
void SetPermissionRequestHandler(const RequestHandler& handler);
|
void SetPermissionRequestHandler(const RequestHandler& handler);
|
||||||
|
void SetPermissionCheckHandler(const CheckHandler& handler);
|
||||||
|
|
||||||
// content::PermissionManager:
|
// content::PermissionManager:
|
||||||
int RequestPermission(
|
int RequestPermission(
|
||||||
|
@ -67,6 +72,11 @@ class AtomPermissionManager : public content::PermissionManager {
|
||||||
const base::Callback<
|
const base::Callback<
|
||||||
void(const std::vector<blink::mojom::PermissionStatus>&)>& callback);
|
void(const std::vector<blink::mojom::PermissionStatus>&)>& callback);
|
||||||
|
|
||||||
|
bool CheckPermissionWithDetails(content::PermissionType permission,
|
||||||
|
content::RenderFrameHost* render_frame_host,
|
||||||
|
const GURL& requesting_origin,
|
||||||
|
const base::DictionaryValue* details) const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void OnPermissionResponse(int request_id,
|
void OnPermissionResponse(int request_id,
|
||||||
int permission_id,
|
int permission_id,
|
||||||
|
@ -93,6 +103,7 @@ class AtomPermissionManager : public content::PermissionManager {
|
||||||
using PendingRequestsMap = base::IDMap<std::unique_ptr<PendingRequest>>;
|
using PendingRequestsMap = base::IDMap<std::unique_ptr<PendingRequest>>;
|
||||||
|
|
||||||
RequestHandler request_handler_;
|
RequestHandler request_handler_;
|
||||||
|
CheckHandler check_handler_;
|
||||||
|
|
||||||
PendingRequestsMap pending_requests_;
|
PendingRequestsMap pending_requests_;
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,21 @@
|
||||||
|
|
||||||
DEFINE_WEB_CONTENTS_USER_DATA_KEY(atom::WebContentsPermissionHelper);
|
DEFINE_WEB_CONTENTS_USER_DATA_KEY(atom::WebContentsPermissionHelper);
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
std::string MediaStreamTypeToString(content::MediaStreamType type) {
|
||||||
|
switch (type) {
|
||||||
|
case content::MediaStreamType::MEDIA_DEVICE_AUDIO_CAPTURE:
|
||||||
|
return "audio";
|
||||||
|
case content::MediaStreamType::MEDIA_DEVICE_VIDEO_CAPTURE:
|
||||||
|
return "video";
|
||||||
|
default:
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
namespace atom {
|
namespace atom {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
@ -63,6 +78,17 @@ void WebContentsPermissionHelper::RequestPermission(
|
||||||
base::Bind(&OnPermissionResponse, callback));
|
base::Bind(&OnPermissionResponse, callback));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool WebContentsPermissionHelper::CheckPermission(
|
||||||
|
content::PermissionType permission,
|
||||||
|
const base::DictionaryValue* details) const {
|
||||||
|
auto* rfh = web_contents_->GetMainFrame();
|
||||||
|
auto* permission_manager = static_cast<AtomPermissionManager*>(
|
||||||
|
web_contents_->GetBrowserContext()->GetPermissionManager());
|
||||||
|
auto origin = web_contents_->GetLastCommittedURL();
|
||||||
|
return permission_manager->CheckPermissionWithDetails(permission, rfh, origin,
|
||||||
|
details);
|
||||||
|
}
|
||||||
|
|
||||||
void WebContentsPermissionHelper::RequestFullscreenPermission(
|
void WebContentsPermissionHelper::RequestFullscreenPermission(
|
||||||
const base::Callback<void(bool)>& callback) {
|
const base::Callback<void(bool)>& callback) {
|
||||||
RequestPermission(
|
RequestPermission(
|
||||||
|
@ -102,4 +128,15 @@ void WebContentsPermissionHelper::RequestOpenExternalPermission(
|
||||||
callback, user_gesture, &details);
|
callback, user_gesture, &details);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool WebContentsPermissionHelper::CheckMediaAccessPermission(
|
||||||
|
const GURL& security_origin,
|
||||||
|
content::MediaStreamType type) const {
|
||||||
|
base::DictionaryValue details;
|
||||||
|
details.SetString("securityOrigin", security_origin.spec());
|
||||||
|
details.SetString("mediaType", MediaStreamTypeToString(type));
|
||||||
|
// The permission type doesn't matter here, AUDIO_CAPTURE/VIDEO_CAPTURE
|
||||||
|
// are presented as same type in content_converter.h.
|
||||||
|
return CheckPermission(content::PermissionType::AUDIO_CAPTURE, &details);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace atom
|
} // namespace atom
|
||||||
|
|
|
@ -23,6 +23,7 @@ class WebContentsPermissionHelper
|
||||||
OPEN_EXTERNAL,
|
OPEN_EXTERNAL,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Asynchronous Requests
|
||||||
void RequestFullscreenPermission(const base::Callback<void(bool)>& callback);
|
void RequestFullscreenPermission(const base::Callback<void(bool)>& callback);
|
||||||
void RequestMediaAccessPermission(
|
void RequestMediaAccessPermission(
|
||||||
const content::MediaStreamRequest& request,
|
const content::MediaStreamRequest& request,
|
||||||
|
@ -34,6 +35,10 @@ class WebContentsPermissionHelper
|
||||||
bool user_gesture,
|
bool user_gesture,
|
||||||
const GURL& url);
|
const GURL& url);
|
||||||
|
|
||||||
|
// Synchronous Checks
|
||||||
|
bool CheckMediaAccessPermission(const GURL& security_origin,
|
||||||
|
content::MediaStreamType type) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
explicit WebContentsPermissionHelper(content::WebContents* web_contents);
|
explicit WebContentsPermissionHelper(content::WebContents* web_contents);
|
||||||
friend class content::WebContentsUserData<WebContentsPermissionHelper>;
|
friend class content::WebContentsUserData<WebContentsPermissionHelper>;
|
||||||
|
@ -43,6 +48,9 @@ class WebContentsPermissionHelper
|
||||||
bool user_gesture = false,
|
bool user_gesture = false,
|
||||||
const base::DictionaryValue* details = nullptr);
|
const base::DictionaryValue* details = nullptr);
|
||||||
|
|
||||||
|
bool CheckPermission(content::PermissionType permission,
|
||||||
|
const base::DictionaryValue* details) const;
|
||||||
|
|
||||||
content::WebContents* web_contents_;
|
content::WebContents* web_contents_;
|
||||||
|
|
||||||
DISALLOW_COPY_AND_ASSIGN(WebContentsPermissionHelper);
|
DISALLOW_COPY_AND_ASSIGN(WebContentsPermissionHelper);
|
||||||
|
|
|
@ -311,6 +311,32 @@ session.fromPartition('some-partition').setPermissionRequestHandler((webContents
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### `ses.setPermissionCheckHandler(handler)`
|
||||||
|
|
||||||
|
* `handler` Function<Boolean> | null
|
||||||
|
* `webContents` [WebContents](web-contents.md) - WebContents checking the permission.
|
||||||
|
* `permission` String - Enum of 'media'.
|
||||||
|
* `requestingOrigin` String - The origin URL of the permission check
|
||||||
|
* `details` Object - Some properties are only available on certain permission types.
|
||||||
|
* `securityOrigin` String - The security orign of the `media` check.
|
||||||
|
* `mediaType` String - The type of media access being requested, can be `video`,
|
||||||
|
`audio` or `unknown`
|
||||||
|
|
||||||
|
Sets the handler which can be used to respond to permission checks for the `session`.
|
||||||
|
Returning `true` will allow the permission and `false` will reject it.
|
||||||
|
To clear the handler, call `setPermissionCheckHandler(null)`.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const {session} = require('electron')
|
||||||
|
session.fromPartition('some-partition').setPermissionCheckHandler((webContents, permission) => {
|
||||||
|
if (webContents.getURL() === 'some-host' && permission === 'notifications') {
|
||||||
|
return false // denied
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
#### `ses.clearHostResolverCache([callback])`
|
#### `ses.clearHostResolverCache([callback])`
|
||||||
|
|
||||||
* `callback` Function (optional) - Called when operation is done.
|
* `callback` Function (optional) - Called when operation is done.
|
||||||
|
|
|
@ -106,6 +106,10 @@ describe('chromium feature', () => {
|
||||||
describe('navigator.mediaDevices', () => {
|
describe('navigator.mediaDevices', () => {
|
||||||
if (isCI) return
|
if (isCI) return
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
remote.getGlobal('permissionChecks').allow()
|
||||||
|
})
|
||||||
|
|
||||||
it('can return labels of enumerated devices', (done) => {
|
it('can return labels of enumerated devices', (done) => {
|
||||||
navigator.mediaDevices.enumerateDevices().then((devices) => {
|
navigator.mediaDevices.enumerateDevices().then((devices) => {
|
||||||
const labels = devices.map((device) => device.label)
|
const labels = devices.map((device) => device.label)
|
||||||
|
@ -118,6 +122,19 @@ describe('chromium feature', () => {
|
||||||
}).catch(done)
|
}).catch(done)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('does not return labels of enumerated devices when permission denied', (done) => {
|
||||||
|
remote.getGlobal('permissionChecks').reject()
|
||||||
|
navigator.mediaDevices.enumerateDevices().then((devices) => {
|
||||||
|
const labels = devices.map((device) => device.label)
|
||||||
|
const labelFound = labels.some((label) => !!label)
|
||||||
|
if (labelFound) {
|
||||||
|
done(new Error(`Device labels were found: ${JSON.stringify(labels)}`))
|
||||||
|
} else {
|
||||||
|
done()
|
||||||
|
}
|
||||||
|
}).catch(done)
|
||||||
|
})
|
||||||
|
|
||||||
it('can return new device id when cookie storage is cleared', (done) => {
|
it('can return new device id when cookie storage is cleared', (done) => {
|
||||||
const options = {
|
const options = {
|
||||||
origin: null,
|
origin: null,
|
||||||
|
|
|
@ -81,6 +81,11 @@ ipcMain.on('echo', function (event, msg) {
|
||||||
|
|
||||||
global.setTimeoutPromisified = util.promisify(setTimeout)
|
global.setTimeoutPromisified = util.promisify(setTimeout)
|
||||||
|
|
||||||
|
global.permissionChecks = {
|
||||||
|
allow: () => electron.session.defaultSession.setPermissionCheckHandler(null),
|
||||||
|
reject: () => electron.session.defaultSession.setPermissionCheckHandler(() => false)
|
||||||
|
}
|
||||||
|
|
||||||
const coverage = new Coverage({
|
const coverage = new Coverage({
|
||||||
outputPath: path.join(__dirname, '..', '..', 'out', 'coverage')
|
outputPath: path.join(__dirname, '..', '..', 'out', 'coverage')
|
||||||
})
|
})
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue