diff --git a/src/sentry/auth/helper.py b/src/sentry/auth/helper.py index 6775cf2a7489..9f5a92846bfa 100644 --- a/src/sentry/auth/helper.py +++ b/src/sentry/auth/helper.py @@ -300,6 +300,7 @@ def _handle_membership( if invite_helper.invite_approved: rpc_om = invite_helper.accept_invite(user) assert rpc_om + self._set_linked_flag(rpc_om) return user, rpc_om # It's possible the user has an _invite request_ that hasn't been approved yet, diff --git a/tests/sentry/auth/test_helper.py b/tests/sentry/auth/test_helper.py index b38877c268d4..6c832096faa3 100644 --- a/tests/sentry/auth/test_helper.py +++ b/tests/sentry/auth/test_helper.py @@ -288,6 +288,8 @@ def test_associate_pending_invite(self) -> None: ) assert assigned_member.id == member.id + assert getattr(assigned_member.flags, "sso:linked") + assert not getattr(assigned_member.flags, "sso:invalid") def test_demo_user_can_be_added_new_user_when_demo_org(self) -> None: # Force demo user behavior, and mark org as demo org diff --git a/tests/sentry/web/frontend/test_auth_organization_login.py b/tests/sentry/web/frontend/test_auth_organization_login.py index 1ff4114a8639..8992ae475721 100644 --- a/tests/sentry/web/frontend/test_auth_organization_login.py +++ b/tests/sentry/web/frontend/test_auth_organization_login.py @@ -844,6 +844,55 @@ def test_flow_as_authenticated_user_with_invite_joining(self) -> None: assert not getattr(test_member.flags, "sso:invalid") assert not getattr(test_member.flags, "member-limit:restricted") + def test_flow_as_anonymous_with_pending_invite(self) -> None: + """New SSO user with a pending org invite should land once, with sso:linked.""" + auth_provider = AuthProvider.objects.create( + organization_id=self.organization.id, provider="dummy" + ) + invite = self.create_member( + email="invited@example.com", + organization=self.organization, + token="abcdef", + ) + + # Invite accept stores these before the user is bounced into SSO. + self.session["invite_token"] = invite.token + self.session["invite_member_id"] = invite.id + self.session["invite_organization_id"] = invite.organization_id + self.save_session() + + resp = self.client.post(self.path, {"init": True}) + + assert resp.status_code == 200 + assert PLACEHOLDER_TEMPLATE in resp.content.decode("utf-8") + + path = reverse("sentry-auth-sso") + + resp = self.client.post(path, {"email": "invited@example.com"}) + + self.assertTemplateUsed(resp, "sentry/auth-confirm-identity.html") + assert resp.status_code == 200 + + frontend_events = {"event_name": "Sign Up", "event_label": "dummy"} + marketing_query = urlencode({"frontend_events": json.dumps(frontend_events)}) + + resp = self.client.post(path, {"op": "newuser"}, follow=True) + assert resp.redirect_chain == [ + (reverse("sentry-login") + f"?{marketing_query}", 302), + ("/organizations/foo/issues/", 302), + ] + + auth_identity = AuthIdentity.objects.get(auth_provider=auth_provider) + user = auth_identity.user + assert user.email == "invited@example.com" + + with assume_test_silo_mode(SiloMode.CELL): + member = OrganizationMember.objects.get(organization=self.organization, user_id=user.id) + + assert member.id == invite.id + assert getattr(member.flags, "sso:linked") + assert not getattr(member.flags, "sso:invalid") + @override_settings(SENTRY_SINGLE_ORGANIZATION=True) @with_feature({"organizations:create": False}) def test_basic_auth_flow_as_not_invited_user(self) -> None: