summaryrefslogtreecommitdiffstats
path: root/chromium/chrome/common/extensions/permissions/chrome_permission_message_provider.cc
blob: 9dde408e86df8cfa960d9d1a278f9182d45b3adf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/common/extensions/permissions/chrome_permission_message_provider.h"

#include <tuple>
#include <vector>

#include "base/metrics/field_trial.h"
#include "base/stl_util.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "extensions/common/extensions_client.h"
#include "extensions/common/permissions/permission_message_util.h"
#include "extensions/common/permissions/permission_set.h"
#include "extensions/common/url_pattern.h"
#include "extensions/common/url_pattern_set.h"
#include "url/gurl.h"

namespace extensions {

namespace {

// Copyable wrapper to make PermissionMessages comparable.
class ComparablePermission {
 public:
  explicit ComparablePermission(const PermissionMessage& msg) : msg_(&msg) {}

  bool operator<(const ComparablePermission& rhs) const {
    return std::tie(msg_->message(), msg_->submessages()) <
           std::tie(rhs.msg_->message(), rhs.msg_->submessages());
  }

  bool operator==(const ComparablePermission& rhs) const {
    return msg_->message() == rhs.msg_->message() &&
           msg_->submessages() == rhs.msg_->submessages();
  }

 private:
  const PermissionMessage* msg_;
};
using ComparablePermissions = std::vector<ComparablePermission>;

}  // namespace

typedef std::set<PermissionMessage> PermissionMsgSet;

ChromePermissionMessageProvider::ChromePermissionMessageProvider() {
}

ChromePermissionMessageProvider::~ChromePermissionMessageProvider() {
}

PermissionMessages ChromePermissionMessageProvider::GetPermissionMessages(
    const PermissionIDSet& permissions) const {
  const std::vector<ChromePermissionMessageRule> rules =
      ChromePermissionMessageRule::GetAllRules();

  return GetPermissionMessagesHelper(permissions, rules);
}

bool ChromePermissionMessageProvider::IsPrivilegeIncrease(
    const PermissionSet& granted_permissions,
    const PermissionSet& requested_permissions,
    Manifest::Type extension_type) const {
  if (IsHostPrivilegeIncrease(granted_permissions, requested_permissions,
                              extension_type))
    return true;

  if (IsAPIOrManifestPrivilegeIncrease(granted_permissions,
                                       requested_permissions))
    return true;

  return false;
}

PermissionIDSet ChromePermissionMessageProvider::GetAllPermissionIDs(
    const PermissionSet& permissions,
    Manifest::Type extension_type) const {
  PermissionIDSet permission_ids;
  AddAPIPermissions(permissions, &permission_ids);
  AddManifestPermissions(permissions, &permission_ids);
  AddHostPermissions(permissions, &permission_ids, extension_type);
  return permission_ids;
}

PermissionMessages
ChromePermissionMessageProvider::GetPowerfulPermissionMessages(
    const PermissionIDSet& permissions) const {
  std::vector<ChromePermissionMessageRule> rules =
      ChromePermissionMessageRule::GetAllRules();

  // TODO(crbug.com/888981): Find a better way to get wanted rules. Maybe add a
  // bool to each one telling if we should consider it here or not.
  constexpr size_t rules_considered = 15;
  rules.erase(rules.begin() + std::min(rules_considered, rules.size()),
              rules.end());

  return GetPermissionMessagesHelper(permissions, rules);
}

void ChromePermissionMessageProvider::AddAPIPermissions(
    const PermissionSet& permissions,
    PermissionIDSet* permission_ids) const {
  for (const APIPermission* permission : permissions.apis())
    permission_ids->InsertAll(permission->GetPermissions());

  // A special hack: The warning message for declarativeWebRequest
  // permissions speaks about blocking parts of pages, which is a
  // subset of what the "<all_urls>" access allows. Therefore we
  // display only the "<all_urls>" warning message if both permissions
  // are required.
  // TODO(treib): The same should apply to other permissions that are implied by
  // "<all_urls>" (aka APIPermission::kHostsAll), such as kTab. This would
  // happen automatically if we didn't differentiate between API/Manifest/Host
  // permissions here.
  if (permissions.ShouldWarnAllHosts())
    permission_ids->erase(APIPermission::kDeclarativeWebRequest);
}

void ChromePermissionMessageProvider::AddManifestPermissions(
    const PermissionSet& permissions,
    PermissionIDSet* permission_ids) const {
  for (const ManifestPermission* p : permissions.manifest_permissions())
    permission_ids->InsertAll(p->GetPermissions());
}

void ChromePermissionMessageProvider::AddHostPermissions(
    const PermissionSet& permissions,
    PermissionIDSet* permission_ids,
    Manifest::Type extension_type) const {
  // Since platform apps always use isolated storage, they can't (silently)
  // access user data on other domains, so there's no need to prompt.
  // Note: this must remain consistent with IsHostPrivilegeIncrease.
  // See crbug.com/255229.
  if (extension_type == Manifest::TYPE_PLATFORM_APP)
    return;

  if (permissions.ShouldWarnAllHosts()) {
    permission_ids->insert(APIPermission::kHostsAll);
  } else {
    URLPatternSet regular_hosts;
    ExtensionsClient::Get()->FilterHostPermissions(
        permissions.effective_hosts(), &regular_hosts, permission_ids);

    std::set<std::string> hosts =
        permission_message_util::GetDistinctHosts(regular_hosts, true, true);
    for (const auto& host : hosts) {
      permission_ids->insert(APIPermission::kHostReadWrite,
                             base::UTF8ToUTF16(host));
    }
  }
}

bool ChromePermissionMessageProvider::IsAPIOrManifestPrivilegeIncrease(
    const PermissionSet& granted_permissions,
    const PermissionSet& requested_permissions) const {
  PermissionIDSet granted_ids;
  AddAPIPermissions(granted_permissions, &granted_ids);
  AddManifestPermissions(granted_permissions, &granted_ids);

  // We compare |granted_ids| against the set of permissions that would be
  // granted if the requested permissions are allowed.
  PermissionIDSet potential_total_ids = granted_ids;
  AddAPIPermissions(requested_permissions, &potential_total_ids);
  AddManifestPermissions(requested_permissions, &potential_total_ids);

  // For M62, we added a new permission ID for new tab page overrides. Consider
  // the addition of this permission to not result in a privilege increase for
  // the time being.
  // TODO(robertshield): Remove this once most of the population is on M62+
  granted_ids.erase(APIPermission::kNewTabPageOverride);
  potential_total_ids.erase(APIPermission::kNewTabPageOverride);

  // If all the IDs were already there, it's not a privilege increase.
  if (granted_ids.Includes(potential_total_ids))
    return false;

  // Otherwise, check the actual messages - not all IDs result in a message,
  // and some messages can suppress others.
  PermissionMessages granted_messages = GetPermissionMessages(granted_ids);
  PermissionMessages total_messages =
      GetPermissionMessages(potential_total_ids);

  ComparablePermissions granted_strings(granted_messages.begin(),
                                        granted_messages.end());
  ComparablePermissions total_strings(total_messages.begin(),
                                      total_messages.end());

  std::sort(granted_strings.begin(), granted_strings.end());
  std::sort(total_strings.begin(), total_strings.end());

  // TODO(devlin): I *think* we can just use strict-equals here, since we should
  // never have more strings in granted than in total (unless there was a
  // significant difference - e.g., going from two lower warnings to a single
  // scarier warning because of adding a new permission). But let's be overly
  // conservative for now.
  return !base::STLIncludes(granted_strings, total_strings);
}

bool ChromePermissionMessageProvider::IsHostPrivilegeIncrease(
    const PermissionSet& granted_permissions,
    const PermissionSet& requested_permissions,
    Manifest::Type extension_type) const {
  // Platform apps host permission changes do not count as privilege increases.
  // Note: this must remain consistent with AddHostPermissions.
  if (extension_type == Manifest::TYPE_PLATFORM_APP)
    return false;

  // If the granted permission set can access any host, then it can't be
  // elevated.
  if (granted_permissions.HasEffectiveAccessToAllHosts())
    return false;

  // Likewise, if the requested permission set has full host access, then it
  // must be a privilege increase.
  if (requested_permissions.HasEffectiveAccessToAllHosts())
    return true;

  const URLPatternSet& granted_list = granted_permissions.effective_hosts();
  const URLPatternSet& requested_list = requested_permissions.effective_hosts();

  std::set<std::string> requested_hosts_set(
      permission_message_util::GetDistinctHosts(requested_list, false, false));
  std::set<std::string> granted_hosts_set(
      permission_message_util::GetDistinctHosts(granted_list, false, false));
  std::set<std::string> requested_hosts_only =
      base::STLSetDifference<std::set<std::string>>(requested_hosts_set,
                                                    granted_hosts_set);

  // Try to match any domain permissions against existing domain permissions
  // that overlap, so that migrating from *.example.com -> foo.example.com
  // does not constitute a permissions increase, even though the strings are
  // not exactly the same.
  for (const auto& requested : requested_hosts_only) {
    bool host_matched = false;
    const base::StringPiece unmatched(requested);
    for (const auto& granted : granted_hosts_set) {
      if (granted.size() > 2 && granted[0] == '*' && granted[1] == '.') {
        const base::StringPiece stripped_granted(granted.data() + 1,
                                                 granted.length() - 1);
        // If the unmatched host ends with the the granted host,
        // after removing the '*', then it's a match. In addition,
        // because we consider having access to "*.domain.com" as
        // granting access to "domain.com" then compare the string
        // with both the "*" and the "." removed.
        if (unmatched.ends_with(stripped_granted) ||
            unmatched == stripped_granted.substr(1)) {
          host_matched = true;
          break;
        }
      }
    }
    if (!host_matched) {
      return true;
    }
  }
  return false;
}

PermissionMessages ChromePermissionMessageProvider::GetPermissionMessagesHelper(
    const PermissionIDSet& permissions,
    const std::vector<ChromePermissionMessageRule>& rules) const {
  // Apply each of the rules, in order, to generate the messages for the given
  // permissions. Once a permission is used in a rule, remove it from the set
  // of available permissions so it cannot be applied to subsequent rules.
  PermissionIDSet remaining_permissions = permissions;
  PermissionMessages messages;
  for (const auto& rule : rules) {
    // Only apply the rule if we have all the required permission IDs.
    if (remaining_permissions.ContainsAllIDs(rule.required_permissions())) {
      // We can apply the rule. Add all the required permissions, and as many
      // optional permissions as we can, to the new message.
      PermissionIDSet used_permissions =
          remaining_permissions.GetAllPermissionsWithIDs(
              rule.all_permissions());
      messages.push_back(rule.GetPermissionMessage(used_permissions));

      remaining_permissions =
          PermissionIDSet::Difference(remaining_permissions, used_permissions);
    }
  }

  return messages;
}

}  // namespace extensions