Skip to content

chore(users): Move User base endpoint file to users directory #77501

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Sep 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 2 additions & 151 deletions src/sentry/api/bases/user.py
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shim for getsentry refs

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
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
Loading