Skip to content

Commit bf6abde

Browse files
committed
Update team permissions
1 parent fae60ee commit bf6abde

File tree

4 files changed

+246
-334
lines changed

4 files changed

+246
-334
lines changed

src/sentry/api/endpoints/organization_member_team_details.py

+4
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ def _can_access(self, request, member, organization):
5656
* If they are modifying their own membership
5757
* If the user's role is higher than the targeted user's role (e.g. "admin" can't modify "owner")
5858
* If the user is an "admin" and they are modifying a team they are a member of
59+
* If the "open membership" setting is enabled and the targeted user is being added to a team
5960
"""
6061

6162
if is_active_superuser(request):
@@ -76,6 +77,9 @@ def _can_access(self, request, member, organization):
7677
):
7778
return True
7879

80+
if request.method == "POST" and organization.flags.allow_joinleave:
81+
return True
82+
7983
return False
8084

8185
def _can_admin_team(self, request, organization, team_slug):

src/sentry/static/sentry/app/views/settings/organizationTeams/teamMembers.jsx

+8-4
Original file line numberDiff line numberDiff line change
@@ -199,10 +199,13 @@ class TeamMembers extends React.Component {
199199
};
200200

201201
renderDropdown = access => {
202-
// You can add members if you have `org:write` or you have `team:admin` AND you belong to the team
203-
// a parent "team details" request should determine your team membership, so this only view is rendered only
204-
// when you are a member
205-
const canAddMembers = access.has('org:write') || access.has('team:admin');
202+
const {organization} = this.props;
203+
204+
// members can add other members to a team if the `Open Membership` setting is enabled
205+
// otherwise, `org:write` or `team:admin` permissions are required
206+
const hasOpenMembership = organization && organization.openMembership;
207+
const hasWriteAccess = access.has('org:write') || access.has('team:admin');
208+
const canAddMembers = hasOpenMembership || hasWriteAccess;
206209

207210
if (!canAddMembers) {
208211
return (
@@ -211,6 +214,7 @@ class TeamMembers extends React.Component {
211214
title={t('You do not have enough permission to add new members')}
212215
isOpen={false}
213216
size="xsmall"
217+
data-test-id="add-member"
214218
>
215219
{t('Add Member')}
216220
</DropdownButton>

tests/js/spec/views/teamMembers.spec.jsx

+54-5
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,28 @@ describe('TeamMembers', function() {
4141
wrapper.update();
4242
});
4343

44-
it('can invite member from team dropdown', async function() {
44+
it('can invite member from team dropdown with access', async function() {
45+
const org = TestStubs.Organization({access: ['team:admin'], openMembership: false});
4546
const wrapper = mountWithTheme(
46-
<TeamMembers
47-
params={{orgId: organization.slug, teamId: team.slug}}
48-
organization={organization}
49-
/>,
47+
<TeamMembers params={{orgId: org.slug, teamId: team.slug}} organization={org} />,
48+
routerContext
49+
);
50+
51+
await tick();
52+
wrapper.update();
53+
54+
wrapper.find('DropdownButton[data-test-id="add-member"]').simulate('click');
55+
wrapper
56+
.find('StyledCreateMemberLink[data-test-id="invite-member"]')
57+
.simulate('click');
58+
59+
expect(openInviteMembersModal).toHaveBeenCalled();
60+
});
61+
62+
it('can invite member from team dropdown with access and `Open Membership` enabled', async function() {
63+
const org = TestStubs.Organization({access: ['team:admin'], openMembership: true});
64+
const wrapper = mountWithTheme(
65+
<TeamMembers params={{orgId: org.slug, teamId: team.slug}} organization={org} />,
5066
routerContext
5167
);
5268

@@ -61,6 +77,39 @@ describe('TeamMembers', function() {
6177
expect(openInviteMembersModal).toHaveBeenCalled();
6278
});
6379

80+
it('can invite member from team dropdown without access and `Open Membership` enabled', async function() {
81+
const org = TestStubs.Organization({access: [], openMembership: true});
82+
const wrapper = mountWithTheme(
83+
<TeamMembers params={{orgId: org.slug, teamId: team.slug}} organization={org} />,
84+
routerContext
85+
);
86+
87+
await tick();
88+
wrapper.update();
89+
90+
wrapper.find('DropdownButton[data-test-id="add-member"]').simulate('click');
91+
wrapper
92+
.find('StyledCreateMemberLink[data-test-id="invite-member"]')
93+
.simulate('click');
94+
95+
expect(openInviteMembersModal).toHaveBeenCalled();
96+
});
97+
98+
it('cannot invite member from team dropdown without access and `Open Membership` disabled', async function() {
99+
const org = TestStubs.Organization({access: [], openMembership: false});
100+
const wrapper = mountWithTheme(
101+
<TeamMembers params={{orgId: org.slug, teamId: team.slug}} organization={org} />,
102+
routerContext
103+
);
104+
105+
await tick();
106+
wrapper.update();
107+
108+
expect(
109+
wrapper.find('DropdownButton[data-test-id="add-member"]').prop('disabled')
110+
).toBe(true);
111+
});
112+
64113
it('can remove member from team', async function() {
65114
const endpoint = `/organizations/${organization.slug}/members/${
66115
members[0].id

0 commit comments

Comments
 (0)