Skip to content

Commit f501200

Browse files
committed
correctly restoring environ["wsgi.input"] after reading POST content
1 parent 6c1b963 commit f501200

File tree

1 file changed

+47
-69
lines changed
  • src/s2repoze/plugins

1 file changed

+47
-69
lines changed

src/s2repoze/plugins/sp.py

Lines changed: 47 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#
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
44
and SAML2 attribute aggregations as metadata collector in your
55
WSGI application.
66
@@ -49,46 +49,29 @@
4949

5050

5151
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
5353
for single-sign-on processing. """
54-
55-
came_from = environ.get("PATH_INFO")
54+
55+
came_from = environ.get("PATH_INFO")
5656
qstr = environ.get("QUERY_STRING", "")
5757
if qstr:
5858
came_from += '?' + qstr
5959
return came_from
60-
60+
6161

6262
def cgi_field_storage_to_dict(field_storage):
6363
"""Get a plain dictionary, rather than the '.value' system used by the
6464
cgi module."""
65-
65+
6666
params = {}
6767
for key in field_storage.keys():
6868
try:
6969
params[key] = field_storage[key].value
7070
except AttributeError:
7171
if isinstance(field_storage[key], basestring):
7272
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
9173

74+
return params
9275

9376
def exception_trace(tag, exc, log):
9477
message = traceback.format_exception(*sys.exc_info())
@@ -113,7 +96,7 @@ def __call__(self, environ, start_response):
11396
class SAML2Plugin(object):
11497

11598
implements(IChallenger, IIdentifier, IAuthenticator, IMetadataProvider)
116-
99+
117100
def __init__(self, rememberer_name, config, saml_client, wayf, cache,
118101
sid_store=None, discovery="", idp_query_param="",
119102
sid_store_cert=None,):
@@ -158,27 +141,24 @@ def forget(self, environ, identity):
158141
def _get_post(self, environ):
159142
"""
160143
Get the posted information
161-
144+
162145
:param environ: A dictionary with environment variables
163146
"""
164-
165-
post_env = environ.copy()
166-
post_env['QUERY_STRING'] = ''
167-
168-
_ = get_body(environ)
169-
147+
148+
body= ''
170149
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+
180160
logger.debug('identify post: %s' % (post,))
181-
161+
182162
return post
183163

184164
def _wayf_redirect(self, came_from):
@@ -190,8 +170,8 @@ def _wayf_redirect(self, came_from):
190170

191171
#noinspection PyUnusedLocal
192172
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
195175
disco
196176
"""
197177

@@ -230,7 +210,7 @@ def _pick_idp(self, environ, came_from):
230210
detail='unknown ECP version')
231211

232212
idps = self.metadata.with_descriptor("idpsso")
233-
213+
234214
logger.info("IdP URL: %s" % idps)
235215

236216
idp_entity_id = query = None
@@ -290,7 +270,7 @@ def _pick_idp(self, environ, came_from):
290270

291271
logger.info("Chosen IdP: '%s'" % idp_entity_id)
292272
return 0, idp_entity_id
293-
273+
294274
#### IChallenger ####
295275
#noinspection PyUnusedLocal
296276
def challenge(self, environ, _status, _app_headers, _forget_headers):
@@ -320,7 +300,7 @@ def challenge(self, environ, _status, _app_headers, _forget_headers):
320300
came_from = construct_came_from(environ)
321301
environ["myapp.came_from"] = came_from
322302
logger.debug("[sp.challenge] RelayState >> '%s'" % came_from)
323-
303+
324304
# Am I part of a virtual organization or more than one ?
325305
try:
326306
vorg_name = environ["myapp.vo"]
@@ -329,7 +309,7 @@ def challenge(self, environ, _status, _app_headers, _forget_headers):
329309
vorg_name = _cli.vorg._name
330310
except AttributeError:
331311
vorg_name = ""
332-
312+
333313
logger.info("[sp.challenge] VO: %s" % vorg_name)
334314

335315
# 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):
373353
req_id, msg_str = _cli.create_authn_request(
374354
dest, vorg=vorg_name, sign=_cli.authn_requests_signed,
375355
message_id=_sid, extensions=extensions)
376-
_sid = req_id
356+
_sid = req_id
377357
else:
378358
req_id, req = _cli.create_authn_request(
379359
dest, vorg=vorg_name, sign=False, extensions=extensions)
@@ -423,7 +403,7 @@ def _construct_identity(self, session_info):
423403
logger.debug("Identity: %s" % identity)
424404

425405
return identity
426-
406+
427407
def _eval_authn_response(self, environ, post, binding=BINDING_HTTP_POST):
428408
logger.info("Got AuthN response, checking..")
429409
logger.info("Outstanding: %s" % (self.outstanding_queries,))
@@ -432,18 +412,18 @@ def _eval_authn_response(self, environ, post, binding=BINDING_HTTP_POST):
432412
# Evaluate the response, returns a AuthnResponse instance
433413
try:
434414
authresp = self.saml_client.parse_authn_request_response(
435-
post["SAMLResponse"], binding, self.outstanding_queries,
415+
post["SAMLResponse"][0], binding, self.outstanding_queries,
436416
self.outstanding_certs)
437417

438418
except Exception, excp:
439419
logger.exception("Exception: %s" % (excp,))
440420
raise
441-
421+
442422
session_info = authresp.session_info()
443423
except TypeError, excp:
444424
logger.exception("Exception: %s" % (excp,))
445425
return None
446-
426+
447427
if session_info["came_from"]:
448428
logger.debug("came_from << %s" % session_info["came_from"])
449429
try:
@@ -478,13 +458,13 @@ def identify(self, environ):
478458
"SAMLResponse" not in query and "SAMLRequest" not in query:
479459
logger.debug('[identify] get or empty post')
480460
return None
481-
461+
482462
# if logger:
483463
# logger.info("ENVIRON: %s" % environ)
484464
# logger.info("self: %s" % (self.__dict__,))
485-
465+
486466
uri = environ.get('REQUEST_URI', construct_url(environ))
487-
467+
488468
logger.debug('[sp.identify] uri: %s' % (uri,))
489469

490470
query = parse_dict_querystring(environ)
@@ -495,15 +475,13 @@ def identify(self, environ):
495475
binding = BINDING_HTTP_REDIRECT
496476
else:
497477
post = self._get_post(environ)
498-
if post.list is None:
499-
post.list = []
500478
binding = BINDING_HTTP_POST
501479

502480
try:
503481
logger.debug('[sp.identify] post keys: %s' % (post.keys(),))
504482
except (TypeError, IndexError):
505483
pass
506-
484+
507485
try:
508486
path_info = environ['PATH_INFO']
509487
logout = False
@@ -514,7 +492,7 @@ def identify(self, environ):
514492
print("logout request received")
515493
try:
516494
response = self.saml_client.handle_logout_request(
517-
post["SAMLRequest"],
495+
post["SAMLRequest"][0],
518496
self.saml_client.users.subjects()[0], binding)
519497
environ['samlsp.pending'] = self._handle_logout(response)
520498
return {}
@@ -536,7 +514,7 @@ def identify(self, environ):
536514
try:
537515
if logout:
538516
response = self.saml_client.parse_logout_request_response(
539-
post["SAMLResponse"], binding)
517+
post["SAMLResponse"][0], binding)
540518
if response:
541519
action = self.saml_client.handle_logout_response(
542520
response)
@@ -572,8 +550,8 @@ def identify(self, environ):
572550
exception_trace("sp.identity", exc, logger)
573551
environ["post.fieldstorage"] = post
574552
return {}
575-
576-
if session_info:
553+
554+
if session_info:
577555
environ["s2repoze.sessioninfo"] = session_info
578556
return self._construct_identity(session_info)
579557
else:
@@ -596,12 +574,12 @@ def add_metadata(self, environ, identity):
596574
logger.debug("Issuers: %s" % _cli.users.sources(name_id))
597575
except KeyError:
598576
pass
599-
577+
600578
if "user" not in identity:
601579
identity["user"] = {}
602580
try:
603581
(ava, _) = _cli.users.get_identity(name_id)
604-
#now = time.gmtime()
582+
#now = time.gmtime()
605583
logger.debug("[add_metadata] adds: %s" % ava)
606584
identity["user"].update(ava)
607585
except KeyError:
@@ -625,7 +603,7 @@ def add_metadata(self, environ, identity):
625603
if not identity["user"]:
626604
# remove cookie and demand re-authentication
627605
pass
628-
606+
629607
# used 2 times : one to get the ticket, the other to validate it
630608
@staticmethod
631609
def _service_url(environ, qstr=None):
@@ -635,7 +613,7 @@ def _service_url(environ, qstr=None):
635613
url = construct_url(environ)
636614
return url
637615

638-
#### IAuthenticatorPlugin ####
616+
#### IAuthenticatorPlugin ####
639617
#noinspection PyUnusedLocal
640618
def authenticate(self, environ, identity=None):
641619
if identity:
@@ -672,7 +650,7 @@ def make_plugin(remember_name=None, # plugin for remember
672650
discovery="",
673651
idp_query_param=""
674652
):
675-
653+
676654
if saml_conf is "":
677655
raise ValueError(
678656
'must include saml_conf in configuration')

0 commit comments

Comments
 (0)