~roadmr/canonical-identity-provider/u2f-db-fields-separate-dev-type

« back to all changes in this revision

Viewing changes to src/api/v20/tests/test_handlers.py

  • Committer: Daniel Manrique
  • Date: 2020-02-21 21:19:46 UTC
  • mfrom: (1697.2.28 work)
  • Revision ID: roadmr@ubuntu.com-20200221211946-k3pds3i97yxy5zwg
MergedĀ fromĀ trunk

Show diffs side-by-side

added added

removed removed

Lines of Context:
21
21
from django.urls.exceptions import NoReverseMatch
22
22
from django.utils.timezone import now
23
23
from gargoyle.testutils import switches
24
 
from mock import Mock, patch
 
24
from mock import Mock, call, patch
25
25
from pymacaroons import Macaroon, Verifier
26
26
from timeline import Timeline
27
27
 
94
94
 
95
95
        response = getattr(self.client, method.lower())(url, **kwargs)
96
96
        if status_code is not None:
97
 
            self.assertEqual(response.status_code, status_code)
 
97
            self.assertEqual(
 
98
                response.status_code, status_code, "%s != %s\n%s" % (
 
99
                    response.status_code, status_code, response.content
 
100
                )
 
101
            )
98
102
        return response
99
103
 
100
104
    def do_request(self, method, data=None, raw_data=None, url=None,
1105
1109
        if error is None:
1106
1110
            self.assertFalse(self.mock_logger.exception.called)
1107
1111
        else:
1108
 
            self.mock_logger.exception.assert_called_once_with(
1109
 
                'RequestsHandler.create: could not verify request (data %r):',
1110
 
                data)
 
1112
            self.assertEqual(
 
1113
                self.mock_logger.exception.call_args,
 
1114
                call(
 
1115
                    'RequestsHandler.create: could not verify request '
 
1116
                    '(data %r):', data
 
1117
                )
 
1118
            )
1111
1119
 
1112
1120
    def assert_workaround_warning_logged(
1113
1121
            self, http_url, query_string, authorization):
1115
1123
            'RequestsHandler.create: caller for validating http_url %r sent '
1116
1124
            'query_string %r or did not send authorization (got %r), will '
1117
1125
            'merge it into original url.')
1118
 
        self.mock_logger.warning.assert_called_once_with(
1119
 
            qs_workaround_warning, http_url, query_string, authorization)
 
1126
        self.assertEqual(
 
1127
            self.mock_logger.warning.call_args,
 
1128
            call(qs_workaround_warning, http_url, query_string, authorization)
 
1129
        )
1120
1130
 
1121
1131
    def assert_token_filtered_warning_logged(
1122
1132
            self, authorization, http_url, http_method):
1123
 
        self.mock_logger.warning.assert_called_with(
1124
 
            'RequestsHandler.create: NOT validating (otherwise valid) '
1125
 
            'signature %r because the token is not allowed to access url %r '
1126
 
            'via %r', authorization, http_url, http_method)
 
1133
        self.assertEqual(
 
1134
            self.mock_logger.warning.call_args,
 
1135
            call(
 
1136
                'RequestsHandler.create: NOT validating (otherwise valid) '
 
1137
                'signature %r because the token is not allowed to access '
 
1138
                'url %r via %r', authorization, http_url, http_method
 
1139
            )
 
1140
        )
1127
1141
 
1128
1142
    def do_post_and_assert_statsd_metric_recorded(self, data, suffix):
1129
1143
        with patch('api.v20.handlers.time') as mock_time:
1716
1730
        super(PasswordResetTokenHandlerTestCase, self).setUp()
1717
1731
        self.email = EmailAddress.objects.get(account=self.account)
1718
1732
        self.data = {'email': self.email.email}
1719
 
        self.mock_logger = self.patch('api.v20.handlers.logging')
 
1733
        self.mock_logger = self.patch('api.v20.handlers.logger')
1720
1734
 
1721
1735
    def test_any_server_error_is_json(self):
1722
1736
        self.assert_any_server_error_is_json(self.do_post, data=self.data)
1745
1759
                       'login support to re-enable it',
1746
1760
            'code': 'ACCOUNT_SUSPENDED',
1747
1761
            'extra': {}})
1748
 
 
1749
 
        condition = "account '%s' is not active" % self.account.displayname
1750
 
        self.mock_logger.debug("PasswordResetTokenHandler.create: email was "
1751
 
                               "not sent out because %s" % condition)
 
1762
        self.assertEqual(
 
1763
            self.mock_logger.debug.call_args,
 
1764
            call(
 
1765
                "PasswordResetTokenHandler.create: email was not sent out "
 
1766
                "because account '{}' is Suspended (by admin)".format(
 
1767
                    self.account.displayname)
 
1768
            )
 
1769
        )
1752
1770
 
1753
1771
    def test_deactivated_account(self):
1754
1772
        self.account.status = AccountStatus.DEACTIVATED
1755
1773
        self.account.save()
1756
1774
 
1757
 
        name = 'identityprovider.models.account.Account.can_reset_password'
1758
 
        with patch(name, False):
1759
 
            content = self.do_post(data=self.data, status_code=403)
1760
 
 
1761
 
        self.assertEqual(content, {
1762
 
            'message': 'Your account has been deactivated. To reactivate it, '
1763
 
                       'please reset your password',
1764
 
            'code': 'ACCOUNT_DEACTIVATED',
1765
 
            'extra': {}})
1766
 
 
1767
 
        condition = "account '%s' is not active" % self.account.displayname
1768
 
        self.mock_logger.debug("PasswordResetTokenHandler.create: email was "
1769
 
                               "not sent out because %s" % condition)
1770
 
 
1771
 
    def test_can_not_reset_password(self):
1772
 
        name = 'identityprovider.models.account.Account.can_reset_password'
1773
 
        with patch(name, False):
1774
 
            content = self.do_post(data=self.data, status_code=403)
1775
 
 
1776
 
        self.assertEqual(content, {
1777
 
            'message': 'Can not reset password. Please contact login support',
1778
 
            'code': 'CAN_NOT_RESET_PASSWORD',
1779
 
            'extra': {}})
1780
 
 
1781
 
        condition = "account '%s' is not active" % self.account.displayname
1782
 
        self.mock_logger.debug("PasswordResetTokenHandler.create: email was "
1783
 
                               "not sent out because %s" % condition)
1784
 
 
1785
 
    def test_reset_password(self):
1786
 
        email = self.email.email
1787
 
        body = self.do_post({'email': email}, status_code=201)
1788
 
 
1789
 
        self.assertEqual(body['email'], email)
1790
 
 
 
1775
        result = self.do_post(data=self.data, status_code=201)
 
1776
 
 
1777
        self.assert_email_used(result, self.account.preferredemail.email)
 
1778
 
 
1779
    def assert_email_used(self, result, expected_email):
 
1780
        # Then the response contains the user's stored email.
 
1781
        self.assertEqual(result['email'], expected_email)
 
1782
        # and the reset email is sent to the user's stored email.
1791
1783
        self.assertEqual(len(mail.outbox), 1)
 
1784
        self.assertEqual([expected_email], mail.outbox[0].to)
 
1785
        # and the mail content contains the user's stored email
1792
1786
        mail_content = unicode(mail.outbox[0].message())
 
1787
        self.assertIn(expected_email, mail_content)
 
1788
        # and the mail contains a reset password link with a token
1793
1789
        links = re.search(
1794
1790
            ('http://.+?/token/(.+?)/\+resetpassword'), mail_content)
1795
1791
        self.assertIsNotNone(links, "Found no +resetpassword links with token")
1796
1792
        token = links.group(1)
1797
1793
        self.assertIn(token, mail_content)
1798
 
        self.assertIn(email, mail_content)
1799
 
 
1800
 
    def test_reset_password_uses_preferred_if_given_not_validated(self):
1801
 
        email = self.factory.make_email_for_account(
1802
 
            self.account, status=EmailStatus.NEW)
1803
 
        self.do_post({'email': email.email}, status_code=201)
1804
 
 
1805
 
        tokens = AuthToken.objects.filter(
1806
 
            token_type=AuthTokenType.PASSWORDRECOVERY,
1807
 
            email=email)
1808
 
        self.assertEqual(tokens.count(), 0)
1809
 
 
1810
 
        tokens = AuthToken.objects.filter(
1811
 
            token_type=AuthTokenType.PASSWORDRECOVERY,
1812
 
            email=self.account.preferredemail)
1813
 
        self.assertEqual(tokens.count(), 1)
1814
 
 
1815
 
        self.assertEqual(len(mail.outbox), 1)
1816
 
        mail_content = unicode(mail.outbox[0].message())
1817
 
        self.assertNotIn(email.email, mail_content)
1818
 
        self.assertIn(self.account.preferredemail.email, mail_content)
1819
 
 
1820
 
    def test_reset_password_uses_given_email_if_validated(self):
1821
 
        email = self.factory.make_email_for_account(
1822
 
            self.account, status=EmailStatus.VALIDATED)
1823
 
        assert self.account.preferredemail.email != email.email
1824
 
        self.do_post({'email': email.email}, status_code=201)
1825
 
 
1826
 
        tokens = AuthToken.objects.filter(
1827
 
            token_type=AuthTokenType.PASSWORDRECOVERY,
1828
 
            email=self.account.preferredemail)
1829
 
        self.assertEqual(tokens.count(), 0)
1830
 
 
1831
 
        tokens = AuthToken.objects.filter(
1832
 
            token_type=AuthTokenType.PASSWORDRECOVERY,
1833
 
            email=email)
1834
 
        self.assertEqual(tokens.count(), 1)
1835
 
 
1836
 
        self.assertEqual(len(mail.outbox), 1)
1837
 
        mail_content = unicode(mail.outbox[0].message())
1838
 
        self.assertNotIn(self.account.preferredemail.email, mail_content)
1839
 
        self.assertIn(email.email, mail_content)
 
1794
        # and a token was generated for the user's stored email
 
1795
        tokens = AuthToken.objects.filter(
 
1796
            token_type=AuthTokenType.PASSWORDRECOVERY,
 
1797
            email=expected_email)
 
1798
        self.assertEqual(tokens.count(), 1)
 
1799
        # and that's the only token generated
 
1800
        self.assertEqual(
 
1801
            1,
 
1802
            AuthToken.objects.filter(
 
1803
                token_type=AuthTokenType.PASSWORDRECOVERY
 
1804
            ).count()
 
1805
        )
 
1806
 
 
1807
    def test_reset_password_given_preferred_email(self):
 
1808
        # Given an account with a preferred email address
 
1809
        self.factory.make_account(email="preferred@mail.com")
 
1810
 
 
1811
        # When a password reset request matches the preferred email.
 
1812
        # Note the address matches but has distinct case, which for unicode
 
1813
        # may be a distinct email address, so it's important we use the stored
 
1814
        # address, not this one given in the request.
 
1815
        result = self.do_post({'email': "PREFERRED@mail.com"}, status_code=201)
 
1816
 
 
1817
        # Then the password reset uses the stored preferred email
 
1818
        self.assert_email_used(result, 'preferred@mail.com')
 
1819
 
 
1820
    def test_reset_password_given_new_email_uses_preferred(self):
 
1821
        # Given an account with one preferred email address
 
1822
        account = self.factory.make_account(email="preferred@mail.com")
 
1823
        # and one new email address
 
1824
        self.factory.make_email_for_account(
 
1825
            account, email="new@mail.com", status=EmailStatus.NEW)
 
1826
 
 
1827
        # When a password reset gives an email matching the new email
 
1828
        result = self.do_post({'email': "NEW@mail.com"}, status_code=201)
 
1829
 
 
1830
        # Then password reset uses the stored preferred email
 
1831
        self.assert_email_used(result, 'preferred@mail.com')
 
1832
 
 
1833
    def test_reset_password_given_new_email_uses_validated(self):
 
1834
        # Given an account with one new email address
 
1835
        account = self.factory.make_account(
 
1836
            email="new@mail.com", email_validated=False)
 
1837
        # and one validated email address
 
1838
        self.factory.make_email_for_account(
 
1839
            account, email="validated@mail.com", status=EmailStatus.VALIDATED)
 
1840
 
 
1841
        # When a password reset gives an email matching the new email
 
1842
        result = self.do_post({'email': "NEW@mail.com"}, status_code=201)
 
1843
 
 
1844
        # Then password reset uses the stored validated email
 
1845
        self.assert_email_used(result, 'validated@mail.com')
 
1846
 
 
1847
    def test_reset_password_given_new_email_uses_new(self):
 
1848
        # Given an account with one new email address
 
1849
        self.factory.make_account(
 
1850
            email="new@mail.com", email_validated=False)
 
1851
 
 
1852
        # When a password reset request matches the new address
 
1853
        result = self.do_post({'email': "NEW@mail.com"}, status_code=201)
 
1854
 
 
1855
        # Then the password reset uses the stored new email
 
1856
        self.assert_email_used(result, 'new@mail.com')
 
1857
 
 
1858
    def test_reset_password_given_validated_email_uses_it(self):
 
1859
        # Given an account with one preferred email address
 
1860
        account = self.factory.make_account(email="preferred@mail.com")
 
1861
        # and one validated email address
 
1862
        self.factory.make_email_for_account(
 
1863
            account, email="validated@mail.com", status=EmailStatus.VALIDATED)
 
1864
 
 
1865
        # When a password reset request matches the validated address
 
1866
        result = self.do_post({'email': "VALIDATED@mail.com"}, status_code=201)
 
1867
 
 
1868
        # Then the password reset uses the stored validated email
 
1869
        self.assert_email_used(result, 'validated@mail.com')
1840
1870
 
1841
1871
    def test_too_many_tokens(self):
1842
1872
        with self.settings(MAX_PASSWORD_RESET_TOKENS=0):