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

fix(invites): allow implicit team membership to invite to projects on org invites #25773

Merged
merged 4 commits into from
Oct 25, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
18 changes: 13 additions & 5 deletions posthog/api/organization_invite.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,15 +78,23 @@ def validate_private_project_access(
if not is_private:
continue
try:
explicit_team_membership: ExplicitTeamMembership = ExplicitTeamMembership.objects.get(
team_membership: ExplicitTeamMembership | OrganizationMembership = ExplicitTeamMembership.objects.get(
team_id=item["id"],
parent_membership__user=self.context["request"].user,
)
except ExplicitTeamMembership.DoesNotExist:
raise exceptions.ValidationError(
team_error,
)
if explicit_team_membership.level < item["level"]:
try:
# No explicit team membership. Try getting the implicit team membership - any org owners and admins can invite to any team
team_membership = OrganizationMembership.objects.get(
organization_id=self.context["organization_id"],
user=self.context["request"].user,
level__in=[OrganizationMembership.Level.ADMIN, OrganizationMembership.Level.OWNER],
)
except OrganizationMembership.DoesNotExist:
raise exceptions.ValidationError(
team_error,
)
if team_membership.level < item["level"]:
raise exceptions.ValidationError(
"You cannot invite to a private project with a higher level than your own.",
)
Expand Down
40 changes: 36 additions & 4 deletions posthog/api/test/test_organization_invites.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,14 +196,14 @@ def test_can_specify_private_project_access_in_invite(self):
{
"target_email": email,
"level": OrganizationMembership.Level.MEMBER,
"private_project_access": [{"id": self.team.id, "level": ExplicitTeamMembership.Level.ADMIN}],
"private_project_access": [{"id": private_team.id, "level": ExplicitTeamMembership.Level.ADMIN}],
},
)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
obj = OrganizationInvite.objects.get(id=response.json()["id"])
self.assertEqual(obj.level, OrganizationMembership.Level.MEMBER)
self.assertEqual(
obj.private_project_access, [{"id": self.team.id, "level": ExplicitTeamMembership.Level.ADMIN}]
obj.private_project_access, [{"id": private_team.id, "level": ExplicitTeamMembership.Level.ADMIN}]
)
self.assertEqual(OrganizationInvite.objects.count(), count + 1)

Expand All @@ -218,16 +218,48 @@ def test_can_specify_private_project_access_in_invite(self):
{
"target_email": email,
"level": OrganizationMembership.Level.MEMBER,
"private_project_access": [{"id": self.team.id, "level": ExplicitTeamMembership.Level.ADMIN}],
"private_project_access": [{"id": private_team.id, "level": ExplicitTeamMembership.Level.ADMIN}],
},
)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
obj = OrganizationInvite.objects.get(id=response.json()["id"])
self.assertEqual(obj.level, OrganizationMembership.Level.MEMBER)
self.assertEqual(
obj.private_project_access, [{"id": private_team.id, "level": ExplicitTeamMembership.Level.ADMIN}]
)
self.assertEqual(OrganizationInvite.objects.count(), count + 1)

def test_can_invite_to_private_project_if_user_has_implicit_access_to_team(self):
"""
Org admins and owners can invite to any private project, even if they're not an explicit admin of the team
because they have implicit access due to their org membership level.
"""
org_membership = OrganizationMembership.objects.get(user=self.user, organization=self.organization)
org_membership.level = OrganizationMembership.Level.ADMIN
org_membership.save()

email = "[email protected]"
count = OrganizationInvite.objects.count()
private_team = Team.objects.create(organization=self.organization, name="Private Team", access_control=True)
response = self.client.post(
"/api/organizations/@current/invites/",
{
"target_email": email,
"level": OrganizationMembership.Level.MEMBER,
"private_project_access": [{"id": private_team.id, "level": ExplicitTeamMembership.Level.ADMIN}],
},
)

self.assertEqual(response.status_code, status.HTTP_201_CREATED)
obj = OrganizationInvite.objects.get(id=response.json()["id"])
self.assertEqual(obj.level, OrganizationMembership.Level.MEMBER)
self.assertEqual(
obj.private_project_access, [{"id": self.team.id, "level": ExplicitTeamMembership.Level.ADMIN}]
obj.private_project_access, [{"id": private_team.id, "level": ExplicitTeamMembership.Level.ADMIN}]
)
self.assertEqual(OrganizationInvite.objects.count(), count + 1)
# reset the org membership level in case it's used in other tests
org_membership.level = OrganizationMembership.Level.MEMBER
org_membership.save()

def test_invite_fails_if_team_in_private_project_access_not_in_org(self):
email = "[email protected]"
Expand Down
Loading