1
0
mirror of https://github.com/kakwa/ldapcherry synced 2024-11-26 03:04:30 +01:00

begin implementation of the main application

This commit is contained in:
kakwa 2015-05-18 19:56:44 +02:00
parent ff6e996912
commit d6bb5c38ed
7 changed files with 312 additions and 89 deletions

View File

@ -96,7 +96,7 @@ auth.mode = 'or'
# resources parameters # resources parameters
[resources] [resources]
# templates directory # templates directory
template_dir = '/usr/share/ldapcherry/templates/' templates.dir = '/usr/share/ldapcherry/templates/'
[/static] [/static]
tools.staticdir.on = True tools.staticdir.on = True

View File

@ -15,6 +15,8 @@ import logging.handlers
from operator import itemgetter from operator import itemgetter
from socket import error as socket_error from socket import error as socket_error
from exceptions import *
#cherrypy http framework imports #cherrypy http framework imports
import cherrypy import cherrypy
from cherrypy.lib.httputil import parse_query_string from cherrypy.lib.httputil import parse_query_string
@ -25,8 +27,38 @@ from mako import lookup
SESSION_KEY = '_cp_username' 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): 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): def _get_param(self, section, key, config, default=None):
""" Get configuration parameter "key" from config """ Get configuration parameter "key" from config
@str section: the section of the config file @str section: the section of the config file
@ -42,106 +74,99 @@ class LdapCherry(object):
else: else:
raise MissingParameter(section, key) 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): def reload(self, config = None):
""" load/reload the configuration """ load/reload the configuration
""" """
try: try:
# definition of the template directory
self.template_dir = self._get_param('resources', 'template_dir', config)
# log configuration handling # log configuration handling
# get log level # get log level
# (if not in configuration file, log level is set to debug) # (if not in configuration file, log level is set to debug)
level = self._get_loglevel(self._get_param('global', 'log.level', config, '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 # definition of the template directory
syslog_formatter = logging.Formatter( self.template_dir = self._get_param('resources', 'templates.dir', config)
"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 # preload templates
self.temp_lookup = lookup.TemplateLookup( self.temp_lookup = lookup.TemplateLookup(
directories=self.template_dir, input_encoding='utf-8' directories=self.template_dir, input_encoding='utf-8'
) )
self.temp_index = self.temp_lookup.get_template('index.tmpl') 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_error = self.temp_lookup.get_template('error.tmpl')
self.temp_login = self.temp_lookup.get_template('login.tmpl') self.temp_login = self.temp_lookup.get_template('login.tmpl')
# loading the authentification module # loading the authentification module
auth_module = self._get_param('auth', 'auth.module', config) #auth_module = self._get_param('auth', 'auth.module', config)
auth = __import__(auth_module, globals(), locals(), ['Auth'], -1) #auth = __import__(auth_module, globals(), locals(), ['Auth'], -1)
self.auth = auth.Auth(config['auth'], cherrypy.log) #self.auth = auth.Auth(config['auth'], cherrypy.log)
except MissingParameter as e: except Exception as e:
cherrypy.log.error( self._handle_exception(e)
msg = "ldapcherry failure, "\
"missing parameter '%(param)s' "\
"in section '%(section)s'" % {
'param': e.key,
'section': e.section
},
severity = logging.ERROR
)
exit(1) exit(1)
def _get_loglevel(self, level): def _get_loglevel(self, level):
@ -177,13 +202,6 @@ class LdapCherry(object):
and returns the right error page and emits a log 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 # log and error page handling
def render_error(alert, message): def render_error(alert, message):
if alert == 'danger': if alert == 'danger':

View File

@ -39,6 +39,13 @@ class MissingRolesFile(Exception):
self.rolefile = rolefile self.rolefile = rolefile
self.log = "fail to open role file <%(rolefile)s>" % { '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): class MissingAttributesFile(Exception):
def __init__(self, attributesfile): def __init__(self, attributesfile):
self.attributesfile = attributesfile self.attributesfile = attributesfile

View File

@ -0,0 +1,15 @@
<%inherit file="navbar.tmpl"/>
<%block name="core">
<div class="row clearfix">
<div class="col-md-12 column">
<div class="alert alert-dismissable alert-${alert}">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
<h4>
${message}
</h4>
</div>
<a class="btn btn-default blue" href='/?zone=${current_zone}'><span class="glyphicon glyphicon-home"> Return</a>
</div>
</div>
</%block>

View File

@ -0,0 +1,29 @@
<%inherit file="base.tmpl"/>
<%block name="core">
<div class="row clearfix" style="margin-top:30px">
<div class="col-md-4 column"></div>
<div class="col-md-4 column well">
<form method='POST' action='/login' role="form" class="form-signin">
<div class="form-group">
<h2 class="form-signin-heading">Please sign in</h2>
<div class="input-group">
<span class="input-group-addon glyphicon glyphicon-user"></span>
<input type="text" class="form-control" name="login" placeholder="Login" required autofocus>
</div>
</div>
<div class="form-group">
<div class="input-group">
<span class="input-group-addon glyphicon glyphicon-lock"></span>
<input type="password" class="form-control" name="password" placeholder="Password" required>
</div>
</div>
<div class="form-group">
<div class="input-group">
<button class="btn btn-default blue" type="submit"><span class="glyphicon glyphicon-off"> Sign in</button>
</div>
</div>
</form>
</div>
<div class="col-md-4 column"></div>
</div>
</%block>

103
tests/cfg/ldapcherry.ini Normal file
View File

@ -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/'

51
tests/test_LdapCherry.py Normal file
View File

@ -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