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.
5 #include "base/memory/scoped_ptr.h"
6 #include "base/strings/utf_string_conversions.h"
7 #include "chrome/browser/extensions/extension_service.h"
8 #include "chrome/browser/extensions/permissions_updater.h"
9 #include "chrome/browser/extensions/test_extension_environment.h"
10 #include "chrome/common/extensions/permissions/chrome_permission_message_provider.h"
11 #include "chrome/grit/generated_resources.h"
12 #include "chrome/test/base/testing_profile.h"
13 #include "components/crx_file/id_util.h"
14 #include "extensions/browser/extension_prefs.h"
15 #include "extensions/common/extension.h"
16 #include "extensions/common/extension_builder.h"
17 #include "extensions/common/manifest.h"
18 #include "extensions/common/manifest_handlers/permissions_parser.h"
19 #include "extensions/common/permissions/permission_set.h"
20 #include "extensions/common/permissions/permissions_data.h"
21 #include "extensions/common/permissions/permissions_info.h"
22 #include "extensions/common/permissions/usb_device_permission.h"
23 #include "extensions/common/permissions/usb_device_permission_data.h"
24 #include "extensions/common/test_util.h"
25 #include "extensions/common/value_builder.h"
26 #include "testing/gtest/include/gtest/gtest.h"
27 #include "ui/base/l10n/l10n_util.h"
29 namespace extensions
{
31 // Tests that ChromePermissionMessageProvider provides not only correct, but
32 // meaningful permission messages that coalesce correctly where appropriate.
33 // There are 3 types of permission messages that need to be tested:
34 // 1. The combined list of active permissions, displayed at install time (or
35 // when the app has been disabled automatically and needs to be re-enabled)
36 // 2. The split list of active permissions, displayed in the App Info dialog,
37 // where the optional permissions are individually revokable
38 // 3. The list of requested optional permissions, displayed in a prompt to the
39 // user when the app requests these during runtime
40 // Some of these tests are prefixed AntiTest_, since they demonstrate existing
41 // problematic functionality. These tests are prefixed with AntiTest_ and will
42 // be changed as the correct behaviour is implemented. TODOs in the test explain
43 // the currently problematic behaviour.
44 class PermissionMessagesUnittest
: public testing::Test
{
46 PermissionMessagesUnittest()
47 : message_provider_(new ChromePermissionMessageProvider()) {}
48 ~PermissionMessagesUnittest() override
{}
51 void CreateAndInstallAppWithPermissions(ListBuilder
& required_permissions
,
52 ListBuilder
& optional_permissions
) {
53 app_
= test_util::BuildApp(ExtensionBuilder().Pass())
56 .Set("permissions", required_permissions
)
57 .Set("optional_permissions", optional_permissions
))
58 .SetID(crx_file::id_util::GenerateId("app"))
59 .SetLocation(Manifest::INTERNAL
)
61 env_
.GetExtensionService()->AddExtension(app_
.get());
64 void CreateAndInstallExtensionWithPermissions(
65 ListBuilder
& required_permissions
,
66 ListBuilder
& optional_permissions
) {
67 app_
= test_util::BuildExtension(ExtensionBuilder().Pass())
70 .Set("permissions", required_permissions
)
71 .Set("optional_permissions", optional_permissions
))
72 .SetID(crx_file::id_util::GenerateId("extension"))
73 .SetLocation(Manifest::INTERNAL
)
75 env_
.GetExtensionService()->AddExtension(app_
.get());
78 // Returns the permission messages that would display in the prompt that
79 // requests all the optional permissions for the current |app_|.
80 std::vector
<base::string16
> GetOptionalPermissionMessages() {
81 scoped_refptr
<const PermissionSet
> granted_permissions
=
82 env_
.GetExtensionPrefs()->GetGrantedPermissions(app_
->id());
83 scoped_refptr
<const PermissionSet
> optional_permissions
=
84 PermissionsParser::GetOptionalPermissions(app_
.get());
85 scoped_refptr
<const PermissionSet
> requested_permissions
=
86 PermissionSet::CreateDifference(optional_permissions
.get(),
87 granted_permissions
.get());
88 return GetMessages(requested_permissions
);
91 void GrantOptionalPermissions() {
92 PermissionsUpdater
perms_updater(env_
.profile());
93 perms_updater
.AddPermissions(
95 PermissionsParser::GetOptionalPermissions(app_
.get()).get());
98 std::vector
<base::string16
> active_permissions() {
99 return GetMessages(app_
->permissions_data()->active_permissions());
102 std::vector
<base::string16
> required_permissions() {
103 return GetMessages(PermissionsParser::GetRequiredPermissions(app_
.get()));
106 std::vector
<base::string16
> optional_permissions() {
107 return GetMessages(PermissionsParser::GetOptionalPermissions(app_
.get()));
111 std::vector
<base::string16
> GetMessages(
112 scoped_refptr
<const PermissionSet
> permissions
) {
113 std::vector
<base::string16
> messages
;
114 for (const CoalescedPermissionMessage
& msg
:
115 message_provider_
->GetPermissionMessages(
116 message_provider_
->GetAllPermissionIDs(permissions
.get(),
118 messages
.push_back(msg
.message());
123 extensions::TestExtensionEnvironment env_
;
124 scoped_ptr
<ChromePermissionMessageProvider
> message_provider_
;
125 scoped_refptr
<const Extension
> app_
;
127 DISALLOW_COPY_AND_ASSIGN(PermissionMessagesUnittest
);
130 // If an app has both the 'history' and 'tabs' permission, one should hide the
131 // other (the 'history' permission has superset permissions).
132 TEST_F(PermissionMessagesUnittest
, HistoryHidesTabsMessage
) {
133 CreateAndInstallExtensionWithPermissions(
134 ListBuilder().Append("tabs").Append("history").Pass(),
135 ListBuilder().Pass());
137 ASSERT_EQ(1U, required_permissions().size());
139 l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WARNING_HISTORY_WRITE
),
140 required_permissions()[0]);
142 ASSERT_EQ(0U, optional_permissions().size());
145 // If an app requests the 'history' permission, but already has the 'tabs'
146 // permission, only the new coalesced message is displayed.
147 TEST_F(PermissionMessagesUnittest
, MixedPermissionMessagesCoalesceOnceGranted
) {
148 CreateAndInstallExtensionWithPermissions(
149 ListBuilder().Append("tabs").Pass(),
150 ListBuilder().Append("history").Pass());
152 ASSERT_EQ(1U, required_permissions().size());
154 l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WARNING_HISTORY_READ
),
155 required_permissions()[0]);
157 ASSERT_EQ(1U, optional_permissions().size());
159 l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WARNING_HISTORY_WRITE
),
160 optional_permissions()[0]);
162 ASSERT_EQ(1U, active_permissions().size());
164 l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WARNING_HISTORY_READ
),
165 active_permissions()[0]);
167 ASSERT_EQ(1U, GetOptionalPermissionMessages().size());
169 l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WARNING_HISTORY_WRITE
),
170 GetOptionalPermissionMessages()[0]);
172 GrantOptionalPermissions();
174 ASSERT_EQ(1U, active_permissions().size());
176 l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WARNING_HISTORY_WRITE
),
177 active_permissions()[0]);
180 // AntiTest: This behavior should be changed and improved.
181 // If an app requests the 'tabs' permission but already has the 'history'
182 // permission, a prompt is displayed. However, no prompt should appear at all,
183 // since 'tabs' is a subset of 'history' and the final list of permissions are
184 // not affected by this grant.
185 TEST_F(PermissionMessagesUnittest
,
186 AntiTest_PromptCanRequestSubsetOfAlreadyGrantedPermissions
) {
187 CreateAndInstallExtensionWithPermissions(
188 ListBuilder().Append("history").Pass(),
189 ListBuilder().Append("tabs").Pass());
191 ASSERT_EQ(1U, required_permissions().size());
193 l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WARNING_HISTORY_WRITE
),
194 required_permissions()[0]);
196 ASSERT_EQ(1U, optional_permissions().size());
198 l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WARNING_HISTORY_READ
),
199 optional_permissions()[0]);
201 ASSERT_EQ(1U, active_permissions().size());
203 l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WARNING_HISTORY_WRITE
),
204 active_permissions()[0]);
206 // TODO(sashab): This prompt should display no permissions, since READ is a
207 // subset permission of WRITE.
208 ASSERT_EQ(1U, GetOptionalPermissionMessages().size());
210 l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WARNING_HISTORY_READ
),
211 GetOptionalPermissionMessages()[0]);
213 GrantOptionalPermissions();
215 ASSERT_EQ(1U, active_permissions().size());
217 l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WARNING_HISTORY_WRITE
),
218 active_permissions()[0]);
221 // AntiTest: This behavior should be changed and improved.
222 // If an app requests the 'sessions' permission, nothing is displayed in the
223 // permission request prompt. However, the required permissions for the app are
224 // actually modified, so the prompt *should* display a message to prevent this
225 // permission from being granted for free.
226 TEST_F(PermissionMessagesUnittest
,
227 AntiTest_PromptCanBeEmptyButCausesChangeInPermissions
) {
228 CreateAndInstallExtensionWithPermissions(
229 ListBuilder().Append("tabs").Pass(),
230 ListBuilder().Append("sessions").Pass());
232 ASSERT_EQ(1U, required_permissions().size());
234 l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WARNING_HISTORY_READ
),
235 required_permissions()[0]);
237 ASSERT_EQ(0U, optional_permissions().size());
239 ASSERT_EQ(1U, active_permissions().size());
241 l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WARNING_HISTORY_READ
),
242 active_permissions()[0]);
244 // TODO(sashab): This prompt should display the sessions permission message,
245 // as well as warn the user that it can affect the existing 'tab' permission.
246 ASSERT_EQ(0U, GetOptionalPermissionMessages().size());
248 GrantOptionalPermissions();
250 ASSERT_EQ(1U, active_permissions().size());
251 EXPECT_EQ(l10n_util::GetStringUTF16(
252 IDS_EXTENSION_PROMPT_WARNING_HISTORY_READ_AND_SESSIONS
),
253 active_permissions()[0]);
256 class USBDevicePermissionMessagesTest
: public testing::Test
{
258 USBDevicePermissionMessagesTest()
259 : message_provider_(new ChromePermissionMessageProvider()) {}
260 ~USBDevicePermissionMessagesTest() override
{}
262 CoalescedPermissionMessages
GetMessages(const PermissionIDSet
& permissions
) {
263 return message_provider_
->GetPermissionMessages(permissions
);
267 scoped_ptr
<ChromePermissionMessageProvider
> message_provider_
;
270 TEST_F(USBDevicePermissionMessagesTest
, SingleDevice
) {
272 const char kMessage
[] =
273 "Access any PVR Mass Storage from HUMAX Co., Ltd. via USB";
275 scoped_ptr
<base::ListValue
> permission_list(new base::ListValue());
276 permission_list
->Append(
277 UsbDevicePermissionData(0x02ad, 0x138c, -1).ToValue().release());
279 UsbDevicePermission
permission(
280 PermissionsInfo::GetInstance()->GetByID(APIPermission::kUsbDevice
));
281 ASSERT_TRUE(permission
.FromValue(permission_list
.get(), NULL
, NULL
));
283 CoalescedPermissionMessages messages
=
284 GetMessages(permission
.GetPermissions());
285 ASSERT_EQ(1U, messages
.size());
286 EXPECT_EQ(base::ASCIIToUTF16(kMessage
), messages
.front().message());
289 const char kMessage
[] = "Access USB devices from HUMAX Co., Ltd.";
291 scoped_ptr
<base::ListValue
> permission_list(new base::ListValue());
292 permission_list
->Append(
293 UsbDevicePermissionData(0x02ad, 0x138d, -1).ToValue().release());
295 UsbDevicePermission
permission(
296 PermissionsInfo::GetInstance()->GetByID(APIPermission::kUsbDevice
));
297 ASSERT_TRUE(permission
.FromValue(permission_list
.get(), NULL
, NULL
));
299 CoalescedPermissionMessages messages
=
300 GetMessages(permission
.GetPermissions());
301 ASSERT_EQ(1U, messages
.size());
302 EXPECT_EQ(base::ASCIIToUTF16(kMessage
), messages
.front().message());
305 const char kMessage
[] = "Access USB devices from an unknown vendor";
307 scoped_ptr
<base::ListValue
> permission_list(new base::ListValue());
308 permission_list
->Append(
309 UsbDevicePermissionData(0x02ae, 0x138d, -1).ToValue().release());
311 UsbDevicePermission
permission(
312 PermissionsInfo::GetInstance()->GetByID(APIPermission::kUsbDevice
));
313 ASSERT_TRUE(permission
.FromValue(permission_list
.get(), NULL
, NULL
));
315 CoalescedPermissionMessages messages
=
316 GetMessages(permission
.GetPermissions());
317 ASSERT_EQ(1U, messages
.size());
318 EXPECT_EQ(base::ASCIIToUTF16(kMessage
), messages
.front().message());
322 TEST_F(USBDevicePermissionMessagesTest
, MultipleDevice
) {
323 const char kMessage
[] = "Access any of these USB devices";
324 const char* kDetails
[] = {
325 "PVR Mass Storage from HUMAX Co., Ltd.",
326 // TODO(treib): The following two should be the other way around -
327 // "unknown devices from Vendor" first, then the catch-all "devices from
328 // unknown vendor". crbug.com/522842
329 "devices from an unknown vendor",
330 "unknown devices from HUMAX Co., Ltd."
334 scoped_ptr
<base::ListValue
> permission_list(new base::ListValue());
335 permission_list
->Append(
336 UsbDevicePermissionData(0x02ad, 0x138c, -1).ToValue().release());
337 // This device's product ID is not in Chrome's database.
338 permission_list
->Append(
339 UsbDevicePermissionData(0x02ad, 0x138d, -1).ToValue().release());
340 // This additional unknown product will be collapsed into the entry above.
341 permission_list
->Append(
342 UsbDevicePermissionData(0x02ad, 0x138e, -1).ToValue().release());
343 // This device's vendor ID is not in Chrome's database.
344 permission_list
->Append(
345 UsbDevicePermissionData(0x02ae, 0x138d, -1).ToValue().release());
346 // This additional unknown vendor will be collapsed into the entry above.
347 permission_list
->Append(
348 UsbDevicePermissionData(0x02af, 0x138d, -1).ToValue().release());
350 UsbDevicePermission
permission(
351 PermissionsInfo::GetInstance()->GetByID(APIPermission::kUsbDevice
));
352 ASSERT_TRUE(permission
.FromValue(permission_list
.get(), NULL
, NULL
));
354 CoalescedPermissionMessages messages
=
355 GetMessages(permission
.GetPermissions());
356 ASSERT_EQ(1U, messages
.size());
357 EXPECT_EQ(base::ASCIIToUTF16(kMessage
), messages
.front().message());
358 const std::vector
<base::string16
>& submessages
=
359 messages
.front().submessages();
360 ASSERT_EQ(arraysize(kDetails
), submessages
.size());
361 for (size_t i
= 0; i
< submessages
.size(); i
++)
362 EXPECT_EQ(base::ASCIIToUTF16(kDetails
[i
]), submessages
[i
]);
365 } // namespace extensions