Browse Source

Merge pull request #5646 from getsentry/sso/use-secondary-emails

sso: utilize secondary emails for existing accounts
David Cramer 7 years ago
parent
commit
4ced96692f

+ 11 - 2
src/sentry/auth/helper.py

@@ -15,7 +15,7 @@ from django.utils.translation import ugettext_lazy as _
 from sentry.app import locks
 from sentry.models import (
     AuditLogEntry, AuditLogEntryEvent, AuthIdentity, AuthProvider, Organization,
-    OrganizationMember, OrganizationMemberTeam, User
+    OrganizationMember, OrganizationMemberTeam, User, UserEmail
 )
 from sentry.tasks.auth import email_missing_links
 from sentry.utils import auth
@@ -391,6 +391,15 @@ class AuthHelper(object):
     def _get_identifier(self, identity):
         return identity.get('email') or identity.get('id')
 
+    def _find_existing_user(self, email):
+        return User.objects.filter(
+            id__in=UserEmail.objects.filter(
+                email__iexact=email,
+                is_verified=True,
+            ).values('user'),
+            is_active=True,
+        ).first()
+
     def _handle_unknown_identity(self, identity):
         """
         Flow is activated upon a user logging in to where an AuthIdentity is
@@ -412,7 +421,7 @@ class AuthHelper(object):
             # TODO(dcramer): its possible they have multiple accounts and at
             # least one is managed (per the check below)
             try:
-                existing_user = auth.find_users(identity['email'], is_active=True)[0]
+                existing_user = self._find_existing_user(identity['email'])
             except IndexError:
                 existing_user = None
 

+ 55 - 1
tests/sentry/web/frontend/test_auth_organization_login.py

@@ -2,7 +2,7 @@ from __future__ import absolute_import
 
 from django.core.urlresolvers import reverse
 
-from sentry.models import AuthIdentity, AuthProvider, OrganizationMember
+from sentry.models import AuthIdentity, AuthProvider, OrganizationMember, UserEmail
 from sentry.testutils import AuthProviderTestCase
 
 
@@ -247,6 +247,60 @@ class OrganizationAuthLoginTest(AuthProviderTestCase):
         assert getattr(member.flags, 'sso:linked')
         assert not getattr(member.flags, 'sso:invalid')
 
+    def test_flow_as_unauthenticated_existing_matched_user_via_secondary_email(self):
+        organization = self.create_organization(name='foo', owner=self.user)
+        auth_provider = AuthProvider.objects.create(
+            organization=organization,
+            provider='dummy',
+        )
+        user = self.create_user('foo@example.com')
+        UserEmail.objects.create(user=user, email='bar@example.com', is_verified=True)
+
+        path = reverse('sentry-auth-organization', args=[organization.slug])
+
+        resp = self.client.post(path)
+
+        assert resp.status_code == 200
+        assert self.provider.TEMPLATE in resp.content.decode('utf-8')
+
+        path = reverse('sentry-auth-sso')
+
+        resp = self.client.post(path, {'email': user.email})
+
+        self.assertTemplateUsed(resp, 'sentry/auth-confirm-identity.html')
+        assert resp.status_code == 200
+        assert resp.context['existing_user'] == user
+        assert resp.context['login_form']
+
+        resp = self.client.post(path, {
+            'op': 'login',
+            'username': user.username,
+            'password': 'admin',
+        })
+
+        self.assertTemplateUsed(resp, 'sentry/auth-confirm-link.html')
+        assert resp.status_code == 200
+
+        resp = self.client.post(path, {'op': 'confirm'})
+
+        assert resp.status_code == 302
+        assert resp['Location'] == 'http://testserver' + reverse('sentry-login')
+
+        auth_identity = AuthIdentity.objects.get(
+            auth_provider=auth_provider,
+        )
+
+        new_user = auth_identity.user
+        assert new_user == user
+
+        member = OrganizationMember.objects.get(
+            organization=organization,
+            user=user,
+        )
+
+        assert getattr(member.flags, 'sso:linked')
+        assert not getattr(member.flags, 'sso:invalid')
+
     def test_flow_as_unauthenticated_existing_unmatched_user_with_merge(self):
         organization = self.create_organization(name='foo', owner=self.user)
         auth_provider = AuthProvider.objects.create(