Merge pull request #2169 from coreos-inc/multi-invite

Change team invitation acceptance to join all invited teams under the org
This commit is contained in:
josephschorr 2016-11-28 19:00:23 -05:00 committed by GitHub
commit b24b8629e5
3 changed files with 119 additions and 12 deletions

View file

@ -312,10 +312,24 @@ def find_matching_team_invite(code, user_obj):
return found return found
def find_organization_invites(organization, user_obj):
""" Finds all organization team invites for the given user under the given organization. """
invite_check = (TeamMemberInvite.user == user_obj)
if user_obj.verified:
invite_check = invite_check | (TeamMemberInvite.email == user_obj.email)
query = (TeamMemberInvite
.select()
.join(Team)
.where(invite_check, Team.organization == organization))
return query
def confirm_team_invite(code, user_obj): def confirm_team_invite(code, user_obj):
""" Confirms the given team invite code for the given user by adding the user to the team """ Confirms the given team invite code for the given user by adding the user to the team
and deleting the code. Raises a DataModelException if the code was not found or does and deleting the code. Raises a DataModelException if the code was not found or does
not apply to the given user. """ not apply to the given user. If the user is invited to two or more teams under the
same organization, they are automatically confirmed for all of them. """
found = find_matching_team_invite(code, user_obj) found = find_matching_team_invite(code, user_obj)
# If the invite is for a specific user, we have to confirm that here. # If the invite is for a specific user, we have to confirm that here.
@ -324,15 +338,18 @@ def confirm_team_invite(code, user_obj):
Please login to that account and try again.""" % found.user.username Please login to that account and try again.""" % found.user.username
raise DataModelException(message) raise DataModelException(message)
# Find all matching invitations for the user under the organization.
for invite in find_organization_invites(found.team.organization, user_obj):
# Add the user to the team. # Add the user to the team.
try: try:
add_user_to_team(user_obj, found.team) add_user_to_team(user_obj, invite.team)
except UserAlreadyInTeam: except UserAlreadyInTeam:
# Ignore. # Ignore.
pass pass
# Delete the invite and return the team. # Delete the invite and return the team.
invite.delete_instance()
team = found.team team = found.team
inviter = found.inviter inviter = found.inviter
found.delete_instance()
return (team, inviter) return (team, inviter)

View file

@ -28,7 +28,8 @@ def permission_view(permission):
def try_accept_invite(code, user): def try_accept_invite(code, user):
(team, inviter) = model.team.confirm_team_invite(code, user) (team, inviter) = model.team.confirm_team_invite(code, user)
model.notification.delete_matching_notifications(user, 'org_team_invite', code=code) model.notification.delete_matching_notifications(user, 'org_team_invite',
org=team.organization.username)
orgname = team.organization.username orgname = team.organization.username
log_action('org_team_member_invite_accepted', orgname, { log_action('org_team_member_invite_accepted', orgname, {
@ -208,6 +209,7 @@ class TeamMemberList(ApiResource):
invites = model.team.get_organization_team_member_invites(team.id) invites = model.team.get_organization_team_member_invites(team.id)
data = { data = {
'name': teamname,
'members': [member_view(m) for m in members] + [invite_view(i) for i in invites], 'members': [member_view(m) for m in members] + [invite_view(i) for i in invites],
'can_edit': edit_permission.can() 'can_edit': edit_permission.can()
} }

View file

@ -227,14 +227,14 @@ class ApiTestCase(unittest.TestCase):
def assertNotInTeam(self, data, membername): def assertNotInTeam(self, data, membername):
for memberData in data['members']: for memberData in data['members']:
if memberData['name'] == membername: if memberData['name'] == membername:
self.fail(membername + ' found in team: ' + json.dumps(data)) self.fail(membername + ' found in team: ' + data['name'])
def assertInTeam(self, data, membername): def assertInTeam(self, data, membername):
for member_data in data['members']: for member_data in data['members']:
if member_data['name'] == membername: if member_data['name'] == membername:
return return
self.fail(membername + ' not found in team: ' + py_json.dumps(data)) self.fail(membername + ' not found in team: ' + data['name'])
def login(self, username, password='password'): def login(self, username, password='password'):
return self.postJsonResponse(Signin, data=dict(username=username, password=password)) return self.postJsonResponse(Signin, data=dict(username=username, password=password))
@ -682,6 +682,94 @@ class TestCreateNewUser(ApiTestCase):
teamname='owners')) teamname='owners'))
self.assertNotInTeam(json, NEW_USER_DETAILS['username']) self.assertNotInTeam(json, NEW_USER_DETAILS['username'])
def test_createuser_withmultipleteaminvites(self):
inviter = model.user.get_user(ADMIN_ACCESS_USER)
owners_team = model.team.get_organization_team(ORGANIZATION, 'owners')
readers_team = model.team.get_organization_team(ORGANIZATION, 'readers')
other_owners_team = model.team.get_organization_team('library', 'owners')
owners_invite = model.team.add_or_invite_to_team(inviter, owners_team, None,
NEW_USER_DETAILS['email'])
readers_invite = model.team.add_or_invite_to_team(inviter, readers_team, None,
NEW_USER_DETAILS['email'])
other_owners_invite = model.team.add_or_invite_to_team(inviter, other_owners_team, None,
NEW_USER_DETAILS['email'])
# Create the user and ensure they have a verified email address.
details = {
'invite_code': owners_invite.invite_token
}
details.update(NEW_USER_DETAILS)
data = self.postJsonResponse(User, data=details, expected_code=200)
# Make sure the user is verified since the email address of the user matches
# that of the team invite.
self.assertFalse('awaiting_verification' in data)
# Make sure the user was not (yet) added to the teams.
self.login(ADMIN_ACCESS_USER)
json = self.getJsonResponse(TeamMemberList,
params=dict(orgname=ORGANIZATION,
teamname='owners'))
self.assertNotInTeam(json, NEW_USER_DETAILS['username'])
json = self.getJsonResponse(TeamMemberList,
params=dict(orgname=ORGANIZATION,
teamname='readers'))
self.assertNotInTeam(json, NEW_USER_DETAILS['username'])
json = self.getJsonResponse(TeamMemberList,
params=dict(orgname='library',
teamname='owners'))
self.assertNotInTeam(json, NEW_USER_DETAILS['username'])
# Accept the first invitation.
self.login(NEW_USER_DETAILS['username'])
self.putJsonResponse(TeamMemberInvite, params=dict(code=owners_invite.invite_token))
# Make sure both codes are now invalid.
self.putResponse(TeamMemberInvite, params=dict(code=owners_invite.invite_token),
expected_code=400)
self.putResponse(TeamMemberInvite, params=dict(code=readers_invite.invite_token),
expected_code=400)
# Make sure the user is now in the two invited teams under the organization, but not
# in the other org's team.
self.login(ADMIN_ACCESS_USER)
json = self.getJsonResponse(TeamMemberList,
params=dict(orgname=ORGANIZATION,
teamname='owners'))
self.assertInTeam(json, NEW_USER_DETAILS['username'])
json = self.getJsonResponse(TeamMemberList,
params=dict(orgname=ORGANIZATION,
teamname='readers'))
self.assertInTeam(json, NEW_USER_DETAILS['username'])
json = self.getJsonResponse(TeamMemberList,
params=dict(orgname='library',
teamname='owners'))
self.assertNotInTeam(json, NEW_USER_DETAILS['username'])
# Accept the second invitation.
self.login(NEW_USER_DETAILS['username'])
self.putJsonResponse(TeamMemberInvite, params=dict(code=other_owners_invite.invite_token))
# Make sure the user was added to the other organization.
self.login(ADMIN_ACCESS_USER)
json = self.getJsonResponse(TeamMemberList,
params=dict(orgname='library',
teamname='owners'))
self.assertInTeam(json, NEW_USER_DETAILS['username'])
# Make sure the invitation codes are now invalid.
self.putResponse(TeamMemberInvite, params=dict(code=other_owners_invite.invite_token),
expected_code=400)
class TestDeleteNamespace(ApiTestCase): class TestDeleteNamespace(ApiTestCase):
def test_deletenamespaces(self): def test_deletenamespaces(self):