|
5 | 5 | from datetime import datetime, timedelta, timezone |
6 | 6 | from typing import TypedDict |
7 | 7 |
|
| 8 | +from django.core.exceptions import PermissionDenied |
8 | 9 | from django.db import models, router, transaction |
9 | 10 | from django.db.models.query_utils import DeferredAttribute |
10 | 11 | from django.urls import reverse |
11 | 12 | from django.utils import timezone as django_timezone |
12 | 13 | from drf_spectacular.utils import OpenApiResponse, extend_schema, extend_schema_serializer |
13 | 14 | from rest_framework import serializers, status |
| 15 | +from rest_framework.exceptions import NotFound |
14 | 16 | from sentry_sdk import capture_exception |
15 | 17 |
|
16 | 18 | from bitfield.types import BitHandler |
@@ -453,6 +455,26 @@ def validate_samplingMode(self, value): |
453 | 455 |
|
454 | 456 | return value |
455 | 457 |
|
| 458 | + def validate_hasGranularReplayPermissions(self, value): |
| 459 | + self._validate_granular_replay_permissions() |
| 460 | + return value |
| 461 | + |
| 462 | + def validate_replayAccessMembers(self, value): |
| 463 | + self._validate_granular_replay_permissions() |
| 464 | + return value |
| 465 | + |
| 466 | + def _validate_granular_replay_permissions(self): |
| 467 | + organization = self.context["organization"] |
| 468 | + request = self.context["request"] |
| 469 | + |
| 470 | + if not features.has("organizations:granular-replay-permissions", organization): |
| 471 | + raise NotFound("This feature is not enabled for your organization.") |
| 472 | + |
| 473 | + if not request.access.has_scope("org:admin"): |
| 474 | + raise PermissionDenied( |
| 475 | + "You do not have permission to modify granular replay permissions." |
| 476 | + ) |
| 477 | + |
456 | 478 | def validate(self, attrs): |
457 | 479 | attrs = super().validate(attrs) |
458 | 480 | if attrs.get("avatarType") == "upload": |
@@ -567,85 +589,65 @@ def save(self, **kwargs): |
567 | 589 | if trusted_relay_info is not None: |
568 | 590 | self.save_trusted_relays(trusted_relay_info, changed_data, org) |
569 | 591 |
|
570 | | - if "hasGranularReplayPermissions" in data or "replayAccessMembers" in data: |
571 | | - if not features.has("organizations:granular-replay-permissions", org): |
572 | | - raise serializers.ValidationError( |
573 | | - { |
574 | | - "hasGranularReplayPermissions": "This feature is not enabled for your organization." |
575 | | - } |
576 | | - ) |
577 | | - |
578 | | - if not self.context["request"].access.has_scope("org:admin"): |
579 | | - raise serializers.ValidationError( |
580 | | - { |
581 | | - "hasGranularReplayPermissions": "You do not have permission to modify granular replay permissions." |
582 | | - } |
583 | | - ) |
584 | | - |
585 | | - if "hasGranularReplayPermissions" in data: |
586 | | - option_key = "sentry:granular-replay-permissions" |
587 | | - new_value = data["hasGranularReplayPermissions"] |
588 | | - |
589 | | - option_inst, created = OrganizationOption.objects.update_or_create( |
590 | | - organization=org, key=option_key, defaults={"value": new_value} |
| 592 | + if "hasGranularReplayPermissions" in data: |
| 593 | + option_key = "sentry:granular-replay-permissions" |
| 594 | + new_value = data["hasGranularReplayPermissions"] |
| 595 | + option_inst, created = OrganizationOption.objects.update_or_create( |
| 596 | + organization=org, key=option_key, defaults={"value": new_value} |
| 597 | + ) |
| 598 | + changed_data["hasGranularReplayPermissions"] = f"to {new_value}" |
| 599 | + |
| 600 | + if "replayAccessMembers" in data: |
| 601 | + member_ids = data["replayAccessMembers"] |
| 602 | + if member_ids is None: |
| 603 | + member_ids = [] |
| 604 | + current_member_ids = set( |
| 605 | + OrganizationMemberReplayAccess.objects.filter(organization=org).values_list( |
| 606 | + "organizationmember_id", flat=True |
591 | 607 | ) |
592 | | - |
593 | | - changed_data["hasGranularReplayPermissions"] = f"to {new_value}" |
594 | | - |
595 | | - if "replayAccessMembers" in data: |
596 | | - member_ids = data["replayAccessMembers"] |
597 | | - |
598 | | - if member_ids is None: |
599 | | - member_ids = [] |
600 | | - |
601 | | - current_member_ids = set( |
602 | | - OrganizationMemberReplayAccess.objects.filter(organization=org).values_list( |
603 | | - "organizationmember_id", flat=True |
| 608 | + ) |
| 609 | + new_member_ids = set(member_ids) |
| 610 | + |
| 611 | + to_add = new_member_ids - current_member_ids |
| 612 | + to_remove = current_member_ids - new_member_ids |
| 613 | + if to_add: |
| 614 | + valid_member_ids = set( |
| 615 | + OrganizationMember.objects.filter(organization=org, id__in=to_add).values_list( |
| 616 | + "id", flat=True |
604 | 617 | ) |
605 | 618 | ) |
606 | | - new_member_ids = set(member_ids) |
607 | | - |
608 | | - to_add = new_member_ids - current_member_ids |
609 | | - to_remove = current_member_ids - new_member_ids |
610 | | - |
611 | | - if to_add: |
612 | | - valid_member_ids = set( |
613 | | - OrganizationMember.objects.filter( |
614 | | - organization=org, id__in=to_add |
615 | | - ).values_list("id", flat=True) |
| 619 | + invalid_member_ids = to_add - valid_member_ids |
| 620 | + if invalid_member_ids: |
| 621 | + raise serializers.ValidationError( |
| 622 | + { |
| 623 | + "replayAccessMembers": f"Invalid organization member IDs: {sorted(invalid_member_ids)}" |
| 624 | + } |
616 | 625 | ) |
617 | | - invalid_member_ids = to_add - valid_member_ids |
618 | | - if invalid_member_ids: |
619 | | - raise serializers.ValidationError( |
620 | | - { |
621 | | - "replayAccessMembers": f"Invalid organization member IDs: {sorted(invalid_member_ids)}" |
622 | | - } |
| 626 | + |
| 627 | + OrganizationMemberReplayAccess.objects.bulk_create( |
| 628 | + [ |
| 629 | + OrganizationMemberReplayAccess( |
| 630 | + organization=org, organizationmember_id=member_id |
623 | 631 | ) |
| 632 | + for member_id in to_add |
| 633 | + ], |
| 634 | + ignore_conflicts=True, |
| 635 | + ) |
624 | 636 |
|
625 | | - OrganizationMemberReplayAccess.objects.bulk_create( |
626 | | - [ |
627 | | - OrganizationMemberReplayAccess( |
628 | | - organization=org, organizationmember_id=member_id |
629 | | - ) |
630 | | - for member_id in to_add |
631 | | - ], |
632 | | - ignore_conflicts=True, |
633 | | - ) |
| 637 | + if to_remove: |
| 638 | + OrganizationMemberReplayAccess.objects.filter( |
| 639 | + organization=org, organizationmember_id__in=to_remove |
| 640 | + ).delete() |
634 | 641 |
|
| 642 | + if to_add or to_remove: |
| 643 | + changes = [] |
| 644 | + if to_add: |
| 645 | + changes.append(f"added {len(to_add)} member(s)") |
635 | 646 | if to_remove: |
636 | | - OrganizationMemberReplayAccess.objects.filter( |
637 | | - organization=org, organizationmember_id__in=to_remove |
638 | | - ).delete() |
639 | | - |
640 | | - if to_add or to_remove: |
641 | | - changes = [] |
642 | | - if to_add: |
643 | | - changes.append(f"added {len(to_add)} member(s)") |
644 | | - if to_remove: |
645 | | - changes.append(f"removed {len(to_remove)} member(s)") |
646 | | - changed_data["replayAccessMembers"] = ( |
647 | | - f"{' and '.join(changes)} (total: {len(new_member_ids)} member(s) with access)" |
648 | | - ) |
| 647 | + changes.append(f"removed {len(to_remove)} member(s)") |
| 648 | + changed_data["replayAccessMembers"] = ( |
| 649 | + f"{' and '.join(changes)} (total: {len(new_member_ids)} member(s) with access)" |
| 650 | + ) |
649 | 651 |
|
650 | 652 | if "openMembership" in data: |
651 | 653 | org.flags.allow_joinleave = data["openMembership"] |
|
0 commit comments