1 // Copyright 2020 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "chrome/browser/permissions/abusive_origin_permission_revocation_request.h"
6 
7 #include "base/time/default_clock.h"
8 #include "chrome/browser/browser_process.h"
9 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
10 #include "chrome/browser/permissions/abusive_origin_notifications_permission_revocation_config.h"
11 #include "chrome/browser/permissions/crowd_deny_preload_data.h"
12 #include "chrome/browser/permissions/permission_manager_factory.h"
13 #include "chrome/browser/safe_browsing/safe_browsing_service.h"
14 #include "components/content_settings/core/browser/host_content_settings_map.h"
15 #include "components/permissions/permission_manager.h"
16 #include "components/permissions/permission_result.h"
17 #include "components/permissions/permission_uma_util.h"
18 #include "components/permissions/permissions_client.h"
19 #include "components/prefs/pref_service.h"
20 #include "components/safe_browsing/core/common/safe_browsing_prefs.h"
21 #include "components/safe_browsing/core/db/database_manager.h"
22 
23 namespace {
24 constexpr char kExcludedKey[] = "exempted";
25 constexpr char kRevokedKey[] = "revoked";
26 constexpr char kPermissionName[] = "notifications";
27 
28 struct OriginStatus {
29   bool is_exempt_from_future_revocations = false;
30   bool has_been_previously_revoked = false;
31 };
32 
GetOriginStatus(Profile * profile,const GURL & origin)33 OriginStatus GetOriginStatus(Profile* profile, const GURL& origin) {
34   std::unique_ptr<base::Value> stored_value =
35       permissions::PermissionsClient::Get()
36           ->GetSettingsMap(profile)
37           ->GetWebsiteSetting(
38               origin, GURL(),
39               ContentSettingsType::PERMISSION_AUTOREVOCATION_DATA, nullptr);
40 
41   OriginStatus status;
42 
43   if (!stored_value || !stored_value->is_dict())
44     return status;
45 
46   base::Value* dict = stored_value->FindPath(kPermissionName);
47   if (!dict)
48     return status;
49 
50   if (dict->FindBoolPath(kExcludedKey).has_value()) {
51     status.is_exempt_from_future_revocations =
52         dict->FindBoolPath(kExcludedKey).value();
53   }
54   if (dict->FindBoolPath(kRevokedKey).has_value()) {
55     status.has_been_previously_revoked =
56         dict->FindBoolPath(kRevokedKey).value();
57   }
58 
59   return status;
60 }
61 
SetOriginStatus(Profile * profile,const GURL & origin,const OriginStatus & status)62 void SetOriginStatus(Profile* profile,
63                      const GURL& origin,
64                      const OriginStatus& status) {
65   base::Value dict(base::Value::Type::DICTIONARY);
66   base::Value permission_dict(base::Value::Type::DICTIONARY);
67   permission_dict.SetKey(kExcludedKey,
68                          base::Value(status.is_exempt_from_future_revocations));
69   permission_dict.SetKey(kRevokedKey,
70                          base::Value(status.has_been_previously_revoked));
71   dict.SetKey(kPermissionName, std::move(permission_dict));
72 
73   permissions::PermissionsClient::Get()
74       ->GetSettingsMap(profile)
75       ->SetWebsiteSettingDefaultScope(
76           origin, GURL(), ContentSettingsType::PERMISSION_AUTOREVOCATION_DATA,
77           base::WrapUnique(dict.DeepCopy()));
78 }
79 
RevokePermission(const GURL & origin,Profile * profile)80 void RevokePermission(const GURL& origin, Profile* profile) {
81   permissions::PermissionsClient::Get()
82       ->GetSettingsMap(profile)
83       ->SetContentSettingDefaultScope(origin, GURL(),
84                                       ContentSettingsType::NOTIFICATIONS,
85                                       ContentSetting::CONTENT_SETTING_DEFAULT);
86 
87   OriginStatus status = GetOriginStatus(profile, origin);
88   status.has_been_previously_revoked = true;
89   SetOriginStatus(profile, origin, status);
90 
91   permissions::PermissionUmaUtil::PermissionRevoked(
92       ContentSettingsType::NOTIFICATIONS,
93       permissions::PermissionSourceUI::AUTO_REVOCATION, origin, profile);
94 }
95 }  // namespace
96 
97 AbusiveOriginPermissionRevocationRequest::
AbusiveOriginPermissionRevocationRequest(Profile * profile,const GURL & origin,OutcomeCallback callback)98     AbusiveOriginPermissionRevocationRequest(Profile* profile,
99                                              const GURL& origin,
100                                              OutcomeCallback callback)
101     : profile_(profile), origin_(origin), callback_(std::move(callback)) {
102   base::SequencedTaskRunnerHandle::Get()->PostTask(
103       FROM_HERE,
104       base::BindOnce(
105           &AbusiveOriginPermissionRevocationRequest::CheckAndRevokeIfAbusive,
106           weak_factory_.GetWeakPtr()));
107 }
108 
109 AbusiveOriginPermissionRevocationRequest::
110     ~AbusiveOriginPermissionRevocationRequest() = default;
111 
CheckAndRevokeIfAbusive()112 void AbusiveOriginPermissionRevocationRequest::CheckAndRevokeIfAbusive() {
113   DCHECK(profile_);
114   DCHECK(callback_);
115 
116   if (!AbusiveOriginNotificationsPermissionRevocationConfig::IsEnabled() ||
117       !safe_browsing::IsSafeBrowsingEnabled(*profile_->GetPrefs()) ||
118       IsOriginExemptedFromFutureRevocations(profile_, origin_)) {
119     std::move(callback_).Run(Outcome::PERMISSION_NOT_REVOKED);
120     return;
121   }
122 
123   CrowdDenyPreloadData* crowd_deny = CrowdDenyPreloadData::GetInstance();
124   permissions::PermissionUmaUtil::RecordCrowdDenyIsLoadedAtAbuseCheckTime(
125       crowd_deny->is_loaded_from_disk());
126   permissions::PermissionUmaUtil::RecordCrowdDenyVersionAtAbuseCheckTime(
127       crowd_deny->version_on_disk());
128 
129   const CrowdDenyPreloadData::SiteReputation* site_reputation =
130       crowd_deny->GetReputationDataForSite(url::Origin::Create(origin_));
131   if (site_reputation && !site_reputation->warning_only() &&
132       (site_reputation->notification_ux_quality() ==
133            CrowdDenyPreloadData::SiteReputation::ABUSIVE_PROMPTS ||
134        site_reputation->notification_ux_quality() ==
135            CrowdDenyPreloadData::SiteReputation::ABUSIVE_CONTENT)) {
136     DCHECK(g_browser_process->safe_browsing_service());
137 
138     if (g_browser_process->safe_browsing_service()) {
139       safe_browsing_request_.emplace(
140           g_browser_process->safe_browsing_service()->database_manager(),
141           base::DefaultClock::GetInstance(), url::Origin::Create(origin_),
142           base::BindOnce(&AbusiveOriginPermissionRevocationRequest::
143                              OnSafeBrowsingVerdictReceived,
144                          weak_factory_.GetWeakPtr()));
145       return;
146     }
147   }
148 
149   std::move(callback_).Run(Outcome::PERMISSION_NOT_REVOKED);
150 }
151 
OnSafeBrowsingVerdictReceived(CrowdDenySafeBrowsingRequest::Verdict verdict)152 void AbusiveOriginPermissionRevocationRequest::OnSafeBrowsingVerdictReceived(
153     CrowdDenySafeBrowsingRequest::Verdict verdict) {
154   DCHECK(safe_browsing_request_);
155   DCHECK(profile_);
156   DCHECK(callback_);
157 
158   if (verdict == CrowdDenySafeBrowsingRequest::Verdict::kUnacceptable) {
159     RevokePermission(origin_, profile_);
160     std::move(callback_).Run(Outcome::PERMISSION_REVOKED_DUE_TO_ABUSE);
161   } else {
162     std::move(callback_).Run(Outcome::PERMISSION_NOT_REVOKED);
163   }
164 }
165 
166 // static
167 bool AbusiveOriginPermissionRevocationRequest::
IsOriginExemptedFromFutureRevocations(Profile * profile,const GURL & origin)168     IsOriginExemptedFromFutureRevocations(Profile* profile,
169                                           const GURL& origin) {
170   OriginStatus status = GetOriginStatus(profile, origin);
171   return status.is_exempt_from_future_revocations;
172 }
173 
174 // static
HasPreviouslyRevokedPermission(Profile * profile,const GURL & origin)175 bool AbusiveOriginPermissionRevocationRequest::HasPreviouslyRevokedPermission(
176     Profile* profile,
177     const GURL& origin) {
178   OriginStatus status = GetOriginStatus(profile, origin);
179   return status.has_been_previously_revoked;
180 }
181 
182 // static
183 void AbusiveOriginPermissionRevocationRequest::
ExemptOriginFromFutureRevocations(Profile * profile,const GURL & origin)184     ExemptOriginFromFutureRevocations(Profile* profile, const GURL& origin) {
185   OriginStatus status = GetOriginStatus(profile, origin);
186   status.is_exempt_from_future_revocations = true;
187   SetOriginStatus(profile, origin, status);
188 }
189