Changed logging setup

This commit is contained in:
Roland Hedberg
2012-06-27 07:50:13 +02:00
parent ce1eb15025
commit 2495375de4
22 changed files with 245 additions and 328 deletions

View File

@@ -14,6 +14,7 @@
# 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.
import logging
import re
import sys
@@ -28,6 +29,7 @@ from saml2.s_utils import sid, MissingValue
from saml2.s_utils import factory
from saml2.s_utils import assertion_factory
logger = logging.getLogger(__name__)
def _filter_values(vals, vlist=None, must=False):
""" Removes values from *vals* that does not appear in vlist

View File

@@ -21,6 +21,11 @@ to do attribute aggregation.
"""
import saml2
import logging
from saml2.client import Saml2Client
logger = logging.getLogger(__name__)
DEFAULT_BINDING = saml2.BINDING_SOAP
class AttributeResolver(object):
@@ -32,7 +37,7 @@ class AttributeResolver(object):
self.saml2client = saml2client
self.metadata = saml2client.config.metadata
else:
self.saml2client = saml2.client.Saml2Client(config)
self.saml2client = Saml2Client(config)
def extend(self, subject_id, issuer, vo_members, name_id_format=None,
sp_name_qualifier=None, log=None, real_id=None):
@@ -42,7 +47,7 @@ class AttributeResolver(object):
:param issuer: Who am I the poses the query
:param vo_members: The entity IDs of the IdP who I'm going to ask
for extra attributes
:param nameid_format: Used to make the IdPs aware of what's going
:param name_id_format: Used to make the IdPs aware of what's going
on here
:param log: Where to log exciting information
:return: A dictionary with all the collected information about the

View File

@@ -28,6 +28,9 @@ import base64
import urllib
from saml2.s_utils import deflate_and_base64_encode
from saml2.soap import SOAPClient, HTTPClient
import logging
logger = logging.getLogger(__name__)
try:
from xml.etree import cElementTree as ElementTree

View File

@@ -2,6 +2,9 @@
import shelve
from saml2 import time_util
import logging
logger = logging.getLogger(__name__)
# The assumption is that any subject may consist of data
# gathered from several different sources, all with their own

View File

@@ -58,6 +58,9 @@ from saml2 import BINDING_HTTP_REDIRECT
from saml2 import BINDING_SOAP
from saml2 import BINDING_HTTP_POST
from saml2 import BINDING_PAOS
import logging
logger = logging.getLogger(__name__)
SSO_BINDING = saml2.BINDING_HTTP_REDIRECT
@@ -82,9 +85,8 @@ class LogoutError(Exception):
class Saml2Client(object):
""" The basic pySAML2 service provider class """
def __init__(self, config=None,
identity_cache=None, state_cache=None,
virtual_organization=None, config_file="", logger=None):
def __init__(self, config=None, identity_cache=None, state_cache=None,
virtual_organization=None, config_file=""):
"""
:param config: A saml2.config.Config instance
:param identity_cache: Where the class should store identity information
@@ -110,18 +112,12 @@ class Saml2Client(object):
self.metadata = self.config.metadata
if logger is None:
self.logger = self.config.setup_logger()
else:
self.logger = logger
# we copy the config.debug variable in an internal
# field for convenience and because we may need to
# change it during the tests
self.debug = self.config.debug
self.sec = security_context(self.config, log=self.logger,
debug=self.debug)
self.sec = security_context(self.config)
if virtual_organization:
self.vorg = VirtualOrg(self, virtual_organization)
@@ -173,8 +169,7 @@ class Saml2Client(object):
try:
return self.config.single_sign_on_services(entityid, binding)[0]
except IndexError:
if self.logger:
self.logger.info("_sso_location: %s, %s" % (entityid,
logger.info("_sso_location: %s, %s" % (entityid,
binding))
raise IdpUnspecified("No IdP to send to given the premises")
@@ -205,15 +200,13 @@ class Saml2Client(object):
else:
return None
def response(self, post, outstanding, log=None, decode=True,
asynchop=True):
def response(self, post, outstanding, decode=True, asynchop=True):
""" Deal with an AuthnResponse or LogoutResponse
:param post: The reply as a dictionary
:param outstanding: A dictionary with session IDs as keys and
the original web request from the user before redirection
as values.
:param log: where loggin should go.
:param decode: Whether the response is Base64 encoded or not
:param asynchop: Whether the response was return over a asynchronous
connection. SOAP for instance is synchronous
@@ -230,39 +223,33 @@ class Saml2Client(object):
except KeyError:
raise Exception("Missing entity_id specification")
if log is None:
log = self.logger
reply_addr = self.service_url()
resp = None
if saml_response:
try:
resp = response_factory(saml_response, self.config,
reply_addr, outstanding, log,
debug=self.debug, decode=decode,
reply_addr, outstanding, decode=decode,
asynchop=asynchop,
allow_unsolicited=self.allow_unsolicited)
except Exception, exc:
if log:
log.error("%s" % exc)
logger.error("%s" % exc)
return None
if log:
log.debug(">> %s", resp)
logger.debug(">> %s", resp)
resp = resp.verify()
if isinstance(resp, AuthnResponse):
self.users.add_information_about_person(resp.session_info())
if log:
log.info("--- ADDED person info ----")
logger.info("--- ADDED person info ----")
elif isinstance(resp, LogoutResponse):
self.handle_logout_response(resp, log)
elif log:
log.error("Response type not supported: %s" % saml2.class_name(resp))
self.handle_logout_response(resp)
else:
logger.error("Response type not supported: %s" % (
saml2.class_name(resp),))
return resp
def authn_request(self, query_id, destination, service_url, spentityid,
my_name="", vorg="", scoping=None, log=None, sign=None,
my_name="", vorg="", scoping=None, sign=None,
binding=saml2.BINDING_HTTP_POST,
nameid_format=saml.NAMEID_FORMAT_TRANSIENT):
""" Creates an authentication request.
@@ -274,7 +261,6 @@ class Saml2Client(object):
:param my_name: The name of this service.
:param vorg: The vitual organization the service belongs to.
:param scoping: The scope of the request
:param log: A service to which logs should be written
:param sign: Whether the request should be signed or not.
:param binding: The protocol to use for the Response !!
:return: <samlp:AuthnRequest> instance
@@ -321,11 +307,7 @@ class Saml2Client(object):
request.name_id_policy = name_id_policy
request.issuer = self._issuer(spentityid)
if log is None:
log = self.logger
if log:
log.info("REQUEST: %s" % request)
logger.info("REQUEST: %s" % request)
return signed_instance_factory(request, self.sec, to_sign)
@@ -357,16 +339,11 @@ class Saml2Client(object):
my_name = self._my_name()
if log is None:
log = self.logger
if log:
log.info("spentityid: %s" % spentityid)
log.info("service_url: %s" % service_url)
log.info("my_name: %s" % my_name)
logger.info("spentityid: %s\nservice_url: %s\nmy_name: %s" % (
spentityid, service_url, my_name))
return self.authn_request(session_id, location, service_url,
spentityid, my_name, vorg, scoping, log,
spentityid, my_name, vorg, scoping,
sign, binding=binding)
def authenticate(self, entityid=None, relay_state="",
@@ -471,7 +448,7 @@ class Saml2Client(object):
def attribute_query(self, subject_id, destination, issuer_id=None,
attribute=None, sp_name_qualifier=None, name_qualifier=None,
nameid_format=None, log=None, real_id=None):
nameid_format=None, real_id=None):
""" Does a attribute request to an attribute authority, this is
by default done over SOAP. Other bindings could be used but not
supported right now.
@@ -486,60 +463,49 @@ class Saml2Client(object):
:param name_qualifier: The unique identifier of the identity
provider that generated the identifier.
:param nameid_format: The format of the name ID
:param log: Function to use for logging
:param real_id: The identifier which is the key to this entity in the
identity database
:return: The attributes returned
"""
if log is None:
log = self.logger
session_id = sid()
issuer = self._issuer(issuer_id)
request = self.create_attribute_query(session_id, subject_id,
destination, issuer, attribute, sp_name_qualifier,
name_qualifier, nameid_format=nameid_format)
if log:
log.info("Request, created: %s" % request)
logger.info("Request, created: %s" % request)
soapclient = SOAPClient(destination, self.config.key_file,
self.config.cert_file,
ca_certs=self.config.ca_certs)
if log:
log.info("SOAP client initiated")
logger.info("SOAP client initiated")
try:
response = soapclient.send(request)
except Exception, exc:
if log:
log.info("SoapClient exception: %s" % (exc,))
logger.info("SoapClient exception: %s" % (exc,))
return None
if log:
log.info("SOAP request sent and got response: %s" % response)
logger.info("SOAP request sent and got response: %s" % response)
# fil = open("response.xml", "w")
# fil.write(response)
# fil.close()
if response:
if log:
log.info("Verifying response")
logger.info("Verifying response")
try:
# synchronous operation
aresp = attribute_response(self.config, issuer, log=log)
aresp = attribute_response(self.config, issuer)
except Exception, exc:
if log:
log.error("%s", (exc,))
logger.error("%s", (exc,))
return None
_resp = aresp.loads(response, False, soapclient.response).verify()
if _resp is None:
if log:
log.error("Didn't like the response")
logger.error("Didn't like the response")
return None
session_info = _resp.session_info()
@@ -548,13 +514,11 @@ class Saml2Client(object):
if real_id is not None:
session_info["name_id"] = real_id
self.users.add_information_about_person(session_info)
if log:
log.info("session: %s" % session_info)
logger.info("session: %s" % session_info)
return session_info
else:
if log:
log.info("No response")
logger.info("No response")
return None
def construct_logout_request(self, subject_id, destination,
@@ -595,8 +559,8 @@ class Saml2Client(object):
return request
def global_logout(self, subject_id, reason="", expire=None,
sign=None, log=None, return_to="/"):
def global_logout(self, subject_id, reason="", expire=None, sign=None,
return_to="/"):
""" More or less a layer of indirection :-/
Bootstrapping the whole thing by finding all the IdPs that should
be notified.
@@ -607,7 +571,6 @@ class Saml2Client(object):
:param expire: The latest the log out should happen.
:param sign: Whether the request should be signed or not.
This also depends on what binding is used.
:param log: A logging function
:param return_to: Where to send the user after she has been
logged out.
:return: Depends on which binding is used:
@@ -616,17 +579,13 @@ class Saml2Client(object):
conversation.
"""
if log is None:
log = self.logger
if log:
log.info("logout request for: %s" % subject_id)
logger.info("logout request for: %s" % subject_id)
# find out which IdPs/AAs I should notify
entity_ids = self.users.issuers_of_info(subject_id)
return self._logout(subject_id, entity_ids, reason, expire,
sign, log, return_to)
sign, return_to)
def _logout(self, subject_id, entity_ids, reason, expire,
sign=None, log=None, return_to="/"):
@@ -640,8 +599,6 @@ class Saml2Client(object):
# for all where I can use the SOAP binding, do those first
not_done = entity_ids[:]
response = False
if log is None:
log = self.logger
for entity_id in entity_ids:
response = False
@@ -654,9 +611,8 @@ class Saml2Client(object):
continue
destination = destinations[0]
if log:
log.info("destination to provider: %s" % destination)
logger.info("destination to provider: %s" % destination)
request = self.construct_logout_request(subject_id, destination,
entity_id, reason, expire)
@@ -670,9 +626,8 @@ class Saml2Client(object):
request.signature = pre_signature_part(request.id,
self.sec.my_cert, 1)
to_sign = [(class_name(request), request.id)]
if log:
log.info("REQUEST: %s" % request)
logger.info("REQUEST: %s" % request)
request = signed_instance_factory(request, self.sec, to_sign)
@@ -683,17 +638,14 @@ class Saml2Client(object):
log=log,
ca_certs=self.config.ca_certs)
if response:
if log:
log.info("Verifying response")
logger.info("Verifying response")
response = self.logout_response(response, log)
if response:
not_done.remove(entity_id)
if log:
log.info("OK response from %s" % destination)
logger.info("OK response from %s" % destination)
else:
if log:
log.info(
logger.info(
"NOT OK response from %s" % destination)
else:
@@ -737,25 +689,19 @@ class Saml2Client(object):
self.users.remove_person(subject_id)
return True
def handle_logout_response(self, response, log):
def handle_logout_response(self, response):
""" handles a Logout response
:param response: A response.Response instance
:param log: A logging function
:return: 4-tuple of (session_id of the last sent logout request,
response message, response headers and message)
"""
if log is None:
log = self.logger
if log:
log.info("state: %s" % (self.state,))
logger.info("state: %s" % (self.state,))
status = self.state[response.in_response_to]
if log:
log.info("status: %s" % (status,))
logger.info("status: %s" % (status,))
issuer = response.issuer()
if log:
log.info("issuer: %s" % issuer)
logger.info("issuer: %s" % issuer)
del self.state[response.in_response_to]
if status["entity_ids"] == [issuer]: # done
self.local_logout(status["subject_id"])
@@ -766,14 +712,12 @@ class Saml2Client(object):
status["entity_ids"],
status["reason"],
status["not_on_or_after"],
status["sign"],
log, )
status["sign"])
def logout_response(self, xmlstr, log=None, binding=BINDING_SOAP):
def logout_response(self, xmlstr, binding=BINDING_SOAP):
""" Deal with a LogoutResponse
:param xmlstr: The response as a xml string
:param log: logging function
:param binding: What type of binding this message came through.
:return: None if the reply doesn't contain a valid SAML LogoutResponse,
otherwise the reponse if the logout was successful and None if it
@@ -781,8 +725,6 @@ class Saml2Client(object):
"""
response = None
if log is None:
log = self.logger
if xmlstr:
try:
@@ -790,16 +732,13 @@ class Saml2Client(object):
return_addr = self.config.endpoint("single_logout_service",
binding=binding)[0]
except Exception:
if log:
log.info("Not supposed to handle this!")
logger.info("Not supposed to handle this!")
return None
try:
response = LogoutResponse(self.sec, return_addr,
debug=self.debug, log=log)
response = LogoutResponse(self.sec, return_addr)
except Exception, exc:
if log:
log.info("%s" % exc)
logger.info("%s" % exc)
return None
if binding == BINDING_HTTP_REDIRECT:
@@ -807,8 +746,7 @@ class Saml2Client(object):
elif binding == BINDING_HTTP_POST:
xmlstr = base64.b64decode(xmlstr)
if log:
log.debug("XMLSTR: %s" % xmlstr)
logger.debug("XMLSTR: %s" % xmlstr)
response = response.loads(xmlstr, False)
@@ -817,11 +755,10 @@ class Saml2Client(object):
if not response:
return None
if log:
log.debug(response)
return self.handle_logout_response(response, log)
logger.debug(response)
return self.handle_logout_response(response)
return response
@@ -836,8 +773,6 @@ class Saml2Client(object):
"""
headers = []
success = False
if log is None:
log = self.logger
try:
saml_request = get['SAMLRequest']
@@ -848,8 +783,7 @@ class Saml2Client(object):
xml = decode_base64_and_inflate(saml_request)
request = samlp.logout_request_from_string(xml)
if log:
log.debug(request)
logger.debug(request)
if request.name_id.text == subject_id:
status = samlp.STATUS_SUCCESS
@@ -862,8 +796,7 @@ class Saml2Client(object):
request.id,
status)
if log:
log.info("RESPONSE: {0:>s}".format(response))
logger.info("RESPONSE: {0:>s}".format(response))
if 'RelayState' in get:
rstate = get['RelayState']
@@ -876,7 +809,7 @@ class Saml2Client(object):
return headers, success
def logout_request(self, request, subject_id, log=None,
def logout_request(self, request, subject_id,
binding=BINDING_HTTP_REDIRECT):
""" Deal with a LogoutRequest
@@ -885,11 +818,9 @@ class Saml2Client(object):
:param subject_id: the id of the current logged user
:return: What is returned also depends on which binding is used.
"""
if log is None:
log = self.logger
if binding == BINDING_HTTP_REDIRECT:
return self.http_redirect_logout_request(request, subject_id, log)
return self.http_redirect_logout_request(request, subject_id)
def make_logout_response(self, idp_entity_id, request_id,
status_code, binding=BINDING_HTTP_REDIRECT):
@@ -951,7 +882,7 @@ class Saml2Client(object):
action=None,
resource=None, subject=None,
binding=saml2.BINDING_HTTP_REDIRECT,
log=None, sign=False):
sign=False):
""" Makes an authz decision query.
:param entityid: The entity ID of the IdP to send the request to
@@ -960,7 +891,6 @@ class Saml2Client(object):
:param resource:
:param subject:
:param binding: Which binding to use for sending the request
:param log: Where to write log messages
:param sign: Whether the request should be signed or not.
:return: AuthzDecisionQuery instance
"""
@@ -977,13 +907,12 @@ class Saml2Client(object):
_action,
saml.Evidence(assertion=assertion),
resource, subject,
binding, log, sign)
binding, sign)
#noinspection PyUnusedLocal
def authz_decision_query(self, entityid, action,
evidence=None, resource=None, subject=None,
binding=saml2.BINDING_HTTP_REDIRECT,
log=None, sign=None):
binding=saml2.BINDING_HTTP_REDIRECT, sign=None):
""" Creates an authz decision query.
:param entityid: The entity ID of the IdP to send the request to
@@ -992,7 +921,6 @@ class Saml2Client(object):
:param resource: The resource you want to perform the action on
:param subject: Who wants to do the thing
:param binding: Which binding to use for sending the request
:param log: Where to write log messages
:param sign: Whether the request should be signed or not.
:return: AuthzDecisionQuery instance
"""
@@ -1001,14 +929,8 @@ class Saml2Client(object):
service_url = self.service_url()
my_name = self._my_name()
if log is None:
log = self.logger
if log:
log.info("spentityid: %s" % spentityid)
log.info("service_url: %s" % service_url)
log.info("my_name: %s" % my_name)
logger.info("spentityid: %s\nservice_url: %s\nmy_name: %s" % (
spentityid, service_url, my_name))
# authen_req = self.authn_request(session_id, location,
# service_url, spentityid, my_name, vorg,
@@ -1026,13 +948,13 @@ class Saml2Client(object):
#noinspection PyUnusedLocal
def authz_decision_query_response(self, response, log=None):
def authz_decision_query_response(self, response):
""" Verify that the response is OK """
pass
#noinspection PyUnusedLocal
def do_authz_decision_query(self, entityid, assertion=None,
log=None, sign=False):
sign=False):
authz_decision_query = self.authz_decision_query(entityid, assertion)
@@ -1051,21 +973,17 @@ class Saml2Client(object):
response = send_using_soap(authz_decision_query, destination,
self.config.key_file,
self.config.cert_file,
log=log,
ca_certs=self.config.ca_certs)
if response:
if log:
log.info("Verifying response")
response = self.authz_decision_query_response(response, log)
logger.info("Verifying response")
response = self.authz_decision_query_response(response)
if response:
#not_done.remove(entity_id)
if log:
log.info("OK response from %s" % destination)
logger.info("OK response from %s" % destination)
return response
else:
if log:
log.info("NOT OK response from %s" % destination)
logger.info("NOT OK response from %s" % destination)
return None

View File

@@ -18,6 +18,8 @@ from saml2.attribute_converter import ac_factory
from saml2.assertion import Policy
from saml2.sigver import get_xmlsec_binary
logger = logging.getLogger(__name__)
COMMON_ARGS = ["entityid", "xmlsec_binary", "debug", "key_file", "cert_file",
"secret", "accepted_time_diff", "name", "ca_certs",
"description",
@@ -84,8 +86,6 @@ LOG_HANDLER = {
}
LOG_FORMAT = "%(asctime)s %(name)s: %(levelname)s %(message)s"
#LOG_FORMAT = "%(asctime)s %(name)s: %(levelname)s [%(sid)s][%(func)s] %
# (message)s"
class ConfigurationError(Exception):
pass

View File

@@ -18,6 +18,7 @@
"""
Contains classes used in the SAML ECP profile
"""
import logging
from saml2 import element_to_extension_element
from saml2 import samlp
@@ -35,6 +36,8 @@ from saml2.s_utils import sid
from saml2.response import authn_response
logger = logging.getLogger(__name__)
SERVICE = "urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp"
def ecp_capable(headers):
@@ -49,14 +52,12 @@ def ecp_capable(headers):
ACTOR = "http://schemas.xmlsoap.org/soap/actor/next"
#noinspection PyUnusedLocal
def ecp_auth_request(cls, entityid=None, relay_state="",
log=None, sign=False):
def ecp_auth_request(cls, entityid=None, relay_state="", sign=False):
""" Makes an authentication request.
:param entityid: The entity ID of the IdP to send the request to
:param relay_state: To where the user should be returned after
successfull log in.
:param log: Where to write log messages
:param sign: Whether the request should be signed or not.
:return: AuthnRequest response
"""
@@ -111,13 +112,11 @@ def ecp_auth_request(cls, entityid=None, relay_state="",
# <samlp:AuthnRequest>
# ----------------------------------------
if log:
log.info("entityid: %s, binding: %s" % (entityid, BINDING_SOAP))
logger.info("entityid: %s, binding: %s" % (entityid, BINDING_SOAP))
location = cls._sso_location(entityid, binding=BINDING_SOAP)
session_id = sid()
authn_req = cls.authn(location, session_id, log=log,
binding=BINDING_PAOS,
authn_req = cls.authn(location, session_id, binding=BINDING_PAOS,
service_url_binding=BINDING_PAOS)
body = soapenv.Body()
@@ -144,9 +143,7 @@ def handle_ecp_authn_response(cls, soap_message, outstanding=None):
item.c_namespace == ecp.NAMESPACE:
_relay_state = item
response = authn_response(cls.config, cls.service_url(),
outstanding, log=cls.logger,
debug=cls.debug,
response = authn_response(cls.config, cls.service_url(), outstanding,
allow_unsolicited=True)
response.loads("%s" % rdict["body"], False, soap_message)
@@ -182,9 +179,8 @@ class ECPServer(Server):
TODO: Still tentative
"""
def __init__(self, config_file="", config=None, _cache="",
log=None, debug=0):
Server.__init__(self, config_file, config, _cache, log, debug)
def __init__(self, config_file="", config=None, _cache=""):
Server.__init__(self, config_file, config, _cache)
def parse_ecp_authn_query(self):
pass

View File

@@ -21,6 +21,7 @@ programs.
"""
import cookielib
import logging
import sys
from saml2 import soap
@@ -37,11 +38,12 @@ from saml2.metadata import MetaData
SERVICE = "urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp"
PAOS_HEADER_INFO = 'ver="%s";"%s"' % (paos.NAMESPACE, SERVICE)
logger = logging.getLogger(__name__)
class Client(object):
def __init__(self, user, passwd, sp="", idp=None, metadata_file=None,
xmlsec_binary=None, verbose=0, ca_certs="",
disable_ssl_certificate_validation=True, logger=None,
debug=False):
disable_ssl_certificate_validation=True):
"""
:param user: user name
:param passwd: user password
@@ -55,15 +57,11 @@ class Client(object):
:param disable_ssl_certificate_validation: If
disable_ssl_certificate_validation is true, SSL cert validation
will not be performed.
:param logger: Somewhere to write logs to
:param debug: Whether debug output is needed
"""
self._idp = idp
self._sp = sp
self.user = user
self.passwd = passwd
self.log = logger
self.debug = debug
self._verbose = verbose
if metadata_file:
@@ -83,9 +81,7 @@ class Client(object):
disable_ssl_certificate_validation=disable_ssl_certificate_validation)
def _debug_info(self, text):
if self.debug:
if self.log:
self.log.debug(text)
logger.debug(text)
if self._verbose:
print >> sys.stderr, text
@@ -104,8 +100,7 @@ class Client(object):
binding=binding)
if ssos:
self._idp = ssos[0]
if self.debug:
self.log.debug("IdP endpoint: '%s'" % self._idp)
logger.debug("IdP endpoint: '%s'" % self._idp)
return self._idp
raise Exception("No suitable endpoint found for entity id '%s'" % (
@@ -244,8 +239,7 @@ class Client(object):
self._debug_info("[P3] IdP response: %s" % response)
self.done_ecp = True
if self.debug:
self.log.debug("Done ECP")
logger.debug("Done ECP")
return None
@@ -297,9 +291,7 @@ class Client(object):
# header blocks may also be present
try:
respdict = soap.class_instances_from_soap_enveloped_saml_thingies(
response,
[paos, ecp,
samlp])
response,[paos, ecp,samlp])
self.ecp_conversation(respdict, idp_entity_id)
# should by now be authenticated so this should go smoothly
response = op(**opargs)

View File

@@ -1,3 +1,4 @@
import logging
import os
import sys
@@ -15,6 +16,8 @@ __author__ = 'rohe0002'
import xmlenc as enc
logger = logging.getLogger(__name__)
#<EncryptedData
# xmlns="http://www.w3.org/2001/04/xmlenc#"
# Type="http://www.w3.org/2001/04/xmlenc#Element">

View File

@@ -32,6 +32,7 @@
# CookieJar class with httplib2.
#
#
import logging
import re
import cookielib
@@ -40,6 +41,8 @@ from httplib2 import Http
import urllib
import urllib2
logger = logging.getLogger(__name__)
class DummyRequest(object):
"""Simulated urllib2.Request object for httplib2

View File

@@ -1,8 +1,12 @@
import logging
__author__ = 'rohe0002'
import cgi
from urllib import quote
logger = logging.getLogger(__name__)
class Response(object):
_template = None
_status = '200 OK'

View File

@@ -1,4 +1,5 @@
#!/usr/bin/env python
import logging
import memcache
from saml2 import time_util
@@ -8,6 +9,7 @@ from saml2.cache import ToOld, CacheError
# gathered from several different sources, all with their own
# timeout time.
logger = logging.getLogger(__name__)
def _key(prefix, name):
return "%s_%s" % (prefix, name)

View File

@@ -1,4 +1,5 @@
#!/usr/bin/env python
import logging
__author__ = 'rolandh'
@@ -11,6 +12,8 @@ from saml2 import time_util
from saml2.cache import ToOld
from saml2.time_util import TIME_FORMAT
logger = logging.getLogger(__name__)
class Cache(object):
def __init__(self, server=None, debug=0, db=None):
if server:

View File

@@ -18,6 +18,7 @@
"""
Contains classes and functions to alleviate the handling of SAML metadata
"""
import logging
import httplib2
import sys
@@ -57,6 +58,8 @@ from saml2.sigver import pem_format
from saml2.validate import valid_instance, NotValid
from saml2.country_codes import D_COUNTRIES
logger = logging.getLogger(__name__)
def metadata_extension_modules():
_pre = "saml2.extension"
res = []
@@ -86,8 +89,7 @@ def keep_updated(func, self=None, entity_id=None, *args, **kwargs):
class MetaData(object):
""" A class to manage metadata information """
def __init__(self, xmlsec_binary=None, attrconv=None, log=None):
self.log = log
def __init__(self, xmlsec_binary=None, attrconv=None):
self.xmlsec_binary = xmlsec_binary
self.attrconv = attrconv or []
self._loc_key = {}
@@ -388,15 +390,9 @@ class MetaData(object):
def do_entity_descriptor(self, entity_descr, source, valid_until=0):
try:
if not valid(entity_descr.valid_until):
if self.log:
self.log.info(
"Entity descriptor (entity id:%s) to old" % \
entity_descr.entity_id)
else:
print >> sys.stderr, \
"Entity descriptor (entity id:%s) to old" % \
entity_descr.entity_id
return
logger.info("Entity descriptor (entity id:%s) to old" % (
entity_descr.entity_id,))
return
except AttributeError:
pass
@@ -483,8 +479,7 @@ class MetaData(object):
self.import_metadata(content, (url, cert))
return True
else:
if self.log:
self.log.info("Response status: %s" % response.status)
logger.info("Response status: %s" % response.status)
return False
@keep_updated

View File

@@ -1,6 +1,8 @@
import logging
from saml2.cache import Cache
logger = logging.getLogger(__name__)
class Population(object):
def __init__(self, cache=None):
if cache:
@@ -33,8 +35,10 @@ class Population(object):
def issuers_of_info(self, subject_id):
return self.cache.entities(subject_id)
def get_identity(self, subject_id, entities=None, check_not_on_or_after=True):
return self.cache.get_identity(subject_id, entities, check_not_on_or_after)
def get_identity(self, subject_id, entities=None,
check_not_on_or_after=True):
return self.cache.get_identity(subject_id, entities,
check_not_on_or_after)
def get_info_from(self, subject_id, entity_id):
return self.cache.get(subject_id, entity_id)

View File

@@ -1,4 +1,4 @@
import sys
import logging
from attribute_converter import to_local
from saml2 import time_util
@@ -9,20 +9,16 @@ from saml2.validate import valid_instance
from saml2.validate import NotValid
from saml2.response import IncorrectlySigned
logger = logging.getLogger(__name__)
def _dummy(_arg):
return None
class Request(object):
def __init__(self, sec_context, receiver_addrs, log=None, timeslack=0,
debug=0):
def __init__(self, sec_context, receiver_addrs, timeslack=0):
self.sec = sec_context
self.receiver_addrs = receiver_addrs
self.timeslack = timeslack
self.log = log
self.debug = debug
if self.debug and not self.log:
self.debug = 0
self.xmlstr = ""
self.name_id = ""
self.message = None
@@ -38,40 +34,32 @@ class Request(object):
def _loads(self, xmldata, decode=True):
if decode:
if self.debug:
self.log.debug("Expected to decode and inflate xml data")
logger.debug("Expected to decode and inflate xml data")
decoded_xml = s_utils.decode_base64_and_inflate(xmldata)
else:
decoded_xml = xmldata
# own copy
self.xmlstr = decoded_xml[:]
if self.debug:
self.log.info("xmlstr: %s" % (self.xmlstr,))
logger.info("xmlstr: %s" % (self.xmlstr,))
try:
self.message = self.signature_check(decoded_xml)
except TypeError:
raise
except Exception, excp:
if self.log:
self.log.info("EXCEPTION: %s", excp)
logger.info("EXCEPTION: %s", excp)
if not self.message:
if self.log:
self.log.error("Response was not correctly signed")
self.log.info(decoded_xml)
logger.error("Response was not correctly signed")
logger.info(decoded_xml)
raise IncorrectlySigned()
if self.debug:
self.log.info("request: %s" % (self.message,))
logger.info("request: %s" % (self.message,))
try:
valid_instance(self.message)
except NotValid, exc:
if self.log:
self.log.error("Not valid request: %s" % exc.args[0])
else:
print >> sys.stderr, "Not valid request: %s" % exc.args[0]
logger.error("Not valid request: %s" % exc.args[0])
raise
return self
@@ -91,12 +79,8 @@ class Request(object):
assert self.message.version == "2.0"
if self.message.destination and \
self.message.destination not in self.receiver_addrs:
if self.log:
self.log.error("%s != %s" % (self.message.destination,
logger.error("%s != %s" % (self.message.destination,
self.receiver_addrs))
else:
print >> sys.stderr, "%s != %s" % (self.message.destination,
self.receiver_addrs)
raise OtherError("Not destined for me!")
assert self.issue_instant_ok()
@@ -138,16 +122,14 @@ class Request(object):
class LogoutRequest(Request):
def __init__(self, sec_context, receiver_addrs, log=None, timeslack=0,
debug=0):
Request.__init__(self, sec_context, receiver_addrs, log, timeslack,
debug)
Request.__init__(self, sec_context, receiver_addrs, timeslack)
self.signature_check = self.sec.correctly_signed_logout_request
class AttributeQuery(Request):
def __init__(self, sec_context, receiver_addrs, log=None, timeslack=0,
debug=0):
Request.__init__(self, sec_context, receiver_addrs, log, timeslack,
debug)
Request.__init__(self, sec_context, receiver_addrs, timeslack)
self.signature_check = self.sec.correctly_signed_attribute_query
def attribute(self):
@@ -159,8 +141,7 @@ class AttributeQuery(Request):
class AuthnRequest(Request):
def __init__(self, sec_context, attribute_converters, receiver_addrs,
log=None, timeslack=0, debug=0):
Request.__init__(self, sec_context, receiver_addrs, log, timeslack,
debug)
Request.__init__(self, sec_context, receiver_addrs, timeslack)
self.attribute_converters = attribute_converters
self.signature_check = self.sec.correctly_signed_authn_request
@@ -172,8 +153,7 @@ class AuthnRequest(Request):
class AuthzRequest(Request):
def __init__(self, sec_context, receiver_addrs, log=None, timeslack=0,
debug=0):
Request.__init__(self, sec_context, receiver_addrs, log, timeslack,
debug)
Request.__init__(self, sec_context, receiver_addrs, timeslack)
self.signature_check = self.sec.correctly_signed_logout_request
def action(self):

View File

@@ -17,6 +17,7 @@
import calendar
import base64
import logging
import sys
from saml2 import samlp
@@ -38,6 +39,8 @@ from saml2.validate import valid_instance
from saml2.validate import valid_address
from saml2.validate import NotValid
logger = logging.getLogger(__name__)
# ---------------------------------------------------------------------------
class IncorrectlySigned(Exception):
@@ -205,7 +208,7 @@ class StatusResponse(object):
# print "issue_instant: %s" % self.response.issue_instant
# print "%s < x < %s" % (lower, upper)
issued_at = str_to_time(self.response.issue_instant)
return issued_at > lower and issued_at < upper
return lower < issued_at < upper
def _verify(self):
if self.request_id and self.in_response_to and \

View File

@@ -1,4 +1,5 @@
#!/usr/bin/env python
import logging
import time
import base64
@@ -22,6 +23,8 @@ except ImportError:
from md5 import md5
import zlib
logger = logging.getLogger(__name__)
class VersionMismatch(Exception):
pass
@@ -304,3 +307,35 @@ def verify_signature(secret, parts):
return True
else:
return False
FTICKS_FORMAT = "F-TICKS/SWAMID/2.0%s#"
def fticks_log(sp, logf, idp_entity_id, user_id, secret, assertion):
"""
'F-TICKS/' federationIdentifier '/' version *('#' attribute '=' value ) '#'
Allowed attributes:
TS the login time stamp
RP the relying party entityID
AP the asserting party entityID (typcially the IdP)
PN a sha256-hash of the local principal name and a unique key
AM the authentication method URN
:param sp: Client instance
:param logf: The log function to use
:param idp_entity_id: IdP entity ID
:param user_id: The user identifier
:param secret: A salt to make the hash more secure
:param assertion: A SAML Assertion instance gotten from the IdP
"""
csum = hmac.new(secret, digestmod=hashlib.sha1)
csum.update(user_id)
info = {
"TS": time.time(),
"RP": sp.entity_id,
"AP": idp_entity_id,
"PN": csum.hexdigest(),
"AM": assertion.AuthnStatement.AuthnContext.AuthnContextClassRef.text
}
logf.info(FTICKS_FORMAT % "#".join(["%s=%s" % (a,v) for a,v in info]))

View File

@@ -18,6 +18,7 @@
"""Contains classes and functions that a SAML2.0 Identity provider (IdP)
or attribute authority (AA) may use to conclude its tasks.
"""
import logging
import shelve
import sys
@@ -57,20 +58,20 @@ from saml2.config import config_factory
from saml2.assertion import Assertion, Policy
logger = logging.getLogger(__name__)
class UnknownVO(Exception):
pass
class Identifier(object):
""" A class that handles identifiers of objects """
def __init__(self, db, voconf=None, debug=0, log=None):
def __init__(self, db, voconf=None):
if isinstance(db, basestring):
self.map = shelve.open(db, writeback=True)
else:
self.map = db
self.voconf = voconf
self.debug = debug
self.log = log
def _store(self, typ, entity_id, local, remote):
self.map["|".join([typ, entity_id, "f", local])] = remote
self.map["|".join([typ, entity_id, "b", remote])] = local
@@ -277,8 +278,7 @@ class Server(object):
idb = addr
if idb is not None:
self.ident = Identifier(idb, self.conf.virtual_organization,
self.debug, self.log)
self.ident = Identifier(idb, self.conf.virtual_organization)
else:
raise Exception("Couldn't open identity database: %s" %
(dbspec,))

View File

@@ -20,6 +20,7 @@ Based on the use of xmlsec1 binaries and not the python xmlsec module.
"""
import base64
import logging
import random
import os
import sys
@@ -40,6 +41,8 @@ from saml2.time_util import instant
from tempfile import NamedTemporaryFile
from subprocess import Popen, PIPE
logger = logging.getLogger(__name__)
SIG = "{%s#}%s" % (ds.NAMESPACE, "Signature")
def signed(item):
@@ -321,9 +324,12 @@ def parse_xmlsec_output(output):
__DEBUG = 0
LOG_LINE = 60*"="+"\n%s\n"+60*"-"+"\n%s"+60*"="
LOG_LINE_2 = 60*"="+"\n%s\n%s\n"+60*"-"+"\n%s"+60*"="
def verify_signature(enctext, xmlsec_binary, cert_file=None, cert_type="pem",
node_name=NODE_NAME, debug=False, node_id=None,
log=None, id_attr=""):
id_attr=""):
""" Verifies the signature of a XML document.
:param enctext: The signed XML document
@@ -374,12 +380,7 @@ def verify_signature(enctext, xmlsec_binary, cert_file=None, cert_type="pem",
print p_err
verified = parse_xmlsec_output(p_err)
except XmlsecError, exc:
if log:
log.error(60*"=")
log.error(p_out)
log.error(60*"-")
log.error("%s" % exc)
log.error(60*"=")
logger.error(LOG_LINE % (p_out, exc))
raise SignatureError("%s" % (exc,))
return verified
@@ -421,12 +422,10 @@ def read_cert_from_file(cert_file, cert_type):
data = open(cert_file).read()
return base64.b64encode(str(data))
def security_context(conf, log=None, debug=None):
def security_context(conf, debug=None):
""" Creates a security context based on the configuration
:param conf: The configuration
:param log: A logger if different from the one specified in the
configuration
:return: A SecurityContext instance
"""
if not conf:
@@ -443,12 +442,11 @@ def security_context(conf, log=None, debug=None):
return SecurityContext(conf.xmlsec_binary, conf.key_file,
cert_file=conf.cert_file, metadata=metadata,
log=log, debug=debug,
only_use_keys_in_metadata=_only_md)
debug=debug, only_use_keys_in_metadata=_only_md)
class SecurityContext(object):
def __init__(self, xmlsec_binary, key_file="", key_type= "pem",
cert_file="", cert_type="pem", metadata=None, log=None,
cert_file="", cert_type="pem", metadata=None,
debug=False, template="", encrypt_key_type="des-192",
only_use_keys_in_metadata=False):
@@ -465,7 +463,6 @@ class SecurityContext(object):
self.metadata = metadata
self.only_use_keys_in_metadata = only_use_keys_in_metadata
self.log = log
self.debug = debug
if not template:
@@ -476,12 +473,8 @@ class SecurityContext(object):
self.key_type = encrypt_key_type
if self.debug and not self.log:
self.debug = 0
def correctly_signed(self, xml, must=False):
if self.log:
self.log.info("verify correct signature")
logger.info("verify correct signature")
return self.correctly_signed_response(xml, must)
def encrypt(self, text, recv_key="", template="", key_type=""):
@@ -501,8 +494,7 @@ class SecurityContext(object):
if not template:
template = self.template
if self.log:
self.log.info("input len: %d" % len(text))
logger.info("input len: %d" % len(text))
_, fil = make_temp("%s" % text, decode=False)
ntf = NamedTemporaryFile()
@@ -513,8 +505,7 @@ class SecurityContext(object):
"--output", ntf.name,
template]
if self.debug:
self.log.debug("Encryption command: %s" % " ".join(com_list))
logger.debug("Encryption command: %s" % " ".join(com_list))
pof = Popen(com_list, stderr=PIPE, stdout=PIPE)
@@ -522,14 +513,8 @@ class SecurityContext(object):
try:
parse_xmlsec_output(p_err)
except XmlsecError, exc:
if self.debug:
p_out = pof.stdout.read()
self.log.error(60*"=")
self.log.error(p_out)
self.log.error(p_err)
self.log.error(60*"-")
self.log.error("%s" % exc)
self.log.error(60*"=")
p_out = pof.stdout.read()
logger.error(LOG_LINE_2 % (p_out, p_err, exc))
raise DecryptError("%s" % (exc,))
ntf.seek(0)
@@ -542,8 +527,7 @@ class SecurityContext(object):
:return: The decrypted text
"""
if self.log:
self.log.info("input len: %d" % len(enctext))
logger.info("input len: %d" % len(enctext))
_, fil = make_temp("%s" % enctext, decode=False)
ntf = NamedTemporaryFile()
@@ -553,8 +537,7 @@ class SecurityContext(object):
"--id-attr:%s" % ID_ATTR, ENC_KEY_CLASS,
fil]
if self.debug:
self.log.debug("Decrypt command: %s" % " ".join(com_list))
logger.debug("Decrypt command: %s" % " ".join(com_list))
pof = Popen(com_list, stderr=PIPE, stdout=PIPE)
@@ -562,14 +545,8 @@ class SecurityContext(object):
try:
parse_xmlsec_output(p_err)
except XmlsecError, exc:
if self.debug:
p_out = pof.stdout.read()
self.log.error(60*"=")
self.log.error(p_out)
self.log.error(p_err)
self.log.error(60*"-")
self.log.error("%s" % exc)
self.log.error(60*"=")
p_out = pof.stdout.read()
logger.error(LOG_LINE_2 % (p_out, p_err, exc))
raise DecryptError("%s" % (exc,))
ntf.seek(0)
@@ -639,12 +616,10 @@ class SecurityContext(object):
verified = True
break
except XmlsecError, exc:
if self.log:
self.log.error("check_sig: %s" % exc)
logger.error("check_sig: %s" % exc)
pass
except Exception, exc:
if self.log:
self.log.error("check_sig: %s" % exc)
logger.error("check_sig: %s" % exc)
raise
if not verified:
@@ -776,21 +751,18 @@ class SecurityContext(object):
# Try to find the signing cert in the assertion
for assertion in response.assertion:
if not assertion.signature:
if self.debug:
self.log.debug("unsigned")
logger.debug("unsigned")
if must:
raise SignatureError("Signature missing")
continue
else:
if self.debug:
self.log.debug("signed")
logger.debug("signed")
try:
self._check_signature(decoded_xml, assertion,
class_name(assertion), origdoc)
except Exception, exc:
if self.log:
self.log.error("correctly_signed_response: %s" % exc)
logger.error("correctly_signed_response: %s" % exc)
raise
return response

View File

@@ -18,6 +18,7 @@
"""
Suppport for the client part of the SAML2.0 SOAP binding.
"""
import logging
from httplib2 import Http
@@ -37,6 +38,9 @@ except ImportError:
#noinspection PyUnresolvedReferences
from elementtree import ElementTree
logger = logging.getLogger(__name__)
class XmlParseError(Exception):
pass
@@ -197,9 +201,8 @@ def soap_fault(message=None, actor=None, code=None, detail=None):
class HTTPClient(object):
""" For sending a message to a HTTP server using POST or GET """
def __init__(self, path, keyfile=None, certfile=None, log=None,
cookiejar=None, ca_certs="",
disable_ssl_certificate_validation=True):
def __init__(self, path, keyfile=None, certfile=None, cookiejar=None,
ca_certs="", disable_ssl_certificate_validation=True):
self.path = path
if cookiejar is not None:
self.cj = True
@@ -210,7 +213,7 @@ class HTTPClient(object):
self.cj = False
self.server = Http(ca_certs=ca_certs,
disable_ssl_certificate_validation=disable_ssl_certificate_validation)
self.log = log
self.response = None
if keyfile:
@@ -299,13 +302,12 @@ class HTTPClient(object):
class SOAPClient(object):
def __init__(self, server_url, keyfile=None, certfile=None, log=None,
def __init__(self, server_url, keyfile=None, certfile=None,
cookiejar=None, ca_certs="",
disable_ssl_certificate_validation=True):
self.server = HTTPClient(server_url, keyfile, certfile, log,
cookiejar, ca_certs=ca_certs,
self.server = HTTPClient(server_url, keyfile, certfile, cookiejar,
ca_certs=ca_certs,
disable_ssl_certificate_validation=disable_ssl_certificate_validation)
self.log = log
self.response = None
def send(self, request, path=None, headers=None, sign=None, sec=None):
@@ -325,8 +327,7 @@ class SOAPClient(object):
self.response = _response
if _response:
if self.log:
self.log.info("SOAP response: %s" % _response)
logger.info("SOAP response: %s" % _response)
return parse_soap_enveloped_saml_response(_response)
else:
return False

View File

@@ -1,14 +1,13 @@
import logging
from saml2.attribute_resolver import AttributeResolver
logger = logging.getLogger(__name__)
class VirtualOrg(object):
def __init__(self, sp, vorg, log=None):
self.sp = sp # The parent SP client instance
self.config = sp.config
self.vorg_name = vorg
if log is None:
self.log = self.sp.logger
else:
self.log = log
self.vorg_conf = self.config.vo_conf(self.vorg_name)
def _cache_session(self, session_info):
@@ -44,8 +43,7 @@ class VirtualOrg(object):
# Remove the ones I have cached data from about this subject
vo_members = [m for m in vo_members if not self.sp.users.cache.active(
subject_id, m)]
if self.log:
self.log.info("VO members (not cached): %s" % vo_members)
logger.info("VO members (not cached): %s" % vo_members)
return vo_members
def get_common_identifier(self, subject_id):
@@ -61,12 +59,9 @@ class VirtualOrg(object):
return None
def do_aggregation(self, subject_id, log=None):
if log is None:
log = self.log
if log:
log.info("** Do VO aggregation **")
log.info("SubjectID: %s, VO:%s" % (subject_id, self.vorg_name))
logger.info("** Do VO aggregation **\nSubjectID: %s, VO:%s" % (
subject_id, self.vorg_name))
to_ask = self.members_to_ask(subject_id)
if to_ask:
@@ -90,11 +85,9 @@ class VirtualOrg(object):
log=log, real_id=subject_id):
_ = self._cache_session(session_info)
if log:
log.info(
">Issuers: %s" % self.sp.users.issuers_of_info(subject_id))
log.info(
"AVA: %s" % (self.sp.users.get_identity(subject_id),))
logger.info(">Issuers: %s" % self.sp.users.issuers_of_info(
subject_id))
logger.info("AVA: %s" % (self.sp.users.get_identity(subject_id),))
return True
else: