Updated IdP example2

This commit is contained in:
Roland Hedberg 2013-02-01 13:49:41 +01:00
parent d1523b6d5e
commit 7f777c2c38
8 changed files with 173 additions and 147 deletions

View File

@ -41,7 +41,7 @@ CONFIG={
# This database holds the map between a subjects local identifier and # This database holds the map between a subjects local identifier and
# the identifier returned to a SP # the identifier returned to a SP
#"xmlsec_binary": "/usr/local/bin/xmlsec1", #"xmlsec_binary": "/usr/local/bin/xmlsec1",
"attribute_map_dir" : "./attributemaps", "attribute_map_dir" : "../attributemaps",
"logger": { "logger": {
"rotating": { "rotating": {
"filename": "idp.log", "filename": "idp.log",

View File

@ -1,4 +1,5 @@
#!/usr/bin/env python #!/usr/bin/env python
import base64
import re import re
import logging import logging
@ -9,23 +10,24 @@ from hashlib import sha1
from urlparse import parse_qs from urlparse import parse_qs
from Cookie import SimpleCookie from Cookie import SimpleCookie
from saml2 import server, BINDING_HTTP_ARTIFACT from saml2 import server
from saml2 import BINDING_HTTP_ARTIFACT
from saml2 import BINDING_URI
from saml2 import BINDING_PAOS
from saml2 import BINDING_SOAP from saml2 import BINDING_SOAP
from saml2 import BINDING_HTTP_REDIRECT from saml2 import BINDING_HTTP_REDIRECT
from saml2 import BINDING_HTTP_POST from saml2 import BINDING_HTTP_POST
from saml2 import time_util from saml2 import time_util
from saml2.httputil import Response from saml2.httputil import Response, NotFound
from saml2.httputil import get_post from saml2.httputil import get_post
from saml2.httputil import Redirect from saml2.httputil import Redirect
from saml2.httputil import Unauthorized from saml2.httputil import Unauthorized
from saml2.httputil import BadRequest from saml2.httputil import BadRequest
from saml2.httputil import ServiceError from saml2.httputil import ServiceError
from saml2.ident import Unknown from saml2.ident import Unknown
from saml2.s_utils import rndstr from saml2.s_utils import rndstr, UnknownPrincipal, UnsupportedBinding
from saml2.s_utils import PolicyError from saml2.s_utils import PolicyError
from saml2.saml import AUTHN_PASSWORD from saml2.saml import AUTHN_PASSWORD
from saml2.saml import NAMEID_FORMAT_PERSISTENT
from saml2.saml import NameID
logger = logging.getLogger("saml2.idp") logger = logging.getLogger("saml2.idp")
@ -113,14 +115,15 @@ def dict2list_of_tuples(d):
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
def _operation(environ, start_response, user, _dict, func, binding): def _operation(environ, start_response, user, _dict, func, binding,
**kwargs):
logger.debug("_operation: %s" % _dict) logger.debug("_operation: %s" % _dict)
if not _dict: if not _dict:
resp = BadRequest('Error parsing request or no request') resp = BadRequest('Error parsing request or no request')
return resp(environ, start_response) return resp(environ, start_response)
else: else:
return func(environ, start_response, user, _dict["SAMLRequest"], return func(environ, start_response, user, _dict["SAMLRequest"],
binding, _dict["RelayState"]) binding, _dict["RelayState"], **kwargs)
def _artifact_oper(environ, start_response, user, _dict, func): def _artifact_oper(environ, start_response, user, _dict, func):
if not _dict: if not _dict:
@ -133,6 +136,13 @@ def _artifact_oper(environ, start_response, user, _dict, func):
return func(environ, start_response, user, request, return func(environ, start_response, user, request,
BINDING_HTTP_ARTIFACT, _dict["RelayState"]) BINDING_HTTP_ARTIFACT, _dict["RelayState"])
def _response(environ, start_response, binding, http_args):
if binding == BINDING_HTTP_ARTIFACT:
resp = Redirect()
else:
resp = Response(http_args["data"], headers=http_args["headers"])
return resp(environ, start_response)
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
AUTHN = (AUTHN_PASSWORD, "http://lingon.catalogix.se/login") AUTHN = (AUTHN_PASSWORD, "http://lingon.catalogix.se/login")
@ -146,7 +156,8 @@ FORM_SPEC = """<form name="myform" method="post" action="%s">
# === Single log in ==== # === Single log in ====
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
def _sso(environ, start_response, user, query, binding, relay_state=""): def _sso(environ, start_response, user, query, binding, relay_state="",
response_bindings=None):
logger.info("--- In SSO ---") logger.info("--- In SSO ---")
logger.debug("user: %s" % user) logger.debug("user: %s" % user)
@ -161,7 +172,16 @@ def _sso(environ, start_response, user, query, binding, relay_state=""):
logger.info("%s" % req_info) logger.info("%s" % req_info)
_authn_req = req_info.message _authn_req = req_info.message
try:
resp_args = IDP.response_args(_authn_req) resp_args = IDP.response_args(_authn_req)
except UnknownPrincipal, excp:
#IDP.create_error_response()
resp = ServiceError("UnknownPrincipal: %s" % (excp,))
return resp(environ, start_response)
except UnsupportedBinding, excp:
#IDP.create_error_response()
resp = ServiceError("UnsupportedBinding: %s" % (excp,))
return resp(environ, start_response)
identity = USERS[user] identity = USERS[user]
logger.info("Identity: %s" % (identity,)) logger.info("Identity: %s" % (identity,))
@ -178,12 +198,13 @@ def _sso(environ, start_response, user, query, binding, relay_state=""):
logger.info("AuthNResponse: %s" % authn_resp) logger.info("AuthNResponse: %s" % authn_resp)
binding, destination = IDP.pick_binding("assertion_consumer_service", binding, destination = IDP.pick_binding("assertion_consumer_service",
bindings=response_bindings,
entity_id=_authn_req.issuer.text) entity_id=_authn_req.issuer.text)
logger.debug("Binding: %s, destination: %s" % (binding, destination))
http_args = IDP.apply_binding(binding, "%s" % authn_resp, destination, http_args = IDP.apply_binding(binding, "%s" % authn_resp, destination,
relay_state, response=True) relay_state, response=True)
resp = Response(http_args["data"], headers=http_args["headers"]) return _response(environ, start_response, binding, http_args)
return resp(environ, start_response)
def sso(environ, start_response, user): def sso(environ, start_response, user):
""" This is the HTTP-redirect endpoint """ """ This is the HTTP-redirect endpoint """
@ -222,6 +243,36 @@ def sso_art(environ, start_response, user):
del IDP.ticket[_dict["key"]] del IDP.ticket[_dict["key"]]
return _artifact_oper(environ, start_response, user, _request, _sso) return _artifact_oper(environ, start_response, user, _request, _sso)
def sso_ecp(environ, start_response, user):
# The ECP interface
logger.info("--- ECP SSO ---")
logger.debug("ENVIRON: %s" % environ)
resp = None
try:
authz_info = environ["HTTP_AUTHORIZATION"]
if authz_info.startswith("Basic "):
_info = base64.b64decode(authz_info[6:])
logger.debug("Authz_info: %s" % _info)
try:
(user,passwd) = _info.split(":")
if PASSWD[user] != passwd:
resp = Unauthorized()
except ValueError:
resp = Unauthorized()
else:
resp = Unauthorized()
except KeyError:
resp = Unauthorized()
if resp:
return resp(environ, start_response)
_dict = unpack_soap(environ)
# Basic auth ?!
return _operation(environ, start_response, user, _dict, _sso, BINDING_SOAP,
response_bindings=[BINDING_PAOS])
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# === Authentication ==== # === Authentication ====
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
@ -286,11 +337,9 @@ def do_authentication(environ, start_response, cookie=None):
def verify_username_and_password(dic): def verify_username_and_password(dic):
global PASSWD global PASSWD
# verify username and password # verify username and password
for user, pwd in PASSWD: if PASSWD[dic["login"][0]] == dic["password"][0]:
if user == dic["login"][0]: return True, dic["login"][0]
if pwd == dic["password"][0]: else:
return True, user
return False, "" return False, ""
@ -331,6 +380,7 @@ def _slo(environ, start_response, _, request, binding, relay_state=""):
try: try:
req_info = IDP.parse_logout_request(request, binding) req_info = IDP.parse_logout_request(request, binding)
except Exception, exc: except Exception, exc:
logger.error("Bad request: %s" % exc)
resp = BadRequest("%s" % exc) resp = BadRequest("%s" % exc)
return resp(environ, start_response) return resp(environ, start_response)
@ -342,6 +392,7 @@ def _slo(environ, start_response, _, request, binding, relay_state=""):
try: try:
IDP.remove_authn_statements(msg.name_id) IDP.remove_authn_statements(msg.name_id)
except KeyError,exc: except KeyError,exc:
logger.error("ServiceError: %s" % exc)
resp = ServiceError("%s" % exc) resp = ServiceError("%s" % exc)
return resp(environ, start_response) return resp(environ, start_response)
@ -350,6 +401,7 @@ def _slo(environ, start_response, _, request, binding, relay_state=""):
try: try:
hinfo = IDP.apply_binding(binding, "%s" % resp, "", relay_state) hinfo = IDP.apply_binding(binding, "%s" % resp, "", relay_state)
except Exception, exc: except Exception, exc:
logger.error("ServiceError: %s" % exc)
resp = ServiceError("%s" % exc) resp = ServiceError("%s" % exc)
return resp(environ, start_response) return resp(environ, start_response)
@ -410,9 +462,9 @@ def not_found(environ, start_response):
return ['Not Found'] return ['Not Found']
PASSWD = [("roland", "dianakra"), PASSWD = {"roland": "dianakra",
("babs", "howes"), "babs": "howes",
("upper", "crust")] "upper": "crust"}
# ---------------------------------------------------------------------------- # ----------------------------------------------------------------------------
@ -433,21 +485,24 @@ def kaka2user(kaka):
def _mni(environ, start_response, user, query, binding, relay_state=""): def _mni(environ, start_response, user, query, binding, relay_state=""):
logger.info("--- Manage Name ID Service ---") logger.info("--- Manage Name ID Service ---")
req = IDP.parse_manage_name_id_response(query, binding) req = IDP.parse_manage_name_id_request(query, binding)
request = req.message
# Do the necessary stuff # Do the necessary stuff
in_response_to = req.message.id name_id = IDP.ident.handle_manage_name_id_request(request.name_id,
name_id = NameID(format=NAMEID_FORMAT_PERSISTENT, text="foobar") request.new_id,
request.new_encrypted_id,
request.terminate)
info = IDP.response_args(req) logger.debug("New NameID: %s" % name_id)
_resp = IDP.create_manage_name_id_response(name_id, **info)
_resp = IDP.create_manage_name_id_response(request)
# It's using SOAP binding # It's using SOAP binding
hinfo = IDP.apply_binding(binding, "%s" % _resp, "", relay_state, hinfo = IDP.apply_binding(binding, "%s" % _resp, "", relay_state,
"SAMLResponse") response=True)
resp = Response(hinfo["data"], resp = Response(hinfo["data"], headers=hinfo["headers"])
headers=dict2list_of_tuples(hinfo["headers"]))
return resp(environ, start_response) return resp(environ, start_response)
def mni(environ, start_response, user): def mni(environ, start_response, user):
@ -478,25 +533,29 @@ def mni_art(environ, start_response, user):
# === Assertion ID request === # === Assertion ID request ===
# ---------------------------------------------------------------------------- # ----------------------------------------------------------------------------
# Only SOAP binding # Only URI binding
def assertion_id_request(environ, start_response, user=None): def assertion_id_request(environ, start_response, user=None):
logger.info("--- Assertion ID Service ---") logger.info("--- Assertion ID Service ---")
_dict = unpack_soap(environ) _binding = BINDING_URI
_binding = BINDING_SOAP
if not _dict: _dict = unpack_artifact(environ)
logger.debug("INPUT: %s" % _dict)
# Presently only HTTP GET is supported
if "ID" in _dict:
aid = _dict["ID"]
else:
resp = BadRequest("Missing or faulty request") resp = BadRequest("Missing or faulty request")
return resp(environ, start_response) return resp(environ, start_response)
req_info = IDP.parse_assertion_id_request("%s" % _dict["SAMLRequest"], try:
_binding) assertion = IDP.create_assertion_id_request_response(aid)
except Unknown:
resp = NotFound(aid)
return resp(environ, start_response)
asids = [x.text for x in req_info.message.assertion_id_ref] hinfo = IDP.apply_binding(_binding, "%s" % assertion, response=True)
resp_args = IDP.response_args(req_info.message, _binding, "spsso")
response = IDP.create_assertion_id_request_response(asids, **resp_args)
hinfo = IDP.apply_binding(_binding, "%s" % response, "","",response=True)
logger.debug("HINFO: %s" % hinfo)
resp = Response(hinfo["data"], headers=hinfo["headers"]) resp = Response(hinfo["data"], headers=hinfo["headers"])
return resp(environ, start_response) return resp(environ, start_response)
@ -559,6 +618,45 @@ def authn_query_service(environ, start_response, user=None):
return resp(environ, start_response) return resp(environ, start_response)
# ----------------------------------------------------------------------------
# === Attribute query service ===
# ----------------------------------------------------------------------------
# Only SOAP binding
def attribute_query_service(environ, start_response, user=None):
"""
:param environ: Execution environment
:param start_response: Function to start the response with
"""
logger.info("--- Attribute Query Service ---")
_dict = unpack_soap(environ)
_binding = BINDING_SOAP
if not _dict:
resp = BadRequest("Missing or faulty request")
return resp(environ, start_response)
_req = IDP.parse_attribute_query("%s" % _dict["SAMLRequest"], _binding)
_query = _req.message
name_id = _query.subject.name_id
uid = IDP.ident.find_local_id(name_id)
logger.debug("Local uid: %s" % uid)
identity = EXTRA[uid]
# Comes in over SOAP so only need to construct the response
args = IDP.response_args(_query, [BINDING_SOAP])
msg = IDP.create_attribute_response(identity, destination="",
name_id=name_id, **args)
logger.debug("response: %s" % msg)
hinfo = IDP.apply_binding(_binding, "%s" % msg, "","",response=True)
resp = Response(hinfo["data"], headers=hinfo["headers"])
return resp(environ, start_response)
# ---------------------------------------------------------------------------- # ----------------------------------------------------------------------------
# Name ID Mapping service # Name ID Mapping service
# When an entity that shares an identifier for a principal with an identity # When an entity that shares an identifier for a principal with an identity
@ -569,11 +667,12 @@ def authn_query_service(environ, start_response, user=None):
def _nim(environ, start_response, user, query, binding, relay_state=""): def _nim(environ, start_response, user, query, binding, relay_state=""):
req = IDP.parse_manage_name_id_response(query, binding) req = IDP.parse_name_id_mapping_request(query, binding)
request = req.message
# Do the necessary stuff # Do the necessary stuff
try: try:
name_id = IDP.ident.handle_name_id_mapping_request() name_id = IDP.ident.handle_name_id_mapping_request(request.name_id,
request.name_id_policy)
except Unknown: except Unknown:
resp = BadRequest("Unknown entity") resp = BadRequest("Unknown entity")
return resp(environ, start_response) return resp(environ, start_response)
@ -581,56 +680,19 @@ def _nim(environ, start_response, user, query, binding, relay_state=""):
resp = BadRequest("Unknown entity") resp = BadRequest("Unknown entity")
return resp(environ, start_response) return resp(environ, start_response)
info = IDP.response_args(req) info = IDP.response_args(request)
_resp = IDP.create_manage_name_id_response(name_id, **info) _resp = IDP.create_name_id_mapping_response(name_id, **info)
# It's using SOAP binding # Only SOAP
hinfo = IDP.apply_binding(binding, "%s" % _resp, "", "", response=True) hinfo = IDP.apply_binding(binding, "%s" % _resp, "", "", response=True)
resp = Response(hinfo["data"], resp = Response(hinfo["data"], headers=hinfo["headers"])
headers=dict2list_of_tuples(hinfo["headers"]))
return resp(environ, start_response) return resp(environ, start_response)
def nim(environ, start_response, user):
""" Expects a HTTP-redirect logout request """
_dict = unpack_redirect(environ)
if not _dict:
resp = Unauthorized('Unknown user')
return resp(environ, start_response)
else:
return _mni(environ, start_response, user, _dict["SAMLRequest"],
BINDING_HTTP_REDIRECT, _dict["RelayState"])
def nim_post(environ, start_response, user):
""" Expects a HTTP-POST logout request """
_dict = unpack_post(environ)
if not _dict:
resp = Unauthorized('Unknown user')
return resp(environ, start_response)
else:
return _mni(environ, start_response, user, _dict["SAMLRequest"],
BINDING_HTTP_REDIRECT, _dict["RelayState"])
def nim_soap(environ, start_response, user): def nim_soap(environ, start_response, user):
_dict = unpack_soap(environ)
return _operation(environ, start_response, user, _dict, _nim, BINDING_SOAP)
_dict = unpack_post(environ)
if not _dict:
resp = Unauthorized('Unknown user')
return resp(environ, start_response)
else:
return _mni(environ, start_response, user, _dict["SAMLRequest"],
BINDING_HTTP_REDIRECT, _dict["RelayState"])
def nim_art(environ, start_response, user):
# Could be by HTTP_REDIRECT or HTTP_POST
_dict = unpack_redirect(environ)
if not _dict:
_dict = unpack_post(environ)
return _artifact_oper(environ, start_response, user, _dict, _mni)
# ---------------------------------------------------------------------------- # ----------------------------------------------------------------------------
# ---------------------------------------------------------------------------- # ----------------------------------------------------------------------------
@ -689,21 +751,17 @@ AUTHN_URLS = [
(r'mni/soap$', mni_soap), (r'mni/soap$', mni_soap),
(r'mni/soap/(.*)$', mni_soap), (r'mni/soap/(.*)$', mni_soap),
# nim # nim
(r'nim/post$', nim_post), (r'nim$', nim_soap),
(r'nim/post/(.*)$', nim_post), (r'nim/(.*)$', nim_soap),
(r'nim/redirect$', nim),
(r'nim/redirect/(.*)$', nim),
(r'nim/art$', nim_art),
(r'nim/art/(.*)$', nim_art),
(r'nim/soap$', nim_soap),
(r'nim/soap/(.*)$', nim_soap),
# #
(r'aqs$', authn_query_service) (r'aqs$', authn_query_service),
(r'attr$', attribute_query_service)
] ]
NON_AUTHN_URLS = [ NON_AUTHN_URLS = [
(r'login?(.*)$', do_authentication), (r'login?(.*)$', do_authentication),
(r'verify?(.*)$', do_verify), (r'verify?(.*)$', do_verify),
(r'sso/ecp$', sso_ecp),
] ]
# ---------------------------------------------------------------------------- # ----------------------------------------------------------------------------
@ -776,6 +834,7 @@ LOOKUP = TemplateLookup(directories=[ROOT + 'templates', ROOT + 'htdocs'],
if __name__ == '__main__': if __name__ == '__main__':
import sys import sys
from idp_user import USERS from idp_user import USERS
from idp_user import EXTRA
from wsgiref.simple_server import make_server from wsgiref.simple_server import make_server
PORT = 8088 PORT = 8088

View File

@ -1,4 +1,6 @@
from saml2 import BINDING_HTTP_REDIRECT #!/usr/bin/env python
# -*- coding: utf-8 -*-
from saml2 import BINDING_HTTP_REDIRECT, BINDING_URI
from saml2 import BINDING_HTTP_ARTIFACT from saml2 import BINDING_HTTP_ARTIFACT
from saml2 import BINDING_HTTP_POST from saml2 import BINDING_HTTP_POST
from saml2 import BINDING_SOAP from saml2 import BINDING_SOAP
@ -27,8 +29,7 @@ CONFIG={
"aa": { "aa": {
"endpoints" : { "endpoints" : {
"attribute_service": [ "attribute_service": [
("%s/attr/post" % BASE, BINDING_HTTP_POST), ("%s/attr" % BASE, BINDING_SOAP)
("%s/attr/soap" % BASE, BINDING_SOAP)
] ]
}, },
"name_id_format": [NAMEID_FORMAT_TRANSIENT, "name_id_format": [NAMEID_FORMAT_TRANSIENT,
@ -47,7 +48,8 @@ CONFIG={
"single_sign_on_service" : [ "single_sign_on_service" : [
("%s/sso/redirect" % BASE, BINDING_HTTP_REDIRECT), ("%s/sso/redirect" % BASE, BINDING_HTTP_REDIRECT),
("%s/sso/post" % BASE, BINDING_HTTP_POST), ("%s/sso/post" % BASE, BINDING_HTTP_POST),
("%s/sso/art" % BASE, BINDING_HTTP_ARTIFACT) ("%s/sso/art" % BASE, BINDING_HTTP_ARTIFACT),
("%s/sso/ecp" % BASE, BINDING_SOAP)
], ],
"single_logout_service": [ "single_logout_service": [
("%s/slo/soap" % BASE, BINDING_SOAP), ("%s/slo/soap" % BASE, BINDING_SOAP),
@ -58,7 +60,7 @@ CONFIG={
("%s/ars" % BASE, BINDING_SOAP) ("%s/ars" % BASE, BINDING_SOAP)
], ],
"assertion_id_request_service": [ "assertion_id_request_service": [
("%s/airs" % BASE, BINDING_SOAP) ("%s/airs" % BASE, BINDING_URI)
], ],
"manage_name_id_service":[ "manage_name_id_service":[
("%s/mni/soap" % BASE, BINDING_SOAP), ("%s/mni/soap" % BASE, BINDING_SOAP),
@ -67,10 +69,7 @@ CONFIG={
("%s/mni/art" % BASE, BINDING_HTTP_ARTIFACT) ("%s/mni/art" % BASE, BINDING_HTTP_ARTIFACT)
], ],
"name_id_mapping_service":[ "name_id_mapping_service":[
("%s/nim/soap" % BASE, BINDING_SOAP), ("%s/nim" % BASE, BINDING_SOAP),
("%s/nim/post" % BASE, BINDING_HTTP_POST),
("%s/nim/redirect" % BASE, BINDING_HTTP_REDIRECT),
("%s/nim/art" % BASE, BINDING_HTTP_ARTIFACT)
], ],
}, },
"policy": { "policy": {

View File

@ -5,24 +5,22 @@ USERS = {
"eduPersonAffiliation": "staff", "eduPersonAffiliation": "staff",
"uid": "rohe0002" "uid": "rohe0002"
}, },
"ozzie": { "babs": {
"surname": "Guillen", "surname": "Babs",
"givenName": "Ozzie", "givenName": "Ozzie",
"eduPersonAffiliation": "affiliate" "eduPersonAffiliation": "affiliate"
}, },
"derek": { "upper": {
"surname": "Jeter", "surname": "Jeter",
"givenName": "Derek", "givenName": "Derek",
"eduPersonAffiliation": "affiliate" "eduPersonAffiliation": "affiliate"
}, },
"ichiro": { }
"surname": "Suzuki",
"givenName": "Ischiro", EXTRA = {
"eduPersonAffiliation": "affiliate" "roland": {
}, "eduPersonEntitlement" : "urn:mace:swamid.se:foo:bar",
"ryan": { "schacGender": "male",
"surname": "Howard", "schacUserPresenceID": "skype:pepe.perez"
"givenName": "Ryan",
"eduPersonAffiliation": "affiliate"
} }
} }

View File

@ -1,20 +1,5 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
#
# Copyright (C) 2006 Google Inc.
# Copyright (C) 2009 Umeå University
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Contains base classes representing SAML elements. """Contains base classes representing SAML elements.
@ -32,21 +17,6 @@
provides methods and functions to convert SAML classes to and from strings. provides methods and functions to convert SAML classes to and from strings.
""" """
# try:
# # lxml: best performance for XML processing
# import lxml.etree as ET
# except ImportError:
# try:
# # Python 2.5+: batteries included
# import xml.etree.cElementTree as ET
# except ImportError:
# try:
# # Python <2.5: standalone ElementTree install
# import elementtree.cElementTree as ET
# except ImportError:
# raise ImportError, "lxml or ElementTree are not installed, "\
# +"see http://codespeak.net/lxml "\
# +"or http://effbot.org/zone/element-index.htm"
import logging import logging
from saml2.validate import valid_instance from saml2.validate import valid_instance