1
|
|
2
|
|
3
|
|
4
|
|
5
|
|
6
|
|
7
|
|
8
|
|
9
|
|
10
|
|
11
|
|
12
|
|
13
|
|
14
|
|
15
|
|
16
|
|
17
|
|
18
|
|
19
|
|
20
|
import re
|
21
|
import os
|
22
|
import urlparse
|
23
|
from urllib2 import urlopen
|
24
|
import urllib
|
25
|
import SOAPpy
|
26
|
import traceback
|
27
|
from cgi import escape, parse_qsl
|
28
|
try:
|
29
|
import json
|
30
|
json_dump = json.dumps
|
31
|
except:
|
32
|
import simplejson as json
|
33
|
if not hasattr(json, 'dumps'):
|
34
|
|
35
|
json_dump = json.write
|
36
|
else:
|
37
|
json_dump = json.dumps
|
38
|
|
39
|
from twisted.web2 import static, http, responsecode
|
40
|
from twisted.web2.resource import PostableResource as Resource
|
41
|
from twisted.web2.http_headers import Cookie as TwCookie, MimeType
|
42
|
from twisted.web2.xmlrpc import XMLRPC
|
43
|
|
44
|
|
45
|
from M2Crypto import SSL
|
46
|
|
47
|
|
48
|
from eolesso.util import *
|
49
|
from eolesso.libsecure import ClientContextFactory, getPageM2
|
50
|
from eolesso.errors import (Redirect, CasError, MissingParameters,
|
51
|
InvalidTicket, InternalError)
|
52
|
|
53
|
import config
|
54
|
from page import gen_page, trace, log
|
55
|
from authserver import SSOSessionManager
|
56
|
|
57
|
|
58
|
import oidc_resources
|
59
|
|
60
|
import saml_resources, saml_message, saml_utils
|
61
|
|
62
|
|
63
|
|
64
|
AUTH_FORM = open(os.path.join('interface', 'authform.tmpl')).read()
|
65
|
|
66
|
if config.USE_SECURID or config.SECURID_PROVIDER:
|
67
|
from securid_utils import SecuridCheck, SECURID_AUTH_FORM
|
68
|
else:
|
69
|
SECURID_AUTH_FORM = ""
|
70
|
|
71
|
|
72
|
|
73
|
client_ctx = ClientContextFactory(certfile = config.CERTFILE, keyfile = config.KEYFILE,
|
74
|
mode = SSL.m2.SSL_VERIFY_PEER|SSL.m2.SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
|
75
|
ca_location = config.CA_LOCATION)
|
76
|
|
77
|
class XMLRPCManager(XMLRPC):
|
78
|
|
79
|
def __init__(self,session_manager):
|
80
|
self.manager = session_manager
|
81
|
XMLRPC.__init__(self)
|
82
|
|
83
|
def xmlrpc_reload_configuration(self, post_data):
|
84
|
log.msg('* {0}'.format(_('reloading server configuration')))
|
85
|
return self.manager.reload_conf()
|
86
|
|
87
|
def xmlrpc_authenticate(self, post_data, username, password, search_branch):
|
88
|
log.msg('* xmlrpc authenticate')
|
89
|
return self.manager.authenticate(username, password, search_branch)
|
90
|
|
91
|
def xmlrpc_validate_session(self, post_data, session_id):
|
92
|
log.msg('* xmlrpc validate_session')
|
93
|
return self.manager.validate_session(session_id)
|
94
|
|
95
|
def xmlrpc_verify_session_id(self, post_data, session_id):
|
96
|
log.msg('* xmlrpc verify_session_id')
|
97
|
return self.manager.verify_session_id(session_id)
|
98
|
|
99
|
def xmlrpc_logout(self, post_data, session_id):
|
100
|
log.msg('* xmlrpc logout')
|
101
|
return self.manager.logout(session_id)
|
102
|
|
103
|
def xmlrpc_verify_app_ticket(self, post_data, app_ticket, appurl):
|
104
|
log.msg('* xmlrpc verify_app_ticket')
|
105
|
return self.manager.verify_app_ticket(app_ticket, appurl)
|
106
|
|
107
|
def xmlrpc_get_user_details(self, post_data, app_ticket, appurl, sections=False, renew=False):
|
108
|
log.msg('* xmlrpc get_user_details')
|
109
|
return self.manager.get_user_details(app_ticket, appurl, sections, renew)
|
110
|
|
111
|
def xmlrpc_get_user_info(self, post_data, session_id, details=False):
|
112
|
log.msg('* xmlrpc get_user_infos')
|
113
|
return self.manager.get_user_info(session_id, details)
|
114
|
|
115
|
def xmlrpc_get_auth_class(self, post_data, app_ticket):
|
116
|
log.msg('* xmlrpc get_auth_class')
|
117
|
return self.manager.get_auth_class(app_ticket)
|
118
|
|
119
|
def xmlrpc_get_auth_instant(self, post_data, app_ticket):
|
120
|
log.msg('* xmlrpc get_auth_instant')
|
121
|
return self.manager.get_auth_instant(app_ticket)
|
122
|
|
123
|
def xmlrpc_check_securid_login(self, post_data, securid_login):
|
124
|
|
125
|
return self.manager.securid_user_store.check_securid_login(securid_login)
|
126
|
|
127
|
|
128
|
class CasResource(Resource):
|
129
|
content_type = 'text/html; charset=utf-8'
|
130
|
required_parameters = ()
|
131
|
headers = http_headers
|
132
|
|
133
|
def set_headers(self, resp, user_headers={}):
|
134
|
"""attache à la réponse HTTP les headers par défaut
|
135
|
+ les éventuels headers passés en paramètre
|
136
|
"""
|
137
|
if self.content_type:
|
138
|
resp.headers.setRawHeaders('Content-type', [self.content_type])
|
139
|
for headers in (self.headers, user_headers):
|
140
|
for h_name, h_values in headers.items():
|
141
|
resp.headers.setRawHeaders(h_name, h_values)
|
142
|
return resp
|
143
|
|
144
|
def render(self, request):
|
145
|
headers = {'Content-type': MimeType.fromString(self.content_type)}
|
146
|
errheaders = {'Content-type': MimeType.fromString('application/xml')}
|
147
|
try:
|
148
|
self.check_required_parameters(request)
|
149
|
return self._render(request)
|
150
|
except Redirect, exc:
|
151
|
traceback.print_exc()
|
152
|
location = exc.location
|
153
|
headers['location'] = location
|
154
|
log.msg(_("Redirecting to "), location)
|
155
|
|
156
|
return http.Response(code=303, headers=headers)
|
157
|
except CasError, exc:
|
158
|
traceback.print_exc()
|
159
|
return http.Response(stream=exc.as_xml(), headers=errheaders)
|
160
|
except Exception, exc:
|
161
|
traceback.print_exc()
|
162
|
log.msg(_("An error occured while rendering content for {0} : ").format(str(request.path)) , str(exc))
|
163
|
error = InternalError("Erreur interne rencontrée, voir les logs du serveur pour plus de détails")
|
164
|
return http.Response(stream=error.as_xml(), headers=errheaders)
|
165
|
|
166
|
def check_required_parameters(self, request):
|
167
|
"""raises a `MissingParameters` exception if all required parameters
|
168
|
are not found in `request.args`.
|
169
|
"""
|
170
|
missing = [param for param in self.required_parameters
|
171
|
if param not in request.args]
|
172
|
if missing:
|
173
|
raise MissingParameters(missing)
|
174
|
|
175
|
class CASCompliantResponse(CasResource):
|
176
|
"""implémentation du protocole CAS pour la validation de ticket
|
177
|
"""
|
178
|
|
179
|
content_type = 'application/xml'
|
180
|
|
181
|
authorized_codes=[responsecode.OK,
|
182
|
responsecode.MULTIPLE_CHOICE,
|
183
|
responsecode.MOVED_PERMANENTLY,
|
184
|
responsecode.FOUND,
|
185
|
responsecode.SEE_OTHER,
|
186
|
responsecode.NOT_MODIFIED,
|
187
|
responsecode.USE_PROXY,
|
188
|
responsecode.TEMPORARY_REDIRECT,
|
189
|
]
|
190
|
|
191
|
required_parameters = ('service','ticket')
|
192
|
|
193
|
def __init__(self, manager, serve_proxy = False):
|
194
|
self.manager = manager
|
195
|
self.serve_proxy = serve_proxy
|
196
|
self.serve_saml=False
|
197
|
super(CASCompliantResponse, self).__init__()
|
198
|
|
199
|
def _gen_user(self, infos, data_filter):
|
200
|
"""génération de la partie données utilisateur de la réponse CAS
|
201
|
"""
|
202
|
data = ""
|
203
|
if data_filter != "":
|
204
|
if self.serve_saml:
|
205
|
|
206
|
for attrs in data_filter.values():
|
207
|
for name, data_key in attrs.items():
|
208
|
if infos[data_key]:
|
209
|
data += """ <Attribute AttributeName="%s" AttributeNamespace="http://www.ja-sig.org/products/cas/">\n""" % name
|
210
|
for val in infos[data_key]:
|
211
|
if type(val) not in (str, unicode):
|
212
|
val = str(val)
|
213
|
data += """ <AttributeValue>%s</AttributeValue>\n""" % escape(val)
|
214
|
data += """ </Attribute>\n"""
|
215
|
else:
|
216
|
|
217
|
for section, attrs in data_filter.items():
|
218
|
if section == "default":
|
219
|
|
220
|
data += "%s\n" % (self._gen_section(attrs, infos))
|
221
|
else:
|
222
|
data += "<cas:%s>\n%s </cas:%s>\n" % (section, self._gen_section(attrs, infos), section)
|
223
|
data = data.rstrip()
|
224
|
else:
|
225
|
if self.serve_saml:
|
226
|
|
227
|
data = ""
|
228
|
else:
|
229
|
data = " <cas:user>%s</cas:user>" % infos['uid'][0]
|
230
|
return data
|
231
|
|
232
|
def _gen_section(self,attrs,infos):
|
233
|
"""génère une section dans la réponse
|
234
|
"""
|
235
|
section = ""
|
236
|
for name, datakey in attrs.items():
|
237
|
for val in infos[datakey]:
|
238
|
if type(val) not in (str, unicode):
|
239
|
val = str(val)
|
240
|
section += " <cas:%s>%s</cas:%s>\n" % (name, escape(val), name)
|
241
|
return section
|
242
|
|
243
|
@trace
|
244
|
def check_proxy_callback(self, cb_url, orig_parameters):
|
245
|
"""vérifie la validité d'une url de callback founie par un service proxy:
|
246
|
- l'url doit être en https
|
247
|
- le certificat SSL doit être valide
|
248
|
- le nom du certificat doit correspondre au service
|
249
|
"""
|
250
|
proxy_ticket = orig_parameters[3]
|
251
|
if self.manager._DBAppSessionFromTicket(proxy_ticket):
|
252
|
|
253
|
ticket = self.manager.app_sessions[proxy_ticket]
|
254
|
log_prefix = "%s -- " % ticket.session_id
|
255
|
else:
|
256
|
log_prefix = ""
|
257
|
|
258
|
log.msg("%s%s" % (log_prefix, _('PGT asked for service {0}').format(orig_parameters[2])))
|
259
|
url = urlparse.urlparse(cb_url)
|
260
|
if url.scheme == 'https':
|
261
|
|
262
|
d = getPageM2(cb_url, checker = client_ctx._cert_verify, contextFactory = client_ctx, timeout = 10)
|
263
|
return d.addCallbacks(self.proxy_cert_ok, self.proxy_errb, callbackArgs = [client_ctx, orig_parameters, url], errbackArgs = [orig_parameters,url])
|
264
|
|
265
|
log.msg(_("!! Invalid proxy url (https is mandatory) !!"))
|
266
|
return self.validate_st(orig_parameters)
|
267
|
|
268
|
@trace
|
269
|
def proxy_cert_ok(self, stream, ctx, orig_parameters, url):
|
270
|
"""gestion de la demande de PGT si proxy valide
|
271
|
- envoi d'une requête GET sur l'url de callback du proxy avec les paramètres pgtId et pgtIou
|
272
|
- vérification du retour : OK si code 200 (ou 3xx si redirection)
|
273
|
- la validation du ticket d'origine (ST ou PT) continue, avec ajout de pgtIou si vérification OK
|
274
|
"""
|
275
|
service = orig_parameters[2]
|
276
|
ticket = orig_parameters[3]
|
277
|
service_host = url.hostname
|
278
|
|
279
|
service_host = config.ALTERNATE_IPS.get(service_host, service_host)
|
280
|
if not is_dn_in_hostname(ctx.peer_cert_data['subject'], service_host):
|
281
|
log.msg(_("!! Certificate error : certificate name does not match service !!"))
|
282
|
|
283
|
else:
|
284
|
pgt_iou = None
|
285
|
|
286
|
app_ticket = self.manager.get_proxy_granting_ticket(ticket, service, url)
|
287
|
if app_ticket is not None:
|
288
|
|
289
|
|
290
|
callb_url, callb_args = urllib.splitquery(url.geturl())
|
291
|
if callb_args:
|
292
|
req_args = parse_qsl(callb_args)
|
293
|
else:
|
294
|
req_args = []
|
295
|
req_args.append(('pgtId', app_ticket.pgt))
|
296
|
req_args.append(('pgtIou', app_ticket.pgtiou))
|
297
|
|
298
|
self.manager.set_urllib_proxy(app_ticket)
|
299
|
|
300
|
u = urlopen("%s?%s" % (callb_url, urllib.urlencode(req_args)))
|
301
|
data = u.read()
|
302
|
self.manager.set_urllib_proxy()
|
303
|
if u.code in self.authorized_codes:
|
304
|
log.msg(_("** Proxy response code OK : {0}").format(u.code))
|
305
|
|
306
|
return self.validate_st(orig_parameters, app_ticket)
|
307
|
|
308
|
log.msg(_("** Proxy response code ERROR : {0}").format(u.code))
|
309
|
else:
|
310
|
log.msg(_("!! Unable to obtain PGT for service {0} !!").format(service))
|
311
|
return self.validate_st(orig_parameters)
|
312
|
|
313
|
@trace
|
314
|
def proxy_errb(self, err, orig_parameters, url):
|
315
|
log.msg(_("!! Proxy rejected : {0} !!").format(url.geturl()))
|
316
|
log.msg("\n!! --> %s !!\n" % err.getErrorMessage())
|
317
|
return self.validate_st(orig_parameters)
|
318
|
|
319
|
def _render(self, request):
|
320
|
error = {'code':'INTERNAL_ERROR','detail':''}
|
321
|
data = error, None
|
322
|
ok = False
|
323
|
|
324
|
from_url = request.args['service'][0]
|
325
|
ticket = request.args['ticket'][0]
|
326
|
if not self.serve_proxy and ticket.startswith('PT'):
|
327
|
raise InvalidTicket(ticket, _("Received PT ticket instead of ST"))
|
328
|
|
329
|
if request.args.get('renew', None) is not None:
|
330
|
renew = True
|
331
|
else:
|
332
|
renew = False
|
333
|
|
334
|
if request.args.get('Request', None):
|
335
|
saml_request = request.args['Request'][0]
|
336
|
|
337
|
pgt_url = None
|
338
|
if config.CAS_VERSION == 2:
|
339
|
try:
|
340
|
pgt_url = request.args['pgtUrl'][0]
|
341
|
return self.check_proxy_callback(pgt_url, [ok, data, from_url, ticket, renew])
|
342
|
except KeyError:
|
343
|
|
344
|
pass
|
345
|
return self.validate_st([ok, data, from_url, ticket, renew])
|
346
|
|
347
|
@trace
|
348
|
def _traversed_proxies(self, ticket):
|
349
|
"""return the <cas:proxies> node listing traversed proxies
|
350
|
|
351
|
NOTE: /serviceValidate and /proxyValidate could share the same
|
352
|
implementation since ticket.proxypath() should return an empty
|
353
|
list in case of ST apptickets, but just in case, we return
|
354
|
an hard-coded empty node for ST tickets
|
355
|
"""
|
356
|
proxies = '\n'.join(' <cas:proxy>%s</cas:proxy>' % url
|
357
|
for url in ticket.proxypath())
|
358
|
return u'\n <cas:proxies>\n%s\n </cas:proxies>' % proxies
|
359
|
|
360
|
@trace
|
361
|
def validate_st(self, validate_parameters, pgt=None):
|
362
|
ok, data, from_url, ticket, renew = validate_parameters
|
363
|
if ticket is not None:
|
364
|
if pgt is not None:
|
365
|
pgt_data = "\n <cas:proxyGrantingTicket>%s</cas:proxyGrantingTicket>" % pgt.pgtiou
|
366
|
|
367
|
if pgt.proxypath() != []:
|
368
|
pgt_data += self._traversed_proxies(pgt)
|
369
|
else:
|
370
|
|
371
|
|
372
|
self.manager.invalidate_proxy(ticket)
|
373
|
pgt_data = ""
|
374
|
|
375
|
ok, data = self.manager.get_user_details(ticket, from_url, True, renew)
|
376
|
infos, filter_data = data
|
377
|
if ok:
|
378
|
|
379
|
|
380
|
user_infos = self._gen_user(infos, filter_data)
|
381
|
|
382
|
if self.serve_saml:
|
383
|
|
384
|
auth_instant = self.manager.get_auth_instant(ticket)
|
385
|
|
386
|
response = saml_message.gen_saml11_response(ticket, auth_instant, from_url, config.IDP_IDENTITY, infos['uid'][0], user_infos)
|
387
|
response = response.encode(config.encoding)
|
388
|
elif config.CAS_VERSION == 2:
|
389
|
|
390
|
resp_body = user_infos + pgt_data
|
391
|
response = cas_response('authenticationSuccess', resp_body)
|
392
|
else:
|
393
|
|
394
|
response = 'yes\n%s\r\n\r\n' % infos['uid'][0]
|
395
|
else:
|
396
|
error, filter_data = data
|
397
|
|
398
|
|
399
|
if self.serve_saml and error['code'] != 'INTERNAL ERROR':
|
400
|
|
401
|
response = saml_message.gen_saml11_error(error, from_url)
|
402
|
response = response.encode(config.encoding)
|
403
|
elif config.CAS_VERSION == 2 or self.serve_saml:
|
404
|
response = cas_response('authenticationFailure', escape(error['detail']), code=error['code'])
|
405
|
else:
|
406
|
response = 'no\r\n\r\n'
|
407
|
if config.CAS_VERSION == 1 and not self.serve_saml:
|
408
|
self.content_type = 'text/html; charset=utf-8'
|
409
|
cas_resp = http.Response(stream=response)
|
410
|
if self.serve_saml:
|
411
|
|
412
|
self.set_headers(cas_resp, {'SOAPAction':['http://www.oasis-open.org/committees/security']})
|
413
|
else:
|
414
|
self.set_headers(cas_resp)
|
415
|
return cas_resp
|
416
|
|
417
|
class SAMLCompliantResponse(CASCompliantResponse):
|
418
|
|
419
|
content_type = 'application/xml'
|
420
|
required_parameters = ('TARGET',)
|
421
|
|
422
|
def __init__(self, manager, serve_proxy = False):
|
423
|
super(SAMLCompliantResponse, self).__init__(manager, serve_proxy)
|
424
|
self.serve_saml = True
|
425
|
|
426
|
self.renderHTTP = super(SAMLCompliantResponse, self).render
|
427
|
|
428
|
def _render(self, request):
|
429
|
target = request.args['TARGET'][0]
|
430
|
|
431
|
return request.stream.read().addCallbacks(self.process_saml11_request, log.msg, callbackArgs = [target])
|
432
|
|
433
|
def process_saml11_request(self, xml_stream, target):
|
434
|
"""procceses a saml1 request and extracts Artifact value
|
435
|
"""
|
436
|
error = {'code':'INTERNAL_ERROR','detail':''}
|
437
|
|
438
|
try:
|
439
|
artifact = saml_utils.get_saml11_artifact(xml_stream)
|
440
|
except SOAPpy.Error:
|
441
|
|
442
|
error = {'code':'INTERNAL ERROR', 'detail':_('Invalid SOAP Headers')}
|
443
|
artifact = None
|
444
|
except:
|
445
|
error = {'code':'INTERNAL ERROR', 'detail':_('No ticket in SAML Request')}
|
446
|
artifact = None
|
447
|
data = error, None
|
448
|
return self.validate_st([False, data, target, artifact, False])
|
449
|
|
450
|
class ProxyResource(CasResource):
|
451
|
"""
|
452
|
page de génération d'un ticket de type PT
|
453
|
"""
|
454
|
addSlash = False
|
455
|
content_type = 'application/xml'
|
456
|
|
457
|
def __init__(self, manager):
|
458
|
self.manager = manager
|
459
|
super(ProxyResource, self).__init__()
|
460
|
|
461
|
def _render(self, request):
|
462
|
try:
|
463
|
pgt = request.args['pgt'][0]
|
464
|
target_service = request.args['targetService'][0]
|
465
|
except:
|
466
|
error = {'code':'INVALID_REQUEST','detail':_("Missing parameter")}
|
467
|
|
468
|
if pgt in self.manager.proxy_granting_sessions:
|
469
|
|
470
|
ticket = self.manager.get_proxy_ticket(pgt, target_service)
|
471
|
response_body = "<cas:proxyTicket>%s</cas:proxyTicket>" % ticket
|
472
|
response = cas_response('proxySuccess', response_body)
|
473
|
return self.set_headers(http.Response(stream=response))
|
474
|
else:
|
475
|
error = {'code':'BAD_PGT', 'detail':_("PGT not recognized by server : {0}").format(pgt)}
|
476
|
response = cas_response('proxyFailure', escape(error['detail']), code=error['code'])
|
477
|
return self.set_headers(http.Response(stream=response))
|
478
|
|
479
|
|
480
|
class LoggedInForm(CasResource):
|
481
|
"""
|
482
|
page de confirmation d'ouverture de session (si pas d'url de redirection fournie)
|
483
|
"""
|
484
|
addSlash = False
|
485
|
|
486
|
def __init__(self, manager):
|
487
|
self.manager = manager
|
488
|
super(LoggedInForm, self).__init__()
|
489
|
|
490
|
def _render(self,request):
|
491
|
try:
|
492
|
css = escape(request.args['css'][0])
|
493
|
except KeyError:
|
494
|
css = config.DEFAULT_CSS
|
495
|
sso_cookie = getCookie(request, 'EoleSSOServer')
|
496
|
if sso_cookie:
|
497
|
user_session = sso_cookie.value
|
498
|
sessions = ""
|
499
|
local_sessions = []
|
500
|
for app_t in self.manager.user_app_tickets[user_session]:
|
501
|
if hasattr(app_t, 'provider_data'):
|
502
|
sessions += """<li>Identité fournie par : %s</li>""" % \
|
503
|
self.manager.external_providers[app_t.provider_data['provider']][2]
|
504
|
elif hasattr(app_t,'saml_ident'):
|
505
|
sessions += """<li>session distante sur %s</li>""" % app_t.saml_ident
|
506
|
else:
|
507
|
if app_t.service_url not in local_sessions:
|
508
|
local_sessions.append(urllib.quote(app_t.service_url, safe=uri_reserved_chars))
|
509
|
if local_sessions:
|
510
|
sessions += "<li>%s</li>" % "</li><li>".join(local_sessions)
|
511
|
if sessions:
|
512
|
content = """<h1>Liste de vos sessions ouvertes :</h1><br/>
|
513
|
<ul>%s</ul>""" % sessions
|
514
|
else:
|
515
|
content = """<h1>Aucune session d'application trouvée</h1>"""
|
516
|
content += """<form action="/logout" method="post">
|
517
|
<p class=formvalidation><input class="btn" type="Submit" value="%s">
|
518
|
</p></form>""" % _("Disconnect")
|
519
|
else:
|
520
|
content = _('Invalid session')
|
521
|
response = http.Response(stream=gen_page(_("Valid SSO session"), content, css))
|
522
|
return self.set_headers(response)
|
523
|
|
524
|
class ErrorPage(CasResource):
|
525
|
"""
|
526
|
page d'erreur http
|
527
|
"""
|
528
|
addSlash = False
|
529
|
|
530
|
def __init__(self, code, description):
|
531
|
self.code = code
|
532
|
self.description = description
|
533
|
title = (_("EoleSSO : Error"))
|
534
|
super(ErrorPage, self).__init__()
|
535
|
|
536
|
def _render(self,request):
|
537
|
return http.StatusResponse(code=self.code, description=self.description)
|
538
|
|
539
|
class UnauthorizedService(ErrorPage):
|
540
|
"""
|
541
|
Page affichée en cas de refus d'envoi d'une réponse CAS à un service
|
542
|
renvoie une erreur http UNAUTHORIZED par défaut.
|
543
|
Il est possible d'afficher une page personnalisée à la place en créant un template
|
544
|
/usr/share/sso/interface/invalid_service.tmpl (fichier d'exemple dans le répertoire)
|
545
|
"""
|
546
|
def __init__(self):
|
547
|
tmpl_file = os.path.join(config.SSO_PATH, 'interface', 'unauthorized_service.tmpl')
|
548
|
if os.path.isfile(tmpl_file):
|
549
|
self.title = _("EoleSSO : Error")
|
550
|
self.tmpl_data = file(tmpl_file).read()
|
551
|
else:
|
552
|
self.tmpl_data = None
|
553
|
description = _('unauthorized service url : {0}')
|
554
|
ErrorPage.__init__(self, responsecode.UNAUTHORIZED, description)
|
555
|
|
556
|
def _render(self,request):
|
557
|
from_url = escape(request.args['service'][0])
|
558
|
if self.tmpl_data:
|
559
|
css = config.DEFAULT_CSS
|
560
|
try:
|
561
|
css = escape(request.args['css'][0])
|
562
|
except KeyError:
|
563
|
pass
|
564
|
response = http.Response(stream=gen_page(self.title, self.tmpl_data.format(from_url), css))
|
565
|
return self.set_headers(response)
|
566
|
else:
|
567
|
return http.StatusResponse(code=self.code, description=self.description.format(from_url))
|
568
|
|
569
|
class LogoutForm(CasResource):
|
570
|
"""
|
571
|
page de confirmation de logout (si pas d'url de redirection fournie)
|
572
|
"""
|
573
|
addSlash = False
|
574
|
|
575
|
def __init__(self, manager):
|
576
|
self.manager = manager
|
577
|
super(LogoutForm, self).__init__()
|
578
|
|
579
|
def _render(self,request):
|
580
|
|
581
|
cookies = getCookies(request)
|
582
|
for cookie in cookies:
|
583
|
if cookie.name == 'EoleSSOServer':
|
584
|
session_id = cookie.value
|
585
|
if session_id is not None:
|
586
|
|
587
|
self.manager.logout(session_id.encode(config.encoding))
|
588
|
cookie.expires = 1
|
589
|
break
|
590
|
try:
|
591
|
css = escape(request.args['css'][0])
|
592
|
except KeyError:
|
593
|
css = config.DEFAULT_CSS
|
594
|
if request.args.has_key('service'):
|
595
|
url_from = request.args['service'][0]
|
596
|
response = RedirectResponse(url_from)
|
597
|
log.msg(_("Logged out : redirecting to {0}").format(url_from))
|
598
|
else:
|
599
|
|
600
|
content = """<form action="/login" method="post">
|
601
|
<p class=formvalidation>
|
602
|
<input class="btn" type="Submit" value="%s"></p></form>""" % _("Connect")
|
603
|
response = http.Response(stream=gen_page(_('SSO session closed'), content, css))
|
604
|
self.set_headers(response)
|
605
|
|
606
|
if cookies:
|
607
|
response.headers.setHeader('Set-Cookie', cookies)
|
608
|
return response
|
609
|
|
610
|
class UserChecker(Resource):
|
611
|
"""ressource utilisée pour détecter certains aspects de l'utilisateur
|
612
|
- vérifie si l'utilisateur est sujet à des problèmes de doublon (annuaire multi établissement)
|
613
|
- Si l'accès OTP est configuré, vérifie si l'utilisateur a enregistré son identifiant OTP
|
614
|
"""
|
615
|
|
616
|
isLeaf = True
|
617
|
content_type = 'application/json; charset=utf-8'
|
618
|
|
619
|
def __init__(self, manager):
|
620
|
|
621
|
self.manager = manager
|
622
|
super(UserChecker, self).__init__()
|
623
|
|
624
|
def render(self, request):
|
625
|
"""méthode de rendu http (renvoie si un utilisateur est déjà enregistré)
|
626
|
Utilisé par le fomulaire depuis une requête AJAX
|
627
|
le nom d'utilisateur à vérifier doit être passé dans la requête (argument username)
|
628
|
"""
|
629
|
username = ''
|
630
|
current_branch = request.args.get('user_branch', ['default'])[0]
|
631
|
if 'username' in request.args:
|
632
|
username = request.args.get('username', [''])[0]
|
633
|
|
634
|
if self.manager._data_proxy.use_branches:
|
635
|
if 'check_branches' in request.args and is_true(request.args.get('check_branches', [''])[0]):
|
636
|
defer_branches = self.manager._data_proxy.get_user_branches(username)
|
637
|
return defer_branches.addCallbacks(self.callb_render, log.msg, callbackArgs=[username, current_branch])
|
638
|
return self.callb_render([], username, current_branch)
|
639
|
|
640
|
@trace
|
641
|
def callb_render(self, search_branches, username, current_branch):
|
642
|
|
643
|
|
644
|
|
645
|
|
646
|
|
647
|
|
648
|
|
649
|
|
650
|
search_branches.sort(key = lambda branche: branche[1])
|
651
|
user_infos = {'search_branches':search_branches}
|
652
|
if username and self.manager.securid_user_store:
|
653
|
|
654
|
otp_login = self.manager._data_proxy.check_otp_config(current_branch)
|
655
|
if otp_login == "identiques":
|
656
|
|
657
|
user_infos['securid_registered'] = 'true'
|
658
|
elif otp_login == "configurables":
|
659
|
user_infos['securid_registered'] = self.manager.securid_user_store.check_registered(username, current_branch)
|
660
|
response = http.Response(code=responsecode.OK, stream=json_dump(user_infos))
|
661
|
response.headers.setRawHeaders('Content-type', [self.content_type])
|
662
|
return response
|
663
|
|
664
|
class LocalCookie(CasResource):
|
665
|
"""
|
666
|
page de génération d'un cookie d'authentification local
|
667
|
"""
|
668
|
addSlash = False
|
669
|
required_parameters = ('return_url','ticket')
|
670
|
|
671
|
def __init__(self, manager):
|
672
|
self.manager = manager
|
673
|
super(LocalCookie, self).__init__()
|
674
|
|
675
|
def _render(self, request):
|
676
|
try:
|
677
|
ticket = request.args['ticket'][0]
|
678
|
return_url = request.args['return_url'][0]
|
679
|
except:
|
680
|
error = {'code':'INVALID_REQUEST','detail':_("Missing parameter")}
|
681
|
log.msg(error)
|
682
|
log.msg(_("Local cookie requested"))
|
683
|
|
684
|
response = RedirectResponse(return_url)
|
685
|
if self.manager.verify_session_id(ticket):
|
686
|
ticket = ticket.encode(config.encoding)
|
687
|
|
688
|
if self.manager.user_sessions.get(ticket,(None,))[0] == None:
|
689
|
print " not implemented : redirect one level higher !"
|
690
|
cookies = []
|
691
|
for cookie in getCookies(request):
|
692
|
if cookie.name != 'EoleSSOServer':
|
693
|
cookies.append(cookie)
|
694
|
cookies.append(TwCookie("EoleSSOServer", ticket, path = "/", discard=True, secure=True))
|
695
|
if cookies:
|
696
|
log.msg(_("Local cookie sent, redirecting to child server"))
|
697
|
response.headers.setHeader('Set-Cookie', cookies)
|
698
|
return response
|
699
|
|
700
|
class InfoEtabs(CasResource):
|
701
|
"""
|
702
|
page de récupération des données des établissements (eole-dispatcher)
|
703
|
"""
|
704
|
addSlash = False
|
705
|
content_type = 'application/json'
|
706
|
|
707
|
def _render(self,request):
|
708
|
info_etabs = get_etabs(os.path.join(config.SSO_PATH,'interface','scripts','etabs.js'), sso_dir=config.SSO_PATH)
|
709
|
response = http.Response(code=responsecode.OK, stream=json_dump(info_etabs._sections))
|
710
|
return self.set_headers(response)
|
711
|
|
712
|
class LoginForm(CasResource):
|
713
|
"""The resource that is returned when you are not logged in"""
|
714
|
addSlash = False
|
715
|
|
716
|
def __init__(self, manager, auth_server, *args):
|
717
|
self.manager = manager
|
718
|
self.auth_server = auth_server
|
719
|
|
720
|
|
721
|
self.select_etab = self.calc_select_etab()
|
722
|
self.content = ''
|
723
|
|
724
|
if os.path.isfile('interface/theme/image/favicon.ico'):
|
725
|
self.favicon = 'interface/theme/image/favicon.ico'
|
726
|
elif os.path.isfile('interface/theme/image/icon.ico'):
|
727
|
self.favicon = 'interface/theme/image/icon.ico'
|
728
|
else:
|
729
|
self.favicon = 'interface/images/icon.ico'
|
730
|
Resource.__init__(self)
|
731
|
|
732
|
@trace
|
733
|
def calc_select_etab(self):
|
734
|
select_etab = ""
|
735
|
if self.manager._data_proxy.use_branches:
|
736
|
select_etab = """<tr id="row_etab" style="display:none;">
|
737
|
<td><label for='select_etab' accesskey='e'>%s</label></td>
|
738
|
<td><select name="select_etab" id="select_etab" onchange="check_user_options('false')">
|
739
|
<option value=""></option>""" % _('User Origin')
|
740
|
|
741
|
|
742
|
select_etab += "</select></td></tr>"
|
743
|
return select_etab
|
744
|
|
745
|
@trace
|
746
|
def get_css_from_service(self, from_url):
|
747
|
css = config.DEFAULT_CSS
|
748
|
|
749
|
service_filter = self.manager._check_filter(from_url)[0]
|
750
|
if service_filter not in ['default', '']:
|
751
|
if os.path.exists(os.path.join('interface','%s.css' % service_filter)):
|
752
|
css = service_filter
|
753
|
return css
|
754
|
|
755
|
@trace
|
756
|
def render_content(self, request):
|
757
|
"""
|
758
|
protocole CAS:
|
759
|
1. service : l'URL d'où on vient
|
760
|
2. renew=true : on force à se réauthentifier
|
761
|
3. gateway=true : si pas authentifié, on revient à l'URL
|
762
|
définie par service sans ticket d'application
|
763
|
ajout Eole
|
764
|
4. redirect_to : permet de définir une page ou revenir après authentification
|
765
|
(utile pour l'authentification depuis des protocoles non CAS)
|
766
|
"""
|
767
|
return self.render_login(request, request.args)
|
768
|
|
769
|
@trace
|
770
|
def render_login(self, request, request_args, provider_data=None):
|
771
|
"""
|
772
|
request args : arguments de la requête actuelle ou arguments de la requête
|
773
|
originale si retour d'une authentification externe.
|
774
|
provider_data: information supplémentaires si l'utilisateur a été authentifié
|
775
|
par un fournisseur d'identité déclaré.
|
776
|
"""
|
777
|
self.content = ""
|
778
|
gateway = is_true(request_args.get('gateway', ['false'])[0])
|
779
|
renew = is_true(request_args.get('renew', ['false'])[0])
|
780
|
warn = is_true(request_args.get('warn', ['false'])[0])
|
781
|
css = config.DEFAULT_CSS
|
782
|
try:
|
783
|
from_url = request_args['service'][0]
|
784
|
css = self.get_css_from_service(from_url)
|
785
|
except KeyError, e:
|
786
|
|
787
|
|
788
|
from_url = ''
|
789
|
try:
|
790
|
css = escape(request_args['css'][0])
|
791
|
except KeyError:
|
792
|
pass
|
793
|
try:
|
794
|
redirect_to = request_args['redirect_to'][0]
|
795
|
except KeyError:
|
796
|
redirect_to = ''
|
797
|
|
798
|
if from_url and config.VERIFY_APP:
|
799
|
if self.manager.get_app_infos(from_url) is None:
|
800
|
return '/unauthorizedService?service=%s' % from_url
|
801
|
|
802
|
user_session = getCookie(request, 'EoleSSOServer')
|
803
|
|
804
|
|
805
|
|
806
|
|
807
|
|
808
|
|
809
|
if not renew and user_session and user_session != 'foo':
|
810
|
session_id = user_session.value.encode(config.encoding)
|
811
|
verif = self.manager.verify_session_id(session_id)
|
812
|
|
813
|
|
814
|
if verif[0]:
|
815
|
if from_url:
|
816
|
app_ticket = self.manager.get_app_ticket(session_id, from_url)
|
817
|
url = urljoin(from_url, 'ticket=%s' % app_ticket)
|
818
|
if warn:
|
819
|
head = """<head><script type="text/javascript">
|
820
|
function RedirectWarn() {
|
821
|
alert("%s");
|
822
|
document.location.href="%s";
|
823
|
};
|
824
|
</script></head>"""
|
825
|
warn_msg = _("You are being authenticated by {0}").format(config.AUTH_SERVER_ADDR)
|
826
|
self.content = str("""<html>%s<body onload="setTimeout(function(){RedirectWarn()}, 200);"></body></html>""" % (head % (warn_msg, urllib.quote(url, safe=uri_reserved_chars))))
|
827
|
return None
|
828
|
return url
|
829
|
return '/loggedin'
|
830
|
|
831
|
if gateway and from_url:
|
832
|
return urljoin(from_url, 'ticket=')
|
833
|
self.auth_msg = _("Authentication needed")
|
834
|
if request_args.has_key('failed'):
|
835
|
if request_args['failed'][0] == '1':
|
836
|
if "provider" in request_args:
|
837
|
|
838
|
provider_label = self.manager.external_providers[request.args['provider'][0]][2]
|
839
|
if request_args.get('reason', [''])[0] == 'denied':
|
840
|
|
841
|
self.auth_msg = _("Account information denied ({0}),<br/>log in with local account").format(provider_label)
|
842
|
else:
|
843
|
self.auth_msg = _("Error occured while authentication to {0}").format(provider_label)
|
844
|
else:
|
845
|
self.auth_msg = _("Authentication failed, try again")
|
846
|
|
847
|
|
848
|
if provider_data:
|
849
|
|
850
|
login_ticket = self.manager.get_login_ticket(provider_data)
|
851
|
else:
|
852
|
|
853
|
|
854
|
login_ticket = self.manager.get_login_ticket(request_args)
|
855
|
if from_url != "":
|
856
|
service_input = """<input type="hidden" name="service" value="%s"/>""" % urllib.quote(from_url, safe=uri_reserved_chars)
|
857
|
else:
|
858
|
service_input = ""
|
859
|
if redirect_to != "":
|
860
|
redirect_input = """<input type="hidden" name="redirect_to" value="%s"/>""" % urllib.quote(redirect_to, safe=uri_reserved_chars)
|
861
|
else:
|
862
|
redirect_input = ""
|
863
|
|
864
|
|
865
|
|
866
|
|
867
|
additional_form = ""
|
868
|
passwd_check_func = ""
|
869
|
if config.DEBUG_LOG:
|
870
|
autocomplete = "on"
|
871
|
else:
|
872
|
autocomplete = "off"
|
873
|
if config.USE_SECURID:
|
874
|
additional_form += SECURID_AUTH_FORM % (_("label_unregistered"),
|
875
|
_("Securid user"), autocomplete, _("Securid user"),
|
876
|
_("OTP PIN number"), _("Current token"), autocomplete,
|
877
|
_("Current token"),
|
878
|
)
|
879
|
passwd_check_func = 'onkeyup="checkotp()"'
|
880
|
|
881
|
theme_txt = os.path.join(config.SSO_PATH, 'interface', 'theme', 'avertissement.txt')
|
882
|
interf_txt = os.path.join(config.SSO_PATH, 'interface', 'avertissement.txt')
|
883
|
if os.path.exists(theme_txt):
|
884
|
avertissement = open(theme_txt).read()
|
885
|
elif os.path.exists(interf_txt):
|
886
|
avertissement = open(interf_txt).read()
|
887
|
else:
|
888
|
avertissement = ""
|
889
|
|
890
|
theme_ie = os.path.join(config.SSO_PATH, 'interface', 'theme', 'alert_ie.tmpl')
|
891
|
interf_ie = os.path.join(config.SSO_PATH, 'interface', 'alert_ie.tmpl')
|
892
|
if os.path.exists(theme_ie):
|
893
|
avertissement_ie = open(theme_ie).read()
|
894
|
elif config.ACTIVER_ENVOLE_INFOS and os.path.exists('/var/www/html/envole-infos/informations_ie.php'):
|
895
|
avertissement_ie = open(interf_ie).read() % config.POSH_URL
|
896
|
else:
|
897
|
avertissement_ie = ""
|
898
|
|
899
|
theme_login = os.path.join(config.SSO_PATH, 'interface', 'theme', 'login_help.tmpl')
|
900
|
interf_login = os.path.join(config.SSO_PATH, 'interface', 'login_help.tmpl')
|
901
|
if os.path.exists(theme_login):
|
902
|
login_help = open(theme_login).read()
|
903
|
elif os.path.exists(interf_login):
|
904
|
login_help = open(interf_login).read()
|
905
|
else:
|
906
|
login_help = ""
|
907
|
if config.DEBUG_LOG:
|
908
|
autocomplete = 'on'
|
909
|
else:
|
910
|
autocomplete = 'off'
|
911
|
provider_infos = ""
|
912
|
if self.manager.external_providers:
|
913
|
if provider_data:
|
914
|
|
915
|
provider_name = provider_data['provider']
|
916
|
provider_label = self.manager.external_providers[provider_data['provider']][2]
|
917
|
alt_msg = _("Accounting with {0}").format(provider_label)
|
918
|
assoc_msg = '<img src="images/logo-{0}.png" alt="{1}" title="{1}">'.format(provider_name, alt_msg)
|
919
|
assoc_msg += _("Unknown {0} account.").format(provider_label)
|
920
|
assoc_msg += " "
|
921
|
assoc_msg += _('Please authenticate with {0} to register it.').format(config.LOCAL_ACCOUNT_LABEL)
|
922
|
provider_infos += """<div class="register_account">{}</div>""".format(assoc_msg)
|
923
|
else:
|
924
|
|
925
|
for oidc_p in self.manager.external_providers:
|
926
|
oid_args = (('provider', oidc_p), ('lt', login_ticket))
|
927
|
alt_msg = _("Connection with {0}").format(self.manager.external_providers[oidc_p][2])
|
928
|
about_info = self.manager.external_providers[oidc_p][3]
|
929
|
if about_info is not None:
|
930
|
about_url = """<br><a class="providerabout "href={0}>{1}</a>""".format(about_info[0], about_info[1])
|
931
|
else:
|
932
|
about_url = ""
|
933
|
provider_infos += '<div class="oicprovider"><a class="btn-connect" href="/oidconnect?{1}"><img src="images/{0}.png" style="border-style: none;" alt="{2}" title="{2}"></a>{3}</div>'.format(oidc_p, urllib.urlencode(oid_args), alt_msg, about_url)
|
934
|
provider_infos += '<div class="oidc_sep" style="clear:both;"></div>'
|
935
|
|
936
|
|
937
|
content = AUTH_FORM % (provider_infos,
|
938
|
login_ticket,
|
939
|
service_input,
|
940
|
redirect_input,
|
941
|
_("Login"), _("Login"), autocomplete,
|
942
|
_("Password"), _("Password"), autocomplete,
|
943
|
passwd_check_func,
|
944
|
self.select_etab,
|
945
|
additional_form,
|
946
|
login_help,
|
947
|
_("Submit"),
|
948
|
avertissement_ie, avertissement)
|
949
|
|
950
|
javascript = """document.forms['cas_auth_form'].username.focus();setTimeout(callb_onload, 200);"""
|
951
|
header_script = """<script type="text/javascript" src="scripts/mootools-core-1.4.2.js"></script>
|
952
|
<script type="text/javascript" src="scripts/tools.js?v=2.0"></script>
|
953
|
<script type="text/javascript" src="scripts/etabs.js"></script>
|
954
|
<script type="text/javascript" src="scripts/homonymes.js"></script>
|
955
|
<script type="text/javascript">
|
956
|
"""
|
957
|
otp_enabled = 'false'
|
958
|
if config.USE_SECURID:
|
959
|
|
960
|
otp_enabled = 'true'
|
961
|
header_script += open('interface/scripts/authform_otp.js').read() %\
|
962
|
(config.OTPPASS_MINSIZE,
|
963
|
config.OTPPASS_MAXSIZE,
|
964
|
config.OTPPASS_REGX)
|
965
|
header_script += open('interface/scripts/authform_ajax.js').read() %\
|
966
|
(otp_enabled,
|
967
|
_('label_password_OTP'),
|
968
|
_('label_registered'),
|
969
|
_('label_password'),
|
970
|
_('label_unregistered'),
|
971
|
_('label_password'),
|
972
|
_('select your origin'),
|
973
|
_('academic'),
|
974
|
_('several users correspond to '),
|
975
|
_('please select your authentication source'),
|
976
|
)
|
977
|
header_script += open('interface/scripts/authform_ie.js').read().strip()
|
978
|
header_script += "\n</script>"
|
979
|
self.content = gen_page(self.auth_msg, content, css, javascript, header_script)
|
980
|
return None
|
981
|
|
982
|
@trace
|
983
|
def render_provider(self, request):
|
984
|
"""checks user data sent by an authorized external provider
|
985
|
and manages association with local users.
|
986
|
"""
|
987
|
try:
|
988
|
login_ticket = request.args['lt'][0]
|
989
|
assert self.manager.login_sessions.validate_session(login_ticket)
|
990
|
except:
|
991
|
return ErrorPage(_("Authentication"),_("Access denied"))
|
992
|
cookies = getCookies(request)
|
993
|
|
994
|
|
995
|
provider_data = self.manager.login_sessions.get_session_info(login_ticket)
|
996
|
provider = provider_data['provider']
|
997
|
username = provider_data['username']
|
998
|
orig_args = provider_data['orig_args']
|
999
|
user_store = self.manager.external_providers[provider][1]
|
1000
|
search_branch, local_user = user_store.get_local_user(username)
|
1001
|
|
1002
|
provider_data['local_user'] = local_user
|
1003
|
provider_data['search_branch'] = search_branch
|
1004
|
if local_user:
|
1005
|
provider_data['need_register'] = False
|
1006
|
|
1007
|
|
1008
|
ldap_host = search_branch.split(':', 1)[0]
|
1009
|
fed_attr_name = "uid"
|
1010
|
for ldap_server in self.manager._data_proxy.ldap_servers:
|
1011
|
if ldap_server[0] == ldap_host:
|
1012
|
fed_attr_name = ldap_server[3]
|
1013
|
feder_attrs = {fed_attr_name:[local_user]}
|
1014
|
|
1015
|
attr_set = {'user_attrs':{fed_attr_name:fed_attr_name}, 'branch_attrs':{}, 'optional_attrs':{}}
|
1016
|
|
1017
|
defer_auth = self.manager.authenticate_federated_user(feder_attrs,
|
1018
|
provider,
|
1019
|
time.time(),
|
1020
|
saml_utils.available_contexts['URN_PROTECTED_PASSWORD'],
|
1021
|
search_branch = search_branch,
|
1022
|
attr_set=attr_set)
|
1023
|
from_url = orig_args.get('service' , [''])[0]
|
1024
|
redirect_to = orig_args.get('redirect_to', [''])[0]
|
1025
|
return defer_auth.addCallback(self.callb_auth, from_url, redirect_to, cookies, provider_data)
|
1026
|
else:
|
1027
|
provider_data['need_register'] = True
|
1028
|
|
1029
|
|
1030
|
redirect_to = self.render_login(request, orig_args, provider_data)
|
1031
|
return self.return_login(redirect_to)
|
1032
|
|
1033
|
@trace
|
1034
|
def render_verify(self, request):
|
1035
|
try:
|
1036
|
username = unicode(request.args['username'][0],config.encoding)
|
1037
|
except:
|
1038
|
return ErrorPage(_("Authentication"),_("Access denied"))
|
1039
|
try:
|
1040
|
search_branch = unicode(request.args['select_etab'][0],config.encoding).encode(config.encoding)
|
1041
|
except:
|
1042
|
search_branch = "default"
|
1043
|
try:
|
1044
|
password = unicode(request.args['password'][0],config.encoding)
|
1045
|
except:
|
1046
|
password = ""
|
1047
|
try:
|
1048
|
from_url = request.args['service'][0]
|
1049
|
except:
|
1050
|
from_url = ""
|
1051
|
try:
|
1052
|
redirect_to = request.args['redirect_to'][0]
|
1053
|
except KeyError:
|
1054
|
redirect_to = ''
|
1055
|
try:
|
1056
|
securid_pwd = request.args['securid_pwd'][0]
|
1057
|
except KeyError:
|
1058
|
securid_pwd = ''
|
1059
|
securid_register = False
|
1060
|
if request.args.has_key('securid_register'):
|
1061
|
|
1062
|
securid_register = True
|
1063
|
already_registered = False
|
1064
|
if is_true(request.args.get('user_registered', [''])[0]):
|
1065
|
|
1066
|
already_registered = True
|
1067
|
try:
|
1068
|
securid_login = request.args['securid_user'][0]
|
1069
|
except KeyError:
|
1070
|
securid_login = ''
|
1071
|
|
1072
|
try:
|
1073
|
ticket = request.args['lt'][0]
|
1074
|
assert self.manager.login_sessions.validate_session(ticket)
|
1075
|
except:
|
1076
|
|
1077
|
if from_url:
|
1078
|
response = RedirectResponse('/login?service=%s' % from_url)
|
1079
|
else:
|
1080
|
response = RedirectResponse('/login')
|
1081
|
return response
|
1082
|
session_infos = self.manager.login_sessions.get_session_info(ticket)
|
1083
|
|
1084
|
|
1085
|
provider_data = None
|
1086
|
cookies = getCookies(request)
|
1087
|
if 'provider' in session_infos:
|
1088
|
provider_data = session_infos
|
1089
|
|
1090
|
provider_data['local_user'] = username
|
1091
|
provider_data['search_branch'] = search_branch
|
1092
|
|
1093
|
|
1094
|
|
1095
|
|
1096
|
|
1097
|
|
1098
|
|
1099
|
|
1100
|
|
1101
|
|
1102
|
|
1103
|
|
1104
|
|
1105
|
|
1106
|
|
1107
|
|
1108
|
|
1109
|
|
1110
|
if config.USE_SECURID and securid_register:
|
1111
|
otp_login = self.manager._data_proxy.check_otp_config(search_branch)
|
1112
|
if otp_login == 'identiques':
|
1113
|
registered_user = username
|
1114
|
else:
|
1115
|
registered_user = self.manager.securid_user_store.get_external_user(username, search_branch)
|
1116
|
if already_registered and registered_user:
|
1117
|
securid_login = registered_user
|
1118
|
securid_pwd = password
|
1119
|
css = config.DEFAULT_CSS
|
1120
|
|
1121
|
if from_url:
|
1122
|
css = self.get_css_from_service(from_url)
|
1123
|
|
1124
|
login_ticket = self.manager.get_login_ticket((username, search_branch, password, from_url, redirect_to, css, securid_login, securid_pwd, already_registered, cookies))
|
1125
|
|
1126
|
|
1127
|
|
1128
|
req_args = (('lt', login_ticket),)
|
1129
|
response = RedirectResponse('/securid?%s' % urllib.urlencode(req_args))
|
1130
|
return response
|
1131
|
|
1132
|
defer_auth = self.manager.authenticate(username, password, search_branch)
|
1133
|
return defer_auth.addCallback(self.callb_auth, from_url, redirect_to, cookies, provider_data)
|
1134
|
|
1135
|
@trace
|
1136
|
def callb_auth(self, res_auth, from_url, redirect_to, request_cookies, provider_data=None):
|
1137
|
"""finalise la mise en place de la session après vérification des identifiants et renvoie l'url sur laquelle rediriger (+ cookies à mettre en place)
|
1138
|
res_auth : id de la session créée et données utilisateur (ou None)
|
1139
|
"""
|
1140
|
session_id, user_data = res_auth
|
1141
|
if session_id != "":
|
1142
|
if provider_data:
|
1143
|
|
1144
|
provider = provider_data['provider']
|
1145
|
username = provider_data['username']
|
1146
|
local_user = provider_data['local_user']
|
1147
|
search_branch = provider_data['search_branch']
|
1148
|
|
1149
|
|
1150
|
if provider_data.get('need_register', False):
|
1151
|
user_store = self.manager.external_providers[provider][1]
|
1152
|
register_ok = user_store.register_user(local_user, username, search_branch)
|
1153
|
|
1154
|
app_ticket = self.manager.get_app_ticket(session_id, from_url, idp_ident=provider_data['provider'])
|
1155
|
self.manager.app_sessions[app_ticket].saml_ident = provider_data['provider']
|
1156
|
self.manager.app_sessions[app_ticket].provider_data = provider_data
|
1157
|
|
1158
|
|
1159
|
session_id = session_id.encode(config.encoding)
|
1160
|
cookies = []
|
1161
|
for cookie in request_cookies:
|
1162
|
if cookie.name != 'EoleSSOServer':
|
1163
|
cookies.append(cookie)
|
1164
|
cookies.append(TwCookie("EoleSSOServer", session_id, path = "/", discard=True, secure=True))
|
1165
|
if from_url == '':
|
1166
|
|
1167
|
if redirect_to != '':
|
1168
|
target_url = redirect_to
|
1169
|
else:
|
1170
|
target_url = '/loggedin'
|
1171
|
else:
|
1172
|
|
1173
|
app_ticket = self.manager.get_app_ticket(session_id, from_url, from_credentials=True)
|
1174
|
target_url = urljoin(from_url, 'ticket=%s' % app_ticket)
|
1175
|
log.msg(_("Redirecting to calling app with ticket : {0}").format(get_service_from_url(from_url)))
|
1176
|
if self.manager.user_sessions.get(session_id,(None,))[0] == None:
|
1177
|
|
1178
|
req_args = (('ticket', session_id), ('return_url', target_url))
|
1179
|
response_url = '%s/gen_cookie?%s' % (self.manager.parent_url, urllib.urlencode(req_args))
|
1180
|
log.msg(_("Redirecting to parent server for session setting"))
|
1181
|
else:
|
1182
|
response_url = target_url
|
1183
|
response = RedirectResponse(response_url)
|
1184
|
if cookies:
|
1185
|
response.headers.setHeader('Set-Cookie', cookies)
|
1186
|
return response
|
1187
|
|
1188
|
|
1189
|
|
1190
|
if from_url:
|
1191
|
return RedirectResponse('/?service=%s&failed=1' %(from_url))
|
1192
|
return RedirectResponse('/?failed=1')
|
1193
|
|
1194
|
|
1195
|
|
1196
|
|
1197
|
|
1198
|
|
1199
|
|
1200
|
|
1201
|
|
1202
|
|
1203
|
|
1204
|
|
1205
|
|
1206
|
|
1207
|
|
1208
|
|
1209
|
|
1210
|
|
1211
|
|
1212
|
|
1213
|
|
1214
|
|
1215
|
|
1216
|
|
1217
|
|
1218
|
|
1219
|
|
1220
|
|
1221
|
|
1222
|
|
1223
|
|
1224
|
@trace
|
1225
|
def _render(self, request):
|
1226
|
|
1227
|
if ('username' in request.args) and (('password' in request.args) or ('securid_pwd' in request.args)):
|
1228
|
return self.render_verify(request)
|
1229
|
else:
|
1230
|
if ('from_provider' in request.args):
|
1231
|
return self.render_provider(request)
|
1232
|
else:
|
1233
|
redirect_to = self.render_content(request)
|
1234
|
return self.return_login(redirect_to)
|
1235
|
|
1236
|
@trace
|
1237
|
def return_login(self, redirect_to):
|
1238
|
if redirect_to == None:
|
1239
|
return self.set_headers(http.Response(stream=self.content))
|
1240
|
else:
|
1241
|
if redirect_to != '/loggedin' and config.DEBUG_LOG:
|
1242
|
log.msg("Authform, %s : %s" % (_("Redirecting to "), get_service_from_url(redirect_to)))
|
1243
|
return RedirectResponse(redirect_to)
|
1244
|
|
1245
|
def locateChild(self, request, segments):
|
1246
|
|
1247
|
if segments[0] == 'logout':
|
1248
|
return saml_resources.SamlLogout(self.manager), ()
|
1249
|
|
1250
|
user_session = getCookie(request, 'EoleSSOServer')
|
1251
|
if user_session and user_session.value in self.manager.pending_logout:
|
1252
|
log.msg(_("logout interrupted for session {0} : terminating").format(user_session.value.encode(config.encoding)))
|
1253
|
|
1254
|
self.manager.logout(user_session.value.encode(config.encoding))
|
1255
|
if segments:
|
1256
|
|
1257
|
if segments[0] in ('login','','verify'):
|
1258
|
return self, ()
|
1259
|
elif segments[0] in ['validate','serviceValidate']:
|
1260
|
return CASCompliantResponse(self.manager), ()
|
1261
|
elif segments[0] == 'proxyValidate':
|
1262
|
return CASCompliantResponse(self.manager, True), ()
|
1263
|
elif segments[0] == 'samlValidate':
|
1264
|
return SAMLCompliantResponse(self.manager, True), ()
|
1265
|
elif segments[0] == 'proxy':
|
1266
|
return ProxyResource(self.manager), ()
|
1267
|
elif segments[0] == 'loggedin':
|
1268
|
return LoggedInForm(self.manager), ()
|
1269
|
elif segments[0] == 'gen_cookie':
|
1270
|
return LocalCookie(self.manager), ()
|
1271
|
elif segments[0] == 'unauthorizedService':
|
1272
|
return UnauthorizedService(), ()
|
1273
|
|
1274
|
elif segments[0] == 'xmlrpc':
|
1275
|
return self.auth_server, ()
|
1276
|
|
1277
|
elif segments[0] == 'oidconnect':
|
1278
|
return oidc_resources.OIDConnect(self.manager), ()
|
1279
|
elif segments[0] == 'oidcallback':
|
1280
|
return oidc_resources.OIDCallback(self.manager), ()
|
1281
|
|
1282
|
elif segments[0] == 'saml':
|
1283
|
return saml_resources.SamlResponse(self.manager), segments[1:]
|
1284
|
|
1285
|
elif segments[0] == 'discovery':
|
1286
|
return saml_resources.IDPDiscoveryService(self.manager), ()
|
1287
|
|
1288
|
elif config.USE_SECURID and segments[0] == 'securid':
|
1289
|
return SecuridCheck(self.manager), ()
|
1290
|
elif segments[0] == 'check_user_options':
|
1291
|
return UserChecker(self.manager), ()
|
1292
|
|
1293
|
elif segments[0] == 'etabs':
|
1294
|
return InfoEtabs(), ()
|
1295
|
|
1296
|
elif segments[0] == 'favicon.ico':
|
1297
|
return static.File(self.favicon), ()
|
1298
|
elif segments[0] == 'css':
|
1299
|
return static.File('interface'), segments[1:]
|
1300
|
elif segments[0] == 'scripts':
|
1301
|
return static.File('interface/scripts'), segments[1:]
|
1302
|
elif segments[0] == 'images':
|
1303
|
return static.File('interface/images'), segments[1:]
|
1304
|
return ErrorPage(responsecode.NOT_FOUND, "%s : %s" % (_('Unreachable resource'), '/'.join(segments))), ()
|