Fix attempts to confirm team invite for mismatched email address

Currently, if a user tries to confirm an invite sent to them on an account with a mismatching email address, we simply redirect to the org (where they get a 403). This change ensures they get the proper error response message, and restyles the error page to be nicer.

Fixes #2227
Fixes https://www.pivotaltracker.com/story/show/136088507
This commit is contained in:
Joseph Schorr 2016-12-15 17:15:11 -05:00
parent 2730c26b2e
commit 785c74de52
3 changed files with 118 additions and 10 deletions

View file

@ -270,6 +270,10 @@ def delete_team_user_invite(team, user_obj):
return True
def lookup_team_invites_by_email(email):
return TeamMemberInvite.select().where(TeamMemberInvite.email == email)
def lookup_team_invites(user_obj):
return TeamMemberInvite.select().where(TeamMemberInvite.user == user_obj)
@ -332,16 +336,12 @@ def confirm_team_invite(code, user_obj):
same organization, they are automatically confirmed for all of them. """
found = find_matching_team_invite(code, user_obj)
# If the invite is for a specific user, we have to confirm that here.
if found.user is not None and found.user != user_obj:
message = """This invite is intended for user "%s".
Please login to that account and try again.""" % found.user.username
raise DataModelException(message)
# Find all matching invitations for the user under the organization.
code_found = False
for invite in find_organization_invites(found.team.organization, user_obj):
# Add the user to the team.
try:
code_found = True
add_user_to_team(user_obj, invite.team)
except UserAlreadyInTeam:
# Ignore.
@ -350,6 +350,16 @@ def confirm_team_invite(code, user_obj):
# Delete the invite and return the team.
invite.delete_instance()
if not code_found:
if found.user:
message = """This invite is intended for user "%s".
Please login to that account and try again.""" % found.user.username
raise DataModelException(message)
else:
message = """This invite is intended for email "%s".
Please login to that account and try again.""" % found.email
raise DataModelException(message)
team = found.team
inviter = found.inviter
return (team, inviter)

View file

@ -5,9 +5,12 @@
<div class="user-setup" ng-show="user.anonymous" redirect-url="redirectUrl"
invite-code="inviteCode">
</div>
<div class="quay-spinner" ng-show="!user.anonymous && loading"></div>
<div class="alert alert-danger" ng-show="!user.anonymous && invalid">
{{ invalid }}
<div class="cor-loader-inline" ng-show="!user.anonymous && loading"></div>
<div class="error-view-element" ng-show="!user.anonymous && invalid">
<h2><i class="fa fa-exclamation-triangle"></i> Confirmation Error</h2>
<h3>Confirmation code does not match this account</h3>
<div>{{ invalid }}</div>
</div>
</div>
</div>

View file

@ -30,7 +30,7 @@ from data.database import RepositoryActionCount, Repository as RepositoryTable
from test.helpers import assert_action_logged
from endpoints.api.team import (TeamMember, TeamMemberList, TeamMemberInvite, OrganizationTeam,
TeamPermissions)
TeamPermissions, InviteTeamMember)
from endpoints.api.tag import RepositoryTagImages, RepositoryTag, RevertTag, ListRepositoryTags
from endpoints.api.search import EntitySearch, ConductSearch
from endpoints.api.image import RepositoryImage, RepositoryImageList
@ -1556,7 +1556,102 @@ class TestAcceptTeamMemberInvite(ApiTestCase):
params=dict(code=invites[0].invite_token),
expected_code=400)
def test_accept_via_email(self):
self.login(ADMIN_ACCESS_USER)
# Create the invite.
member = model.user.get_user(NO_ACCESS_USER)
response = self.putJsonResponse(InviteTeamMember,
params=dict(orgname=ORGANIZATION, teamname='owners',
email=member.email))
self.assertEquals(True, response['invited'])
# Login as the user.
self.login(member.username)
# Accept the invite.
invites = list(model.team.lookup_team_invites_by_email(member.email))
self.assertEquals(1, len(invites))
self.putJsonResponse(TeamMemberInvite, params=dict(code=invites[0].invite_token))
# Verify the user is now on the team.
json = self.getJsonResponse(TeamMemberList,
params=dict(orgname=ORGANIZATION,
teamname='owners'))
self.assertInTeam(json, member.username)
# Verify the accept now fails.
self.putResponse(TeamMemberInvite,
params=dict(code=invites[0].invite_token),
expected_code=400)
def test_accept_invite_different_user(self):
self.login(ADMIN_ACCESS_USER)
# Create the invite.
response = self.putJsonResponse(TeamMember,
params=dict(orgname=ORGANIZATION, teamname='owners',
membername=NO_ACCESS_USER))
self.assertEquals(True, response['invited'])
# Login as a different user.
self.login(PUBLIC_USER)
# Try to accept the invite.
user = model.user.get_user(NO_ACCESS_USER)
invites = list(model.team.lookup_team_invites(user))
self.assertEquals(1, len(invites))
self.putResponse(TeamMemberInvite, params=dict(code=invites[0].invite_token),
expected_code=400)
# Ensure the invite is still valid.
user = model.user.get_user(NO_ACCESS_USER)
invites = list(model.team.lookup_team_invites(user))
self.assertEquals(1, len(invites))
# Ensure the user is *not* a member of the team.
self.login(ADMIN_ACCESS_USER)
json = self.getJsonResponse(TeamMemberList,
params=dict(orgname=ORGANIZATION,
teamname='owners'))
self.assertNotInTeam(json, PUBLIC_USER)
def test_accept_invite_different_email(self):
self.login(ADMIN_ACCESS_USER)
# Create the invite.
response = self.putJsonResponse(InviteTeamMember,
params=dict(orgname=ORGANIZATION, teamname='owners',
email='someemail@example.com'))
self.assertEquals(True, response['invited'])
# Login as a different user.
self.login(PUBLIC_USER)
# Try to accept the invite.
invites = list(model.team.lookup_team_invites_by_email('someemail@example.com'))
self.assertEquals(1, len(invites))
self.putResponse(TeamMemberInvite, params=dict(code=invites[0].invite_token),
expected_code=400)
# Ensure the invite is still valid.
invites = list(model.team.lookup_team_invites_by_email('someemail@example.com'))
self.assertEquals(1, len(invites))
# Ensure the user is *not* a member of the team.
self.login(ADMIN_ACCESS_USER)
json = self.getJsonResponse(TeamMemberList,
params=dict(orgname=ORGANIZATION,
teamname='owners'))
self.assertNotInTeam(json, PUBLIC_USER)
class TestDeclineTeamMemberInvite(ApiTestCase):
def test_decline_wronguser(self):