Skip to content

Commit

Permalink
rebase; refactor site metadata; persist perms
Browse files Browse the repository at this point in the history
show "connected" account in popup
  • Loading branch information
rekmarks committed Aug 26, 2019
1 parent 5c370ea commit 15b78d3
Show file tree
Hide file tree
Showing 17 changed files with 202 additions and 357 deletions.
94 changes: 41 additions & 53 deletions app/scripts/controllers/permissions.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
const JsonRpcEngine = require('json-rpc-engine')
const asMiddleware = require('json-rpc-engine/src/asMiddleware')
const createAsyncMiddleware = require('json-rpc-engine/src/createAsyncMiddleware')
const ObservableStore = require('obs-store')
const RpcCap = require('json-rpc-capabilities-middleware').CapabilitiesController
const uuid = require('uuid/v4')
const { errors: rpcErrors } = require('eth-json-rpc-errors')

// Methods that do not require any permissions to use:
const SAFE_METHODS = require('../lib/permissions-safe-methods.json')

const METHOD_PREFIX = 'wallet_'
const INTERNAL_METHOD_PREFIX = 'metamask_'

function prefix (method) {
return METHOD_PREFIX + method
Expand All @@ -19,6 +21,7 @@ class PermissionsController {
constructor ({
openPopup, closePopup, keyringController,
} = {}, restoredState) {
this.memStore = new ObservableStore({ siteMetadata: {} })
this._openPopup = openPopup
this._closePopup = closePopup
this.keyringController = keyringController
Expand All @@ -39,69 +42,50 @@ class PermissionsController {
* Create middleware for preprocessing permissions requests.
*/
createRequestMiddleware () {
return createAsyncMiddleware(async (req, _, next) => {
return createAsyncMiddleware(async (req, res, next) => {

/**
* TODO:lps:review I believe we should centralize permissioning
* logic by blocking requests here. For example, the extension currently
* behaves differently when it receives an eth_accounts call while locked.
* If the calling domain has the permission, an empty array is returned
* by the KeyringController's locked behavior. If it does not have the
* permission, an auth error is thrown.
* Centralizing this logic here requires thoughtful design.
*/
// if (
// !keyring.memStore.getState().isUnlocked
// && !SAFE_METHODS.includes(req.method)
// ) {
// try {
// this._openPopup()
// await new Promise((resolve, reject) => {
// ...
// })
// } catch (error) {
// res.error = { code: 1, message: 'User denied access.' }
// return
// }
// }

// validate and add metadata to permissions requests
if (req.method === prefix('requestPermissions')) {

if (
!Array.isArray(req.params) ||
req.params.length !== 1 ||
typeof req.params[0] !== 'object' ||
Array.isArray(req.params[0])
) throw new Error('Bad request.')

// add unique id and site metadata to request params, as expected by
// json-rpc-capabilities-middleware
const metadata = {
metadata: {
id: uuid(),
site: (
req._siteMetadata
? req._siteMetadata
: { name: null, icon: null }
),
},
if (typeof req.method !== 'string') {
res.error = rpcErrors.invalidRequest(null, req)
return // TODO:json-rpc-engine
}

if (req.method.startsWith(INTERNAL_METHOD_PREFIX)) {
switch (req.method.split(INTERNAL_METHOD_PREFIX)[1]) {
case 'sendSiteMetadata':
if (
req.siteMetadata &&
typeof req.siteMetadata.name === 'string'
) {
this.memStore.putState({
siteMetadata: {
...this.memStore.getState().siteMetadata,
[req.origin]: req.siteMetadata,
},
})
}
break
default:
res.error = rpcErrors.methodNotFound(null, req.method)
break
}
req.params.push(metadata)
if (!res.error) res.result = true
return
}

return next()
})
}

// TODO:lps:review see initializeProvider() in metamask-controller for why this
// method exists
/**
* Returns the accounts that should be exposed for the given origin domain,
* if any.
* if any. This method exists for when a trusted context needs to know
* which accounts are exposed to a given domain.
*
* Do not use in untrusted contexts; just send an RPC request.
*
* @param {string} origin
*/
async getAccounts (origin) {
getAccounts (origin) {
return new Promise((resolve, _) => {
const req = { method: 'eth_accounts' }
const res = {}
Expand Down Expand Up @@ -170,6 +154,10 @@ class PermissionsController {
* @param {string} origin = The origin string representing the domain.
*/
_initializePermissions (restoredState) {

// TODO:permissions stop persisting permissionsDescriptions and remove this line
const initState = { ...restoredState, permissionsRequests: [] }

this.testProfile = {
name: 'Dan Finlay',
}
Expand Down Expand Up @@ -246,7 +234,7 @@ class PermissionsController {
// TODO: Attenuate requested permissions in approval screen.
// Like selecting the account to display.
},
}, restoredState)
}, initState)
}

}
Expand Down
1 change: 1 addition & 0 deletions app/scripts/lib/createOriginMiddleware.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

module.exports = createOriginMiddleware

/**
Expand Down
8 changes: 5 additions & 3 deletions app/scripts/metamask-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -253,8 +253,7 @@ module.exports = class MetamaskController extends EventEmitter {
openPopup: opts.openPopup,
closePopup: opts.closePopup,
},
// TOOD: Persist/restore state here:
{})
initState.PermissionsController)

this.store.updateStructure({
AppStateController: this.appStateController.store,
Expand All @@ -269,6 +268,7 @@ module.exports = class MetamaskController extends EventEmitter {
CachedBalancesController: this.cachedBalancesController.store,
OnboardingController: this.onboardingController.store,
IncomingTransactionsController: this.incomingTransactionsController.store,
// TODO:permissions permissionsRequests should be memStore only
PermissionsController: this.permissionsController.permissions,
})

Expand All @@ -292,6 +292,7 @@ module.exports = class MetamaskController extends EventEmitter {
OnboardingController: this.onboardingController.store,
IncomingTransactionsController: this.incomingTransactionsController.store,
PermissionsController: this.permissionsController.permissions,
SiteMetadata: this.permissionsController.memStore,
})
this.memStore.subscribe(this.sendUpdate.bind(this))
}
Expand Down Expand Up @@ -320,7 +321,7 @@ module.exports = class MetamaskController extends EventEmitter {
) {
return await this.permissionsController.getAccounts(origin)
}
return []
return [] // changing this is a breaking change
},
// tx signing
processTransaction: this.newUnapprovedTransaction.bind(this),
Expand Down Expand Up @@ -511,6 +512,7 @@ module.exports = class MetamaskController extends EventEmitter {
rejectPermissionsRequest: nodeify(this.permissionsController.rejectPermissionsRequest, this.permissionsController),
removePermissionsFor: this.permissionsController.removePermissionsFor.bind(this.permissionsController),
clearPermissions: this.permissionsController.clearPermissions.bind(this.permissionsController),
getApprovedAccounts: nodeify(this.permissionsController.getAccounts.bind(this.permissionsController)),
}
}

Expand Down
33 changes: 33 additions & 0 deletions app/scripts/migrations/034.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
const version = 34
const clone = require('clone')

/**
* The purpose of this migration is to enable the {@code privacyMode} feature flag and set the user as being migrated
* if it was {@code false}.
*/
module.exports = {
version,
migrate: async function (originalVersionedData) {
const versionedData = clone(originalVersionedData)
versionedData.meta.version = version
const state = versionedData.data
versionedData.data = transformState(state)
return versionedData
},
}

function transformState (state) {
const { PreferencesController } = state

if (PreferencesController) {
const featureFlags = PreferencesController.featureFlags || {}

if (!featureFlags.privacyMode && typeof PreferencesController.migratedPrivacyMode === 'undefined') {
// Mark the state has being migrated and enable Privacy Mode
PreferencesController.migratedPrivacyMode = true
featureFlags.privacyMode = true
}
}

return state
}
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
"eth-block-tracker": "^4.4.2",
"eth-contract-metadata": "^1.9.2",
"eth-ens-namehash": "^2.0.8",
"eth-json-rpc-errors": "^1.0.1",
"eth-json-rpc-filters": "^4.1.0",
"eth-json-rpc-infura": "^4.0.1",
"eth-keyring-controller": "^5.0.1",
Expand Down Expand Up @@ -107,15 +108,15 @@
"gaba": "^1.6.0",
"human-standard-token-abi": "^2.0.0",
"jazzicon": "^1.2.0",
"json-rpc-capabilities-middleware": "^0.15.2",
"json-rpc-engine": "^4.0.0",
"json-rpc-capabilities-middleware": "^0.15.3",
"json-rpc-engine": "^5.1.3",
"json-rpc-middleware-stream": "^2.1.1",
"jsonschema": "^1.2.4",
"lodash.debounce": "^4.0.8",
"lodash.shuffle": "^4.2.0",
"loglevel": "^1.4.1",
"luxon": "^1.8.2",
"metamask-inpage-provider": "github:rekmarks/metamask-inpage-provider#permissions",
"metamask-inpage-provider": "rekmarks/metamask-inpage-provider#permissions",
"metamask-logo": "^2.1.4",
"mkdirp": "^0.5.1",
"multihashes": "^0.4.12",
Expand Down Expand Up @@ -163,7 +164,6 @@
"single-call-balance-checker-abi": "^1.0.0",
"swappable-obj-proxy": "^1.1.0",
"textarea-caret": "^3.0.1",
"uuid": "^3.3.2",
"valid-url": "^1.0.9",
"web3": "^0.20.7",
"web3-stream-provider": "^4.0.0",
Expand Down
Loading

0 comments on commit 15b78d3

Please sign in to comment.