Browse Source

fix(api): Allow signed links to pass through SSO (#6050)

* fix(api): Allow signed links to pass through SSO

Fixes GH-6044

* fix(api): Add tests for skipping sso with signed link

* ref(api): Refactor user_signed_link check for readablity
Daniel Griesser 7 years ago
parent
commit
6e821514e9
3 changed files with 83 additions and 6 deletions
  1. 13 2
      src/sentry/api/bases/organization.py
  2. 12 1
      src/sentry/utils/auth.py
  3. 58 3
      tests/integration/test_api.py

+ 13 - 2
src/sentry/api/bases/organization.py

@@ -45,8 +45,19 @@ class OrganizationPermission(ScopedPermission):
 
         else:
             request.access = access.from_request(request, organization)
-            # session auth needs to confirm various permissions
-            if request.user.is_authenticated() and self.needs_sso(request, organization):
+
+            if auth.is_user_signed_request(request):
+                # if the user comes from a signed request
+                # we let him pass if sso is enabled
+                logger.info(
+                    'access.signed-sso-passthrough',
+                    extra={
+                        'organization_id': organization.id,
+                        'user_id': request.user.id,
+                    }
+                )
+            elif request.user.is_authenticated() and self.needs_sso(request, organization):
+                # session auth needs to confirm various permissions
                 logger.info(
                     'access.must-sso',
                     extra={

+ 12 - 1
src/sentry/utils/auth.py

@@ -195,7 +195,8 @@ def login(request, user, passed_2fa=None, after_2fa=None, organization_id=None):
     """
     has_2fa = Authenticator.objects.user_has_2fa(user)
     if passed_2fa is None:
-        passed_2fa = (request.session.get(MFA_SESSION_KEY, '') == six.text_type(user.id))
+        passed_2fa = (request.session.get(MFA_SESSION_KEY, '')
+                      == six.text_type(user.id))
 
     if has_2fa and not passed_2fa:
         request.session['_pending_2fa'] = [user.id, time(), organization_id]
@@ -266,6 +267,16 @@ def has_user_registration():
     return features.has('auth:register') and options.get('auth.allow-registration')
 
 
+def is_user_signed_request(request):
+    """
+    This function returns True if the request is a signed valid link
+    """
+    try:
+        return request.user_from_signed_request
+    except AttributeError:
+        return False
+
+
 class EmailAuthBackend(ModelBackend):
     """
     Authenticate against django.contrib.auth.models.User.

+ 58 - 3
tests/integration/test_api.py

@@ -2,9 +2,12 @@ from __future__ import absolute_import
 
 import six
 
+from django.core.urlresolvers import reverse
+
 from sentry.models import AuthIdentity, AuthProvider
 from sentry.testutils import AuthProviderTestCase
 from sentry.utils.auth import SSO_SESSION_KEY
+from sentry.utils.linksign import generate_signed_link
 
 
 class AuthenticationTest(AuthProviderTestCase):
@@ -12,8 +15,10 @@ class AuthenticationTest(AuthProviderTestCase):
         user = self.create_user('foo@example.com', is_superuser=False)
         organization = self.create_organization(name='foo')
         team = self.create_team(name='bar', organization=organization)
-        project = self.create_project(name='baz', organization=organization, team=team)
-        member = self.create_member(user=user, organization=organization, teams=[team])
+        project = self.create_project(
+            name='baz', organization=organization, team=team)
+        member = self.create_member(
+            user=user, organization=organization, teams=[team])
         setattr(member.flags, 'sso:linked', True)
         member.save()
         group = self.create_group(project=project)
@@ -60,4 +65,54 @@ class AuthenticationTest(AuthProviderTestCase):
         # now that SSO is marked as complete, we should be able to access dash
         for path in paths:
             resp = self.client.get(path)
-            assert resp.status_code == 200, (path, resp.status_code, resp.content)
+            assert resp.status_code == 200, (path,
+                                             resp.status_code, resp.content)
+
+    def test_sso_auth_required_signed_link(self):
+        user = self.create_user('foo@example.com', is_superuser=False)
+        organization = self.create_organization(name='foo')
+        team = self.create_team(name='bar', organization=organization)
+        project = self.create_project(
+            name='baz', organization=organization, team=team)
+        member = self.create_member(
+            user=user, organization=organization, teams=[team])
+        setattr(member.flags, 'sso:linked', True)
+        member.save()
+        group = self.create_group(project=project)
+        self.create_event(group=group)
+
+        auth_provider = AuthProvider.objects.create(
+            organization=organization,
+            provider='dummy',
+            flags=0,
+        )
+
+        AuthIdentity.objects.create(
+            auth_provider=auth_provider,
+            user=user,
+        )
+
+        self.login_as(user)
+
+        unsigned_link = reverse(
+            'sentry-api-0-project-fix-processing-issues',
+            kwargs={
+                'project_slug': project.slug,
+                'organization_slug': organization.slug,
+            }
+        )
+
+        resp = self.client.get(unsigned_link)
+        assert resp.status_code == 401, (resp.status_code, resp.content)
+
+        signed_link = generate_signed_link(
+            user,
+            'sentry-api-0-project-fix-processing-issues',
+            kwargs={
+                'project_slug': project.slug,
+                'organization_slug': organization.slug,
+            }
+        )
+
+        resp = self.client.get(signed_link)
+        assert resp.status_code == 200