fix(crm): keep the visitor session and site host on appointment verification

(cherry picked from commit 48f93fdd03)
This commit is contained in:
Nabin Hait
2026-10-22 09:33:48 +00:00
committed by Mergify
parent e007159c21
commit 397c462eee
3 changed files with 31 additions and 4 deletions
@@ -197,7 +197,7 @@ class Appointment(Document):
frappe.sendmail(
recipients=[self.customer_email],
template=template,
args={"full_name": self.customer_name, "site_url": frappe.utils.get_url(), **args},
args={"full_name": self.customer_name, "site_url": get_url(allow_header_override=False), **args},
subject=subject,
**kwargs,
)
@@ -343,7 +343,7 @@ class Appointment(Document):
def _get_verify_url(self):
key = self.generate_verification_key()
return get_url("/book_appointment/verify?" + urlencode({"key": key}))
return get_url("/book_appointment/verify?" + urlencode({"key": key}), allow_header_override=False)
def generate_verification_key(self):
# store only the hash; the raw key lives solely in the emailed link
@@ -213,6 +213,30 @@ class TestAppointment(FrappeTestCase):
stored = frappe.db.get_value("Appointment", appointment.name, "verification_token")
self.assertEqual(stored, sha256_hash(params["key"]))
def test_verification_keeps_the_visitor_session(self):
appointment = self._create_portal_appointment("portal_visitor_session@example.com")
_parsed, params = parse_verify_url(appointment._get_verify_url())
self.addCleanup(frappe.set_user, frappe.session.user)
frappe.set_user("test@example.com")
frappe.local.session.data = frappe._dict(csrf_token="token")
session = frappe.local.session
with patch.object(Appointment, "send_appointment_confirmed_email"):
frappe.local.form_dict = frappe._dict(params)
context = verify_index.get_context(frappe._dict())
frappe.local.flags.commit = False
self.assertTrue(context.success)
self.assertIs(frappe.local.session, session)
self.assertEqual((session.user, session.data.csrf_token), ("test@example.com", "token"))
def test_verify_url_ignores_the_request_host(self):
appointment = self._create_portal_appointment("portal_visitor_host@example.com")
self.addCleanup(setattr, frappe.local, "request", getattr(frappe.local, "request", None))
set_request(method="GET", path="/book_appointment", headers={"Host": "attacker.example"})
self.assertNotIn("attacker.example", appointment._get_verify_url())
def test_email_verification_within_expiry_window(self):
# Link used within the validity window - verification succeeds and the
# appointment gets linked, assigned and added to the calendar
+5 -2
View File
@@ -48,11 +48,14 @@ def get_context(context):
def verify_appointment(appointment):
# the signed link is the authorization; materializing the appointment
# (agent assignment) needs system privileges the Guest visitor lacks
visitor = frappe.session.user
# switch on a detached session so the visitor's own session data is left untouched
session = frappe.local.session
frappe.local.session = frappe._dict()
try:
frappe.set_user("Administrator")
appointment.email_verified = True
appointment.status = "Open"
appointment.save(ignore_permissions=True)
finally:
frappe.set_user(visitor)
frappe.set_user(session.user)
frappe.local.session = session