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(teams): Update open membership team permissions #16244

Merged
merged 2 commits into from
Jan 9, 2020
Merged
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
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