15
15
16
16
import logging
17
17
import xml .etree .ElementTree as ET
18
- from typing import AnyStr , Dict , Optional , Tuple
18
+ from typing import Dict , Optional , Tuple
19
19
20
20
from six .moves import urllib
21
21
@@ -48,26 +48,47 @@ def __init__(self, hs):
48
48
49
49
self ._http_client = hs .get_proxied_http_client ()
50
50
51
- def _build_service_param (self , client_redirect_url : AnyStr ) -> str :
51
+ def _build_service_param (self , args : Dict [str , str ]) -> str :
52
+ """
53
+ Generates a value to use as the "service" parameter when redirecting or
54
+ querying the CAS service.
55
+
56
+ Args:
57
+ args: Additional arguments to include in the final redirect URL.
58
+
59
+ Returns:
60
+ The URL to use as a "service" parameter.
61
+ """
52
62
return "%s%s?%s" % (
53
63
self ._cas_service_url ,
54
64
"/_matrix/client/r0/login/cas/ticket" ,
55
- urllib .parse .urlencode ({ "redirectUrl" : client_redirect_url } ),
65
+ urllib .parse .urlencode (args ),
56
66
)
57
67
58
- async def _handle_cas_response (
59
- self , request : SynapseRequest , cas_response_body : str , client_redirect_url : str
60
- ) -> None :
68
+ async def _validate_ticket (
69
+ self , ticket : str , service_args : Dict [ str , str ]
70
+ ) -> Tuple [ str , Optional [ str ]] :
61
71
"""
62
- Retrieves the user and display name from the CAS response and continues with the authentication .
72
+ Validate a CAS ticket with the server, parse the response, and return the user and display name .
63
73
64
74
Args:
65
- request: The original client request.
66
- cas_response_body: The response from the CAS server.
67
- client_redirect_url: The URl to redirect the client to when
68
- everything is done.
75
+ ticket: The CAS ticket from the client.
76
+ service_args: Additional arguments to include in the service URL.
77
+ Should be the same as those passed to `get_redirect_url`.
69
78
"""
70
- user , attributes = self ._parse_cas_response (cas_response_body )
79
+ uri = self ._cas_server_url + "/proxyValidate"
80
+ args = {
81
+ "ticket" : ticket ,
82
+ "service" : self ._build_service_param (service_args ),
83
+ }
84
+ try :
85
+ body = await self ._http_client .get_raw (uri , args )
86
+ except PartialDownloadError as pde :
87
+ # Twisted raises this error if the connection is closed,
88
+ # even if that's being used old-http style to signal end-of-data
89
+ body = pde .response
90
+
91
+ user , attributes = self ._parse_cas_response (body )
71
92
displayname = attributes .pop (self ._cas_displayname_attribute , None )
72
93
73
94
for required_attribute , required_value in self ._cas_required_attributes .items ():
@@ -82,7 +103,7 @@ async def _handle_cas_response(
82
103
if required_value != actual_value :
83
104
raise LoginError (401 , "Unauthorized" , errcode = Codes .UNAUTHORIZED )
84
105
85
- await self . _on_successful_auth ( user , request , client_redirect_url , displayname )
106
+ return user , displayname
86
107
87
108
def _parse_cas_response (
88
109
self , cas_response_body : str
@@ -127,78 +148,74 @@ def _parse_cas_response(
127
148
)
128
149
return user , attributes
129
150
130
- async def _on_successful_auth (
131
- self ,
132
- username : str ,
133
- request : SynapseRequest ,
134
- client_redirect_url : str ,
135
- user_display_name : Optional [str ] = None ,
136
- ) -> None :
137
- """Called once the user has successfully authenticated with the SSO.
138
-
139
- Registers the user if necessary, and then returns a redirect (with
140
- a login token) to the client.
151
+ def get_redirect_url (self , service_args : Dict [str , str ]) -> str :
152
+ """
153
+ Generates a URL for the CAS server where the client should be redirected.
141
154
142
155
Args:
143
- username: the remote user id. We'll map this onto
144
- something sane for a MXID localpath.
156
+ service_args: Additional arguments to include in the final redirect URL.
145
157
146
- request: the incoming request from the browser. We'll
147
- respond to it with a redirect.
158
+ Returns:
159
+ The URL to redirect the client to.
160
+ """
161
+ args = urllib .parse .urlencode (
162
+ {"service" : self ._build_service_param (service_args )}
163
+ )
148
164
149
- client_redirect_url: the redirect_url the client gave us when
150
- it first started the process.
165
+ return "%s/login?%s" % (self ._cas_server_url , args )
151
166
152
- user_display_name: if set, and we have to register a new user,
153
- we will set their displayname to this.
167
+ async def handle_ticket (
168
+ self ,
169
+ request : SynapseRequest ,
170
+ ticket : str ,
171
+ client_redirect_url : Optional [str ],
172
+ session : Optional [str ],
173
+ ) -> None :
154
174
"""
155
- localpart = map_username_to_mxid_localpart (username )
156
- user_id = UserID (localpart , self ._hostname ).to_string ()
157
- registered_user_id = await self ._auth_handler .check_user_exists (user_id )
158
- if not registered_user_id :
159
- registered_user_id = await self ._registration_handler .register_user (
160
- localpart = localpart , default_display_name = user_display_name
161
- )
175
+ Called once the user has successfully authenticated with the SSO.
176
+ Validates a CAS ticket sent by the client and completes the auth process.
162
177
163
- self . _auth_handler . complete_sso_login (
164
- registered_user_id , request , client_redirect_url
165
- )
178
+ If the user interactive authentication session is provided, marks the
179
+ UI Auth session as complete, then returns an HTML page notifying the
180
+ user they are done.
166
181
167
- def handle_redirect_request (self , client_redirect_url : bytes ) -> bytes :
168
- """
169
- Generates a URL to the CAS server where the client should be redirected.
182
+ Otherwise, this registers the user if necessary, and then returns a
183
+ redirect (with a login token) to the client.
170
184
171
185
Args:
172
- client_redirect_url: The final URL the client should go to after the
173
- user has negotiated SSO .
186
+ request: the incoming request from the browser. We'll
187
+ respond to it with a redirect or an HTML page .
174
188
175
- Returns:
176
- The URL to redirect to.
177
- """
178
- args = urllib .parse .urlencode (
179
- {"service" : self ._build_service_param (client_redirect_url )}
180
- )
189
+ ticket: The CAS ticket provided by the client.
181
190
182
- return ("%s/login?%s" % (self ._cas_server_url , args )).encode ("ascii" )
191
+ client_redirect_url: the redirectUrl parameter from the `/cas/ticket` HTTP request, if given.
192
+ This should be the same as the redirectUrl from the original `/login/sso/redirect` request.
183
193
184
- async def handle_ticket_request (
185
- self , request : SynapseRequest , client_redirect_url : str , ticket : str
186
- ) -> None :
194
+ session: The session parameter from the `/cas/ticket` HTTP request, if given.
195
+ This should be the UI Auth session id.
187
196
"""
188
- Validates a CAS ticket sent by the client for login/registration.
197
+ args = {}
198
+ if client_redirect_url :
199
+ args ["redirectUrl" ] = client_redirect_url
200
+ if session :
201
+ args ["session" ] = session
202
+ username , user_display_name = await self ._validate_ticket (ticket , args )
189
203
190
- On a successful request, writes a redirect to the request.
191
- """
192
- uri = self ._cas_server_url + "/proxyValidate"
193
- args = {
194
- "ticket" : ticket ,
195
- "service" : self ._build_service_param (client_redirect_url ),
196
- }
197
- try :
198
- body = await self ._http_client .get_raw (uri , args )
199
- except PartialDownloadError as pde :
200
- # Twisted raises this error if the connection is closed,
201
- # even if that's being used old-http style to signal end-of-data
202
- body = pde .response
204
+ localpart = map_username_to_mxid_localpart (username )
205
+ user_id = UserID (localpart , self ._hostname ).to_string ()
206
+ registered_user_id = await self ._auth_handler .check_user_exists (user_id )
203
207
204
- await self ._handle_cas_response (request , body , client_redirect_url )
208
+ if session :
209
+ self ._auth_handler .complete_sso_ui_auth (
210
+ registered_user_id , session , request ,
211
+ )
212
+
213
+ else :
214
+ if not registered_user_id :
215
+ registered_user_id = await self ._registration_handler .register_user (
216
+ localpart = localpart , default_display_name = user_display_name
217
+ )
218
+
219
+ self ._auth_handler .complete_sso_login (
220
+ registered_user_id , request , client_redirect_url
221
+ )
0 commit comments