From f4229e63e315654032dc3f0c8a69e2892c72758c Mon Sep 17 00:00:00 2001 From: Thais Siqueira Date: Fri, 31 Mar 2017 14:10:30 -0300 Subject: [#927] Implements sending recovery code by email. with @tayanefernandes --- service/pixelated/account_recovery.py | 30 +++++++++++- service/pixelated/resources/__init__.py | 3 ++ .../pixelated/resources/backup_account_resource.py | 4 +- .../unit/resources/test_backup_account_resource.py | 10 +++- service/test/unit/resources/test_base_resource.py | 8 ++++ service/test/unit/test_account_recovery.py | 53 +++++++++++++++++----- .../backup_account/backup_email/backup_email.js | 12 +++-- .../backup_email/backup_email.spec.js | 20 +++++++- 8 files changed, 121 insertions(+), 19 deletions(-) diff --git a/service/pixelated/account_recovery.py b/service/pixelated/account_recovery.py index 234bb1fe..fa1bf4f9 100644 --- a/service/pixelated/account_recovery.py +++ b/service/pixelated/account_recovery.py @@ -13,23 +13,51 @@ # # You should have received a copy of the GNU Affero General Public License # along with Pixelated. If not, see . + from twisted.internet.defer import inlineCallbacks, returnValue from twisted.logger import Logger +from twisted.mail import smtp + +from email.mime.text import MIMEText + log = Logger() class AccountRecovery(object): - def __init__(self, session, soledad): + def __init__(self, session, soledad, smtp_config, backup_email): self._bonafide_session = session self._soledad = soledad + self._smtp_config = smtp_config + self._backup_email = backup_email @inlineCallbacks def update_recovery_code(self): try: code = self._soledad.create_recovery_code() response = yield self._bonafide_session.update_recovery_code(code) + yield self._send_mail(code, self._backup_email) + returnValue(response) + except Exception as e: log.warn('Something went wrong when trying to save the recovery code') raise + + @inlineCallbacks + def _send_mail(self, code, backup_email): + msg = MIMEText('Your code %s' % code) + msg['Subject'] = 'Recovery Code' + msg['From'] = 'team@pixelated-project.org' + msg['To'] = backup_email + + try: + send_mail_result = yield smtp.sendmail( + str(self._smtp_config.remote_smtp_host), + 'team@pixelated-project.org', + [backup_email], + msg.as_string()) + returnValue(send_mail_result) + except Exception as e: + log.warn('Failed trying to send the email with the recovery code') + raise diff --git a/service/pixelated/resources/__init__.py b/service/pixelated/resources/__init__.py index 61e60720..3b2a7118 100644 --- a/service/pixelated/resources/__init__.py +++ b/service/pixelated/resources/__init__.py @@ -122,6 +122,9 @@ class BaseResource(Resource): def soledad(self, request): return self._service(request, '_leap_session').soledad + def get_backup_email(self, request): + return json.loads(request.content.getvalue()).get('backupEmail') + class UnAuthorizedResource(Resource): diff --git a/service/pixelated/resources/backup_account_resource.py b/service/pixelated/resources/backup_account_resource.py index b752b4c7..677e9e75 100644 --- a/service/pixelated/resources/backup_account_resource.py +++ b/service/pixelated/resources/backup_account_resource.py @@ -51,7 +51,9 @@ class BackupAccountResource(BaseResource): def render_POST(self, request): account_recovery = AccountRecovery( self._authenticator.bonafide_session, - self.soledad(request)) + self.soledad(request), + self._service(request, '_leap_session').smtp_config, + self.get_backup_email(request)) def update_response(response): request.setResponseCode(NO_CONTENT) diff --git a/service/test/unit/resources/test_backup_account_resource.py b/service/test/unit/resources/test_backup_account_resource.py index 2b68dd1b..b8654203 100644 --- a/service/test/unit/resources/test_backup_account_resource.py +++ b/service/test/unit/resources/test_backup_account_resource.py @@ -50,12 +50,16 @@ class TestBackupAccountResource(unittest.TestCase): mock_account_recovery.update_recovery_code.return_value = defer.succeed("Success") request = DummyRequest(['/backup-account']) request.method = 'POST' + request.content = MagicMock() + request.content.getvalue.return_value = '{"email": "test@test.com"}' d = self.web.get(request) def assert_update_recovery_code_called(_): mock_account_recovery_init.assert_called_with( self.resource._authenticator.bonafide_session, - self.resource.soledad(request)) + self.resource.soledad(request), + self.resource._service(request, '_leap_session').smtp_config, + self.resource.get_backup_email(request)) mock_account_recovery.update_recovery_code.assert_called() d.addCallback(assert_update_recovery_code_called) @@ -66,6 +70,8 @@ class TestBackupAccountResource(unittest.TestCase): mock_update_recovery_code.return_value = defer.succeed("Success") request = DummyRequest(['/backup-account']) request.method = 'POST' + request.content = MagicMock() + request.content.getvalue.return_value = '{"email": "test@test.com"}' d = self.web.get(request) def assert_successful_response(_): @@ -79,6 +85,8 @@ class TestBackupAccountResource(unittest.TestCase): mock_update_recovery_code.return_value = defer.fail(Exception) request = DummyRequest(['/backup-account']) request.method = 'POST' + request.content = MagicMock() + request.content.getvalue.return_value = '{"email": "test@test.com"}' d = self.web.get(request) def assert_successful_response(_): diff --git a/service/test/unit/resources/test_base_resource.py b/service/test/unit/resources/test_base_resource.py index 35ae67c7..d7110917 100644 --- a/service/test/unit/resources/test_base_resource.py +++ b/service/test/unit/resources/test_base_resource.py @@ -25,3 +25,11 @@ class TestBaseResource(unittest.TestCase): base_resource = BaseResource(mock_services_factory) self.assertEqual(base_resource.soledad('request'), mock_services_factory.services()._leap_session.soledad) + + def test_get_backup_email_from_request(self): + mock_services_factory = MagicMock() + request = MagicMock() + request.content.getvalue.return_value = '{"backupEmail": "test@test.com"}' + base_resource = BaseResource(mock_services_factory) + + self.assertEqual(base_resource.get_backup_email(request), 'test@test.com') diff --git a/service/test/unit/test_account_recovery.py b/service/test/unit/test_account_recovery.py index af14814a..f113169a 100644 --- a/service/test/unit/test_account_recovery.py +++ b/service/test/unit/test_account_recovery.py @@ -13,24 +13,55 @@ # # You should have received a copy of the GNU Affero General Public License # along with Pixelated. If not, see . +from email.mime.text import MIMEText -from twisted.internet.defer import inlineCallbacks +from twisted.internet import defer from twisted.trial import unittest +from twisted.mail import smtp -from mock import patch, Mock +from mock import patch, Mock, MagicMock +from mockito import mock, when, any as ANY from pixelated.account_recovery import AccountRecovery class AccountRecoveryTest(unittest.TestCase): + def setUp(self): + self.generated_code = '4645a2f8997e5d0d' + self.mock_bonafide_session = Mock() + self.mock_soledad = Mock() + self.mock_smtp_config = Mock() + self.keymanager = Mock() + self.mock_smtp_config.remote_smtp_host = 'test.com' + self.mock_soledad.create_recovery_code.return_value = self.generated_code + self.backup_email = 'test@test.com' + self.account_recovery = AccountRecovery( + self.mock_bonafide_session, + self.mock_soledad, + self.mock_smtp_config, + self.backup_email) + self.mock_smtp = Mock() - @inlineCallbacks + @defer.inlineCallbacks def test_update_recovery_code(self): - generated_code = '4645a2f8997e5d0d' - mock_bonafide_session = Mock() - mock_soledad = Mock() - mock_soledad.create_recovery_code.return_value = generated_code - account_recovery = AccountRecovery(mock_bonafide_session, mock_soledad) - - yield account_recovery.update_recovery_code() - mock_bonafide_session.update_recovery_code.assert_called_once_with(generated_code) + when(self.account_recovery)._send_mail(ANY).thenReturn(defer.succeed(None)) + response = yield self.account_recovery.update_recovery_code() + self.mock_bonafide_session.update_recovery_code.assert_called_once_with(self.generated_code) + + @defer.inlineCallbacks + def test_send_recovery_code_by_email(self): + msg = MIMEText('Your code %s' % self.generated_code) + msg['Subject'] = 'Recovery Code' + msg['From'] = 'team@pixelated-project.org' + msg['To'] = self.backup_email + + result = MagicMock() + deferred_sendmail = defer.succeed(result) + with patch.object(smtp, 'sendmail', return_value=deferred_sendmail) as mock_sendmail: + response = yield self.account_recovery._send_mail(self.generated_code, self.backup_email) + + mock_sendmail.assert_called_with( + 'test.com', + 'team@pixelated-project.org', + [self.backup_email], + msg.as_string()) diff --git a/web-ui/src/backup_account/backup_email/backup_email.js b/web-ui/src/backup_account/backup_email/backup_email.js index 9d622d8d..8fa71191 100644 --- a/web-ui/src/backup_account/backup_email/backup_email.js +++ b/web-ui/src/backup_account/backup_email/backup_email.js @@ -31,7 +31,7 @@ export class BackupEmail extends React.Component { constructor(props) { super(props); - this.state = { error: '', submitButtonDisabled: true }; + this.state = { error: '', submitButtonDisabled: true, backupEmail: '' }; } validateEmail = (event) => { @@ -54,7 +54,8 @@ export class BackupEmail extends React.Component { 'Content-Type': 'application/json' }, body: JSON.stringify({ - csrftoken: [browser.getCookie('XSRF-TOKEN')] + csrftoken: [browser.getCookie('XSRF-TOKEN')], + backupEmail: this.state.backupEmail }) }).then((response) => { if (response.ok) { @@ -65,6 +66,11 @@ export class BackupEmail extends React.Component { }); }; + handleChange = (event) => { + this.setState({ backupEmail: event.target.value }); + this.validateEmail(event); + } + render() { const t = this.props.t; return ( @@ -78,7 +84,7 @@ export class BackupEmail extends React.Component {

{t('backup-account.backup-email.title')}

{t('backup-account.backup-email.paragraph1')}

{t('backup-account.backup-email.paragraph2')}

- + { let backupEmail; let mockOnSubmit; let mockTranslations; + let backupEmailInstance; beforeEach(() => { mockOnSubmit = expect.createSpy(); @@ -30,8 +31,6 @@ describe('BackupEmail', () => { }); describe('Email validation', () => { - let backupEmailInstance; - beforeEach(() => { backupEmailInstance = backupEmail.instance(); }); @@ -84,6 +83,17 @@ describe('BackupEmail', () => { }); }); + describe('Email changing handler', () => { + beforeEach(() => { + backupEmailInstance = backupEmail.instance(); + }); + + it('sets user backup email in the state', () => { + backupEmailInstance.handleChange({ target: { value: 'test@test.com' } }); + expect(backupEmailInstance.state.backupEmail).toEqual('test@test.com'); + }); + }); + describe('Submit', () => { let preventDefaultSpy; @@ -98,6 +108,8 @@ describe('BackupEmail', () => { fetchMock.post('/backup-account', 204); backupEmail = shallow(); + + backupEmail.find('InputField').simulate('change', { target: { value: 'test@test.com' } }); backupEmail.find('form').simulate('submit', { preventDefault: preventDefaultSpy }); }); @@ -109,6 +121,10 @@ describe('BackupEmail', () => { expect(fetchMock.lastOptions('/backup-account').body).toContain('"csrftoken":["abc123"]'); }); + it('sends user email as content', () => { + expect(fetchMock.lastOptions('/backup-account').body).toContain('"backupEmail":"test@test.com"'); + }); + it('sends content-type header', () => { expect(fetchMock.lastOptions('/backup-account').headers['Content-Type']).toEqual('application/json'); }); -- cgit v1.2.3 From da79ad444e3021c785c9b5d9c6cf8df8836d8563 Mon Sep 17 00:00:00 2001 From: Sriram Viswanathan Date: Thu, 30 Mar 2017 18:26:07 -0300 Subject: [#1022] Downgrade Twisted to original version 16.1.1 --- service/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/service/requirements.txt b/service/requirements.txt index 16fc0e0e..3ddb02df 100644 --- a/service/requirements.txt +++ b/service/requirements.txt @@ -5,7 +5,7 @@ pyasn1==0.1.9 requests==2.11.1 srp==1.0.6 whoosh==2.6.0 -Twisted==17.1.0 +Twisted==16.1.1 -e 'git+https://0xacab.org/leap/leap_pycommon.git@master#egg=leap.common' -e 'git+https://0xacab.org/pixelated/bitmask-dev.git@development#egg=leap.bitmask' -e 'git+https://0xacab.org/pixelated/soledad.git@development#egg=leap.soledad.common&subdirectory=common/' -- cgit v1.2.3 From 304215fe36666c8401bd9fa01a50cab61ddc10ca Mon Sep 17 00:00:00 2001 From: Thais Siqueira Date: Fri, 31 Mar 2017 17:32:49 -0300 Subject: [#927] Log exception with recovery code with @tayanefernandes --- service/pixelated/account_recovery.py | 9 +++++---- service/pixelated/resources/__init__.py | 3 --- service/pixelated/resources/backup_account_resource.py | 7 ++++++- service/test/unit/resources/test_backup_account_resource.py | 8 +++++++- service/test/unit/resources/test_base_resource.py | 8 -------- 5 files changed, 18 insertions(+), 17 deletions(-) diff --git a/service/pixelated/account_recovery.py b/service/pixelated/account_recovery.py index fa1bf4f9..c0a18792 100644 --- a/service/pixelated/account_recovery.py +++ b/service/pixelated/account_recovery.py @@ -41,8 +41,9 @@ class AccountRecovery(object): returnValue(response) except Exception as e: - log.warn('Something went wrong when trying to save the recovery code') - raise + log.error('Something went wrong when trying to save the recovery code') + log.error(e) + raise e @inlineCallbacks def _send_mail(self, code, backup_email): @@ -59,5 +60,5 @@ class AccountRecovery(object): msg.as_string()) returnValue(send_mail_result) except Exception as e: - log.warn('Failed trying to send the email with the recovery code') - raise + log.error('Failed trying to send the email with the recovery code') + raise e diff --git a/service/pixelated/resources/__init__.py b/service/pixelated/resources/__init__.py index 3b2a7118..61e60720 100644 --- a/service/pixelated/resources/__init__.py +++ b/service/pixelated/resources/__init__.py @@ -122,9 +122,6 @@ class BaseResource(Resource): def soledad(self, request): return self._service(request, '_leap_session').soledad - def get_backup_email(self, request): - return json.loads(request.content.getvalue()).get('backupEmail') - class UnAuthorizedResource(Resource): diff --git a/service/pixelated/resources/backup_account_resource.py b/service/pixelated/resources/backup_account_resource.py index 677e9e75..f51ac2ec 100644 --- a/service/pixelated/resources/backup_account_resource.py +++ b/service/pixelated/resources/backup_account_resource.py @@ -15,6 +15,8 @@ # along with Pixelated. If not, see . import os +import json + from xml.sax import SAXParseException from pixelated.resources import BaseResource @@ -53,7 +55,7 @@ class BackupAccountResource(BaseResource): self._authenticator.bonafide_session, self.soledad(request), self._service(request, '_leap_session').smtp_config, - self.get_backup_email(request)) + self._get_backup_email(request)) def update_response(response): request.setResponseCode(NO_CONTENT) @@ -66,3 +68,6 @@ class BackupAccountResource(BaseResource): d = account_recovery.update_recovery_code() d.addCallbacks(update_response, error_response) return NOT_DONE_YET + + def _get_backup_email(self, request): + return json.loads(request.content.getvalue()).get('backupEmail') diff --git a/service/test/unit/resources/test_backup_account_resource.py b/service/test/unit/resources/test_backup_account_resource.py index b8654203..e5e2793a 100644 --- a/service/test/unit/resources/test_backup_account_resource.py +++ b/service/test/unit/resources/test_backup_account_resource.py @@ -59,7 +59,7 @@ class TestBackupAccountResource(unittest.TestCase): self.resource._authenticator.bonafide_session, self.resource.soledad(request), self.resource._service(request, '_leap_session').smtp_config, - self.resource.get_backup_email(request)) + self.resource._get_backup_email(request)) mock_account_recovery.update_recovery_code.assert_called() d.addCallback(assert_update_recovery_code_called) @@ -94,3 +94,9 @@ class TestBackupAccountResource(unittest.TestCase): d.addCallback(assert_successful_response) return d + + def test_get_backup_email_from_request(self): + request = MagicMock() + request.content.getvalue.return_value = '{"backupEmail": "test@test.com"}' + + self.assertEqual(self.resource._get_backup_email(request), 'test@test.com') diff --git a/service/test/unit/resources/test_base_resource.py b/service/test/unit/resources/test_base_resource.py index d7110917..35ae67c7 100644 --- a/service/test/unit/resources/test_base_resource.py +++ b/service/test/unit/resources/test_base_resource.py @@ -25,11 +25,3 @@ class TestBaseResource(unittest.TestCase): base_resource = BaseResource(mock_services_factory) self.assertEqual(base_resource.soledad('request'), mock_services_factory.services()._leap_session.soledad) - - def test_get_backup_email_from_request(self): - mock_services_factory = MagicMock() - request = MagicMock() - request.content.getvalue.return_value = '{"backupEmail": "test@test.com"}' - base_resource = BaseResource(mock_services_factory) - - self.assertEqual(base_resource.get_backup_email(request), 'test@test.com') -- cgit v1.2.3