1 // Copyright 2014 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 <stddef.h>
6 
7 #include <memory>
8 #include <utility>
9 
10 #include "base/stl_util.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "chrome/browser/extensions/extension_service.h"
13 #include "chrome/browser/extensions/permissions_test_util.h"
14 #include "chrome/browser/extensions/permissions_updater.h"
15 #include "chrome/browser/extensions/test_extension_environment.h"
16 #include "chrome/common/extensions/permissions/chrome_permission_message_provider.h"
17 #include "chrome/grit/generated_resources.h"
18 #include "chrome/test/base/testing_profile.h"
19 #include "components/crx_file/id_util.h"
20 #include "extensions/browser/extension_prefs.h"
21 #include "extensions/common/extension.h"
22 #include "extensions/common/extension_builder.h"
23 #include "extensions/common/manifest.h"
24 #include "extensions/common/manifest_handlers/permissions_parser.h"
25 #include "extensions/common/permissions/permission_set.h"
26 #include "extensions/common/permissions/permissions_data.h"
27 #include "extensions/common/permissions/permissions_info.h"
28 #include "extensions/common/permissions/usb_device_permission.h"
29 #include "extensions/common/permissions/usb_device_permission_data.h"
30 #include "extensions/common/value_builder.h"
31 #include "testing/gtest/include/gtest/gtest.h"
32 #include "ui/base/l10n/l10n_util.h"
33 
34 namespace extensions {
35 
36 // Tests that ChromePermissionMessageProvider provides not only correct, but
37 // meaningful permission messages that coalesce correctly where appropriate.
38 // There are 3 types of permission messages that need to be tested:
39 //  1. The combined list of active permissions, displayed at install time (or
40 //     when the app has been disabled automatically and needs to be re-enabled)
41 //  2. The split list of active permissions, displayed in the App Info dialog,
42 //     where the optional permissions are individually revokable
43 //  3. The list of requested optional permissions, displayed in a prompt to the
44 //     user when the app requests these during runtime
45 // Some of these tests are prefixed AntiTest_, since they demonstrate existing
46 // problematic functionality. These tests are prefixed with AntiTest_ and will
47 // be changed as the correct behaviour is implemented. TODOs in the test explain
48 // the currently problematic behaviour.
49 class PermissionMessagesUnittest : public testing::Test {
50  public:
PermissionMessagesUnittest()51   PermissionMessagesUnittest()
52       : message_provider_(new ChromePermissionMessageProvider()) {}
~PermissionMessagesUnittest()53   ~PermissionMessagesUnittest() override {}
54 
55  protected:
CreateAndInstallExtensionWithPermissions(std::unique_ptr<base::ListValue> required_permissions,std::unique_ptr<base::ListValue> optional_permissions)56   void CreateAndInstallExtensionWithPermissions(
57       std::unique_ptr<base::ListValue> required_permissions,
58       std::unique_ptr<base::ListValue> optional_permissions) {
59     app_ = ExtensionBuilder("Test")
60                .SetManifestKey("permissions", std::move(required_permissions))
61                .SetManifestKey("optional_permissions",
62                                std::move(optional_permissions))
63                .SetID(crx_file::id_util::GenerateId("extension"))
64                .SetLocation(Manifest::INTERNAL)
65                .Build();
66     env_.GetExtensionService()->AddExtension(app_.get());
67   }
68 
69   // Returns the permission messages that would display in the prompt that
70   // requests all the optional permissions for the current |app_|.
GetOptionalPermissionMessages()71   std::vector<base::string16> GetOptionalPermissionMessages() {
72     std::unique_ptr<const PermissionSet> granted_permissions =
73         env_.GetExtensionPrefs()->GetGrantedPermissions(app_->id());
74     const PermissionSet& optional_permissions =
75         PermissionsParser::GetOptionalPermissions(app_.get());
76     std::unique_ptr<const PermissionSet> requested_permissions =
77         PermissionSet::CreateDifference(optional_permissions,
78                                         *granted_permissions);
79     return GetMessages(*requested_permissions);
80   }
81 
GrantOptionalPermissions()82   void GrantOptionalPermissions() {
83     permissions_test_util::GrantOptionalPermissionsAndWaitForCompletion(
84         env_.profile(), *app_,
85         PermissionsParser::GetOptionalPermissions(app_.get()));
86   }
87 
active_permissions()88   std::vector<base::string16> active_permissions() {
89     return GetMessages(app_->permissions_data()->active_permissions());
90   }
91 
required_permissions()92   std::vector<base::string16> required_permissions() {
93     return GetMessages(PermissionsParser::GetRequiredPermissions(app_.get()));
94   }
95 
optional_permissions()96   std::vector<base::string16> optional_permissions() {
97     return GetMessages(PermissionsParser::GetOptionalPermissions(app_.get()));
98   }
99 
100  private:
GetMessages(const PermissionSet & permissions)101   std::vector<base::string16> GetMessages(const PermissionSet& permissions) {
102     std::vector<base::string16> messages;
103     for (const PermissionMessage& msg :
104          message_provider_->GetPermissionMessages(
105              message_provider_->GetAllPermissionIDs(permissions,
106                                                     app_->GetType()))) {
107       messages.push_back(msg.message());
108     }
109     return messages;
110   }
111 
112   extensions::TestExtensionEnvironment env_;
113   std::unique_ptr<ChromePermissionMessageProvider> message_provider_;
114   scoped_refptr<const Extension> app_;
115 
116   DISALLOW_COPY_AND_ASSIGN(PermissionMessagesUnittest);
117 };
118 
119 // If an app has both the 'history' and 'tabs' permission, one should hide the
120 // other (the 'history' permission has superset permissions).
TEST_F(PermissionMessagesUnittest,HistoryHidesTabsMessage)121 TEST_F(PermissionMessagesUnittest, HistoryHidesTabsMessage) {
122   CreateAndInstallExtensionWithPermissions(
123       ListBuilder().Append("tabs").Append("history").Build(),
124       ListBuilder().Build());
125 
126   ASSERT_EQ(1U, required_permissions().size());
127   EXPECT_EQ(
128       l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WARNING_HISTORY_WRITE),
129       required_permissions()[0]);
130 
131   ASSERT_EQ(0U, optional_permissions().size());
132 }
133 
134 // If an app requests the 'history' permission, but already has the 'tabs'
135 // permission, only the new coalesced message is displayed.
TEST_F(PermissionMessagesUnittest,MixedPermissionMessagesCoalesceOnceGranted)136 TEST_F(PermissionMessagesUnittest, MixedPermissionMessagesCoalesceOnceGranted) {
137   CreateAndInstallExtensionWithPermissions(
138       ListBuilder().Append("tabs").Build(),
139       ListBuilder().Append("history").Build());
140 
141   ASSERT_EQ(1U, required_permissions().size());
142   EXPECT_EQ(
143       l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WARNING_HISTORY_READ),
144       required_permissions()[0]);
145 
146   ASSERT_EQ(1U, optional_permissions().size());
147   EXPECT_EQ(
148       l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WARNING_HISTORY_WRITE),
149       optional_permissions()[0]);
150 
151   ASSERT_EQ(1U, active_permissions().size());
152   EXPECT_EQ(
153       l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WARNING_HISTORY_READ),
154       active_permissions()[0]);
155 
156   ASSERT_EQ(1U, GetOptionalPermissionMessages().size());
157   EXPECT_EQ(
158       l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WARNING_HISTORY_WRITE),
159       GetOptionalPermissionMessages()[0]);
160 
161   GrantOptionalPermissions();
162 
163   ASSERT_EQ(1U, active_permissions().size());
164   EXPECT_EQ(
165       l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WARNING_HISTORY_WRITE),
166       active_permissions()[0]);
167 }
168 
169 // AntiTest: This behavior should be changed and improved.
170 // If an app requests the 'tabs' permission but already has the 'history'
171 // permission, a prompt is displayed. However, no prompt should appear at all,
172 // since 'tabs' is a subset of 'history' and the final list of permissions are
173 // not affected by this grant.
TEST_F(PermissionMessagesUnittest,AntiTest_PromptCanRequestSubsetOfAlreadyGrantedPermissions)174 TEST_F(PermissionMessagesUnittest,
175        AntiTest_PromptCanRequestSubsetOfAlreadyGrantedPermissions) {
176   CreateAndInstallExtensionWithPermissions(
177       ListBuilder().Append("history").Build(),
178       ListBuilder().Append("tabs").Build());
179 
180   ASSERT_EQ(1U, required_permissions().size());
181   EXPECT_EQ(
182       l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WARNING_HISTORY_WRITE),
183       required_permissions()[0]);
184 
185   ASSERT_EQ(1U, optional_permissions().size());
186   EXPECT_EQ(
187       l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WARNING_HISTORY_READ),
188       optional_permissions()[0]);
189 
190   ASSERT_EQ(1U, active_permissions().size());
191   EXPECT_EQ(
192       l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WARNING_HISTORY_WRITE),
193       active_permissions()[0]);
194 
195   // TODO(sashab): This prompt should display no permissions, since READ is a
196   // subset permission of WRITE.
197   ASSERT_EQ(1U, GetOptionalPermissionMessages().size());
198   EXPECT_EQ(
199       l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WARNING_HISTORY_READ),
200       GetOptionalPermissionMessages()[0]);
201 
202   GrantOptionalPermissions();
203 
204   ASSERT_EQ(1U, active_permissions().size());
205   EXPECT_EQ(
206       l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WARNING_HISTORY_WRITE),
207       active_permissions()[0]);
208 }
209 
210 // AntiTest: This behavior should be changed and improved.
211 // If an app requests the 'sessions' permission, nothing is displayed in the
212 // permission request prompt. However, the required permissions for the app are
213 // actually modified, so the prompt *should* display a message to prevent this
214 // permission from being granted for free.
TEST_F(PermissionMessagesUnittest,AntiTest_PromptCanBeEmptyButCausesChangeInPermissions)215 TEST_F(PermissionMessagesUnittest,
216        AntiTest_PromptCanBeEmptyButCausesChangeInPermissions) {
217   CreateAndInstallExtensionWithPermissions(
218       ListBuilder().Append("tabs").Build(),
219       ListBuilder().Append("sessions").Build());
220 
221   ASSERT_EQ(1U, required_permissions().size());
222   EXPECT_EQ(
223       l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WARNING_HISTORY_READ),
224       required_permissions()[0]);
225 
226   ASSERT_EQ(0U, optional_permissions().size());
227 
228   ASSERT_EQ(1U, active_permissions().size());
229   EXPECT_EQ(
230       l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WARNING_HISTORY_READ),
231       active_permissions()[0]);
232 
233   // TODO(sashab): This prompt should display the sessions permission message,
234   // as well as warn the user that it can affect the existing 'tab' permission.
235   ASSERT_EQ(0U, GetOptionalPermissionMessages().size());
236 
237   GrantOptionalPermissions();
238 
239   ASSERT_EQ(1U, active_permissions().size());
240   EXPECT_EQ(l10n_util::GetStringUTF16(
241                 IDS_EXTENSION_PROMPT_WARNING_HISTORY_READ_AND_SESSIONS),
242             active_permissions()[0]);
243 }
244 
245 class USBDevicePermissionMessagesTest : public testing::Test {
246  public:
USBDevicePermissionMessagesTest()247   USBDevicePermissionMessagesTest()
248       : message_provider_(new ChromePermissionMessageProvider()) {}
~USBDevicePermissionMessagesTest()249   ~USBDevicePermissionMessagesTest() override {}
250 
GetMessages(const PermissionIDSet & permissions)251   PermissionMessages GetMessages(const PermissionIDSet& permissions) {
252     return message_provider_->GetPermissionMessages(permissions);
253   }
254 
255  private:
256   std::unique_ptr<ChromePermissionMessageProvider> message_provider_;
257 };
258 
TEST_F(USBDevicePermissionMessagesTest,SingleDevice)259 TEST_F(USBDevicePermissionMessagesTest, SingleDevice) {
260   {
261     const char kMessage[] =
262         "Access any PVR Mass Storage from HUMAX Co., Ltd. via USB";
263 
264     std::unique_ptr<base::ListValue> permission_list(new base::ListValue());
265     permission_list->Append(
266         UsbDevicePermissionData(0x02ad, 0x138c, -1, -1).ToValue());
267 
268     UsbDevicePermission permission(
269         PermissionsInfo::GetInstance()->GetByID(APIPermission::kUsbDevice));
270     ASSERT_TRUE(permission.FromValue(permission_list.get(), NULL, NULL));
271 
272     PermissionMessages messages = GetMessages(permission.GetPermissions());
273     ASSERT_EQ(1U, messages.size());
274     EXPECT_EQ(base::ASCIIToUTF16(kMessage), messages.front().message());
275   }
276   {
277     const char kMessage[] = "Access USB devices from HUMAX Co., Ltd.";
278 
279     std::unique_ptr<base::ListValue> permission_list(new base::ListValue());
280     permission_list->Append(
281         UsbDevicePermissionData(0x02ad, 0x138d, -1, -1).ToValue());
282 
283     UsbDevicePermission permission(
284         PermissionsInfo::GetInstance()->GetByID(APIPermission::kUsbDevice));
285     ASSERT_TRUE(permission.FromValue(permission_list.get(), NULL, NULL));
286 
287     PermissionMessages messages = GetMessages(permission.GetPermissions());
288     ASSERT_EQ(1U, messages.size());
289     EXPECT_EQ(base::ASCIIToUTF16(kMessage), messages.front().message());
290   }
291   {
292     const char kMessage[] = "Access USB devices from an unknown vendor";
293 
294     std::unique_ptr<base::ListValue> permission_list(new base::ListValue());
295     permission_list->Append(
296         UsbDevicePermissionData(0x02ae, 0x138d, -1, -1).ToValue());
297 
298     UsbDevicePermission permission(
299         PermissionsInfo::GetInstance()->GetByID(APIPermission::kUsbDevice));
300     ASSERT_TRUE(permission.FromValue(permission_list.get(), NULL, NULL));
301 
302     PermissionMessages messages = GetMessages(permission.GetPermissions());
303     ASSERT_EQ(1U, messages.size());
304     EXPECT_EQ(base::ASCIIToUTF16(kMessage), messages.front().message());
305   }
306 }
307 
TEST_F(USBDevicePermissionMessagesTest,MultipleDevice)308 TEST_F(USBDevicePermissionMessagesTest, MultipleDevice) {
309   const char kMessage[] = "Access any of these USB devices";
310   const char* kDetails[] = {
311       "PVR Mass Storage from HUMAX Co., Ltd.",
312       "unknown devices from HUMAX Co., Ltd.",
313       "devices from an unknown vendor"
314   };
315 
316   // Prepare data set
317   std::unique_ptr<base::ListValue> permission_list(new base::ListValue());
318   permission_list->Append(
319       UsbDevicePermissionData(0x02ad, 0x138c, -1, -1).ToValue());
320   // This device's product ID is not in Chrome's database.
321   permission_list->Append(
322       UsbDevicePermissionData(0x02ad, 0x138d, -1, -1).ToValue());
323   // This additional unknown product will be collapsed into the entry above.
324   permission_list->Append(
325       UsbDevicePermissionData(0x02ad, 0x138e, -1, -1).ToValue());
326   // This device's vendor ID is not in Chrome's database.
327   permission_list->Append(
328       UsbDevicePermissionData(0x02ae, 0x138d, -1, -1).ToValue());
329   // This additional unknown vendor will be collapsed into the entry above.
330   permission_list->Append(
331       UsbDevicePermissionData(0x02af, 0x138d, -1, -1).ToValue());
332 
333   UsbDevicePermission permission(
334       PermissionsInfo::GetInstance()->GetByID(APIPermission::kUsbDevice));
335   ASSERT_TRUE(permission.FromValue(permission_list.get(), NULL, NULL));
336 
337   PermissionMessages messages = GetMessages(permission.GetPermissions());
338   ASSERT_EQ(1U, messages.size());
339   EXPECT_EQ(base::ASCIIToUTF16(kMessage), messages.front().message());
340   const std::vector<base::string16>& submessages =
341       messages.front().submessages();
342   ASSERT_EQ(base::size(kDetails), submessages.size());
343   for (size_t i = 0; i < submessages.size(); i++)
344     EXPECT_EQ(base::ASCIIToUTF16(kDetails[i]), submessages[i]);
345 }
346 
347 }  // namespace extensions
348