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:
parent
ff6e996912
commit
d6bb5c38ed
@ -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
|
||||||
|
@ -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':
|
||||||
|
@ -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
|
||||||
|
15
resources/templates/error.tmpl
Normal file
15
resources/templates/error.tmpl
Normal 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>
|
||||||
|
|
29
resources/templates/login.tmpl
Normal file
29
resources/templates/login.tmpl
Normal 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
103
tests/cfg/ldapcherry.ini
Normal 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
51
tests/test_LdapCherry.py
Normal 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
|
||||||
|
|
Loading…
Reference in New Issue
Block a user