Skip to content
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
5 changes: 3 additions & 2 deletions src/sentry/api/endpoints/accept_organization_invite.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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
13 changes: 7 additions & 6 deletions src/sentry/api/endpoints/accept_project_transfer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -74,20 +75,19 @@ 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"]
except KeyError:
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(
{
Expand All @@ -106,14 +106,15 @@ 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"]
except KeyError:
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)

Expand All @@ -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:
Expand Down
5 changes: 4 additions & 1 deletion src/sentry/api/endpoints/api_application_details.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down
8 changes: 5 additions & 3 deletions src/sentry/api/endpoints/api_applications.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand All @@ -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(
Expand All @@ -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)
9 changes: 5 additions & 4 deletions src/sentry/api/endpoints/api_authorizations.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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(
Expand All @@ -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,
):
Expand All @@ -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,
):
Expand Down
14 changes: 8 additions & 6 deletions src/sentry/api/endpoints/api_tokens.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -85,25 +86,26 @@ 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"],
expires_at=None,
)

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)
Expand All @@ -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)
10 changes: 5 additions & 5 deletions src/sentry/api/endpoints/assistant.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
Expand Down Expand Up @@ -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
)
)
Expand All @@ -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)

Expand All @@ -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
Expand All @@ -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

Expand Down
26 changes: 17 additions & 9 deletions src/sentry/api/endpoints/auth_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()
Expand All @@ -95,20 +100,23 @@ 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)
return authenticated
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:
Expand Down Expand Up @@ -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,
},
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand All @@ -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,
},
)
Expand Down
4 changes: 2 additions & 2 deletions src/sentry/api/endpoints/auth_organization_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 "

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Login config 403s non-user callers

Low Severity

require_user is used only to interpolate an email into a warning on this public GET. For an authenticated caller with no user, such as a service account, that raises user-required and fails the whole login-config response instead of returning empty personal state.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 02a0ff5. Configure here.

f"{organization.name} organization. Ask an organization admin to "
"invite you, or sign in with a different account."
)
Expand Down
Loading
Loading