From ea8cf6dbcb79ad05f84f2e0150f15d6252fea1ce Mon Sep 17 00:00:00 2001 From: Roland Hedberg Date: Thu, 31 Mar 2011 12:41:52 +0200 Subject: [PATCH] Added logger specification to the configuration --- src/saml2/client.py | 45 +++++++++++++++++----- src/saml2/config.py | 76 ++++++++++++++++++++++++++++++++++++- src/saml2/server.py | 8 +++- tests/server_conf.py | 10 ++++- tests/server_conf_syslog.py | 54 ++++++++++++++++++++++++++ tests/test_31_config.py | 37 ++++++++++++++++++ 6 files changed, 217 insertions(+), 13 deletions(-) create mode 100644 tests/server_conf_syslog.py diff --git a/src/saml2/client.py b/src/saml2/client.py index de09fcc..f9bc2ba 100644 --- a/src/saml2/client.py +++ b/src/saml2/client.py @@ -104,6 +104,8 @@ class Saml2Client(object): self.metadata = self.config.metadata self.sec = security_context(config) + self.logger = self.config.setup_logger() + if virtual_organization: self.vorg = VirtualOrg(self, virtual_organization) else: @@ -180,6 +182,9 @@ 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 @@ -255,7 +260,10 @@ 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) @@ -315,7 +323,10 @@ class Saml2Client(object): location = self._sso_location(entityid) 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("location: %s" % location) @@ -419,7 +430,10 @@ class Saml2Client(object): :param log: Function to use for logging :return: The attributes returned """ - + + if log is None: + log = self.logger + session_id = sid() issuer = self.issuer(issuer_id) @@ -448,7 +462,7 @@ class Saml2Client(object): log.info("Verifying response") try: - aresp = attribute_response(self.config, "", issuer, log=log) + aresp = attribute_response(self.config, issuer, log) except Exception, exc: if log: log.error("%s", (exc,)) @@ -468,8 +482,7 @@ class Saml2Client(object): return None def construct_logout_request(self, subject_id, destination, - issuer_entity_id, - reason=None, expire=None): + issuer_entity_id, reason=None, expire=None): """ Constructs a LogoutRequest :param subject_id: The identifier of the subject @@ -526,7 +539,10 @@ class Saml2Client(object): if SOAP binding has been used the just the result of that conversation. """ - + + if log is None: + log = self.logger + if log: log.info("logout request for: %s" % subject_id) @@ -548,7 +564,9 @@ 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 @@ -645,6 +663,9 @@ class Saml2Client(object): :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,)) status = self.state[response.in_response_to] @@ -679,6 +700,8 @@ class Saml2Client(object): """ response = None + if log is None: + log = self.logger if xmlstr: try: @@ -732,6 +755,8 @@ class Saml2Client(object): """ headers = [] success = False + if log is None: + log = self.logger try: saml_request = get['SAMLRequest'] @@ -779,7 +804,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) diff --git a/src/saml2/config.py b/src/saml2/config.py index c88fabc..855a9c2 100644 --- a/src/saml2/config.py +++ b/src/saml2/config.py @@ -3,9 +3,15 @@ __author__ = 'rolandh' import sys +import logging +import logging.handlers + from importlib import import_module + from saml2 import BINDING_SOAP, BINDING_HTTP_REDIRECT from saml2 import metadata +from saml2 import root_logger + from saml2.attribute_converter import ac_factory from saml2.assertion import Policy @@ -16,6 +22,7 @@ COMMON_ARGS = ["entityid", "xmlsec_binary", "debug", "key_file", "cert_file", "contact_person", "name_form", "virtual_organization", + "logger" ] SP_ARGS = [ @@ -48,6 +55,24 @@ SPEC = { "aa": COMMON_ARGS + COMPLEX_ARGS + AA_IDP_ARGS, } +# --------------- Logging stuff --------------- + +LOG_LEVEL = {'debug': logging.DEBUG, + 'info': logging.INFO, + 'warning': logging.WARNING, + 'error': logging.ERROR, + 'critical': logging.CRITICAL} + +LOG_HANDLER = { + "rotating": logging.handlers.RotatingFileHandler, + "syslog": logging.handlers.SysLogHandler, + "timerotate": logging.handlers.TimedRotatingFileHandler, +} + +LOG_FORMAT = "%(asctime)s - %(name)s - %(levelname)s - %(message)s" + +# ----------------------------------------------------------------- + class Config(object): def_context = "" @@ -193,6 +218,52 @@ class Config(object): except IndexError: return None + def setup_logger(self): + try: + _logconf = self.logger + except KeyError: + return None + + if root_logger.level != logging.NOTSET: # Someone got there before me + return root_logger + + if "loglevel" in _logconf: + root_logger.setLevel(LOG_LEVEL[_logconf["loglevel"]]) + else: # reasonable default + root_logger.setLevel(logging.WARNING) + + handler = None + for htyp in LOG_HANDLER: + if htyp in _logconf: + if htyp == "syslog": + args = _logconf[htyp] + if "socktype" in args: + import socket + if args["socktype"] == "dgram": + args["socktype"] = socket.SOCK_DGRAM + elif args["socktype"] == "stream": + args["socktype"] = socket.SOCK_STREAM + else: + raise Exception("Unknown socktype!") + handler = LOG_HANDLER[htyp](**args) + else: + handler = LOG_HANDLER[htyp](**_logconf[htyp]) + break + + if handler is None: + raise Exception("You have to define a log handler") + + if "format" in _logconf: + formatter = logging.Formatter(_logconf["format"]) + else: + formatter = logging.Formatter(LOG_FORMAT) + + handler.setFormatter(formatter) + root_logger.addHandler(handler) + + return root_logger + + class SPConfig(Config): def_context = "sp" @@ -238,7 +309,7 @@ class SPConfig(Config): return [] - def idps(self, langpref=["en"]): + def idps(self, langpref=None): """ Returns a dictionary of usefull IdPs, the keys being the entity ID of the service and the names of the services as values @@ -246,6 +317,9 @@ class SPConfig(Config): is used. :return: Dictionary """ + if langpref is None: + langpref = ["en"] + if self.idp: return dict([(e, nd[0]) for (e, nd) in self.metadata.idps(langpref).items() if e in self.idp]) diff --git a/src/saml2/server.py b/src/saml2/server.py index 47feac3..4f18993 100644 --- a/src/saml2/server.py +++ b/src/saml2/server.py @@ -214,9 +214,13 @@ class Server(object): self.conf = config else: raise Exception("Missing configuration") - + + if self.log is None: + self.log = self.conf.setup_logger() + self.metadata = self.conf.metadata self.sec = security_context(self.conf, log) + # if cache: # if isinstance(cache, basestring): # self.cache = Cache(cache) @@ -638,7 +642,7 @@ class Server(object): sp_entity_id = request.issuer.text.strip() binding = None - destination = "" + destinations = [] for binding in bindings: destinations = self.conf.single_logout_services(sp_entity_id, binding) diff --git a/tests/server_conf.py b/tests/server_conf.py index 3070dc8..89ff1a1 100644 --- a/tests/server_conf.py +++ b/tests/server_conf.py @@ -40,5 +40,13 @@ CONFIG={ "email_address": ["tech@eample.com", "tech@example.org"], "contact_type": "technical" }, - ] + ], + "logger": { + "rotating": { + "filename": "sp.log", + "maxBytes": 100000, + "backupCount": 5, + }, + "loglevel": "warning", + } } \ No newline at end of file diff --git a/tests/server_conf_syslog.py b/tests/server_conf_syslog.py new file mode 100644 index 0000000..90b179c --- /dev/null +++ b/tests/server_conf_syslog.py @@ -0,0 +1,54 @@ +__author__ = 'rolandh' + +CONFIG={ + "entityid" : "urn:mace:example.com:saml:roland:sp", + "name" : "urn:mace:example.com:saml:roland:sp", + "description": "My own SP", + "service": { + "sp": { + "endpoints":{ + "assertion_consumer_service": ["http://lingon.catalogix.se:8087/"], + }, + "required_attributes": ["surName", "givenName", "mail"], + "optional_attributes": ["title"], + "idp": ["urn:mace:example.com:saml:roland:idp"], + } + }, + "debug" : 1, + "key_file" : "test.key", + "cert_file" : "test.pem", + "xmlsec_binary" : "/usr/local/bin/xmlsec1", + "metadata": { + "local": ["idp.xml", "vo_metadata.xml"], + }, + "virtual_organization" : { + "urn:mace:example.com:it:tek":{ + "nameid_format" : "urn:oid:1.3.6.1.4.1.1466.115.121.1.15-NameID", + "common_identifier": "umuselin", + } + }, + "subject_data": "subject_data.db", + "accepted_time_diff": 60, + "attribute_map_dir" : "attributemaps", + "organization": { + "name": ("AB Exempel", "se"), + "display_name": ("AB Exempel", "se"), + "url": "http://www.example.org", + }, + "contact_person": [{ + "given_name": "Roland", + "sur_name": "Hedberg", + "telephone_number": "+46 70 100 0000", + "email_address": ["tech@eample.com", "tech@example.org"], + "contact_type": "technical" + }, + ], + "logger": { + "syslog": { + "address": ("localhost", 514), + "facility": "local3", + "socktype": "dgram", + }, + "loglevel": "info", + } +} \ No newline at end of file diff --git a/tests/test_31_config.py b/tests/test_31_config.py index 7a77412..80388fa 100644 --- a/tests/test_31_config.py +++ b/tests/test_31_config.py @@ -6,6 +6,9 @@ from saml2.config import SPConfig, IdPConfig, Config from saml2.metadata import MetaData from py.test import raises +from saml2 import root_logger +import logging + sp1 = { "entityid" : "urn:mace:umu.se:saml:roland:sp", "service": { @@ -196,6 +199,40 @@ def test_wayf(): idps = c.idps(["se","en"]) assert idps == {'urn:mace:example.com:saml:roland:idp': 'Exempel AB'} + c.setup_logger() + + assert root_logger.level != logging.NOTSET + assert root_logger.level == logging.WARNING + assert len(root_logger.handlers) == 1 + assert isinstance(root_logger.handlers[0], + logging.handlers.RotatingFileHandler) + handler = root_logger.handlers[0] + assert handler.backupCount == 5 + assert handler.maxBytes == 100000 + assert handler.mode == "a" + assert root_logger.name == "pySAML2" + assert root_logger.level == 30 + +def test_conf_syslog(): + c = SPConfig().load_file("server_conf_syslog") + c.context = "sp" + + # otherwise the logger setting is not changed + root_logger.level == logging.NOTSET + c.setup_logger() + + assert root_logger.level != logging.NOTSET + assert root_logger.level == logging.INFO + assert len(root_logger.handlers) == 1 + assert isinstance(root_logger.handlers[0], + logging.handlers.SyslogHandler) + handler = root_logger.handlers[0] + assert handler.backupCount == 5 + assert handler.maxBytes == 100000 + assert handler.mode == "a" + assert root_logger.name == "pySAML2" + assert root_logger.level == 20 + #noinspection PyUnresolvedReferences def test_3(): cnf = Config()