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:
parent
2730c26b2e
commit
785c74de52
3 changed files with 118 additions and 10 deletions
|
@ -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)
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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):
|
||||||
|
|
Reference in a new issue