-
-
Notifications
You must be signed in to change notification settings - Fork 4.2k
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
ref(hc): Remove get_orgs() #54635
ref(hc): Remove get_orgs() #54635
Changes from 5 commits
9456323
edeb703
b302197
b9c0df9
6a6f7f7
623bc23
f663d8d
d8e9ace
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,5 @@ | ||
from typing import Any, Mapping | ||
|
||
from django.db import router, transaction | ||
from rest_framework import status | ||
from rest_framework.request import Request | ||
|
@@ -13,6 +15,7 @@ | |
get_option_value_from_int, | ||
get_type_from_fine_tuning_key, | ||
) | ||
from sentry.services.hybrid_cloud.user.service import user_service | ||
from sentry.types.integrations import ExternalProviders | ||
|
||
INVALID_EMAIL_MSG = ( | ||
|
@@ -80,7 +83,8 @@ def put(self, request: Request, user, notification_type) -> Response: | |
|
||
# Validate that all of the IDs are integers. | ||
try: | ||
ids_to_update = {int(i) for i in request.data.keys()} | ||
for k in request.data.keys(): | ||
int(k) | ||
except ValueError: | ||
return Response( | ||
{"detail": "Invalid id value provided. Id values should be integers."}, | ||
|
@@ -89,28 +93,11 @@ def put(self, request: Request, user, notification_type) -> Response: | |
|
||
# Make sure that the IDs we are going to update are a subset of the | ||
# user's list of organizations or projects. | ||
parents = ( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Requiring validation of project is and organization ids forces a fan out across all regions for a user. In reality, these values are not foreign keys. We also frequently "leave behind" notification settings without clearing them in all cases, so letting arbitrary values from a user isn't particularly problematic. Having notification settings for a project or org you don't belong to doesn't grant any access or necessarily grant any notifications. |
||
user.get_orgs() if notification_type == FineTuningAPIKey.DEPLOY else user.get_projects() | ||
) | ||
parent_ids = {parent.id for parent in parents} | ||
if not ids_to_update.issubset(parent_ids): | ||
bad_ids = ids_to_update - parent_ids | ||
return Response( | ||
{ | ||
"detail": "At least one of the requested projects is not \ | ||
available to this user, because the user does not belong \ | ||
to the necessary teams. (ids of unavailable projects: %s)" | ||
% bad_ids | ||
}, | ||
status=status.HTTP_403_FORBIDDEN, | ||
) | ||
|
||
if notification_type == FineTuningAPIKey.EMAIL: | ||
return self._handle_put_emails(user, request.data) | ||
|
||
return self._handle_put_notification_settings( | ||
user, notification_type, parents, request.data | ||
) | ||
return self._handle_put_notification_settings(user, notification_type, request.data) | ||
|
||
@staticmethod | ||
def _handle_put_reports(user, data): | ||
|
@@ -122,7 +109,7 @@ def _handle_put_reports(user, data): | |
value = set(user_option.value or []) | ||
|
||
# The set of IDs of the organizations of which the user is a member. | ||
org_ids = {organization.id for organization in user.get_orgs()} | ||
org_ids = {o.id for o in user_service.get_organizations(user_id=user.id, only_visible=True)} | ||
for org_id, enabled in data.items(): | ||
org_id = int(org_id) | ||
# We want "0" to be falsey | ||
|
@@ -167,26 +154,26 @@ def _handle_put_emails(user, data): | |
return Response(status=status.HTTP_204_NO_CONTENT) | ||
|
||
@staticmethod | ||
def _handle_put_notification_settings(user, notification_type: FineTuningAPIKey, parents, data): | ||
def _handle_put_notification_settings( | ||
user, notification_type: FineTuningAPIKey, data: Mapping[str, Any] | ||
): | ||
with transaction.atomic(using=router.db_for_write(NotificationSetting)): | ||
for parent in parents: | ||
# We fetched every available parent but only care about the ones in `request.data`. | ||
if str(parent.id) not in data: | ||
continue | ||
for setting_obj_id_str, value_str in data.items(): | ||
setting_obj_id_int = int(setting_obj_id_str) | ||
|
||
# This partitioning always does the same thing because notification_type stays constant. | ||
project_option, organization_option = ( | ||
(None, parent) | ||
(None, setting_obj_id_int) | ||
if notification_type == FineTuningAPIKey.DEPLOY | ||
else (parent, None) | ||
else (setting_obj_id_int, None) | ||
) | ||
|
||
type = get_type_from_fine_tuning_key(notification_type) | ||
value = int(data[str(parent.id)]) | ||
value_int = int(value_str) | ||
NotificationSetting.objects.update_settings( | ||
ExternalProviders.EMAIL, | ||
type, | ||
get_option_value_from_int(type, value), | ||
get_option_value_from_int(type, value_int), | ||
user_id=user.id, | ||
project=project_option, | ||
organization=organization_option, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,17 +1,45 @@ | ||
from __future__ import annotations | ||
|
||
from django.db.models import Q | ||
from rest_framework.request import Request | ||
from rest_framework.response import Response | ||
from typing_extensions import override | ||
|
||
from sentry.api.base import control_silo_endpoint | ||
from sentry.api.bases.user import UserEndpoint | ||
from sentry.api.base import Endpoint, region_silo_endpoint | ||
from sentry.api.bases.user import UserPermission | ||
from sentry.api.exceptions import ResourceDoesNotExist | ||
from sentry.api.paginator import OffsetPaginator | ||
from sentry.api.serializers import serialize | ||
from sentry.models import Organization, User | ||
from sentry.services.hybrid_cloud.user import RpcUser | ||
from sentry.services.hybrid_cloud.user.service import user_service | ||
|
||
|
||
@region_silo_endpoint | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Swapping this to a region endpoint. In the future, we plan to have the frontend query a user's organization settings per non single tenant region they belong to. Pushing this to the frontend makes sense in the long term. |
||
class UserOrganizationsEndpoint(Endpoint): | ||
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 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 | ||
|
||
@control_silo_endpoint | ||
class UserOrganizationsEndpoint(UserEndpoint): | ||
def get(self, request: Request, user) -> Response: | ||
queryset = user.get_orgs() | ||
def get(self, request: Request, user: RpcUser) -> Response: | ||
queryset = Organization.objects.get_for_user_ids({user.id}) | ||
|
||
query = request.GET.get("query") | ||
if query: | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SentryApps will be control silo, which means they won't have direct access to organizations. the context here represents an RPC lookup of org details.