Skip to content

Commit

Permalink
chore(users): Move User base endpoint file to users directory (#77501)
Browse files Browse the repository at this point in the history
issue ref(#73856)
  • Loading branch information
Christinarlong committed Sep 13, 2024
1 parent 3f245a1 commit ff9a79b
Show file tree
Hide file tree
Showing 31 changed files with 196 additions and 183 deletions.
153 changes: 2 additions & 151 deletions src/sentry/api/bases/user.py
Original file line number Diff line number Diff line change
@@ -1,152 +1,3 @@
from __future__ import annotations
from sentry.users.api.bases.user import RegionSiloUserEndpoint, UserAndStaffPermission, UserEndpoint

from django.contrib.auth.models import AnonymousUser
from rest_framework.permissions import BasePermission
from rest_framework.request import Request
from typing_extensions import override

from sentry.api.base import Endpoint
from sentry.api.exceptions import ResourceDoesNotExist
from sentry.api.permissions import SentryPermission, StaffPermissionMixin
from sentry.auth.services.access.service import access_service
from sentry.auth.superuser import is_active_superuser, superuser_has_permission
from sentry.auth.system import is_system_auth
from sentry.models.organization import OrganizationStatus
from sentry.models.organizationmapping import OrganizationMapping
from sentry.models.organizationmembermapping import OrganizationMemberMapping
from sentry.organizations.services.organization import organization_service
from sentry.users.models.user import User
from sentry.users.services.user import RpcUser
from sentry.users.services.user.service import user_service


class UserPermission(SentryPermission):
def has_object_permission(self, request: Request, view, user: User | RpcUser | None = None):
if user is None or request.user.id == user.id:
return True
if is_system_auth(request.auth):
return True
if request.auth:
return False

if is_active_superuser(request):
# collect admin level permissions (only used when a user is active superuser)
permissions = access_service.get_permissions_for_user(request.user.id)

if superuser_has_permission(request, permissions):
return True

return False


class UserAndStaffPermission(StaffPermissionMixin, UserPermission):
"""
Allows staff to access any endpoints this permission is used on. Note that
UserPermission already includes a check for Superuser
"""


class OrganizationUserPermission(UserAndStaffPermission):
scope_map = {"DELETE": ["member:admin"]}

def has_org_permission(self, request: Request, user):
"""
Org can act on a user account, if the user is a member of only one org
e.g. reset org member's 2FA
"""

organization_id = self._get_single_organization_id(user)
if organization_id is None:
return False
organization = organization_service.get_organization_by_id(
id=organization_id, user_id=request.user.id
)
if not organization:
return False

self.determine_access(request, organization)
assert request.method is not None
allowed_scopes = set(self.scope_map.get(request.method, []))
return any(request.access.has_scope(s) for s in allowed_scopes)

@staticmethod
def _get_single_organization_id(user) -> int | None:
"""If the user is a member of only one active org, return its ID."""

# Multiple OrganizationMemberMappings are okay if only one
# of them points to an *active* organization
membership_ids = OrganizationMemberMapping.objects.filter(user_id=user.id).values_list(
"organization_id", flat=True
)

try:
org_mapping = OrganizationMapping.objects.get(
status=OrganizationStatus.ACTIVE, organization_id__in=membership_ids
)
except (OrganizationMapping.DoesNotExist, OrganizationMapping.MultipleObjectsReturned):
return None
return org_mapping.organization_id

def has_object_permission(self, request: Request, view, user=None):
if super().has_object_permission(request, view, user):
return True
return self.has_org_permission(request, user)


class UserEndpoint(Endpoint):
"""
The base endpoint for APIs that deal with Users. Inherit from this class to
get permission checks and to automatically convert user ID "me" to the
currently logged in user's ID.
"""

permission_classes: tuple[type[BasePermission], ...] = (UserPermission,)

@override
def convert_args(self, request: Request, user_id: int | str | None = None, *args, **kwargs):
if user_id == "me":
if not request.user.is_authenticated:
raise ResourceDoesNotExist
user_id = request.user.id

if user_id is None:
raise ResourceDoesNotExist

try:
user = User.objects.get(id=user_id)
except (User.DoesNotExist, ValueError):
raise ResourceDoesNotExist

self.check_object_permissions(request, user)

kwargs["user"] = user
return args, kwargs


class RegionSiloUserEndpoint(Endpoint):
"""
The base endpoint for APIs that deal with Users but live in the region silo.
Inherit from this class to get permission checks and to automatically
convert user ID "me" to the currently logged in user's ID.
"""

permission_classes = (UserPermission,)

@override
def convert_args(self, request: Request, user_id: str | None = None, *args, **kwargs):
user: RpcUser | User | None = None

if user_id == "me":
if isinstance(request.user, AnonymousUser) or not request.user.is_authenticated:
raise ResourceDoesNotExist
user = request.user
elif user_id is not None:
user = user_service.get_user(user_id=int(user_id))

if not user:
raise ResourceDoesNotExist

self.check_object_permissions(request, user)

kwargs["user"] = user
return args, kwargs
__all__ = ("UserEndpoint", "RegionSiloUserEndpoint", "UserAndStaffPermission")
2 changes: 1 addition & 1 deletion src/sentry/api/endpoints/user_notification_details.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
from sentry.api.api_owners import ApiOwner
from sentry.api.api_publish_status import ApiPublishStatus
from sentry.api.base import control_silo_endpoint
from sentry.api.bases.user import UserEndpoint
from sentry.api.serializers import Serializer, serialize
from sentry.notifications.types import UserOptionsSettingsKey
from sentry.users.api.bases.user import UserEndpoint
from sentry.users.models.user_option import UserOption

USER_OPTION_SETTINGS = {
Expand Down
2 changes: 1 addition & 1 deletion src/sentry/api/endpoints/user_notification_email.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from sentry.api.api_owners import ApiOwner
from sentry.api.api_publish_status import ApiPublishStatus
from sentry.api.base import control_silo_endpoint
from sentry.api.bases.user import UserEndpoint
from sentry.users.api.bases.user import UserEndpoint
from sentry.users.models.user_option import UserOption
from sentry.users.models.useremail import UserEmail

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
from sentry.api.api_owners import ApiOwner
from sentry.api.api_publish_status import ApiPublishStatus
from sentry.api.base import control_silo_endpoint
from sentry.api.bases.user import UserEndpoint
from sentry.api.exceptions import ParameterValidationError
from sentry.api.serializers import serialize
from sentry.api.validators.notifications import validate_type
from sentry.models.notificationsettingoption import NotificationSettingOption
from sentry.notifications.serializers import NotificationSettingsOptionSerializer
from sentry.notifications.validators import UserNotificationSettingOptionWithValueSerializer
from sentry.users.api.bases.user import UserEndpoint
from sentry.users.models.user import User


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
from sentry.api.api_owners import ApiOwner
from sentry.api.api_publish_status import ApiPublishStatus
from sentry.api.base import control_silo_endpoint
from sentry.api.bases.user import UserEndpoint
from sentry.models.notificationsettingoption import NotificationSettingOption
from sentry.users.api.bases.user import UserEndpoint
from sentry.users.models.user import User


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
from sentry.api.api_owners import ApiOwner
from sentry.api.api_publish_status import ApiPublishStatus
from sentry.api.base import control_silo_endpoint
from sentry.api.bases.user import UserEndpoint
from sentry.api.exceptions import ParameterValidationError
from sentry.api.serializers import serialize
from sentry.api.validators.notifications import validate_type
Expand All @@ -15,6 +14,7 @@
from sentry.notifications.serializers import NotificationSettingsProviderSerializer
from sentry.notifications.types import NotificationSettingsOptionEnum
from sentry.notifications.validators import UserNotificationSettingsProvidersDetailsSerializer
from sentry.users.api.bases.user import UserEndpoint
from sentry.users.models.user import User


Expand Down
2 changes: 1 addition & 1 deletion src/sentry/api/endpoints/user_organizationintegrations.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
from sentry.api.api_owners import ApiOwner
from sentry.api.api_publish_status import ApiPublishStatus
from sentry.api.base import control_silo_endpoint
from sentry.api.bases.user import UserEndpoint
from sentry.api.paginator import OffsetPaginator
from sentry.api.serializers import serialize
from sentry.constants import ObjectStatus
from sentry.integrations.models.organization_integration import OrganizationIntegration
from sentry.users.api.bases.user import UserEndpoint
from sentry.users.services.user.service import user_service


Expand Down
2 changes: 1 addition & 1 deletion src/sentry/api/endpoints/user_organizations.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@

from sentry.api.api_publish_status import ApiPublishStatus
from sentry.api.base import region_silo_endpoint
from sentry.api.bases.user import RegionSiloUserEndpoint
from sentry.api.paginator import OffsetPaginator
from sentry.api.serializers import serialize
from sentry.models.organization import Organization
from sentry.users.api.bases.user import RegionSiloUserEndpoint
from sentry.users.services.user import RpcUser


Expand Down
2 changes: 1 addition & 1 deletion src/sentry/api/endpoints/user_subscriptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from sentry.api.api_owners import ApiOwner
from sentry.api.api_publish_status import ApiPublishStatus
from sentry.api.base import control_silo_endpoint
from sentry.api.bases.user import UserEndpoint
from sentry.users.api.bases.user import UserEndpoint
from sentry.users.models.user import User
from sentry.users.models.useremail import UserEmail

Expand Down
2 changes: 1 addition & 1 deletion src/sentry/api/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ def has_permission(self, request: Request, view: object) -> bool:
current_scopes = request.auth.get_scopes()
return any(s in allowed_scopes for s in current_scopes)

def has_object_permission(self, request: Request, view: object, obj: Any) -> bool:
def has_object_permission(self, request: Request, view: object | None, obj: Any) -> bool:
return False


Expand Down
Loading

0 comments on commit ff9a79b

Please sign in to comment.