Skip to content
This repository was archived by the owner on Apr 26, 2024. It is now read-only.

Commit 0ad6d28

Browse files
authored
Rework UI Auth session validation for registration (#7455)
Be less strict about validation of UI authentication sessions during registration to match client expecations.
1 parent aa5aa6f commit 0ad6d28

File tree

6 files changed

+280
-102
lines changed

6 files changed

+280
-102
lines changed

changelog.d/7455.bugfix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Ensure that a user inteactive authentication session is tied to a single request.

synapse/handlers/auth.py

Lines changed: 40 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,7 @@ async def check_auth(
252252
clientdict: Dict[str, Any],
253253
clientip: str,
254254
description: str,
255+
validate_clientdict: bool = True,
255256
) -> Tuple[dict, dict, str]:
256257
"""
257258
Takes a dictionary sent by the client in the login / registration
@@ -277,6 +278,10 @@ async def check_auth(
277278
description: A human readable string to be displayed to the user that
278279
describes the operation happening on their account.
279280
281+
validate_clientdict: Whether to validate that the operation happening
282+
on the account has not changed. If this is false,
283+
the client dict is persisted instead of validated.
284+
280285
Returns:
281286
A tuple of (creds, params, session_id).
282287
@@ -317,30 +322,51 @@ async def check_auth(
317322
except StoreError:
318323
raise SynapseError(400, "Unknown session ID: %s" % (sid,))
319324

325+
# If the client provides parameters, update what is persisted,
326+
# otherwise use whatever was last provided.
327+
#
328+
# This was designed to allow the client to omit the parameters
329+
# and just supply the session in subsequent calls so it split
330+
# auth between devices by just sharing the session, (eg. so you
331+
# could continue registration from your phone having clicked the
332+
# email auth link on there). It's probably too open to abuse
333+
# because it lets unauthenticated clients store arbitrary objects
334+
# on a homeserver.
335+
#
336+
# Revisit: Assuming the REST APIs do sensible validation, the data
337+
# isn't arbitrary.
338+
#
339+
# Note that the registration endpoint explicitly removes the
340+
# "initial_device_display_name" parameter if it is provided
341+
# without a "password" parameter. See the changes to
342+
# synapse.rest.client.v2_alpha.register.RegisterRestServlet.on_POST
343+
# in commit 544722bad23fc31056b9240189c3cbbbf0ffd3f9.
320344
if not clientdict:
321-
# This was designed to allow the client to omit the parameters
322-
# and just supply the session in subsequent calls so it split
323-
# auth between devices by just sharing the session, (eg. so you
324-
# could continue registration from your phone having clicked the
325-
# email auth link on there). It's probably too open to abuse
326-
# because it lets unauthenticated clients store arbitrary objects
327-
# on a homeserver.
328-
# Revisit: Assuming the REST APIs do sensible validation, the data
329-
# isn't arbitrary.
330345
clientdict = session.clientdict
331346

332347
# Ensure that the queried operation does not vary between stages of
333348
# the UI authentication session. This is done by generating a stable
334-
# comparator based on the URI, method, and body (minus the auth dict)
335-
# and storing it during the initial query. Subsequent queries ensure
336-
# that this comparator has not changed.
337-
comparator = (uri, method, clientdict)
338-
if (session.uri, session.method, session.clientdict) != comparator:
349+
# comparator based on the URI, method, and client dict (minus the
350+
# auth dict) and storing it during the initial query. Subsequent
351+
# queries ensure that this comparator has not changed.
352+
if validate_clientdict:
353+
session_comparator = (session.uri, session.method, session.clientdict)
354+
comparator = (uri, method, clientdict)
355+
else:
356+
session_comparator = (session.uri, session.method) # type: ignore
357+
comparator = (uri, method) # type: ignore
358+
359+
if session_comparator != comparator:
339360
raise SynapseError(
340361
403,
341362
"Requested operation has changed during the UI authentication session.",
342363
)
343364

365+
# For backwards compatibility the registration endpoint persists
366+
# changes to the client dict instead of validating them.
367+
if not validate_clientdict:
368+
await self.store.set_ui_auth_clientdict(sid, clientdict)
369+
344370
if not authdict:
345371
raise InteractiveAuthIncompleteError(
346372
self._auth_dict_for_flows(flows, session.session_id)

synapse/rest/client/v2_alpha/register.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -516,6 +516,7 @@ async def on_POST(self, request):
516516
body,
517517
self.hs.get_ip_from_request(request),
518518
"register a new account",
519+
validate_clientdict=False,
519520
)
520521

521522
# Check that we're not trying to register a denied 3pid.

synapse/storage/data_stores/main/ui_auth.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,27 @@ async def get_completed_ui_auth_stages(
172172

173173
return results
174174

175+
async def set_ui_auth_clientdict(
176+
self, session_id: str, clientdict: JsonDict
177+
) -> None:
178+
"""
179+
Store an updated clientdict for a given session ID.
180+
181+
Args:
182+
session_id: The ID of this session as returned from check_auth
183+
clientdict:
184+
The dictionary from the client root level, not the 'auth' key.
185+
"""
186+
# The clientdict gets stored as JSON.
187+
clientdict_json = json.dumps(clientdict)
188+
189+
self.db.simple_update_one(
190+
table="ui_auth_sessions",
191+
keyvalues={"session_id": session_id},
192+
updatevalues={"clientdict": clientdict_json},
193+
desc="set_ui_auth_client_dict",
194+
)
195+
175196
async def set_ui_auth_session_data(self, session_id: str, key: str, value: Any):
176197
"""
177198
Store a key-value pair into the sessions data associated with this

0 commit comments

Comments
 (0)