diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 84e58b2b9..f73b5b23e 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -1553,6 +1553,12 @@ "usedByClients": { "message": "Used by a variety of different clients" }, + "usePhishingDetection": { + "message": "Use Phishing Detection" + }, + "usePhishingDetectionDescription": { + "message": "Display a warning for phishing domains targeting Ethereum users" + }, "userName": { "message": "Username" }, diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index e692dbda1..ab539e8fd 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -36,6 +36,7 @@ class PreferencesController { suggestedTokens: {}, useBlockie: false, useNonceField: false, + usePhishDetect: true, // WARNING: Do not use feature flags for security-sensitive things. // Feature flag toggling is available in the global namespace @@ -103,6 +104,16 @@ class PreferencesController { this.store.updateState({ useNonceField: val }) } + /** + * Setter for the `usePhishDetect` property + * + * @param {boolean} val - Whether or not the user prefers phishing domain protection + * + */ + setUsePhishDetect (val) { + this.store.updateState({ usePhishDetect: val }) + } + /** * Setter for the `participateInMetaMetrics` property * diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index c7f2dc351..327a325ba 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -446,6 +446,7 @@ export default class MetamaskController extends EventEmitter { setCurrentCurrency: this.setCurrentCurrency.bind(this), setUseBlockie: this.setUseBlockie.bind(this), setUseNonceField: this.setUseNonceField.bind(this), + setUsePhishDetect: this.setUsePhishDetect.bind(this), setIpfsGateway: this.setIpfsGateway.bind(this), setParticipateInMetaMetrics: this.setParticipateInMetaMetrics.bind(this), setMetaMetricsSendCount: this.setMetaMetricsSendCount.bind(this), @@ -1470,9 +1471,10 @@ export default class MetamaskController extends EventEmitter { * @param {MessageSender} sender - The sender of the messages on this stream */ setupUntrustedCommunication (connectionStream, sender) { + const { usePhishDetect } = this.preferencesController.store.getState() const hostname = (new URL(sender.url)).hostname - // Check if new connection is blacklisted - if (this.phishingController.test(hostname)) { + // Check if new connection is blacklisted if phishing detection is on + if (usePhishDetect && this.phishingController.test(hostname)) { log.debug('MetaMask - sending phishing warning for', hostname) this.sendPhishingWarning(connectionStream, hostname) return @@ -1988,6 +1990,20 @@ export default class MetamaskController extends EventEmitter { } } + /** + * Sets whether or not to use phishing detection. + * @param {boolean} val + * @param {Function} cb + */ + setUsePhishDetect (val, cb) { + try { + this.preferencesController.setUsePhishDetect(val) + cb(null) + } catch (err) { + cb(err) + } + } + /** * Sets the IPFS gateway to use for ENS content resolution. * @param {string} val - the host of the gateway to set diff --git a/test/e2e/fixtures/imported-account/state.json b/test/e2e/fixtures/imported-account/state.json index 4bf97d646..64d7dbdd3 100644 --- a/test/e2e/fixtures/imported-account/state.json +++ b/test/e2e/fixtures/imported-account/state.json @@ -110,7 +110,8 @@ "suggestedTokens": {}, "tokens": [], "useBlockie": false, - "useNonceField": false + "useNonceField": false, + "usePhishDetect": true }, "config": {}, "firstTimeInfo": { diff --git a/test/unit/app/controllers/preferences-controller-test.js b/test/unit/app/controllers/preferences-controller-test.js index d81453e37..66d3f4872 100644 --- a/test/unit/app/controllers/preferences-controller-test.js +++ b/test/unit/app/controllers/preferences-controller-test.js @@ -543,5 +543,17 @@ describe('preferences controller', function () { assert.deepEqual(preferencesController.store.getState().frequentRpcListDetail, []) }) }) -}) + describe('setUsePhishDetect', function () { + it('should default to true', function () { + const state = preferencesController.store.getState() + assert.equal(state.usePhishDetect, true) + }) + + it('should set the usePhishDetect property in state', function () { + assert.equal(preferencesController.store.getState().usePhishDetect, true) + preferencesController.setUsePhishDetect(false) + assert.equal(preferencesController.store.getState().usePhishDetect, false) + }) + }) +}) diff --git a/ui/app/pages/settings/security-tab/security-tab.component.js b/ui/app/pages/settings/security-tab/security-tab.component.js index f79fc5227..cb3da6c1a 100644 --- a/ui/app/pages/settings/security-tab/security-tab.component.js +++ b/ui/app/pages/settings/security-tab/security-tab.component.js @@ -13,10 +13,12 @@ export default class SecurityTab extends PureComponent { static propTypes = { warning: PropTypes.string, history: PropTypes.object, - participateInMetaMetrics: PropTypes.bool, - setParticipateInMetaMetrics: PropTypes.func, - showIncomingTransactions: PropTypes.bool, - setShowIncomingTransactionsFeatureFlag: PropTypes.func, + participateInMetaMetrics: PropTypes.bool.isRequired, + setParticipateInMetaMetrics: PropTypes.func.isRequired, + showIncomingTransactions: PropTypes.bool.isRequired, + setShowIncomingTransactionsFeatureFlag: PropTypes.func.isRequired, + setUsePhishDetect: PropTypes.func.isRequired, + usePhishDetect: PropTypes.bool.isRequired, } renderSeedWords () { @@ -105,6 +107,32 @@ export default class SecurityTab extends PureComponent { ) } + renderPhishingDetectionToggle () { + const { t } = this.context + const { usePhishDetect, setUsePhishDetect } = this.props + + return ( +
+
+ { t('usePhishingDetection') } +
+ { t('usePhishingDetectionDescription') } +
+
+
+
+ setUsePhishDetect(!value)} + offLabel={t('off')} + onLabel={t('on')} + /> +
+
+
+ ) + } + renderContent () { const { warning } = this.props @@ -113,6 +141,7 @@ export default class SecurityTab extends PureComponent { { warning &&
{ warning }
} { this.renderSeedWords() } { this.renderIncomingTransactionsOptIn() } + { this.renderPhishingDetectionToggle() } { this.renderMetaMetricsOptIn() } ) diff --git a/ui/app/pages/settings/security-tab/security-tab.container.js b/ui/app/pages/settings/security-tab/security-tab.container.js index 56e9ad495..138419913 100644 --- a/ui/app/pages/settings/security-tab/security-tab.container.js +++ b/ui/app/pages/settings/security-tab/security-tab.container.js @@ -5,6 +5,7 @@ import { withRouter } from 'react-router-dom' import { setFeatureFlag, setParticipateInMetaMetrics, + setUsePhishDetect, } from '../../../store/actions' const mapStateToProps = (state) => { @@ -14,12 +15,14 @@ const mapStateToProps = (state) => { showIncomingTransactions, } = {}, participateInMetaMetrics, + usePhishDetect, } = metamask return { warning, showIncomingTransactions, participateInMetaMetrics, + usePhishDetect, } } @@ -27,6 +30,7 @@ const mapDispatchToProps = (dispatch) => { return { setParticipateInMetaMetrics: (val) => dispatch(setParticipateInMetaMetrics(val)), setShowIncomingTransactionsFeatureFlag: (shouldShow) => dispatch(setFeatureFlag('showIncomingTransactions', shouldShow)), + setUsePhishDetect: (val) => dispatch(setUsePhishDetect(val)), } } diff --git a/ui/app/pages/settings/security-tab/tests/security-tab.test.js b/ui/app/pages/settings/security-tab/tests/security-tab.test.js index 80b8f3b06..3dff3b9ac 100644 --- a/ui/app/pages/settings/security-tab/tests/security-tab.test.js +++ b/ui/app/pages/settings/security-tab/tests/security-tab.test.js @@ -19,6 +19,8 @@ describe('Security Tab', function () { privacyMode: true, warning: '', participateInMetaMetrics: false, + setUsePhishDetect: sinon.spy(), + usePhishDetect: true, } beforeEach(function () { @@ -46,8 +48,14 @@ describe('Security Tab', function () { assert(props.setShowIncomingTransactionsFeatureFlag.calledOnce) }) + it('toggles phishing detection', function () { + const phishDetect = wrapper.find({ type: 'checkbox' }).at(1) + phishDetect.simulate('click') + assert(props.setUsePhishDetect.calledOnce) + }) + it('toggles metaMetrics', function () { - const metaMetrics = wrapper.find({ type: 'checkbox' }).at(1) + const metaMetrics = wrapper.find({ type: 'checkbox' }).at(2) metaMetrics.simulate('click') assert(props.setParticipateInMetaMetrics.calledOnce) diff --git a/ui/app/selectors/selectors.js b/ui/app/selectors/selectors.js index 12c1a25ee..b0aaa9df8 100644 --- a/ui/app/selectors/selectors.js +++ b/ui/app/selectors/selectors.js @@ -349,6 +349,10 @@ export function getUseNonceField (state) { return Boolean(state.metamask.useNonceField) } +export function getUsePhishDetect (state) { + return Boolean(state.metamask.usePhishDetect) +} + export function getCustomNonceValue (state) { return String(state.metamask.customNonceValue) } diff --git a/ui/app/store/actions.js b/ui/app/store/actions.js index 08c41cb24..68feec060 100644 --- a/ui/app/store/actions.js +++ b/ui/app/store/actions.js @@ -2125,6 +2125,19 @@ export function setUseNonceField (val) { } } +export function setUsePhishDetect (val) { + return (dispatch) => { + dispatch(showLoadingIndication()) + log.debug(`background.setUsePhishDetect`) + background.setUsePhishDetect(val, (err) => { + dispatch(hideLoadingIndication()) + if (err) { + return dispatch(displayWarning(err.message)) + } + }) + } +} + export function setIpfsGateway (val) { return (dispatch) => { dispatch(showLoadingIndication())