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:
commit
b24b8629e5
3 changed files with 119 additions and 12 deletions
|
@ -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)
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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):
|
||||||
|
|
Reference in a new issue