1 // Copyright 2013 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.
5 #include "chrome/common/extensions/permissions/chrome_permission_message_provider.h"
9 #include "base/memory/scoped_vector.h"
10 #include "base/metrics/field_trial.h"
11 #include "base/stl_util.h"
12 #include "base/strings/string_util.h"
13 #include "base/strings/stringprintf.h"
14 #include "chrome/common/extensions/permissions/chrome_permission_message_rules.h"
15 #include "chrome/grit/generated_resources.h"
16 #include "extensions/common/extensions_client.h"
17 #include "extensions/common/permissions/permission_message_util.h"
18 #include "extensions/common/permissions/permission_set.h"
19 #include "extensions/common/url_pattern.h"
20 #include "extensions/common/url_pattern_set.h"
21 #include "grit/extensions_strings.h"
22 #include "ui/base/l10n/l10n_util.h"
25 namespace extensions
{
29 // Copyable wrapper to make CoalescedPermissionMessages comparable.
30 class ComparablePermission
{
32 explicit ComparablePermission(const CoalescedPermissionMessage
& msg
)
35 bool operator<(const ComparablePermission
& rhs
) const {
36 if (msg_
->message() < rhs
.msg_
->message())
38 if (msg_
->message() > rhs
.msg_
->message())
40 return msg_
->submessages() < rhs
.msg_
->submessages();
44 const CoalescedPermissionMessage
* msg_
;
46 using ComparablePermissions
= std::vector
<ComparablePermission
>;
50 typedef std::set
<PermissionMessage
> PermissionMsgSet
;
52 ChromePermissionMessageProvider::ChromePermissionMessageProvider() {
55 ChromePermissionMessageProvider::~ChromePermissionMessageProvider() {
58 CoalescedPermissionMessages
59 ChromePermissionMessageProvider::GetPermissionMessages(
60 const PermissionIDSet
& permissions
) const {
61 std::vector
<ChromePermissionMessageRule
> rules
=
62 ChromePermissionMessageRule::GetAllRules();
64 // Apply each of the rules, in order, to generate the messages for the given
65 // permissions. Once a permission is used in a rule, remove it from the set
66 // of available permissions so it cannot be applied to subsequent rules.
67 PermissionIDSet remaining_permissions
= permissions
;
68 CoalescedPermissionMessages messages
;
69 for (const auto& rule
: rules
) {
70 // Only apply the rule if we have all the required permission IDs.
71 if (remaining_permissions
.ContainsAllIDs(rule
.required_permissions())) {
72 // We can apply the rule. Add all the required permissions, and as many
73 // optional permissions as we can, to the new message.
74 PermissionIDSet used_permissions
=
75 remaining_permissions
.GetAllPermissionsWithIDs(
76 rule
.all_permissions());
77 messages
.push_back(rule
.GetPermissionMessage(used_permissions
));
79 remaining_permissions
=
80 PermissionIDSet::Difference(remaining_permissions
, used_permissions
);
87 bool ChromePermissionMessageProvider::IsPrivilegeIncrease(
88 const PermissionSet
* old_permissions
,
89 const PermissionSet
* new_permissions
,
90 Manifest::Type extension_type
) const {
91 // Things can't get worse than native code access.
92 if (old_permissions
->HasEffectiveFullAccess())
95 // Otherwise, it's a privilege increase if the new one has full access.
96 if (new_permissions
->HasEffectiveFullAccess())
99 if (IsHostPrivilegeIncrease(old_permissions
, new_permissions
, extension_type
))
102 if (IsAPIPrivilegeIncrease(old_permissions
, new_permissions
))
105 if (IsManifestPermissionPrivilegeIncrease(old_permissions
, new_permissions
))
111 PermissionIDSet
ChromePermissionMessageProvider::GetAllPermissionIDs(
112 const PermissionSet
* permissions
,
113 Manifest::Type extension_type
) const {
114 PermissionIDSet permission_ids
;
115 AddAPIPermissions(permissions
, &permission_ids
);
116 AddManifestPermissions(permissions
, &permission_ids
);
117 AddHostPermissions(permissions
, &permission_ids
, extension_type
);
118 return permission_ids
;
121 void ChromePermissionMessageProvider::AddAPIPermissions(
122 const PermissionSet
* permissions
,
123 PermissionIDSet
* permission_ids
) const {
124 for (const APIPermission
* permission
: permissions
->apis())
125 permission_ids
->InsertAll(permission
->GetPermissions());
127 // A special hack: The warning message for declarativeWebRequest
128 // permissions speaks about blocking parts of pages, which is a
129 // subset of what the "<all_urls>" access allows. Therefore we
130 // display only the "<all_urls>" warning message if both permissions
132 // TODO(treib): The same should apply to other permissions that are implied by
133 // "<all_urls>" (aka APIPermission::kHostsAll), such as kTab. This would
134 // happen automatically if we didn't differentiate between API/Manifest/Host
136 if (permissions
->ShouldWarnAllHosts())
137 permission_ids
->erase(APIPermission::kDeclarativeWebRequest
);
140 void ChromePermissionMessageProvider::AddManifestPermissions(
141 const PermissionSet
* permissions
,
142 PermissionIDSet
* permission_ids
) const {
143 for (const ManifestPermission
* p
: permissions
->manifest_permissions())
144 permission_ids
->InsertAll(p
->GetPermissions());
147 void ChromePermissionMessageProvider::AddHostPermissions(
148 const PermissionSet
* permissions
,
149 PermissionIDSet
* permission_ids
,
150 Manifest::Type extension_type
) const {
151 // Since platform apps always use isolated storage, they can't (silently)
152 // access user data on other domains, so there's no need to prompt.
153 // Note: this must remain consistent with IsHostPrivilegeIncrease.
154 // See crbug.com/255229.
155 if (extension_type
== Manifest::TYPE_PLATFORM_APP
)
158 if (permissions
->ShouldWarnAllHosts()) {
159 permission_ids
->insert(APIPermission::kHostsAll
);
161 URLPatternSet regular_hosts
;
162 ExtensionsClient::Get()->FilterHostPermissions(
163 permissions
->effective_hosts(), ®ular_hosts
, permission_ids
);
165 std::set
<std::string
> hosts
=
166 permission_message_util::GetDistinctHosts(regular_hosts
, true, true);
167 if (!hosts
.empty()) {
168 permission_message_util::AddHostPermissions(
169 permission_ids
, hosts
, permission_message_util::kReadWrite
);
174 bool ChromePermissionMessageProvider::IsAPIPrivilegeIncrease(
175 const PermissionSet
* old_permissions
,
176 const PermissionSet
* new_permissions
) const {
177 PermissionIDSet old_ids
;
178 AddAPIPermissions(old_permissions
, &old_ids
);
179 PermissionIDSet new_ids
;
180 AddAPIPermissions(new_permissions
, &new_ids
);
182 // A special hack: kFileSystemWriteDirectory implies kFileSystemDirectory.
183 // TODO(sammc): Remove this. See http://crbug.com/284849.
184 if (old_ids
.ContainsID(APIPermission::kFileSystemWriteDirectory
))
185 old_ids
.insert(APIPermission::kFileSystemDirectory
);
187 return IsAPIOrManifestPrivilegeIncrease(old_ids
, new_ids
);
190 bool ChromePermissionMessageProvider::IsManifestPermissionPrivilegeIncrease(
191 const PermissionSet
* old_permissions
,
192 const PermissionSet
* new_permissions
) const {
193 PermissionIDSet old_ids
;
194 AddManifestPermissions(old_permissions
, &old_ids
);
195 PermissionIDSet new_ids
;
196 AddManifestPermissions(new_permissions
, &new_ids
);
198 return IsAPIOrManifestPrivilegeIncrease(old_ids
, new_ids
);
201 bool ChromePermissionMessageProvider::IsAPIOrManifestPrivilegeIncrease(
202 const PermissionIDSet
& old_ids
,
203 const PermissionIDSet
& new_ids
) const {
204 // If all the IDs were already there, it's not a privilege increase.
205 if (old_ids
.Includes(new_ids
))
208 // Otherwise, check the actual messages - not all IDs result in a message,
209 // and some messages can suppress others.
210 CoalescedPermissionMessages old_messages
= GetPermissionMessages(old_ids
);
211 CoalescedPermissionMessages new_messages
= GetPermissionMessages(new_ids
);
213 ComparablePermissions
old_strings(old_messages
.begin(), old_messages
.end());
214 ComparablePermissions
new_strings(new_messages
.begin(), new_messages
.end());
216 std::sort(old_strings
.begin(), old_strings
.end());
217 std::sort(new_strings
.begin(), new_strings
.end());
219 return !base::STLIncludes(old_strings
, new_strings
);
222 bool ChromePermissionMessageProvider::IsHostPrivilegeIncrease(
223 const PermissionSet
* old_permissions
,
224 const PermissionSet
* new_permissions
,
225 Manifest::Type extension_type
) const {
226 // Platform apps host permission changes do not count as privilege increases.
227 // Note: this must remain consistent with AddHostPermissions.
228 if (extension_type
== Manifest::TYPE_PLATFORM_APP
)
231 // If the old permission set can access any host, then it can't be elevated.
232 if (old_permissions
->HasEffectiveAccessToAllHosts())
235 // Likewise, if the new permission set has full host access, then it must be
236 // a privilege increase.
237 if (new_permissions
->HasEffectiveAccessToAllHosts())
240 const URLPatternSet
& old_list
= old_permissions
->effective_hosts();
241 const URLPatternSet
& new_list
= new_permissions
->effective_hosts();
243 // TODO(jstritar): This is overly conservative with respect to subdomains.
244 // For example, going from *.google.com to www.google.com will be
245 // considered an elevation, even though it is not (http://crbug.com/65337).
246 std::set
<std::string
> new_hosts_set(
247 permission_message_util::GetDistinctHosts(new_list
, false, false));
248 std::set
<std::string
> old_hosts_set(
249 permission_message_util::GetDistinctHosts(old_list
, false, false));
250 std::set
<std::string
> new_hosts_only
=
251 base::STLSetDifference
<std::set
<std::string
> >(new_hosts_set
,
254 return !new_hosts_only
.empty();
257 } // namespace extensions