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