Skip to content
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

feat(api): Document add+remove member from team #50900

Merged
merged 35 commits into from
Aug 9, 2023

Conversation

schew2381
Copy link
Contributor

@schew2381 schew2381 commented Jun 13, 2023

Requires #53364 + #54148

Background

Sentry has 2 types of tokens to hit our API, user and organization auth tokens. The organization tokens are also commonly referred to as integration tokens because you can only create them through the Integration flow (see here for walkthrough).

User Auth Tokens

User auth tokens are tied to a Sentry user account, not an organization. You can create a single user token that can be used for multiple organizations here. User tokens can have non-hierarchical scopes (eg: org:write doesn't have org:read because the UI allows you to select individual scopes)
Screenshot 2023-08-03 at 3 13 00 PM

When hitting an endpoint, we take the intersection of the user token's scope and the scopes allotted to the user's organization role. If any scope in this intersection is allowed to access the endpoint, we allow it to proceed. For example, if a regular organization member uses a user token with (org:read, org:admin), the intersection will only include (org:admin).

Organization Auth Tokens

Organization tokens are tied to a single organization. They can also have have non-hierarchical scopes but only when created through a non-public API. The UI only allows you to create scopes with hierarchy (selecting admin includes org:read and org:write)
Screenshot 2023-08-03 at 3 22 18 PM

When hitting an endpoint, we compare the exact scopes on the token without any extra logic

Complications

The permissions scopes for both endpoints are complicated because they depend on the Open Membership setting, the type of token, and the role of the user. The endpoint logic and documentation is tedious especially with user auth tokens which contain many edge cases. Additionally, we don't document which roles have which permissions, only what actions are allowed per role.

Bugs - Fixed in separate PR #53364 + #54148

  • When Open Membership was disabled, we allowed org tokens withorg:read to still add members to teams
  • Org owners and managers were not allowed to remove members from teams they were not part of using user tokens with team:write
    • The fix changed the scope to team:admin per our scopes documentation and also allowed the above to occur (with team:admin over team:write)

Edge Cases

  • You can always delete yourself from a team using org:read w/ user token
  • Team Admins that are regular org members can act on teams using team:write, but they don't have that permission on an org level so they can't even hit the endpoint with just team:write b/c their user auth token is denied
    • We get around this by instructing Team Admins to use org:read to make it past the base permission check, and team:write to pass the endpoint logic
  • Org admins have team:write, but they should only be allowed delete members from teams they are a part of. This makes the second bugfix above more difficult, b/c we cannot simply check for team:write on the user token.
    • This is an excellent example of how the deprecated Org Admin role complicates things. The role has team:write on an org-level, but we restrict some team actions depending on whether or not they are part of the team.

Add member

Screenshot 2023-08-08 at 12 14 28 AM

Delete member

Screenshot 2023-08-08 at 12 16 25 AM

@schew2381 schew2381 changed the title feat(api): Make add members to team endpoint public feat(api): Document add+remove member from team Jun 27, 2023
@codecov
Copy link

codecov bot commented Jun 27, 2023

Codecov Report

Merging #50900 (130701e) into master (e58fb2a) will increase coverage by 0.01%.
Report is 67 commits behind head on master.
The diff coverage is 100.00%.

Additional details and impacted files
@@            Coverage Diff             @@
##           master   #50900      +/-   ##
==========================================
+ Coverage   79.62%   79.64%   +0.01%     
==========================================
  Files        4982     4986       +4     
  Lines      211081   211434     +353     
  Branches    35960    36040      +80     
==========================================
+ Hits       168082   168400     +318     
- Misses      37831    37849      +18     
- Partials     5168     5185      +17     
Files Changed Coverage Δ
.../api/endpoints/organization_member/team_details.py 85.81% <100.00%> (+0.85%) ⬆️
src/sentry/api/serializers/models/team.py 94.17% <100.00%> (+0.17%) ⬆️
src/sentry/apidocs/examples/team_examples.py 100.00% <100.00%> (ø)
src/sentry/apidocs/parameters.py 100.00% <100.00%> (ø)

... and 97 files with indirect coverage changes

@schew2381 schew2381 requested review from sentaur-athena and a team July 24, 2023 23:20
@schew2381 schew2381 marked this pull request as ready for review July 24, 2023 23:21
@schew2381 schew2381 requested a review from a team as a code owner July 24, 2023 23:21
Note that the permission scopes also vary depending on the organization setting `"Open
Membership"` and the type of authorization token. The following table outlines the accepted
scopes.
<table style="width: 100%;">
Copy link
Member

Choose a reason for hiding this comment

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

Do you think this can be used by other APIs as well? If so can you create a util method that would generate the html from a python map or something? Ideally can this be part of extend_schema?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't think this is extensible to other APIs for now, probably just the ones in this file b/c they all involve Team Admin

@region_silo_endpoint
class OrganizationMemberTeamDetailsEndpoint(OrganizationMemberEndpoint):
public = {"DELETE", "POST"}
Copy link
Member

Choose a reason for hiding this comment

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

just for my own sake -- how are we determining what is public? what is currently public?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We're doing APIs that are most requested/used by our customers first in phases that you can fine in this notion doc. The delete+post are phase 1 endpoints

Comment on lines +181 to +192
# We require a third Team Response TypedDict that inherits like so:
# TeamSerializerResponse
# * BaseTeamSerializerResponse
# * _TeamSerializerResponseOptional
# instead of having this inheritance:
# BaseTeamSerializerResponse
# * _TeamSerializerResponseOptional
# b/c of how drf-spectacular builds schema using @extend_schema. When specifying a DRF serializer
# as a response, the schema will include all optional fields even if the response body for that
# request never includes those fields. There is no way to have a single serializer that we can
# manipulate to exclude optional fields at will, so we need two separate serializers where one
# returns the base response fields, and the other returns the combined base+optional response fields
Copy link
Member

Choose a reason for hiding this comment

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

does _TeamSerializerResponseOptional exist? or is this just a placeholder to allow for optional fields in TeamSerializerResponse?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes it exists here. We put optional fields in there that may not be set

schew2381 added a commit that referenced this pull request Aug 8, 2023
## Duplicate Functionality
There is another endpoint called `OrganizationMemberTeamDetailsEndpoint` that can also update a single member's Team Role like the endpoint this PR documents. This is the API that's called when you're actually on the `Team` tab pictured below.
<img width="833" alt="Screenshot 2023-08-04 at 2 12 11 PM"
src="https://github.com/getsentry/sentry/assets/67301797/d4252e6b-dfda-4e47-9b3a-501c34aeb261">

I decided to not document the duplicate endpoint because it runs into the same permission scopes issue as
#50900 with regards to Team Admins. I also wanted to avoid 2 endpoints that do the same thing in our documentation.

Unfortunately, this means that Team Admins that are regular org members cannot change roles for the teams they're on. I don't believe this is a huge issue because Team Admins do this through the UI, and changing team roles is not as common as adding/removing team members.


## Endpoint Title
This is titled `Update an Organization Member's Roles` instead of `Update an Organization Member` because the only other thing this endpoint can do is resend an invite to an invited member who hasn't joined yet. I don't believe this is necessary to document, and including `roles` in the source makes the endpoint much easier to find.

![Screenshot 2023-08-08 at 1 56 45
PM](https://github.com/getsentry/sentry/assets/67301797/bd30e4e6-2d39-40c9-9bf7-3df5c5d293d5)


## Garbage in Response
Unfortunately we interact with roles on the frontend by piping them through API responses. This leads to extremely long responses where the roles by themselves make up ~80% of body. Ideally we would add role information to the frontend, but that is a very difficult problem.

Because the frontend expects roles, the `TypedDict` return type for the PUT response serializer cannot have these roles as optional. The example response is forced to include them or else the example validator errors. Below is what the roles look like in the example (very little is shown).

![Screenshot 2023-08-02 at 6 30 12
PM](https://github.com/getsentry/sentry/assets/67301797/1328f460-7eed-4ae3-a02f-433983c885de)
![Screenshot 2023-08-02 at 6 30 26
PM](https://github.com/getsentry/sentry/assets/67301797/f1fcc297-b831-4aa5-a6de-4ad42525b715)
@schew2381 schew2381 merged commit d35deae into master Aug 9, 2023
55 of 56 checks passed
@schew2381 schew2381 deleted the seiji/feat/add-member-to-team-docs-update branch August 9, 2023 17:06
@github-actions github-actions bot locked and limited conversation to collaborators Aug 25, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Scope: Backend Automatically applied to PRs that change backend components
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants