|
@@ -9,9 +9,41 @@ from django.conf import settings
|
|
|
from sentry.models import AuthIdentity, AuthProvider, OrganizationMember
|
|
|
|
|
|
|
|
|
+def _sso_params(member):
|
|
|
+ """
|
|
|
+ Return a tuple of (requires_sso, sso_is_valid) for a given member.
|
|
|
+ """
|
|
|
+ # TODO(dcramer): we want to optimize this access pattern as its several
|
|
|
+ # network hops and needed in a lot of places
|
|
|
+ try:
|
|
|
+ auth_provider = AuthProvider.objects.get(
|
|
|
+ organization=member.organization_id,
|
|
|
+ )
|
|
|
+ except AuthProvider.DoesNotExist:
|
|
|
+ sso_is_valid = True
|
|
|
+ requires_sso = False
|
|
|
+ else:
|
|
|
+ if auth_provider.flags.allow_unlinked:
|
|
|
+ requires_sso = False
|
|
|
+ sso_is_valid = True
|
|
|
+ else:
|
|
|
+ requires_sso = True
|
|
|
+ try:
|
|
|
+ auth_identity = AuthIdentity.objects.get(
|
|
|
+ auth_provider=auth_provider,
|
|
|
+ user=member.user_id,
|
|
|
+ )
|
|
|
+ except AuthIdentity.DoesNotExist:
|
|
|
+ sso_is_valid = False
|
|
|
+ else:
|
|
|
+ sso_is_valid = auth_identity.is_valid(member)
|
|
|
+ return requires_sso, sso_is_valid
|
|
|
+
|
|
|
+
|
|
|
class BaseAccess(object):
|
|
|
is_active = False
|
|
|
sso_is_valid = False
|
|
|
+ requires_sso = False
|
|
|
# teams with valid access
|
|
|
teams = ()
|
|
|
# teams with valid membership
|
|
@@ -52,13 +84,15 @@ class Access(BaseAccess):
|
|
|
# TODO(dcramer): this is still a little gross, and ideally backend access
|
|
|
# would be based on the same scopes as API access so theres clarity in
|
|
|
# what things mean
|
|
|
- def __init__(self, scopes, is_active, teams, memberships, sso_is_valid):
|
|
|
+ def __init__(self, scopes, is_active, teams, memberships, sso_is_valid,
|
|
|
+ requires_sso):
|
|
|
self.teams = teams
|
|
|
self.memberships = memberships
|
|
|
self.scopes = scopes
|
|
|
|
|
|
self.is_active = is_active
|
|
|
self.sso_is_valid = sso_is_valid
|
|
|
+ self.requires_sso = requires_sso
|
|
|
|
|
|
|
|
|
def from_request(request, organization, scopes=None):
|
|
@@ -66,13 +100,26 @@ def from_request(request, organization, scopes=None):
|
|
|
return DEFAULT
|
|
|
|
|
|
if request.is_superuser():
|
|
|
+ # we special case superuser so that if they're a member of the org
|
|
|
+ # they must still follow SSO checks, but they gain global access
|
|
|
+ try:
|
|
|
+ member = OrganizationMember.objects.get(
|
|
|
+ user=request.user,
|
|
|
+ organization=organization,
|
|
|
+ )
|
|
|
+ except OrganizationMember.DoesNotExist:
|
|
|
+ requires_sso, sso_is_valid = False, True
|
|
|
+ else:
|
|
|
+ requires_sso, sso_is_valid = _sso_params(member)
|
|
|
+
|
|
|
team_list = list(organization.team_set.all())
|
|
|
return Access(
|
|
|
scopes=scopes if scopes is not None else settings.SENTRY_SCOPES,
|
|
|
is_active=True,
|
|
|
teams=team_list,
|
|
|
memberships=team_list,
|
|
|
- sso_is_valid=True,
|
|
|
+ sso_is_valid=sso_is_valid,
|
|
|
+ requires_sso=requires_sso,
|
|
|
)
|
|
|
return from_user(request.user, organization, scopes=scopes)
|
|
|
|
|
@@ -101,25 +148,7 @@ def from_user(user, organization, scopes=None):
|
|
|
def from_member(member, scopes=None):
|
|
|
# TODO(dcramer): we want to optimize this access pattern as its several
|
|
|
# network hops and needed in a lot of places
|
|
|
- try:
|
|
|
- auth_provider = AuthProvider.objects.get(
|
|
|
- organization=member.organization_id,
|
|
|
- )
|
|
|
- except AuthProvider.DoesNotExist:
|
|
|
- sso_is_valid = True
|
|
|
- else:
|
|
|
- if auth_provider.flags.allow_unlinked:
|
|
|
- sso_is_valid = True
|
|
|
- else:
|
|
|
- try:
|
|
|
- auth_identity = AuthIdentity.objects.get(
|
|
|
- auth_provider=auth_provider,
|
|
|
- user=member.user_id,
|
|
|
- )
|
|
|
- except AuthIdentity.DoesNotExist:
|
|
|
- sso_is_valid = False
|
|
|
- else:
|
|
|
- sso_is_valid = auth_identity.is_valid(member)
|
|
|
+ requires_sso, sso_is_valid = _sso_params(member)
|
|
|
|
|
|
team_memberships = member.get_teams()
|
|
|
if member.organization.flags.allow_joinleave:
|
|
@@ -134,6 +163,7 @@ def from_member(member, scopes=None):
|
|
|
|
|
|
return Access(
|
|
|
is_active=True,
|
|
|
+ requires_sso=requires_sso,
|
|
|
sso_is_valid=sso_is_valid,
|
|
|
scopes=scopes,
|
|
|
memberships=team_memberships,
|
|
@@ -142,24 +172,11 @@ def from_member(member, scopes=None):
|
|
|
|
|
|
|
|
|
class NoAccess(BaseAccess):
|
|
|
- @property
|
|
|
- def sso_is_valid(self):
|
|
|
- return True
|
|
|
-
|
|
|
- @property
|
|
|
- def is_active(self):
|
|
|
- return False
|
|
|
-
|
|
|
- @property
|
|
|
- def teams(self):
|
|
|
- return ()
|
|
|
-
|
|
|
- @property
|
|
|
- def memberships(self):
|
|
|
- return ()
|
|
|
-
|
|
|
- @property
|
|
|
- def scopes(self):
|
|
|
- return frozenset()
|
|
|
+ requires_sso = False
|
|
|
+ sso_is_valid = True
|
|
|
+ is_active = False
|
|
|
+ teams = ()
|
|
|
+ memberships = ()
|
|
|
+ scopes = frozenset()
|
|
|
|
|
|
DEFAULT = NoAccess()
|