-
-
Notifications
You must be signed in to change notification settings - Fork 4.5k
feat(replay): add model to allow per-user access control for replays #104446
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
base: master
Are you sure you want to change the base?
feat(replay): add model to allow per-user access control for replays #104446
Conversation
|
This PR has a migration; here is the generated SQL for for --
-- Create model OrganizationMemberReplayAccess
--
CREATE TABLE "sentry_organizationmemberreplayaccess" ("id" bigint NOT NULL PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY, "date_added" timestamp with time zone NOT NULL, "organization_id" bigint NOT NULL, "organizationmember_id" bigint NOT NULL);
CREATE UNIQUE INDEX CONCURRENTLY "sentry_organizationmembe_organization_id_organiza_9700a74d_uniq" ON "sentry_organizationmemberreplayaccess" ("organization_id", "organizationmember_id");
ALTER TABLE "sentry_organizationmemberreplayaccess" ADD CONSTRAINT "sentry_organizationmembe_organization_id_organiza_9700a74d_uniq" UNIQUE USING INDEX "sentry_organizationmembe_organization_id_organiza_9700a74d_uniq";
ALTER TABLE "sentry_organizationmemberreplayaccess" ADD CONSTRAINT "sentry_organizationm_organization_id_99e648de_fk_sentry_or" FOREIGN KEY ("organization_id") REFERENCES "sentry_organization" ("id") DEFERRABLE INITIALLY DEFERRED NOT VALID;
ALTER TABLE "sentry_organizationmemberreplayaccess" VALIDATE CONSTRAINT "sentry_organizationm_organization_id_99e648de_fk_sentry_or";
ALTER TABLE "sentry_organizationmemberreplayaccess" ADD CONSTRAINT "sentry_organizationm_organizationmember_i_2283bdfc_fk_sentry_or" FOREIGN KEY ("organizationmember_id") REFERENCES "sentry_organizationmember" ("id") DEFERRABLE INITIALLY DEFERRED NOT VALID;
ALTER TABLE "sentry_organizationmemberreplayaccess" VALIDATE CONSTRAINT "sentry_organizationm_organizationmember_i_2283bdfc_fk_sentry_or";
CREATE INDEX CONCURRENTLY "sentry_organizationmemberreplayaccess_organization_id_99e648de" ON "sentry_organizationmemberreplayaccess" ("organization_id");
CREATE INDEX CONCURRENTLY "sentry_organizationmemberr_organizationmember_id_2283bdfc" ON "sentry_organizationmemberreplayaccess" ("organizationmember_id"); |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #104446 +/- ##
===========================================
- Coverage 80.52% 80.51% -0.01%
===========================================
Files 9330 9331 +1
Lines 400670 400832 +162
Branches 25692 25692
===========================================
+ Hits 322634 322742 +108
- Misses 77570 77624 +54
Partials 466 466 |
| child=serializers.IntegerField(), | ||
| required=False, | ||
| allow_null=True, | ||
| help_text="List of organization member IDs that have access to replay data. Only modifiable by owners and managers.", |
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.
Most of our permission systems are based on team membership and not individual members. Should this also use the team model?
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.
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.
My only concern was that we're diverging from the typical permissions model. It sounds like you're doing that on purpose though.
| hasGranularReplayPermissions = serializers.BooleanField( | ||
| help_text="Specify `true` to enable granular replay permissions, allowing per-member access control for replay data.", | ||
| required=False, | ||
| ) | ||
| replayAccessMembers = serializers.ListField( |
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.
If replayAccessMembers is sent in a request without hasGranularReplayPermissions will those changes be applied or silently dropped?
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.
The API request should go through and apply the changes nonetheless (e.g. to apply a list of privileged users first, and then switch to enforcing access), even when the flag is not turned on.
wedamija
left a comment
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.
I want to make sure that this definitely needs to be per user, and not per team. Usually throughout sentry we assign permissions per team, and it's pretty rare that we give individual permissions to folks.
I think that ideally, we'd add a new permission scope, and teams that have a row here are given the scope.
Alternatively, if you do need this at the user level, I'm thinking that this table might be overkill. You could just add another boolean to
sentry/src/sentry/models/organizationmember.py
Lines 59 to 69 in 6c26d1f
| _OrganizationMemberFlags = TypedDict( | |
| "_OrganizationMemberFlags", | |
| { | |
| "sso:linked": bool, | |
| "sso:invalid": bool, | |
| "member-limit:restricted": bool, | |
| "idp:provisioned": bool, | |
| "idp:role-restricted": bool, | |
| "partnership:restricted": bool, | |
| }, | |
| ) |
OrganizationMember is allowed to access replays.
6938277 to
fdc6279
Compare
|
This PR has a migration; here is the generated SQL for for --
-- Create model OrganizationMemberReplayAccess
--
CREATE TABLE "sentry_organizationmemberreplayaccess" ("id" bigint NOT NULL PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY, "date_updated" timestamp with time zone NOT NULL, "date_added" timestamp with time zone NOT NULL, "organizationmember_id" bigint NOT NULL UNIQUE);
ALTER TABLE "sentry_organizationmemberreplayaccess" ADD CONSTRAINT "sentry_organizationm_organizationmember_i_2283bdfc_fk_sentry_or" FOREIGN KEY ("organizationmember_id") REFERENCES "sentry_organizationmember" ("id") DEFERRABLE INITIALLY DEFERRED NOT VALID;
ALTER TABLE "sentry_organizationmemberreplayaccess" VALIDATE CONSTRAINT "sentry_organizationm_organizationmember_i_2283bdfc_fk_sentry_or"; |
| new_user_ids = set(user_ids) | ||
|
|
||
| to_add = new_user_ids - current_user_ids | ||
| to_remove = current_user_ids - new_user_ids |
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.
Bug: None values cause inconsistent read/write behavior in replay access
The serializer filters out None values from replayAccessMembers when reading, but the save() method does not filter them from current_user_ids. If an OrganizationMember linked to a replay access record has user_id=None (e.g., after membership changes), the API returns a list without that entry. When the client sends the same list back, to_remove will contain None, but the SQL IN (NULL) clause won't match any rows, causing the delete to silently fail. The audit log will incorrectly report users were removed when they weren't, and orphan records will remain.
This PR adds elements to our data model to enable granular permissions for replay.
Two pieces of data are added:
sentry:granular-replay-permissionsthat specifies whether or not the granular access control should be enabled andOrganizationMemberReplayAccessto track which organization members have access to replay data if the granular permission controls are enabledUpdated serializers (
DetailedOrganizationSerializerResponse,OrganizationDetailsPutSerializer, andOrganizationSerializer) to expose and allow modification of thereplayAccessMembersfield and thehasGranularReplayPermissionsflagModified organization API endpoints to handle reading and updating granular replay permissions, including validation for feature flag and admin scope, and logic to add or remove member access.
More information on the plan & product impact can be found here Research granular permissions for viewing Replays
A comment on the implementation approach, for the record: I thought about making this a more generic permissions model, so we can use it for other use-cases, but since this is currently only scoped to Replay, I want to do what we need and not optimize for any later use-case. If this is to be extended to other data types / product features, this should be easily migrate-able to a more generic model that contains relationships between Organization, OrganizationMember and the permission type.
Closes TET-1565
Closes TET-1566