Browse Source

fix: persist permission granted to serial ports (#30209)

John Kleinschmidt 3 years ago
parent
commit
461db8f1ab

+ 54 - 0
shell/browser/electron_browser_context.cc

@@ -8,6 +8,8 @@
 
 #include <utility>
 
+#include <vector>
+
 #include "base/barrier_closure.h"
 #include "base/base_paths.h"
 #include "base/command_line.h"
@@ -93,6 +95,9 @@ std::string MakePartitionName(const std::string& input) {
 
 }  // namespace
 
+const char kSerialGrantedDevicesPref[] =
+    "profile.content_settings.exceptions.serial-chooser-data";
+
 // static
 ElectronBrowserContext::BrowserContextMap&
 ElectronBrowserContext::browser_context_map() {
@@ -189,6 +194,7 @@ void ElectronBrowserContext::InitPrefs() {
   registry->RegisterFilePathPref(prefs::kDownloadDefaultDirectory,
                                  download_dir);
   registry->RegisterDictionaryPref(prefs::kDevToolsFileSystemPaths);
+  registry->RegisterDictionaryPref(kSerialGrantedDevicesPref);
   InspectableWebContents::RegisterPrefs(registry.get());
   MediaDeviceIDSalt::RegisterPrefs(registry.get());
   ZoomLevelDelegate::RegisterPrefs(registry.get());
@@ -409,6 +415,54 @@ void ElectronBrowserContext::SetSSLConfigClient(
   ssl_config_client_ = std::move(client);
 }
 
+void ElectronBrowserContext::GrantObjectPermission(
+    const url::Origin& origin,
+    base::Value object,
+    const std::string& pref_key) {
+  std::string origin_string = origin.Serialize();
+  DictionaryPrefUpdate update(prefs(), pref_key);
+  base::Value* const current_objects = update.Get();
+  if (!current_objects || !current_objects->is_dict()) {
+    base::ListValue objects_for_origin;
+    objects_for_origin.Append(std::move(object));
+    base::DictionaryValue objects_by_origin;
+    objects_by_origin.SetPath(origin_string, std::move(objects_for_origin));
+    prefs()->Set(pref_key, std::move(objects_by_origin));
+  } else {
+    base::Value* const objects_mutable =
+        current_objects->FindListKey(origin_string);
+    if (objects_mutable) {
+      base::Value::ListStorage objects = std::move(*objects_mutable).TakeList();
+      objects.push_back(std::move(object));
+      *objects_mutable = base::Value(std::move(objects));
+    } else {
+      base::Value new_objects(base::Value::Type::LIST);
+      new_objects.Append(std::move(object));
+      current_objects->SetKey(origin_string, std::move(new_objects));
+    }
+  }
+}
+
+std::vector<std::unique_ptr<base::Value>>
+ElectronBrowserContext::GetGrantedObjects(const url::Origin& origin,
+                                          const std::string& pref_key) {
+  auto* current_objects = prefs()->Get(pref_key);
+  if (!current_objects || !current_objects->is_dict()) {
+    return {};
+  }
+
+  const base::Value* objects_for_origin =
+      current_objects->FindPath(origin.Serialize());
+  if (!objects_for_origin)
+    return {};
+
+  std::vector<std::unique_ptr<base::Value>> results;
+  for (const auto& object : objects_for_origin->GetList())
+    results.push_back(std::make_unique<base::Value>(object.Clone()));
+
+  return results;
+}
+
 // static
 ElectronBrowserContext* ElectronBrowserContext::From(
     const std::string& partition,

+ 17 - 0
shell/browser/electron_browser_context.h

@@ -8,6 +8,7 @@
 #include <map>
 #include <memory>
 #include <string>
+#include <vector>
 
 #include "base/memory/weak_ptr.h"
 #include "chrome/browser/predictors/preconnect_manager.h"
@@ -45,6 +46,9 @@ class ResolveProxyHelper;
 class WebViewManager;
 class ProtocolRegistry;
 
+// Preference keys for device apis
+extern const char kSerialGrantedDevicesPref[];
+
 class ElectronBrowserContext : public content::BrowserContext {
  public:
   // partition_id => browser_context
@@ -139,6 +143,19 @@ class ElectronBrowserContext : public content::BrowserContext {
   network::mojom::SSLConfigPtr GetSSLConfig();
   void SetSSLConfigClient(mojo::Remote<network::mojom::SSLConfigClient> client);
 
+  // Grants |origin| access to |object| by writing it into the browser context.
+  // To be used in place of ObjectPermissionContextBase::GrantObjectPermission.
+  void GrantObjectPermission(const url::Origin& origin,
+                             base::Value object,
+                             const std::string& pref_key);
+
+  // Returns the list of objects that |origin| has been granted permission to
+  // access. To be used in place of
+  // ObjectPermissionContextBase::GetGrantedObjects.
+  std::vector<std::unique_ptr<base::Value>> GetGrantedObjects(
+      const url::Origin& origin,
+      const std::string& pref_key);
+
   ~ElectronBrowserContext() override;
 
  private:

+ 0 - 1
shell/browser/serial/electron_serial_delegate.cc

@@ -71,7 +71,6 @@ bool ElectronSerialDelegate::HasPortPermission(
   auto* chooser_context =
       SerialChooserContextFactory::GetForBrowserContext(browser_context);
   return chooser_context->HasPortPermission(
-      frame->GetLastCommittedOrigin(),
       web_contents->GetMainFrame()->GetLastCommittedOrigin(), port);
 }
 

+ 52 - 16
shell/browser/serial/serial_chooser_context.cc

@@ -4,6 +4,7 @@
 
 #include "shell/browser/serial/serial_chooser_context.h"
 
+#include <memory>
 #include <string>
 #include <utility>
 
@@ -46,7 +47,7 @@ base::UnguessableToken DecodeToken(base::StringPiece input) {
     return base::UnguessableToken();
   }
 
-  const auto* data = reinterpret_cast<const uint64_t*>(buffer.data());
+  const uint64_t* data = reinterpret_cast<const uint64_t*>(buffer.data());
   return base::UnguessableToken::Deserialize(data[0], data[1]);
 }
 
@@ -82,30 +83,73 @@ base::Value PortInfoToValue(const device::mojom::SerialPortInfo& port) {
   return value;
 }
 
-SerialChooserContext::SerialChooserContext() = default;
+SerialChooserContext::SerialChooserContext(
+    ElectronBrowserContext* browser_context)
+    : browser_context_(browser_context) {}
+
 SerialChooserContext::~SerialChooserContext() = default;
 
 void SerialChooserContext::GrantPortPermission(
-    const url::Origin& requesting_origin,
-    const url::Origin& embedding_origin,
+    const url::Origin& origin,
     const device::mojom::SerialPortInfo& port) {
   base::Value value = PortInfoToValue(port);
   port_info_.insert({port.token, value.Clone()});
 
-  ephemeral_ports_[{requesting_origin, embedding_origin}].insert(port.token);
+  if (CanStorePersistentEntry(port)) {
+    browser_context_->GrantObjectPermission(origin, std::move(value),
+                                            kSerialGrantedDevicesPref);
+    return;
+  }
+
+  ephemeral_ports_[origin].insert(port.token);
 }
 
 bool SerialChooserContext::HasPortPermission(
-    const url::Origin& requesting_origin,
-    const url::Origin& embedding_origin,
+    const url::Origin& origin,
     const device::mojom::SerialPortInfo& port) {
-  auto it = ephemeral_ports_.find({requesting_origin, embedding_origin});
+  auto it = ephemeral_ports_.find(origin);
   if (it != ephemeral_ports_.end()) {
     const std::set<base::UnguessableToken> ports = it->second;
     if (base::Contains(ports, port.token))
       return true;
   }
 
+  if (!CanStorePersistentEntry(port)) {
+    return false;
+  }
+
+  std::vector<std::unique_ptr<base::Value>> object_list =
+      browser_context_->GetGrantedObjects(origin, kSerialGrantedDevicesPref);
+  for (const auto& device : object_list) {
+#if defined(OS_WIN)
+    const std::string& device_instance_id =
+        *device->FindStringKey(kDeviceInstanceIdKey);
+    if (port.device_instance_id == device_instance_id)
+      return true;
+#else
+    const int vendor_id = *device->FindIntKey(kVendorIdKey);
+    const int product_id = *device->FindIntKey(kProductIdKey);
+    const std::string& serial_number = *device->FindStringKey(kSerialNumberKey);
+
+    // Guaranteed by the CanStorePersistentEntry) check above.
+    DCHECK(port.has_vendor_id);
+    DCHECK(port.has_product_id);
+    DCHECK(port.serial_number && !port.serial_number->empty());
+    if (port.vendor_id != vendor_id || port.product_id != product_id ||
+        port.serial_number != serial_number) {
+      continue;
+    }
+
+#if defined(OS_MAC)
+    const std::string& usb_driver_name = *device->FindStringKey(kUsbDriverKey);
+    if (port.usb_driver_name != usb_driver_name) {
+      continue;
+    }
+#endif  // defined(OS_MAC)
+
+    return true;
+#endif  // defined(OS_WIN)
+  }
   return false;
 }
 
@@ -170,14 +214,6 @@ void SerialChooserContext::OnPortRemoved(
   for (auto& observer : port_observer_list_)
     observer.OnPortRemoved(*port);
 
-  std::vector<std::pair<url::Origin, url::Origin>> revoked_url_pairs;
-  for (auto& map_entry : ephemeral_ports_) {
-    std::set<base::UnguessableToken>& ports = map_entry.second;
-    if (ports.erase(port->token) > 0) {
-      revoked_url_pairs.push_back(map_entry.first);
-    }
-  }
-
   port_info_.erase(port->token);
 }
 

+ 9 - 15
shell/browser/serial/serial_chooser_context.h

@@ -18,6 +18,7 @@
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/remote.h"
 #include "services/device/public/mojom/serial.mojom-forward.h"
+#include "shell/browser/electron_browser_context.h"
 #include "third_party/blink/public/mojom/serial/serial.mojom.h"
 #include "url/gurl.h"
 #include "url/origin.h"
@@ -33,15 +34,13 @@ class SerialChooserContext : public KeyedService,
  public:
   using PortObserver = content::SerialDelegate::Observer;
 
-  SerialChooserContext();
+  explicit SerialChooserContext(ElectronBrowserContext* browser_context);
   ~SerialChooserContext() override;
 
   // Serial-specific interface for granting and checking permissions.
-  void GrantPortPermission(const url::Origin& requesting_origin,
-                           const url::Origin& embedding_origin,
+  void GrantPortPermission(const url::Origin& origin,
                            const device::mojom::SerialPortInfo& port);
-  bool HasPortPermission(const url::Origin& requesting_origin,
-                         const url::Origin& embedding_origin,
+  bool HasPortPermission(const url::Origin& origin,
                          const device::mojom::SerialPortInfo& port);
   static bool CanStorePersistentEntry(
       const device::mojom::SerialPortInfo& port);
@@ -62,16 +61,11 @@ class SerialChooserContext : public KeyedService,
   void SetUpPortManagerConnection(
       mojo::PendingRemote<device::mojom::SerialPortManager> manager);
   void OnPortManagerConnectionError();
-  void OnGetPorts(const url::Origin& requesting_origin,
-                  const url::Origin& embedding_origin,
-                  blink::mojom::SerialService::GetPortsCallback callback,
-                  std::vector<device::mojom::SerialPortInfoPtr> ports);
-
-  // Tracks the set of ports to which an origin (potentially embedded in another
-  // origin) has access to. Key is (requesting_origin, embedding_origin).
-  std::map<std::pair<url::Origin, url::Origin>,
-           std::set<base::UnguessableToken>>
-      ephemeral_ports_;
+
+  ElectronBrowserContext* browser_context_;
+
+  // Tracks the set of ports to which an origin has access to.
+  std::map<url::Origin, std::set<base::UnguessableToken>> ephemeral_ports_;
 
   // Holds information about ports in |ephemeral_ports_|.
   std::map<base::UnguessableToken, base::Value> port_info_;

+ 4 - 1
shell/browser/serial/serial_chooser_context_factory.cc

@@ -5,6 +5,7 @@
 #include "shell/browser/serial/serial_chooser_context_factory.h"
 
 #include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "shell/browser/electron_browser_context.h"
 #include "shell/browser/serial/serial_chooser_context.h"
 
 namespace electron {
@@ -18,7 +19,9 @@ SerialChooserContextFactory::~SerialChooserContextFactory() = default;
 
 KeyedService* SerialChooserContextFactory::BuildServiceInstanceFor(
     content::BrowserContext* context) const {
-  return new SerialChooserContext();
+  auto* browser_context =
+      static_cast<electron::ElectronBrowserContext*>(context);
+  return new SerialChooserContext(browser_context);
 }
 
 // static

+ 2 - 4
shell/browser/serial/serial_chooser_controller.cc

@@ -68,8 +68,7 @@ SerialChooserController::SerialChooserController(
       filters_(std::move(filters)),
       callback_(std::move(callback)),
       serial_delegate_(serial_delegate) {
-  requesting_origin_ = render_frame_host->GetLastCommittedOrigin();
-  embedding_origin_ = web_contents->GetMainFrame()->GetLastCommittedOrigin();
+  origin_ = web_contents->GetMainFrame()->GetLastCommittedOrigin();
 
   chooser_context_ = SerialChooserContextFactory::GetForBrowserContext(
                          web_contents->GetBrowserContext())
@@ -125,8 +124,7 @@ void SerialChooserController::OnDeviceChosen(const std::string& port_id) {
           return ptr->token.ToString() == port_id;
         });
     if (it != ports_.end()) {
-      chooser_context_->GrantPortPermission(requesting_origin_,
-                                            embedding_origin_, *it->get());
+      chooser_context_->GrantPortPermission(origin_, *it->get());
       RunCallback(it->Clone());
     } else {
       RunCallback(/*port=*/nullptr);

+ 1 - 2
shell/browser/serial/serial_chooser_controller.h

@@ -53,8 +53,7 @@ class SerialChooserController final : public SerialChooserContext::PortObserver,
 
   std::vector<blink::mojom::SerialPortFilterPtr> filters_;
   content::SerialChooser::Callback callback_;
-  url::Origin requesting_origin_;
-  url::Origin embedding_origin_;
+  url::Origin origin_;
 
   base::WeakPtr<SerialChooserContext> chooser_context_;