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 return True
def lookup_team_invites_by_email(email):
return TeamMemberInvite.select().where(TeamMemberInvite.email == email)
def lookup_team_invites(user_obj): def lookup_team_invites(user_obj):
return TeamMemberInvite.select().where(TeamMemberInvite.user == 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. """ 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 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. # Find all matching invitations for the user under the organization.
code_found = False
for invite in find_organization_invites(found.team.organization, user_obj): 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:
code_found = True
add_user_to_team(user_obj, invite.team) add_user_to_team(user_obj, invite.team)
except UserAlreadyInTeam: except UserAlreadyInTeam:
# Ignore. # Ignore.
@ -350,6 +350,16 @@ def confirm_team_invite(code, user_obj):
# Delete the invite and return the team. # Delete the invite and return the team.
invite.delete_instance() 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 team = found.team
inviter = found.inviter inviter = found.inviter
return (team, inviter) return (team, inviter)

View file

@ -5,9 +5,12 @@
<div class="user-setup" ng-show="user.anonymous" redirect-url="redirectUrl" <div class="user-setup" ng-show="user.anonymous" redirect-url="redirectUrl"
invite-code="inviteCode"> invite-code="inviteCode">
</div> </div>
<div class="quay-spinner" ng-show="!user.anonymous && loading"></div> <div class="cor-loader-inline" ng-show="!user.anonymous && loading"></div>
<div class="alert alert-danger" ng-show="!user.anonymous && invalid">
{{ invalid }} <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> </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 test.helpers import assert_action_logged
from endpoints.api.team import (TeamMember, TeamMemberList, TeamMemberInvite, OrganizationTeam, from endpoints.api.team import (TeamMember, TeamMemberList, TeamMemberInvite, OrganizationTeam,
TeamPermissions) TeamPermissions, InviteTeamMember)
from endpoints.api.tag import RepositoryTagImages, RepositoryTag, RevertTag, ListRepositoryTags from endpoints.api.tag import RepositoryTagImages, RepositoryTag, RevertTag, ListRepositoryTags
from endpoints.api.search import EntitySearch, ConductSearch from endpoints.api.search import EntitySearch, ConductSearch
from endpoints.api.image import RepositoryImage, RepositoryImageList from endpoints.api.image import RepositoryImage, RepositoryImageList
@ -1556,7 +1556,102 @@ class TestAcceptTeamMemberInvite(ApiTestCase):
params=dict(code=invites[0].invite_token), params=dict(code=invites[0].invite_token),
expected_code=400) 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): class TestDeclineTeamMemberInvite(ApiTestCase):
def test_decline_wronguser(self): def test_decline_wronguser(self):