diff --git a/.travis.yml b/.travis.yml index 9a7d9ea..b9fdd09 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,8 +3,8 @@ language: python sudo: false env: - - TOX_ENV=py27 - - TOX_ENV=py34 + - TOXENV=py27 + - TOXENV=py34 addons: apt: @@ -14,5 +14,8 @@ addons: services: - mongodb +install: + - pip install -U tox + script: - - ./setup.py test + - tox diff --git a/example/idp2/idp.py b/example/idp2/idp.py index b86e299..7471856 100755 --- a/example/idp2/idp.py +++ b/example/idp2/idp.py @@ -143,16 +143,19 @@ class Service(object): return resp(self.environ, self.start_response) else: kwargs = {} + try: - _encrypt_cert = encrypt_cert_from_item( + kwargs['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, **kwargs) except KeyError: - # Can live with no relay state - return self.do(saml_msg["SAMLRequest"], binding, - saml_msg["RelayState"], **kwargs) + pass + + try: + kwargs['relay_state'] = saml_msg['RelayState'] + except KeyError: + pass + + return self.do(saml_msg["SAMLRequest"], binding, **kwargs) def artifact_operation(self, saml_msg): if not saml_msg: diff --git a/example/idp2/idp_user.py b/example/idp2/idp_user.py index a4032aa..71e9bf9 100644 --- a/example/idp2/idp_user.py +++ b/example/idp2/idp_user.py @@ -68,7 +68,7 @@ USERS = { "ou": "IT", "initials": "P", #"schacHomeOrganization": "example.com", - "email": "roland@example.com", + "mail": "roland@example.com", "displayName": "P. Roland Hedberg", "labeledURL": "http://www.example.com/rohe My homepage", "norEduPersonNIN": "SE197001012222" diff --git a/example/sp-wsgi/sp.py b/example/sp-wsgi/sp.py index 278b108..1792ab7 100755 --- a/example/sp-wsgi/sp.py +++ b/example/sp-wsgi/sp.py @@ -38,6 +38,7 @@ from saml2.httputil import NotImplemented from saml2.response import StatusError from saml2.response import VerificationError from saml2.s_utils import UnknownPrincipal +from saml2.s_utils import decode_base64_and_inflate from saml2.s_utils import UnsupportedBinding from saml2.s_utils import sid from saml2.s_utils import rndstr @@ -634,8 +635,18 @@ class SLO(Service): self.sp = sp self.cache = cache - def do(self, response, binding, relay_state="", mtype="response"): - req_info = self.sp.parse_logout_request_response(response, binding) + def do(self, message, binding, relay_state="", mtype="response"): + try: + txt = decode_base64_and_inflate(message) + is_logout_request = 'LogoutRequest' in txt.split('>', 1)[0] + except: # TODO: parse the XML correctly + is_logout_request = False + + if is_logout_request: + self.sp.parse_logout_request(message, binding) + else: + self.sp.parse_logout_request_response(message, binding) + return finish_logout(self.environ, self.start_response) # ---------------------------------------------------------------------------- diff --git a/setup.py b/setup.py index 341592b..7315457 100755 --- a/setup.py +++ b/setup.py @@ -6,21 +6,6 @@ import sys from setuptools import setup from setuptools.command.test import test as TestCommand - -class PyTest(TestCommand): - - def finalize_options(self): - TestCommand.finalize_options(self) - self.test_args = [] - self.test_suite = True - - def run_tests(self): - #import here, cause outside the eggs aren't loaded - import pytest - errno = pytest.main(self.test_args) - sys.exit(errno) - - install_requires = [ # core dependencies 'decorator', @@ -35,18 +20,6 @@ install_requires = [ 'six' ] -tests_require = [ - 'mongodict', - 'pyasn1', - 'pymongo==3.0.1', - 'python-memcached >= 1.51', - 'pytest', - 'mako', - 'webob', - 'mock' - #'pytest-coverage', -] - version = '' with open('src/saml2/__init__.py', 'r') as fd: version = re.search(r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', @@ -79,13 +52,6 @@ setup( scripts=["tools/parse_xsd2.py", "tools/make_metadata.py", "tools/mdexport.py", "tools/merge_metadata.py"], - - tests_require=tests_require, - extras_require={ - 'testing': tests_require, - }, install_requires=install_requires, zip_safe=False, - test_suite='tests', - cmdclass={'test': PyTest}, ) diff --git a/src/saml2/__init__.py b/src/saml2/__init__.py index 4afd69c..16555e3 100644 --- a/src/saml2/__init__.py +++ b/src/saml2/__init__.py @@ -979,7 +979,7 @@ def extension_elements_to_elements(extension_elements, schemas): if isinstance(schemas, list): pass elif isinstance(schemas, dict): - schemas = schemas.values() + schemas = list(schemas.values()) else: return res diff --git a/src/saml2/attribute_converter.py b/src/saml2/attribute_converter.py index 5a1b371..4888db3 100644 --- a/src/saml2/attribute_converter.py +++ b/src/saml2/attribute_converter.py @@ -425,11 +425,19 @@ class AttributeConverter(object): :return: An Attribute instance """ try: + _attr = self._to[attr] + except KeyError: + try: + _attr = self._to[attr.lower()] + except: + _attr = '' + + if _attr: return factory(saml.Attribute, - name=self._to[attr], + name=_attr, name_format=self.name_format, friendly_name=attr) - except KeyError: + else: return factory(saml.Attribute, name=attr) def from_format(self, attr): diff --git a/src/saml2/client.py b/src/saml2/client.py index 42c6eb8..fca9859 100644 --- a/src/saml2/client.py +++ b/src/saml2/client.py @@ -56,6 +56,7 @@ class Saml2Client(Base): successfull log in. :param binding: Which binding to use for sending the request :param vorg: The entity_id of the virtual organization I'm a member of + :param nameid_format: :param scoping: For which IdPs this query are aimed. :param consent: Whether the principal have given her consent :param extensions: Possible extensions @@ -95,6 +96,7 @@ class Saml2Client(Base): successfull log in. :param binding: Which binding to use for sending the request :param vorg: The entity_id of the virtual organization I'm a member of + :param nameid_format: :param scoping: For which IdPs this query are aimed. :param consent: Whether the principal have given her consent :param extensions: Possible extensions diff --git a/src/saml2/client_base.py b/src/saml2/client_base.py index e4152a0..046aafa 100644 --- a/src/saml2/client_base.py +++ b/src/saml2/client_base.py @@ -155,6 +155,9 @@ class Base(Entity): except IndexError: raise IdpUnspecified("No IdP to send to given the premises") + def sso_location(self, entityid=None, binding=BINDING_HTTP_REDIRECT): + return self._sso_location(entityid, binding) + def _my_name(self): return self.config.name diff --git a/src/saml2/entity.py b/src/saml2/entity.py index 4b3bdca..b5fcb3d 100644 --- a/src/saml2/entity.py +++ b/src/saml2/entity.py @@ -215,10 +215,16 @@ class Entity(HTTPBase): if binding == BINDING_HTTP_POST: logger.info("HTTP POST") + # if self.entity_type == 'sp': + # info = self.use_http_post(msg_str, destination, relay_state, + # typ) + # info["url"] = destination + # info["method"] = "POST" + # else: info = self.use_http_form_post(msg_str, destination, relay_state, typ) info["url"] = destination - info["method"] = "GET" + info["method"] = "POST" elif binding == BINDING_HTTP_REDIRECT: logger.info("HTTP REDIRECT") info = self.use_http_get(msg_str, destination, relay_state, typ, diff --git a/src/saml2/entity_category/edugain.py b/src/saml2/entity_category/edugain.py index a1cd357..f15aaee 100644 --- a/src/saml2/entity_category/edugain.py +++ b/src/saml2/entity_category/edugain.py @@ -1,10 +1,14 @@ __author__ = 'rolandh' COC = "http://www.geant.net/uri/dataprotection-code-of-conduct/v1" +COCO = COC RELEASE = { "": ["eduPersonTargetedID"], - COC: ["eduPersonPrincipalName", "eduPersonScopedAffiliation", "mail", - "displayName", "schacHomeOrganization"] + # COC: ["eduPersonPrincipalName", "eduPersonScopedAffiliation", "mail", + # "displayName", "schacHomeOrganization"], + COCO: ["eduPersonPrincipalName", "eduPersonScopedAffiliation", + 'eduPersonAffiliation', "mail", "displayName", 'cn', + "schacHomeOrganization", 'schacHomeOrganizationType'] } diff --git a/src/saml2/entity_category/incommon.py b/src/saml2/entity_category/incommon.py index e1c1462..9df7730 100644 --- a/src/saml2/entity_category/incommon.py +++ b/src/saml2/entity_category/incommon.py @@ -8,4 +8,3 @@ RELEASE = { "eduPersonScopedAffiliation", "mail", "givenName", "sn", "displayName"] } - diff --git a/src/saml2/httpbase.py b/src/saml2/httpbase.py index 016e0ff..1dd49d1 100644 --- a/src/saml2/httpbase.py +++ b/src/saml2/httpbase.py @@ -11,6 +11,7 @@ from six.moves.http_cookies import SimpleCookie from saml2.time_util import utc_now from saml2 import class_name, SAMLError from saml2.pack import http_form_post_message +from saml2.pack import http_post_message from saml2.pack import make_soap_enveloped_saml_thingy from saml2.pack import http_redirect_message @@ -248,6 +249,23 @@ class HTTPBase(object): return r + @staticmethod + def use_http_post(message, destination, relay_state, + typ="SAMLRequest"): + """ + Return a urlencoded message that should be POSTed to the recipient. + + :param message: The response + :param destination: Where the response should be sent + :param relay_state: The relay_state received in the request + :param typ: Whether a Request, Response or Artifact + :return: dictionary + """ + if not isinstance(message, six.string_types): + message = "%s" % (message,) + + return http_post_message(message, relay_state, typ) + @staticmethod def use_http_form_post(message, destination, relay_state, typ="SAMLRequest"): diff --git a/src/saml2/httputil.py b/src/saml2/httputil.py index 360982d..607baed 100644 --- a/src/saml2/httputil.py +++ b/src/saml2/httputil.py @@ -17,7 +17,6 @@ from saml2 import time_util __author__ = 'rohe0002' - logger = logging.getLogger(__name__) @@ -73,8 +72,8 @@ class Created(Response): class Redirect(Response): _template = '\n