1 // Copyright (c) 2012 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/extensions/api/permissions/permissions_api_helpers.h"
6
7 #include <stddef.h>
8
9 #include "base/json/json_reader.h"
10 #include "base/json/json_writer.h"
11 #include "base/values.h"
12 #include "chrome/common/extensions/api/permissions.h"
13 #include "content/public/common/url_constants.h"
14 #include "extensions/common/error_utils.h"
15 #include "extensions/common/extension.h"
16 #include "extensions/common/permissions/permission_set.h"
17 #include "extensions/common/permissions/permissions_info.h"
18 #include "extensions/common/permissions/usb_device_permission.h"
19 #include "extensions/common/url_pattern_set.h"
20
21 namespace extensions {
22
23 using api::permissions::Permissions;
24
25 namespace permissions_api_helpers {
26
27 namespace {
28
29 const char kDelimiter[] = "|";
30 const char kInvalidParameter[] =
31 "Invalid argument for permission '*'.";
32 const char kInvalidOrigin[] =
33 "Invalid value for origin pattern *: *";
34 const char kUnknownPermissionError[] =
35 "'*' is not a recognized permission.";
36 const char kUnsupportedPermissionId[] =
37 "Only the usbDevices permission supports arguments.";
38
39 // Extracts an API permission that supports arguments. In practice, this is
40 // restricted to the UsbDevicePermission.
UnpackPermissionWithArguments(base::StringPiece permission_name,base::StringPiece permission_arg,const std::string & permission_str,std::string * error)41 std::unique_ptr<APIPermission> UnpackPermissionWithArguments(
42 base::StringPiece permission_name,
43 base::StringPiece permission_arg,
44 const std::string& permission_str,
45 std::string* error) {
46 std::unique_ptr<base::Value> permission_json =
47 base::JSONReader::ReadDeprecated(permission_arg);
48 if (!permission_json.get()) {
49 *error = ErrorUtils::FormatErrorMessage(kInvalidParameter, permission_str);
50 return nullptr;
51 }
52
53 std::unique_ptr<APIPermission> permission;
54
55 // Explicitly check the permissions that accept arguments until
56 // https://crbug.com/162042 is fixed.
57 const APIPermissionInfo* usb_device_permission_info =
58 PermissionsInfo::GetInstance()->GetByID(APIPermission::kUsbDevice);
59 if (permission_name == usb_device_permission_info->name()) {
60 permission =
61 std::make_unique<UsbDevicePermission>(usb_device_permission_info);
62 } else {
63 *error = kUnsupportedPermissionId;
64 return nullptr;
65 }
66
67 CHECK(permission);
68 if (!permission->FromValue(permission_json.get(), nullptr, nullptr)) {
69 *error = ErrorUtils::FormatErrorMessage(kInvalidParameter, permission_str);
70 return nullptr;
71 }
72
73 return permission;
74 }
75
76 // A helper method to unpack API permissions from the list in
77 // |permissions_input|, and populate the appropriate fields of |result|.
78 // Returns true on success; on failure, returns false and populates |error|.
UnpackAPIPermissions(const std::vector<std::string> & permissions_input,const PermissionSet & required_permissions,const PermissionSet & optional_permissions,UnpackPermissionSetResult * result,std::string * error)79 bool UnpackAPIPermissions(const std::vector<std::string>& permissions_input,
80 const PermissionSet& required_permissions,
81 const PermissionSet& optional_permissions,
82 UnpackPermissionSetResult* result,
83 std::string* error) {
84 PermissionsInfo* info = PermissionsInfo::GetInstance();
85 APIPermissionSet apis;
86 // Iterate over the inputs and find the corresponding API permissions.
87 for (const auto& permission_str : permissions_input) {
88 // This is a compromise: we currently can't switch to a blend of
89 // objects/strings all the way through the API. Until then, put this
90 // processing here.
91 // http://code.google.com/p/chromium/issues/detail?id=162042
92 size_t delimiter = permission_str.find(kDelimiter);
93 if (delimiter != std::string::npos) {
94 base::StringPiece permission_piece(permission_str);
95 std::unique_ptr<APIPermission> permission = UnpackPermissionWithArguments(
96 permission_piece.substr(0, delimiter),
97 permission_piece.substr(delimiter + 1), permission_str, error);
98 if (!permission)
99 return false;
100
101 apis.insert(std::move(permission));
102 } else {
103 const APIPermissionInfo* permission_info =
104 info->GetByName(permission_str);
105 if (!permission_info) {
106 *error = ErrorUtils::FormatErrorMessage(kUnknownPermissionError,
107 permission_str);
108 return false;
109 }
110 apis.insert(permission_info->id());
111 }
112 }
113
114 // Validate and partition the parsed APIs.
115 for (const auto* api_permission : apis) {
116 if (required_permissions.apis().count(api_permission->id())) {
117 result->required_apis.insert(api_permission->Clone());
118 continue;
119 }
120
121 if (!optional_permissions.apis().count(api_permission->id())) {
122 result->unlisted_apis.insert(api_permission->Clone());
123 continue;
124 }
125
126 // Permissions that don't support being optional are filtered out during
127 // manifest parsing, so between that and filtering out APIs that aren't in
128 // the optional set, all of these should support being optional.
129 DCHECK(api_permission->info()->supports_optional());
130
131 result->optional_apis.insert(api_permission->Clone());
132 }
133
134 return true;
135 }
136
137 // A helper method to unpack host permissions from the list in
138 // |permissions_input|, and populate the appropriate fields of |result|.
139 // Returns true on success; on failure, returns false and populates |error|.
UnpackOriginPermissions(const std::vector<std::string> & origins_input,const PermissionSet & required_permissions,const PermissionSet & optional_permissions,bool allow_file_access,UnpackPermissionSetResult * result,std::string * error)140 bool UnpackOriginPermissions(const std::vector<std::string>& origins_input,
141 const PermissionSet& required_permissions,
142 const PermissionSet& optional_permissions,
143 bool allow_file_access,
144 UnpackPermissionSetResult* result,
145 std::string* error) {
146 int user_script_schemes = UserScript::ValidUserScriptSchemes();
147 int explicit_schemes = Extension::kValidHostPermissionSchemes;
148
149 auto filter_schemes = [allow_file_access](URLPattern* pattern) {
150 // NOTE: We use pattern->valid_schemes() here (instead of
151 // |user_script_schemes| or |explicit_schemes|) because
152 // URLPattern::Parse() can mutate the valid schemes for a pattern, and we
153 // don't want to override those changes.
154 int valid_schemes = pattern->valid_schemes();
155
156 // We disallow the chrome:-scheme unless the pattern is explicitly
157 // "chrome://..." - that is, <all_urls> should not match the chrome:-scheme.
158 // Patterns which explicitly specify the chrome:-scheme are safe, since
159 // manifest parsing won't allow them unless the kExtensionsOnChromeURLs
160 // switch is enabled.
161 // Note that we don't check PermissionsData::AllUrlsIncludesChromeUrls()
162 // here, since that's only needed for Chromevox (which doesn't use optional
163 // permissions).
164 if (pattern->scheme() != content::kChromeUIScheme)
165 valid_schemes &= ~URLPattern::SCHEME_CHROMEUI;
166
167 // Similarly, <all_urls> should only match file:-scheme URLs if file access
168 // is granted.
169 if (!allow_file_access && pattern->scheme() != url::kFileScheme)
170 valid_schemes &= ~URLPattern::SCHEME_FILE;
171
172 if (valid_schemes != pattern->valid_schemes())
173 pattern->SetValidSchemes(valid_schemes);
174 };
175
176 for (const auto& origin_str : origins_input) {
177 URLPattern explicit_origin(explicit_schemes);
178 URLPattern::ParseResult parse_result = explicit_origin.Parse(origin_str);
179 if (URLPattern::ParseResult::kSuccess != parse_result) {
180 *error = ErrorUtils::FormatErrorMessage(
181 kInvalidOrigin, origin_str,
182 URLPattern::GetParseResultString(parse_result));
183 return false;
184 }
185
186 filter_schemes(&explicit_origin);
187
188 if ((explicit_origin.valid_schemes() & URLPattern::SCHEME_FILE) &&
189 !allow_file_access) {
190 // This should only happen with patterns that specify file schemes;
191 // otherwise they should have been filtered out in filter_schemes().
192 DCHECK_EQ(url::kFileScheme, explicit_origin.scheme());
193 result->restricted_file_scheme_patterns.AddPattern(explicit_origin);
194 // Don't add the pattern to any other set to indicate that it can't be
195 // requested/granted/contained.
196 continue;
197 }
198
199 bool used_origin = false;
200 if (required_permissions.explicit_hosts().ContainsPattern(
201 explicit_origin)) {
202 used_origin = true;
203 result->required_explicit_hosts.AddPattern(explicit_origin);
204 } else if (optional_permissions.explicit_hosts().ContainsPattern(
205 explicit_origin)) {
206 used_origin = true;
207 result->optional_explicit_hosts.AddPattern(explicit_origin);
208 }
209
210 URLPattern scriptable_origin(user_script_schemes);
211 if (scriptable_origin.Parse(origin_str) ==
212 URLPattern::ParseResult::kSuccess) {
213 filter_schemes(&scriptable_origin);
214 if (required_permissions.scriptable_hosts().ContainsPattern(
215 scriptable_origin)) {
216 used_origin = true;
217 result->required_scriptable_hosts.AddPattern(scriptable_origin);
218 }
219 }
220
221 if (!used_origin)
222 result->unlisted_hosts.AddPattern(explicit_origin);
223 }
224
225 return true;
226 }
227
228 } // namespace
229
230 UnpackPermissionSetResult::UnpackPermissionSetResult() = default;
231 UnpackPermissionSetResult::~UnpackPermissionSetResult() = default;
232
PackPermissionSet(const PermissionSet & set)233 std::unique_ptr<Permissions> PackPermissionSet(const PermissionSet& set) {
234 std::unique_ptr<Permissions> permissions(new Permissions());
235
236 permissions->permissions.reset(new std::vector<std::string>());
237 for (const APIPermission* api : set.apis()) {
238 std::unique_ptr<base::Value> value(api->ToValue());
239 if (!value) {
240 permissions->permissions->push_back(api->name());
241 } else {
242 std::string name(api->name());
243 std::string json;
244 base::JSONWriter::Write(*value, &json);
245 permissions->permissions->push_back(name + kDelimiter + json);
246 }
247 }
248
249 // TODO(rpaquay): We currently don't expose manifest permissions
250 // to apps/extensions via the permissions API.
251
252 permissions->origins.reset(new std::vector<std::string>());
253 for (const URLPattern& pattern : set.effective_hosts())
254 permissions->origins->push_back(pattern.GetAsString());
255
256 return permissions;
257 }
258
UnpackPermissionSet(const Permissions & permissions_input,const PermissionSet & required_permissions,const PermissionSet & optional_permissions,bool allow_file_access,std::string * error)259 std::unique_ptr<UnpackPermissionSetResult> UnpackPermissionSet(
260 const Permissions& permissions_input,
261 const PermissionSet& required_permissions,
262 const PermissionSet& optional_permissions,
263 bool allow_file_access,
264 std::string* error) {
265 DCHECK(error);
266
267 // TODO(rpaquay): We currently don't expose manifest permissions
268 // to apps/extensions via the permissions API.
269
270 auto result = std::make_unique<UnpackPermissionSetResult>();
271
272 if (permissions_input.permissions &&
273 !UnpackAPIPermissions(*permissions_input.permissions,
274 required_permissions, optional_permissions,
275 result.get(), error)) {
276 return nullptr;
277 }
278
279 if (permissions_input.origins &&
280 !UnpackOriginPermissions(*permissions_input.origins, required_permissions,
281 optional_permissions, allow_file_access,
282 result.get(), error)) {
283 return nullptr;
284 }
285
286 return result;
287 }
288
289 } // namespace permissions_api_helpers
290 } // namespace extensions
291