diff --git a/ldapcherry/__init__.py b/ldapcherry/__init__.py index baa706b..6e81d7d 100644 --- a/ldapcherry/__init__.py +++ b/ldapcherry/__init__.py @@ -1,7 +1,298 @@ +#!/usr/bin/env python # -*- coding: utf-8 -*- # vim:set expandtab tabstop=4 shiftwidth=4: # # The MIT License (MIT) -# LdapCherry +# ldapCherry # Copyright (c) 2014 Carpentier Pierre-Francois +#generic imports +import sys +import re +import traceback +import logging +import logging.handlers +from operator import itemgetter +from socket import error as socket_error + +#cherrypy http framework imports +import cherrypy +from cherrypy.lib.httputil import parse_query_string + +#mako template engines imports +from mako.template import Template +from mako import lookup + +SESSION_KEY = '_cp_username' + +class ldapCherry(object): + + def _get_param(self, section, key, config, default=None): + """ Get configuration parameter "key" from config + @str section: the section of the config file + @str key: the key to get + @dict config: the configuration (dictionnary) + @str default: the default value if parameter "key" is not present + @rtype: str (value of config['key'] if present default otherwith + """ + if section in config and key in config[section]: + return config[section][key] + if not default is None: + return default + else: + raise MissingParameter(section, key) + + 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')) + + # 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) + + # 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) + + 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 + ) + exit(1) + + def _get_loglevel(self, level): + """ return logging level object + corresponding to a given level passed as + a string + """ + if level == 'debug': + return logging.DEBUG + elif level == 'notice': + return logging.NOTICE + elif level == 'info': + return logging.INFO + elif level == 'warning' or level == 'warn': + return logging.WARNING + elif level == 'error' or level == 'err': + return logging.ERROR + elif level == 'critical' or level == 'crit': + return logging.CRITICAL + elif level == 'alert': + return logging.ALERT + elif level == 'emergency' or level == 'emerg': + return logging.EMERGENCY + else: + return logging.INFO + + def _reraise(self, exception): + """ reraise a given exception""" + raise exception + + def _error_handler(self, exception, backend=''): + """ exception handling function, takes an exception + 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': + severity = logging.ERROR + elif alert == 'warning': + severity = logging.WARNING + else: + severity = logging.CRITICAL + + cherrypy.log.error( + msg = message, + severity = severity + ) + + return self.temp_error.render( + logout_button = self.auth.logout_button, + alert = alert, + message = message, + ) + + # reraise the exception + try: + self._reraise(exception) + + # error handling + except ldapcherry.exception.MissingParameter: + cherrypy.response.status = 500 + alert = 'danger' + message = 'Example danger' + return render_error(alert, message) + + except KeyError: + cherrypy.response.status = 400 + alert = 'warning' + message = 'Example warning' + return render_error(alert, message) + + @cherrypy.expose + def signin(self): + """simple signin page + """ + return self.temp_login.render() + + @cherrypy.expose + def login(self, login, password): + """login page + """ + if self.auth.check_credentials(login, password): + message = "login success for user '%(user)s'" % { + 'user': login + } + cherrypy.log.error( + msg = message, + severity = logging.INFO + ) + cherrypy.session[SESSION_KEY] = cherrypy.request.login = login + raise cherrypy.HTTPRedirect("/") + else: + message = "login failed for user '%(user)s'" % { + 'user': login + } + cherrypy.log.error( + msg = message, + severity = logging.WARNING + ) + raise cherrypy.HTTPRedirect("/signin") + + @cherrypy.expose + def logout(self): + """ logout page + """ + user = self.auth.end_session() + message = "user '%(user)s' logout" % { + 'user': user + } + cherrypy.log.error( + msg = message, + severity = logging.INFO + ) + + raise cherrypy.HTTPRedirect("/signin") + + @cherrypy.expose + def index(self, **params): + """main page rendering + """ + pass + + @cherrypy.expose + def searchuser(self): + """ search user page """ + pass + + @cherrypy.expose + def adduser(self): + """ add user page """ + pass + + @cherrypy.expose + def removeuser(self): + """ remove user page """ + pass + + @cherrypy.expose + def modifyuser(self): + """ modify user page """ + pass + + @cherrypy.expose + def modifyself(self): + """ self modify user page """ + pass diff --git a/ldapcherry/exceptions.py b/ldapcherry/exceptions.py index baa706b..f8e91cf 100644 --- a/ldapcherry/exceptions.py +++ b/ldapcherry/exceptions.py @@ -5,3 +5,7 @@ # LdapCherry # Copyright (c) 2014 Carpentier Pierre-Francois +class MissingParameter(Exception): + def __init__(self, section, key): + self.section = section + self.key = key