Skip to content

Commit 966b563

Browse files
committed
check verified emails when accepting invite
1 parent 50774f5 commit 966b563

File tree

2 files changed

+78
-10
lines changed

2 files changed

+78
-10
lines changed

src/sentry/api/endpoints/accept_organization_invite.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,17 @@ def post(
235235
data={"details": "unable to accept organization invite"},
236236
)
237237
else:
238+
if self.request.user.is_authenticated:
239+
user_verified_emails = {e.email for e in self.request.user.get_verified_emails()}
240+
241+
if invite_context.member.email not in user_verified_emails:
242+
return Response(
243+
status=403,
244+
data={
245+
"details": "Your account must have a verified email matching the email the invite was sent to."
246+
},
247+
)
248+
238249
response = Response(status=status.HTTP_204_NO_CONTENT)
239250

240251
helper.accept_invite()

tests/sentry/api/endpoints/test_accept_organization_invite.py

Lines changed: 67 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from datetime import timedelta
2+
from uuid import uuid4
23

34
from django.conf import settings
45
from django.db import router
@@ -15,6 +16,7 @@
1516
from sentry.models.organizationmember import InviteStatus, OrganizationMember
1617
from sentry.models.organizationmembermapping import OrganizationMemberMapping
1718
from sentry.models.outbox import outbox_context
19+
from sentry.models.useremail import UserEmail
1820
from sentry.silo import SiloMode, unguarded_write
1921
from sentry.testutils.cases import TestCase
2022
from sentry.testutils.factories import Factories
@@ -117,7 +119,7 @@ def test_not_needs_authentication(self):
117119
self.login_as(self.user)
118120

119121
om = Factories.create_member(
120-
email="newuser@example.com", token="abc", organization=self.organization
122+
email=self.user.email, token="abc", organization=self.organization
121123
)
122124
for path in self._get_paths([om.id, om.token]):
123125
resp = self.client.get(path)
@@ -131,7 +133,7 @@ def test_user_needs_2fa(self):
131133
self.login_as(self.user)
132134

133135
om = Factories.create_member(
134-
email="newuser@example.com", token="abc", organization=self.organization
136+
email=self.user.email, token="abc", organization=self.organization
135137
)
136138

137139
for path in self._get_paths([om.id, om.token]):
@@ -165,7 +167,7 @@ def test_multi_region_organizationmember_id(self):
165167

166168
with assume_test_silo_mode_of(OrganizationMember), outbox_context(flush=False):
167169
om = OrganizationMember.objects.create(
168-
email="newuser@example.com", token="abc", organization_id=self.organization.id
170+
email=self.user.email, token="abc", organization_id=self.organization.id
169171
)
170172
with unguarded_write(using=router.db_for_write(OrganizationMemberMapping)):
171173
OrganizationMemberMapping.objects.create(
@@ -211,7 +213,7 @@ def test_user_has_2fa(self):
211213
self.login_as(self.user)
212214

213215
om = Factories.create_member(
214-
email="newuser@example.com", token="abc", organization=self.organization
216+
email=self.user.email, token="abc", organization=self.organization
215217
)
216218
for path in self._get_paths([om.id, om.token]):
217219
resp = self.client.get(path)
@@ -225,7 +227,7 @@ def test_user_can_use_sso(self):
225227
self.login_as(self.user)
226228

227229
om = Factories.create_member(
228-
email="newuser@example.com", token="abc", organization=self.organization
230+
email=self.user.email, token="abc", organization=self.organization
229231
)
230232
for path in self._get_paths([om.id, om.token]):
231233
resp = self.client.get(path)
@@ -307,6 +309,61 @@ def test_cannot_accept_unapproved_invite(self):
307309
assert om.is_pending
308310
assert om.token
309311

312+
def test_cannot_accept_without_matching_verified_email(self):
313+
newuser = self.create_user()
314+
315+
newuser_email = UserEmail.objects.get(user=newuser, email=newuser.email)
316+
newuser_email.is_verified = False
317+
newuser_email.save()
318+
319+
self.login_as(newuser)
320+
321+
assert not newuser_email.is_verified
322+
323+
om = Factories.create_member(
324+
email=newuser.email,
325+
role="member",
326+
token="abc",
327+
organization=self.organization,
328+
invite_status=InviteStatus.APPROVED.value,
329+
)
330+
331+
for path in self._get_paths([om.id, om.token]):
332+
resp = self.client.post(path)
333+
assert resp.status_code == 403, resp.content
334+
335+
with assume_test_silo_mode_of(OrganizationMember):
336+
om = OrganizationMember.objects.get(id=om.id)
337+
338+
assert om.is_pending
339+
assert om.token
340+
341+
def test_can_accept_with_matching_verified_email(self):
342+
urls = self._get_urls()
343+
344+
for url in urls:
345+
newuser = self.create_user() # implicitly verifies the email
346+
self.login_as(newuser)
347+
348+
om = Factories.create_member(
349+
email=newuser.email,
350+
role="member",
351+
token=uuid4().hex,
352+
organization=self.organization,
353+
invite_status=InviteStatus.APPROVED.value,
354+
)
355+
356+
path = self._get_path(url, [om.id, om.token])
357+
resp = self.client.post(path)
358+
359+
assert resp.status_code == 204, resp.content
360+
361+
with assume_test_silo_mode_of(OrganizationMember):
362+
om = OrganizationMember.objects.get(id=om.id)
363+
364+
assert not om.is_pending
365+
assert not om.token
366+
310367
def test_member_already_exists(self):
311368
urls = self._get_urls()
312369

@@ -317,7 +374,7 @@ def test_member_already_exists(self):
317374
om = Factories.create_member(
318375
email=user.email,
319376
role="member",
320-
token="abc",
377+
token=uuid4().hex,
321378
organization=self.organization,
322379
)
323380
path = self._get_path(url, [om.id, om.token])
@@ -355,7 +412,7 @@ def test_can_accept_when_user_has_2fa(self):
355412
self.login_as(user)
356413

357414
om = Factories.create_member(
358-
email="newuser" + str(i) + "@example.com",
415+
email=user.email,
359416
role="member",
360417
token="abc",
361418
organization=self.organization,
@@ -408,19 +465,19 @@ def test_2fa_cookie_deleted_after_accept(self):
408465
self.login_as(user)
409466

410467
om = Factories.create_member(
411-
email="newuser" + str(i) + "@example.com",
468+
email=user.email,
412469
role="member",
413470
token="abc",
414471
organization=self.organization,
415472
)
416473
path = self._get_path(url, [om.id, om.token])
417474
resp = self.client.get(path)
418-
assert resp.status_code == 200
475+
assert resp.status_code == 200, resp.content
419476
self._assert_pending_invite_details_in_session(om)
420477

421478
self._enroll_user_in_2fa(user)
422479
resp = self.client.post(path)
423-
assert resp.status_code == 204
480+
assert resp.status_code == 204, resp.content
424481

425482
self._assert_pending_invite_details_not_in_session(resp)
426483

0 commit comments

Comments
 (0)