def test_require_2fa__cannot_delete_last_auth(self, email_log):
        self._require_2fa_for_organization()

        # enroll in one auth method
        interface = TotpInterface()
        interface.enroll(self.user)
        auth = interface.authenticator

        url = reverse(
            'sentry-api-0-user-authenticator-details',
            kwargs={
                'user_id': self.user.id,
                'auth_id': auth.id,
            }
        )

        resp = self.client.delete(url, format='json')
        assert resp.status_code == 403, (resp.status_code, resp.content)
        self.assertIn('requires 2FA', resp.content)

        assert Authenticator.objects.filter(
            id=auth.id,
        ).exists()

        assert email_log.info.call_count == 0
예제 #2
0
    def setUp(self):
        self.owner = self.create_user()
        self.org = self.create_organization(owner=self.owner)

        self.member = self.create_user()
        self.member_om = self.create_member(
            organization=self.org, user=self.member, role="member", teams=[]
        )
        self.login_as(self.member)
        totp = TotpInterface()
        totp.enroll(self.member)
        self.interface_id = totp.authenticator.id
        assert Authenticator.objects.filter(user=self.member).exists()
예제 #3
0
    def test_send_setup_2fa_emails_no_non_compliant_members(self):
        owner = self.create_user('*****@*****.**')
        TotpInterface().enroll(owner)
        org = self.create_organization(owner=owner)

        for num in range(0, 10):
            user = self.create_user('*****@*****.**' % num)
            self.create_member(organization=org, user=user)
            TotpInterface().enroll(user)

        with self.options({'system.url-prefix':
                           'http://example.com'}), self.tasks():
            org.send_setup_2fa_emails()

        assert len(mail.outbox) == 0
    def setUp(self):
        self.owner = self.create_user()
        self.org = self.create_organization(owner=self.owner)

        self.member = self.create_user()
        self.member_om = self.create_member(
            organization=self.org,
            user=self.member,
            role='member',
            teams=[],
        )
        self.login_as(self.member)
        totp = TotpInterface()
        totp.enroll(self.member)
        self.interface_id = totp.authenticator.id
        assert Authenticator.objects.filter(user=self.member).exists()
예제 #5
0
    def setUp(self):
        self.user = self.create_user('*****@*****.**')
        self.org = self.create_organization(owner=self.user, name='saml2-org')

        # enable require 2FA and enroll user
        TotpInterface().enroll(self.user)
        self.org.update(
            flags=models.F('flags').bitor(Organization.flags.require_2fa))
        assert self.org.flags.require_2fa.is_set

        self.auth_provider = AuthProvider.objects.create(
            provider=self.provider_name,
            config=dummy_provider_config,
            organization=self.org,
        )

        # The system.url-prefix, which is used to generate absolute URLs, must
        # have a TLD for the SAML2 library to consider the URL generated for
        # the ACS endpoint valid.
        self.url_prefix = settings.SENTRY_OPTIONS.get('system.url-prefix')

        settings.SENTRY_OPTIONS.update({
            'system.url-prefix':
            'http://testserver.com',
        })

        super(AuthSAML2Test, self).setUp()
    def test_require_2fa__delete_with_multiple_auth__ok(self, email_log):
        self._require_2fa_for_organization()

        new_options = settings.SENTRY_OPTIONS.copy()
        new_options["sms.twilio-account"] = "twilio-account"

        with self.settings(SENTRY_OPTIONS=new_options):
            # enroll in two auth methods
            interface = SmsInterface()
            interface.phone_number = "5551231234"
            interface.enroll(self.user)

            interface = TotpInterface()
            interface.enroll(self.user)
            auth = interface.authenticator

            url = reverse(
                "sentry-api-0-user-authenticator-details",
                kwargs={
                    "user_id": self.user.id,
                    "auth_id": auth.id
                },
            )
            resp = self.client.delete(url, format="json")
            assert resp.status_code == 204, (resp.status_code, resp.content)

            assert not Authenticator.objects.filter(id=auth.id).exists()

            self._assert_security_email_sent("mfa-removed", email_log)
예제 #7
0
    def test_member_disable_all_2fa_blocked(self):
        TotpInterface().enroll(self.no_2fa_user)
        self.login_as(self.no_2fa_user)
        self.assert_can_access_org_home()

        Authenticator.objects.get(user=self.no_2fa_user).delete()
        self.assert_redirected_to_2fa()
예제 #8
0
    def setUp(self):
        # 2FA enforced org
        self.org_2fa = self.create_organization(owner=self.create_user())
        self.enable_org_2fa(self.org_2fa)
        self.no_2fa_user = self.create_user()
        self.create_member(organization=self.org_2fa,
                           user=self.no_2fa_user,
                           role="member")

        # 2FA not enforced org
        self.owner = self.create_user()
        self.organization = self.create_organization(owner=self.owner)
        self.manager = self.create_user()
        self.create_member(organization=self.organization,
                           user=self.manager,
                           role="manager")
        self.org_user = self.create_user()
        self.create_member(organization=self.organization,
                           user=self.org_user,
                           role="member")

        # 2FA enrolled user
        self.has_2fa = self.create_user()
        TotpInterface().enroll(self.has_2fa)
        self.create_member(organization=self.organization,
                           user=self.has_2fa,
                           role="manager")
        assert Authenticator.objects.user_has_2fa(self.has_2fa)
예제 #9
0
 def test_owner_can_set_2fa_single_member(self):
     org = self.create_organization(owner=self.owner)
     TotpInterface().enroll(self.owner)
     with self.options({"system.url-prefix":
                        "http://example.com"}), self.tasks():
         self.assert_can_enable_org_2fa(org, self.owner)
     assert len(mail.outbox) == 0
예제 #10
0
    def test_org_requires_2fa_with_enrolled_user(self):
        self.org_require_2fa()
        user = self.create_user()
        self.create_member(user=user, organization=self.org, role="member")

        TotpInterface().enroll(user)
        assert self.has_object_perm("GET", self.org, user=user)
예제 #11
0
 def test_can_enforce_2fa_with_non_saml_sso_enabled(self):
     org = self.create_organization(owner=self.owner)
     TotpInterface().enroll(self.owner)
     self.auth_provider = AuthProvider.objects.create(
         provider='github',
         organization=org,
     )
     self.assert_can_enable_org_2fa(self.organization, self.owner)
예제 #12
0
    def test_member_disable_all_2fa_blocked(self):
        TotpInterface().enroll(self.no_2fa_user)
        self.login_as(self.no_2fa_user)

        self.assert_can_access_org_details(self.path)

        Authenticator.objects.get(user=self.no_2fa_user).delete()
        self.assert_cannot_access_org_details(self.path)
예제 #13
0
 def test_2fa_no_signin(self):
     TotpInterface().enroll(self.user)
     resp = self.client.post(self.path, {
         'password': '******',
         'confirm_password': '******'
     })
     assert resp.status_code == 302
     assert self.client.session.get('_auth_user_id') is None
    def test_new_member_must_enable_2fa(self):
        new_user = self.create_user()
        self.create_member(organization=self.org_2fa, user=new_user, role="member")
        self.login_as(new_user)
        self.assert_cannot_access_org_details(self.path)

        TotpInterface().enroll(new_user)
        self.assert_can_access_org_details(self.path)
예제 #15
0
    def test_cannot_reset_member_2fa__org_requires_2fa(self):
        self.login_as(self.owner)
        TotpInterface().enroll(self.owner)

        self.org.update(flags=F("flags").bitor(Organization.flags.require_2fa))
        assert self.org.flags.require_2fa.is_set is True

        self.assert_cannot_remove_authenticators()
    def test_manager_can_set_2fa(self):
        org = self.create_organization(owner=self.owner)
        self.create_member(organization=org, user=self.manager, role="manager")

        self.assert_cannot_enable_org_2fa(org, self.manager, 400)
        TotpInterface().enroll(self.manager)
        with self.options({"system.url-prefix": "http://example.com"}), self.tasks():
            self.assert_can_enable_org_2fa(org, self.manager)
        self.assert_2fa_email_equal(mail.outbox, [self.owner.email])
예제 #17
0
    def test_new_member_must_enable_2fa(self):
        new_user = self.create_user()
        self.create_member(organization=self.org_2fa, user=new_user, role="member")
        self.login_as(new_user)

        self.assert_redirected_to_2fa()

        TotpInterface().enroll(new_user)
        self.assert_can_access_org_home()
 def test_remove_2fa_password(self):
     user = self.create_user('*****@*****.**')
     TotpInterface().enroll(user)
     path = reverse('sentry-account-settings-2fa-totp')
     self.login_as(user)
     resp = self.client.post(path, data={'remove': ''})
     assert resp.status_code == 200
     self.assertTemplateUsed('sentry/account/twofactor/remove.html')
     self.assertContains(resp, 'Do you want to remove the method?')
     self.assertContains(resp, 'Sentry account password')
 def test_security_renders_with_2fa(self):
     user = self.create_user('*****@*****.**')
     self.login_as(user)
     TotpInterface().enroll(user)
     path = reverse('sentry-account-security')
     resp = self.client.get(path)
     self.assertTemplateUsed('sentry/account/security.html')
     assert 'has_2fa' in resp.context
     assert resp.context['has_2fa'] is True
     self.assertContains(resp, 'Manage')
예제 #20
0
 def _create_user_and_member(self, has_2fa=False, has_user_email=True, has_member_email=False):
     user = self._create_user(has_email=has_user_email)
     if has_2fa:
         TotpInterface().enroll(user)
     if has_member_email:
         email = uuid4().hex
         member = self.create_member(organization=self.org, user=user, email=email)
     else:
         member = self.create_member(organization=self.org, user=user)
     return user, member
예제 #21
0
 def add_2fa_users_to_org(self, organization, num_of_users, num_with_2fa):
     non_compliant_members = []
     for num in range(0, num_of_users):
         user = self.create_user('*****@*****.**' % num)
         self.create_member(organization=organization, user=user)
         if num % num_with_2fa:
             TotpInterface().enroll(user)
         else:
             non_compliant_members.append(user.email)
     return non_compliant_members
예제 #22
0
    def test_bulk_users_have_2fa(self):
        user1 = self.create_user("*****@*****.**")
        user2 = self.create_user("*****@*****.**")

        TotpInterface().enroll(user1)

        assert Authenticator.objects.bulk_users_have_2fa([user1.id, user2.id, 9999]) == {
            user1.id: True,
            user2.id: False,
            9999: False,
        }
예제 #23
0
    def test_send_setup_2fa_emails(self):
        owner = self.create_user('*****@*****.**')
        TotpInterface().enroll(owner)
        org = self.create_organization(owner=owner)
        non_compliant_members = []
        for num in range(0, 10):
            user = self.create_user('*****@*****.**' % num)
            self.create_member(organization=org, user=user)
            if num % 2:
                TotpInterface().enroll(user)
            else:
                non_compliant_members.append(user.email)

        with self.options({'system.url-prefix':
                           'http://example.com'}), self.tasks():
            org.send_setup_2fa_emails()

        assert len(mail.outbox) == len(non_compliant_members)
        assert sorted([email.to[0] for email in mail.outbox
                       ]) == sorted(non_compliant_members)
예제 #24
0
    def test_require_2fa__cannot_delete_last_auth(self, email_log):
        self._require_2fa_for_organization()

        # enroll in one auth method
        interface = TotpInterface()
        interface.enroll(self.user)
        auth = interface.authenticator

        url = reverse('sentry-api-0-user-authenticator-details',
                      kwargs={
                          'user_id': self.user.id,
                          'auth_id': auth.id,
                      })

        resp = self.client.delete(url, format='json')
        assert resp.status_code == 403, (resp.status_code, resp.content)
        self.assertIn('requires 2FA', resp.content)

        assert Authenticator.objects.filter(id=auth.id, ).exists()

        assert email_log.info.call_count == 0
예제 #25
0
    def test_get_authenticator_details(self):
        interface = TotpInterface()
        interface.enroll(self.user)
        auth = interface.authenticator

        url = reverse('sentry-api-0-user-authenticator-details',
                      kwargs={
                          'user_id': self.user.id,
                          'auth_id': auth.id,
                      })

        resp = self.client.get(url)
        assert resp.status_code == 200
        assert resp.data['isEnrolled']
        assert resp.data['id'] == "totp"
        assert resp.data['authId'] == six.text_type(auth.id)

        # should not have these because enrollment
        assert 'totp_secret' not in resp.data
        assert 'form' not in resp.data
        assert 'qrcode' not in resp.data
 def test_2fa_settings_render_with_2fa(self):
     user = self.create_user('*****@*****.**')
     path = reverse('sentry-account-settings-2fa')
     self.login_as(user)
     TotpInterface().enroll(user)
     resp = self.client.get(path)
     assert resp.status_code == 200
     self.assertTemplateUsed('sentry/account/twofactor.html')
     assert 'has_2fa' in resp.context
     assert resp.context['has_2fa'] is True
     self.assertNotContains(resp,
                            'this can only be managed if 2FA is enabled')
     self.assertContains(resp, '<span class="icon-trash">')
예제 #27
0
    def test_user_has_2fa(self):
        user = self.create_user('*****@*****.**')
        assert Authenticator.objects.user_has_2fa(user) is False
        assert Authenticator.objects.filter(user=user).count() == 0

        RecoveryCodeInterface().enroll(user)

        assert Authenticator.objects.user_has_2fa(user) is False
        assert Authenticator.objects.filter(user=user).count() == 1

        TotpInterface().enroll(user)

        assert Authenticator.objects.user_has_2fa(user) is True
        assert Authenticator.objects.filter(user=user).count() == 2
 def test_remove_2fa_SSO_deletes_lost_passswords(self):
     user = self.create_user('*****@*****.**')
     user.set_unusable_password()
     user.save()
     TotpInterface().enroll(user)
     path = reverse('sentry-account-settings-2fa-totp')
     self.login_as(user)
     LostPasswordHash.objects.create(user=user)
     resp = self.client.post(path, data={'remove': ''})
     assert resp.status_code == 200
     self.assertTemplateUsed('sentry/account/twofactor/remove.html')
     self.assertContains(resp, 'Do you want to remove the method?')
     self.assertNotContains(resp, 'Sentry account password')
     assert not LostPasswordHash.objects.filter(user=user).exists()
    def test_get_authenticator_details(self):
        interface = TotpInterface()
        interface.enroll(self.user)
        auth = interface.authenticator

        url = reverse(
            "sentry-api-0-user-authenticator-details",
            kwargs={
                "user_id": self.user.id,
                "auth_id": auth.id
            },
        )

        resp = self.client.get(url)
        assert resp.status_code == 200
        assert resp.data["isEnrolled"]
        assert resp.data["id"] == "totp"
        assert resp.data["authId"] == six.text_type(auth.id)

        # should not have these because enrollment
        assert "totp_secret" not in resp.data
        assert "form" not in resp.data
        assert "qrcode" not in resp.data
    def test_get_authenticator_details(self):
        interface = TotpInterface()
        interface.enroll(self.user)
        auth = interface.authenticator

        url = reverse(
            'sentry-api-0-user-authenticator-details',
            kwargs={
                'user_id': self.user.id,
                'auth_id': auth.id,
            }
        )

        resp = self.client.get(url)
        assert resp.status_code == 200
        assert resp.data['isEnrolled']
        assert resp.data['id'] == "totp"
        assert resp.data['authId'] == six.text_type(auth.id)

        # should not have these because enrollment
        assert 'totp_secret' not in resp.data
        assert 'form' not in resp.data
        assert 'qrcode' not in resp.data
    def test_owner_can_set_org_2fa(self):
        org = self.create_organization(owner=self.owner)
        TotpInterface().enroll(self.owner)
        user_emails_without_2fa = self.add_2fa_users_to_org(org)

        with self.options({"system.url-prefix": "http://example.com"}), self.tasks():
            self.assert_can_enable_org_2fa(org, self.owner)
        self.assert_2fa_email_equal(mail.outbox, user_emails_without_2fa)

        mail.outbox = []
        with self.options({"system.url-prefix": "http://example.com"}), self.tasks():
            response = self.api_disable_org_2fa(org, self.owner)

        assert response.status_code == 200
        assert not Organization.objects.get(id=org.id).flags.require_2fa
        assert len(mail.outbox) == 0
    def test_list_all_authenticators(self):
        user = self.create_user(email="*****@*****.**", is_superuser=True)
        self.login_as(user=user, superuser=True)
        url = reverse("sentry-api-0-user-authenticator-index",
                      kwargs={"user_id": "me"})

        resp = self.client.get(url, format="json")

        assert resp.status_code == 200
        interface = [i for i in resp.data if i["id"] == "totp"][0]
        assert not interface["isEnrolled"]

        # Enroll in Totp - should still be listed
        TotpInterface().enroll(user)

        resp = self.client.get(url, format="json")
        assert resp.status_code == 200
        interface = [i for i in resp.data if i["id"] == "totp"][0]
        assert interface["isEnrolled"]
예제 #33
0
    def test_list_all_authenticators(self):
        user = self.create_user(email='*****@*****.**', is_superuser=True)
        self.login_as(user=user, superuser=True)
        url = reverse(
            'sentry-api-0-user-authenticator-index', kwargs={'user_id': 'me'}
        )

        resp = self.client.get(url, format='json')

        assert resp.status_code == 200
        interface = [i for i in resp.data if i['id'] == "totp"][0]
        assert not interface['isEnrolled']

        # Enroll in Totp - should still be listed
        TotpInterface().enroll(user)

        resp = self.client.get(url, format='json')
        assert resp.status_code == 200
        interface = [i for i in resp.data if i['id'] == "totp"][0]
        assert interface['isEnrolled']
예제 #34
0
    def test_various_options(self):
        org = self.create_organization(owner=self.user)
        initial = org.get_audit_log_data()
        AuditLogEntry.objects.filter(organization=org).delete()

        self.login_as(user=self.user)
        url = reverse(
            'sentry-api-0-organization-details', kwargs={
                'organization_slug': org.slug,
            }
        )

        data = {
            'openMembership': False,
            'isEarlyAdopter': True,
            'allowSharedIssues': False,
            'enhancedPrivacy': True,
            'dataScrubber': True,
            'dataScrubberDefaults': True,
            'sensitiveFields': [u'password'],
            'safeFields': [u'email'],
            'storeCrashReports': True,
            'scrubIPAddresses': True,
            'scrapeJavaScript': False,
            'defaultRole': 'owner',
            'require2FA': True
        }

        # needed to set require2FA
        interface = TotpInterface()
        interface.enroll(self.user)
        assert Authenticator.objects.user_has_2fa(self.user)

        response = self.client.put(url, data=data)
        assert response.status_code == 200, response.content
        org = Organization.objects.get(id=org.id)
        assert initial != org.get_audit_log_data()

        assert org.flags.early_adopter
        assert not org.flags.allow_joinleave
        assert org.flags.disable_shared_issues
        assert org.flags.enhanced_privacy
        assert org.flags.require_2fa
        assert org.default_role == 'owner'

        options = {o.key: o.value for o in OrganizationOption.objects.filter(
            organization=org,
        )}

        assert options.get('sentry:require_scrub_defaults')
        assert options.get('sentry:require_scrub_data')
        assert options.get('sentry:require_scrub_ip_address')
        assert options.get('sentry:sensitive_fields') == ['password']
        assert options.get('sentry:safe_fields') == ['email']
        assert options.get('sentry:store_crash_reports') is True
        assert options.get('sentry:scrape_javascript') is False

        # log created
        log = AuditLogEntry.objects.get(organization=org)
        assert log.get_event_display() == 'org.edit'
        # org fields & flags
        assert u'to {}'.format(data['defaultRole']) in log.data['default_role']
        assert u'to {}'.format(data['openMembership']) in log.data['allow_joinleave']
        assert u'to {}'.format(data['isEarlyAdopter']) in log.data['early_adopter']
        assert u'to {}'.format(data['enhancedPrivacy']) in log.data['enhanced_privacy']
        assert u'to {}'.format(not data['allowSharedIssues']) in log.data['disable_shared_issues']
        assert u'to {}'.format(data['require2FA']) in log.data['require_2fa']
        # org options
        assert u'to {}'.format(data['dataScrubber']) in log.data['dataScrubber']
        assert u'to {}'.format(data['dataScrubberDefaults']) in log.data['dataScrubberDefaults']
        assert u'to {}'.format(data['sensitiveFields']) in log.data['sensitiveFields']
        assert u'to {}'.format(data['safeFields']) in log.data['safeFields']
        assert u'to {}'.format(data['scrubIPAddresses']) in log.data['scrubIPAddresses']
        assert u'to {}'.format(data['scrapeJavaScript']) in log.data['scrapeJavaScript']
 def _enroll_user_in_2fa(self):
     interface = TotpInterface()
     interface.enroll(self.user)
     self.assertTrue(Authenticator.objects.user_has_2fa(self.user))