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
-
60
+
61
61
62
62
def cgi_field_storage_to_dict (field_storage ):
63
63
"""Get a plain dictionary, rather than the '.value' system used by the
64
64
cgi module."""
65
-
65
+
66
66
params = {}
67
67
for key in field_storage .keys ():
68
68
try :
69
69
params [key ] = field_storage [key ].value
70
70
except AttributeError :
71
71
if isinstance (field_storage [key ], basestring ):
72
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
73
74
+ return params
92
75
93
76
def exception_trace (tag , exc , log ):
94
77
message = traceback .format_exception (* sys .exc_info ())
@@ -113,7 +96,7 @@ def __call__(self, environ, start_response):
113
96
class SAML2Plugin (object ):
114
97
115
98
implements (IChallenger , IIdentifier , IAuthenticator , IMetadataProvider )
116
-
99
+
117
100
def __init__ (self , rememberer_name , config , saml_client , wayf , cache ,
118
101
sid_store = None , discovery = "" , idp_query_param = "" ,
119
102
sid_store_cert = None ,):
@@ -158,27 +141,24 @@ def forget(self, environ, identity):
158
141
def _get_post (self , environ ):
159
142
"""
160
143
Get the posted information
161
-
144
+
162
145
:param environ: A dictionary with environment variables
163
146
"""
164
-
165
- post_env = environ .copy ()
166
- post_env ['QUERY_STRING' ] = ''
167
-
168
- _ = get_body (environ )
169
-
147
+
148
+ body = ''
170
149
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
-
150
+ length = int (environ .get ('CONTENT_LENGTH' , '0' ))
151
+ except ValueError :
152
+ length = 0
153
+ if length != 0 :
154
+ body = environ ['wsgi.input' ].read (length ) # get the POST variables
155
+ environ ['s2repoze.body' ] = body # store the request body for later use by pysaml2
156
+ environ ['wsgi.input' ] = StringIO (body ) # restore the request body as a stream so that everything seems untouched
157
+
158
+ post = parse_qs (body ) # parse the POST fields into a dict
159
+
180
160
logger .debug ('identify post: %s' % (post ,))
181
-
161
+
182
162
return post
183
163
184
164
def _wayf_redirect (self , came_from ):
@@ -190,8 +170,8 @@ def _wayf_redirect(self, came_from):
190
170
191
171
#noinspection PyUnusedLocal
192
172
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
173
+ """
174
+ If more than one idp and if none is selected, I have to do wayf or
195
175
disco
196
176
"""
197
177
@@ -230,7 +210,7 @@ def _pick_idp(self, environ, came_from):
230
210
detail = 'unknown ECP version' )
231
211
232
212
idps = self .metadata .with_descriptor ("idpsso" )
233
-
213
+
234
214
logger .info ("IdP URL: %s" % idps )
235
215
236
216
idp_entity_id = query = None
@@ -290,7 +270,7 @@ def _pick_idp(self, environ, came_from):
290
270
291
271
logger .info ("Chosen IdP: '%s'" % idp_entity_id )
292
272
return 0 , idp_entity_id
293
-
273
+
294
274
#### IChallenger ####
295
275
#noinspection PyUnusedLocal
296
276
def challenge (self , environ , _status , _app_headers , _forget_headers ):
@@ -320,7 +300,7 @@ def challenge(self, environ, _status, _app_headers, _forget_headers):
320
300
came_from = construct_came_from (environ )
321
301
environ ["myapp.came_from" ] = came_from
322
302
logger .debug ("[sp.challenge] RelayState >> '%s'" % came_from )
323
-
303
+
324
304
# Am I part of a virtual organization or more than one ?
325
305
try :
326
306
vorg_name = environ ["myapp.vo" ]
@@ -329,7 +309,7 @@ def challenge(self, environ, _status, _app_headers, _forget_headers):
329
309
vorg_name = _cli .vorg ._name
330
310
except AttributeError :
331
311
vorg_name = ""
332
-
312
+
333
313
logger .info ("[sp.challenge] VO: %s" % vorg_name )
334
314
335
315
# If more than one idp and if none is selected, I have to do wayf
@@ -373,7 +353,7 @@ def challenge(self, environ, _status, _app_headers, _forget_headers):
373
353
req_id , msg_str = _cli .create_authn_request (
374
354
dest , vorg = vorg_name , sign = _cli .authn_requests_signed ,
375
355
message_id = _sid , extensions = extensions )
376
- _sid = req_id
356
+ _sid = req_id
377
357
else :
378
358
req_id , req = _cli .create_authn_request (
379
359
dest , vorg = vorg_name , sign = False , extensions = extensions )
@@ -423,7 +403,7 @@ def _construct_identity(self, session_info):
423
403
logger .debug ("Identity: %s" % identity )
424
404
425
405
return identity
426
-
406
+
427
407
def _eval_authn_response (self , environ , post , binding = BINDING_HTTP_POST ):
428
408
logger .info ("Got AuthN response, checking.." )
429
409
logger .info ("Outstanding: %s" % (self .outstanding_queries ,))
@@ -432,18 +412,18 @@ def _eval_authn_response(self, environ, post, binding=BINDING_HTTP_POST):
432
412
# Evaluate the response, returns a AuthnResponse instance
433
413
try :
434
414
authresp = self .saml_client .parse_authn_request_response (
435
- post ["SAMLResponse" ], binding , self .outstanding_queries ,
415
+ post ["SAMLResponse" ][ 0 ] , binding , self .outstanding_queries ,
436
416
self .outstanding_certs )
437
417
438
418
except Exception , excp :
439
419
logger .exception ("Exception: %s" % (excp ,))
440
420
raise
441
-
421
+
442
422
session_info = authresp .session_info ()
443
423
except TypeError , excp :
444
424
logger .exception ("Exception: %s" % (excp ,))
445
425
return None
446
-
426
+
447
427
if session_info ["came_from" ]:
448
428
logger .debug ("came_from << %s" % session_info ["came_from" ])
449
429
try :
@@ -478,13 +458,13 @@ def identify(self, environ):
478
458
"SAMLResponse" not in query and "SAMLRequest" not in query :
479
459
logger .debug ('[identify] get or empty post' )
480
460
return None
481
-
461
+
482
462
# if logger:
483
463
# logger.info("ENVIRON: %s" % environ)
484
464
# logger.info("self: %s" % (self.__dict__,))
485
-
465
+
486
466
uri = environ .get ('REQUEST_URI' , construct_url (environ ))
487
-
467
+
488
468
logger .debug ('[sp.identify] uri: %s' % (uri ,))
489
469
490
470
query = parse_dict_querystring (environ )
@@ -495,15 +475,13 @@ def identify(self, environ):
495
475
binding = BINDING_HTTP_REDIRECT
496
476
else :
497
477
post = self ._get_post (environ )
498
- if post .list is None :
499
- post .list = []
500
478
binding = BINDING_HTTP_POST
501
479
502
480
try :
503
481
logger .debug ('[sp.identify] post keys: %s' % (post .keys (),))
504
482
except (TypeError , IndexError ):
505
483
pass
506
-
484
+
507
485
try :
508
486
path_info = environ ['PATH_INFO' ]
509
487
logout = False
@@ -514,7 +492,7 @@ def identify(self, environ):
514
492
print ("logout request received" )
515
493
try :
516
494
response = self .saml_client .handle_logout_request (
517
- post ["SAMLRequest" ],
495
+ post ["SAMLRequest" ][ 0 ] ,
518
496
self .saml_client .users .subjects ()[0 ], binding )
519
497
environ ['samlsp.pending' ] = self ._handle_logout (response )
520
498
return {}
@@ -536,7 +514,7 @@ def identify(self, environ):
536
514
try :
537
515
if logout :
538
516
response = self .saml_client .parse_logout_request_response (
539
- post ["SAMLResponse" ], binding )
517
+ post ["SAMLResponse" ][ 0 ] , binding )
540
518
if response :
541
519
action = self .saml_client .handle_logout_response (
542
520
response )
@@ -572,8 +550,8 @@ def identify(self, environ):
572
550
exception_trace ("sp.identity" , exc , logger )
573
551
environ ["post.fieldstorage" ] = post
574
552
return {}
575
-
576
- if session_info :
553
+
554
+ if session_info :
577
555
environ ["s2repoze.sessioninfo" ] = session_info
578
556
return self ._construct_identity (session_info )
579
557
else :
@@ -596,12 +574,12 @@ def add_metadata(self, environ, identity):
596
574
logger .debug ("Issuers: %s" % _cli .users .sources (name_id ))
597
575
except KeyError :
598
576
pass
599
-
577
+
600
578
if "user" not in identity :
601
579
identity ["user" ] = {}
602
580
try :
603
581
(ava , _ ) = _cli .users .get_identity (name_id )
604
- #now = time.gmtime()
582
+ #now = time.gmtime()
605
583
logger .debug ("[add_metadata] adds: %s" % ava )
606
584
identity ["user" ].update (ava )
607
585
except KeyError :
@@ -625,7 +603,7 @@ def add_metadata(self, environ, identity):
625
603
if not identity ["user" ]:
626
604
# remove cookie and demand re-authentication
627
605
pass
628
-
606
+
629
607
# used 2 times : one to get the ticket, the other to validate it
630
608
@staticmethod
631
609
def _service_url (environ , qstr = None ):
@@ -635,7 +613,7 @@ def _service_url(environ, qstr=None):
635
613
url = construct_url (environ )
636
614
return url
637
615
638
- #### IAuthenticatorPlugin ####
616
+ #### IAuthenticatorPlugin ####
639
617
#noinspection PyUnusedLocal
640
618
def authenticate (self , environ , identity = None ):
641
619
if identity :
@@ -672,7 +650,7 @@ def make_plugin(remember_name=None, # plugin for remember
672
650
discovery = "" ,
673
651
idp_query_param = ""
674
652
):
675
-
653
+
676
654
if saml_conf is "" :
677
655
raise ValueError (
678
656
'must include saml_conf in configuration' )
0 commit comments