Commit 446a91b0 authored by Pavel Feldman's avatar Pavel Feldman Committed by Commit Bot

DevTools: allow overriding permissions for all origins within context.

Permission overrides were implemented for the automation purposes
(WebDriver, Puppeteer, Playwright, etc). Before this change, protocol
required concrete |origin| to be specified in order to override the
permissions. This change makes this |origin| input parameter optional
and allows global permission override within the given browser context.

TBR=mlamouri for updating chrome/ due to changes in content/ semantics.

Change-Id: I35e78307643b179a46e296d81f261e5b596c6252
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2095668
Commit-Queue: Pavel Feldman <pfeldman@chromium.org>
Reviewed-by: default avatarPeter Marshall <petermarshall@chromium.org>
Reviewed-by: default avatarYang Guo <yangguo@chromium.org>
Reviewed-by: default avatarAndrey Kosyakov <caseq@chromium.org>
Cr-Commit-Position: refs/heads/master@{#750221}
parent 9be7b29d
......@@ -526,13 +526,15 @@ PermissionStatus PermissionManager::GetPermissionStatusForFrame(
bool PermissionManager::IsPermissionOverridableByDevTools(
content::PermissionType permission,
const url::Origin& origin) {
const base::Optional<url::Origin>& origin) {
ContentSettingsType type = PermissionTypeToContentSettingSafe(permission);
PermissionContextBase* context = GetPermissionContext(type);
return context && !context->IsPermissionKillSwitchOn() &&
context->IsPermissionAvailableToOrigins(origin.GetURL(),
origin.GetURL());
if (!context || context->IsPermissionKillSwitchOn())
return false;
return !origin || context->IsPermissionAvailableToOrigins(origin->GetURL(),
origin->GetURL());
}
int PermissionManager::SubscribePermissionStatusChange(
......@@ -685,7 +687,7 @@ PermissionResult PermissionManager::GetPermissionStatusHelper(
}
void PermissionManager::SetPermissionOverridesForDevTools(
const url::Origin& origin,
const base::Optional<url::Origin>& optional_origin,
const PermissionOverrides& overrides) {
ContentSettingsTypeOverrides result;
for (const auto& item : overrides) {
......@@ -694,6 +696,8 @@ void PermissionManager::SetPermissionOverridesForDevTools(
if (content_setting != ContentSettingsType::DEFAULT)
result[content_setting] = PermissionStatusToContentSetting(item.second);
}
const url::Origin& origin =
optional_origin.value_or(devtools_global_overrides_origin_);
devtools_permission_overrides_[origin] = std::move(result);
}
......@@ -705,6 +709,8 @@ ContentSetting PermissionManager::GetPermissionOverrideForDevTools(
const url::Origin& origin,
ContentSettingsType permission) {
auto it = devtools_permission_overrides_.find(origin);
if (it == devtools_permission_overrides_.end())
it = devtools_permission_overrides_.find(devtools_global_overrides_origin_);
if (it == devtools_permission_overrides_.end())
return CONTENT_SETTING_DEFAULT;
......
......@@ -111,8 +111,9 @@ class PermissionManager : public KeyedService,
content::PermissionType permission,
content::RenderFrameHost* render_frame_host,
const GURL& requesting_origin) override;
bool IsPermissionOverridableByDevTools(content::PermissionType permission,
const url::Origin& origin) override;
bool IsPermissionOverridableByDevTools(
content::PermissionType permission,
const base::Optional<url::Origin>& origin) override;
int SubscribePermissionStatusChange(
content::PermissionType permission,
content::RenderFrameHost* render_frame_host,
......@@ -129,7 +130,7 @@ class PermissionManager : public KeyedService,
// For the given |origin|, overrides permissions that belong to |overrides|.
// These permissions are in-sync with the PermissionController.
void SetPermissionOverridesForDevTools(
const url::Origin& origin,
const base::Optional<url::Origin>& origin,
const PermissionOverrides& overrides) override;
void ResetPermissionOverridesForDevTools() override;
......@@ -187,6 +188,7 @@ class PermissionManager : public KeyedService,
base::flat_map<ContentSettingsType, ContentSetting>;
std::map<url::Origin, ContentSettingsTypeOverrides>
devtools_permission_overrides_;
url::Origin devtools_global_overrides_origin_;
bool is_shutting_down_ = false;
......
......@@ -304,7 +304,7 @@ Response BrowserHandler::FindBrowserContext(
}
Response BrowserHandler::SetPermission(
const std::string& origin,
Maybe<std::string> origin,
std::unique_ptr<protocol::Browser::PermissionDescriptor> permission,
const protocol::Browser::PermissionSetting& setting,
Maybe<std::string> browser_context_id) {
......@@ -327,11 +327,14 @@ Response BrowserHandler::SetPermission(
PermissionControllerImpl* permission_controller =
PermissionControllerImpl::FromBrowserContext(browser_context);
url::Origin overridden_origin = url::Origin::Create(GURL(origin));
if (overridden_origin.opaque())
return Response::InvalidParams(
"Permission can't be granted to opaque origins.");
base::Optional<url::Origin> overridden_origin;
if (origin.isJust()) {
overridden_origin = url::Origin::Create(GURL(origin.fromJust()));
if (overridden_origin->opaque())
return Response::InvalidParams(
"Permission can't be granted to opaque origins.");
}
PermissionControllerImpl::OverrideStatus status =
permission_controller->SetOverrideForDevTools(overridden_origin, type,
permission_status);
......@@ -345,7 +348,7 @@ Response BrowserHandler::SetPermission(
}
Response BrowserHandler::GrantPermissions(
const std::string& origin,
Maybe<std::string> origin,
std::unique_ptr<protocol::Array<protocol::Browser::PermissionType>>
permissions,
Maybe<std::string> browser_context_id) {
......@@ -366,14 +369,17 @@ Response BrowserHandler::GrantPermissions(
PermissionControllerImpl* permission_controller =
PermissionControllerImpl::FromBrowserContext(browser_context);
url::Origin overridden_origin = url::Origin::Create(GURL(origin));
if (overridden_origin.opaque())
return Response::InvalidParams(
"Permission can't be granted to opaque origins.");
base::Optional<url::Origin> overridden_origin;
if (origin.isJust()) {
overridden_origin = url::Origin::Create(GURL(origin.fromJust()));
if (overridden_origin->opaque())
return Response::InvalidParams(
"Permission can't be granted to opaque origins.");
}
PermissionControllerImpl::OverrideStatus status =
permission_controller->GrantOverridesForDevTools(overridden_origin,
internal_permissions);
if (status != PermissionControllerImpl::OverrideStatus::kOverrideSet) {
return Response::InvalidParams(
"Permissions can't be granted in current context.");
......
......@@ -50,13 +50,13 @@ class BrowserHandler : public DevToolsDomainHandler, public Browser::Backend {
std::unique_ptr<protocol::Array<std::string>>* arguments) override;
Response SetPermission(
const std::string& origin,
Maybe<std::string> origin,
std::unique_ptr<protocol::Browser::PermissionDescriptor> permission,
const protocol::Browser::PermissionSetting& setting,
Maybe<std::string> browser_context_id) override;
Response GrantPermissions(
const std::string& origin,
Maybe<std::string> origin,
std::unique_ptr<protocol::Array<protocol::Browser::PermissionType>>
permissions,
Maybe<std::string> browser_context_id) override;
......
......@@ -188,8 +188,8 @@ void PermissionControllerImpl::NotifyChangedSubscriptions(
PermissionControllerImpl::OverrideStatus
PermissionControllerImpl::SetOverrideForDevTools(
const url::Origin& origin,
const PermissionType& permission,
const base::Optional<url::Origin>& origin,
PermissionType permission,
const blink::mojom::PermissionStatus& status) {
PermissionControllerDelegate* delegate =
browser_context_->GetPermissionControllerDelegate();
......@@ -197,8 +197,8 @@ PermissionControllerImpl::SetOverrideForDevTools(
!delegate->IsPermissionOverridableByDevTools(permission, origin)) {
return OverrideStatus::kOverrideNotSet;
}
const auto old_statuses = GetSubscriptionsStatuses(origin.GetURL());
const auto old_statuses = GetSubscriptionsStatuses(
origin ? base::make_optional(origin->GetURL()) : base::nullopt);
devtools_permission_overrides_.Set(origin, permission, status);
NotifyChangedSubscriptions(old_statuses);
......@@ -208,16 +208,19 @@ PermissionControllerImpl::SetOverrideForDevTools(
PermissionControllerImpl::OverrideStatus
PermissionControllerImpl::GrantOverridesForDevTools(
const url::Origin& origin,
const base::Optional<url::Origin>& origin,
const std::vector<PermissionType>& permissions) {
PermissionControllerDelegate* delegate =
browser_context_->GetPermissionControllerDelegate();
if (delegate)
for (const auto permission : permissions)
if (delegate) {
for (const auto permission : permissions) {
if (!delegate->IsPermissionOverridableByDevTools(permission, origin))
return OverrideStatus::kOverrideNotSet;
}
}
const auto old_statuses = GetSubscriptionsStatuses(origin.GetURL());
const auto old_statuses = GetSubscriptionsStatuses(
origin ? base::make_optional(origin->GetURL()) : base::nullopt);
devtools_permission_overrides_.GrantPermissions(origin, permissions);
// If any statuses changed because they lose overrides or the new overrides
// modify their previous state (overridden or not), subscribers must be
......@@ -243,7 +246,7 @@ void PermissionControllerImpl::ResetOverridesForDevTools() {
}
void PermissionControllerImpl::UpdateDelegateOverridesForDevTools(
const url::Origin& origin) {
const base::Optional<url::Origin>& origin) {
PermissionControllerDelegate* delegate =
browser_context_->GetPermissionControllerDelegate();
if (!delegate)
......
......@@ -31,13 +31,14 @@ class CONTENT_EXPORT PermissionControllerImpl : public PermissionController {
enum class OverrideStatus { kOverrideNotSet, kOverrideSet };
// For the given |origin|, grant permissions in |overrides| and reject all
// others.
// others. If no |origin| is specified, grant permissions to all origins in
// the browser context.
OverrideStatus GrantOverridesForDevTools(
const url::Origin& origin,
const base::Optional<url::Origin>& origin,
const std::vector<PermissionType>& permissions);
OverrideStatus SetOverrideForDevTools(
const url::Origin& origin,
const PermissionType& permission,
const base::Optional<url::Origin>& origin,
PermissionType permission,
const blink::mojom::PermissionStatus& status);
void ResetOverridesForDevTools();
......@@ -93,7 +94,8 @@ class CONTENT_EXPORT PermissionControllerImpl : public PermissionController {
void NotifyChangedSubscriptions(const SubscriptionsStatusMap& old_statuses);
void OnDelegatePermissionStatusChange(Subscription* subscription,
blink::mojom::PermissionStatus status);
void UpdateDelegateOverridesForDevTools(const url::Origin& origin);
void UpdateDelegateOverridesForDevTools(
const base::Optional<url::Origin>& origin);
DevToolsPermissionOverrides devtools_permission_overrides_;
SubscriptionsMap subscriptions_;
......
......@@ -42,11 +42,11 @@ class MockManagerWithRequests : public MockPermissionManager {
const base::OnceCallback<void(
const std::vector<blink::mojom::PermissionStatus>&)> callback));
MOCK_METHOD2(SetPermissionOverridesForDevTools,
void(const url::Origin& origin,
void(const base::Optional<url::Origin>& origin,
const PermissionOverrides& overrides));
MOCK_METHOD0(ResetPermissionOverridesForDevTools, void());
MOCK_METHOD2(IsPermissionOverridableByDevTools,
bool(PermissionType, const url::Origin&));
bool(PermissionType, const base::Optional<url::Origin>&));
private:
DISALLOW_COPY_AND_ASSIGN(MockManagerWithRequests);
......@@ -92,7 +92,7 @@ TEST_F(PermissionControllerImplTest, ResettingOverridesForwardsReset) {
}
TEST_F(PermissionControllerImplTest, SettingOverridesForwardsUpdates) {
url::Origin kTestOrigin = url::Origin::Create(GURL(kTestUrl));
auto kTestOrigin = base::make_optional(url::Origin::Create(GURL(kTestUrl)));
EXPECT_CALL(*mock_manager(),
SetPermissionOverridesForDevTools(
kTestOrigin, testing::ElementsAre(testing::Pair(
......
......@@ -17,10 +17,12 @@ DevToolsPermissionOverrides::DevToolsPermissionOverrides(
DevToolsPermissionOverrides& DevToolsPermissionOverrides::operator=(
DevToolsPermissionOverrides&& other) = default;
void DevToolsPermissionOverrides::Set(const url::Origin& origin,
const PermissionType& permission,
const PermissionStatus status) {
PermissionOverrides& origin_overrides = overrides_[origin];
void DevToolsPermissionOverrides::Set(
const base::Optional<url::Origin>& origin,
PermissionType permission,
const blink::mojom::PermissionStatus& status) {
PermissionOverrides& origin_overrides =
overrides_[origin.value_or(global_overrides_origin_)];
origin_overrides[permission] = status;
// Special override status - MIDI_SYSEX is stronger than MIDI, meaning that
......@@ -37,29 +39,37 @@ void DevToolsPermissionOverrides::Set(const url::Origin& origin,
base::Optional<PermissionStatus> DevToolsPermissionOverrides::Get(
const url::Origin& origin,
const PermissionType& type) const {
PermissionType permission) const {
auto current_override = overrides_.find(origin);
if (current_override == overrides_.end())
current_override = overrides_.find(global_overrides_origin_);
if (current_override == overrides_.end())
return base::nullopt;
auto new_status = current_override->second.find(type);
if (new_status == current_override->second.end())
return base::nullopt;
return base::make_optional(new_status->second);
auto new_status = current_override->second.find(permission);
if (new_status != current_override->second.end())
return base::make_optional(new_status->second);
return base::nullopt;
}
const PermissionOverrides& DevToolsPermissionOverrides::GetAll(
const url::Origin& origin) const {
const base::Optional<url::Origin>& origin) const {
static const base::NoDestructor<PermissionOverrides> empty_overrides;
auto it = overrides_.find(origin);
auto it = origin ? overrides_.find(*origin) : overrides_.end();
if (it == overrides_.end())
it = overrides_.find(global_overrides_origin_);
if (it == overrides_.end())
return *empty_overrides;
return it->second;
}
void DevToolsPermissionOverrides::Reset(
const base::Optional<url::Origin>& origin) {
overrides_.erase(origin.value_or(global_overrides_origin_));
}
void DevToolsPermissionOverrides::GrantPermissions(
const url::Origin& origin,
const base::Optional<url::Origin>& origin,
const std::vector<PermissionType>& permissions) {
const std::vector<PermissionType>& kAllPermissionTypes =
GetAllPermissionTypes();
......@@ -69,14 +79,8 @@ void DevToolsPermissionOverrides::GrantPermissions(
for (const auto& permission : permissions)
granted_overrides[permission] = PermissionStatus::GRANTED;
Reset(origin);
SetAll(origin, granted_overrides);
}
void DevToolsPermissionOverrides::SetAll(const url::Origin& origin,
const PermissionOverrides& overrides) {
PermissionOverrides& current_override = overrides_[origin];
for (const auto& setting : overrides)
current_override[setting.first] = setting.second;
for (const auto& setting : granted_overrides)
Set(origin, setting.first, setting.second);
}
} // namespace content
......@@ -32,31 +32,34 @@ class CONTENT_EXPORT DevToolsPermissionOverrides {
base::flat_map<PermissionType, blink::mojom::PermissionStatus>;
// Set permission override for |permission| at |origin| to |status|.
void Set(const url::Origin& origin,
const PermissionType& permission,
const blink::mojom::PermissionStatus status);
// Null |origin| specifies global overrides.
void Set(const base::Optional<url::Origin>& origin,
PermissionType permission,
const blink::mojom::PermissionStatus& status);
// Get override for |origin| set for |permission|, if specified.
base::Optional<blink::mojom::PermissionStatus> Get(
const url::Origin& origin,
const PermissionType& permission) const;
PermissionType permission) const;
// Get all overrides for particular |origin|, stored in |overrides| if found.
// Will return empty overrides if none previously existed.
const PermissionOverrides& GetAll(const url::Origin& origin) const;
// Get all overrides for particular |origin|, stored in |overrides|
// if found. Will return empty overrides if none previously existed. Returns
// global overrides when |origin| is nullptr.
const PermissionOverrides& GetAll(
const base::Optional<url::Origin>& origin) const;
// Resets overrides for |origin|.
void Reset(const url::Origin& origin) { overrides_.erase(origin); }
// Null |origin| resets global overrides.
void Reset(const base::Optional<url::Origin>& origin);
// Sets status for |permissions| to GRANTED in |origin|, and DENIED for all
// others.
void GrantPermissions(const url::Origin& origin,
// Sets status for |permissions| to GRANTED in |origin|, and DENIED
// for all others.
// Null |origin| grants permissions globally for context.
void GrantPermissions(const base::Optional<url::Origin>& origin,
const std::vector<PermissionType>& permissions);
private:
// Insert/adds every entry of |overrides| to |origin|'s overrides.
void SetAll(const url::Origin& origin, const PermissionOverrides& overrides);
url::Origin global_overrides_origin_;
base::flat_map<url::Origin, PermissionOverrides> overrides_;
};
......
......@@ -8,7 +8,7 @@ namespace content {
bool PermissionControllerDelegate::IsPermissionOverridableByDevTools(
PermissionType permission,
const url::Origin& origin) {
const base::Optional<url::Origin>& origin) {
return true;
}
......
......@@ -100,7 +100,7 @@ class CONTENT_EXPORT PermissionControllerDelegate {
// are tracked by the delegate. This method should only be called by the
// PermissionController owning the delegate.
virtual void SetPermissionOverridesForDevTools(
const url::Origin& origin,
const base::Optional<url::Origin>& origin,
const PermissionOverrides& overrides) {}
// Removes overrides that have been set, if any, for all origins. If delegate
......@@ -109,8 +109,9 @@ class CONTENT_EXPORT PermissionControllerDelegate {
// Returns whether permission can be overridden by
// DevToolsPermissionOverrides.
virtual bool IsPermissionOverridableByDevTools(PermissionType permission,
const url::Origin& origin);
virtual bool IsPermissionOverridableByDevTools(
PermissionType permission,
const base::Optional<url::Origin>& origin);
};
} // namespace content
......
......@@ -632,8 +632,8 @@ domain Browser
# Set permission settings for given origin.
experimental command setPermission
parameters
# Origin the permission applies to.
string origin
# Origin the permission applies to, all origins if not specified.
optional string origin
# Descriptor of permission to override.
PermissionDescriptor permission
# Setting of the permission.
......@@ -644,7 +644,8 @@ domain Browser
# Grant specific permissions to the given origin and reject all others.
experimental command grantPermissions
parameters
string origin
# Origin the permission applies to, all origins if not specified.
optional string origin
array of PermissionType permissions
# BrowserContext to override permissions. When omitted, default browser context is used.
optional BrowserContextID browserContextId
......
Test that permissions could be granted to all origins
Grant geolocation audioCapture to all
Granted: ["geolocation","audioCapture"]
Query geolocation
=> granted
Query microphone
=> granted
Resetting all permissions
Query geolocation
=> denied
Query microphone
=> denied
Testing local permissions override global permissions
Grant geolocation to all
Granted: ["geolocation"]
Grant audioCapture to http://devtools.test:8000
Granted: ["audioCapture"]
Query geolocation
=> denied
Query microphone
=> granted
(async function(testRunner) {
const {page, session, dp} = await testRunner.startBlank(
`Test that permissions could be granted to all origins`);
// Reset all permissions initially.
await dp.Browser.resetPermissions();
await page.navigate('http://devtools.test:8000/inspector-protocol/resources/empty.html');
await grant(undefined, 'geolocation', 'audioCapture');
await dumpPermission('geolocation');
await dumpPermission('microphone');
testRunner.log('Resetting all permissions');
await dp.Browser.resetPermissions();
await dumpPermission('geolocation');
await dumpPermission('microphone');
testRunner.log('Testing local permissions override global permissions');
await grant(undefined, 'geolocation');
await grant('http://devtools.test:8000', 'audioCapture');
await dumpPermission('geolocation');
await dumpPermission('microphone');
testRunner.completeTest();
async function grant(origin, ...permissions) {
testRunner.log(`Grant ${permissions.join(' ')} to ${origin || 'all'}`);
const response = await dp.Browser.grantPermissions({ origin, permissions });
if (response.error)
testRunner.log(' Failed to grant: ' + JSON.stringify(permissions) + ' error: ' + response.error.message);
else
testRunner.log(' Granted: ' + JSON.stringify(permissions));
}
async function dumpPermission(name) {
testRunner.log(`Query ${name}`);
const result = await session.evaluateAsync(async (permission) => {
const result = await navigator.permissions.query({name: permission});
return result.state;
}, name);
testRunner.log(` => ${result}`);
}
})
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment