diff --git a/example/idp2/idp.py b/example/idp2/idp.py index 9a7f68b..90339c8 100755 --- a/example/idp2/idp.py +++ b/example/idp2/idp.py @@ -20,6 +20,7 @@ from saml2 import BINDING_HTTP_REDIRECT from saml2 import BINDING_HTTP_POST from saml2 import server from saml2 import time_util +from saml2.authn import is_equal from saml2.authn_context import AuthnBroker from saml2.authn_context import PASSWORD @@ -131,20 +132,20 @@ class Service(object): else: # saml_msg may also contain Signature and SigAlg if "Signature" in saml_msg: - args = {"signature": saml_msg["signature"], + kwargs = {"signature": saml_msg["signature"], "sigalg": saml_msg["SigAlg"]} else: - args = {} + kwargs = {} try: _encrypt_cert = encrypt_cert_from_item( saml_msg["req_info"].message) return self.do(saml_msg["SAMLRequest"], binding, saml_msg["RelayState"], - encrypt_cert=_encrypt_cert, **args) + encrypt_cert=_encrypt_cert, **kwargs) except KeyError: # Can live with no relay state # TODO or can we, for inacademia? return self.do(saml_msg["SAMLRequest"], binding, - saml_msg["RelayState"], **args) + saml_msg["RelayState"], **kwargs) def artifact_operation(self, saml_msg): if not saml_msg: @@ -454,10 +455,9 @@ class SSO(Service): except TypeError: resp = Unauthorized() else: - logger.debug("Authz_info: %s" % _info) try: (user, passwd) = _info.split(":") - if PASSWD[user] != passwd: + if is_equal(PASSWD[user], passwd): resp = Unauthorized() self.user = user self.environ[ @@ -931,12 +931,16 @@ def metadata(environ, start_response): def staticfile(environ, start_response): try: - path = args.path + path = args.path[:] if path is None or len(path) == 0: path = os.path.dirname(os.path.abspath(__file__)) if path[-1] != "/": path += "/" path += environ.get('PATH_INFO', '').lstrip('/') + path = os.path.realpath(path) + if not path.startswith(args.path): + resp = Unauthorized() + return resp(environ, start_response) start_response('200 OK', [('Content-Type', "text/xml")]) return open(path, 'r').read() except Exception as ex: diff --git a/example/idp2/idp_uwsgi.py b/example/idp2/idp_uwsgi.py index 71aa714..ca8d105 100755 --- a/example/idp2/idp_uwsgi.py +++ b/example/idp2/idp_uwsgi.py @@ -10,6 +10,7 @@ from hashlib import sha1 from urlparse import parse_qs from Cookie import SimpleCookie import os +from saml2.authn import is_equal from saml2.profile import ecp from saml2 import server @@ -73,12 +74,14 @@ def get_eptid(idp, req_info, session): req_info.sender(), session["permanent_id"], session["authn_auth"]) + # ----------------------------------------------------------------------------- def dict2list_of_tuples(d): return [(k, v) for k, v in d.items()] + # ----------------------------------------------------------------------------- @@ -95,7 +98,7 @@ class Service(object): return dict([(k, v[0]) for k, v in parse_qs(_qs).items()]) else: return None - + def unpack_post(self): _dict = parse_qs(get_post(self.environ)) logger.debug("unpack_post:: %s" % _dict) @@ -103,14 +106,14 @@ class Service(object): return dict([(k, v[0]) for k, v in _dict.items()]) except Exception: return None - + def unpack_soap(self): try: query = get_post(self.environ) return {"SAMLRequest": query, "RelayState": ""} except Exception: return None - + def unpack_either(self): if self.environ["REQUEST_METHOD"] == "GET": _dict = self.unpack_redirect() @@ -292,7 +295,7 @@ class SSO(Service): if not _resp: identity = USERS[self.user].copy() - #identity["eduPersonTargetedID"] = get_eptid(IDP, query, session) + # identity["eduPersonTargetedID"] = get_eptid(IDP, query, session) logger.info("Identity: %s" % (identity,)) if REPOZE_ID_EQUIVALENT: @@ -357,7 +360,8 @@ class SSO(Service): _req = self.req_info.message - if "SigAlg" in saml_msg and "Signature" in saml_msg: # Signed request + if "SigAlg" in saml_msg and "Signature" in saml_msg: # Signed + # request issuer = _req.issuer.text _certs = IDP.metadata.certs(issuer, "any", "signing") verified_ok = False @@ -405,7 +409,7 @@ class SSO(Service): return self.not_authn(key, _req.requested_authn_context) # def artifact(self): - # # Can be either by HTTP_Redirect or HTTP_POST + # # Can be either by HTTP_Redirect or HTTP_POST # _req = self._store_request(self.unpack_either()) # if isinstance(_req, basestring): # return self.not_authn(_req) @@ -424,10 +428,9 @@ class SSO(Service): except TypeError: resp = Unauthorized() else: - logger.debug("Authz_info: %s" % _info) try: (user, passwd) = _info.split(":") - if PASSWD[user] != passwd: + if is_equal(PASSWD[user], passwd): resp = Unauthorized() self.user = user self.environ[ @@ -449,6 +452,7 @@ class SSO(Service): self.op_type = "ecp" return self.operation(_dict, BINDING_SOAP) + # ----------------------------------------------------------------------------- # === Authentication ==== # ----------------------------------------------------------------------------- @@ -474,11 +478,11 @@ def do_authentication(environ, start_response, authn_context, key, # ----------------------------------------------------------------------------- PASSWD = { - "daev0001": "qwerty", - "haho0032": "qwerty", - "roland": "dianakra", - "babs": "howes", - "upper": "crust"} + "daev0001": "qwerty", + "haho0032": "qwerty", + "roland": "dianakra", + "babs": "howes", + "upper": "crust"} def username_password_authn(environ, start_response, reference, key, @@ -552,7 +556,7 @@ def not_found(environ, start_response): # === Single log out === # ----------------------------------------------------------------------------- -#def _subject_sp_info(req_info): +# def _subject_sp_info(req_info): # # look for the subject # subject = req_info.subject_id() # subject = subject.text.strip() @@ -570,7 +574,7 @@ class SLO(Service): logger.error("Bad request: %s" % exc) resp = BadRequest("%s" % exc) return resp(self.environ, self.start_response) - + msg = req_info.message if msg.name_id: lid = IDP.ident.find_local_id(msg.name_id) @@ -587,16 +591,16 @@ class SLO(Service): logger.error("ServiceError: %s" % exc) resp = ServiceError("%s" % exc) return resp(self.environ, self.start_response) - + resp = IDP.create_logout_response(msg, [binding]) - + try: hinfo = IDP.apply_binding(binding, "%s" % resp, "", relay_state) except Exception as exc: logger.error("ServiceError: %s" % exc) resp = ServiceError("%s" % exc) return resp(self.environ, self.start_response) - + #_tlh = dict2list_of_tuples(hinfo["headers"]) delco = delete_cookie(self.environ, "idpauthn") if delco: @@ -604,35 +608,36 @@ class SLO(Service): logger.info("Header: %s" % (hinfo["headers"],)) resp = Response(hinfo["data"], headers=hinfo["headers"]) return resp(self.environ, self.start_response) - + + # ---------------------------------------------------------------------------- # Manage Name ID service # ---------------------------------------------------------------------------- class NMI(Service): - def do(self, query, binding, relay_state="", encrypt_cert=None): logger.info("--- Manage Name ID Service ---") req = IDP.parse_manage_name_id_request(query, binding) request = req.message - + # Do the necessary stuff name_id = IDP.ident.handle_manage_name_id_request( request.name_id, request.new_id, request.new_encrypted_id, request.terminate) - + logger.debug("New NameID: %s" % name_id) - + _resp = IDP.create_manage_name_id_response(request) - + # It's using SOAP binding hinfo = IDP.apply_binding(BINDING_SOAP, "%s" % _resp, "", relay_state, response=True) - + resp = Response(hinfo["data"], headers=hinfo["headers"]) return resp(self.environ, self.start_response) - + + # ---------------------------------------------------------------------------- # === Assertion ID request === # ---------------------------------------------------------------------------- @@ -648,9 +653,9 @@ class AIDR(Service): except Unknown: resp = NotFound(aid) return resp(self.environ, self.start_response) - + hinfo = IDP.apply_binding(BINDING_URI, "%s" % assertion, response=True) - + logger.debug("HINFO: %s" % hinfo) resp = Response(hinfo["data"], headers=hinfo["headers"]) return resp(self.environ, self.start_response) @@ -680,6 +685,7 @@ class ARS(Service): resp = Response(hinfo["data"], headers=hinfo["headers"]) return resp(self.environ, self.start_response) + # ---------------------------------------------------------------------------- # === Authn query service === # ---------------------------------------------------------------------------- @@ -734,6 +740,7 @@ class ATTR(Service): resp = Response(hinfo["data"], headers=hinfo["headers"]) return resp(self.environ, self.start_response) + # ---------------------------------------------------------------------------- # Name ID Mapping service # When an entity that shares an identifier for a principal with an identity @@ -757,17 +764,17 @@ class NIM(Service): except PolicyError: resp = BadRequest("Unknown entity") return resp(self.environ, self.start_response) - + info = IDP.response_args(request) _resp = IDP.create_name_id_mapping_response(name_id, **info) - + # Only SOAP hinfo = IDP.apply_binding(BINDING_SOAP, "%s" % _resp, "", "", response=True) - + resp = Response(hinfo["data"], headers=hinfo["headers"]) return resp(self.environ, self.start_response) - + # ---------------------------------------------------------------------------- # Cookie handling @@ -862,10 +869,10 @@ def metadata(environ, start_response): try: path = args.path if path is None or len(path) == 0: - path = os.path.dirname(os.path.abspath( __file__ )) + path = os.path.dirname(os.path.abspath(__file__)) if path[-1] != "/": path += "/" - metadata = create_metadata_string(path+args.config, IDP.config, + metadata = create_metadata_string(path + args.config, IDP.config, args.valid, args.cert, args.keyfile, args.id, args.name, args.sign) start_response('200 OK', [('Content-Type', "text/xml")]) @@ -874,6 +881,7 @@ def metadata(environ, start_response): logger.error("An error occured while creating metadata:" + ex.message) return not_found(environ, start_response) + def staticfile(environ, start_response): try: path = args.path @@ -882,12 +890,17 @@ def staticfile(environ, start_response): if path[-1] != "/": path += "/" path += environ.get('PATH_INFO', '').lstrip('/') + path = os.path.realpath(path) + if not path.startswith(args.path): + resp = Unauthorized() + return resp(environ, start_response) start_response('200 OK', [('Content-Type', "text/xml")]) return open(path, 'r').read() except Exception as ex: logger.error("An error occured while creating metadata:" + ex.message) return not_found(environ, start_response) + def application(environ, start_response): """ The main WSGI application. Dispatch the current request to @@ -924,7 +937,6 @@ def application(environ, start_response): except KeyError: user = None - url_patterns = AUTHN_URLS if not user: logger.info("-- No USER --") @@ -956,7 +968,7 @@ def application(environ, start_response): # by moving some initialization out of __name__ == '__main__' section. # uwsgi -s 0.0.0.0:8088 --protocol http --callable application --module idp -args = type('Config', (object,), { }) +args = type('Config', (object,), {}) args.config = 'idp_conf' args.mako_root = './' args.path = None @@ -984,7 +996,8 @@ if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument('-p', dest='path', help='Path to configuration file.') parser.add_argument('-v', dest='valid', - help="How long, in days, the metadata is valid from the time of creation") + help="How long, in days, the metadata is valid from " + "the time of creation") parser.add_argument('-c', dest='cert', help='certificate') parser.add_argument('-i', dest='id', help="The ID of the entities descriptor") diff --git a/example/idp2_repoze/idp.py b/example/idp2_repoze/idp.py index 0420d16..685fc0b 100755 --- a/example/idp2_repoze/idp.py +++ b/example/idp2_repoze/idp.py @@ -19,6 +19,7 @@ from saml2 import BINDING_SOAP from saml2 import BINDING_HTTP_REDIRECT from saml2 import BINDING_HTTP_POST from saml2 import time_util +from saml2.authn import is_equal from saml2.authn_context import AuthnBroker from saml2.authn_context import PASSWORD @@ -414,7 +415,7 @@ class SSO(Service): logger.debug("Authz_info: %s" % _info) try: (user, passwd) = _info.split(":") - if PASSWD[user] != passwd: + if is_equal(PASSWD[user], passwd): resp = Unauthorized() self.user = user except (ValueError, TypeError): diff --git a/src/saml2/authn.py b/src/saml2/authn.py index 8c6e218..804feee 100644 --- a/src/saml2/authn.py +++ b/src/saml2/authn.py @@ -39,6 +39,16 @@ class UserAuthnMethod(object): raise NotImplemented +def is_equal(a, b): + if len(a) != len(b): + return False + + result = 0 + for x, y in zip(a, b): + result |= x ^ y + return result == 0 + + def url_encode_params(params=None): if not isinstance(params, dict): raise EncodeError("You must pass in a dictionary!") @@ -137,7 +147,7 @@ class UsernamePasswordMako(UserAuthnMethod): return resp def _verify(self, pwd, user): - assert pwd == self.passwd[user] + assert is_equal(pwd, self.passwd[user]) def verify(self, request, **kwargs): """ @@ -149,7 +159,7 @@ class UsernamePasswordMako(UserAuthnMethod): wants the user after authentication. """ - logger.debug("verify(%s)" % request) + #logger.debug("verify(%s)" % request) if isinstance(request, basestring): _dict = parse_qs(request) elif isinstance(request, dict): @@ -157,8 +167,6 @@ class UsernamePasswordMako(UserAuthnMethod): else: raise ValueError("Wrong type of input") - logger.debug("dict: %s" % _dict) - logger.debug("passwd: %s" % self.passwd) # verify username and password try: self._verify(_dict["password"][0], _dict["login"][0])