electron_usb_delegate.cc 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. // Copyright (c) 2022 Microsoft, Inc.
  2. // Use of this source code is governed by the MIT license that can be
  3. // found in the LICENSE file.
  4. #include "shell/browser/usb/electron_usb_delegate.h"
  5. #include <utility>
  6. #include "base/containers/contains.h"
  7. #include "base/containers/cxx20_erase.h"
  8. #include "base/observer_list.h"
  9. #include "base/observer_list_types.h"
  10. #include "base/scoped_observation.h"
  11. #include "content/public/browser/render_frame_host.h"
  12. #include "content/public/browser/web_contents.h"
  13. #include "extensions/buildflags/buildflags.h"
  14. #include "services/device/public/mojom/usb_enumeration_options.mojom.h"
  15. #include "shell/browser/electron_permission_manager.h"
  16. #include "shell/browser/usb/usb_chooser_context.h"
  17. #include "shell/browser/usb/usb_chooser_context_factory.h"
  18. #include "shell/browser/usb/usb_chooser_controller.h"
  19. #include "shell/browser/web_contents_permission_helper.h"
  20. #if BUILDFLAG(ENABLE_EXTENSIONS)
  21. #include "base/containers/fixed_flat_set.h"
  22. #include "chrome/common/chrome_features.h"
  23. #include "extensions/browser/extension_registry.h"
  24. #include "extensions/browser/guest_view/web_view/web_view_guest.h"
  25. #include "extensions/common/constants.h"
  26. #include "extensions/common/extension.h"
  27. #include "services/device/public/mojom/usb_device.mojom.h"
  28. #endif
  29. namespace {
  30. using ::content::UsbChooser;
  31. electron::UsbChooserContext* GetChooserContext(
  32. content::BrowserContext* browser_context) {
  33. return electron::UsbChooserContextFactory::GetForBrowserContext(
  34. browser_context);
  35. }
  36. #if BUILDFLAG(ENABLE_EXTENSIONS)
  37. // These extensions can claim the smart card USB class and automatically gain
  38. // permissions for devices that have an interface with this class.
  39. constexpr auto kSmartCardPrivilegedExtensionIds =
  40. base::MakeFixedFlatSet<base::StringPiece>({
  41. // Smart Card Connector Extension and its Beta version, see
  42. // crbug.com/1233881.
  43. "khpfeaanjngmcnplbdlpegiifgpfgdco",
  44. "mockcojkppdndnhgonljagclgpkjbkek",
  45. });
  46. bool DeviceHasInterfaceWithClass(
  47. const device::mojom::UsbDeviceInfo& device_info,
  48. uint8_t interface_class) {
  49. for (const auto& configuration : device_info.configurations) {
  50. for (const auto& interface : configuration->interfaces) {
  51. for (const auto& alternate : interface->alternates) {
  52. if (alternate->class_code == interface_class)
  53. return true;
  54. }
  55. }
  56. }
  57. return false;
  58. }
  59. #endif // BUILDFLAG(ENABLE_EXTENSIONS)
  60. bool IsDevicePermissionAutoGranted(
  61. const url::Origin& origin,
  62. const device::mojom::UsbDeviceInfo& device_info) {
  63. #if BUILDFLAG(ENABLE_EXTENSIONS)
  64. // Note: The `DeviceHasInterfaceWithClass()` call is made after checking the
  65. // origin, since that method call is expensive.
  66. if (origin.scheme() == extensions::kExtensionScheme &&
  67. base::Contains(kSmartCardPrivilegedExtensionIds, origin.host()) &&
  68. DeviceHasInterfaceWithClass(device_info,
  69. device::mojom::kUsbSmartCardClass)) {
  70. return true;
  71. }
  72. #endif // BUILDFLAG(ENABLE_EXTENSIONS)
  73. return false;
  74. }
  75. } // namespace
  76. namespace electron {
  77. // Manages the UsbDelegate observers for a single browser context.
  78. class ElectronUsbDelegate::ContextObservation
  79. : public UsbChooserContext::DeviceObserver {
  80. public:
  81. ContextObservation(ElectronUsbDelegate* parent,
  82. content::BrowserContext* browser_context)
  83. : parent_(parent), browser_context_(browser_context) {
  84. auto* chooser_context = GetChooserContext(browser_context_);
  85. device_observation_.Observe(chooser_context);
  86. }
  87. ContextObservation(ContextObservation&) = delete;
  88. ContextObservation& operator=(ContextObservation&) = delete;
  89. ~ContextObservation() override = default;
  90. // UsbChooserContext::DeviceObserver:
  91. void OnDeviceAdded(const device::mojom::UsbDeviceInfo& device_info) override {
  92. for (auto& observer : observer_list_)
  93. observer.OnDeviceAdded(device_info);
  94. }
  95. void OnDeviceRemoved(
  96. const device::mojom::UsbDeviceInfo& device_info) override {
  97. for (auto& observer : observer_list_)
  98. observer.OnDeviceRemoved(device_info);
  99. }
  100. void OnDeviceManagerConnectionError() override {
  101. for (auto& observer : observer_list_)
  102. observer.OnDeviceManagerConnectionError();
  103. }
  104. void OnBrowserContextShutdown() override {
  105. parent_->observations_.erase(browser_context_);
  106. // Return since `this` is now deleted.
  107. }
  108. void AddObserver(content::UsbDelegate::Observer* observer) {
  109. observer_list_.AddObserver(observer);
  110. }
  111. void RemoveObserver(content::UsbDelegate::Observer* observer) {
  112. observer_list_.RemoveObserver(observer);
  113. }
  114. private:
  115. // Safe because `parent_` owns `this`.
  116. const raw_ptr<ElectronUsbDelegate> parent_;
  117. // Safe because `this` is destroyed when the context is lost.
  118. const raw_ptr<content::BrowserContext> browser_context_;
  119. base::ScopedObservation<UsbChooserContext, UsbChooserContext::DeviceObserver>
  120. device_observation_{this};
  121. base::ObserverList<content::UsbDelegate::Observer> observer_list_;
  122. };
  123. ElectronUsbDelegate::ElectronUsbDelegate() = default;
  124. ElectronUsbDelegate::~ElectronUsbDelegate() = default;
  125. void ElectronUsbDelegate::AdjustProtectedInterfaceClasses(
  126. content::BrowserContext* browser_context,
  127. const url::Origin& origin,
  128. content::RenderFrameHost* frame,
  129. std::vector<uint8_t>& classes) {
  130. auto* permission_manager = static_cast<ElectronPermissionManager*>(
  131. browser_context->GetPermissionControllerDelegate());
  132. classes = permission_manager->CheckProtectedUSBClasses(classes);
  133. }
  134. std::unique_ptr<UsbChooser> ElectronUsbDelegate::RunChooser(
  135. content::RenderFrameHost& frame,
  136. blink::mojom::WebUsbRequestDeviceOptionsPtr options,
  137. blink::mojom::WebUsbService::GetPermissionCallback callback) {
  138. UsbChooserController* controller = ControllerForFrame(&frame);
  139. if (controller) {
  140. DeleteControllerForFrame(&frame);
  141. }
  142. AddControllerForFrame(&frame, std::move(options), std::move(callback));
  143. // Return a nullptr because the return value isn't used for anything. The
  144. // return value is simply used in Chromium to cleanup the chooser UI once the
  145. // usb service is destroyed.
  146. return nullptr;
  147. }
  148. bool ElectronUsbDelegate::CanRequestDevicePermission(
  149. content::BrowserContext* browser_context,
  150. const url::Origin& origin) {
  151. base::Value::Dict details;
  152. details.Set("securityOrigin", origin.GetURL().spec());
  153. auto* permission_manager = static_cast<ElectronPermissionManager*>(
  154. browser_context->GetPermissionControllerDelegate());
  155. return permission_manager->CheckPermissionWithDetails(
  156. static_cast<blink::PermissionType>(
  157. WebContentsPermissionHelper::PermissionType::USB),
  158. nullptr, origin.GetURL(), std::move(details));
  159. }
  160. void ElectronUsbDelegate::RevokeDevicePermissionWebInitiated(
  161. content::BrowserContext* browser_context,
  162. const url::Origin& origin,
  163. const device::mojom::UsbDeviceInfo& device) {
  164. GetChooserContext(browser_context)
  165. ->RevokeDevicePermissionWebInitiated(origin, device);
  166. }
  167. const device::mojom::UsbDeviceInfo* ElectronUsbDelegate::GetDeviceInfo(
  168. content::BrowserContext* browser_context,
  169. const std::string& guid) {
  170. return GetChooserContext(browser_context)->GetDeviceInfo(guid);
  171. }
  172. bool ElectronUsbDelegate::HasDevicePermission(
  173. content::BrowserContext* browser_context,
  174. const url::Origin& origin,
  175. const device::mojom::UsbDeviceInfo& device) {
  176. if (IsDevicePermissionAutoGranted(origin, device))
  177. return true;
  178. return GetChooserContext(browser_context)
  179. ->HasDevicePermission(origin, device);
  180. }
  181. void ElectronUsbDelegate::GetDevices(
  182. content::BrowserContext* browser_context,
  183. blink::mojom::WebUsbService::GetDevicesCallback callback) {
  184. GetChooserContext(browser_context)->GetDevices(std::move(callback));
  185. }
  186. void ElectronUsbDelegate::GetDevice(
  187. content::BrowserContext* browser_context,
  188. const std::string& guid,
  189. base::span<const uint8_t> blocked_interface_classes,
  190. mojo::PendingReceiver<device::mojom::UsbDevice> device_receiver,
  191. mojo::PendingRemote<device::mojom::UsbDeviceClient> device_client) {
  192. GetChooserContext(browser_context)
  193. ->GetDevice(guid, blocked_interface_classes, std::move(device_receiver),
  194. std::move(device_client));
  195. }
  196. void ElectronUsbDelegate::AddObserver(content::BrowserContext* browser_context,
  197. Observer* observer) {
  198. GetContextObserver(browser_context)->AddObserver(observer);
  199. }
  200. void ElectronUsbDelegate::RemoveObserver(
  201. content::BrowserContext* browser_context,
  202. Observer* observer) {
  203. GetContextObserver(browser_context)->RemoveObserver(observer);
  204. }
  205. ElectronUsbDelegate::ContextObservation*
  206. ElectronUsbDelegate::GetContextObserver(
  207. content::BrowserContext* browser_context) {
  208. if (!base::Contains(observations_, browser_context)) {
  209. observations_.emplace(browser_context, std::make_unique<ContextObservation>(
  210. this, browser_context));
  211. }
  212. return observations_[browser_context].get();
  213. }
  214. bool ElectronUsbDelegate::IsServiceWorkerAllowedForOrigin(
  215. const url::Origin& origin) {
  216. #if BUILDFLAG(ENABLE_EXTENSIONS)
  217. // WebUSB is only available on extension service workers for now.
  218. if (base::FeatureList::IsEnabled(
  219. features::kEnableWebUsbOnExtensionServiceWorker) &&
  220. origin.scheme() == extensions::kExtensionScheme) {
  221. return true;
  222. }
  223. #endif // BUILDFLAG(ENABLE_EXTENSIONS)
  224. return false;
  225. }
  226. UsbChooserController* ElectronUsbDelegate::ControllerForFrame(
  227. content::RenderFrameHost* render_frame_host) {
  228. auto mapping = controller_map_.find(render_frame_host);
  229. return mapping == controller_map_.end() ? nullptr : mapping->second.get();
  230. }
  231. UsbChooserController* ElectronUsbDelegate::AddControllerForFrame(
  232. content::RenderFrameHost* render_frame_host,
  233. blink::mojom::WebUsbRequestDeviceOptionsPtr options,
  234. blink::mojom::WebUsbService::GetPermissionCallback callback) {
  235. auto* web_contents =
  236. content::WebContents::FromRenderFrameHost(render_frame_host);
  237. auto controller = std::make_unique<UsbChooserController>(
  238. render_frame_host, std::move(options), std::move(callback), web_contents,
  239. weak_factory_.GetWeakPtr());
  240. controller_map_.insert(
  241. std::make_pair(render_frame_host, std::move(controller)));
  242. return ControllerForFrame(render_frame_host);
  243. }
  244. void ElectronUsbDelegate::DeleteControllerForFrame(
  245. content::RenderFrameHost* render_frame_host) {
  246. controller_map_.erase(render_frame_host);
  247. }
  248. bool ElectronUsbDelegate::PageMayUseUsb(content::Page& page) {
  249. content::RenderFrameHost& main_rfh = page.GetMainDocument();
  250. #if BUILDFLAG(ENABLE_EXTENSIONS)
  251. // WebViewGuests have no mechanism to show permission prompts and their
  252. // embedder can't grant USB access through its permissionrequest API. Also
  253. // since webviews use a separate StoragePartition, they must not gain access
  254. // through permissions granted in non-webview contexts.
  255. if (extensions::WebViewGuest::FromRenderFrameHost(&main_rfh)) {
  256. return false;
  257. }
  258. #endif // BUILDFLAG(ENABLE_EXTENSIONS)
  259. // USB permissions are scoped to a BrowserContext instead of a
  260. // StoragePartition, so we need to be careful about usage across
  261. // StoragePartitions. Until this is scoped correctly, we'll try to avoid
  262. // inappropriate sharing by restricting access to the API. We can't be as
  263. // strict as we'd like, as cases like extensions and Isolated Web Apps still
  264. // need USB access in non-default partitions, so we'll just guard against
  265. // HTTP(S) as that presents a clear risk for inappropriate sharing.
  266. // TODO(crbug.com/1469672): USB permissions should be explicitly scoped to
  267. // StoragePartitions.
  268. if (main_rfh.GetStoragePartition() !=
  269. main_rfh.GetBrowserContext()->GetDefaultStoragePartition()) {
  270. return !main_rfh.GetLastCommittedURL().SchemeIsHTTPOrHTTPS();
  271. }
  272. return true;
  273. }
  274. } // namespace electron