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:
Samuel Attard 2018-08-29 02:05:08 +12:00 коммит произвёл Charles Kerr
Родитель afdb6c5f90
Коммит 68da311ed1
10 изменённых файлов: 142 добавлений и 1 удалений

Просмотреть файл

@ -627,6 +627,18 @@ void Session::SetPermissionRequestHandler(v8::Local<v8::Value> val,
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) {
base::Closure callback;
args->GetNext(&callback);
@ -814,6 +826,8 @@ void Session::BuildPrototype(v8::Isolate* isolate,
.SetMethod("setCertificateVerifyProc", &Session::SetCertVerifyProc)
.SetMethod("setPermissionRequestHandler",
&Session::SetPermissionRequestHandler)
.SetMethod("setPermissionCheckHandler",
&Session::SetPermissionCheckHandler)
.SetMethod("clearHostResolverCache", &Session::ClearHostResolverCache)
.SetMethod("clearAuthCache", &Session::ClearAuthCache)
.SetMethod("allowNTLMCredentialsForDomains",

Просмотреть файл

@ -75,6 +75,8 @@ class Session : public mate::TrackableObject<Session>,
void SetCertVerifyProc(v8::Local<v8::Value> proc, mate::Arguments* args);
void SetPermissionRequestHandler(v8::Local<v8::Value> val,
mate::Arguments* args);
void SetPermissionCheckHandler(v8::Local<v8::Value> val,
mate::Arguments* args);
void ClearHostResolverCache(mate::Arguments* args);
void ClearAuthCache(mate::Arguments* args);
void AllowNTLMCredentialsForDomains(const std::string& domains);

Просмотреть файл

@ -718,7 +718,9 @@ void WebContents::FindReply(content::WebContents* web_contents,
bool WebContents::CheckMediaAccessPermission(content::WebContents* web_contents,
const GURL& security_origin,
content::MediaStreamType type) {
return true;
auto* permission_helper =
WebContentsPermissionHelper::FromWebContents(web_contents);
return permission_helper->CheckMediaAccessPermission(security_origin, type);
}
void WebContents::RequestMediaAccessPermission(

Просмотреть файл

@ -100,6 +100,11 @@ void AtomPermissionManager::SetPermissionRequestHandler(
request_handler_ = handler;
}
void AtomPermissionManager::SetPermissionCheckHandler(
const CheckHandler& handler) {
check_handler_ = handler;
}
int AtomPermissionManager::RequestPermission(
content::PermissionType permission,
content::RenderFrameHost* render_frame_host,
@ -223,4 +228,18 @@ int AtomPermissionManager::SubscribePermissionStatusChange(
void AtomPermissionManager::UnsubscribePermissionStatusChange(
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

Просмотреть файл

@ -31,9 +31,14 @@ class AtomPermissionManager : public content::PermissionManager {
content::PermissionType,
const StatusCallback&,
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.
void SetPermissionRequestHandler(const RequestHandler& handler);
void SetPermissionCheckHandler(const CheckHandler& handler);
// content::PermissionManager:
int RequestPermission(
@ -67,6 +72,11 @@ class AtomPermissionManager : public content::PermissionManager {
const base::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:
void OnPermissionResponse(int request_id,
int permission_id,
@ -93,6 +103,7 @@ class AtomPermissionManager : public content::PermissionManager {
using PendingRequestsMap = base::IDMap<std::unique_ptr<PendingRequest>>;
RequestHandler request_handler_;
CheckHandler check_handler_;
PendingRequestsMap pending_requests_;

Просмотреть файл

@ -14,6 +14,21 @@
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 {
@ -63,6 +78,17 @@ void WebContentsPermissionHelper::RequestPermission(
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(
const base::Callback<void(bool)>& callback) {
RequestPermission(
@ -102,4 +128,15 @@ void WebContentsPermissionHelper::RequestOpenExternalPermission(
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

Просмотреть файл

@ -23,6 +23,7 @@ class WebContentsPermissionHelper
OPEN_EXTERNAL,
};
// Asynchronous Requests
void RequestFullscreenPermission(const base::Callback<void(bool)>& callback);
void RequestMediaAccessPermission(
const content::MediaStreamRequest& request,
@ -34,6 +35,10 @@ class WebContentsPermissionHelper
bool user_gesture,
const GURL& url);
// Synchronous Checks
bool CheckMediaAccessPermission(const GURL& security_origin,
content::MediaStreamType type) const;
private:
explicit WebContentsPermissionHelper(content::WebContents* web_contents);
friend class content::WebContentsUserData<WebContentsPermissionHelper>;
@ -43,6 +48,9 @@ class WebContentsPermissionHelper
bool user_gesture = false,
const base::DictionaryValue* details = nullptr);
bool CheckPermission(content::PermissionType permission,
const base::DictionaryValue* details) const;
content::WebContents* web_contents_;
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])`
* `callback` Function (optional) - Called when operation is done.

Просмотреть файл

@ -106,6 +106,10 @@ describe('chromium feature', () => {
describe('navigator.mediaDevices', () => {
if (isCI) return
afterEach(() => {
remote.getGlobal('permissionChecks').allow()
})
it('can return labels of enumerated devices', (done) => {
navigator.mediaDevices.enumerateDevices().then((devices) => {
const labels = devices.map((device) => device.label)
@ -118,6 +122,19 @@ describe('chromium feature', () => {
}).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) => {
const options = {
origin: null,

Просмотреть файл

@ -81,6 +81,11 @@ ipcMain.on('echo', function (event, msg) {
global.setTimeoutPromisified = util.promisify(setTimeout)
global.permissionChecks = {
allow: () => electron.session.defaultSession.setPermissionCheckHandler(null),
reject: () => electron.session.defaultSession.setPermissionCheckHandler(() => false)
}
const coverage = new Coverage({
outputPath: path.join(__dirname, '..', '..', 'out', 'coverage')
})