Check USB device path access when prompting users to select a device.
[chromium-blink-merge.git] / chrome / browser / chromeos / dbus / printer_service_provider.cc
blob6310dd20469fa889ad5fbf57cfd71049dae4766c
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.
5 #include "chrome/browser/chromeos/dbus/printer_service_provider.h"
7 #include <stdint.h>
9 #include <limits>
11 #include "ash/session/session_state_delegate.h"
12 #include "ash/shell.h"
13 #include "ash/wm/window_util.h"
14 #include "base/bind.h"
15 #include "base/bind_helpers.h"
16 #include "base/command_line.h"
17 #include "base/memory/scoped_ptr.h"
18 #include "base/metrics/histogram.h"
19 #include "base/strings/string_number_conversions.h"
20 #include "base/strings/string_util.h"
21 #include "base/strings/stringprintf.h"
22 #include "base/strings/utf_string_conversions.h"
23 #include "base/sys_info.h"
24 #include "chrome/browser/browser_process.h"
25 #include "chrome/browser/chromeos/profiles/profile_helper.h"
26 #include "chrome/browser/notifications/notification.h"
27 #include "chrome/browser/notifications/notification_delegate.h"
28 #include "chrome/browser/notifications/notification_ui_manager.h"
29 #include "chrome/browser/profiles/profile_manager.h"
30 #include "chrome/browser/ui/browser.h"
31 #include "chrome/browser/ui/browser_tabstrip.h"
32 #include "chrome/browser/ui/browser_window.h"
33 #include "chrome/browser/ui/scoped_tabbed_browser_displayer.h"
34 #include "chrome/browser/ui/tab_contents/tab_contents_iterator.h"
35 #include "chrome/browser/ui/tabs/tab_strip_model.h"
36 #include "chrome/common/chrome_version_info.h"
37 #include "chrome/grit/generated_resources.h"
38 #include "chromeos/chromeos_switches.h"
39 #include "components/user_manager/user.h"
40 #include "components/user_manager/user_manager.h"
41 #include "content/public/browser/browser_thread.h"
42 #include "content/public/browser/web_contents.h"
43 #include "dbus/bus.h"
44 #include "dbus/exported_object.h"
45 #include "dbus/message.h"
46 #include "device/usb/usb_ids.h"
47 #include "extensions/browser/extension_registry.h"
48 #include "extensions/common/extension.h"
49 #include "extensions/common/extension_set.h"
50 #include "extensions/common/permissions/api_permission.h"
51 #include "extensions/common/permissions/permissions_data.h"
52 #include "extensions/common/permissions/usb_device_permission.h"
53 #include "grit/theme_resources.h"
54 #include "net/base/escape.h"
55 #include "third_party/cros_system_api/dbus/service_constants.h"
56 #include "ui/aura/window.h"
57 #include "ui/base/l10n/l10n_util.h"
58 #include "ui/base/resource/resource_bundle.h"
60 namespace {
62 const char kPrinterAdded[] = "PrinterAdded";
64 const char kPrinterProviderFoundNotificationID[] =
65 "chrome://settings/printer/printer_app_found";
67 const char kNoPrinterProviderNotificationID[] =
68 "chrome://settings/printer/no_printer_app";
70 enum PrinterServiceEvent {
71 PRINTER_ADDED,
72 PAGE_DISPLAYED,
73 PRINTER_SERVICE_EVENT_MAX,
76 // TODO(vitalybuka): update URL with more relevant information.
77 const char kCloudPrintLearnUrl[] =
78 "https://www.google.com/landing/cloudprint/index.html";
80 void ActivateContents(Browser* browser, content::WebContents* contents) {
81 browser->tab_strip_model()->ActivateTabAt(
82 browser->tab_strip_model()->GetIndexOfWebContents(contents), false);
85 Browser* ActivateAndGetBrowserForUrl(GURL url) {
86 for (TabContentsIterator it; !it.done(); it.Next()) {
87 if (it->GetLastCommittedURL() == url) {
88 ActivateContents(it.browser(), *it);
89 return it.browser();
92 return nullptr;
95 bool HexStringToUInt16(const std::string& input, uint16* output) {
96 uint32 output_uint = 0;
97 if (!base::HexStringToUInt(input, &output_uint) ||
98 output_uint > std::numeric_limits<uint16>::max())
99 return false;
100 *output = static_cast<uint16>(output_uint);
101 return true;
104 void FindOrOpenCloudPrintPage(const std::string& /* vendor */,
105 const std::string& /* product */) {
106 UMA_HISTOGRAM_ENUMERATION("PrinterService.PrinterServiceEvent", PRINTER_ADDED,
107 PRINTER_SERVICE_EVENT_MAX);
108 if (!ash::Shell::GetInstance()->session_state_delegate()->
109 IsActiveUserSessionStarted() ||
110 ash::Shell::GetInstance()->session_state_delegate()->IsScreenLocked()) {
111 return;
114 Profile* profile = ProfileManager::GetLastUsedProfile();
115 if (!profile)
116 return;
118 GURL url(kCloudPrintLearnUrl);
120 if (!ActivateAndGetBrowserForUrl(url)) {
121 chrome::ScopedTabbedBrowserDisplayer displayer(
122 profile, chrome::HOST_DESKTOP_TYPE_ASH);
123 UMA_HISTOGRAM_ENUMERATION("PrinterService.PrinterServiceEvent",
124 PAGE_DISPLAYED, PRINTER_SERVICE_EVENT_MAX);
125 chrome::AddSelectedTabWithURL(displayer.browser(), url,
126 ui::PAGE_TRANSITION_LINK);
130 base::string16 GetNotificationTitle(uint16 vendor_id, uint16 product_id) {
131 const char* vendor_name = device::UsbIds::GetVendorName(vendor_id);
132 if (vendor_name) {
133 return l10n_util::GetStringFUTF16(IDS_PRINTER_DETECTED_NOTIFICATION_TITLE,
134 base::UTF8ToUTF16(vendor_name));
135 } else {
136 return l10n_util::GetStringUTF16(
137 IDS_PRINTER_DETECTED_NOTIFICATION_TITLE_UNKNOWN_VENDOR);
141 std::string GetNotificationTag(const std::string& vendor_id,
142 const std::string& product_id) {
143 return vendor_id + ":" + product_id;
146 // Checks if there is an enabled extension with printerProvider permission and
147 // usbDevices persmission for the USB (vendor_id, product_id) pair.
148 bool HasAppThatSupportsPrinter(Profile* profile,
149 uint16 vendor_id,
150 uint16 product_id) {
151 const extensions::ExtensionSet& enabled_extensions =
152 extensions::ExtensionRegistry::Get(profile)->enabled_extensions();
153 for (const auto& extension : enabled_extensions) {
154 if (!extension->permissions_data() ||
155 !extension->permissions_data()->HasAPIPermission(
156 extensions::APIPermission::kPrinterProvider) ||
157 !extension->permissions_data()->HasAPIPermission(
158 extensions::APIPermission::kUsb)) {
159 continue;
162 extensions::UsbDevicePermission::CheckParam param(
163 vendor_id, product_id,
164 extensions::UsbDevicePermissionData::UNSPECIFIED_INTERFACE);
165 if (extension->permissions_data()->CheckAPIPermissionWithParam(
166 extensions::APIPermission::kUsbDevice, &param)) {
167 return true;
170 return false;
173 // Delegate for notification shown when a printer provider app for the plugged
174 // in printer is found.
175 class PrinterProviderExistsNotificationDelegate : public NotificationDelegate {
176 public:
177 PrinterProviderExistsNotificationDelegate(const std::string& vendor_id,
178 const std::string& product_id)
179 : vendor_id_(vendor_id), product_id_(product_id) {}
181 std::string id() const override {
182 return "system.printer.printer_provider_exists/" +
183 GetNotificationTag(vendor_id_, product_id_);
186 private:
187 ~PrinterProviderExistsNotificationDelegate() override = default;
189 const std::string vendor_id_;
190 const std::string product_id_;
192 DISALLOW_COPY_AND_ASSIGN(PrinterProviderExistsNotificationDelegate);
195 // Delegate for notification shown when there are no printer provider apps that
196 // support the plugged in printer found.
197 // The notification is clickable, and clicking it is supposed to launch
198 // Chrome Web Store widget listing apps that can support the plugged in printer.
199 // (not implemented yet).
200 class SearchPrinterAppNotificationDelegate : public NotificationDelegate {
201 public:
202 SearchPrinterAppNotificationDelegate(const std::string& vendor_id,
203 const std::string& product_id)
204 : vendor_id_(vendor_id), product_id_(product_id) {}
206 std::string id() const override {
207 return "system.printer.no_printer_provider_found/" +
208 GetNotificationTag(vendor_id_, product_id_);
210 bool HasClickedListener() override { return true; }
212 void Click() override {
213 // TODO(tbarzic): Implement this (http://crbug.com/439448).
216 private:
217 ~SearchPrinterAppNotificationDelegate() override = default;
219 std::string vendor_id_;
220 std::string product_id_;
222 DISALLOW_COPY_AND_ASSIGN(SearchPrinterAppNotificationDelegate);
225 // Shows a notification for a plugged in printer.
226 // If there is a printerProvider app that handles the printer's USB (vendor_id,
227 // product_id) pair, the notification informs the user that the printer is ready
228 // to be used, otherwise it offers the user to search the Chrome Web Store for
229 // an app that can handle the printer.
230 void ShowPrinterPluggedNotification(
231 NotificationUIManager* notification_ui_manager,
232 const std::string& vendor_id_str,
233 const std::string& product_id_str) {
234 uint16 vendor_id = 0;
235 uint16 product_id = 0;
236 if (!HexStringToUInt16(vendor_id_str, &vendor_id) ||
237 !HexStringToUInt16(product_id_str, &product_id)) {
238 LOG(WARNING) << "Invalid USB ID " << vendor_id_str << ":" << product_id_str;
239 return;
242 const user_manager::User* user =
243 user_manager::UserManager::Get()
244 ? user_manager::UserManager::Get()->GetActiveUser()
245 : nullptr;
246 if (!user || !user->HasGaiaAccount())
247 return;
249 Profile* profile = chromeos::ProfileHelper::Get()->GetProfileByUser(user);
250 if (!profile)
251 return;
253 ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
254 scoped_ptr<Notification> notification;
256 if (HasAppThatSupportsPrinter(profile, vendor_id, product_id)) {
257 notification.reset(new Notification(
258 message_center::NOTIFICATION_TYPE_SIMPLE,
259 GURL(kPrinterProviderFoundNotificationID),
260 GetNotificationTitle(vendor_id, product_id),
261 l10n_util::GetStringUTF16(
262 IDS_PRINTER_DETECTED_NOTIFICATION_PRINT_APP_FOUND_BODY),
263 bundle.GetImageNamed(IDR_PRINTER_NOTIFICATION),
264 message_center::NotifierId(message_center::NotifierId::SYSTEM_COMPONENT,
265 kPrinterProviderFoundNotificationID),
266 base::string16(), GetNotificationTag(vendor_id_str, product_id_str),
267 message_center::RichNotificationData(),
268 new PrinterProviderExistsNotificationDelegate(vendor_id_str,
269 product_id_str)));
270 } else {
271 message_center::RichNotificationData options;
272 options.clickable = true;
273 notification.reset(new Notification(
274 message_center::NOTIFICATION_TYPE_SIMPLE,
275 GURL(kNoPrinterProviderNotificationID),
276 GetNotificationTitle(vendor_id, product_id),
277 l10n_util::GetStringUTF16(
278 IDS_PRINTER_DETECTED_NOTIFICATION_NO_PRINT_APP_BODY),
279 bundle.GetImageNamed(IDR_PRINTER_NOTIFICATION),
280 message_center::NotifierId(message_center::NotifierId::SYSTEM_COMPONENT,
281 kNoPrinterProviderNotificationID),
282 base::string16(), GetNotificationTag(vendor_id_str, product_id_str),
283 options, new SearchPrinterAppNotificationDelegate(vendor_id_str,
284 product_id_str)));
287 notification->SetSystemPriority();
288 notification_ui_manager->Add(*notification, profile);
291 } // namespace
293 namespace chromeos {
295 PrinterServiceProvider::PrinterServiceProvider()
296 : notification_ui_manager_(nullptr), weak_ptr_factory_(this) {
299 PrinterServiceProvider::~PrinterServiceProvider() {
302 void PrinterServiceProvider::Start(
303 scoped_refptr<dbus::ExportedObject> exported_object) {
304 exported_object_ = exported_object;
306 DVLOG(1) << "PrinterServiceProvider started";
307 exported_object_->ExportMethod(
308 kLibCrosServiceInterface,
309 kPrinterAdded,
310 base::Bind(&PrinterServiceProvider::PrinterAdded,
311 weak_ptr_factory_.GetWeakPtr()),
312 base::Bind(&PrinterServiceProvider::OnExported,
313 weak_ptr_factory_.GetWeakPtr()));
316 void PrinterServiceProvider::SetNotificationUIManagerForTesting(
317 NotificationUIManager* manager) {
318 notification_ui_manager_ = manager;
321 void PrinterServiceProvider::OnExported(
322 const std::string& interface_name,
323 const std::string& method_name,
324 bool success) {
325 if (!success) {
326 LOG(ERROR) << "Failed to export " << interface_name << "."
327 << method_name;
329 DVLOG(1) << "Method exported: " << interface_name << "." << method_name;
332 void PrinterServiceProvider::ShowCloudPrintHelp(const std::string& vendor,
333 const std::string& product) {
334 content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
335 base::Bind(&FindOrOpenCloudPrintPage, vendor,
336 product));
339 void PrinterServiceProvider::PrinterAdded(
340 dbus::MethodCall* method_call,
341 dbus::ExportedObject::ResponseSender response_sender) {
342 DVLOG(1) << "PrinterAdded " << method_call->ToString();
344 dbus::MessageReader reader(method_call);
346 std::string vendor_id;
347 reader.PopString(&vendor_id);
348 StringToUpperASCII(&vendor_id);
350 std::string product_id;
351 reader.PopString(&product_id);
352 StringToUpperASCII(&product_id);
354 // Send an empty response.
355 response_sender.Run(dbus::Response::FromMethodCall(method_call));
357 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
358 switches::kEnablePrinterAppSearch)) {
359 ShowPrinterPluggedNotification(
360 notification_ui_manager_ ? notification_ui_manager_
361 : g_browser_process->notification_ui_manager(),
362 vendor_id, product_id);
363 return;
366 // Disable showing Cloudprint help on canary and dev channel, as these have
367 // support for printerProvider API.
368 // TODO(tbarzic): Remove this and offer the user to search for an extension
369 // that can act as a print driver (using printerProvider API) for USB printers
370 // detected by this service. http://crbug.com/439448
371 if (base::SysInfo::IsRunningOnChromeOS() &&
372 chrome::VersionInfo::GetChannel() <= chrome::VersionInfo::CHANNEL_DEV)
373 return;
375 ShowCloudPrintHelp(vendor_id, product_id);
378 } // namespace chromeos