Browse Source

feat: add additional remote APIs filtering (#16293)

Milan Burda 6 years ago
parent
commit
349a3c20ae

+ 46 - 0
docs/api/app.md

@@ -435,6 +435,52 @@ Emitted when `remote.getGlobal()` is called in the renderer process of `webConte
 Calling `event.preventDefault()` will prevent the global from being returned.
 Custom value can be returned by setting `event.returnValue`.
 
+### Event: 'remote-get-builtin'
+
+Returns:
+
+* `event` Event
+* `webContents` [WebContents](web-contents.md)
+* `moduleName` String
+
+Emitted when `remote.getBuiltin()` is called in the renderer process of `webContents`.
+Calling `event.preventDefault()` will prevent the module from being returned.
+Custom value can be returned by setting `event.returnValue`.
+
+### Event: 'remote-get-current-window'
+
+Returns:
+
+* `event` Event
+* `webContents` [WebContents](web-contents.md)
+
+Emitted when `remote.getCurrentWindow()` is called in the renderer process of `webContents`.
+Calling `event.preventDefault()` will prevent the object from being returned.
+Custom value can be returned by setting `event.returnValue`.
+
+### Event: 'remote-get-current-web-contents'
+
+Returns:
+
+* `event` Event
+* `webContents` [WebContents](web-contents.md)
+
+Emitted when `remote.getCurrentWebContents()` is called in the renderer process of `webContents`.
+Calling `event.preventDefault()` will prevent the object from being returned.
+Custom value can be returned by setting `event.returnValue`.
+
+### Event: 'remote-get-guest-web-contents'
+
+Returns:
+
+* `event` Event
+* `webContents` [WebContents](web-contents.md)
+* `guestWebContents` [WebContents](web-contents.md)
+
+Emitted when `<webview>.getWebContents()` is called in the renderer process of `webContents`.
+Calling `event.preventDefault()` will prevent the object from being returned.
+Custom value can be returned by setting `event.returnValue`.
+
 ## Methods
 
 The `app` object has the following methods:

+ 42 - 0
docs/api/web-contents.md

@@ -694,6 +694,48 @@ Emitted when `remote.getGlobal()` is called in the renderer process.
 Calling `event.preventDefault()` will prevent the global from being returned.
 Custom value can be returned by setting `event.returnValue`.
 
+#### Event: 'remote-get-builtin'
+
+Returns:
+
+* `event` Event
+* `moduleName` String
+
+Emitted when `remote.getBuiltin()` is called in the renderer process.
+Calling `event.preventDefault()` will prevent the module from being returned.
+Custom value can be returned by setting `event.returnValue`.
+
+#### Event: 'remote-get-current-window'
+
+Returns:
+
+* `event` Event
+
+Emitted when `remote.getCurrentWindow()` is called in the renderer process.
+Calling `event.preventDefault()` will prevent the object from being returned.
+Custom value can be returned by setting `event.returnValue`.
+
+#### Event: 'remote-get-current-web-contents'
+
+Returns:
+
+* `event` Event
+
+Emitted when `remote.getCurrentWebContents()` is called in the renderer process.
+Calling `event.preventDefault()` will prevent the object from being returned.
+Custom value can be returned by setting `event.returnValue`.
+
+#### Event: 'remote-get-guest-web-contents'
+
+Returns:
+
+* `event` Event
+* `guestWebContents` [WebContents](web-contents.md)
+
+Emitted when `<webview>.getWebContents()` is called in the renderer process.
+Calling `event.preventDefault()` will prevent the object from being returned.
+Custom value can be returned by setting `event.returnValue`.
+
 ### Instance Methods
 
 #### `contents.loadURL(url[, options])`

+ 15 - 11
lib/browser/api/web-contents.js

@@ -377,17 +377,21 @@ WebContents.prototype._init = function () {
     })
   })
 
-  this.on('desktop-capturer-get-sources', (event, ...args) => {
-    app.emit('desktop-capturer-get-sources', event, this, ...args)
-  })
-
-  this.on('remote-require', (event, ...args) => {
-    app.emit('remote-require', event, this, ...args)
-  })
-
-  this.on('remote-get-global', (event, ...args) => {
-    app.emit('remote-get-global', event, this, ...args)
-  })
+  const forwardedEvents = [
+    'desktop-capturer-get-sources',
+    'remote-require',
+    'remote-get-global',
+    'remote-get-builtin',
+    'remote-get-current-window',
+    'remote-get-current-web-contents',
+    'remote-get-guest-web-contents'
+  ]
+
+  for (const eventName of forwardedEvents) {
+    this.on(eventName, (event, ...args) => {
+      app.emit(eventName, event, this, ...args)
+    })
+  }
 
   deprecate.event(this, 'did-get-response-details', '-did-get-response-details')
   deprecate.event(this, 'did-get-redirect-request', '-did-get-redirect-request')

+ 60 - 15
lib/browser/rpc-server.js

@@ -298,42 +298,75 @@ handleRemoteCommand('ELECTRON_BROWSER_REQUIRE', function (event, contextId, modu
   const customEvent = eventBinding.createWithSender(event.sender)
   event.sender.emit('remote-require', customEvent, moduleName)
 
-  if (customEvent.defaultPrevented) {
-    if (typeof customEvent.returnValue === 'undefined') {
-      throw new Error(`Invalid module: ${moduleName}`)
+  if (customEvent.returnValue === undefined) {
+    if (customEvent.defaultPrevented) {
+      throw new Error(`Blocked remote.require('${moduleName}')`)
+    } else {
+      customEvent.returnValue = process.mainModule.require(moduleName)
     }
-  } else {
-    customEvent.returnValue = process.mainModule.require(moduleName)
   }
 
   return valueToMeta(event.sender, contextId, customEvent.returnValue)
 })
 
-handleRemoteCommand('ELECTRON_BROWSER_GET_BUILTIN', function (event, contextId, module) {
-  return valueToMeta(event.sender, contextId, electron[module])
+handleRemoteCommand('ELECTRON_BROWSER_GET_BUILTIN', function (event, contextId, moduleName) {
+  const customEvent = eventBinding.createWithSender(event.sender)
+  event.sender.emit('remote-get-builtin', customEvent, moduleName)
+
+  if (customEvent.returnValue === undefined) {
+    if (customEvent.defaultPrevented) {
+      throw new Error(`Blocked remote.getBuiltin('${moduleName}')`)
+    } else {
+      customEvent.returnValue = electron[moduleName]
+    }
+  }
+
+  return valueToMeta(event.sender, contextId, customEvent.returnValue)
 })
 
 handleRemoteCommand('ELECTRON_BROWSER_GLOBAL', function (event, contextId, globalName) {
   const customEvent = eventBinding.createWithSender(event.sender)
   event.sender.emit('remote-get-global', customEvent, globalName)
 
-  if (customEvent.defaultPrevented) {
-    if (typeof customEvent.returnValue === 'undefined') {
-      throw new Error(`Invalid global: ${globalName}`)
+  if (customEvent.returnValue === undefined) {
+    if (customEvent.defaultPrevented) {
+      throw new Error(`Blocked remote.getGlobal('${globalName}')`)
+    } else {
+      customEvent.returnValue = global[globalName]
     }
-  } else {
-    customEvent.returnValue = global[globalName]
   }
 
   return valueToMeta(event.sender, contextId, customEvent.returnValue)
 })
 
 handleRemoteCommand('ELECTRON_BROWSER_CURRENT_WINDOW', function (event, contextId) {
-  return valueToMeta(event.sender, contextId, event.sender.getOwnerBrowserWindow())
+  const customEvent = eventBinding.createWithSender(event.sender)
+  event.sender.emit('remote-get-current-window', customEvent)
+
+  if (customEvent.returnValue === undefined) {
+    if (customEvent.defaultPrevented) {
+      throw new Error('Blocked remote.getCurrentWindow()')
+    } else {
+      customEvent.returnValue = event.sender.getOwnerBrowserWindow()
+    }
+  }
+
+  return valueToMeta(event.sender, contextId, customEvent.returnValue)
 })
 
 handleRemoteCommand('ELECTRON_BROWSER_CURRENT_WEB_CONTENTS', function (event, contextId) {
-  return valueToMeta(event.sender, contextId, event.sender)
+  const customEvent = eventBinding.createWithSender(event.sender)
+  event.sender.emit('remote-get-current-web-contents', customEvent)
+
+  if (customEvent.returnValue === undefined) {
+    if (customEvent.defaultPrevented) {
+      throw new Error('Blocked remote.getCurrentWebContents()')
+    } else {
+      customEvent.returnValue = event.sender
+    }
+  }
+
+  return valueToMeta(event.sender, contextId, customEvent.returnValue)
 })
 
 handleRemoteCommand('ELECTRON_BROWSER_CONSTRUCTOR', function (event, contextId, id, args) {
@@ -413,7 +446,19 @@ handleRemoteCommand('ELECTRON_BROWSER_CONTEXT_RELEASE', (event, contextId) => {
 
 handleRemoteCommand('ELECTRON_BROWSER_GUEST_WEB_CONTENTS', function (event, contextId, guestInstanceId) {
   const guest = guestViewManager.getGuestForWebContents(guestInstanceId, event.sender)
-  return valueToMeta(event.sender, contextId, guest)
+
+  const customEvent = eventBinding.createWithSender(event.sender)
+  event.sender.emit('remote-get-guest-web-contents', customEvent, guest)
+
+  if (customEvent.returnValue === undefined) {
+    if (customEvent.defaultPrevented) {
+      throw new Error(`Blocked remote.getGuestForWebContents()`)
+    } else {
+      customEvent.returnValue = guest
+    }
+  }
+
+  return valueToMeta(event.sender, contextId, customEvent.returnValue)
 })
 
 // Implements window.close()

+ 72 - 20
spec/api-app-spec.js

@@ -379,51 +379,103 @@ describe('app module', () => {
       w = new BrowserWindow({ show: false })
     })
 
-    it('should emit desktop-capturer-get-sources event when desktopCapturer.getSources() is invoked', (done) => {
-      app.once('desktop-capturer-get-sources', (event, webContents) => {
-        expect(webContents).to.equal(w.webContents)
-        done()
-      })
+    it('should emit desktop-capturer-get-sources event when desktopCapturer.getSources() is invoked', async () => {
       w = new BrowserWindow({
         show: false,
         webPreferences: {
           nodeIntegration: true
         }
       })
-      w.loadURL('about:blank')
+      await w.loadURL('about:blank')
+
+      const promise = emittedOnce(app, 'desktop-capturer-get-sources')
       w.webContents.executeJavaScript(`require('electron').desktopCapturer.getSources({ types: ['screen'] }, () => {})`)
+
+      const [, webContents] = await promise
+      expect(webContents).to.equal(w.webContents)
     })
 
-    it('should emit remote-require event when remote.require() is invoked', (done) => {
-      app.once('remote-require', (event, webContents, moduleName) => {
-        expect(webContents).to.equal(w.webContents)
-        expect(moduleName).to.equal('test')
-        done()
-      })
+    it('should emit remote-require event when remote.require() is invoked', async () => {
       w = new BrowserWindow({
         show: false,
         webPreferences: {
           nodeIntegration: true
         }
       })
-      w.loadURL('about:blank')
+      await w.loadURL('about:blank')
+
+      const promise = emittedOnce(app, 'remote-require')
       w.webContents.executeJavaScript(`require('electron').remote.require('test')`)
+
+      const [, webContents, moduleName] = await promise
+      expect(webContents).to.equal(w.webContents)
+      expect(moduleName).to.equal('test')
     })
 
-    it('should emit remote-get-global event when remote.getGlobal() is invoked', (done) => {
-      app.once('remote-get-global', (event, webContents, globalName) => {
-        expect(webContents).to.equal(w.webContents)
-        expect(globalName).to.equal('test')
-        done()
-      })
+    it('should emit remote-get-global event when remote.getGlobal() is invoked', async () => {
       w = new BrowserWindow({
         show: false,
         webPreferences: {
           nodeIntegration: true
         }
       })
-      w.loadURL('about:blank')
+      await w.loadURL('about:blank')
+
+      const promise = emittedOnce(app, 'remote-get-global')
       w.webContents.executeJavaScript(`require('electron').remote.getGlobal('test')`)
+
+      const [, webContents, globalName] = await promise
+      expect(webContents).to.equal(w.webContents)
+      expect(globalName).to.equal('test')
+    })
+
+    it('should emit remote-get-builtin event when remote.getBuiltin() is invoked', async () => {
+      w = new BrowserWindow({
+        show: false,
+        webPreferences: {
+          nodeIntegration: true
+        }
+      })
+      await w.loadURL('about:blank')
+
+      const promise = emittedOnce(app, 'remote-get-builtin')
+      w.webContents.executeJavaScript(`require('electron').remote.app`)
+
+      const [, webContents, moduleName] = await promise
+      expect(webContents).to.equal(w.webContents)
+      expect(moduleName).to.equal('app')
+    })
+
+    it('should emit remote-get-current-window event when remote.getCurrentWindow() is invoked', async () => {
+      w = new BrowserWindow({
+        show: false,
+        webPreferences: {
+          nodeIntegration: true
+        }
+      })
+      await w.loadURL('about:blank')
+
+      const promise = emittedOnce(app, 'remote-get-current-window')
+      w.webContents.executeJavaScript(`require('electron').remote.getCurrentWindow()`)
+
+      const [, webContents] = await promise
+      expect(webContents).to.equal(w.webContents)
+    })
+
+    it('should emit remote-get-current-web-contents event when remote.getCurrentWebContents() is invoked', async () => {
+      w = new BrowserWindow({
+        show: false,
+        webPreferences: {
+          nodeIntegration: true
+        }
+      })
+      await w.loadURL('about:blank')
+
+      const promise = emittedOnce(app, 'remote-get-current-web-contents')
+      w.webContents.executeJavaScript(`require('electron').remote.getCurrentWebContents()`)
+
+      const [, webContents] = await promise
+      expect(webContents).to.equal(w.webContents)
     })
   })
 

+ 42 - 4
spec/api-remote-spec.js

@@ -28,7 +28,7 @@ describe('remote module', () => {
 
   afterEach(() => closeWindow(w).then(() => { w = null }))
 
-  describe('remote.getGlobal', () => {
+  describe('remote.getGlobal filtering', () => {
     it('can return custom values', () => {
       ipcRenderer.send('handle-next-remote-get-global', { test: 'Hello World!' })
       expect(remote.getGlobal('test')).to.be.equal('Hello World!')
@@ -36,11 +36,23 @@ describe('remote module', () => {
 
     it('throws when no returnValue set', () => {
       ipcRenderer.send('handle-next-remote-get-global')
-      expect(() => remote.getGlobal('process')).to.throw('Invalid global: process')
+      expect(() => remote.getGlobal('test')).to.throw(`Blocked remote.getGlobal('test')`)
     })
   })
 
-  describe('remote.require', () => {
+  describe('remote.getBuiltin filtering', () => {
+    it('can return custom values', () => {
+      ipcRenderer.send('handle-next-remote-get-builtin', { test: 'Hello World!' })
+      expect(remote.getBuiltin('test')).to.be.equal('Hello World!')
+    })
+
+    it('throws when no returnValue set', () => {
+      ipcRenderer.send('handle-next-remote-get-builtin')
+      expect(() => remote.getBuiltin('test')).to.throw(`Blocked remote.getBuiltin('test')`)
+    })
+  })
+
+  describe('remote.require filtering', () => {
     it('can return custom values', () => {
       ipcRenderer.send('handle-next-remote-require', { test: 'Hello World!' })
       expect(remote.require('test')).to.be.equal('Hello World!')
@@ -48,9 +60,11 @@ describe('remote module', () => {
 
     it('throws when no returnValue set', () => {
       ipcRenderer.send('handle-next-remote-require')
-      expect(() => remote.require('electron')).to.throw('Invalid module: electron')
+      expect(() => remote.require('test')).to.throw(`Blocked remote.require('test')`)
     })
+  })
 
+  describe('remote.require', () => {
     it('should returns same object for the same module', () => {
       const dialog1 = remote.require('electron')
       const dialog2 = remote.require('electron')
@@ -451,6 +465,30 @@ describe('remote module', () => {
     })
   })
 
+  describe('remote.getCurrentWindow filtering', () => {
+    it('can return custom value', () => {
+      ipcRenderer.send('handle-next-remote-get-current-window', 'Hello World!')
+      expect(remote.getCurrentWindow()).to.be.equal('Hello World!')
+    })
+
+    it('throws when no returnValue set', () => {
+      ipcRenderer.send('handle-next-remote-get-current-window')
+      expect(() => remote.getCurrentWindow()).to.throw('Blocked remote.getCurrentWindow()')
+    })
+  })
+
+  describe('remote.getCurrentWebContents filtering', () => {
+    it('can return custom value', () => {
+      ipcRenderer.send('handle-next-remote-get-current-web-contents', 'Hello World!')
+      expect(remote.getCurrentWebContents()).to.be.equal('Hello World!')
+    })
+
+    it('throws when no returnValue set', () => {
+      ipcRenderer.send('handle-next-remote-get-current-web-contents')
+      expect(() => remote.getCurrentWebContents()).to.throw('Blocked remote.getCurrentWebContents()')
+    })
+  })
+
   describe('remote class', () => {
     const cl = remote.require(path.join(fixtures, 'module', 'class.js'))
     const base = cl.base

+ 29 - 20
spec/static/main.js

@@ -235,29 +235,38 @@ app.on('ready', function () {
   })
 })
 
-ipcMain.on('handle-next-desktop-capturer-get-sources', function (event) {
-  event.sender.once('desktop-capturer-get-sources', (event) => {
-    event.preventDefault()
-  })
-})
-
-ipcMain.on('handle-next-remote-require', function (event, modulesMap = {}) {
-  event.sender.once('remote-require', (event, moduleName) => {
-    event.preventDefault()
-    if (modulesMap.hasOwnProperty(moduleName)) {
-      event.returnValue = modulesMap[moduleName]
-    }
+for (const eventName of [
+  'remote-require',
+  'remote-get-global',
+  'remote-get-builtin'
+]) {
+  ipcMain.on(`handle-next-${eventName}`, function (event, valuesMap = {}) {
+    event.sender.once(eventName, (event, name) => {
+      if (valuesMap.hasOwnProperty(name)) {
+        event.returnValue = valuesMap[name]
+      } else {
+        event.preventDefault()
+      }
+    })
   })
-})
+}
 
-ipcMain.on('handle-next-remote-get-global', function (event, globalsMap = {}) {
-  event.sender.once('remote-get-global', (event, globalName) => {
-    event.preventDefault()
-    if (globalsMap.hasOwnProperty(globalName)) {
-      event.returnValue = globalsMap[globalName]
-    }
+for (const eventName of [
+  'desktop-capturer-get-sources',
+  'remote-get-current-window',
+  'remote-get-current-web-contents',
+  'remote-get-guest-web-contents'
+]) {
+  ipcMain.on(`handle-next-${eventName}`, function (event, returnValue) {
+    event.sender.once(eventName, (event) => {
+      if (returnValue) {
+        event.returnValue = returnValue
+      } else {
+        event.preventDefault()
+      }
+    })
   })
-})
+}
 
 ipcMain.on('set-client-certificate-option', function (event, skip) {
   app.once('select-client-certificate', function (event, webContents, url, list, callback) {

+ 18 - 0
spec/webview-spec.js

@@ -1188,6 +1188,24 @@ describe('<webview> tag', function () {
     })
   })
 
+  describe('<webview>.getWebContents filtering', () => {
+    it('can return custom value', async () => {
+      const src = 'about:blank'
+      await loadWebView(webview, { src })
+
+      ipcRenderer.send('handle-next-remote-get-guest-web-contents', 'Hello World!')
+      expect(webview.getWebContents()).to.be.equal('Hello World!')
+    })
+
+    it('throws when no returnValue set', async () => {
+      const src = 'about:blank'
+      await loadWebView(webview, { src })
+
+      ipcRenderer.send('handle-next-remote-get-guest-web-contents')
+      expect(() => webview.getWebContents()).to.throw('Blocked remote.getGuestForWebContents()')
+    })
+  })
+
   // FIXME(deepak1556): Ch69 follow up.
   xdescribe('document.visibilityState/hidden', () => {
     afterEach(() => {