diff --git a/src/sentry/api/endpoints/accept_organization_invite.py b/src/sentry/api/endpoints/accept_organization_invite.py index c67ab713f262..a1dbec180d6c 100644 --- a/src/sentry/api/endpoints/accept_organization_invite.py +++ b/src/sentry/api/endpoints/accept_organization_invite.py @@ -16,6 +16,7 @@ remove_invite_details_from_session, ) from sentry.api.utils import to_valid_int_id +from sentry.auth.principal import acting_user_id, require_user from sentry.demo_mode.utils import is_demo_user from sentry.models.authprovider import AuthProvider from sentry.organizations.services.organization import RpcUserInviteContext, organization_service @@ -65,7 +66,7 @@ def convert_args( # so we should fetch the invite context as if they were anonymous. # This matches the original behavior where logout happened before get_invite_state(). if request.user.is_authenticated and not is_demo_user(request.user): - user_id: int | None = request.user.id + user_id: int | None = acting_user_id(request.user) else: user_id = None @@ -225,7 +226,7 @@ def post( else: response = Response(status=status.HTTP_204_NO_CONTENT) - helper.accept_invite(request.user) + helper.accept_invite(require_user(request)) remove_invite_details_from_session(request) return response diff --git a/src/sentry/api/endpoints/accept_project_transfer.py b/src/sentry/api/endpoints/accept_project_transfer.py index 15c59b1cb4ba..76481e7db258 100644 --- a/src/sentry/api/endpoints/accept_project_transfer.py +++ b/src/sentry/api/endpoints/accept_project_transfer.py @@ -17,6 +17,7 @@ from sentry.api.serializers.models.organization import ( OrganizationWithProjectsAndTeamsSerializer, ) +from sentry.auth.principal import require_user from sentry.constants import CELL_API_DEPRECATION_DATE from sentry.core.endpoints.project_transfer import SALT from sentry.models.options.project_option import ProjectOption @@ -74,6 +75,7 @@ def get_validated_data(self, data, user: User): def get(self, request: Request) -> Response: if not request.user.is_authenticated: return Response(status=400) + user = require_user(request) try: data = request.GET["data"] @@ -81,13 +83,11 @@ def get(self, request: Request) -> Response: raise Http404 try: - data, project = self.get_validated_data(data, request.user) + data, project = self.get_validated_data(data, user) except InvalidPayload as e: return Response({"detail": str(e)}, status=400) - organizations = Organization.objects.get_organizations_where_user_is_owner( - user_id=request.user.id - ) + organizations = Organization.objects.get_organizations_where_user_is_owner(user_id=user.id) return Response( { @@ -106,6 +106,7 @@ def get(self, request: Request) -> Response: def post(self, request: Request) -> Response: if not request.user.is_authenticated: return Response(status=400) + user = require_user(request) try: data = request.data["data"] @@ -113,7 +114,7 @@ def post(self, request: Request) -> Response: raise Http404 try: - data, project = self.get_validated_data(data, request.user) + data, project = self.get_validated_data(data, user) except InvalidPayload as e: return Response({"detail": str(e)}, status=400) @@ -134,7 +135,7 @@ def post(self, request: Request) -> Response: # check if user is an owner of the organization is_org_owner = request.access.has_role_in_organization( - role=roles.get_top_dog().id, organization=organization, user_id=request.user.id + role=roles.get_top_dog().id, organization=organization, user_id=user.id ) if not is_org_owner: diff --git a/src/sentry/api/endpoints/api_application_details.py b/src/sentry/api/endpoints/api_application_details.py index 8b83edaff192..67ebea310a92 100644 --- a/src/sentry/api/endpoints/api_application_details.py +++ b/src/sentry/api/endpoints/api_application_details.py @@ -10,6 +10,7 @@ from sentry.api.exceptions import ResourceDoesNotExist from sentry.api.permissions import SentryIsAuthenticated from sentry.api.serializers import serialize +from sentry.auth.principal import owned_by_acting_user from sentry.deletions.models.scheduleddeletion import ScheduledDeletion from sentry.models.apiapplication import ApiApplication, ApiApplicationStatus @@ -43,7 +44,9 @@ def convert_args( ): try: application = ApiApplication.objects.get( - owner_id=request.user.id, client_id=app_id, status=ApiApplicationStatus.active + owned_by_acting_user(request.user, "owner_id"), + client_id=app_id, + status=ApiApplicationStatus.active, ) except ApiApplication.DoesNotExist: raise ResourceDoesNotExist diff --git a/src/sentry/api/endpoints/api_applications.py b/src/sentry/api/endpoints/api_applications.py index b14be1ad9e9c..1327aef57f51 100644 --- a/src/sentry/api/endpoints/api_applications.py +++ b/src/sentry/api/endpoints/api_applications.py @@ -7,6 +7,7 @@ from sentry.api.paginator import OffsetPaginator from sentry.api.permissions import SentryIsAuthenticated from sentry.api.serializers import serialize +from sentry.auth.principal import owned_by_acting_user, require_user from sentry.models.apiapplication import ApiApplication, ApiApplicationStatus @@ -21,7 +22,7 @@ class ApiApplicationsEndpoint(Endpoint): def get(self, request: Request) -> Response: queryset = ApiApplication.objects.filter( - owner_id=request.user.id, status=ApiApplicationStatus.active + owned_by_acting_user(request.user, "owner_id"), status=ApiApplicationStatus.active ) return self.paginate( @@ -34,10 +35,11 @@ def get(self, request: Request) -> Response: def post(self, request: Request) -> Response: is_public = request.data.get("isPublic", False) + owner_id = require_user(request).id if is_public: - app = ApiApplication.objects.create(owner_id=request.user.id, client_secret=None) + app = ApiApplication.objects.create(owner_id=owner_id, client_secret=None) else: - app = ApiApplication.objects.create(owner_id=request.user.id) + app = ApiApplication.objects.create(owner_id=owner_id) return Response(serialize(app, request.user), status=201) diff --git a/src/sentry/api/endpoints/api_authorizations.py b/src/sentry/api/endpoints/api_authorizations.py index 5a4702838f55..0af2db74cece 100644 --- a/src/sentry/api/endpoints/api_authorizations.py +++ b/src/sentry/api/endpoints/api_authorizations.py @@ -9,6 +9,7 @@ from sentry.api.paginator import OffsetPaginator from sentry.api.permissions import SentryIsAuthenticated from sentry.api.serializers import serialize +from sentry.auth.principal import owned_by_acting_user from sentry.hybridcloud.models.outbox import outbox_context from sentry.models.apiapplication import ApiApplicationStatus from sentry.models.apiauthorization import ApiAuthorization @@ -31,7 +32,7 @@ def get(self, request: Request) -> Response: return Response(status=400) queryset = ApiAuthorization.objects.filter( - user_id=request.user.id, application__status=ApiApplicationStatus.active + owned_by_acting_user(request.user), application__status=ApiApplicationStatus.active ).select_related("application") return self.paginate( @@ -52,14 +53,14 @@ def delete(self, request: Request) -> Response: try: api_authorization = ApiAuthorization.objects.get( - user_id=request.user.id, id=authorization + owned_by_acting_user(request.user), id=authorization ) except ApiAuthorization.DoesNotExist: return Response(status=404) with outbox_context(transaction.atomic(using=router.db_for_write(ApiToken)), flush=False): for token in ApiToken.objects.filter( - user_id=request.user.id, + owned_by_acting_user(request.user), application=api_authorization.application_id, scoping_organization_id=api_authorization.organization_id, ): @@ -69,7 +70,7 @@ def delete(self, request: Request) -> Response: # that may not have been exchanged for a token yet with outbox_context(transaction.atomic(using=router.db_for_write(ApiGrant)), flush=False): for grant in ApiGrant.objects.filter( - user_id=request.user.id, + owned_by_acting_user(request.user), application=api_authorization.application_id, organization_id=api_authorization.organization_id, ): diff --git a/src/sentry/api/endpoints/api_tokens.py b/src/sentry/api/endpoints/api_tokens.py index f7ed17afdcd8..983681d2fbde 100644 --- a/src/sentry/api/endpoints/api_tokens.py +++ b/src/sentry/api/endpoints/api_tokens.py @@ -18,6 +18,7 @@ from sentry.api.permissions import DisallowImpersonatedTokenCreation, SentryIsAuthenticated from sentry.api.serializers import serialize from sentry.auth.elevated_mode import has_elevated_mode +from sentry.auth.principal import acting_user_id, require_user from sentry.hybridcloud.models.outbox import outbox_context from sentry.models.apitoken import ApiToken from sentry.security.utils import capture_security_activity @@ -44,7 +45,7 @@ def get_appropriate_user_id(request: Request) -> int: assert request.user.is_authenticated # Get the user id for the user that made the current request as a baseline default - user_id = request.user.id + user_id = require_user(request).id if has_elevated_mode(request): datastore = request.GET if request.GET else request.data # If a userId override is not found, use the id for the user who made the request @@ -85,9 +86,10 @@ def post(self, request: Request) -> Response: if serializer.is_valid(): result = serializer.validated_data assert request.user.is_authenticated, "User must not be anonymous" + user = require_user(request) token = ApiToken.objects.create( - user_id=request.user.id, + user_id=user.id, name=result.get("name", None), token_type=AuthTokenType.USER, scope_list=result["scopes"], @@ -95,15 +97,15 @@ def post(self, request: Request) -> Response: ) capture_security_activity( - account=request.user, + account=user, type="api-token-generated", - actor=request.user, + actor=user, ip_address=request.META["REMOTE_ADDR"], context={}, send_email=True, ) - analytics.record(ApiTokenCreated(user_id=request.user.id)) + analytics.record(ApiTokenCreated(user_id=user.id)) return Response(serialize(token, request.user), status=201) return Response(serializer.errors, status=400) @@ -127,6 +129,6 @@ def delete(self, request: Request): token_to_delete.delete() - analytics.record(ApiTokenDeleted(user_id=request.user.id)) + analytics.record(ApiTokenDeleted(user_id=acting_user_id(request.user))) return Response(status=204) diff --git a/src/sentry/api/endpoints/assistant.py b/src/sentry/api/endpoints/assistant.py index b774be068900..dfdf87407c96 100644 --- a/src/sentry/api/endpoints/assistant.py +++ b/src/sentry/api/endpoints/assistant.py @@ -12,6 +12,7 @@ from sentry.api.base import Endpoint, control_silo_endpoint from sentry.api.permissions import SentryIsAuthenticated from sentry.assistant import manager +from sentry.auth.principal import owned_by_acting_user, require_user from sentry.models.assistant import AssistantActivity VALID_STATUSES = frozenset(("viewed", "dismissed", "restart")) @@ -69,7 +70,7 @@ def get(self, request: Request) -> Response: return Response(status=400) seen_ids = set( - AssistantActivity.objects.filter(user_id=request.user.id).values_list( + AssistantActivity.objects.filter(owned_by_acting_user(request.user)).values_list( "guide_id", flat=True ) ) @@ -90,6 +91,7 @@ def put(self, request: Request): """ if not request.user.is_authenticated: return Response(status=400) + user = require_user(request) serializer = AssistantSerializer(data=request.data) @@ -104,7 +106,7 @@ def put(self, request: Request): fields = {} if status == Status.RESTART.value: - AssistantActivity.objects.filter(user_id=request.user.id, guide_id=guide_id).delete() + AssistantActivity.objects.filter(user_id=user.id, guide_id=guide_id).delete() else: if useful is not None: fields["useful"] = useful @@ -114,9 +116,7 @@ def put(self, request: Request): fields["dismissed_ts"] = timezone.now() try: - AssistantActivity.objects.create( - user_id=request.user.id, guide_id=guide_id, **fields - ) + AssistantActivity.objects.create(user_id=user.id, guide_id=guide_id, **fields) except IntegrityError: pass diff --git a/src/sentry/api/endpoints/auth_index.py b/src/sentry/api/endpoints/auth_index.py index ec142e3e9721..d65a0a355ca4 100644 --- a/src/sentry/api/endpoints/auth_index.py +++ b/src/sentry/api/endpoints/auth_index.py @@ -21,7 +21,12 @@ from sentry.api.validators import AuthVerifyValidator from sentry.api.validators.auth import MISSING_PASSWORD_OR_U2F_CODE from sentry.auth.authenticators.u2f import U2fInterface -from sentry.auth.principal import set_request_principal +from sentry.auth.principal import ( + acting_user_id, + owned_by_acting_user, + require_user, + set_request_principal, +) from sentry.auth.providers.saml2.provider import handle_saml_single_logout from sentry.auth.services.auth.impl import promote_request_rpc_user from sentry.auth.superuser import SUPERUSER_ORG_ID, SuperuserAccessSerializer @@ -85,7 +90,7 @@ def _verify_user_via_inputs(validator: AuthVerifyValidator, request: Request) -> # See if we have a u2f challenge/response if "challenge" in validator.validated_data and "response" in validator.validated_data: try: - interface = Authenticator.objects.get_interface(request.user, "u2f") + interface = Authenticator.objects.get_interface(require_user(request), "u2f") assert isinstance(interface, U2fInterface) if not interface.is_enrolled(): raise LookupError() @@ -95,7 +100,7 @@ def _verify_user_via_inputs(validator: AuthVerifyValidator, request: Request) -> if not authenticated: logger.warning( "u2f_authentication.verification_failed", - extra={"user": request.user.id}, + extra={"user": acting_user_id(request.user)}, ) else: metrics.incr("auth.2fa.success", sample_rate=1.0, skip_internal=False) @@ -103,12 +108,15 @@ def _verify_user_via_inputs(validator: AuthVerifyValidator, request: Request) -> except ValueError as err: logger.warning( "u2f_authentication.value_error", - extra={"user": request.user.id, "error_message": err}, + extra={"user": acting_user_id(request.user), "error_message": err}, ) except LookupError: logger.warning( "u2f_authentication.interface_not_enrolled", - extra={"validated_data": validator.validated_data, "user": request.user.id}, + extra={ + "validated_data": validator.validated_data, + "user": acting_user_id(request.user), + }, ) # attempt password authentication elif "password" in validator.validated_data: @@ -174,7 +182,7 @@ def _validate_superuser( logger.info( "auth-index.validate_superuser", extra={ - "user": request.user.id, + "user": acting_user_id(request.user), "raise_exception": not DISABLE_SSO_CHECK_FOR_LOCAL_DEV, "verify_authenticator": verify_authenticator, }, @@ -219,7 +227,7 @@ def post(self, request: Request) -> Response: # If 2fa login is enabled then we cannot sign in with username and # password through this api endpoint. - if request.user.has_2fa(): + if require_user(request).has_2fa(): return Response( { "2fa_required": True, @@ -305,7 +313,7 @@ def put(self, request: Request) -> Response: if verify_authenticator: if not Authenticator.objects.filter( - user_id=request.user.id, type=U2fInterface.type + owned_by_acting_user(request.user), type=U2fInterface.type ).exists(): return Response( {"detail": {"code": "no_u2f"}}, status=status.HTTP_403_FORBIDDEN @@ -314,7 +322,7 @@ def put(self, request: Request) -> Response: "auth-index.put", extra={ "organization": SUPERUSER_ORG_ID, - "user": request.user.id, + "user": acting_user_id(request.user), "verify_authenticator": verify_authenticator, }, ) diff --git a/src/sentry/api/endpoints/auth_organization_config.py b/src/sentry/api/endpoints/auth_organization_config.py index d1bf8a6a9c7f..dc7ccea437a9 100644 --- a/src/sentry/api/endpoints/auth_organization_config.py +++ b/src/sentry/api/endpoints/auth_organization_config.py @@ -17,7 +17,7 @@ ) from sentry.auth import access from sentry.auth.exceptions import ProviderNotRegistered -from sentry.auth.principal import acting_user_id +from sentry.auth.principal import acting_user_id, require_user from sentry.constants import WARN_SESSION_EXPIRED from sentry.demo_mode.utils import is_demo_mode_enabled, is_demo_org from sentry.models.authprovider import AuthProvider @@ -104,7 +104,7 @@ def get(self, request: Request, organization_id_or_slug: str) -> Response: and not is_demo_organization ): warnings.append( - f"Your account ({request.user.email}) is not a member of the " + f"Your account ({require_user(request).email}) is not a member of the " f"{organization.name} organization. Ask an organization admin to " "invite you, or sign in with a different account." ) diff --git a/src/sentry/api/endpoints/broadcast_details.py b/src/sentry/api/endpoints/broadcast_details.py index 986637900be1..5e6ed0d00481 100644 --- a/src/sentry/api/endpoints/broadcast_details.py +++ b/src/sentry/api/endpoints/broadcast_details.py @@ -11,7 +11,7 @@ from sentry.api.permissions import SentryIsAuthenticated from sentry.api.serializers import AdminBroadcastSerializer, BroadcastSerializer, serialize from sentry.api.validators import AdminBroadcastValidator, BroadcastValidator -from sentry.auth.principal import acting_user_id +from sentry.auth.principal import acting_user_id, require_user from sentry.models.broadcast import Broadcast, BroadcastSeen logger = logging.getLogger("sentry") @@ -127,7 +127,9 @@ def put(self, request: Request, broadcast: Broadcast) -> Response: if result.get("hasSeen"): try: with transaction.atomic(using=router.db_for_write(Broadcast)): - BroadcastSeen.objects.create(broadcast=broadcast, user_id=request.user.id) + BroadcastSeen.objects.create( + broadcast=broadcast, user_id=require_user(request).id + ) except IntegrityError: pass diff --git a/src/sentry/api/endpoints/broadcast_index.py b/src/sentry/api/endpoints/broadcast_index.py index 389a448e55da..1c8e61a89678 100644 --- a/src/sentry/api/endpoints/broadcast_index.py +++ b/src/sentry/api/endpoints/broadcast_index.py @@ -17,12 +17,11 @@ from sentry.api.paginator import DateTimePaginator from sentry.api.serializers import AdminBroadcastSerializer, BroadcastSerializer, serialize from sentry.api.validators import AdminBroadcastValidator, BroadcastValidator -from sentry.auth.principal import acting_user_id +from sentry.auth.principal import acting_user_id, owned_by_acting_user, require_user from sentry.db.models.query import in_icontains from sentry.models.broadcast import Broadcast, BroadcastSeen from sentry.organizations.services.organization.model import RpcOrganization from sentry.search.utils import tokenize_query -from sentry.users.models.user import User logger = logging.getLogger("sentry") @@ -162,17 +161,18 @@ def put(self, request: Request) -> Response: unseen_queryset = queryset else: unseen_queryset = queryset.exclude( - id__in=queryset.filter(broadcastseen__user_id=request.user.id).values("id") + id__in=queryset.filter( + owned_by_acting_user(request.user, "broadcastseen__user_id") + ).values("id") ) broadcast_ids = list(unseen_queryset.values_list("id", flat=True)) if broadcast_ids: + user_id = require_user(request).id seen_at = timezone.now() db = router.db_for_write(BroadcastSeen) seen_rows = [ - BroadcastSeen( - broadcast_id=broadcast_id, user_id=request.user.id, date_seen=seen_at - ) + BroadcastSeen(broadcast_id=broadcast_id, user_id=user_id, date_seen=seen_at) for broadcast_id in broadcast_ids ] with transaction.atomic(using=db): @@ -201,7 +201,7 @@ def post(self, request: Request) -> Response: date_expires=result.get("dateExpires"), media_url=result.get("mediaUrl"), category=result.get("category"), - created_by_id=User.objects.get(id=request.user.id), + created_by_id_id=acting_user_id(request.user), ) logger.info( "broadcasts.create", @@ -215,7 +215,9 @@ def post(self, request: Request) -> Response: if result.get("hasSeen"): try: with transaction.atomic(using=router.db_for_write(BroadcastSeen)): - BroadcastSeen.objects.create(broadcast=broadcast, user_id=request.user.id) + BroadcastSeen.objects.create( + broadcast=broadcast, user_id=require_user(request).id + ) except IntegrityError: pass diff --git a/src/sentry/api/endpoints/event_attachment_details.py b/src/sentry/api/endpoints/event_attachment_details.py index 52fe5c324383..1f2169fd4541 100644 --- a/src/sentry/api/endpoints/event_attachment_details.py +++ b/src/sentry/api/endpoints/event_attachment_details.py @@ -20,6 +20,7 @@ from sentry.apidocs.parameters import EventParams, GlobalParams from sentry.apidocs.response_types import DetailResponse from sentry.apidocs.utils import inline_sentry_response_serializer +from sentry.auth.principal import acting_user_id from sentry.auth.superuser import superuser_has_permission from sentry.auth.system import is_system_auth from sentry.constants import ATTACHMENTS_ROLE_DEFAULT @@ -216,7 +217,7 @@ def delete( group_id=attachment.group_id, project=project, type=ActivityType.DELETED_ATTACHMENT.value, - user_id=request.user.id, + user_id=acting_user_id(request.user), data={}, ) attachment.delete() diff --git a/src/sentry/api/endpoints/index.py b/src/sentry/api/endpoints/index.py index e920ad2fa09c..c340861fa8ab 100644 --- a/src/sentry/api/endpoints/index.py +++ b/src/sentry/api/endpoints/index.py @@ -4,6 +4,7 @@ from sentry.api.api_publish_status import ApiPublishStatus from sentry.api.base import Endpoint, control_silo_endpoint from sentry.api.serializers import serialize +from sentry.auth.principal import require_user from sentry.users.models.user import User @@ -16,7 +17,7 @@ class IndexEndpoint(Endpoint): def get(self, request: Request) -> Response: if request.user.is_authenticated: - user = User.objects.get(id=request.user.id) + user = User.objects.get(id=require_user(request).id) user = serialize(user, user) else: user = None diff --git a/src/sentry/api/endpoints/internal/mail.py b/src/sentry/api/endpoints/internal/mail.py index 24be19b48882..e4a4aefe83b3 100644 --- a/src/sentry/api/endpoints/internal/mail.py +++ b/src/sentry/api/endpoints/internal/mail.py @@ -7,6 +7,7 @@ from sentry.api.api_publish_status import ApiPublishStatus from sentry.api.base import Endpoint, all_silo_endpoint from sentry.api.permissions import SuperuserPermission +from sentry.auth.principal import require_user from sentry.utils.email import send_mail @@ -30,13 +31,14 @@ def get(self, request: Request) -> Response: "mailUseSsl": options.get("mail.use-ssl"), "mailFrom": options.get("mail.from"), "mailListNamespace": settings.SENTRY_MAIL_LIST_NAMESPACE, - "testMailEmail": request.user.email, + "testMailEmail": require_user(request).email, } return Response(data) def post(self, request: Request) -> Response: assert request.user.is_authenticated + email = require_user(request).email error = None body = ( @@ -47,7 +49,7 @@ def post(self, request: Request) -> Response: "{} Test Email".format(options.get("mail.subject-prefix")), body, options.get("mail.from"), - [request.user.email], + [email], fail_silently=False, ) except Exception as e: diff --git a/tests/sentry/auth/test_principal.py b/tests/sentry/auth/test_principal.py index dedd08500514..65d8803d4d30 100644 --- a/tests/sentry/auth/test_principal.py +++ b/tests/sentry/auth/test_principal.py @@ -274,7 +274,7 @@ def _is_request_user_type_check(node: ast.AST) -> bool: # Lower these as callers move off. Exact, so the baseline can't drift back up. -REQUEST_USER_ID_BASELINE = 378 +REQUEST_USER_ID_BASELINE = 347 REQUEST_USER_TYPE_CHECK_BASELINE = 31