1 // Copyright 2014 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "components/permissions/permission_context_base.h"
12 #include "base/functional/bind.h"
13 #include "base/functional/callback.h"
14 #include "base/logging.h"
15 #include "base/metrics/field_trial_params.h"
16 #include "base/observer_list.h"
17 #include "base/strings/stringprintf.h"
18 #include "base/time/time.h"
19 #include "build/build_config.h"
20 #include "components/content_settings/core/browser/content_settings_registry.h"
21 #include "components/content_settings/core/browser/content_settings_utils.h"
22 #include "components/content_settings/core/browser/host_content_settings_map.h"
23 #include "components/content_settings/core/common/content_settings_types.h"
24 #include "components/permissions/features.h"
25 #include "components/permissions/permission_decision_auto_blocker.h"
26 #include "components/permissions/permission_request.h"
27 #include "components/permissions/permission_request_id.h"
28 #include "components/permissions/permission_request_manager.h"
29 #include "components/permissions/permission_uma_util.h"
30 #include "components/permissions/permission_util.h"
31 #include "components/permissions/permissions_client.h"
32 #include "components/permissions/request_type.h"
33 #include "components/permissions/unused_site_permissions_service.h"
34 #include "content/public/browser/browser_thread.h"
35 #include "content/public/browser/global_routing_id.h"
36 #include "content/public/browser/navigation_entry.h"
37 #include "content/public/browser/render_frame_host.h"
38 #include "content/public/browser/web_contents.h"
39 #include "content/public/common/content_features.h"
40 #include "services/network/public/cpp/is_potentially_trustworthy.h"
41 #include "third_party/blink/public/mojom/permissions_policy/permissions_policy_feature.mojom.h"
44 namespace permissions {
47 const char kPermissionBlockedKillSwitchMessage[] =
48 "%s permission has been blocked.";
50 #if BUILDFLAG(IS_ANDROID)
51 const char kPermissionBlockedRepeatedDismissalsMessage[] =
52 "%s permission has been blocked as the user has dismissed the permission "
53 "prompt several times. This can be reset in Site Settings. See "
54 "https://www.chromestatus.com/feature/6443143280984064 for more "
57 const char kPermissionBlockedRepeatedIgnoresMessage[] =
58 "%s permission has been blocked as the user has ignored the permission "
59 "prompt several times. This can be reset in Site Settings. See "
60 "https://www.chromestatus.com/feature/6443143280984064 for more "
63 const char kPermissionBlockedRepeatedDismissalsMessage[] =
64 "%s permission has been blocked as the user has dismissed the permission "
65 "prompt several times. This can be reset in Page Info which can be "
66 "accessed by clicking the lock icon next to the URL. See "
67 "https://www.chromestatus.com/feature/6443143280984064 for more "
70 const char kPermissionBlockedRepeatedIgnoresMessage[] =
71 "%s permission has been blocked as the user has ignored the permission "
72 "prompt several times. This can be reset in Page Info which can be "
73 "accessed by clicking the lock icon next to the URL. See "
74 "https://www.chromestatus.com/feature/6443143280984064 for more "
78 const char kPermissionBlockedRecentDisplayMessage[] =
79 "%s permission has been blocked as the prompt has already been displayed "
80 "to the user recently.";
82 const char kPermissionBlockedPermissionsPolicyMessage[] =
83 "%s permission has been blocked because of a permissions policy applied to"
84 " the current document. See https://goo.gl/EuHzyv for more details.";
86 void LogPermissionBlockedMessage(content::RenderFrameHost* rfh,
88 ContentSettingsType type) {
89 rfh->GetOutermostMainFrame()->AddMessageToConsole(
90 blink::mojom::ConsoleMessageLevel::kWarning,
91 base::StringPrintf(message,
92 PermissionUtil::GetPermissionString(type).c_str()));
98 const char PermissionContextBase::kPermissionsKillSwitchFieldStudy[] =
99 "PermissionsKillSwitch";
101 const char PermissionContextBase::kPermissionsKillSwitchBlockedValue[] =
104 PermissionContextBase::PermissionContextBase(
105 content::BrowserContext* browser_context,
106 ContentSettingsType content_settings_type,
107 blink::mojom::PermissionsPolicyFeature permissions_policy_feature)
108 : browser_context_(browser_context),
109 content_settings_type_(content_settings_type),
110 permissions_policy_feature_(permissions_policy_feature) {
111 PermissionDecisionAutoBlocker::UpdateFromVariations();
114 PermissionContextBase::~PermissionContextBase() {
115 DCHECK(permission_observers_.empty());
116 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
119 void PermissionContextBase::RequestPermission(
120 const PermissionRequestID& id,
121 const GURL& requesting_frame,
123 BrowserPermissionCallback callback) {
124 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
126 content::RenderFrameHost* const rfh =
127 content::RenderFrameHost::FromID(id.global_render_frame_host_id());
130 // Permission request is not allowed without a valid RenderFrameHost.
131 std::move(callback).Run(CONTENT_SETTING_ASK);
135 const GURL requesting_origin = requesting_frame.DeprecatedGetOriginAsURL();
136 const GURL embedding_origin =
137 PermissionUtil::GetLastCommittedOriginAsURL(rfh->GetMainFrame());
139 if (!requesting_origin.is_valid() || !embedding_origin.is_valid()) {
140 std::string type_name =
141 PermissionUtil::GetPermissionString(content_settings_type_);
143 DVLOG(1) << "Attempt to use " << type_name
144 << " from an invalid URL: " << requesting_origin << ","
145 << embedding_origin << " (" << type_name
146 << " is not supported in popups)";
147 NotifyPermissionSet(id, requesting_origin, embedding_origin,
148 std::move(callback), /*persist=*/false,
149 CONTENT_SETTING_BLOCK, /*is_one_time=*/false,
150 /*is_final_decision=*/true);
154 // Check the content setting to see if the user has already made a decision,
155 // or if the origin is under embargo. If so, respect that decision.
157 PermissionResult result =
158 GetPermissionStatus(rfh, requesting_origin, embedding_origin);
160 if (result.content_setting == CONTENT_SETTING_ALLOW ||
161 result.content_setting == CONTENT_SETTING_BLOCK) {
162 switch (result.source) {
163 case PermissionStatusSource::KILL_SWITCH:
164 // Block the request and log to the developer console.
165 LogPermissionBlockedMessage(rfh, kPermissionBlockedKillSwitchMessage,
166 content_settings_type_);
167 PermissionUmaUtil::RecordPermissionRequestedFromFrame(
168 content_settings_type_, rfh);
169 std::move(callback).Run(CONTENT_SETTING_BLOCK);
171 case PermissionStatusSource::MULTIPLE_DISMISSALS:
172 LogPermissionBlockedMessage(rfh,
173 kPermissionBlockedRepeatedDismissalsMessage,
174 content_settings_type_);
175 PermissionUmaUtil::RecordPermissionRequestedFromFrame(
176 content_settings_type_, rfh);
178 case PermissionStatusSource::MULTIPLE_IGNORES:
179 LogPermissionBlockedMessage(rfh,
180 kPermissionBlockedRepeatedIgnoresMessage,
181 content_settings_type_);
182 PermissionUmaUtil::RecordPermissionRequestedFromFrame(
183 content_settings_type_, rfh);
185 case PermissionStatusSource::FEATURE_POLICY:
186 LogPermissionBlockedMessage(rfh,
187 kPermissionBlockedPermissionsPolicyMessage,
188 content_settings_type_);
190 case PermissionStatusSource::RECENT_DISPLAY:
191 LogPermissionBlockedMessage(rfh, kPermissionBlockedRecentDisplayMessage,
192 content_settings_type_);
194 case PermissionStatusSource::UNSPECIFIED:
195 PermissionUmaUtil::RecordPermissionRequestedFromFrame(
196 content_settings_type_, rfh);
198 case PermissionStatusSource::PORTAL:
199 case PermissionStatusSource::FENCED_FRAME:
200 case PermissionStatusSource::INSECURE_ORIGIN:
201 case PermissionStatusSource::VIRTUAL_URL_DIFFERENT_ORIGIN:
205 // If we are under embargo, record the embargo reason for which we have
206 // suppressed the prompt.
207 PermissionUmaUtil::RecordEmbargoPromptSuppressionFromSource(result.source);
208 NotifyPermissionSet(id, requesting_origin, embedding_origin,
209 std::move(callback), /*persist=*/false,
210 result.content_setting, /*is_one_time=*/false,
211 /*is_final_decision=*/true);
215 PermissionUmaUtil::RecordPermissionRequestedFromFrame(content_settings_type_,
218 // We are going to show a prompt now.
219 PermissionUmaUtil::PermissionRequested(content_settings_type_);
220 PermissionUmaUtil::RecordEmbargoPromptSuppression(
221 PermissionEmbargoStatus::NOT_EMBARGOED);
223 DecidePermission(id, requesting_origin, embedding_origin, user_gesture,
224 std::move(callback));
227 bool PermissionContextBase::IsRestrictedToSecureOrigins() const {
231 void PermissionContextBase::UserMadePermissionDecision(
232 const PermissionRequestID& id,
233 const GURL& requesting_origin,
234 const GURL& embedding_origin,
235 ContentSetting content_setting) {}
237 std::unique_ptr<PermissionRequest>
238 PermissionContextBase::CreatePermissionRequest(
239 const GURL& request_origin,
240 ContentSettingsType content_settings_type,
242 content::WebContents* web_contents,
243 PermissionRequest::PermissionDecidedCallback permission_decided_callback,
244 base::OnceClosure delete_callback) const {
245 return std::make_unique<PermissionRequest>(
246 request_origin, ContentSettingsTypeToRequestType(content_settings_type),
247 has_gesture, std::move(permission_decided_callback),
248 std::move(delete_callback));
251 PermissionResult PermissionContextBase::GetPermissionStatus(
252 content::RenderFrameHost* render_frame_host,
253 const GURL& requesting_origin,
254 const GURL& embedding_origin) const {
255 // If the permission has been disabled through Finch, block all requests.
256 if (IsPermissionKillSwitchOn()) {
257 return PermissionResult(CONTENT_SETTING_BLOCK,
258 PermissionStatusSource::KILL_SWITCH);
261 if (!IsPermissionAvailableToOrigins(requesting_origin, embedding_origin)) {
262 return PermissionResult(CONTENT_SETTING_BLOCK,
263 PermissionStatusSource::INSECURE_ORIGIN);
266 // Check whether the feature is enabled for the frame by permissions policy.
267 // We can only do this when a RenderFrameHost has been provided.
268 if (render_frame_host &&
269 !PermissionAllowedByPermissionsPolicy(render_frame_host)) {
270 return PermissionResult(CONTENT_SETTING_BLOCK,
271 PermissionStatusSource::FEATURE_POLICY);
274 if (render_frame_host) {
275 content::WebContents* web_contents =
276 content::WebContents::FromRenderFrameHost(render_frame_host);
278 // Automatically deny all HTTP or HTTPS requests where the virtual URL and
279 // the loaded URL are for different origins. The loaded URL is the one
280 // actually in the renderer, but the virtual URL is the one
281 // seen by the user. This may be very confusing for a user to see in a
282 // permissions request.
283 content::NavigationEntry* entry =
284 web_contents->GetController().GetLastCommittedEntry();
286 const GURL virtual_url = entry->GetVirtualURL();
287 const GURL loaded_url = entry->GetURL();
288 if (virtual_url.SchemeIsHTTPOrHTTPS() &&
289 loaded_url.SchemeIsHTTPOrHTTPS() &&
290 !url::IsSameOriginWith(virtual_url, loaded_url)) {
291 return PermissionResult(
292 CONTENT_SETTING_BLOCK,
293 PermissionStatusSource::VIRTUAL_URL_DIFFERENT_ORIGIN);
298 ContentSetting content_setting = GetPermissionStatusInternal(
299 render_frame_host, requesting_origin, embedding_origin);
301 if (content_setting != CONTENT_SETTING_ASK) {
302 return PermissionResult(content_setting,
303 PermissionStatusSource::UNSPECIFIED);
306 absl::optional<PermissionResult> result =
307 PermissionsClient::Get()
308 ->GetPermissionDecisionAutoBlocker(browser_context_)
309 ->GetEmbargoResult(requesting_origin, content_settings_type_);
311 DCHECK(result->content_setting == CONTENT_SETTING_BLOCK);
314 return PermissionResult(CONTENT_SETTING_ASK,
315 PermissionStatusSource::UNSPECIFIED);
318 bool PermissionContextBase::IsPermissionAvailableToOrigins(
319 const GURL& requesting_origin,
320 const GURL& embedding_origin) const {
321 if (IsRestrictedToSecureOrigins()) {
322 if (!network::IsUrlPotentiallyTrustworthy(requesting_origin))
325 // TODO(raymes): We should check the entire chain of embedders here whenever
326 // possible as this corresponds to the requirements of the secure contexts
327 // spec and matches what is implemented in blink. Right now we just check
328 // the top level and requesting origins.
329 if (!PermissionsClient::Get()->CanBypassEmbeddingOriginCheck(
330 requesting_origin, embedding_origin) &&
331 !network::IsUrlPotentiallyTrustworthy(embedding_origin)) {
338 PermissionResult PermissionContextBase::UpdatePermissionStatusWithDeviceStatus(
339 PermissionResult result,
340 const GURL& requesting_origin,
341 const GURL& embedding_origin) const {
345 void PermissionContextBase::ResetPermission(const GURL& requesting_origin,
346 const GURL& embedding_origin) {
347 if (!content_settings::ContentSettingsRegistry::GetInstance()->Get(
348 content_settings_type_)) {
351 PermissionsClient::Get()
352 ->GetSettingsMap(browser_context_)
353 ->SetContentSettingDefaultScope(requesting_origin, embedding_origin,
354 content_settings_type_,
355 CONTENT_SETTING_DEFAULT);
358 bool PermissionContextBase::IsPermissionKillSwitchOn() const {
359 const std::string param = base::GetFieldTrialParamValue(
360 kPermissionsKillSwitchFieldStudy,
361 PermissionUtil::GetPermissionString(content_settings_type_));
363 return param == kPermissionsKillSwitchBlockedValue;
366 ContentSetting PermissionContextBase::GetPermissionStatusInternal(
367 content::RenderFrameHost* render_frame_host,
368 const GURL& requesting_origin,
369 const GURL& embedding_origin) const {
370 return PermissionsClient::Get()
371 ->GetSettingsMap(browser_context_)
372 ->GetContentSetting(requesting_origin, embedding_origin,
373 content_settings_type_);
376 void PermissionContextBase::DecidePermission(
377 const PermissionRequestID& id,
378 const GURL& requesting_origin,
379 const GURL& embedding_origin,
381 BrowserPermissionCallback callback) {
382 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
384 // Under permission delegation, when we display a permission prompt, the
385 // origin displayed in the prompt should never differ from the top-level
386 // origin. Storage access API requests are excluded as they are expected to
387 // request permissions from the frame origin needing access.
388 DCHECK(PermissionsClient::Get()->CanBypassEmbeddingOriginCheck(
389 requesting_origin, embedding_origin) ||
390 requesting_origin == embedding_origin ||
391 content_settings_type_ == ContentSettingsType::STORAGE_ACCESS);
393 content::RenderFrameHost* rfh =
394 content::RenderFrameHost::FromID(id.global_render_frame_host_id());
397 content::WebContents* web_contents =
398 content::WebContents::FromRenderFrameHost(rfh);
399 PermissionRequestManager* permission_request_manager =
400 PermissionRequestManager::FromWebContents(web_contents);
401 // TODO(felt): sometimes |permission_request_manager| is null. This check is
402 // meant to prevent crashes. See crbug.com/457091.
403 if (!permission_request_manager)
406 std::unique_ptr<PermissionRequest> request_ptr = CreatePermissionRequest(
407 requesting_origin, content_settings_type_, user_gesture, web_contents,
408 base::BindRepeating(&PermissionContextBase::PermissionDecided,
409 weak_factory_.GetWeakPtr(), id, requesting_origin,
411 base::BindOnce(&PermissionContextBase::CleanUpRequest,
412 weak_factory_.GetWeakPtr(), id));
413 PermissionRequest* request = request_ptr.get();
415 bool inserted = pending_requests_
416 .insert(std::make_pair(
417 id.ToString(), std::make_pair(std::move(request_ptr),
418 std::move(callback))))
420 DCHECK(inserted) << "Duplicate id " << id.ToString();
422 permission_request_manager->AddRequest(rfh, request);
425 void PermissionContextBase::PermissionDecided(const PermissionRequestID& id,
426 const GURL& requesting_origin,
427 const GURL& embedding_origin,
428 ContentSetting content_setting,
430 bool is_final_decision) {
431 DCHECK(content_setting == CONTENT_SETTING_ALLOW ||
432 content_setting == CONTENT_SETTING_BLOCK ||
433 content_setting == CONTENT_SETTING_DEFAULT);
434 UserMadePermissionDecision(id, requesting_origin, embedding_origin,
437 bool persist = content_setting != CONTENT_SETTING_DEFAULT;
439 auto request = pending_requests_.find(id.ToString());
440 DCHECK(request != pending_requests_.end());
441 // Check if `request` has `BrowserPermissionCallback`. The call back might be
442 // missing if a permission prompt was preignored and we already notified an
444 if (request->second.second) {
445 NotifyPermissionSet(id, requesting_origin, embedding_origin,
446 std::move(request->second.second), persist,
447 content_setting, is_one_time, is_final_decision);
449 NotifyPermissionSet(id, requesting_origin, embedding_origin,
450 base::DoNothing(), persist, content_setting,
451 is_one_time, is_final_decision);
455 content::BrowserContext* PermissionContextBase::browser_context() const {
456 return browser_context_;
459 void PermissionContextBase::OnContentSettingChanged(
460 const ContentSettingsPattern& primary_pattern,
461 const ContentSettingsPattern& secondary_pattern,
462 ContentSettingsTypeSet content_type_set) {
463 if (!content_type_set.Contains(content_settings_type_))
466 for (permissions::Observer& obs : permission_observers_) {
467 obs.OnPermissionChanged(primary_pattern, secondary_pattern,
472 void PermissionContextBase::AddObserver(
473 permissions::Observer* permission_observer) {
474 if (permission_observers_.empty() &&
475 !content_setting_observer_registered_by_subclass_) {
476 PermissionsClient::Get()
477 ->GetSettingsMap(browser_context_)
480 permission_observers_.AddObserver(permission_observer);
483 void PermissionContextBase::RemoveObserver(
484 permissions::Observer* permission_observer) {
485 permission_observers_.RemoveObserver(permission_observer);
486 if (permission_observers_.empty() &&
487 !content_setting_observer_registered_by_subclass_) {
488 PermissionsClient::Get()
489 ->GetSettingsMap(browser_context_)
490 ->RemoveObserver(this);
494 void PermissionContextBase::NotifyPermissionSet(
495 const PermissionRequestID& id,
496 const GURL& requesting_origin,
497 const GURL& embedding_origin,
498 BrowserPermissionCallback callback,
500 ContentSetting content_setting,
502 bool is_final_decision) {
503 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
506 UpdateContentSetting(requesting_origin, embedding_origin, content_setting,
510 if (is_final_decision) {
511 UpdateTabContext(id, requesting_origin,
512 content_setting == CONTENT_SETTING_ALLOW);
513 if (content_setting == CONTENT_SETTING_ALLOW) {
514 if (auto* rfh = content::RenderFrameHost::FromID(
515 id.global_render_frame_host_id())) {
516 PermissionUmaUtil::RecordPermissionsUsageSourceAndPolicyConfiguration(
517 content_settings_type_, rfh);
522 if (content_setting == CONTENT_SETTING_DEFAULT)
523 content_setting = CONTENT_SETTING_ASK;
525 std::move(callback).Run(content_setting);
528 void PermissionContextBase::CleanUpRequest(const PermissionRequestID& id) {
529 size_t success = pending_requests_.erase(id.ToString());
530 DCHECK(success == 1) << "Missing request " << id.ToString();
533 void PermissionContextBase::UpdateContentSetting(const GURL& requesting_origin,
534 const GURL& embedding_origin,
535 ContentSetting content_setting,
537 DCHECK_EQ(requesting_origin, requesting_origin.DeprecatedGetOriginAsURL());
538 DCHECK_EQ(embedding_origin, embedding_origin.DeprecatedGetOriginAsURL());
539 DCHECK(content_setting == CONTENT_SETTING_ALLOW ||
540 content_setting == CONTENT_SETTING_BLOCK);
542 content_settings::ContentSettingConstraints constraints = {
543 base::Time(), is_one_time ? content_settings::SessionModel::OneTime
544 : content_settings::SessionModel::Durable};
546 #if !BUILDFLAG(IS_ANDROID)
547 if (base::FeatureList::IsEnabled(
548 features::kRecordPermissionExpirationTimestamps)) {
549 // The Permissions module in Safety check will revoke permissions after
550 // a finite amount of time if the permission can be revoked.
551 if (content_settings::CanBeAutoRevoked(content_settings_type_,
552 content_setting, is_one_time)) {
553 // For #2, by definition, that should be all of them. If that changes in
554 // the future, consider whether revocation for such permission makes
555 // sense, and/or change this to an early return so that we don't
556 // unnecessarily record timestamps where we don't need them.
557 constraints.track_last_visit_for_autoexpiration = true;
560 #endif // !BUILDFLAG(IS_ANDROID)
562 PermissionsClient::Get()
563 ->GetSettingsMap(browser_context_)
564 ->SetContentSettingDefaultScope(requesting_origin, embedding_origin,
565 content_settings_type_, content_setting,
569 bool PermissionContextBase::PermissionAllowedByPermissionsPolicy(
570 content::RenderFrameHost* rfh) const {
571 // Some features don't have an associated permissions policy yet. Allow those.
572 if (permissions_policy_feature_ ==
573 blink::mojom::PermissionsPolicyFeature::kNotFound)
576 return rfh->IsFeatureEnabled(permissions_policy_feature_);
579 } // namespace permissions