1
1
#
2
- """
3
- A plugin that allows you to use SAML2 SSO as authentication
2
+ """
3
+ A plugin that allows you to use SAML2 SSO as authentication
4
4
and SAML2 attribute aggregations as metadata collector in your
5
5
WSGI application.
6
6
49
49
50
50
51
51
def construct_came_from (environ ):
52
- """ The URL that the user used when the process where interupted
52
+ """ The URL that the user used when the process where interupted
53
53
for single-sign-on processing. """
54
-
55
- came_from = environ .get ("PATH_INFO" )
54
+
55
+ came_from = environ .get ("PATH_INFO" )
56
56
qstr = environ .get ("QUERY_STRING" , "" )
57
57
if qstr :
58
58
came_from += '?' + qstr
59
59
return came_from
60
-
61
-
62
- def cgi_field_storage_to_dict (field_storage ):
63
- """Get a plain dictionary, rather than the '.value' system used by the
64
- cgi module."""
65
-
66
- params = {}
67
- for key in field_storage .keys ():
68
- try :
69
- params [key ] = field_storage [key ].value
70
- except AttributeError :
71
- if isinstance (field_storage [key ], basestring ):
72
- params [key ] = field_storage [key ]
73
-
74
- return params
75
-
76
-
77
- def get_body (environ ):
78
- length = int (environ ["CONTENT_LENGTH" ])
79
- try :
80
- body = environ ["wsgi.input" ].read (length )
81
- except Exception , excp :
82
- logger .exception ("Exception while reading post: %s" % (excp ,))
83
- raise
84
-
85
- # restore what I might have upset
86
- from StringIO import StringIO
87
- environ ['wsgi.input' ] = StringIO (body )
88
- environ ['s2repoze.body' ] = body
89
-
90
- return body
91
-
92
60
93
61
def exception_trace (tag , exc , log ):
94
62
message = traceback .format_exception (* sys .exc_info ())
@@ -113,7 +81,7 @@ def __call__(self, environ, start_response):
113
81
class SAML2Plugin (object ):
114
82
115
83
implements (IChallenger , IIdentifier , IAuthenticator , IMetadataProvider )
116
-
84
+
117
85
def __init__ (self , rememberer_name , config , saml_client , wayf , cache ,
118
86
sid_store = None , discovery = "" , idp_query_param = "" ,
119
87
sid_store_cert = None ,):
@@ -158,27 +126,24 @@ def forget(self, environ, identity):
158
126
def _get_post (self , environ ):
159
127
"""
160
128
Get the posted information
161
-
129
+
162
130
:param environ: A dictionary with environment variables
163
131
"""
164
-
165
- post_env = environ .copy ()
166
- post_env ['QUERY_STRING' ] = ''
167
-
168
- _ = get_body (environ )
169
-
132
+
133
+ body = ''
170
134
try :
171
- post = cgi .FieldStorage (
172
- fp = environ ['wsgi.input' ],
173
- environ = post_env ,
174
- keep_blank_values = True
175
- )
176
- except Exception , excp :
177
- logger .debug ("Exception (II): %s" % (excp ,))
178
- raise
179
-
135
+ length = int (environ .get ('CONTENT_LENGTH' , '0' ))
136
+ except ValueError :
137
+ length = 0
138
+ if length != 0 :
139
+ body = environ ['wsgi.input' ].read (length ) # get the POST variables
140
+ environ ['s2repoze.body' ] = body # store the request body for later use by pysaml2
141
+ environ ['wsgi.input' ] = StringIO (body ) # restore the request body as a stream so that everything seems untouched
142
+
143
+ post = parse_qs (body ) # parse the POST fields into a dict
144
+
180
145
logger .debug ('identify post: %s' % (post ,))
181
-
146
+
182
147
return post
183
148
184
149
def _wayf_redirect (self , came_from ):
@@ -190,8 +155,8 @@ def _wayf_redirect(self, came_from):
190
155
191
156
#noinspection PyUnusedLocal
192
157
def _pick_idp (self , environ , came_from ):
193
- """
194
- If more than one idp and if none is selected, I have to do wayf or
158
+ """
159
+ If more than one idp and if none is selected, I have to do wayf or
195
160
disco
196
161
"""
197
162
@@ -230,7 +195,7 @@ def _pick_idp(self, environ, came_from):
230
195
detail = 'unknown ECP version' )
231
196
232
197
idps = self .metadata .with_descriptor ("idpsso" )
233
-
198
+
234
199
logger .info ("IdP URL: %s" % idps )
235
200
236
201
idp_entity_id = query = None
@@ -290,7 +255,7 @@ def _pick_idp(self, environ, came_from):
290
255
291
256
logger .info ("Chosen IdP: '%s'" % idp_entity_id )
292
257
return 0 , idp_entity_id
293
-
258
+
294
259
#### IChallenger ####
295
260
#noinspection PyUnusedLocal
296
261
def challenge (self , environ , _status , _app_headers , _forget_headers ):
@@ -320,7 +285,7 @@ def challenge(self, environ, _status, _app_headers, _forget_headers):
320
285
came_from = construct_came_from (environ )
321
286
environ ["myapp.came_from" ] = came_from
322
287
logger .debug ("[sp.challenge] RelayState >> '%s'" % came_from )
323
-
288
+
324
289
# Am I part of a virtual organization or more than one ?
325
290
try :
326
291
vorg_name = environ ["myapp.vo" ]
@@ -329,7 +294,7 @@ def challenge(self, environ, _status, _app_headers, _forget_headers):
329
294
vorg_name = _cli .vorg ._name
330
295
except AttributeError :
331
296
vorg_name = ""
332
-
297
+
333
298
logger .info ("[sp.challenge] VO: %s" % vorg_name )
334
299
335
300
# If more than one idp and if none is selected, I have to do wayf
@@ -373,7 +338,7 @@ def challenge(self, environ, _status, _app_headers, _forget_headers):
373
338
req_id , msg_str = _cli .create_authn_request (
374
339
dest , vorg = vorg_name , sign = _cli .authn_requests_signed ,
375
340
message_id = _sid , extensions = extensions )
376
- _sid = req_id
341
+ _sid = req_id
377
342
else :
378
343
req_id , req = _cli .create_authn_request (
379
344
dest , vorg = vorg_name , sign = False , extensions = extensions )
@@ -423,7 +388,7 @@ def _construct_identity(self, session_info):
423
388
logger .debug ("Identity: %s" % identity )
424
389
425
390
return identity
426
-
391
+
427
392
def _eval_authn_response (self , environ , post , binding = BINDING_HTTP_POST ):
428
393
logger .info ("Got AuthN response, checking.." )
429
394
logger .info ("Outstanding: %s" % (self .outstanding_queries ,))
@@ -432,18 +397,18 @@ def _eval_authn_response(self, environ, post, binding=BINDING_HTTP_POST):
432
397
# Evaluate the response, returns a AuthnResponse instance
433
398
try :
434
399
authresp = self .saml_client .parse_authn_request_response (
435
- post ["SAMLResponse" ], binding , self .outstanding_queries ,
400
+ post ["SAMLResponse" ][ 0 ] , binding , self .outstanding_queries ,
436
401
self .outstanding_certs )
437
402
438
403
except Exception , excp :
439
404
logger .exception ("Exception: %s" % (excp ,))
440
405
raise
441
-
406
+
442
407
session_info = authresp .session_info ()
443
408
except TypeError , excp :
444
409
logger .exception ("Exception: %s" % (excp ,))
445
410
return None
446
-
411
+
447
412
if session_info ["came_from" ]:
448
413
logger .debug ("came_from << %s" % session_info ["came_from" ])
449
414
try :
@@ -478,13 +443,13 @@ def identify(self, environ):
478
443
"SAMLResponse" not in query and "SAMLRequest" not in query :
479
444
logger .debug ('[identify] get or empty post' )
480
445
return None
481
-
446
+
482
447
# if logger:
483
448
# logger.info("ENVIRON: %s" % environ)
484
449
# logger.info("self: %s" % (self.__dict__,))
485
-
450
+
486
451
uri = environ .get ('REQUEST_URI' , construct_url (environ ))
487
-
452
+
488
453
logger .debug ('[sp.identify] uri: %s' % (uri ,))
489
454
490
455
query = parse_dict_querystring (environ )
@@ -495,15 +460,13 @@ def identify(self, environ):
495
460
binding = BINDING_HTTP_REDIRECT
496
461
else :
497
462
post = self ._get_post (environ )
498
- if post .list is None :
499
- post .list = []
500
463
binding = BINDING_HTTP_POST
501
464
502
465
try :
503
466
logger .debug ('[sp.identify] post keys: %s' % (post .keys (),))
504
467
except (TypeError , IndexError ):
505
468
pass
506
-
469
+
507
470
try :
508
471
path_info = environ ['PATH_INFO' ]
509
472
logout = False
@@ -514,7 +477,7 @@ def identify(self, environ):
514
477
print ("logout request received" )
515
478
try :
516
479
response = self .saml_client .handle_logout_request (
517
- post ["SAMLRequest" ],
480
+ post ["SAMLRequest" ][ 0 ] ,
518
481
self .saml_client .users .subjects ()[0 ], binding )
519
482
environ ['samlsp.pending' ] = self ._handle_logout (response )
520
483
return {}
@@ -536,7 +499,7 @@ def identify(self, environ):
536
499
try :
537
500
if logout :
538
501
response = self .saml_client .parse_logout_request_response (
539
- post ["SAMLResponse" ], binding )
502
+ post ["SAMLResponse" ][ 0 ] , binding )
540
503
if response :
541
504
action = self .saml_client .handle_logout_response (
542
505
response )
@@ -552,7 +515,7 @@ def identify(self, environ):
552
515
return {}
553
516
else :
554
517
session_info = self ._eval_authn_response (
555
- environ , cgi_field_storage_to_dict ( post ) ,
518
+ environ , post ,
556
519
binding = binding )
557
520
except Exception , err :
558
521
environ ["s2repoze.saml_error" ] = err
@@ -572,8 +535,8 @@ def identify(self, environ):
572
535
exception_trace ("sp.identity" , exc , logger )
573
536
environ ["post.fieldstorage" ] = post
574
537
return {}
575
-
576
- if session_info :
538
+
539
+ if session_info :
577
540
environ ["s2repoze.sessioninfo" ] = session_info
578
541
return self ._construct_identity (session_info )
579
542
else :
@@ -596,12 +559,12 @@ def add_metadata(self, environ, identity):
596
559
logger .debug ("Issuers: %s" % _cli .users .sources (name_id ))
597
560
except KeyError :
598
561
pass
599
-
562
+
600
563
if "user" not in identity :
601
564
identity ["user" ] = {}
602
565
try :
603
566
(ava , _ ) = _cli .users .get_identity (name_id )
604
- #now = time.gmtime()
567
+ #now = time.gmtime()
605
568
logger .debug ("[add_metadata] adds: %s" % ava )
606
569
identity ["user" ].update (ava )
607
570
except KeyError :
@@ -625,7 +588,7 @@ def add_metadata(self, environ, identity):
625
588
if not identity ["user" ]:
626
589
# remove cookie and demand re-authentication
627
590
pass
628
-
591
+
629
592
# used 2 times : one to get the ticket, the other to validate it
630
593
@staticmethod
631
594
def _service_url (environ , qstr = None ):
@@ -635,7 +598,7 @@ def _service_url(environ, qstr=None):
635
598
url = construct_url (environ )
636
599
return url
637
600
638
- #### IAuthenticatorPlugin ####
601
+ #### IAuthenticatorPlugin ####
639
602
#noinspection PyUnusedLocal
640
603
def authenticate (self , environ , identity = None ):
641
604
if identity :
@@ -672,7 +635,7 @@ def make_plugin(remember_name=None, # plugin for remember
672
635
discovery = "" ,
673
636
idp_query_param = ""
674
637
):
675
-
638
+
676
639
if saml_conf is "" :
677
640
raise ValueError (
678
641
'must include saml_conf in configuration' )
0 commit comments