diff --git a/conf/ldapcherry.ini b/conf/ldapcherry.ini index e3fdf4e..f70081c 100644 --- a/conf/ldapcherry.ini +++ b/conf/ldapcherry.ini @@ -96,7 +96,7 @@ auth.mode = 'or' # resources parameters [resources] # templates directory -template_dir = '/usr/share/ldapcherry/templates/' +templates.dir = '/usr/share/ldapcherry/templates/' [/static] tools.staticdir.on = True diff --git a/ldapcherry/__init__.py b/ldapcherry/__init__.py index b8aa19b..40dfb64 100644 --- a/ldapcherry/__init__.py +++ b/ldapcherry/__init__.py @@ -15,6 +15,8 @@ import logging.handlers from operator import itemgetter from socket import error as socket_error +from exceptions import * + #cherrypy http framework imports import cherrypy from cherrypy.lib.httputil import parse_query_string @@ -25,8 +27,38 @@ from mako import lookup SESSION_KEY = '_cp_username' +# Custom log function to overrige weird error.log function +# of cherrypy +def syslog_error(msg='', context='', + severity=logging.INFO, traceback=False): + if traceback: + msg += cherrypy._cperror.format_exc() + if context == '': + cherrypy.log.error_log.log(severity, msg) + else: + cherrypy.log.error_log.log(severity, + ' '.join((context, msg))) + class LdapCherry(object): + def _handle_exception(self, e): + if hasattr(e, 'log'): + cherrypy.log.error( + msg = e.log, + severity = logging.ERROR + ) + else: + cherrypy.log.error( + msg = "Unkwon exception <%(e)s>" % { 'e' : str(e) }, + severity = logging.ERROR + ) + # log the traceback as 'debug' + cherrypy.log.error( + msg = '', + severity = logging.DEBUG, + traceback= True + ) + def _get_param(self, section, key, config, default=None): """ Get configuration parameter "key" from config @str section: the section of the config file @@ -42,106 +74,99 @@ class LdapCherry(object): else: raise MissingParameter(section, key) + def _set_access_log(self, config, level): + access_handler = self._get_param('global', 'log.access_handler', config, 'syslog') + + # log format for syslog + syslog_formatter = logging.Formatter( + "ldapcherry[%(process)d]: %(message)s") + + # replace access log handler by a syslog handler + if access_handler == 'syslog': + cherrypy.log.access_log.handlers = [] + handler = logging.handlers.SysLogHandler(address = '/dev/log', + facility='user') + handler.setFormatter(syslog_formatter) + cherrypy.log.access_log.addHandler(handler) + + # if file, we keep the default + elif access_handler == 'file': + pass + + # replace access log handler by a null handler + elif access_handler == 'none': + cherrypy.log.access_log.handlers = [] + handler = logging.NullHandler() + cherrypy.log.access_log.addHandler(handler) + + # set log level + cherrypy.log.access_log.setLevel(level) + + def _set_error_log(self, config, level): + error_handler = self._get_param('global', 'log.error_handler', config, 'syslog') + + # log format for syslog + syslog_formatter = logging.Formatter( + "ldapcherry[%(process)d]: %(message)s") + + # replacing the error handler by a syslog handler + if error_handler == 'syslog': + cherrypy.log.error_log.handlers = [] + + # redefining log.error method because cherrypy does weird + # things like adding the date inside the message + # or adding space even if context is empty + # (by the way, what's the use of "context"?) + cherrypy.log.error = syslog_error + + handler = logging.handlers.SysLogHandler(address = '/dev/log', + facility='user') + handler.setFormatter(syslog_formatter) + cherrypy.log.error_log.addHandler(handler) + + # if file, we keep the default + elif error_handler == 'file': + pass + + # replacing the error handler by a null handler + elif error_handler == 'none': + cherrypy.log.error_log.handlers = [] + handler = logging.NullHandler() + cherrypy.log.error_log.addHandler(handler) + + # set log level + cherrypy.log.error_log.setLevel(level) + def reload(self, config = None): """ load/reload the configuration """ - try: - # definition of the template directory - self.template_dir = self._get_param('resources', 'template_dir', config) # log configuration handling # get log level # (if not in configuration file, log level is set to debug) level = self._get_loglevel(self._get_param('global', 'log.level', config, 'debug')) + # configure access log + self._set_access_log(config, level) + # configure error log + self._set_error_log(config, level) - # log format for syslog - syslog_formatter = logging.Formatter( - "ldapcherry[%(process)d]: %(message)s") - - access_handler = self._get_param('global', 'log.access_handler', config, 'syslog') - - # replace access log handler by a syslog handler - if access_handler == 'syslog': - cherrypy.log.access_log.handlers = [] - handler = logging.handlers.SysLogHandler(address = '/dev/log', - facility='user') - handler.setFormatter(syslog_formatter) - cherrypy.log.access_log.addHandler(handler) - - # if file, we keep the default - elif access_handler == 'file': - pass - - # replace access log handler by a null handler - elif access_handler == 'none': - cherrypy.log.access_log.handlers = [] - handler = logging.NullHandler() - cherrypy.log.access_log.addHandler(handler) - - error_handler = self._get_param('global', 'log.error_handler', config, 'syslog') - - # replacing the error handler by a syslog handler - if error_handler == 'syslog': - cherrypy.log.error_log.handlers = [] - - # redefining log.error method because cherrypy does weird - # things like adding the date inside the message - # or adding space even if context is empty - # (by the way, what's the use of "context"?) - def syslog_error(msg='', context='', - severity=logging.INFO, traceback=False): - if traceback: - msg += cherrypy._cperror.format_exc() - if context == '': - cherrypy.log.error_log.log(severity, msg) - else: - cherrypy.log.error_log.log(severity, - ' '.join((context, msg))) - cherrypy.log.error = syslog_error - - handler = logging.handlers.SysLogHandler(address = '/dev/log', - facility='user') - handler.setFormatter(syslog_formatter) - cherrypy.log.error_log.addHandler(handler) - - # if file, we keep the default - elif error_handler == 'file': - pass - - # replacing the error handler by a null handler - elif error_handler == 'none': - cherrypy.log.error_log.handlers = [] - handler = logging.NullHandler() - cherrypy.log.error_log.addHandler(handler) - - # set log level - cherrypy.log.error_log.setLevel(level) - cherrypy.log.access_log.setLevel(level) - + # definition of the template directory + self.template_dir = self._get_param('resources', 'templates.dir', config) # preload templates self.temp_lookup = lookup.TemplateLookup( directories=self.template_dir, input_encoding='utf-8' ) self.temp_index = self.temp_lookup.get_template('index.tmpl') - self.temp_result = self.temp_lookup.get_template('result.tmpl') self.temp_error = self.temp_lookup.get_template('error.tmpl') self.temp_login = self.temp_lookup.get_template('login.tmpl') # loading the authentification module - auth_module = self._get_param('auth', 'auth.module', config) - auth = __import__(auth_module, globals(), locals(), ['Auth'], -1) - self.auth = auth.Auth(config['auth'], cherrypy.log) + #auth_module = self._get_param('auth', 'auth.module', config) + #auth = __import__(auth_module, globals(), locals(), ['Auth'], -1) + #self.auth = auth.Auth(config['auth'], cherrypy.log) - except MissingParameter as e: - cherrypy.log.error( - msg = "ldapcherry failure, "\ - "missing parameter '%(param)s' "\ - "in section '%(section)s'" % { - 'param': e.key, - 'section': e.section - }, - severity = logging.ERROR - ) + except Exception as e: + self._handle_exception(e) exit(1) def _get_loglevel(self, level): @@ -177,13 +202,6 @@ class LdapCherry(object): and returns the right error page and emits a log """ - # log the traceback as 'debug' - cherrypy.log.error( - msg = '', - severity = logging.DEBUG, - traceback= True - ) - # log and error page handling def render_error(alert, message): if alert == 'danger': diff --git a/ldapcherry/exceptions.py b/ldapcherry/exceptions.py index 7034c68..05d4478 100644 --- a/ldapcherry/exceptions.py +++ b/ldapcherry/exceptions.py @@ -39,6 +39,13 @@ class MissingRolesFile(Exception): self.rolefile = rolefile self.log = "fail to open role file <%(rolefile)s>" % { 'rolefile' : rolefile} +class MissingMainFile(Exception): + def __init__(self, config): + self.rolefile = rolefile + self.log = "fail to open main file <%(config)s>" % { 'rolefile' : rolefile} + + + class MissingAttributesFile(Exception): def __init__(self, attributesfile): self.attributesfile = attributesfile diff --git a/resources/templates/error.tmpl b/resources/templates/error.tmpl new file mode 100644 index 0000000..c0fcea4 --- /dev/null +++ b/resources/templates/error.tmpl @@ -0,0 +1,15 @@ +<%inherit file="navbar.tmpl"/> +<%block name="core"> +
+
+
+ +

+ ${message} +

+
+ Return +
+
+ + diff --git a/resources/templates/login.tmpl b/resources/templates/login.tmpl new file mode 100644 index 0000000..361ac04 --- /dev/null +++ b/resources/templates/login.tmpl @@ -0,0 +1,29 @@ +<%inherit file="base.tmpl"/> +<%block name="core"> +
+
+
+ +
+
+
+ diff --git a/tests/cfg/ldapcherry.ini b/tests/cfg/ldapcherry.ini new file mode 100644 index 0000000..f70081c --- /dev/null +++ b/tests/cfg/ldapcherry.ini @@ -0,0 +1,103 @@ +# global parameters +[global] + +# listing interface +server.socket_host = '127.0.0.1' +# port +server.socket_port = 8080 +# number of threads +server.thread_pool = 8 +#don't show traceback on error +request.show_tracebacks = False + +# log configuration +# /!\ you can't have multiple log handlers +##################################### +# configuration to log in files # +##################################### +## logger 'file' for access log +#log.access_handler = 'file' +## logger syslog for error and ldapcherry log +#log.error_handler = 'file' +## access log file +#log.access_file = '/tmp/ldapcherry_access.log' +## error and ldapcherry log file +#log.error_file = '/tmp/ldapcherry_error.log' + +##################################### +# configuration to log in syslog # +##################################### +# logger syslog for access log +#log.access_handler = 'syslog' +## logger syslog for error and ldapcherry log +log.error_handler = 'syslog' + +##################################### +# configuration to not log at all # +##################################### +# logger none for access log +log.access_handler = 'none' +# logger none for error and ldapcherry log +#log.error_handler = 'none' + +# log level +log.level = 'info' + +# session configuration +# activate session +tools.sessions.on = True +# session timeout +tools.sessions.timeout = 10 +# file session storage(to use if multiple processes, +# default is in RAM and per process) +#tools.sessions.storage_type = "file" +# session +#tools.sessions.storage_path = "/var/lib/ldapcherry/sessions" + +[attributes] + +# file discribing form content +attributes.file = '/etc/ldapcherry/attributes.yml' + +[roles] + +# file listing roles +roles.file = '/etc/ldapcherry/roles.yml' + +[backends] + +ldap.module = 'ldapcherry.backends.ldap' +ldap.groupdn = 'ou=group,dc=example,dc=com' +ldap.people = 'ou=group,dc=example,dc=com' +ldap.authdn = 'cn=ldapcherry,dc=example,dc=com' +ldap.password = 'password' +ldap.uri = 'ldaps://ldap.ldapcherry.org' +ldap.ca = '/etc/dnscherry/TEST-cacert.pem' +ldap.starttls = 'on' +ldap.checkcert = 'off' + +ad.module = 'ldapcherry.backends.ad' +ad.auth = 'Administrator' +ad.password = 'password' + +# authentification parameters +[auth] + +# Auth mode +# * and: user must authenticate on all backends +# * or: user must authenticate on one of the backend +# * none: disable authentification +# * custom: custom authentification module (need auth.module param) +auth.mode = 'or' + +# custom auth module to load +#auth.module = 'ldapcherry.auth.modNone' + +# resources parameters +[resources] +# templates directory +templates.dir = '/usr/share/ldapcherry/templates/' + +[/static] +tools.staticdir.on = True +tools.staticdir.dir = '/usr/share/ldapcherry/static/' diff --git a/tests/test_LdapCherry.py b/tests/test_LdapCherry.py new file mode 100644 index 0000000..47d0d63 --- /dev/null +++ b/tests/test_LdapCherry.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from __future__ import with_statement +from __future__ import unicode_literals + +import pytest +import sys +from sets import Set +from ldapcherry import LdapCherry +from ldapcherry.exceptions import DumplicateRoleKey, MissingKey, DumplicateRoleContent, MissingRolesFile, MissingRole +from ldapcherry.pyyamlwrapper import DumplicatedKey, RelationError +import cherrypy +from cherrypy.process import plugins, servers +from cherrypy import Application + +# monkey patching cherrypy to disable config interpolation +def new_as_dict(self, raw=True, vars=None): + """Convert an INI file to a dictionary""" + # Load INI file into a dict + result = {} + for section in self.sections(): + if section not in result: + result[section] = {} + for option in self.options(section): + value = self.get(section, option, raw=raw, vars=vars) + try: + value = cherrypy.lib.reprconf.unrepr(value) + except Exception: + x = sys.exc_info()[1] + msg = ("Config error in section: %r, option: %r, " + "value: %r. Config values must be valid Python." % + (section, option, value)) + raise ValueError(msg, x.__class__.__name__, x.args) + result[section][option] = value + return result +cherrypy.lib.reprconf.Parser.as_dict = new_as_dict + + +def loadconf(configfile, instance): + app = cherrypy.tree.mount(instance, '/', configfile) + cherrypy.config.update(configfile) + instance.reload(app.config) + +class TestError(object): + + def testNominal(self): + app = LdapCherry() + loadconf('./tests/cfg/ldapcherry.ini', app) + return True +