feature/default_network_editable
bitpshr 6 years ago committed by Dan Finlay
parent ba40fcbcb4
commit d7618bd5c6
  1. 17
      app/scripts/contentscript.js
  2. 44
      app/scripts/controllers/provider-approval.js
  3. 16
      app/scripts/inpage.js
  4. 5
      app/scripts/metamask-controller.js

@ -12,7 +12,7 @@ const TransformStream = require('stream').Transform
const inpageContent = fs.readFileSync(path.join(__dirname, '..', '..', 'dist', 'chrome', 'inpage.js')).toString() const inpageContent = fs.readFileSync(path.join(__dirname, '..', '..', 'dist', 'chrome', 'inpage.js')).toString()
const inpageSuffix = '//# sourceURL=' + extension.extension.getURL('inpage.js') + '\n' const inpageSuffix = '//# sourceURL=' + extension.extension.getURL('inpage.js') + '\n'
const inpageBundle = inpageContent + inpageSuffix const inpageBundle = inpageContent + inpageSuffix
let originApproved = false let isEnabled = false
// Eventually this streaming injection could be replaced with: // Eventually this streaming injection could be replaced with:
// https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Language_Bindings/Components.utils.exportFunction // https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Language_Bindings/Components.utils.exportFunction
@ -40,7 +40,7 @@ function injectScript (content) {
scriptTag.textContent = content scriptTag.textContent = content
container.insertBefore(scriptTag, container.children[0]) container.insertBefore(scriptTag, container.children[0])
} catch (e) { } catch (e) {
console.error('Metamask script injection failed.', e) console.error('MetaMask script injection failed', e)
} }
} }
@ -57,12 +57,11 @@ function setupStreams () {
const pluginPort = extension.runtime.connect({ name: 'contentscript' }) const pluginPort = extension.runtime.connect({ name: 'contentscript' })
const pluginStream = new PortStream(pluginPort) const pluginStream = new PortStream(pluginPort)
// Until this origin is approved, cut-off publicConfig stream writes at the content // Filter out selectedAddress until this origin is enabled
// script level so malicious sites can't snoop on the currently-selected address
const approvalTransform = new TransformStream({ const approvalTransform = new TransformStream({
objectMode: true, objectMode: true,
transform: (data, _, done) => { transform: (data, _, done) => {
if (typeof data === 'object' && data.name && data.name === 'publicConfig' && !originApproved) { if (typeof data === 'object' && data.name && data.name === 'publicConfig' && !isEnabled) {
data.data.selectedAddress = undefined data.data.selectedAddress = undefined
} }
done(null, { ...data }) done(null, { ...data })
@ -117,7 +116,7 @@ function setupStreams () {
* Establishes listeners for requests to fully-enable the provider from the dapp context * Establishes listeners for requests to fully-enable the provider from the dapp context
* and for full-provider approvals and rejections from the background script context. Dapps * and for full-provider approvals and rejections from the background script context. Dapps
* should not post messages directly and should instead call provider.enable(), which * should not post messages directly and should instead call provider.enable(), which
* handles posting these messages automatically. * handles posting these messages internally.
*/ */
function listenForProviderRequest () { function listenForProviderRequest () {
window.addEventListener('message', ({ source, data }) => { window.addEventListener('message', ({ source, data }) => {
@ -143,11 +142,10 @@ function listenForProviderRequest () {
} }
}) })
extension.runtime.onMessage.addListener(({ action, isEnabled, isApproved, isUnlocked }) => { extension.runtime.onMessage.addListener(({ action = '', isApproved, isUnlocked }) => {
if (!action) { return }
switch (action) { switch (action) {
case 'approve-provider-request': case 'approve-provider-request':
originApproved = true isEnabled = true
injectScript(`window.dispatchEvent(new CustomEvent('ethereumprovider', { detail: {}}))`) injectScript(`window.dispatchEvent(new CustomEvent('ethereumprovider', { detail: {}}))`)
break break
case 'reject-provider-request': case 'reject-provider-request':
@ -160,6 +158,7 @@ function listenForProviderRequest () {
injectScript(`window.dispatchEvent(new CustomEvent('metamaskisunlocked', { detail: { isUnlocked: ${isUnlocked}}}))`) injectScript(`window.dispatchEvent(new CustomEvent('metamaskisunlocked', { detail: { isUnlocked: ${isUnlocked}}}))`)
break break
case 'metamask-set-locked': case 'metamask-set-locked':
isEnabled = false
injectScript(`window.dispatchEvent(new CustomEvent('metamasksetlocked', { detail: {}}))`) injectScript(`window.dispatchEvent(new CustomEvent('metamasksetlocked', { detail: {}}))`)
break break
} }

@ -9,29 +9,29 @@ class ProviderApprovalController {
* *
* @param {Object} [config] - Options to configure controller * @param {Object} [config] - Options to configure controller
*/ */
constructor ({ closePopup, openPopup, keyringController, platform, preferencesController, publicConfigStore } = {}) { constructor ({ closePopup, keyringController, openPopup, platform, preferencesController, publicConfigStore } = {}) {
this.store = new ObservableStore() this.approvedOrigins = {}
this.closePopup = closePopup this.closePopup = closePopup
this.keyringController = keyringController
this.openPopup = openPopup this.openPopup = openPopup
this.platform = platform this.platform = platform
this.publicConfigStore = publicConfigStore
this.approvedOrigins = {}
this.preferencesController = preferencesController this.preferencesController = preferencesController
this.keyringController = keyringController this.publicConfigStore = publicConfigStore
platform && platform.addMessageListener && platform.addMessageListener(({ action, origin }) => { this.store = new ObservableStore()
if (!action) { return }
platform && platform.addMessageListener && platform.addMessageListener(({ action = '', origin }) => {
switch (action) { switch (action) {
case 'init-provider-request': case 'init-provider-request':
this.handleProviderRequest(origin) this._handleProviderRequest(origin)
break break
case 'init-is-approved': case 'init-is-approved':
this.handleIsApproved(origin) this._handleIsApproved(origin)
break break
case 'init-is-unlocked': case 'init-is-unlocked':
this.handleIsUnlocked() this._handleIsUnlocked()
break break
case 'init-privacy-request': case 'init-privacy-request':
this.handlePrivacyStatusRequest() this._handlePrivacyRequest()
break break
} }
}) })
@ -42,7 +42,7 @@ class ProviderApprovalController {
* *
* @param {string} origin - Origin of the window requesting full provider access * @param {string} origin - Origin of the window requesting full provider access
*/ */
handleProviderRequest (origin) { _handleProviderRequest (origin) {
this.store.updateState({ providerRequests: [{ origin }] }) this.store.updateState({ providerRequests: [{ origin }] })
const isUnlocked = this.keyringController.memStore.getState().isUnlocked const isUnlocked = this.keyringController.memStore.getState().isUnlocked
if (isUnlocked && this.isApproved(origin)) { if (isUnlocked && this.isApproved(origin)) {
@ -53,21 +53,27 @@ class ProviderApprovalController {
} }
/** /**
* Called by a tab to determine if a full Ethereum provider API is exposed * Called by a tab to determine if an origin has been approved in the past
* *
* @param {string} origin - Origin of the window requesting provider status * @param {string} origin - Origin of the window
*/ */
async handleIsApproved (origin) { _handleIsApproved (origin) {
const isApproved = this.isApproved(origin) const isApproved = this.isApproved(origin)
this.platform && this.platform.sendMessage({ action: 'answer-is-approved', isApproved }, { active: true }) this.platform && this.platform.sendMessage({ action: 'answer-is-approved', isApproved }, { active: true })
} }
handleIsUnlocked () { /**
* Called by a tab to determine if MetaMask is currently locked or unlocked
*/
_handleIsUnlocked () {
const isUnlocked = this.keyringController.memStore.getState().isUnlocked const isUnlocked = this.keyringController.memStore.getState().isUnlocked
this.platform && this.platform.sendMessage({ action: 'answer-is-unlocked', isUnlocked }, { active: true }) this.platform && this.platform.sendMessage({ action: 'answer-is-unlocked', isUnlocked }, { active: true })
} }
handlePrivacyStatusRequest () { /**
* Called to check privacy mode; if privacy mode is off, this will automatically enable the provider (legacy behavior)
*/
_handlePrivacyRequest () {
const privacyMode = this.preferencesController.getFeatureFlags().privacyMode const privacyMode = this.preferencesController.getFeatureFlags().privacyMode
if (!privacyMode) { if (!privacyMode) {
this.platform && this.platform.sendMessage({ action: 'approve-provider-request' }, { active: true }) this.platform && this.platform.sendMessage({ action: 'approve-provider-request' }, { active: true })
@ -121,6 +127,10 @@ class ProviderApprovalController {
return !privacyMode || this.approvedOrigins[origin] return !privacyMode || this.approvedOrigins[origin]
} }
/**
* Tells all tabs that MetaMask is now locked. This is primarily used to set
* internal flags in the contentscript and inpage script.
*/
setLocked () { setLocked () {
this.platform.sendMessage({ action: 'metamask-set-locked' }) this.platform.sendMessage({ action: 'metamask-set-locked' })
} }

@ -6,14 +6,18 @@ const LocalMessageDuplexStream = require('post-message-stream')
const setupDappAutoReload = require('./lib/auto-reload.js') const setupDappAutoReload = require('./lib/auto-reload.js')
const MetamaskInpageProvider = require('metamask-inpage-provider') const MetamaskInpageProvider = require('metamask-inpage-provider')
let isEnabled = false
let warned = false
restoreContextAfterImports() restoreContextAfterImports()
log.setDefaultLevel(process.env.METAMASK_DEBUG ? 'debug' : 'warn') log.setDefaultLevel(process.env.METAMASK_DEBUG ? 'debug' : 'warn')
console.warn('ATTENTION: In an effort to improve user privacy, MetaMask ' + console.warn('ATTENTION: In an effort to improve user privacy, MetaMask ' +
'stopped exposing user accounts to dapps by default beginning November 2nd, 2018. ' + 'stopped exposing user accounts to dapps if "privacy mode" is enabled on ' +
'Dapps should call provider.enable() in order to view and use accounts. Please see ' + 'November 2nd, 2018. Dapps should now call provider.enable() in order to view and use ' +
'https://bit.ly/2QQHXvF for complete information and up-to-date example code.') 'accounts. Please see https://bit.ly/2QQHXvF for complete information and up-to-date ' +
'example code.')
// //
// setup plugin communication // setup plugin communication
@ -30,9 +34,8 @@ var inpageProvider = new MetamaskInpageProvider(metamaskStream)
// set a high max listener count to avoid unnecesary warnings // set a high max listener count to avoid unnecesary warnings
inpageProvider.setMaxListeners(100) inpageProvider.setMaxListeners(100)
var isEnabled = false
var warned = false
// set up a listener for when MetaMask is locked
window.addEventListener('metamasksetlocked', () => { window.addEventListener('metamasksetlocked', () => {
isEnabled = false isEnabled = false
}) })
@ -44,6 +47,7 @@ inpageProvider.enable = function () {
if (typeof detail.error !== 'undefined') { if (typeof detail.error !== 'undefined') {
reject(detail.error) reject(detail.error)
} else { } else {
// wait for the publicConfig store to populate with an account
const publicConfig = new Promise((resolve) => { const publicConfig = new Promise((resolve) => {
const { selectedAddress } = inpageProvider.publicConfigStore.getState() const { selectedAddress } = inpageProvider.publicConfigStore.getState()
if (selectedAddress) { if (selectedAddress) {
@ -55,6 +59,7 @@ inpageProvider.enable = function () {
} }
}) })
// wait for the background to update with an accoount
const ethAccounts = new Promise((resolveAccounts, rejectAccounts) => { const ethAccounts = new Promise((resolveAccounts, rejectAccounts) => {
inpageProvider.sendAsync({ method: 'eth_accounts', params: [] }, (error, response) => { inpageProvider.sendAsync({ method: 'eth_accounts', params: [] }, (error, response) => {
if (error) { if (error) {
@ -143,7 +148,6 @@ const proxiedInpageProvider = new Proxy(inpageProvider, {
window.ethereum = proxiedInpageProvider window.ethereum = proxiedInpageProvider
// //
// setup web3 // setup web3
// //

@ -222,11 +222,11 @@ module.exports = class MetamaskController extends EventEmitter {
this.providerApprovalController = new ProviderApprovalController({ this.providerApprovalController = new ProviderApprovalController({
closePopup: opts.closePopup, closePopup: opts.closePopup,
keyringController: this.keyringController,
openPopup: opts.openPopup, openPopup: opts.openPopup,
platform: opts.platform, platform: opts.platform,
preferencesController: this.preferencesController, preferencesController: this.preferencesController,
publicConfigStore: this.publicConfigStore, publicConfigStore: this.publicConfigStore,
keyringController: this.keyringController,
}) })
this.store.updateStructure({ this.store.updateStructure({
@ -1577,6 +1577,9 @@ module.exports = class MetamaskController extends EventEmitter {
return this.blacklistController.whitelistDomain(hostname) return this.blacklistController.whitelistDomain(hostname)
} }
/**
* Locks MetaMask
*/
setLocked() { setLocked() {
this.providerApprovalController.setLocked() this.providerApprovalController.setLocked()
return this.keyringController.setLocked() return this.keyringController.setLocked()

Loading…
Cancel
Save