Skip to content
Open
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
6 changes: 3 additions & 3 deletions src/sentry/api/bases/organization.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
)
from sentry.api.permissions import DemoSafePermission, StaffPermissionMixin
from sentry.api.utils import get_date_range_from_params, is_member_disabled_from_limit
from sentry.auth.principal import acting_user_id
from sentry.auth.principal import acting_user_id, require_user
from sentry.auth.services.auth import AuthenticatedToken
from sentry.auth.superuser import is_active_superuser
from sentry.constants import ALL_ACCESS_PROJECT_ID, ALL_ACCESS_PROJECTS_SLUG, ObjectStatus
Expand Down Expand Up @@ -107,7 +107,7 @@ def is_not_2fa_compliant(
if not organization.flags.require_2fa:
return False

if request.user.is_authenticated and request.user.has_2fa():
if request.user.is_authenticated and require_user(request).has_2fa():
return False

if request.user.is_authenticated and request.user.is_sentry_app:
Expand Down Expand Up @@ -962,7 +962,7 @@ def has_release_permission(
key = None
if not is_agent_auth(request.auth) and not is_service_account_auth(request.auth):
if request.user.is_authenticated:
actor_id = "user:%s" % request.user.id
actor_id = "user:%s" % require_user(request).id
elif request.auth is not None:
actor_id = "apikey:%s" % request.auth.entity_id
if actor_id is not None:
Expand Down
3 changes: 2 additions & 1 deletion src/sentry/api/bases/organization_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
from sentry.search.events.constants import DURATION_UNITS, SIZE_UNITS
from sentry.search.events.fields import get_function_alias, is_function
from sentry.search.events.types import SAMPLING_MODES, SnubaParams
from sentry.service_accounts.principal import ServiceAccountPrincipal
from sentry.snuba import discover, metrics_enhanced_performance, metrics_performance
from sentry.snuba.dataset import Dataset
from sentry.snuba.metrics.extraction import MetricSpecType
Expand Down Expand Up @@ -290,7 +291,7 @@ def save_split_decision(
has_errors: bool,
has_transactions_data: bool,
organization: Organization,
user: User | AnonymousUser,
user: User | AnonymousUser | ServiceAccountPrincipal,
) -> int | None:
"""This can be removed once the discover dataset has been fully split"""
source = DashboardDatasetSourcesTypes.INFERRED.value
Expand Down
7 changes: 5 additions & 2 deletions src/sentry/api/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from rest_framework.request import Request

from sentry.api.exceptions import PrimaryEmailVerificationRequired, SudoRequired, UserRequired
from sentry.auth.principal import has_user
from sentry.auth.principal import has_user, require_user
from sentry.models.apikey import is_api_key_auth
from sentry.models.apitoken import is_api_token_auth
from sentry.models.orgauthtoken import is_org_auth_token_auth
Expand Down Expand Up @@ -58,7 +58,10 @@ def wrapped(self: T, request: Request, *args: P.args, **kwargs: P.kwargs) -> Htt
def primary_email_verification_required[T, **P](func: _RFMethod[T, P]) -> _RFMethod[T, P]:
@wraps(func)
def wrapped(self: T, request: Request, *args: P.args, **kwargs: P.kwargs) -> HttpResponseBase:
if isinstance(request.user, AnonymousUser) or not request.user.has_verified_primary_email():
if (
isinstance(request.user, AnonymousUser)
or not require_user(request).has_verified_primary_email()
):
raise PrimaryEmailVerificationRequired(request.user)
return func(self, request, *args, **kwargs)

Expand Down
7 changes: 3 additions & 4 deletions src/sentry/api/endpoints/organization_agentic_onboarding.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
AgenticOnboardingRunSerializer,
)
from sentry.api.serializers.rest_framework import CamelSnakeSerializer
from sentry.auth.principal import require_user
from sentry.models.organization import Organization
from sentry.onboarding.agentic_progress.model import (
MAX_EVENT_NOTE_LENGTH,
Expand Down Expand Up @@ -168,8 +169,7 @@ class OrganizationAgenticOnboardingRunIndexEndpoint(OrganizationEndpoint):
def post(self, request: Request, organization: Organization) -> Response:
serializer = AgenticOnboardingRunRequestSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
user_id = request.user.id
assert user_id is not None
user_id = require_user(request).id
try:
run, onboarding_code = get_onboarding_progress_service().create_or_resume(
user_id=user_id,
Expand Down Expand Up @@ -207,8 +207,7 @@ def post(self, request: Request, organization: Organization) -> Response:
serializer = AgenticOnboardingStatusRequestSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
values = serializer.validated_data
user_id = request.user.id
assert user_id is not None
user_id = require_user(request).id

try:
run, changed, stage_status_changed = get_onboarding_progress_service().update(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from sentry.api.serializers.models.agentic_onboarding import (
AgenticOnboardingRunSerializer,
)
from sentry.auth.principal import require_user
from sentry.models.organization import Organization
from sentry.onboarding.agentic_progress.model import OnboardingRunTerminal
from sentry.onboarding.agentic_progress.service import (
Expand All @@ -27,8 +28,7 @@ class OrganizationAgenticOnboardingRunDetailsEndpoint(OrganizationEndpoint):

@extend_schema(responses={200: AgenticOnboardingRunSerializer})
def get(self, request: Request, organization: Organization, run_id: str) -> Response:
user_id = request.user.id
assert user_id is not None
user_id = require_user(request).id
run = get_onboarding_progress_service().get(
run_id=run_id, user_id=user_id, organization_id=organization.id
)
Expand All @@ -39,8 +39,7 @@ def get(self, request: Request, organization: Organization, run_id: str) -> Resp

@extend_schema(responses={200: AgenticOnboardingRunSerializer})
def delete(self, request: Request, organization: Organization, run_id: str) -> Response:
user_id = request.user.id
assert user_id is not None
user_id = require_user(request).id
try:
run = get_onboarding_progress_service().cancel(
run_id=run_id, user_id=user_id, organization_id=organization.id
Expand Down
2 changes: 1 addition & 1 deletion src/sentry/api/endpoints/organization_auth_tokens.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ def post(
name=name,
organization_id=organization.id,
scope_list=sorted(set(scopes)),
created_by_id=request.user.id,
created_by_id=acting_user_id(request.user),
token_last_characters=token_str[-4:],
token_hashed=token_hashed,
)
Expand Down
10 changes: 6 additions & 4 deletions src/sentry/api/endpoints/organization_fork.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from sentry.api.base import Endpoint, cell_silo_endpoint
from sentry.api.permissions import SuperuserOrStaffFeatureFlaggedPermission
from sentry.api.serializers import serialize
from sentry.auth.principal import acting_user_id, require_user
from sentry.hybridcloud.services.organization_mapping import organization_mapping_service
from sentry.models.organization import OrganizationStatus
from sentry.relocation.api.endpoints.index import (
Expand Down Expand Up @@ -69,10 +70,11 @@ def post(self, request: Request, organization_id_or_slug) -> Response:
:auth: required
"""

logger.info("relocations.fork.post.start", extra={"caller": request.user.id})
logger.info("relocations.fork.post.start", extra={"caller": acting_user_id(request.user)})

if not request.user.is_authenticated:
return Response(status=status.HTTP_400_BAD_REQUEST)
user = require_user(request)

org_mapping = (
organization_mapping_service.get(organization_id=organization_id_or_slug)
Expand Down Expand Up @@ -151,7 +153,7 @@ def post(self, request: Request, organization_id_or_slug) -> Response:
# Identify who will be the owner of the newly forked organization, and ensure that they
# don't already have relocations in flight.
owners = organization_mapping_service.get_owners(organization_id=org_mapping.id)
owner = owners[0] if len(owners) > 0 else request.user
owner = owners[0] if len(owners) > 0 else user
err = validate_relocation_uniqueness(owner)
if err is not None:
return err
Expand All @@ -162,7 +164,7 @@ def post(self, request: Request, organization_id_or_slug) -> Response:
provenance = Relocation.Provenance.SAAS_TO_SAAS
with atomic_transaction(using=(router.db_for_write(Relocation))):
new_relocation = Relocation.objects.create(
creator_id=request.user.id,
creator_id=user.id,
owner_id=owner.id,
step=Relocation.Step.UPLOADING.value,
scheduled_pause_at_step=get_autopause_value(provenance),
Expand All @@ -180,7 +182,7 @@ def post(self, request: Request, organization_id_or_slug) -> Response:
try:
analytics.record(
RelocationForkedEvent(
creator_id=request.user.id,
creator_id=user.id,
owner_id=owner.id,
uuid=str(new_relocation.uuid),
from_org_slug=org_mapping.slug,
Expand Down
6 changes: 2 additions & 4 deletions src/sentry/api/endpoints/organization_intercom_jwt.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
import logging

from django.conf import settings
from django.contrib.auth.models import AnonymousUser
from rest_framework import status
from rest_framework.request import Request
from rest_framework.response import Response
Expand All @@ -11,6 +10,7 @@
from sentry.api.api_publish_status import ApiPublishStatus
from sentry.api.base import control_silo_endpoint
from sentry.api.bases.organization import ControlSiloOrganizationEndpoint
from sentry.auth.principal import require_user
from sentry.organizations.services.organization.model import (
RpcOrganization,
RpcUserOrganizationContext,
Expand Down Expand Up @@ -49,8 +49,7 @@ def get(
Returns a JWT signed with HS256 containing user identity claims,
along with user data for the frontend to pass to Intercom.
"""
if isinstance(request.user, AnonymousUser):
return Response(status=status.HTTP_401_UNAUTHORIZED)
user = require_user(request)

signing_secret = settings.SENTRY_INTERCOM_API_SECRET
if not signing_secret:
Expand All @@ -60,7 +59,6 @@ def get(
status=status.HTTP_503_SERVICE_UNAVAILABLE,
)

user = request.user
now = int(datetime.datetime.now(datetime.UTC).timestamp())
exp = now + JWT_VALIDITY_WINDOW_SECONDS

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
MonitoringProviderPermission,
)
from sentry.auth.exceptions import IdentityNotValid
from sentry.auth.principal import owned_by_acting_user
from sentry.identity import default_manager as identity_manager
from sentry.identity.base import Provider
from sentry.identity.oauth2 import OAuth2Provider
Expand Down Expand Up @@ -86,8 +87,8 @@ def put(
# Reuse the stored site from the existing connection.
org_identity = (
OrganizationIdentity.objects.filter(
owned_by_acting_user(request.user, "identity__user_id"),
organization_id=organization.id,
identity__user_id=request.user.id, # type: ignore[misc]
identity__idp__type=provider_key,
)
.select_related("identity")
Expand Down Expand Up @@ -186,8 +187,8 @@ def delete(

org_identities: list[OrganizationIdentity] = list(
OrganizationIdentity.objects.filter(
owned_by_acting_user(request.user, "identity__user_id"),
organization_id=organization.id,
identity__user_id=request.user.id, # type: ignore[misc]
identity__idp__type=provider_key,
).select_related("identity")
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
ControlSiloOrganizationEndpoint,
OrganizationPermission,
)
from sentry.auth.principal import owned_by_acting_user
from sentry.organizations.services.organization.model import RpcOrganization
from sentry.users.models.identity import OrganizationIdentity

Expand Down Expand Up @@ -48,8 +49,8 @@ def get(self, request: Request, organization: RpcOrganization, **kwargs: object)

connected_providers = set(
OrganizationIdentity.objects.filter(
owned_by_acting_user(request.user, "identity__user_id"),
organization_id=organization.id,
identity__user_id=request.user.id, # type: ignore[misc]
identity__idp__type__in=MONITORING_PROVIDERS.keys(),
).values_list("identity__idp__type", flat=True)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
convert_dict_key_case,
snake_to_camel_case,
)
from sentry.auth.principal import acting_user_id
from sentry.constants import ObjectStatus
from sentry.integrations.gcp.client import verify_gcp_connection
from sentry.integrations.gcp.utils import parse_customer_sa_email, validate_gcp_project_id
Expand Down Expand Up @@ -180,7 +181,7 @@ def post(self, request: Request, organization: Organization, **kwargs: object) -
mark_investigation_telemetry_connected.delay(
organization_id=organization.id,
provider="gcp",
user_id=request.user.id,
user_id=acting_user_id(request.user),
retry=False,
)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
from django.contrib.auth.models import AnonymousUser
from rest_framework import serializers
from rest_framework.request import Request

Expand All @@ -9,6 +8,7 @@
from sentry.api.base import cell_silo_endpoint
from sentry.api.bases.organization import OrganizationEndpoint, OrganizationPermission
from sentry.api.serializers.rest_framework.base import CamelSnakeSerializer
from sentry.auth.principal import require_user
from sentry.models.organization import Organization
from sentry.users.models.user import User
from sentry.utils.email import MessageBuilder
Expand Down Expand Up @@ -57,19 +57,15 @@ def post(self, request: Request, organization: Organization):
serializer = OnboardingContinuationSerializer(data=request.data)
if not serializer.is_valid():
return self.respond(serializer.errors, status=400)
if isinstance(request.user, AnonymousUser):
return self.respond(status=401)

user = require_user(request)
msg = MessageBuilder(
**get_request_builder_args(
request.user, organization, serializer.validated_data["platforms"]
)
**get_request_builder_args(user, organization, serializer.validated_data["platforms"])
)
msg.send_async([request.user.email])
msg.send_async([user.email])
analytics.record(
OnboardingContinuationSent(
organization_id=organization.id,
user_id=request.user.id,
user_id=user.id,
providers="email",
)
)
Expand Down
3 changes: 2 additions & 1 deletion src/sentry/api/endpoints/organization_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
ControlSiloOrganizationEndpoint,
OrganizationPermission,
)
from sentry.auth.principal import acting_user_id
from sentry.exceptions import NotRegistered
from sentry.identity.pipeline import IdentityPipeline
from sentry.integrations.pipeline import (
Expand Down Expand Up @@ -127,7 +128,7 @@ def _initialize_pipeline(
return Response({"detail": "Pipeline does not support API mode."}, status=400)

pipeline.set_api_mode()
pipeline.bind_state("user_id", request.user.id)
pipeline.bind_state("user_id", acting_user_id(request.user))

metrics.incr(
"integrations.pipeline_api.initialize",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from sentry.api.bases import NoProjects, OrganizationEventsEndpointBase
from sentry.api.paginator import GenericOffsetPaginator
from sentry.api.utils import handle_query_errors
from sentry.auth.principal import acting_user_id
from sentry.exceptions import InvalidSearchQuery
from sentry.models.organization import Organization
from sentry.search.events.builder.profile_functions import ProfileTopFunctionsTimeseriesQueryBuilder
Expand Down Expand Up @@ -82,7 +83,9 @@ def get(self, request: Request, organization: Organization) -> Response:
if not self.has_feature(organization, request):
return Response(status=404)

viewer_context = SeerViewerContext(organization_id=organization.id, user_id=request.user.id)
viewer_context = SeerViewerContext(
organization_id=organization.id, user_id=acting_user_id(request.user)
)

try:
snuba_params = self.get_snuba_params(request, organization)
Expand Down
11 changes: 7 additions & 4 deletions src/sentry/api/endpoints/organization_recent_searches.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from sentry.api.base import cell_silo_endpoint
from sentry.api.bases.organization import OrganizationEndpoint, OrganizationPermission
from sentry.api.serializers import serialize
from sentry.auth.principal import owned_by_acting_user, require_user
from sentry.models.recentsearch import RecentSearch, remove_excess_recent_searches
from sentry.models.search_common import SearchType

Expand Down Expand Up @@ -54,15 +55,16 @@ def get(self, request: Request, organization) -> Response:

query_kwargs = {
"organization": organization,
"user_id": request.user.id,
"type": search_type,
}

if "query" in request.GET:
query_kwargs["query__icontains"] = request.GET["query"]

recent_searches = list(
RecentSearch.objects.filter(**query_kwargs).order_by("-last_seen")[:limit]
RecentSearch.objects.filter(
owned_by_acting_user(request.user), **query_kwargs
).order_by("-last_seen")[:limit]
)

return Response(serialize(recent_searches, request.user))
Expand All @@ -72,16 +74,17 @@ def post(self, request: Request, organization) -> Response:

if serializer.is_valid():
result = serializer.validated_data
user = require_user(request)

_, created = RecentSearch.objects.update_or_create(
organization_id=organization.id,
user_id=request.user.id,
user_id=user.id,
type=result["type"],
query=result["query"],
defaults={"last_seen": timezone.now()},
)
if created:
remove_excess_recent_searches(organization, request.user, result["type"])
remove_excess_recent_searches(organization, user, result["type"])
status = 201 if created else 204

return Response(status=status)
Expand Down
Loading
Loading