Skip to content

Commit

Permalink
feat(teams): Update open membership team permissions (#16244)
Browse files Browse the repository at this point in the history
  • Loading branch information
maheskett authored and Nisanthan Nanthakumar committed Jan 9, 2020
1 parent e3a3f11 commit 0a5fef6
Show file tree
Hide file tree
Showing 5 changed files with 252 additions and 338 deletions.
4 changes: 4 additions & 0 deletions src/sentry/api/endpoints/organization_member_team_details.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ def _can_access(self, request, member, organization):
* If they are modifying their own membership
* If the user's role is higher than the targeted user's role (e.g. "admin" can't modify "owner")
* If the user is an "admin" and they are modifying a team they are a member of
* If the "open membership" setting is enabled and the targeted user is being added to a team
"""

if is_active_superuser(request):
Expand All @@ -76,6 +77,9 @@ def _can_access(self, request, member, organization):
):
return True

if request.method == "POST" and organization.flags.allow_joinleave:
return True

return False

def _can_admin_team(self, request, organization, team_slug):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,11 +137,11 @@ const TeamDetails = createReactClass({
} else if (!team || !team.hasAccess) {
return (
<Alert type="warning">
<h4>{t('You do not have access to this team')}</h4>

{team && (
{team ? (
<RequestAccessWrapper>
{tct('You may try to request access to [team]', {team: `#${team.slug}`})}
{tct('You do not have access to the [teamSlug] team.', {
teamSlug: <strong>{`#${team.slug}`}</strong>,
})}
<Button
disabled={this.state.requesting || team.isPending}
size="small"
Expand All @@ -150,6 +150,8 @@ const TeamDetails = createReactClass({
{team.isPending ? t('Request Pending') : t('Request Access')}
</Button>
</RequestAccessWrapper>
) : (
<div>{t('You do not have access to this team.')}</div>
)}
</Alert>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,10 +199,13 @@ class TeamMembers extends React.Component {
};

renderDropdown = access => {
// You can add members if you have `org:write` or you have `team:admin` AND you belong to the team
// a parent "team details" request should determine your team membership, so this only view is rendered only
// when you are a member
const canAddMembers = access.has('org:write') || access.has('team:admin');
const {organization} = this.props;

// members can add other members to a team if the `Open Membership` setting is enabled
// otherwise, `org:write` or `team:admin` permissions are required
const hasOpenMembership = organization && organization.openMembership;
const hasWriteAccess = access.has('org:write') || access.has('team:admin');
const canAddMembers = hasOpenMembership || hasWriteAccess;

if (!canAddMembers) {
return (
Expand All @@ -211,6 +214,7 @@ class TeamMembers extends React.Component {
title={t('You do not have enough permission to add new members')}
isOpen={false}
size="xsmall"
data-test-id="add-member"
>
{t('Add Member')}
</DropdownButton>
Expand Down
59 changes: 54 additions & 5 deletions tests/js/spec/views/teamMembers.spec.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,28 @@ describe('TeamMembers', function() {
wrapper.update();
});

it('can invite member from team dropdown', async function() {
it('can invite member from team dropdown with access', async function() {
const org = TestStubs.Organization({access: ['team:admin'], openMembership: false});
const wrapper = mountWithTheme(
<TeamMembers
params={{orgId: organization.slug, teamId: team.slug}}
organization={organization}
/>,
<TeamMembers params={{orgId: org.slug, teamId: team.slug}} organization={org} />,
routerContext
);

await tick();
wrapper.update();

wrapper.find('DropdownButton[data-test-id="add-member"]').simulate('click');
wrapper
.find('StyledCreateMemberLink[data-test-id="invite-member"]')
.simulate('click');

expect(openInviteMembersModal).toHaveBeenCalled();
});

it('can invite member from team dropdown with access and `Open Membership` enabled', async function() {
const org = TestStubs.Organization({access: ['team:admin'], openMembership: true});
const wrapper = mountWithTheme(
<TeamMembers params={{orgId: org.slug, teamId: team.slug}} organization={org} />,
routerContext
);

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

it('can invite member from team dropdown without access and `Open Membership` enabled', async function() {
const org = TestStubs.Organization({access: [], openMembership: true});
const wrapper = mountWithTheme(
<TeamMembers params={{orgId: org.slug, teamId: team.slug}} organization={org} />,
routerContext
);

await tick();
wrapper.update();

wrapper.find('DropdownButton[data-test-id="add-member"]').simulate('click');
wrapper
.find('StyledCreateMemberLink[data-test-id="invite-member"]')
.simulate('click');

expect(openInviteMembersModal).toHaveBeenCalled();
});

it('cannot invite member from team dropdown without access and `Open Membership` disabled', async function() {
const org = TestStubs.Organization({access: [], openMembership: false});
const wrapper = mountWithTheme(
<TeamMembers params={{orgId: org.slug, teamId: team.slug}} organization={org} />,
routerContext
);

await tick();
wrapper.update();

expect(
wrapper.find('DropdownButton[data-test-id="add-member"]').prop('disabled')
).toBe(true);
});

it('can remove member from team', async function() {
const endpoint = `/organizations/${organization.slug}/members/${
members[0].id
Expand Down
Loading

0 comments on commit 0a5fef6

Please sign in to comment.