Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions src/sentry/issues/endpoints/project_ownership.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,10 @@ def _validate_no_codeowners(rules):

def _validate_team_ownership(self, rules):
"""
Validate that the user has permission to assign ownership to the teams
referenced in the rules. Users must either have team:admin scope or be
a member of each team they're assigning ownership to.
Validate that the user has permission to assign ownership to the teams referenced in the
rules. If the organization has open team membership enabled, any user can assign ownership.
Otherwise, users must either have `team:admin` scope or be a member of each team to which
they're assigning ownership.
"""
team_slugs = {
owner.get("identifier")
Expand All @@ -94,6 +95,13 @@ def _validate_team_ownership(self, rules):
)

project = self.context["ownership"].project
organization = project.organization

# If open team membership is enabled, users can assign any team as owner since they could
# join any team anyway
if organization and organization.flags.allow_joinleave:
return

user_team_count = OrganizationMemberTeam.objects.filter(
team__slug__in=team_slugs,
team__projectteam__project=project,
Expand Down
53 changes: 46 additions & 7 deletions tests/sentry/issues/endpoints/test_project_ownership.py
Original file line number Diff line number Diff line change
Expand Up @@ -394,12 +394,15 @@ def test_update_by_member_denied(self) -> None:
resp = self.client.put(self.path, {"fallthrough": False})
assert resp.status_code == 403

def test_member_cannot_assign_team_not_member_of(self) -> None:
def test_closed_membership_org_member_cannot_assign_team_not_member_of(self) -> None:
"""
Test that a member cannot add an ownership rule assigning issues to a team
they are not a member of. This is a regression test for an authorization
bypass vulnerability.
Test that a member cannot add an ownership rule assigning issues to a team they are not a
member of if the org has open team membership off. This is a regression test for an
authorization bypass vulnerability.
"""
self.organization.flags.allow_joinleave = False
self.organization.save()

other_team = self.create_team(organization=self.organization, slug="other-team", members=[])
self.project.add_team(other_team)

Expand Down Expand Up @@ -435,11 +438,30 @@ def test_admin_can_assign_any_team(self) -> None:
assert resp.status_code == 200
assert resp.data["raw"] == "*.js #other-team"

def test_member_mixed_teams_denied(self) -> None:
def test_open_membership_org_member_can_assign_any_team(self) -> None:
"""
Test that if a member tries to add rules with multiple teams,
and they're not a member of one of them, the request is denied.
Test that a user in an org with open membership CAN add ownership rules for any team.
"""
self.organization.flags.allow_joinleave = True
self.organization.save()

other_team = self.create_team(organization=self.organization, slug="other-team", members=[])
self.project.add_team(other_team)

self.login_as(user=self.member_user)

resp = self.client.put(self.path, {"raw": "*.js #other-team"})
assert resp.status_code == 200
assert resp.data["raw"] == "*.js #other-team"

def test_closed_membership_org_member_mixed_teams_denied(self) -> None:
"""
Test that in an org with closed team membership, if a member tries to add rules with
multiple teams, and they're not a member of one of them, the request is denied.
"""
self.organization.flags.allow_joinleave = False
self.organization.save()

other_team = self.create_team(organization=self.organization, slug="other-team", members=[])
self.project.add_team(other_team)

Expand All @@ -448,3 +470,20 @@ def test_member_mixed_teams_denied(self) -> None:
resp = self.client.put(self.path, {"raw": "*.js #tiger-team #other-team"})
assert resp.status_code == 400
assert "do not have permission" in str(resp.data["raw"][0])

def test_open_membership_org_member_mixed_teams_allowed(self) -> None:
"""
Test that in an org with open team membership, if a member tries to add rules with multiple
teams, and they're not a member of one of them, the request still goes through.
"""
self.organization.flags.allow_joinleave = True
self.organization.save()

other_team = self.create_team(organization=self.organization, slug="other-team", members=[])
self.project.add_team(other_team)

self.login_as(user=self.member_user)

resp = self.client.put(self.path, {"raw": "*.js #tiger-team #other-team"})
assert resp.status_code == 200
assert resp.data["raw"] == "*.js #tiger-team #other-team"
Loading