|
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 |
@@ -484,6 +486,26 @@ def validate_samplingMode(self, value): |
484 | 486 |
|
485 | 487 | return value |
486 | 488 |
|
| 489 | + def validate_hasGranularReplayPermissions(self, value): |
| 490 | + self._validate_granular_replay_permissions() |
| 491 | + return value |
| 492 | + |
| 493 | + def validate_replayAccessMembers(self, value): |
| 494 | + self._validate_granular_replay_permissions() |
| 495 | + return value |
| 496 | + |
| 497 | + def _validate_granular_replay_permissions(self): |
| 498 | + organization = self.context["organization"] |
| 499 | + request = self.context["request"] |
| 500 | + |
| 501 | + if not features.has("organizations:granular-replay-permissions", organization): |
| 502 | + raise NotFound("This feature is not enabled for your organization.") |
| 503 | + |
| 504 | + if not request.access.has_scope("org:admin"): |
| 505 | + raise PermissionDenied( |
| 506 | + "You do not have permission to modify granular replay permissions." |
| 507 | + ) |
| 508 | + |
487 | 509 | def validate(self, attrs): |
488 | 510 | attrs = super().validate(attrs) |
489 | 511 | if attrs.get("avatarType") == "upload": |
@@ -598,85 +620,65 @@ def save(self, **kwargs): |
598 | 620 | if trusted_relay_info is not None: |
599 | 621 | self.save_trusted_relays(trusted_relay_info, changed_data, org) |
600 | 622 |
|
601 | | - if "hasGranularReplayPermissions" in data or "replayAccessMembers" in data: |
602 | | - if not features.has("organizations:granular-replay-permissions", org): |
603 | | - raise serializers.ValidationError( |
604 | | - { |
605 | | - "hasGranularReplayPermissions": "This feature is not enabled for your organization." |
606 | | - } |
607 | | - ) |
608 | | - |
609 | | - if not self.context["request"].access.has_scope("org:admin"): |
610 | | - raise serializers.ValidationError( |
611 | | - { |
612 | | - "hasGranularReplayPermissions": "You do not have permission to modify granular replay permissions." |
613 | | - } |
614 | | - ) |
615 | | - |
616 | | - if "hasGranularReplayPermissions" in data: |
617 | | - option_key = "sentry:granular-replay-permissions" |
618 | | - new_value = data["hasGranularReplayPermissions"] |
619 | | - |
620 | | - option_inst, created = OrganizationOption.objects.update_or_create( |
621 | | - organization=org, key=option_key, defaults={"value": new_value} |
| 623 | + if "hasGranularReplayPermissions" in data: |
| 624 | + option_key = "sentry:granular-replay-permissions" |
| 625 | + new_value = data["hasGranularReplayPermissions"] |
| 626 | + option_inst, created = OrganizationOption.objects.update_or_create( |
| 627 | + organization=org, key=option_key, defaults={"value": new_value} |
| 628 | + ) |
| 629 | + changed_data["hasGranularReplayPermissions"] = f"to {new_value}" |
| 630 | + |
| 631 | + if "replayAccessMembers" in data: |
| 632 | + member_ids = data["replayAccessMembers"] |
| 633 | + if member_ids is None: |
| 634 | + member_ids = [] |
| 635 | + current_member_ids = set( |
| 636 | + OrganizationMemberReplayAccess.objects.filter(organization=org).values_list( |
| 637 | + "organizationmember_id", flat=True |
622 | 638 | ) |
623 | | - |
624 | | - changed_data["hasGranularReplayPermissions"] = f"to {new_value}" |
625 | | - |
626 | | - if "replayAccessMembers" in data: |
627 | | - member_ids = data["replayAccessMembers"] |
628 | | - |
629 | | - if member_ids is None: |
630 | | - member_ids = [] |
631 | | - |
632 | | - current_member_ids = set( |
633 | | - OrganizationMemberReplayAccess.objects.filter(organization=org).values_list( |
634 | | - "organizationmember_id", flat=True |
| 639 | + ) |
| 640 | + new_member_ids = set(member_ids) |
| 641 | + |
| 642 | + to_add = new_member_ids - current_member_ids |
| 643 | + to_remove = current_member_ids - new_member_ids |
| 644 | + if to_add: |
| 645 | + valid_member_ids = set( |
| 646 | + OrganizationMember.objects.filter(organization=org, id__in=to_add).values_list( |
| 647 | + "id", flat=True |
635 | 648 | ) |
636 | 649 | ) |
637 | | - new_member_ids = set(member_ids) |
638 | | - |
639 | | - to_add = new_member_ids - current_member_ids |
640 | | - to_remove = current_member_ids - new_member_ids |
641 | | - |
642 | | - if to_add: |
643 | | - valid_member_ids = set( |
644 | | - OrganizationMember.objects.filter( |
645 | | - organization=org, id__in=to_add |
646 | | - ).values_list("id", flat=True) |
| 650 | + invalid_member_ids = to_add - valid_member_ids |
| 651 | + if invalid_member_ids: |
| 652 | + raise serializers.ValidationError( |
| 653 | + { |
| 654 | + "replayAccessMembers": f"Invalid organization member IDs: {sorted(invalid_member_ids)}" |
| 655 | + } |
647 | 656 | ) |
648 | | - invalid_member_ids = to_add - valid_member_ids |
649 | | - if invalid_member_ids: |
650 | | - raise serializers.ValidationError( |
651 | | - { |
652 | | - "replayAccessMembers": f"Invalid organization member IDs: {sorted(invalid_member_ids)}" |
653 | | - } |
| 657 | + |
| 658 | + OrganizationMemberReplayAccess.objects.bulk_create( |
| 659 | + [ |
| 660 | + OrganizationMemberReplayAccess( |
| 661 | + organization=org, organizationmember_id=member_id |
654 | 662 | ) |
| 663 | + for member_id in to_add |
| 664 | + ], |
| 665 | + ignore_conflicts=True, |
| 666 | + ) |
655 | 667 |
|
656 | | - OrganizationMemberReplayAccess.objects.bulk_create( |
657 | | - [ |
658 | | - OrganizationMemberReplayAccess( |
659 | | - organization=org, organizationmember_id=member_id |
660 | | - ) |
661 | | - for member_id in to_add |
662 | | - ], |
663 | | - ignore_conflicts=True, |
664 | | - ) |
| 668 | + if to_remove: |
| 669 | + OrganizationMemberReplayAccess.objects.filter( |
| 670 | + organization=org, organizationmember_id__in=to_remove |
| 671 | + ).delete() |
665 | 672 |
|
| 673 | + if to_add or to_remove: |
| 674 | + changes = [] |
| 675 | + if to_add: |
| 676 | + changes.append(f"added {len(to_add)} member(s)") |
666 | 677 | if to_remove: |
667 | | - OrganizationMemberReplayAccess.objects.filter( |
668 | | - organization=org, organizationmember_id__in=to_remove |
669 | | - ).delete() |
670 | | - |
671 | | - if to_add or to_remove: |
672 | | - changes = [] |
673 | | - if to_add: |
674 | | - changes.append(f"added {len(to_add)} member(s)") |
675 | | - if to_remove: |
676 | | - changes.append(f"removed {len(to_remove)} member(s)") |
677 | | - changed_data["replayAccessMembers"] = ( |
678 | | - f"{' and '.join(changes)} (total: {len(new_member_ids)} member(s) with access)" |
679 | | - ) |
| 678 | + changes.append(f"removed {len(to_remove)} member(s)") |
| 679 | + changed_data["replayAccessMembers"] = ( |
| 680 | + f"{' and '.join(changes)} (total: {len(new_member_ids)} member(s) with access)" |
| 681 | + ) |
680 | 682 |
|
681 | 683 | if "openMembership" in data: |
682 | 684 | org.flags.allow_joinleave = data["openMembership"] |
|
0 commit comments