Browse Source

ref(hc): Remove OrganizationMember from get_user_auth_state signature (#45621)

This is part of removing non-RPC-serializable types from all hybrid
cloud service methods.

Introduce `RpcOrganizationMemberSummary`, because
`DatabaseBackedOrganizationService.serialize_member` makes some costly
database queries that we should avoid in `OrganizationMemberAccess` and
elsewhere.
Ryan Skonnord 2 years ago
parent
commit
b859564931

+ 7 - 2
src/sentry/auth/access.py

@@ -42,6 +42,7 @@ from sentry.services.hybrid_cloud.organization import (
     RpcUserOrganizationContext,
     organization_service,
 )
+from sentry.services.hybrid_cloud.organization.impl import DatabaseBackedOrganizationService
 from sentry.services.hybrid_cloud.user import RpcUser, user_service
 from sentry.utils import metrics
 from sentry.utils.request_cache import request_cache
@@ -587,7 +588,7 @@ class OrganizationMemberAccess(DbAccess):
         auth_state = auth_service.get_user_auth_state(
             organization_id=member.organization_id,
             is_superuser=False,
-            org_member=member,
+            org_member=DatabaseBackedOrganizationService.summarize_member(member),
             user_id=member.user_id,
         )
         sso_state = auth_state.sso_state
@@ -976,7 +977,11 @@ def from_request(
             user_id=request.user.id,
             organization_id=organization.id,
             is_superuser=is_superuser,
-            org_member=member,
+            org_member=(
+                DatabaseBackedOrganizationService.summarize_member(member)
+                if member is not None
+                else None
+            ),
         ).sso_state
 
         return OrganizationGlobalAccess(

+ 6 - 3
src/sentry/services/hybrid_cloud/auth/__init__.py

@@ -15,10 +15,13 @@ from rest_framework.authentication import BaseAuthentication
 from rest_framework.request import Request
 
 from sentry.api.authentication import ApiKeyAuthentication, TokenAuthentication
-from sentry.models import OrganizationMember
 from sentry.relay.utils import get_header_relay_id, get_header_relay_signature
 from sentry.services.hybrid_cloud import InterfaceWithLifecycle, silo_mode_delegation, stubbed
-from sentry.services.hybrid_cloud.organization import RpcOrganization, RpcOrganizationMember
+from sentry.services.hybrid_cloud.organization import (
+    RpcOrganization,
+    RpcOrganizationMember,
+    RpcOrganizationMemberSummary,
+)
 from sentry.services.hybrid_cloud.user import RpcUser
 from sentry.silo import SiloMode
 from sentry.utils.linksign import find_signature
@@ -298,7 +301,7 @@ class AuthService(InterfaceWithLifecycle):
         user_id: int,
         is_superuser: bool,
         organization_id: Optional[int],
-        org_member: Optional[Union[RpcOrganizationMember, OrganizationMember]],
+        org_member: Optional[RpcOrganizationMemberSummary],
     ) -> RpcAuthState:
         pass
 

+ 5 - 10
src/sentry/services/hybrid_cloud/auth/impl.py

@@ -41,6 +41,7 @@ from sentry.services.hybrid_cloud.organization import (
     RpcOrganization,
     RpcOrganizationMember,
     RpcOrganizationMemberFlags,
+    RpcOrganizationMemberSummary,
     organization_service,
 )
 from sentry.services.hybrid_cloud.organization.impl import DatabaseBackedOrganizationService
@@ -57,10 +58,7 @@ _SSO_NONMEMBER = RpcMemberSsoState(False, False)
 # When OrgMemberMapping table is created for the control silo, org_member_class will use that rather
 # than the OrganizationMember type.
 def query_sso_state(
-    organization_id: int | None,
-    is_super_user: bool,
-    member: RpcOrganizationMember | OrganizationMember | None,
-    org_member_class: Any = OrganizationMember,
+    organization_id: int | None, is_super_user: bool, member: RpcOrganizationMemberSummary | None
 ) -> RpcMemberSsoState:
     """
     Check whether SSO is required and valid for a given member.
@@ -106,7 +104,7 @@ def query_sso_state(
                         organization_id=org_id
                     )
                     return (
-                        org_member_class.objects.filter(
+                        OrganizationMember.objects.filter(
                             Q(id__in=all_top_dogs_from_teams) | Q(role=roles.get_top_dog().id),
                             organization_id=org_id,
                             user__is_active=True,
@@ -233,13 +231,10 @@ class DatabaseBackedAuthService(AuthService):
         user_id: int,
         is_superuser: bool,
         organization_id: int | None,
-        org_member: RpcOrganizationMember | OrganizationMember | None,
+        org_member: RpcOrganizationMemberSummary | None,
     ) -> RpcAuthState:
         sso_state = query_sso_state(
-            organization_id=organization_id,
-            is_super_user=is_superuser,
-            member=org_member,
-            org_member_class=OrganizationMember,
+            organization_id=organization_id, is_super_user=is_superuser, member=org_member
         )
         permissions: List[str] = list()
         # "permissions" is a bit of a misnomer -- these are all admin level permissions, and the intent is that if you

+ 7 - 4
src/sentry/services/hybrid_cloud/organization/__init__.py

@@ -75,17 +75,20 @@ class RpcOrganizationMemberFlags:
 
 
 @dataclass
-class RpcOrganizationMember:
+class RpcOrganizationMemberSummary:
     id: int = -1
     organization_id: int = -1
-    # This can be null when the user is deleted.
-    user_id: Optional[int] = None
+    user_id: Optional[int] = None  # This can be null when the user is deleted.
+    flags: RpcOrganizationMemberFlags = field(default_factory=lambda: RpcOrganizationMemberFlags())
+
+
+@dataclass
+class RpcOrganizationMember(RpcOrganizationMemberSummary):
     member_teams: List[RpcTeamMember] = field(default_factory=list)
     role: str = ""
     has_global_access: bool = False
     project_ids: List[int] = field(default_factory=list)
     scopes: List[str] = field(default_factory=list)
-    flags: RpcOrganizationMemberFlags = field(default_factory=lambda: RpcOrganizationMemberFlags())
 
     def get_audit_log_metadata(self, user_email: str) -> Mapping[str, Any]:
         team_ids = [mt.team_id for mt in self.member_teams]

+ 13 - 0
src/sentry/services/hybrid_cloud/organization/impl.py

@@ -24,6 +24,7 @@ from sentry.services.hybrid_cloud.organization import (
     RpcOrganizationInvite,
     RpcOrganizationMember,
     RpcOrganizationMemberFlags,
+    RpcOrganizationMemberSummary,
     RpcOrganizationSummary,
     RpcProject,
     RpcTeam,
@@ -88,6 +89,18 @@ class DatabaseBackedOrganizationService(OrganizationService):
 
         return rpc_member
 
+    @classmethod
+    def summarize_member(
+        cls,
+        member: OrganizationMember,
+    ) -> RpcOrganizationMemberSummary:
+        return RpcOrganizationMemberSummary(
+            id=member.id,
+            organization_id=member.organization_id,
+            user_id=member.user_id,
+            flags=cls._serialize_member_flags(member),
+        )
+
     @classmethod
     def _serialize_flags(cls, org: Organization) -> RpcOrganizationFlags:
         result = RpcOrganizationFlags()