Merge remote-tracking branch 'upstream/master'
# Conflicts: # setup.py # src/saml2/server.py
This commit is contained in:
@@ -3,8 +3,8 @@ language: python
|
|||||||
sudo: false
|
sudo: false
|
||||||
|
|
||||||
env:
|
env:
|
||||||
- TOX_ENV=py27
|
- TOXENV=py27
|
||||||
- TOX_ENV=py34
|
- TOXENV=py34
|
||||||
|
|
||||||
addons:
|
addons:
|
||||||
apt:
|
apt:
|
||||||
@@ -14,5 +14,8 @@ addons:
|
|||||||
services:
|
services:
|
||||||
- mongodb
|
- mongodb
|
||||||
|
|
||||||
|
install:
|
||||||
|
- pip install -U tox
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- ./setup.py test
|
- tox
|
||||||
|
@@ -143,16 +143,19 @@ class Service(object):
|
|||||||
return resp(self.environ, self.start_response)
|
return resp(self.environ, self.start_response)
|
||||||
else:
|
else:
|
||||||
kwargs = {}
|
kwargs = {}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
_encrypt_cert = encrypt_cert_from_item(
|
kwargs['encrypt_cert'] = encrypt_cert_from_item(
|
||||||
saml_msg["req_info"].message)
|
saml_msg["req_info"].message)
|
||||||
return self.do(saml_msg["SAMLRequest"], binding,
|
|
||||||
saml_msg["RelayState"],
|
|
||||||
encrypt_cert=_encrypt_cert, **kwargs)
|
|
||||||
except KeyError:
|
except KeyError:
|
||||||
# Can live with no relay state
|
pass
|
||||||
return self.do(saml_msg["SAMLRequest"], binding,
|
|
||||||
saml_msg["RelayState"], **kwargs)
|
try:
|
||||||
|
kwargs['relay_state'] = saml_msg['RelayState']
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return self.do(saml_msg["SAMLRequest"], binding, **kwargs)
|
||||||
|
|
||||||
def artifact_operation(self, saml_msg):
|
def artifact_operation(self, saml_msg):
|
||||||
if not saml_msg:
|
if not saml_msg:
|
||||||
|
@@ -68,7 +68,7 @@ USERS = {
|
|||||||
"ou": "IT",
|
"ou": "IT",
|
||||||
"initials": "P",
|
"initials": "P",
|
||||||
#"schacHomeOrganization": "example.com",
|
#"schacHomeOrganization": "example.com",
|
||||||
"email": "roland@example.com",
|
"mail": "roland@example.com",
|
||||||
"displayName": "P. Roland Hedberg",
|
"displayName": "P. Roland Hedberg",
|
||||||
"labeledURL": "http://www.example.com/rohe My homepage",
|
"labeledURL": "http://www.example.com/rohe My homepage",
|
||||||
"norEduPersonNIN": "SE197001012222"
|
"norEduPersonNIN": "SE197001012222"
|
||||||
|
@@ -38,6 +38,7 @@ from saml2.httputil import NotImplemented
|
|||||||
from saml2.response import StatusError
|
from saml2.response import StatusError
|
||||||
from saml2.response import VerificationError
|
from saml2.response import VerificationError
|
||||||
from saml2.s_utils import UnknownPrincipal
|
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 UnsupportedBinding
|
||||||
from saml2.s_utils import sid
|
from saml2.s_utils import sid
|
||||||
from saml2.s_utils import rndstr
|
from saml2.s_utils import rndstr
|
||||||
@@ -634,8 +635,18 @@ class SLO(Service):
|
|||||||
self.sp = sp
|
self.sp = sp
|
||||||
self.cache = cache
|
self.cache = cache
|
||||||
|
|
||||||
def do(self, response, binding, relay_state="", mtype="response"):
|
def do(self, message, binding, relay_state="", mtype="response"):
|
||||||
req_info = self.sp.parse_logout_request_response(response, binding)
|
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)
|
return finish_logout(self.environ, self.start_response)
|
||||||
|
|
||||||
# ----------------------------------------------------------------------------
|
# ----------------------------------------------------------------------------
|
||||||
|
34
setup.py
34
setup.py
@@ -6,21 +6,6 @@ import sys
|
|||||||
from setuptools import setup
|
from setuptools import setup
|
||||||
from setuptools.command.test import test as TestCommand
|
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 = [
|
install_requires = [
|
||||||
# core dependencies
|
# core dependencies
|
||||||
'decorator',
|
'decorator',
|
||||||
@@ -35,18 +20,6 @@ install_requires = [
|
|||||||
'six'
|
'six'
|
||||||
]
|
]
|
||||||
|
|
||||||
tests_require = [
|
|
||||||
'mongodict',
|
|
||||||
'pyasn1',
|
|
||||||
'pymongo==3.0.1',
|
|
||||||
'python-memcached >= 1.51',
|
|
||||||
'pytest',
|
|
||||||
'mako',
|
|
||||||
'webob',
|
|
||||||
'mock'
|
|
||||||
#'pytest-coverage',
|
|
||||||
]
|
|
||||||
|
|
||||||
version = ''
|
version = ''
|
||||||
with open('src/saml2/__init__.py', 'r') as fd:
|
with open('src/saml2/__init__.py', 'r') as fd:
|
||||||
version = re.search(r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]',
|
version = re.search(r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]',
|
||||||
@@ -79,13 +52,6 @@ setup(
|
|||||||
|
|
||||||
scripts=["tools/parse_xsd2.py", "tools/make_metadata.py",
|
scripts=["tools/parse_xsd2.py", "tools/make_metadata.py",
|
||||||
"tools/mdexport.py", "tools/merge_metadata.py"],
|
"tools/mdexport.py", "tools/merge_metadata.py"],
|
||||||
|
|
||||||
tests_require=tests_require,
|
|
||||||
extras_require={
|
|
||||||
'testing': tests_require,
|
|
||||||
},
|
|
||||||
install_requires=install_requires,
|
install_requires=install_requires,
|
||||||
zip_safe=False,
|
zip_safe=False,
|
||||||
test_suite='tests',
|
|
||||||
cmdclass={'test': PyTest},
|
|
||||||
)
|
)
|
||||||
|
@@ -979,7 +979,7 @@ def extension_elements_to_elements(extension_elements, schemas):
|
|||||||
if isinstance(schemas, list):
|
if isinstance(schemas, list):
|
||||||
pass
|
pass
|
||||||
elif isinstance(schemas, dict):
|
elif isinstance(schemas, dict):
|
||||||
schemas = schemas.values()
|
schemas = list(schemas.values())
|
||||||
else:
|
else:
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
@@ -425,11 +425,19 @@ class AttributeConverter(object):
|
|||||||
:return: An Attribute instance
|
:return: An Attribute instance
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
|
_attr = self._to[attr]
|
||||||
|
except KeyError:
|
||||||
|
try:
|
||||||
|
_attr = self._to[attr.lower()]
|
||||||
|
except:
|
||||||
|
_attr = ''
|
||||||
|
|
||||||
|
if _attr:
|
||||||
return factory(saml.Attribute,
|
return factory(saml.Attribute,
|
||||||
name=self._to[attr],
|
name=_attr,
|
||||||
name_format=self.name_format,
|
name_format=self.name_format,
|
||||||
friendly_name=attr)
|
friendly_name=attr)
|
||||||
except KeyError:
|
else:
|
||||||
return factory(saml.Attribute, name=attr)
|
return factory(saml.Attribute, name=attr)
|
||||||
|
|
||||||
def from_format(self, attr):
|
def from_format(self, attr):
|
||||||
|
@@ -56,6 +56,7 @@ class Saml2Client(Base):
|
|||||||
successfull log in.
|
successfull log in.
|
||||||
:param binding: Which binding to use for sending the request
|
: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 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 scoping: For which IdPs this query are aimed.
|
||||||
:param consent: Whether the principal have given her consent
|
:param consent: Whether the principal have given her consent
|
||||||
:param extensions: Possible extensions
|
:param extensions: Possible extensions
|
||||||
@@ -95,6 +96,7 @@ class Saml2Client(Base):
|
|||||||
successfull log in.
|
successfull log in.
|
||||||
:param binding: Which binding to use for sending the request
|
: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 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 scoping: For which IdPs this query are aimed.
|
||||||
:param consent: Whether the principal have given her consent
|
:param consent: Whether the principal have given her consent
|
||||||
:param extensions: Possible extensions
|
:param extensions: Possible extensions
|
||||||
|
@@ -155,6 +155,9 @@ class Base(Entity):
|
|||||||
except IndexError:
|
except IndexError:
|
||||||
raise IdpUnspecified("No IdP to send to given the premises")
|
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):
|
def _my_name(self):
|
||||||
return self.config.name
|
return self.config.name
|
||||||
|
|
||||||
|
@@ -215,10 +215,16 @@ class Entity(HTTPBase):
|
|||||||
|
|
||||||
if binding == BINDING_HTTP_POST:
|
if binding == BINDING_HTTP_POST:
|
||||||
logger.info("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,
|
info = self.use_http_form_post(msg_str, destination,
|
||||||
relay_state, typ)
|
relay_state, typ)
|
||||||
info["url"] = destination
|
info["url"] = destination
|
||||||
info["method"] = "GET"
|
info["method"] = "POST"
|
||||||
elif binding == BINDING_HTTP_REDIRECT:
|
elif binding == BINDING_HTTP_REDIRECT:
|
||||||
logger.info("HTTP REDIRECT")
|
logger.info("HTTP REDIRECT")
|
||||||
info = self.use_http_get(msg_str, destination, relay_state, typ,
|
info = self.use_http_get(msg_str, destination, relay_state, typ,
|
||||||
|
@@ -1,10 +1,14 @@
|
|||||||
__author__ = 'rolandh'
|
__author__ = 'rolandh'
|
||||||
|
|
||||||
COC = "http://www.geant.net/uri/dataprotection-code-of-conduct/v1"
|
COC = "http://www.geant.net/uri/dataprotection-code-of-conduct/v1"
|
||||||
|
COCO = COC
|
||||||
|
|
||||||
RELEASE = {
|
RELEASE = {
|
||||||
"": ["eduPersonTargetedID"],
|
"": ["eduPersonTargetedID"],
|
||||||
COC: ["eduPersonPrincipalName", "eduPersonScopedAffiliation", "mail",
|
# COC: ["eduPersonPrincipalName", "eduPersonScopedAffiliation", "mail",
|
||||||
"displayName", "schacHomeOrganization"]
|
# "displayName", "schacHomeOrganization"],
|
||||||
|
COCO: ["eduPersonPrincipalName", "eduPersonScopedAffiliation",
|
||||||
|
'eduPersonAffiliation', "mail", "displayName", 'cn',
|
||||||
|
"schacHomeOrganization", 'schacHomeOrganizationType']
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -8,4 +8,3 @@ RELEASE = {
|
|||||||
"eduPersonScopedAffiliation", "mail",
|
"eduPersonScopedAffiliation", "mail",
|
||||||
"givenName", "sn", "displayName"]
|
"givenName", "sn", "displayName"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -11,6 +11,7 @@ from six.moves.http_cookies import SimpleCookie
|
|||||||
from saml2.time_util import utc_now
|
from saml2.time_util import utc_now
|
||||||
from saml2 import class_name, SAMLError
|
from saml2 import class_name, SAMLError
|
||||||
from saml2.pack import http_form_post_message
|
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 make_soap_enveloped_saml_thingy
|
||||||
from saml2.pack import http_redirect_message
|
from saml2.pack import http_redirect_message
|
||||||
|
|
||||||
@@ -248,6 +249,23 @@ class HTTPBase(object):
|
|||||||
|
|
||||||
return r
|
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
|
@staticmethod
|
||||||
def use_http_form_post(message, destination, relay_state,
|
def use_http_form_post(message, destination, relay_state,
|
||||||
typ="SAMLRequest"):
|
typ="SAMLRequest"):
|
||||||
|
@@ -17,7 +17,6 @@ from saml2 import time_util
|
|||||||
|
|
||||||
__author__ = 'rohe0002'
|
__author__ = 'rohe0002'
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@@ -73,8 +72,8 @@ class Created(Response):
|
|||||||
|
|
||||||
class Redirect(Response):
|
class Redirect(Response):
|
||||||
_template = '<html>\n<head><title>Redirecting to %s</title></head>\n' \
|
_template = '<html>\n<head><title>Redirecting to %s</title></head>\n' \
|
||||||
'<body>\nYou are being redirected to <a href="%s">%s</a>\n' \
|
'<body>\nYou are being redirected to <a href="%s">%s</a>\n' \
|
||||||
'</body>\n</html>'
|
'</body>\n</html>'
|
||||||
_status = '302 Found'
|
_status = '302 Found'
|
||||||
|
|
||||||
def __call__(self, environ, start_response, **kwargs):
|
def __call__(self, environ, start_response, **kwargs):
|
||||||
@@ -86,8 +85,8 @@ class Redirect(Response):
|
|||||||
|
|
||||||
class SeeOther(Response):
|
class SeeOther(Response):
|
||||||
_template = '<html>\n<head><title>Redirecting to %s</title></head>\n' \
|
_template = '<html>\n<head><title>Redirecting to %s</title></head>\n' \
|
||||||
'<body>\nYou are being redirected to <a href="%s">%s</a>\n' \
|
'<body>\nYou are being redirected to <a href="%s">%s</a>\n' \
|
||||||
'</body>\n</html>'
|
'</body>\n</html>'
|
||||||
_status = '303 See Other'
|
_status = '303 See Other'
|
||||||
|
|
||||||
def __call__(self, environ, start_response, **kwargs):
|
def __call__(self, environ, start_response, **kwargs):
|
||||||
@@ -156,6 +155,7 @@ class HttpParameters():
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def extract(environ, empty=False, err=False):
|
def extract(environ, empty=False, err=False):
|
||||||
"""Extracts strings in form data and returns a dict.
|
"""Extracts strings in form data and returns a dict.
|
||||||
|
|
||||||
@@ -266,7 +266,7 @@ def unpack_artifact(environ):
|
|||||||
|
|
||||||
def unpack_any(environ):
|
def unpack_any(environ):
|
||||||
if environ['REQUEST_METHOD'].upper() == 'GET':
|
if environ['REQUEST_METHOD'].upper() == 'GET':
|
||||||
# Could be either redirect or artifact
|
# Could be either redirect or artifact
|
||||||
_dict = unpack_redirect(environ)
|
_dict = unpack_redirect(environ)
|
||||||
if "ID" in _dict:
|
if "ID" in _dict:
|
||||||
binding = BINDING_URI
|
binding = BINDING_URI
|
||||||
@@ -307,7 +307,7 @@ def cookie_signature(seed, *parts):
|
|||||||
return sha1.hexdigest()
|
return sha1.hexdigest()
|
||||||
|
|
||||||
|
|
||||||
def make_cookie(name, load, seed, expire=0, domain="", path="",
|
def make_cookie(name, load, seed, expire=0, domain="", path="",
|
||||||
timestamp=""):
|
timestamp=""):
|
||||||
"""
|
"""
|
||||||
Create and return a cookie
|
Create and return a cookie
|
||||||
|
@@ -1,19 +1,20 @@
|
|||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
|
import hashlib
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import json
|
import json
|
||||||
import six
|
|
||||||
|
|
||||||
|
import requests
|
||||||
|
import six
|
||||||
from hashlib import sha1
|
from hashlib import sha1
|
||||||
from os.path import isfile, join
|
from os.path import isfile, join
|
||||||
from saml2.httpbase import HTTPBase
|
from saml2.httpbase import HTTPBase
|
||||||
from saml2.extension.idpdisc import BINDING_DISCO
|
from saml2.extension.idpdisc import BINDING_DISCO
|
||||||
from saml2.extension.idpdisc import DiscoveryResponse
|
from saml2.extension.idpdisc import DiscoveryResponse
|
||||||
from saml2.md import EntitiesDescriptor
|
from saml2.md import EntitiesDescriptor
|
||||||
|
|
||||||
from saml2.mdie import to_dict
|
from saml2.mdie import to_dict
|
||||||
|
|
||||||
from saml2 import md
|
from saml2 import md
|
||||||
from saml2 import samlp
|
from saml2 import samlp
|
||||||
from saml2 import SAMLError
|
from saml2 import SAMLError
|
||||||
@@ -60,13 +61,27 @@ REQ2SRV = {
|
|||||||
"discovery_service_request": "discovery_response"
|
"discovery_service_request": "discovery_response"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
ENTITYATTRIBUTES = "urn:oasis:names:tc:SAML:metadata:attribute&EntityAttributes"
|
ENTITYATTRIBUTES = "urn:oasis:names:tc:SAML:metadata:attribute&EntityAttributes"
|
||||||
ENTITY_CATEGORY = "http://macedir.org/entity-category"
|
ENTITY_CATEGORY = "http://macedir.org/entity-category"
|
||||||
ENTITY_CATEGORY_SUPPORT = "http://macedir.org/entity-category-support"
|
ENTITY_CATEGORY_SUPPORT = "http://macedir.org/entity-category-support"
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------
|
# ---------------------------------------------------
|
||||||
|
|
||||||
|
def load_extensions():
|
||||||
|
from saml2 import extension
|
||||||
|
import pkgutil
|
||||||
|
|
||||||
|
package = extension
|
||||||
|
prefix = package.__name__ + "."
|
||||||
|
ext_map = {}
|
||||||
|
for importer, modname, ispkg in pkgutil.iter_modules(package.__path__,
|
||||||
|
prefix):
|
||||||
|
module = __import__(modname, fromlist="dummy")
|
||||||
|
ext_map[module.NAMESPACE] = module
|
||||||
|
|
||||||
|
return ext_map
|
||||||
|
|
||||||
|
|
||||||
def destinations(srvs):
|
def destinations(srvs):
|
||||||
return [s["location"] for s in srvs]
|
return [s["location"] for s in srvs]
|
||||||
@@ -121,6 +136,7 @@ class MetaData(object):
|
|||||||
self.metadata = metadata
|
self.metadata = metadata
|
||||||
self.entity = None
|
self.entity = None
|
||||||
self.cert = None
|
self.cert = None
|
||||||
|
self.to_old = []
|
||||||
|
|
||||||
def items(self):
|
def items(self):
|
||||||
'''
|
'''
|
||||||
@@ -313,7 +329,43 @@ class MetaData(object):
|
|||||||
'''
|
'''
|
||||||
Returns certificates for the given Entity
|
Returns certificates for the given Entity
|
||||||
'''
|
'''
|
||||||
raise NotImplementedError
|
ent = self[entity_id]
|
||||||
|
|
||||||
|
def extract_certs(srvs):
|
||||||
|
res = []
|
||||||
|
for srv in srvs:
|
||||||
|
if "key_descriptor" in srv:
|
||||||
|
for key in srv["key_descriptor"]:
|
||||||
|
if "use" in key and key["use"] == use:
|
||||||
|
for dat in key["key_info"]["x509_data"]:
|
||||||
|
cert = repack_cert(
|
||||||
|
dat["x509_certificate"]["text"])
|
||||||
|
if cert not in res:
|
||||||
|
res.append(cert)
|
||||||
|
elif not "use" in key:
|
||||||
|
for dat in key["key_info"]["x509_data"]:
|
||||||
|
cert = repack_cert(
|
||||||
|
dat["x509_certificate"]["text"])
|
||||||
|
if cert not in res:
|
||||||
|
res.append(cert)
|
||||||
|
|
||||||
|
return res
|
||||||
|
|
||||||
|
if descriptor == "any":
|
||||||
|
res = []
|
||||||
|
for descr in ["spsso", "idpsso", "role", "authn_authority",
|
||||||
|
"attribute_authority", "pdp"]:
|
||||||
|
try:
|
||||||
|
srvs = ent["%s_descriptor" % descr]
|
||||||
|
except KeyError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
res.extend(extract_certs(srvs))
|
||||||
|
else:
|
||||||
|
srvs = ent["%s_descriptor" % descriptor]
|
||||||
|
res = extract_certs(srvs)
|
||||||
|
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
class InMemoryMetaData(MetaData):
|
class InMemoryMetaData(MetaData):
|
||||||
@@ -360,7 +412,8 @@ class InMemoryMetaData(MetaData):
|
|||||||
try:
|
try:
|
||||||
if not valid(entity_descr.valid_until):
|
if not valid(entity_descr.valid_until):
|
||||||
logger.error("Entity descriptor (entity id:%s) to old",
|
logger.error("Entity descriptor (entity id:%s) to old",
|
||||||
entity_descr.entity_id)
|
entity_descr.entity_id)
|
||||||
|
self.to_old.append(entity_descr.entity_id)
|
||||||
return
|
return
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
@@ -423,7 +476,8 @@ class InMemoryMetaData(MetaData):
|
|||||||
try:
|
try:
|
||||||
if not valid(self.entities_descr.valid_until):
|
if not valid(self.entities_descr.valid_until):
|
||||||
raise ToOld(
|
raise ToOld(
|
||||||
"Metadata not valid anymore, it's only valid until %s" % (
|
"Metadata not valid anymore, it's only valid "
|
||||||
|
"until %s" % (
|
||||||
self.entities_descr.valid_until,))
|
self.entities_descr.valid_until,))
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
@@ -508,45 +562,6 @@ class InMemoryMetaData(MetaData):
|
|||||||
|
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def certs(self, entity_id, descriptor, use="signing"):
|
|
||||||
ent = self.__getitem__(entity_id)
|
|
||||||
if descriptor == "any":
|
|
||||||
res = []
|
|
||||||
for descr in ["spsso", "idpsso", "role", "authn_authority",
|
|
||||||
"attribute_authority", "pdp"]:
|
|
||||||
try:
|
|
||||||
srvs = ent["%s_descriptor" % descr]
|
|
||||||
except KeyError:
|
|
||||||
continue
|
|
||||||
|
|
||||||
for srv in srvs:
|
|
||||||
for key in srv["key_descriptor"]:
|
|
||||||
if "use" in key and key["use"] == use:
|
|
||||||
for dat in key["key_info"]["x509_data"]:
|
|
||||||
cert = repack_cert(
|
|
||||||
dat["x509_certificate"]["text"])
|
|
||||||
if cert not in res:
|
|
||||||
res.append(cert)
|
|
||||||
elif not "use" in key:
|
|
||||||
for dat in key["key_info"]["x509_data"]:
|
|
||||||
cert = repack_cert(
|
|
||||||
dat["x509_certificate"]["text"])
|
|
||||||
if cert not in res:
|
|
||||||
res.append(cert)
|
|
||||||
else:
|
|
||||||
srvs = ent["%s_descriptor" % descriptor]
|
|
||||||
|
|
||||||
res = []
|
|
||||||
for srv in srvs:
|
|
||||||
for key in srv["key_descriptor"]:
|
|
||||||
if "use" in key and key["use"] == use:
|
|
||||||
for dat in key["key_info"]["x509_data"]:
|
|
||||||
res.append(dat["x509_certificate"]["text"])
|
|
||||||
elif not "use" in key:
|
|
||||||
for dat in key["key_info"]["x509_data"]:
|
|
||||||
res.append(dat["x509_certificate"]["text"])
|
|
||||||
return res
|
|
||||||
|
|
||||||
def signed(self):
|
def signed(self):
|
||||||
if self.entities_descr and self.entities_descr.signature:
|
if self.entities_descr and self.entities_descr.signature:
|
||||||
return True
|
return True
|
||||||
@@ -564,8 +579,8 @@ class InMemoryMetaData(MetaData):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
node_name = self.node_name \
|
node_name = self.node_name \
|
||||||
or "%s:%s" % (md.EntitiesDescriptor.c_namespace,
|
or "%s:%s" % (md.EntitiesDescriptor.c_namespace,
|
||||||
md.EntitiesDescriptor.c_tag)
|
md.EntitiesDescriptor.c_tag)
|
||||||
|
|
||||||
if self.security.verify_signature(
|
if self.security.verify_signature(
|
||||||
txt, node_name=node_name, cert_file=self.cert):
|
txt, node_name=node_name, cert_file=self.cert):
|
||||||
@@ -581,6 +596,7 @@ class MetaDataFile(InMemoryMetaData):
|
|||||||
Handles Metadata file on the same machine. The format of the file is
|
Handles Metadata file on the same machine. The format of the file is
|
||||||
the SAML Metadata format.
|
the SAML Metadata format.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, onts, attrc, filename=None, cert=None, **kwargs):
|
def __init__(self, onts, attrc, filename=None, cert=None, **kwargs):
|
||||||
super(MetaDataFile, self).__init__(onts, attrc, **kwargs)
|
super(MetaDataFile, self).__init__(onts, attrc, **kwargs)
|
||||||
if not filename:
|
if not filename:
|
||||||
@@ -601,6 +617,7 @@ class MetaDataLoader(MetaDataFile):
|
|||||||
Handles Metadata file loaded by a passed in function.
|
Handles Metadata file loaded by a passed in function.
|
||||||
The format of the file is the SAML Metadata format.
|
The format of the file is the SAML Metadata format.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, onts, attrc, loader_callable, cert=None,
|
def __init__(self, onts, attrc, loader_callable, cert=None,
|
||||||
security=None, **kwargs):
|
security=None, **kwargs):
|
||||||
super(MetaDataLoader, self).__init__(onts, attrc, **kwargs)
|
super(MetaDataLoader, self).__init__(onts, attrc, **kwargs)
|
||||||
@@ -686,6 +703,7 @@ class MetaDataMD(InMemoryMetaData):
|
|||||||
Handles locally stored metadata, the file format is the text representation
|
Handles locally stored metadata, the file format is the text representation
|
||||||
of the Python representation of the metadata.
|
of the Python representation of the metadata.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, onts, attrc, filename, **kwargs):
|
def __init__(self, onts, attrc, filename, **kwargs):
|
||||||
super(MetaDataMD, self).__init__(onts, attrc, **kwargs)
|
super(MetaDataMD, self).__init__(onts, attrc, **kwargs)
|
||||||
self.filename = filename
|
self.filename = filename
|
||||||
@@ -701,27 +719,32 @@ SAML_METADATA_CONTENT_TYPE = 'application/samlmetadata+xml'
|
|||||||
class MetaDataMDX(InMemoryMetaData):
|
class MetaDataMDX(InMemoryMetaData):
|
||||||
""" Uses the md protocol to fetch entity information
|
""" Uses the md protocol to fetch entity information
|
||||||
"""
|
"""
|
||||||
def __init__(self, entity_transform, onts, attrc, url, security, cert,
|
|
||||||
http, **kwargs):
|
@staticmethod
|
||||||
|
def sha1_entity_transform(entity_id):
|
||||||
|
return "{{sha1}}{}".format(
|
||||||
|
hashlib.sha1(entity_id.encode("utf-8")).hexdigest())
|
||||||
|
|
||||||
|
def __init__(self, url, entity_transform=None):
|
||||||
"""
|
"""
|
||||||
:params entity_transform: function transforming (e.g. base64 or sha1
|
:params url: mdx service url
|
||||||
|
:params entity_transform: function transforming (e.g. base64,
|
||||||
|
sha1 hash or URL quote
|
||||||
hash) the entity id. It is applied to the entity id before it is
|
hash) the entity id. It is applied to the entity id before it is
|
||||||
concatenated with the request URL sent to the MDX server.
|
concatenated with the request URL sent to the MDX server. Defaults to
|
||||||
:params onts:
|
sha1 transformation.
|
||||||
:params attrc:
|
|
||||||
:params url:
|
|
||||||
:params security: SecurityContext()
|
|
||||||
:params cert:
|
|
||||||
:params http:
|
|
||||||
"""
|
"""
|
||||||
super(MetaDataMDX, self).__init__(onts, attrc, **kwargs)
|
super(MetaDataMDX, self).__init__(None, None)
|
||||||
self.url = url
|
self.url = url
|
||||||
self.security = security
|
|
||||||
self.cert = cert
|
if entity_transform:
|
||||||
self.http = http
|
self.entity_transform = entity_transform
|
||||||
self.entity_transform = entity_transform
|
else:
|
||||||
|
|
||||||
|
self.entity_transform = MetaDataMDX.sha1_entity_transform
|
||||||
|
|
||||||
def load(self):
|
def load(self):
|
||||||
|
# Do nothing
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def __getitem__(self, item):
|
def __getitem__(self, item):
|
||||||
@@ -729,13 +752,9 @@ class MetaDataMDX(InMemoryMetaData):
|
|||||||
return self.entity[item]
|
return self.entity[item]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
mdx_url = "%s/entities/%s" % (self.url, self.entity_transform(item))
|
mdx_url = "%s/entities/%s" % (self.url, self.entity_transform(item))
|
||||||
response = self.http.send(
|
response = requests.get(mdx_url, headers={
|
||||||
mdx_url, headers={'Accept': SAML_METADATA_CONTENT_TYPE})
|
'Accept': SAML_METADATA_CONTENT_TYPE})
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
node_name = self.node_name \
|
|
||||||
or "%s:%s" % (md.EntitiesDescriptor.c_namespace,
|
|
||||||
md.EntitiesDescriptor.c_tag)
|
|
||||||
|
|
||||||
_txt = response.text.encode("utf-8")
|
_txt = response.text.encode("utf-8")
|
||||||
|
|
||||||
if self.parse_and_check_signature(_txt):
|
if self.parse_and_check_signature(_txt):
|
||||||
@@ -744,8 +763,14 @@ class MetaDataMDX(InMemoryMetaData):
|
|||||||
logger.info("Response status: %s", response.status_code)
|
logger.info("Response status: %s", response.status_code)
|
||||||
raise KeyError
|
raise KeyError
|
||||||
|
|
||||||
|
def single_sign_on_service(self, entity_id, binding=None, typ="idpsso"):
|
||||||
|
if binding is None:
|
||||||
|
binding = BINDING_HTTP_REDIRECT
|
||||||
|
return self.service(entity_id, "idpsso_descriptor",
|
||||||
|
"single_sign_on_service", binding)
|
||||||
|
|
||||||
class MetadataStore(object):
|
|
||||||
|
class MetadataStore(MetaData):
|
||||||
def __init__(self, onts, attrc, config, ca_certs=None,
|
def __init__(self, onts, attrc, config, ca_certs=None,
|
||||||
check_validity=True,
|
check_validity=True,
|
||||||
disable_ssl_certificate_validation=False,
|
disable_ssl_certificate_validation=False,
|
||||||
@@ -770,6 +795,7 @@ class MetadataStore(object):
|
|||||||
self.metadata = {}
|
self.metadata = {}
|
||||||
self.check_validity = check_validity
|
self.check_validity = check_validity
|
||||||
self.filter = filter
|
self.filter = filter
|
||||||
|
self.to_old = {}
|
||||||
|
|
||||||
def load(self, typ, *args, **kwargs):
|
def load(self, typ, *args, **kwargs):
|
||||||
if self.filter:
|
if self.filter:
|
||||||
@@ -861,12 +887,15 @@ class MetadataStore(object):
|
|||||||
for key in item['metadata']:
|
for key in item['metadata']:
|
||||||
# Separately handle MetaDataFile and directory
|
# Separately handle MetaDataFile and directory
|
||||||
if MDloader == MetaDataFile and os.path.isdir(key[0]):
|
if MDloader == MetaDataFile and os.path.isdir(key[0]):
|
||||||
files = [f for f in os.listdir(key[0]) if isfile(join(key[0], f))]
|
files = [f for f in os.listdir(key[0]) if
|
||||||
|
isfile(join(key[0], f))]
|
||||||
for fil in files:
|
for fil in files:
|
||||||
_fil = join(key[0], fil)
|
_fil = join(key[0], fil)
|
||||||
_md = MetaDataFile(self.onts, self.attrc, _fil)
|
_md = MetaDataFile(self.onts, self.attrc, _fil)
|
||||||
_md.load()
|
_md.load()
|
||||||
self.metadata[_fil] = _md
|
self.metadata[_fil] = _md
|
||||||
|
if _md.to_old:
|
||||||
|
self.to_old[_fil] = _md.to_old
|
||||||
return
|
return
|
||||||
|
|
||||||
if len(key) == 2:
|
if len(key) == 2:
|
||||||
@@ -875,11 +904,13 @@ class MetadataStore(object):
|
|||||||
_md = MDloader(self.onts, self.attrc, key[0], **kwargs)
|
_md = MDloader(self.onts, self.attrc, key[0], **kwargs)
|
||||||
_md.load()
|
_md.load()
|
||||||
self.metadata[key[0]] = _md
|
self.metadata[key[0]] = _md
|
||||||
|
if _md.to_old:
|
||||||
|
self.to_old[key[0]] = _md.to_old
|
||||||
|
|
||||||
def service(self, entity_id, typ, service, binding=None):
|
def service(self, entity_id, typ, service, binding=None):
|
||||||
known_entity = False
|
known_entity = False
|
||||||
logger.debug("service(%s, %s, %s, %s)", entity_id, typ, service,
|
logger.debug("service(%s, %s, %s, %s)", entity_id, typ, service,
|
||||||
binding)
|
binding)
|
||||||
for key, _md in self.metadata.items():
|
for key, _md in self.metadata.items():
|
||||||
srvs = _md.service(entity_id, typ, service, binding)
|
srvs = _md.service(entity_id, typ, service, binding)
|
||||||
if srvs:
|
if srvs:
|
||||||
@@ -1049,45 +1080,6 @@ class MetadataStore(object):
|
|||||||
return name(_md[entity_id], langpref)
|
return name(_md[entity_id], langpref)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def certs(self, entity_id, descriptor, use="signing"):
|
|
||||||
ent = self.__getitem__(entity_id)
|
|
||||||
if descriptor == "any":
|
|
||||||
res = []
|
|
||||||
for descr in ["spsso", "idpsso", "role", "authn_authority",
|
|
||||||
"attribute_authority", "pdp"]:
|
|
||||||
try:
|
|
||||||
srvs = ent["%s_descriptor" % descr]
|
|
||||||
except KeyError:
|
|
||||||
continue
|
|
||||||
|
|
||||||
for srv in srvs:
|
|
||||||
for key in srv["key_descriptor"]:
|
|
||||||
if "use" in key and key["use"] == use:
|
|
||||||
for dat in key["key_info"]["x509_data"]:
|
|
||||||
cert = repack_cert(
|
|
||||||
dat["x509_certificate"]["text"])
|
|
||||||
if cert not in res:
|
|
||||||
res.append(cert)
|
|
||||||
elif not "use" in key:
|
|
||||||
for dat in key["key_info"]["x509_data"]:
|
|
||||||
cert = repack_cert(
|
|
||||||
dat["x509_certificate"]["text"])
|
|
||||||
if cert not in res:
|
|
||||||
res.append(cert)
|
|
||||||
else:
|
|
||||||
srvs = ent["%s_descriptor" % descriptor]
|
|
||||||
|
|
||||||
res = []
|
|
||||||
for srv in srvs:
|
|
||||||
for key in srv["key_descriptor"]:
|
|
||||||
if "use" in key and key["use"] == use:
|
|
||||||
for dat in key["key_info"]["x509_data"]:
|
|
||||||
res.append(dat["x509_certificate"]["text"])
|
|
||||||
elif not "use" in key:
|
|
||||||
for dat in key["key_info"]["x509_data"]:
|
|
||||||
res.append(dat["x509_certificate"]["text"])
|
|
||||||
return res
|
|
||||||
|
|
||||||
def vo_members(self, entity_id):
|
def vo_members(self, entity_id):
|
||||||
ad = self.__getitem__(entity_id)["affiliation_descriptor"]
|
ad = self.__getitem__(entity_id)["affiliation_descriptor"]
|
||||||
return [m["text"] for m in ad["affiliate_member"]]
|
return [m["text"] for m in ad["affiliate_member"]]
|
||||||
@@ -1179,7 +1171,7 @@ class MetadataStore(object):
|
|||||||
for ent_id, ent_desc in _md.items():
|
for ent_id, ent_desc in _md.items():
|
||||||
if descriptor in ent_desc:
|
if descriptor in ent_desc:
|
||||||
if ent_id in res:
|
if ent_id in res:
|
||||||
#print("duplicated entity_id: %s" % res)
|
# print("duplicated entity_id: %s" % res)
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
res.append(ent_id)
|
res.append(ent_id)
|
||||||
@@ -1214,4 +1206,3 @@ class MetadataStore(object):
|
|||||||
return "%s" % res
|
return "%s" % res
|
||||||
elif format == "md":
|
elif format == "md":
|
||||||
return json.dumps(self.items(), indent=2)
|
return json.dumps(self.items(), indent=2)
|
||||||
|
|
||||||
|
@@ -79,6 +79,32 @@ def http_form_post_message(message, location, relay_state="",
|
|||||||
return {"headers": [("Content-type", "text/html")], "data": response}
|
return {"headers": [("Content-type", "text/html")], "data": response}
|
||||||
|
|
||||||
|
|
||||||
|
def http_post_message(message, relay_state="", typ="SAMLRequest", **kwargs):
|
||||||
|
"""
|
||||||
|
|
||||||
|
:param message: The message
|
||||||
|
:param relay_state: for preserving and conveying state information
|
||||||
|
:return: A tuple containing header information and a HTML message.
|
||||||
|
"""
|
||||||
|
if not isinstance(message, six.string_types):
|
||||||
|
message = str(message)
|
||||||
|
if not isinstance(message, six.binary_type):
|
||||||
|
message = message.encode('utf-8')
|
||||||
|
|
||||||
|
if typ == "SAMLRequest" or typ == "SAMLResponse":
|
||||||
|
_msg = base64.b64encode(message)
|
||||||
|
else:
|
||||||
|
_msg = message
|
||||||
|
_msg = _msg.decode('ascii')
|
||||||
|
|
||||||
|
part = {typ: _msg}
|
||||||
|
if relay_state:
|
||||||
|
part["RelayState"] = relay_state
|
||||||
|
|
||||||
|
return {"headers": [("Content-type", 'application/x-www-form-urlencoded')],
|
||||||
|
"data": urlencode(part)}
|
||||||
|
|
||||||
|
|
||||||
def http_redirect_message(message, location, relay_state="", typ="SAMLRequest",
|
def http_redirect_message(message, location, relay_state="", typ="SAMLRequest",
|
||||||
sigalg=None, key=None, **kwargs):
|
sigalg=None, key=None, **kwargs):
|
||||||
"""The HTTP Redirect binding defines a mechanism by which SAML protocol
|
"""The HTTP Redirect binding defines a mechanism by which SAML protocol
|
||||||
|
@@ -58,6 +58,7 @@ from saml2.validate import NotValid
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
@@ -160,9 +161,11 @@ class StatusUnknownPrincipal(StatusError):
|
|||||||
class StatusUnsupportedBinding(StatusError):
|
class StatusUnsupportedBinding(StatusError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class StatusResponder(StatusError):
|
class StatusResponder(StatusError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
STATUSCODE2EXCEPTION = {
|
STATUSCODE2EXCEPTION = {
|
||||||
STATUS_VERSION_MISMATCH: StatusVersionMismatch,
|
STATUS_VERSION_MISMATCH: StatusVersionMismatch,
|
||||||
STATUS_AUTHN_FAILED: StatusAuthnFailed,
|
STATUS_AUTHN_FAILED: StatusAuthnFailed,
|
||||||
@@ -186,6 +189,8 @@ STATUSCODE2EXCEPTION = {
|
|||||||
STATUS_UNSUPPORTED_BINDING: StatusUnsupportedBinding,
|
STATUS_UNSUPPORTED_BINDING: StatusUnsupportedBinding,
|
||||||
STATUS_RESPONDER: StatusResponder,
|
STATUS_RESPONDER: StatusResponder,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
@@ -206,7 +211,8 @@ def for_me(conditions, myself):
|
|||||||
if audience.text.strip() == myself:
|
if audience.text.strip() == myself:
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
#print("Not for me: %s != %s" % (audience.text.strip(), myself))
|
# print("Not for me: %s != %s" % (audience.text.strip(),
|
||||||
|
# myself))
|
||||||
pass
|
pass
|
||||||
|
|
||||||
return False
|
return False
|
||||||
@@ -336,7 +342,7 @@ class StatusResponse(object):
|
|||||||
logger.exception("EXCEPTION: %s", excp)
|
logger.exception("EXCEPTION: %s", excp)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
#print("<", self.response)
|
# print("<", self.response)
|
||||||
|
|
||||||
return self._postamble()
|
return self._postamble()
|
||||||
|
|
||||||
@@ -377,7 +383,7 @@ class StatusResponse(object):
|
|||||||
if self.request_id and self.in_response_to and \
|
if self.request_id and self.in_response_to and \
|
||||||
self.in_response_to != self.request_id:
|
self.in_response_to != self.request_id:
|
||||||
logger.error("Not the id I expected: %s != %s",
|
logger.error("Not the id I expected: %s != %s",
|
||||||
self.in_response_to, self.request_id)
|
self.in_response_to, self.request_id)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -391,9 +397,9 @@ class StatusResponse(object):
|
|||||||
|
|
||||||
if self.asynchop:
|
if self.asynchop:
|
||||||
if self.response.destination and \
|
if self.response.destination and \
|
||||||
self.response.destination not in self.return_addrs:
|
self.response.destination not in self.return_addrs:
|
||||||
logger.error("%s not in %s", self.response.destination,
|
logger.error("%s not in %s", self.response.destination,
|
||||||
self.return_addrs)
|
self.return_addrs)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
assert self.issue_instant_ok()
|
assert self.issue_instant_ok()
|
||||||
@@ -436,7 +442,7 @@ class NameIDMappingResponse(StatusResponse):
|
|||||||
request_id=0, asynchop=True):
|
request_id=0, asynchop=True):
|
||||||
StatusResponse.__init__(self, sec_context, return_addrs, timeslack,
|
StatusResponse.__init__(self, sec_context, return_addrs, timeslack,
|
||||||
request_id, asynchop)
|
request_id, asynchop)
|
||||||
self.signature_check = self.sec\
|
self.signature_check = self.sec \
|
||||||
.correctly_signed_name_id_mapping_response
|
.correctly_signed_name_id_mapping_response
|
||||||
|
|
||||||
|
|
||||||
@@ -506,7 +512,7 @@ class AuthnResponse(StatusResponse):
|
|||||||
if self.asynchop:
|
if self.asynchop:
|
||||||
if self.in_response_to in self.outstanding_queries:
|
if self.in_response_to in self.outstanding_queries:
|
||||||
self.came_from = self.outstanding_queries[self.in_response_to]
|
self.came_from = self.outstanding_queries[self.in_response_to]
|
||||||
#del self.outstanding_queries[self.in_response_to]
|
# del self.outstanding_queries[self.in_response_to]
|
||||||
try:
|
try:
|
||||||
if not self.check_subject_confirmation_in_response_to(
|
if not self.check_subject_confirmation_in_response_to(
|
||||||
self.in_response_to):
|
self.in_response_to):
|
||||||
@@ -632,12 +638,12 @@ class AuthnResponse(StatusResponse):
|
|||||||
|
|
||||||
def read_attribute_statement(self, attr_statem):
|
def read_attribute_statement(self, attr_statem):
|
||||||
logger.debug("Attribute Statement: %s", attr_statem)
|
logger.debug("Attribute Statement: %s", attr_statem)
|
||||||
for aconv in self.attribute_converters:
|
# for aconv in self.attribute_converters:
|
||||||
logger.debug("Converts name format: %s", aconv.name_format)
|
# logger.debug("Converts name format: %s", aconv.name_format)
|
||||||
|
|
||||||
self.decrypt_attributes(attr_statem)
|
self.decrypt_attributes(attr_statem)
|
||||||
return to_local(self.attribute_converters, attr_statem,
|
return to_local(self.attribute_converters, attr_statem,
|
||||||
self.allow_unknown_attributes)
|
self.allow_unknown_attributes)
|
||||||
|
|
||||||
def get_identity(self):
|
def get_identity(self):
|
||||||
""" The assertion can contain zero or one attributeStatements
|
""" The assertion can contain zero or one attributeStatements
|
||||||
@@ -650,7 +656,8 @@ class AuthnResponse(StatusResponse):
|
|||||||
for tmp_assertion in _assertion.advice.assertion:
|
for tmp_assertion in _assertion.advice.assertion:
|
||||||
if tmp_assertion.attribute_statement:
|
if tmp_assertion.attribute_statement:
|
||||||
assert len(tmp_assertion.attribute_statement) == 1
|
assert len(tmp_assertion.attribute_statement) == 1
|
||||||
ava.update(self.read_attribute_statement(tmp_assertion.attribute_statement[0]))
|
ava.update(self.read_attribute_statement(
|
||||||
|
tmp_assertion.attribute_statement[0]))
|
||||||
if _assertion.attribute_statement:
|
if _assertion.attribute_statement:
|
||||||
assert len(_assertion.attribute_statement) == 1
|
assert len(_assertion.attribute_statement) == 1
|
||||||
_attr_statem = _assertion.attribute_statement[0]
|
_attr_statem = _assertion.attribute_statement[0]
|
||||||
@@ -681,7 +688,7 @@ class AuthnResponse(StatusResponse):
|
|||||||
if data.in_response_to in self.outstanding_queries:
|
if data.in_response_to in self.outstanding_queries:
|
||||||
self.came_from = self.outstanding_queries[
|
self.came_from = self.outstanding_queries[
|
||||||
data.in_response_to]
|
data.in_response_to]
|
||||||
#del self.outstanding_queries[data.in_response_to]
|
# del self.outstanding_queries[data.in_response_to]
|
||||||
elif self.allow_unsolicited:
|
elif self.allow_unsolicited:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
@@ -690,7 +697,7 @@ class AuthnResponse(StatusResponse):
|
|||||||
# recognize
|
# recognize
|
||||||
logger.debug("in response to: '%s'", data.in_response_to)
|
logger.debug("in response to: '%s'", data.in_response_to)
|
||||||
logger.info("outstanding queries: %s",
|
logger.info("outstanding queries: %s",
|
||||||
self.outstanding_queries.keys())
|
self.outstanding_queries.keys())
|
||||||
raise Exception(
|
raise Exception(
|
||||||
"Combination of session id and requestURI I don't "
|
"Combination of session id and requestURI I don't "
|
||||||
"recall")
|
"recall")
|
||||||
@@ -768,7 +775,8 @@ class AuthnResponse(StatusResponse):
|
|||||||
logger.debug("signed")
|
logger.debug("signed")
|
||||||
if not verified and self.do_not_verify is False:
|
if not verified and self.do_not_verify is False:
|
||||||
try:
|
try:
|
||||||
self.sec.check_signature(assertion, class_name(assertion),self.xmlstr)
|
self.sec.check_signature(assertion, class_name(assertion),
|
||||||
|
self.xmlstr)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.error("correctly_signed_response: %s", exc)
|
logger.error("correctly_signed_response: %s", exc)
|
||||||
raise
|
raise
|
||||||
@@ -778,10 +786,10 @@ class AuthnResponse(StatusResponse):
|
|||||||
logger.debug("assertion keys: %s", assertion.keyswv())
|
logger.debug("assertion keys: %s", assertion.keyswv())
|
||||||
logger.debug("outstanding_queries: %s", self.outstanding_queries)
|
logger.debug("outstanding_queries: %s", self.outstanding_queries)
|
||||||
|
|
||||||
#if self.context == "AuthnReq" or self.context == "AttrQuery":
|
# if self.context == "AuthnReq" or self.context == "AttrQuery":
|
||||||
if self.context == "AuthnReq":
|
if self.context == "AuthnReq":
|
||||||
self.authn_statement_ok()
|
self.authn_statement_ok()
|
||||||
# elif self.context == "AttrQuery":
|
# elif self.context == "AttrQuery":
|
||||||
# self.authn_statement_ok(True)
|
# self.authn_statement_ok(True)
|
||||||
|
|
||||||
if not self.condition_ok():
|
if not self.condition_ok():
|
||||||
@@ -789,7 +797,7 @@ class AuthnResponse(StatusResponse):
|
|||||||
|
|
||||||
logger.debug("--- Getting Identity ---")
|
logger.debug("--- Getting Identity ---")
|
||||||
|
|
||||||
#if self.context == "AuthnReq" or self.context == "AttrQuery":
|
# if self.context == "AuthnReq" or self.context == "AttrQuery":
|
||||||
# self.ava = self.get_identity()
|
# self.ava = self.get_identity()
|
||||||
# logger.debug("--- AVA: %s", self.ava)
|
# logger.debug("--- AVA: %s", self.ava)
|
||||||
|
|
||||||
@@ -805,13 +813,17 @@ class AuthnResponse(StatusResponse):
|
|||||||
logger.exception("get subject")
|
logger.exception("get subject")
|
||||||
raise
|
raise
|
||||||
|
|
||||||
def decrypt_assertions(self, encrypted_assertions, decr_txt, issuer=None, verified=False):
|
def decrypt_assertions(self, encrypted_assertions, decr_txt, issuer=None,
|
||||||
""" Moves the decrypted assertion from the encrypted assertion to a list.
|
verified=False):
|
||||||
|
""" Moves the decrypted assertion from the encrypted assertion to a
|
||||||
|
list.
|
||||||
|
|
||||||
:param encrypted_assertions: A list of encrypted assertions.
|
:param encrypted_assertions: A list of encrypted assertions.
|
||||||
:param decr_txt: The string representation containing the decrypted data. Used when verifying signatures.
|
:param decr_txt: The string representation containing the decrypted
|
||||||
|
data. Used when verifying signatures.
|
||||||
:param issuer: The issuer of the response.
|
:param issuer: The issuer of the response.
|
||||||
:param verified: If True do not verify signatures, otherwise verify the signature if it exists.
|
:param verified: If True do not verify signatures, otherwise verify
|
||||||
|
the signature if it exists.
|
||||||
:return: A list of decrypted assertions.
|
:return: A list of decrypted assertions.
|
||||||
"""
|
"""
|
||||||
res = []
|
res = []
|
||||||
@@ -824,7 +836,8 @@ class AuthnResponse(StatusResponse):
|
|||||||
if not self.sec.check_signature(
|
if not self.sec.check_signature(
|
||||||
assertion, origdoc=decr_txt,
|
assertion, origdoc=decr_txt,
|
||||||
node_name=class_name(assertion), issuer=issuer):
|
node_name=class_name(assertion), issuer=issuer):
|
||||||
logger.error("Failed to verify signature on '%s'", assertion)
|
logger.error("Failed to verify signature on '%s'",
|
||||||
|
assertion)
|
||||||
raise SignatureError()
|
raise SignatureError()
|
||||||
res.append(assertion)
|
res.append(assertion)
|
||||||
return res
|
return res
|
||||||
@@ -836,11 +849,12 @@ class AuthnResponse(StatusResponse):
|
|||||||
:return: True encrypted data exists otherwise false.
|
:return: True encrypted data exists otherwise false.
|
||||||
"""
|
"""
|
||||||
for _assertion in enc_assertions:
|
for _assertion in enc_assertions:
|
||||||
if _assertion.encrypted_data is not None:
|
if _assertion.encrypted_data is not None:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def find_encrypt_data_assertion_list(self, _assertions):
|
def find_encrypt_data_assertion_list(self, _assertions):
|
||||||
""" Verifies if a list of assertions contains encrypted data in the advice element.
|
""" Verifies if a list of assertions contains encrypted data in the
|
||||||
|
advice element.
|
||||||
|
|
||||||
:param _assertions: A list of assertions.
|
:param _assertions: A list of assertions.
|
||||||
:return: True encrypted data exists otherwise false.
|
:return: True encrypted data exists otherwise false.
|
||||||
@@ -848,12 +862,14 @@ class AuthnResponse(StatusResponse):
|
|||||||
for _assertion in _assertions:
|
for _assertion in _assertions:
|
||||||
if _assertion.advice:
|
if _assertion.advice:
|
||||||
if _assertion.advice.encrypted_assertion:
|
if _assertion.advice.encrypted_assertion:
|
||||||
res = self.find_encrypt_data_assertion(_assertion.advice.encrypted_assertion)
|
res = self.find_encrypt_data_assertion(
|
||||||
|
_assertion.advice.encrypted_assertion)
|
||||||
if res:
|
if res:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def find_encrypt_data(self, resp):
|
def find_encrypt_data(self, resp):
|
||||||
""" Verifies if a saml response contains encrypted assertions with encrypted data.
|
""" Verifies if a saml response contains encrypted assertions with
|
||||||
|
encrypted data.
|
||||||
|
|
||||||
:param resp: A saml response.
|
:param resp: A saml response.
|
||||||
:return: True encrypted data exists otherwise false.
|
:return: True encrypted data exists otherwise false.
|
||||||
@@ -867,7 +883,8 @@ class AuthnResponse(StatusResponse):
|
|||||||
for tmp_assertion in resp.assertion:
|
for tmp_assertion in resp.assertion:
|
||||||
if tmp_assertion.advice:
|
if tmp_assertion.advice:
|
||||||
if tmp_assertion.advice.encrypted_assertion:
|
if tmp_assertion.advice.encrypted_assertion:
|
||||||
res = self.find_encrypt_data_assertion(tmp_assertion.advice.encrypted_assertion)
|
res = self.find_encrypt_data_assertion(
|
||||||
|
tmp_assertion.advice.encrypted_assertion)
|
||||||
if res:
|
if res:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
@@ -875,7 +892,8 @@ class AuthnResponse(StatusResponse):
|
|||||||
def parse_assertion(self, keys=None):
|
def parse_assertion(self, keys=None):
|
||||||
""" Parse the assertions for a saml response.
|
""" Parse the assertions for a saml response.
|
||||||
|
|
||||||
:param keys: A string representing a RSA key or a list of strings containing RSA keys.
|
:param keys: A string representing a RSA key or a list of strings
|
||||||
|
containing RSA keys.
|
||||||
:return: True if the assertions are parsed otherwise False.
|
:return: True if the assertions are parsed otherwise False.
|
||||||
"""
|
"""
|
||||||
if self.context == "AuthnQuery":
|
if self.context == "AuthnQuery":
|
||||||
@@ -884,12 +902,13 @@ class AuthnResponse(StatusResponse):
|
|||||||
else: # This is a saml2int limitation
|
else: # This is a saml2int limitation
|
||||||
try:
|
try:
|
||||||
assert len(self.response.assertion) == 1 or \
|
assert len(self.response.assertion) == 1 or \
|
||||||
len(self.response.encrypted_assertion) == 1
|
len(self.response.encrypted_assertion) == 1
|
||||||
except AssertionError:
|
except AssertionError:
|
||||||
raise Exception("No assertion part")
|
raise Exception("No assertion part")
|
||||||
|
|
||||||
has_encrypted_assertions = self.find_encrypt_data(self.response) #self.response.encrypted_assertion
|
has_encrypted_assertions = self.find_encrypt_data(self.response) #
|
||||||
#if not has_encrypted_assertions and self.response.assertion:
|
# self.response.encrypted_assertion
|
||||||
|
# if not has_encrypted_assertions and self.response.assertion:
|
||||||
# for tmp_assertion in self.response.assertion:
|
# for tmp_assertion in self.response.assertion:
|
||||||
# if tmp_assertion.advice:
|
# if tmp_assertion.advice:
|
||||||
# if tmp_assertion.advice.encrypted_assertion:
|
# if tmp_assertion.advice.encrypted_assertion:
|
||||||
@@ -912,15 +931,20 @@ class AuthnResponse(StatusResponse):
|
|||||||
decr_text_old = decr_text
|
decr_text_old = decr_text
|
||||||
decr_text = self.sec.decrypt_keys(decr_text, keys)
|
decr_text = self.sec.decrypt_keys(decr_text, keys)
|
||||||
resp = samlp.response_from_string(decr_text)
|
resp = samlp.response_from_string(decr_text)
|
||||||
_enc_assertions = self.decrypt_assertions(resp.encrypted_assertion, decr_text)
|
_enc_assertions = self.decrypt_assertions(resp.encrypted_assertion,
|
||||||
|
decr_text)
|
||||||
decr_text_old = None
|
decr_text_old = None
|
||||||
while (self.find_encrypt_data(resp) or self.find_encrypt_data_assertion_list(_enc_assertions)) and \
|
while (self.find_encrypt_data(
|
||||||
|
resp) or self.find_encrypt_data_assertion_list(
|
||||||
|
_enc_assertions)) and \
|
||||||
decr_text_old != decr_text:
|
decr_text_old != decr_text:
|
||||||
decr_text_old = decr_text
|
decr_text_old = decr_text
|
||||||
decr_text = self.sec.decrypt_keys(decr_text, keys)
|
decr_text = self.sec.decrypt_keys(decr_text, keys)
|
||||||
resp = samlp.response_from_string(decr_text)
|
resp = samlp.response_from_string(decr_text)
|
||||||
_enc_assertions = self.decrypt_assertions(resp.encrypted_assertion, decr_text, verified=True)
|
_enc_assertions = self.decrypt_assertions(
|
||||||
#_enc_assertions = self.decrypt_assertions(resp.encrypted_assertion, decr_text, verified=True)
|
resp.encrypted_assertion, decr_text, verified=True)
|
||||||
|
# _enc_assertions = self.decrypt_assertions(
|
||||||
|
# resp.encrypted_assertion, decr_text, verified=True)
|
||||||
all_assertions = _enc_assertions
|
all_assertions = _enc_assertions
|
||||||
if resp.assertion:
|
if resp.assertion:
|
||||||
all_assertions = all_assertions + resp.assertion
|
all_assertions = all_assertions + resp.assertion
|
||||||
@@ -928,9 +952,10 @@ class AuthnResponse(StatusResponse):
|
|||||||
for tmp_ass in all_assertions:
|
for tmp_ass in all_assertions:
|
||||||
if tmp_ass.advice and tmp_ass.advice.encrypted_assertion:
|
if tmp_ass.advice and tmp_ass.advice.encrypted_assertion:
|
||||||
|
|
||||||
advice_res = self.decrypt_assertions(tmp_ass.advice.encrypted_assertion,
|
advice_res = self.decrypt_assertions(
|
||||||
decr_text,
|
tmp_ass.advice.encrypted_assertion,
|
||||||
tmp_ass.issuer)
|
decr_text,
|
||||||
|
tmp_ass.issuer)
|
||||||
if tmp_ass.advice.assertion:
|
if tmp_ass.advice.assertion:
|
||||||
tmp_ass.advice.assertion.extend(advice_res)
|
tmp_ass.advice.assertion.extend(advice_res)
|
||||||
else:
|
else:
|
||||||
@@ -1211,7 +1236,7 @@ class AssertionIDResponse(object):
|
|||||||
logger.exception("EXCEPTION: %s", excp)
|
logger.exception("EXCEPTION: %s", excp)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
#print("<", self.response)
|
# print("<", self.response)
|
||||||
|
|
||||||
return self._postamble()
|
return self._postamble()
|
||||||
|
|
||||||
@@ -1233,4 +1258,3 @@ class AssertionIDResponse(object):
|
|||||||
logger.debug("response: %s", self.response)
|
logger.debug("response: %s", self.response)
|
||||||
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
@@ -57,6 +57,7 @@ AUTHN_DICT_MAP = {
|
|||||||
"subject_locality": "subject_locality"
|
"subject_locality": "subject_locality"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def _shelve_compat(name, *args, **kwargs):
|
def _shelve_compat(name, *args, **kwargs):
|
||||||
try:
|
try:
|
||||||
return shelve.open(name, *args, **kwargs)
|
return shelve.open(name, *args, **kwargs)
|
||||||
@@ -132,7 +133,7 @@ class Server(Entity):
|
|||||||
elif isinstance(dbspec, six.string_types):
|
elif isinstance(dbspec, six.string_types):
|
||||||
idb = _shelve_compat(dbspec, writeback=True, protocol=2)
|
idb = _shelve_compat(dbspec, writeback=True, protocol=2)
|
||||||
else: # database spec is a a 2-tuple (type, address)
|
else: # database spec is a a 2-tuple (type, address)
|
||||||
#print(>> sys.stderr, "DBSPEC: %s" % (dbspec,))
|
# print(>> sys.stderr, "DBSPEC: %s" % (dbspec,))
|
||||||
(typ, addr) = dbspec
|
(typ, addr) = dbspec
|
||||||
if typ == "shelve":
|
if typ == "shelve":
|
||||||
idb = _shelve_compat(addr, writeback=True, protocol=2)
|
idb = _shelve_compat(addr, writeback=True, protocol=2)
|
||||||
@@ -289,8 +290,10 @@ class Server(Entity):
|
|||||||
|
|
||||||
# ------------------------------------------------------------------------
|
# ------------------------------------------------------------------------
|
||||||
|
|
||||||
def setup_assertion(self, authn, sp_entity_id, in_response_to, consumer_url, name_id, policy, _issuer,
|
def setup_assertion(self, authn, sp_entity_id, in_response_to, consumer_url,
|
||||||
authn_statement, identity, best_effort, sign_response, add_subject=True):
|
name_id, policy, _issuer,
|
||||||
|
authn_statement, identity, best_effort, sign_response,
|
||||||
|
add_subject=True):
|
||||||
ast = Assertion(identity)
|
ast = Assertion(identity)
|
||||||
ast.acs = self.config.getattr("attribute_converters", "idp")
|
ast.acs = self.config.getattr("attribute_converters", "idp")
|
||||||
if policy is None:
|
if policy is None:
|
||||||
@@ -305,25 +308,29 @@ class Server(Entity):
|
|||||||
if authn: # expected to be a dictionary
|
if authn: # expected to be a dictionary
|
||||||
# Would like to use dict comprehension but ...
|
# Would like to use dict comprehension but ...
|
||||||
authn_args = dict([
|
authn_args = dict([
|
||||||
(AUTHN_DICT_MAP[k], v) for k, v in authn.items()
|
(AUTHN_DICT_MAP[k], v) for k, v in
|
||||||
if k in AUTHN_DICT_MAP])
|
authn.items()
|
||||||
|
if k in AUTHN_DICT_MAP])
|
||||||
|
|
||||||
assertion = ast.construct(sp_entity_id, in_response_to,
|
assertion = ast.construct(sp_entity_id, in_response_to,
|
||||||
consumer_url, name_id,
|
consumer_url, name_id,
|
||||||
self.config.attribute_converters,
|
self.config.attribute_converters,
|
||||||
policy, issuer=_issuer, add_subject=add_subject,
|
policy, issuer=_issuer,
|
||||||
|
add_subject=add_subject,
|
||||||
**authn_args)
|
**authn_args)
|
||||||
elif authn_statement: # Got a complete AuthnStatement
|
elif authn_statement: # Got a complete AuthnStatement
|
||||||
assertion = ast.construct(sp_entity_id, in_response_to,
|
assertion = ast.construct(sp_entity_id, in_response_to,
|
||||||
consumer_url, name_id,
|
consumer_url, name_id,
|
||||||
self.config.attribute_converters,
|
self.config.attribute_converters,
|
||||||
policy, issuer=_issuer,
|
policy, issuer=_issuer,
|
||||||
authn_statem=authn_statement, add_subject=add_subject)
|
authn_statem=authn_statement,
|
||||||
|
add_subject=add_subject)
|
||||||
else:
|
else:
|
||||||
assertion = ast.construct(sp_entity_id, in_response_to,
|
assertion = ast.construct(sp_entity_id, in_response_to,
|
||||||
consumer_url, name_id,
|
consumer_url, name_id,
|
||||||
self.config.attribute_converters,
|
self.config.attribute_converters,
|
||||||
policy, issuer=_issuer, add_subject=add_subject)
|
policy, issuer=_issuer,
|
||||||
|
add_subject=add_subject)
|
||||||
return assertion
|
return assertion
|
||||||
|
|
||||||
def _authn_response(self, in_response_to, consumer_url,
|
def _authn_response(self, in_response_to, consumer_url,
|
||||||
@@ -332,8 +339,8 @@ class Server(Entity):
|
|||||||
sign_assertion=False, sign_response=False,
|
sign_assertion=False, sign_response=False,
|
||||||
best_effort=False, encrypt_assertion=False,
|
best_effort=False, encrypt_assertion=False,
|
||||||
encrypt_cert_advice=None, encrypt_cert_assertion=None, authn_statement=None,
|
encrypt_cert_advice=None, encrypt_cert_assertion=None, authn_statement=None,
|
||||||
encrypt_assertion_self_contained=False, encrypted_advice_attributes=False, pefim=False,
|
encrypt_assertion_self_contained=False, encrypted_advice_attributes=False,
|
||||||
sign_alg=None, digest_alg=None):
|
pefim=False, sign_alg=None, digest_alg=None):
|
||||||
""" Create a response. A layer of indirection.
|
""" Create a response. A layer of indirection.
|
||||||
|
|
||||||
:param in_response_to: The session identifier of the request
|
:param in_response_to: The session identifier of the request
|
||||||
@@ -352,23 +359,27 @@ class Server(Entity):
|
|||||||
:param best_effort: Even if not the SPs demands can be met send a
|
:param best_effort: Even if not the SPs demands can be met send a
|
||||||
response.
|
response.
|
||||||
:param encrypt_assertion: True if assertions should be encrypted.
|
:param encrypt_assertion: True if assertions should be encrypted.
|
||||||
:param encrypt_assertion_self_contained: True if all encrypted assertions should have alla namespaces
|
:param encrypt_assertion_self_contained: True if all encrypted
|
||||||
|
assertions should have alla namespaces
|
||||||
selfcontained.
|
selfcontained.
|
||||||
:param encrypted_advice_attributes: True if assertions in the advice element should be encrypted.
|
:param encrypted_advice_attributes: True if assertions in the advice
|
||||||
:param encrypt_cert_advice: Certificate to be used for encryption of assertions in the advice element.
|
element should be encrypted.
|
||||||
:param encrypt_cert_assertion: Certificate to be used for encryption of assertions.
|
:param encrypt_cert_advice: Certificate to be used for encryption of
|
||||||
|
assertions in the advice element.
|
||||||
|
:param encrypt_cert_assertion: Certificate to be used for encryption
|
||||||
|
of assertions.
|
||||||
:param authn_statement: Authentication statement.
|
:param authn_statement: Authentication statement.
|
||||||
:param sign_assertion: True if assertions should be signed.
|
:param sign_assertion: True if assertions should be signed.
|
||||||
:param pefim: True if a response according to the PEFIM profile should be created.
|
:param pefim: True if a response according to the PEFIM profile
|
||||||
|
should be created.
|
||||||
:return: A response instance
|
:return: A response instance
|
||||||
"""
|
"""
|
||||||
|
|
||||||
to_sign = []
|
|
||||||
args = {}
|
args = {}
|
||||||
#if identity:
|
# if identity:
|
||||||
_issuer = self._issuer(issuer)
|
_issuer = self._issuer(issuer)
|
||||||
|
|
||||||
#if encrypt_assertion and show_nameid:
|
# if encrypt_assertion and show_nameid:
|
||||||
# tmp_name_id = name_id
|
# tmp_name_id = name_id
|
||||||
# name_id = None
|
# name_id = None
|
||||||
# name_id = None
|
# name_id = None
|
||||||
@@ -380,19 +391,27 @@ class Server(Entity):
|
|||||||
if pefim:
|
if pefim:
|
||||||
encrypted_advice_attributes = True
|
encrypted_advice_attributes = True
|
||||||
encrypt_assertion_self_contained = True
|
encrypt_assertion_self_contained = True
|
||||||
assertion_attributes = self.setup_assertion(None, sp_entity_id, None, None, None, policy,
|
assertion_attributes = self.setup_assertion(None, sp_entity_id,
|
||||||
None, None, identity, best_effort, sign_response, False)
|
None, None, None,
|
||||||
assertion = self.setup_assertion(authn, sp_entity_id, in_response_to, consumer_url,
|
policy,
|
||||||
name_id, policy, _issuer, authn_statement, [], True,
|
None, None, identity,
|
||||||
|
best_effort,
|
||||||
|
sign_response, False)
|
||||||
|
assertion = self.setup_assertion(authn, sp_entity_id,
|
||||||
|
in_response_to, consumer_url,
|
||||||
|
name_id, policy, _issuer,
|
||||||
|
authn_statement, [], True,
|
||||||
sign_response)
|
sign_response)
|
||||||
assertion.advice = saml.Advice()
|
assertion.advice = saml.Advice()
|
||||||
|
|
||||||
#assertion.advice.assertion_id_ref.append(saml.AssertionIDRef())
|
# assertion.advice.assertion_id_ref.append(saml.AssertionIDRef())
|
||||||
#assertion.advice.assertion_uri_ref.append(saml.AssertionURIRef())
|
# assertion.advice.assertion_uri_ref.append(saml.AssertionURIRef())
|
||||||
assertion.advice.assertion.append(assertion_attributes)
|
assertion.advice.assertion.append(assertion_attributes)
|
||||||
else:
|
else:
|
||||||
assertion = self.setup_assertion(authn, sp_entity_id, in_response_to, consumer_url,
|
assertion = self.setup_assertion(authn, sp_entity_id,
|
||||||
name_id, policy, _issuer, authn_statement, identity, True,
|
in_response_to, consumer_url,
|
||||||
|
name_id, policy, _issuer,
|
||||||
|
authn_statement, identity, True,
|
||||||
sign_response)
|
sign_response)
|
||||||
|
|
||||||
to_sign = []
|
to_sign = []
|
||||||
@@ -402,27 +421,13 @@ class Server(Entity):
|
|||||||
sign_alg=sign_alg, digest_alg=digest_alg)
|
sign_alg=sign_alg, digest_alg=digest_alg)
|
||||||
to_sign.append((class_name(assertion), assertion.id))
|
to_sign.append((class_name(assertion), assertion.id))
|
||||||
|
|
||||||
#if not encrypted_advice_attributes:
|
|
||||||
# if sign_assertion:
|
|
||||||
# if assertion.advice and assertion.advice.assertion:
|
|
||||||
# for tmp_assertion in assertion.advice.assertion:
|
|
||||||
# tmp_assertion.signature = pre_signature_part(tmp_assertion.id, self.sec.my_cert, 1)
|
|
||||||
# to_sign.append((class_name(tmp_assertion), tmp_assertion.id))
|
|
||||||
|
|
||||||
# Store which assertion that has been sent to which SP about which
|
|
||||||
# subject.
|
|
||||||
|
|
||||||
# self.cache.set(assertion.subject.name_id.text,
|
|
||||||
# sp_entity_id, {"ava": identity, "authn": authn},
|
|
||||||
# assertion.conditions.not_on_or_after)
|
|
||||||
|
|
||||||
args["assertion"] = assertion
|
args["assertion"] = assertion
|
||||||
|
|
||||||
if (self.support_AssertionIDRequest() or self.support_AuthnQuery()):
|
if (self.support_AssertionIDRequest() or self.support_AuthnQuery()):
|
||||||
self.session_db.store_assertion(assertion, to_sign)
|
self.session_db.store_assertion(assertion, to_sign)
|
||||||
|
|
||||||
return self._response(in_response_to, consumer_url, status, issuer,
|
return self._response(in_response_to, consumer_url, status, issuer,
|
||||||
sign_response, to_sign,sp_entity_id=sp_entity_id,
|
sign_response, to_sign, sp_entity_id=sp_entity_id,
|
||||||
encrypt_assertion=encrypt_assertion,
|
encrypt_assertion=encrypt_assertion,
|
||||||
encrypt_cert_advice=encrypt_cert_advice,
|
encrypt_cert_advice=encrypt_cert_advice,
|
||||||
encrypt_cert_assertion=encrypt_cert_assertion,
|
encrypt_cert_assertion=encrypt_cert_assertion,
|
||||||
@@ -434,7 +439,7 @@ class Server(Entity):
|
|||||||
|
|
||||||
# ------------------------------------------------------------------------
|
# ------------------------------------------------------------------------
|
||||||
|
|
||||||
#noinspection PyUnusedLocal
|
# noinspection PyUnusedLocal
|
||||||
def create_attribute_response(self, identity, in_response_to, destination,
|
def create_attribute_response(self, identity, in_response_to, destination,
|
||||||
sp_entity_id, userid="", name_id=None,
|
sp_entity_id, userid="", name_id=None,
|
||||||
status=None, issuer=None,
|
status=None, issuer=None,
|
||||||
@@ -504,7 +509,9 @@ class Server(Entity):
|
|||||||
sp_entity_id, name_id_policy=None, userid=None,
|
sp_entity_id, name_id_policy=None, userid=None,
|
||||||
name_id=None, authn=None, issuer=None,
|
name_id=None, authn=None, issuer=None,
|
||||||
sign_response=None, sign_assertion=None,
|
sign_response=None, sign_assertion=None,
|
||||||
encrypt_cert_advice=None, encrypt_cert_assertion=None, encrypt_assertion=None,
|
encrypt_cert_advice=None,
|
||||||
|
encrypt_cert_assertion=None,
|
||||||
|
encrypt_assertion=None,
|
||||||
encrypt_assertion_self_contained=True,
|
encrypt_assertion_self_contained=True,
|
||||||
encrypted_advice_attributes=False, pefim=False, sign_alg=None, digest_alg=None,
|
encrypted_advice_attributes=False, pefim=False, sign_alg=None, digest_alg=None,
|
||||||
**kwargs):
|
**kwargs):
|
||||||
@@ -524,13 +531,18 @@ class Server(Entity):
|
|||||||
:param sign_assertion: Whether the assertion should be signed or not.
|
:param sign_assertion: Whether the assertion should be signed or not.
|
||||||
:param sign_response: Whether the response should be signed or not.
|
:param sign_response: Whether the response should be signed or not.
|
||||||
:param encrypt_assertion: True if assertions should be encrypted.
|
:param encrypt_assertion: True if assertions should be encrypted.
|
||||||
:param encrypt_assertion_self_contained: True if all encrypted assertions should have alla namespaces
|
:param encrypt_assertion_self_contained: True if all encrypted
|
||||||
|
assertions should have alla namespaces
|
||||||
selfcontained.
|
selfcontained.
|
||||||
:param encrypted_advice_attributes: True if assertions in the advice element should be encrypted.
|
:param encrypted_advice_attributes: True if assertions in the advice
|
||||||
:param encrypt_cert_advice: Certificate to be used for encryption of assertions in the advice element.
|
element should be encrypted.
|
||||||
:param encrypt_cert_assertion: Certificate to be used for encryption of assertions.
|
:param encrypt_cert_advice: Certificate to be used for encryption of
|
||||||
|
assertions in the advice element.
|
||||||
|
:param encrypt_cert_assertion: Certificate to be used for encryption
|
||||||
|
of assertions.
|
||||||
:param sign_assertion: True if assertions should be signed.
|
:param sign_assertion: True if assertions should be signed.
|
||||||
:param pefim: True if a response according to the PEFIM profile should be created.
|
:param pefim: True if a response according to the PEFIM profile
|
||||||
|
should be created.
|
||||||
:return: A response instance
|
:return: A response instance
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -559,34 +571,43 @@ class Server(Entity):
|
|||||||
if encrypt_assertion is None:
|
if encrypt_assertion is None:
|
||||||
encrypt_assertion = False
|
encrypt_assertion = False
|
||||||
|
|
||||||
|
|
||||||
if encrypt_assertion_self_contained is None:
|
if encrypt_assertion_self_contained is None:
|
||||||
encrypt_assertion_self_contained = self.config.getattr("encrypt_assertion_self_contained", "idp")
|
encrypt_assertion_self_contained = self.config.getattr(
|
||||||
|
"encrypt_assertion_self_contained", "idp")
|
||||||
if encrypt_assertion_self_contained is None:
|
if encrypt_assertion_self_contained is None:
|
||||||
encrypt_assertion_self_contained = True
|
encrypt_assertion_self_contained = True
|
||||||
|
|
||||||
if encrypted_advice_attributes is None:
|
if encrypted_advice_attributes is None:
|
||||||
encrypted_advice_attributes = self.config.getattr("encrypted_advice_attributes", "idp")
|
encrypted_advice_attributes = self.config.getattr(
|
||||||
|
"encrypted_advice_attributes", "idp")
|
||||||
if encrypted_advice_attributes is None:
|
if encrypted_advice_attributes is None:
|
||||||
encrypted_advice_attributes = False
|
encrypted_advice_attributes = False
|
||||||
|
|
||||||
if encrypted_advice_attributes or pefim:
|
if encrypted_advice_attributes or pefim:
|
||||||
verify_encrypt_cert = self.config.getattr("verify_encrypt_cert_advice", "idp")
|
verify_encrypt_cert = self.config.getattr(
|
||||||
|
"verify_encrypt_cert_advice", "idp")
|
||||||
if verify_encrypt_cert is not None:
|
if verify_encrypt_cert is not None:
|
||||||
if encrypt_cert_advice is None:
|
if encrypt_cert_advice is None:
|
||||||
raise CertificateError("No SPCertEncType certificate for encryption contained in authentication "
|
raise CertificateError(
|
||||||
"request.")
|
"No SPCertEncType certificate for encryption "
|
||||||
|
"contained in authentication "
|
||||||
|
"request.")
|
||||||
if not verify_encrypt_cert(encrypt_cert_advice):
|
if not verify_encrypt_cert(encrypt_cert_advice):
|
||||||
raise CertificateError("Invalid certificate for encryption!")
|
raise CertificateError(
|
||||||
|
"Invalid certificate for encryption!")
|
||||||
|
|
||||||
if encrypt_assertion:
|
if encrypt_assertion:
|
||||||
verify_encrypt_cert = self.config.getattr("verify_encrypt_cert_assertion", "idp")
|
verify_encrypt_cert = self.config.getattr(
|
||||||
|
"verify_encrypt_cert_assertion", "idp")
|
||||||
if verify_encrypt_cert is not None:
|
if verify_encrypt_cert is not None:
|
||||||
if encrypt_cert_assertion is None:
|
if encrypt_cert_assertion is None:
|
||||||
raise CertificateError("No SPCertEncType certificate for encryption contained in authentication "
|
raise CertificateError(
|
||||||
"request.")
|
"No SPCertEncType certificate for encryption "
|
||||||
|
"contained in authentication "
|
||||||
|
"request.")
|
||||||
if not verify_encrypt_cert(encrypt_cert_assertion):
|
if not verify_encrypt_cert(encrypt_cert_assertion):
|
||||||
raise CertificateError("Invalid certificate for encryption!")
|
raise CertificateError(
|
||||||
|
"Invalid certificate for encryption!")
|
||||||
|
|
||||||
if not name_id:
|
if not name_id:
|
||||||
try:
|
try:
|
||||||
@@ -628,13 +649,17 @@ class Server(Entity):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
_authn = authn
|
_authn = authn
|
||||||
if (sign_assertion or sign_response) and self.sec.cert_handler.generate_cert():
|
if (
|
||||||
|
sign_assertion or sign_response) and \
|
||||||
|
self.sec.cert_handler.generate_cert():
|
||||||
with self.lock:
|
with self.lock:
|
||||||
self.sec.cert_handler.update_cert(True)
|
self.sec.cert_handler.update_cert(True)
|
||||||
return self._authn_response(in_response_to, # in_response_to
|
return self._authn_response(in_response_to,
|
||||||
|
# in_response_to
|
||||||
destination, # consumer_url
|
destination, # consumer_url
|
||||||
sp_entity_id, # sp_entity_id
|
sp_entity_id, # sp_entity_id
|
||||||
identity, # identity as dictionary
|
identity,
|
||||||
|
# identity as dictionary
|
||||||
name_id,
|
name_id,
|
||||||
authn=_authn,
|
authn=_authn,
|
||||||
issuer=issuer,
|
issuer=issuer,
|
||||||
@@ -686,8 +711,8 @@ class Server(Entity):
|
|||||||
authn_decl=authn_decl)
|
authn_decl=authn_decl)
|
||||||
|
|
||||||
#noinspection PyUnusedLocal
|
#noinspection PyUnusedLocal
|
||||||
def create_assertion_id_request_response(self, assertion_id, sign=False, sign_alg=None, digest_alg=None,
|
def create_assertion_id_request_response(self, assertion_id, sign=False, sign_alg=None,
|
||||||
**kwargs):
|
digest_alg=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
:param assertion_id:
|
:param assertion_id:
|
||||||
@@ -709,7 +734,7 @@ class Server(Entity):
|
|||||||
else:
|
else:
|
||||||
return assertion
|
return assertion
|
||||||
|
|
||||||
#noinspection PyUnusedLocal
|
# noinspection PyUnusedLocal
|
||||||
def create_name_id_mapping_response(self, name_id=None, encrypted_id=None,
|
def create_name_id_mapping_response(self, name_id=None, encrypted_id=None,
|
||||||
in_response_to=None,
|
in_response_to=None,
|
||||||
issuer=None, sign_response=False,
|
issuer=None, sign_response=False,
|
||||||
|
@@ -301,6 +301,10 @@ not_on_or_after = before
|
|||||||
valid = before
|
valid = before
|
||||||
|
|
||||||
|
|
||||||
|
def utc_time_sans_frac():
|
||||||
|
return int("%d" % time.mktime(time.gmtime()))
|
||||||
|
|
||||||
|
|
||||||
def later_than(after, before):
|
def later_than(after, before):
|
||||||
""" True if then is later or equal to that """
|
""" True if then is later or equal to that """
|
||||||
if isinstance(after, six.string_types):
|
if isinstance(after, six.string_types):
|
||||||
|
@@ -36,7 +36,6 @@ CONFIG = {
|
|||||||
# }
|
# }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"subject_data": full_path("subject_data_3.db"),
|
|
||||||
#"domain": "umu.se",
|
#"domain": "umu.se",
|
||||||
#"name_qualifier": ""
|
#"name_qualifier": ""
|
||||||
},
|
},
|
||||||
|
@@ -45,7 +45,6 @@ CONFIG = {
|
|||||||
# }
|
# }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"subject_data": full_path("subject_data_2.db"),
|
|
||||||
#"domain": "umu.se",
|
#"domain": "umu.se",
|
||||||
#"name_qualifier": ""
|
#"name_qualifier": ""
|
||||||
},
|
},
|
||||||
|
@@ -2,14 +2,15 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
import datetime
|
import datetime
|
||||||
import re
|
import re
|
||||||
from six.moves.urllib.parse import quote_plus
|
#from six.moves.urllib.parse import quote_plus
|
||||||
|
from future.backports.urllib.parse import quote_plus
|
||||||
from saml2.config import Config
|
from saml2.config import Config
|
||||||
from saml2.httpbase import HTTPBase
|
from saml2.mdstore import MetadataStore
|
||||||
|
from saml2.mdstore import MetaDataMDX
|
||||||
from saml2.mdstore import MetadataStore, MetaDataMDX
|
from saml2.mdstore import SAML_METADATA_CONTENT_TYPE
|
||||||
from saml2.mdstore import destinations
|
from saml2.mdstore import destinations
|
||||||
|
from saml2.mdstore import load_extensions
|
||||||
from saml2.mdstore import name
|
from saml2.mdstore import name
|
||||||
|
|
||||||
from saml2 import md
|
from saml2 import md
|
||||||
from saml2 import sigver
|
from saml2 import sigver
|
||||||
from saml2 import BINDING_SOAP
|
from saml2 import BINDING_SOAP
|
||||||
@@ -20,21 +21,32 @@ from saml2 import saml
|
|||||||
from saml2 import config
|
from saml2 import config
|
||||||
from saml2.attribute_converter import ac_factory
|
from saml2.attribute_converter import ac_factory
|
||||||
from saml2.attribute_converter import d_to_local_name
|
from saml2.attribute_converter import d_to_local_name
|
||||||
|
|
||||||
from saml2.extension import mdui
|
|
||||||
from saml2.extension import idpdisc
|
|
||||||
from saml2.extension import dri
|
|
||||||
from saml2.extension import mdattr
|
|
||||||
from saml2.extension import ui
|
|
||||||
from saml2.s_utils import UnknownPrincipal
|
from saml2.s_utils import UnknownPrincipal
|
||||||
from saml2 import xmldsig
|
from saml2 import xmldsig
|
||||||
from saml2 import xmlenc
|
from saml2 import xmlenc
|
||||||
|
|
||||||
from pathutils import full_path
|
from pathutils import full_path
|
||||||
|
|
||||||
|
import responses
|
||||||
|
|
||||||
sec_config = config.Config()
|
sec_config = config.Config()
|
||||||
# sec_config.xmlsec_binary = sigver.get_xmlsec_binary(["/opt/local/bin"])
|
# sec_config.xmlsec_binary = sigver.get_xmlsec_binary(["/opt/local/bin"])
|
||||||
|
|
||||||
|
TEST_CERT = """MIICsDCCAhmgAwIBAgIJAJrzqSSwmDY9MA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV
|
||||||
|
BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX
|
||||||
|
aWRnaXRzIFB0eSBMdGQwHhcNMDkxMDA2MTk0OTQxWhcNMDkxMTA1MTk0OTQxWjBF
|
||||||
|
MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50
|
||||||
|
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB
|
||||||
|
gQDJg2cms7MqjniT8Fi/XkNHZNPbNVQyMUMXE9tXOdqwYCA1cc8vQdzkihscQMXy
|
||||||
|
3iPw2cMggBu6gjMTOSOxECkuvX5ZCclKr8pXAJM5cY6gVOaVO2PdTZcvDBKGbiaN
|
||||||
|
efiEw5hnoZomqZGp8wHNLAUkwtH9vjqqvxyS/vclc6k2ewIDAQABo4GnMIGkMB0G
|
||||||
|
A1UdDgQWBBRePsKHKYJsiojE78ZWXccK9K4aJTB1BgNVHSMEbjBsgBRePsKHKYJs
|
||||||
|
iojE78ZWXccK9K4aJaFJpEcwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUt
|
||||||
|
U3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAJrzqSSw
|
||||||
|
mDY9MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAJSrKOEzHO7TL5cy6
|
||||||
|
h3qh+3+JAk8HbGBW+cbX6KBCAw/mzU8flK25vnWwXS3dv2FF3Aod0/S7AWNfKib5
|
||||||
|
U/SA9nJaz/mWeF9S0farz9AQFc8/NSzAzaVq7YbM4F6f6N2FRl7GikdXRCed45j6
|
||||||
|
mrPzGzk3ECbupFnqyREH3+ZPSdk="""
|
||||||
|
|
||||||
TEST_METADATA_STRING = """
|
TEST_METADATA_STRING = """
|
||||||
<EntitiesDescriptor
|
<EntitiesDescriptor
|
||||||
xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
|
xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
|
||||||
@@ -51,21 +63,8 @@ TEST_METADATA_STRING = """
|
|||||||
<ds:KeyInfo>
|
<ds:KeyInfo>
|
||||||
<ds:X509Data>
|
<ds:X509Data>
|
||||||
<ds:X509Certificate>
|
<ds:X509Certificate>
|
||||||
MIICsDCCAhmgAwIBAgIJAJrzqSSwmDY9MA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV
|
{cert_data}
|
||||||
BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX
|
</ds:X509Certificate>
|
||||||
aWRnaXRzIFB0eSBMdGQwHhcNMDkxMDA2MTk0OTQxWhcNMDkxMTA1MTk0OTQxWjBF
|
|
||||||
MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50
|
|
||||||
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB
|
|
||||||
gQDJg2cms7MqjniT8Fi/XkNHZNPbNVQyMUMXE9tXOdqwYCA1cc8vQdzkihscQMXy
|
|
||||||
3iPw2cMggBu6gjMTOSOxECkuvX5ZCclKr8pXAJM5cY6gVOaVO2PdTZcvDBKGbiaN
|
|
||||||
efiEw5hnoZomqZGp8wHNLAUkwtH9vjqqvxyS/vclc6k2ewIDAQABo4GnMIGkMB0G
|
|
||||||
A1UdDgQWBBRePsKHKYJsiojE78ZWXccK9K4aJTB1BgNVHSMEbjBsgBRePsKHKYJs
|
|
||||||
iojE78ZWXccK9K4aJaFJpEcwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUt
|
|
||||||
U3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAJrzqSSw
|
|
||||||
mDY9MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAJSrKOEzHO7TL5cy6
|
|
||||||
h3qh+3+JAk8HbGBW+cbX6KBCAw/mzU8flK25vnWwXS3dv2FF3Aod0/S7AWNfKib5
|
|
||||||
U/SA9nJaz/mWeF9S0farz9AQFc8/NSzAzaVq7YbM4F6f6N2FRl7GikdXRCed45j6
|
|
||||||
mrPzGzk3ECbupFnqyREH3+ZPSdk=</ds:X509Certificate>
|
|
||||||
</ds:X509Data>
|
</ds:X509Data>
|
||||||
</ds:KeyInfo>
|
</ds:KeyInfo>
|
||||||
</KeyDescriptor>
|
</KeyDescriptor>
|
||||||
@@ -85,20 +84,17 @@ TEST_METADATA_STRING = """
|
|||||||
</ContactPerson>
|
</ContactPerson>
|
||||||
</EntityDescriptor>
|
</EntityDescriptor>
|
||||||
</EntitiesDescriptor>
|
</EntitiesDescriptor>
|
||||||
"""
|
""".format(cert_data=TEST_CERT)
|
||||||
|
|
||||||
ONTS = {
|
ONTS = {
|
||||||
saml.NAMESPACE: saml,
|
saml.NAMESPACE: saml,
|
||||||
mdui.NAMESPACE: mdui,
|
|
||||||
mdattr.NAMESPACE: mdattr,
|
|
||||||
dri.NAMESPACE: dri,
|
|
||||||
ui.NAMESPACE: ui,
|
|
||||||
idpdisc.NAMESPACE: idpdisc,
|
|
||||||
md.NAMESPACE: md,
|
md.NAMESPACE: md,
|
||||||
xmldsig.NAMESPACE: xmldsig,
|
xmldsig.NAMESPACE: xmldsig,
|
||||||
xmlenc.NAMESPACE: xmlenc
|
xmlenc.NAMESPACE: xmlenc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ONTS.update(load_extensions())
|
||||||
|
|
||||||
ATTRCONV = ac_factory(full_path("attributemaps"))
|
ATTRCONV = ac_factory(full_path("attributemaps"))
|
||||||
|
|
||||||
METADATACONF = {
|
METADATACONF = {
|
||||||
@@ -149,7 +145,11 @@ METADATACONF = {
|
|||||||
}],
|
}],
|
||||||
"11": [{
|
"11": [{
|
||||||
"class": "saml2.mdstore.InMemoryMetaData",
|
"class": "saml2.mdstore.InMemoryMetaData",
|
||||||
"metadata": [(TEST_METADATA_STRING, )]
|
"metadata": [(TEST_METADATA_STRING,)]
|
||||||
|
}],
|
||||||
|
"12": [{
|
||||||
|
"class": "saml2.mdstore.MetaDataFile",
|
||||||
|
"metadata": [(full_path("uu.xml"),)],
|
||||||
}],
|
}],
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -304,6 +304,36 @@ def test_metadata_file():
|
|||||||
assert len(mds.keys()) == 560
|
assert len(mds.keys()) == 560
|
||||||
|
|
||||||
|
|
||||||
|
@responses.activate
|
||||||
|
def test_mdx_service():
|
||||||
|
entity_id = "http://xenosmilus.umdc.umu.se/simplesaml/saml2/idp/metadata.php"
|
||||||
|
|
||||||
|
url = "http://mdx.example.com/entities/{}".format(
|
||||||
|
quote_plus(MetaDataMDX.sha1_entity_transform(entity_id)))
|
||||||
|
responses.add(responses.GET, url, body=TEST_METADATA_STRING, status=200,
|
||||||
|
content_type=SAML_METADATA_CONTENT_TYPE)
|
||||||
|
|
||||||
|
mdx = MetaDataMDX("http://mdx.example.com")
|
||||||
|
sso_loc = mdx.service(entity_id, "idpsso_descriptor", "single_sign_on_service")
|
||||||
|
assert sso_loc[BINDING_HTTP_REDIRECT][0]["location"] == "http://xenosmilus.umdc.umu.se/simplesaml/saml2/idp/metadata.php"
|
||||||
|
certs = mdx.certs(entity_id, "idpsso")
|
||||||
|
assert len(certs) == 1
|
||||||
|
|
||||||
|
|
||||||
|
@responses.activate
|
||||||
|
def test_mdx_single_sign_on_service():
|
||||||
|
entity_id = "http://xenosmilus.umdc.umu.se/simplesaml/saml2/idp/metadata.php"
|
||||||
|
|
||||||
|
url = "http://mdx.example.com/entities/{}".format(
|
||||||
|
quote_plus(MetaDataMDX.sha1_entity_transform(entity_id)))
|
||||||
|
responses.add(responses.GET, url, body=TEST_METADATA_STRING, status=200,
|
||||||
|
content_type=SAML_METADATA_CONTENT_TYPE)
|
||||||
|
|
||||||
|
mdx = MetaDataMDX("http://mdx.example.com")
|
||||||
|
sso_loc = mdx.single_sign_on_service(entity_id, BINDING_HTTP_REDIRECT)
|
||||||
|
assert sso_loc[0]["location"] == "http://xenosmilus.umdc.umu.se/simplesaml/saml2/idp/metadata.php"
|
||||||
|
|
||||||
|
|
||||||
# pyff-test not available
|
# pyff-test not available
|
||||||
# def test_mdx_service():
|
# def test_mdx_service():
|
||||||
# sec_config.xmlsec_binary = sigver.get_xmlsec_binary(["/opt/local/bin"])
|
# sec_config.xmlsec_binary = sigver.get_xmlsec_binary(["/opt/local/bin"])
|
||||||
@@ -372,7 +402,7 @@ def test_load_string():
|
|||||||
disable_ssl_certificate_validation=True)
|
disable_ssl_certificate_validation=True)
|
||||||
|
|
||||||
mds.imp(METADATACONF["11"])
|
mds.imp(METADATACONF["11"])
|
||||||
#print(mds)
|
# print(mds)
|
||||||
assert len(mds.keys()) == 1
|
assert len(mds.keys()) == 1
|
||||||
idps = mds.with_descriptor("idpsso")
|
idps = mds.with_descriptor("idpsso")
|
||||||
|
|
||||||
@@ -384,5 +414,58 @@ def test_load_string():
|
|||||||
assert len(certs) == 1
|
assert len(certs) == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_certs_from_metadata():
|
||||||
|
mds = MetadataStore(ONTS.values(), ATTRCONV, None)
|
||||||
|
mds.imp(METADATACONF["11"])
|
||||||
|
certs1 = mds.certs("http://xenosmilus.umdc.umu.se/simplesaml/saml2/idp/metadata.php", "any")
|
||||||
|
certs2 = mds.certs("http://xenosmilus.umdc.umu.se/simplesaml/saml2/idp/metadata.php", "idpsso")
|
||||||
|
|
||||||
|
assert certs1[0] == certs2[0] == TEST_CERT
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_certs_from_metadata_without_keydescriptor():
|
||||||
|
mds = MetadataStore(ONTS.values(), ATTRCONV, None)
|
||||||
|
mds.imp([{
|
||||||
|
"class": "saml2.mdstore.InMemoryMetaData",
|
||||||
|
"metadata": [("""
|
||||||
|
<EntitiesDescriptor
|
||||||
|
xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
|
||||||
|
xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata"
|
||||||
|
xmlns:shibmeta="urn:mace:shibboleth:metadata:1.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
|
||||||
|
Name="urn:mace:example.com:test-1.0">
|
||||||
|
<EntityDescriptor
|
||||||
|
entityID="http://xenosmilus.umdc.umu.se/simplesaml/saml2/idp/metadata.php"
|
||||||
|
xml:base="swamid-1.0/idp.umu.se-saml2.xml">
|
||||||
|
<IDPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
|
||||||
|
<NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>
|
||||||
|
<SingleSignOnService
|
||||||
|
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
|
||||||
|
Location="http://xenosmilus.umdc.umu.se/simplesaml/saml2/idp/metadata.php"/>
|
||||||
|
</IDPSSODescriptor>
|
||||||
|
<Organization>
|
||||||
|
<OrganizationName xml:lang="en">Catalogix</OrganizationName>
|
||||||
|
<OrganizationDisplayName xml:lang="en">Catalogix</OrganizationDisplayName>
|
||||||
|
<OrganizationURL xml:lang="en">http://www.catalogix.se</OrganizationURL>
|
||||||
|
</Organization>
|
||||||
|
<ContactPerson contactType="technical">
|
||||||
|
<SurName>Hedberg</SurName>
|
||||||
|
<EmailAddress>datordrift@catalogix.se</EmailAddress>
|
||||||
|
</ContactPerson>
|
||||||
|
</EntityDescriptor>
|
||||||
|
</EntitiesDescriptor>""",)]
|
||||||
|
}])
|
||||||
|
certs = mds.certs("http://xenosmilus.umdc.umu.se/simplesaml/saml2/idp/metadata.php", "idpsso")
|
||||||
|
|
||||||
|
assert len(certs) == 0
|
||||||
|
|
||||||
|
def test_metadata_extension_algsupport():
|
||||||
|
mds = MetadataStore(list(ONTS.values()), ATTRCONV, None)
|
||||||
|
mds.imp(METADATACONF["12"])
|
||||||
|
mdf = mds.metadata[full_path("uu.xml")]
|
||||||
|
_txt = mdf.dumps()
|
||||||
|
assert mds
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
test_load_local()
|
test_metadata_extension_algsupport()
|
||||||
|
2
tests/test_requirements.txt
Normal file
2
tests/test_requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
pymongo==3.0.1
|
||||||
|
responses==0.5.0
|
@@ -67,9 +67,10 @@ secc = security_context(conf)
|
|||||||
|
|
||||||
if args.id:
|
if args.id:
|
||||||
desc, xmldoc = entities_descriptor(eds, valid_for, args.name, args.id,
|
desc, xmldoc = entities_descriptor(eds, valid_for, args.name, args.id,
|
||||||
args.sign, secc)
|
args.sign, secc)
|
||||||
valid_instance(desc)
|
valid_instance(desc)
|
||||||
print(desc.to_string(nspair))
|
xmldoc = metadata_tostring_fix(desc, nspair, xmldoc)
|
||||||
|
print(xmldoc.decode("utf-8"))
|
||||||
else:
|
else:
|
||||||
for eid in eds:
|
for eid in eds:
|
||||||
if args.sign:
|
if args.sign:
|
||||||
|
Reference in New Issue
Block a user