diff options
| -rw-r--r-- | service/pixelated/account_recovery.py | 10 | ||||
| -rw-r--r-- | service/pixelated/assets/recovery.mail.pt-BR | 20 | ||||
| -rw-r--r-- | service/pixelated/resources/backup_account_resource.py | 7 | ||||
| -rw-r--r-- | service/pixelated/resources/login_resource.py | 11 | ||||
| -rw-r--r-- | service/pixelated/support/language.py | 24 | ||||
| -rw-r--r-- | service/test/unit/resources/test_backup_account_resource.py | 7 | ||||
| -rw-r--r-- | service/test/unit/resources/test_login_resource.py | 21 | ||||
| -rw-r--r-- | service/test/unit/support/test_language.py | 40 | ||||
| -rw-r--r-- | service/test/unit/test_account_recovery.py | 35 | 
9 files changed, 138 insertions, 37 deletions
diff --git a/service/pixelated/account_recovery.py b/service/pixelated/account_recovery.py index 723d4048..4e8a1583 100644 --- a/service/pixelated/account_recovery.py +++ b/service/pixelated/account_recovery.py @@ -15,6 +15,7 @@  # along with Pixelated. If not, see <http://www.gnu.org/licenses/>.  import pkg_resources +import binascii  from twisted.internet.defer import inlineCallbacks, returnValue  from twisted.logger import Logger @@ -28,12 +29,13 @@ log = Logger()  class AccountRecovery(object): -    def __init__(self, session, soledad, smtp_config, backup_email, domain): +    def __init__(self, session, soledad, smtp_config, backup_email, domain, language='en-US'):          self._bonafide_session = session          self._soledad = soledad          self._smtp_config = smtp_config          self._backup_email = backup_email          self._domain = domain +        self._language = language      @inlineCallbacks      def update_recovery_code(self): @@ -72,15 +74,15 @@ class AccountRecovery(object):              log.error('Failed trying to send the email with the recovery code')              raise e -    def _get_recovery_mail(self, code, language='en-US'): +    def _get_recovery_mail(self, code):          recovery_mail = pkg_resources.resource_filename(              'pixelated.assets', -            'recovery.mail.%s' % (language)) +            'recovery.mail.%s' % (self._language))          account_recovery_url = '{}/{}'.format(self._domain, AccountRecoveryResource.BASE_URL)          with open(recovery_mail) as mail_template_file:              return mail_template_file.read().format(                  domain=self._domain, -                recovery_code=code, +                recovery_code=binascii.hexlify(code),                  account_recovery_url=account_recovery_url) diff --git a/service/pixelated/assets/recovery.mail.pt-BR b/service/pixelated/assets/recovery.mail.pt-BR new file mode 100644 index 00000000..77271b39 --- /dev/null +++ b/service/pixelated/assets/recovery.mail.pt-BR @@ -0,0 +1,20 @@ +Olá, + +Você está recebendo este email porque você se registrou em um provedor do Pixelated, no {domain}. +Se algum dia esquecer sua senha, você pode acessar esse link {account_recovery_url} e usar o código abaixo: + +{recovery_code} + +Esse código é a única forma de recuperar o acesso a sua conta se perder a senha. +Guarde-o com carinho!!! + +Por quê isso é importante? + +Pixelated é um cliente de email que respeita sua privacidade e usa criptografia PGP para isso. +Sua senha também te da acesso as suas chaves, então se você esquecê-la você perderá acesso a sua conta e a habilidade de descriptografar suas mensagens. +Nós entedemos que esquecer a senha é algo comum, por isso desenvolvemos uma forma mais segura de recuperar sua conta e, consequentemente, um pouco mais chata ;) + +Esse código é uma metade do código necesário para recuperar a conta. A outra metade está com o administrador da conta. Se você esquecer a senha, use esse código e o do administrador para recuperar acesso a conta. É como se fosse um cadeado com duas chaves :) +Você só terá sucesso se tiver ambos os códigos, então, nunca machuca pedir novamente: GUARDE ESSE CÓDIGO! + +PS: Se você não criou uma conta no site {domain}, por favor ignore esse email. diff --git a/service/pixelated/resources/backup_account_resource.py b/service/pixelated/resources/backup_account_resource.py index ec3e9dee..94129122 100644 --- a/service/pixelated/resources/backup_account_resource.py +++ b/service/pixelated/resources/backup_account_resource.py @@ -25,6 +25,7 @@ from twisted.web.template import Element, XMLFile, renderElement  from pixelated.resources import BaseResource  from pixelated.resources import get_protected_static_folder  from pixelated.account_recovery import AccountRecovery +from pixelated.support.language import parse_accept_language  class BackupAccountPage(Element): @@ -56,7 +57,8 @@ class BackupAccountResource(BaseResource):              self.soledad(request),              self._service(request, '_leap_session').smtp_config,              self._get_backup_email(request), -            self._leap_provider.server_name) +            self._leap_provider.server_name, +            language=self._get_language(request))          def update_response(response):              request.setResponseCode(NO_CONTENT) @@ -72,3 +74,6 @@ class BackupAccountResource(BaseResource):      def _get_backup_email(self, request):          return json.loads(request.content.getvalue()).get('backupEmail') + +    def _get_language(self, request): +        return parse_accept_language(request.getAllHeaders()) diff --git a/service/pixelated/resources/login_resource.py b/service/pixelated/resources/login_resource.py index 45942ea6..5b0b70d0 100644 --- a/service/pixelated/resources/login_resource.py +++ b/service/pixelated/resources/login_resource.py @@ -22,6 +22,8 @@ from pixelated.config.leap import BootstrapUserServices  from pixelated.resources import BaseResource, UnAuthorizedResource, IPixelatedSession  from pixelated.resources.account_recovery_resource import AccountRecoveryResource  from pixelated.resources import get_public_static_folder, respond_json +from pixelated.support.language import parse_accept_language +  from twisted.cred.error import UnauthorizedLogin  from twisted.internet import defer  from twisted.logger import Logger @@ -36,15 +38,6 @@ from twisted.web.template import Element, XMLFile, renderElement, renderer  log = Logger() -def parse_accept_language(all_headers): -    accepted_languages = ['pt-BR', 'en-US'] -    languages = all_headers.get('accept-language', '').split(';')[0] -    for language in accepted_languages: -        if language in languages: -            return language -    return 'pt-BR' - -  class DisclaimerElement(Element):      loader = XMLFile(FilePath(os.path.join(get_public_static_folder(), '_login_disclaimer_banner.html'))) diff --git a/service/pixelated/support/language.py b/service/pixelated/support/language.py new file mode 100644 index 00000000..4999bc04 --- /dev/null +++ b/service/pixelated/support/language.py @@ -0,0 +1,24 @@ +# +# Copyright (c) 2017 ThoughtWorks, Inc. +# +# Pixelated is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pixelated is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with Pixelated. If not, see <http://www.gnu.org/licenses/>. + + +def parse_accept_language(all_headers): +    accepted_languages = ['pt-BR', 'en-US'] +    languages = all_headers.get('accept-language', '').split(';')[0] +    for language in accepted_languages: +        if language in languages: +            return language +    return 'pt-BR' diff --git a/service/test/unit/resources/test_backup_account_resource.py b/service/test/unit/resources/test_backup_account_resource.py index 220e3909..e16fa0e1 100644 --- a/service/test/unit/resources/test_backup_account_resource.py +++ b/service/test/unit/resources/test_backup_account_resource.py @@ -44,8 +44,10 @@ class TestBackupAccountResource(unittest.TestCase):          d.addCallback(assert_200_when_user_logged_in)          return d +    @patch('pixelated.resources.backup_account_resource.parse_accept_language')      @patch('pixelated.resources.backup_account_resource.AccountRecovery') -    def test_post_updates_recovery_code(self, mock_account_recovery_init): +    def test_post_updates_recovery_code(self, mock_account_recovery_init, mock_language): +        mock_language.return_value = 'pt-BR'          mock_account_recovery = MagicMock()          mock_account_recovery_init.return_value = mock_account_recovery          mock_account_recovery.update_recovery_code.return_value = defer.succeed("Success") @@ -61,7 +63,8 @@ class TestBackupAccountResource(unittest.TestCase):                  self.resource.soledad(request),                  self.resource._service(request, '_leap_session').smtp_config,                  self.resource._get_backup_email(request), -                self.leap_provider.server_name) +                self.leap_provider.server_name, +                language='pt-BR')              mock_account_recovery.update_recovery_code.assert_called()          d.addCallback(assert_update_recovery_code_called) diff --git a/service/test/unit/resources/test_login_resource.py b/service/test/unit/resources/test_login_resource.py index eaaba1d4..e33b5618 100644 --- a/service/test/unit/resources/test_login_resource.py +++ b/service/test/unit/resources/test_login_resource.py @@ -24,30 +24,9 @@ from twisted.trial import unittest  from twisted.web.test.requesthelper import DummyRequest  from pixelated.resources.login_resource import LoginResource, LoginStatusResource -from pixelated.resources.login_resource import parse_accept_language  from test.unit.resources import DummySite -class TestParseAcceptLanguage(unittest.TestCase): -    def test_parse_pt_br_simple(self): -        all_headers = { -            'accept-language': 'pt-BR,pt;q=0.8,en-US;q=0.5,en;q=0.3'} -        parsed_language = parse_accept_language(all_headers) -        self.assertEqual('pt-BR', parsed_language) - -    def test_parse_en_us_simple(self): -        all_headers = { -            'accept-language': 'en-US,en;q=0.8,en-US;q=0.5,en;q=0.3'} -        parsed_language = parse_accept_language(all_headers) -        self.assertEqual('en-US', parsed_language) - -    def test_parse_pt_br_as_default(self): -        all_headers = { -            'accept-language': 'de-DE,de;q=0.8,en-US;q=0.5,en;q=0.3'} -        parsed_language = parse_accept_language(all_headers) -        self.assertEqual('pt-BR', parsed_language) - -  class TestLoginResource(unittest.TestCase):      def setUp(self):          self.services_factory = mock() diff --git a/service/test/unit/support/test_language.py b/service/test/unit/support/test_language.py new file mode 100644 index 00000000..23983b53 --- /dev/null +++ b/service/test/unit/support/test_language.py @@ -0,0 +1,40 @@ +# +# Copyright (c) 2017 ThoughtWorks, Inc. +# +# Pixelated is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pixelated is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with Pixelated. If not, see <http://www.gnu.org/licenses/>. + + +from twisted.trial import unittest + +from pixelated.support.language import parse_accept_language + + +class TestParseAcceptLanguage(unittest.TestCase): +    def test_parse_pt_br_simple(self): +        all_headers = { +            'accept-language': 'pt-BR,pt;q=0.8,en-US;q=0.5,en;q=0.3'} +        parsed_language = parse_accept_language(all_headers) +        self.assertEqual('pt-BR', parsed_language) + +    def test_parse_en_us_simple(self): +        all_headers = { +            'accept-language': 'en-US,en;q=0.8,en-US;q=0.5,en;q=0.3'} +        parsed_language = parse_accept_language(all_headers) +        self.assertEqual('en-US', parsed_language) + +    def test_parse_pt_br_as_default(self): +        all_headers = { +            'accept-language': 'de-DE,de;q=0.8,en-US;q=0.5,en;q=0.3'} +        parsed_language = parse_accept_language(all_headers) +        self.assertEqual('pt-BR', parsed_language) diff --git a/service/test/unit/test_account_recovery.py b/service/test/unit/test_account_recovery.py index b0edc466..eb7927d0 100644 --- a/service/test/unit/test_account_recovery.py +++ b/service/test/unit/test_account_recovery.py @@ -50,6 +50,41 @@ class AccountRecoveryTest(unittest.TestCase):          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_creates_recovery_code(self): +        when(self.account_recovery)._send_mail(ANY).thenReturn(defer.succeed(None)) +        yield self.account_recovery.update_recovery_code() +        self.mock_soledad.create_recovery_code.assert_called_once() + +    @patch('pixelated.account_recovery.smtp.sendmail') +    @patch('pixelated.account_recovery.pkg_resources.resource_filename') +    @defer.inlineCallbacks +    def test_default_email_template(self, mock_resource, mock_sendmail): +        mock_sendmail.return_value = defer.succeed(None) + +        with patch('pixelated.account_recovery.open', mock_open(read_data=''), create=True): +            yield self.account_recovery.update_recovery_code() +            mock_resource.assert_called_once_with('pixelated.assets', +                                                  'recovery.mail.en-US') + +    @patch('pixelated.account_recovery.smtp.sendmail') +    @patch('pixelated.account_recovery.pkg_resources.resource_filename') +    @defer.inlineCallbacks +    def test_portuguese_email_template(self, mock_resource, mock_sendmail): +        self.account_recovery = AccountRecovery( +            self.mock_bonafide_session, +            self.mock_soledad, +            self.mock_smtp_config, +            self.backup_email, +            self.domain, +            language='pt-BR') +        mock_sendmail.return_value = defer.succeed(None) + +        with patch('pixelated.account_recovery.open', mock_open(read_data=''), create=True): +            yield self.account_recovery.update_recovery_code() +            mock_resource.assert_called_once_with('pixelated.assets', +                                                  'recovery.mail.pt-BR') +      @patch('pixelated.account_recovery.smtp.sendmail')      @patch('pixelated.account_recovery.pkg_resources.resource_filename')      @defer.inlineCallbacks  | 
