22
22
from twisted .web .client import PartialDownloadError
23
23
24
24
from synapse .api .errors import HttpResponseException
25
+ from synapse .handlers .sso import MappingException , UserAttributes
25
26
from synapse .http .site import SynapseRequest
26
27
from synapse .types import UserID , map_username_to_mxid_localpart
27
28
@@ -62,6 +63,7 @@ class CasHandler:
62
63
def __init__ (self , hs : "HomeServer" ):
63
64
self .hs = hs
64
65
self ._hostname = hs .hostname
66
+ self ._store = hs .get_datastore ()
65
67
self ._auth_handler = hs .get_auth_handler ()
66
68
self ._registration_handler = hs .get_registration_handler ()
67
69
@@ -72,6 +74,9 @@ def __init__(self, hs: "HomeServer"):
72
74
73
75
self ._http_client = hs .get_proxied_http_client ()
74
76
77
+ # identifier for the external_ids table
78
+ self ._auth_provider_id = "cas"
79
+
75
80
self ._sso_handler = hs .get_sso_handler ()
76
81
77
82
def _build_service_param (self , args : Dict [str , str ]) -> str :
@@ -267,6 +272,14 @@ async def _handle_cas_response(
267
272
This should be the UI Auth session id.
268
273
"""
269
274
275
+ # first check if we're doing a UIA
276
+ if session :
277
+ return await self ._sso_handler .complete_sso_ui_auth_request (
278
+ self ._auth_provider_id , cas_response .username , session , request ,
279
+ )
280
+
281
+ # otherwise, we're handling a login request.
282
+
270
283
# Ensure that the attributes of the logged in user meet the required
271
284
# attributes.
272
285
for required_attribute , required_value in self ._cas_required_attributes .items ():
@@ -293,54 +306,79 @@ async def _handle_cas_response(
293
306
)
294
307
return
295
308
296
- # Pull out the user-agent and IP from the request.
297
- user_agent = request .get_user_agent ("" )
298
- ip_address = self .hs .get_ip_from_request (request )
299
-
300
- # Get the matrix ID from the CAS username.
301
- user_id = await self ._map_cas_user_to_matrix_user (
302
- cas_response , user_agent , ip_address
303
- )
309
+ # Call the mapper to register/login the user
304
310
305
- if session :
306
- await self ._auth_handler .complete_sso_ui_auth (
307
- user_id , session , request ,
308
- )
309
- else :
310
- # If this not a UI auth request than there must be a redirect URL.
311
- assert client_redirect_url
311
+ # If this not a UI auth request than there must be a redirect URL.
312
+ assert client_redirect_url is not None
312
313
313
- await self ._auth_handler .complete_sso_login (
314
- user_id , request , client_redirect_url
315
- )
314
+ try :
315
+ await self ._complete_cas_login (cas_response , request , client_redirect_url )
316
+ except MappingException as e :
317
+ logger .exception ("Could not map user" )
318
+ self ._sso_handler .render_error (request , "mapping_error" , str (e ))
316
319
317
- async def _map_cas_user_to_matrix_user (
318
- self , cas_response : CasResponse , user_agent : str , ip_address : str ,
319
- ) -> str :
320
+ async def _complete_cas_login (
321
+ self ,
322
+ cas_response : CasResponse ,
323
+ request : SynapseRequest ,
324
+ client_redirect_url : str ,
325
+ ) -> None :
320
326
"""
321
- Given a CAS username, retrieve the user ID for it and possibly register the user.
327
+ Given a CAS response, complete the login flow
328
+
329
+ Retrieves the remote user ID, registers the user if necessary, and serves
330
+ a redirect back to the client with a login-token.
322
331
323
332
Args:
324
333
cas_response: The parsed CAS response.
325
- user_agent : The user agent of the client making the request.
326
- ip_address : The IP address of the client making the request .
334
+ request : The request to respond to
335
+ client_redirect_url : The redirect URL passed in by the client .
327
336
328
- Returns:
329
- The user ID associated with this response.
337
+ Raises:
338
+ MappingException if there was a problem mapping the response to a user.
339
+ RedirectException: some mapping providers may raise this if they need
340
+ to redirect to an interstitial page.
330
341
"""
331
-
342
+ # Note that CAS does not support a mapping provider, so the logic is hard-coded.
332
343
localpart = map_username_to_mxid_localpart (cas_response .username )
333
- user_id = UserID (localpart , self ._hostname ).to_string ()
334
- registered_user_id = await self ._auth_handler .check_user_exists (user_id )
335
344
336
- displayname = cas_response .attributes .get (self ._cas_displayname_attribute , None )
345
+ async def cas_response_to_user_attributes (failures : int ) -> UserAttributes :
346
+ """
347
+ Map from CAS attributes to user attributes.
348
+ """
349
+ # Due to the grandfathering logic matching any previously registered
350
+ # mxids it isn't expected for there to be any failures.
351
+ if failures :
352
+ raise RuntimeError ("CAS is not expected to de-duplicate Matrix IDs" )
353
+
354
+ display_name = cas_response .attributes .get (
355
+ self ._cas_displayname_attribute , None
356
+ )
357
+
358
+ return UserAttributes (localpart = localpart , display_name = display_name )
337
359
338
- # If the user does not exist, register it.
339
- if not registered_user_id :
340
- registered_user_id = await self ._registration_handler .register_user (
341
- localpart = localpart ,
342
- default_display_name = displayname ,
343
- user_agent_ips = [(user_agent , ip_address )],
360
+ async def grandfather_existing_users () -> Optional [str ]:
361
+ # Since CAS did not always use the user_external_ids table, always
362
+ # to attempt to map to existing users.
363
+ user_id = UserID (localpart , self ._hostname ).to_string ()
364
+
365
+ logger .debug (
366
+ "Looking for existing account based on mapped %s" , user_id ,
344
367
)
345
368
346
- return registered_user_id
369
+ users = await self ._store .get_users_by_id_case_insensitive (user_id )
370
+ if users :
371
+ registered_user_id = list (users .keys ())[0 ]
372
+ logger .info ("Grandfathering mapping to %s" , registered_user_id )
373
+ return registered_user_id
374
+
375
+ return None
376
+
377
+ await self ._sso_handler .complete_sso_login_request (
378
+ self ._auth_provider_id ,
379
+ cas_response .username ,
380
+ request ,
381
+ client_redirect_url ,
382
+ cas_response_to_user_attributes ,
383
+ grandfather_existing_users ,
384
+ )
0 commit comments