1
0
mirror of https://github.com/kakwa/ldapcherry synced 2024-11-25 18:54:29 +01:00
This commit is contained in:
kakwa 2015-07-25 22:05:23 +02:00
commit 7d55cb2d14
17 changed files with 512 additions and 65 deletions

View File

@ -4,6 +4,20 @@
Nice and simple application to manage users and groups in multiple directory services.
.. image:: https://travis-ci.org/kakwa/ldapcherry.svg?branch=master
:target: https://travis-ci.org/kakwa/ldapcherry
.. image:: https://coveralls.io/repos/kakwa/ldapcherry/badge.svg
:target: https://coveralls.io/r/kakwa/ldapcherry
.. image:: https://img.shields.io/pypi/dm/ldapcherry.svg
:target: https://pypi.python.org/pypi/ldapcherry
:alt: Number of PyPI downloads
.. image:: https://img.shields.io/pypi/v/ldapcherry.svg
:target: https://pypi.python.org/pypi/ldapcherry
:alt: PyPI version
----
:Doc: `ldapcherry documentation on ReadTheDoc <http://ldapcherry.readthedocs.org/en/latest/>`_
@ -14,12 +28,6 @@ Nice and simple application to manage users and groups in multiple directory ser
----
.. image:: https://travis-ci.org/kakwa/ldapcherry.svg?branch=master
:target: https://travis-ci.org/kakwa/ldapcherry
.. image:: https://coveralls.io/repos/kakwa/ldapcherry/badge.svg
:target: https://coveralls.io/r/kakwa/ldapcherry
****************
Presentation
****************
@ -32,12 +40,12 @@ It's main features are:
* roles management (as in "groups of groups")
* autofill forms
* password policy
* self modification of some selected fields by normal (non admin) users
* self modification of some selected fields by normal (non administrator) users
* nice bootstrap interface
* modular through pluggable auth, password policy and backend modules
* modular through pluggable authentication, password policy and backend modules
LdapCherry is not limited to ldap, it can handle virtually any user backend (ex: SQL database, htpasswd file, etc)
through the proper pluggin (provided that it is implemented ^^).
through the proper plugin (provided that it is implemented ^^).
LdapCherry also aims to be as simple as possible to deploy: no crazy dependencies,
few configuration files, extensive debug logs and full documentation.

View File

@ -158,9 +158,22 @@ div.body h6 {
div.body h1 { border-top: 20px solid white; margin-top: 0; font-size: 225%; color: #EEEEEE; background-color: #6f8c93;}
div.body h2 { font-size: 140%; color: #EEEEEE; background-color: #6f8c93; }
div.body h3 { font-size: 120%; color: #2C001E; background-color: #accbd3; }
div.body h4 { font-size: 100%; color: #EEEEEE; background-color: #accbd3; color: #000000}
div.body h5 { font-size: 100%; color: #EEEEEE; background-color: #accbd3; color: #000000}
div.body h6 { font-size: 100%; color: #EEEEEE; background-color: #accbd3; color: #000000}
div.body h4 { font-size: 100%; color: #000000; background-color: #accbd3; }
div.body h5 { font-size: 100%; color: #000000; background-color: #accbd3; }
div.body h6 { font-size: 100%; color: #000000; background-color: #accbd3; }
div.body dt { font-size: 110%; color: #2C001E; background-color: #accbd3;}
.viewcode-link {
color: #000000;
}
div.body tt {
color: #000000;
/* padding: 1px 2px; */
}
a.headerlink {
color: #333333;
@ -226,14 +239,6 @@ pre {
-moz-box-shadow: 1px 1px 1px #d8d8d8;
}
tt {
background-color: #EFEFEF;
color: #222;
/* padding: 1px 2px; */
font-size: 1.1em;
font-family: "Ubuntu Mono", Monaco, Consolas, "DejaVu Sans Mono", "Lucida Console", monospace;
}
.viewcode-back {
font-family: Ubuntu, "DejaVu Sans", "Trebuchet MS", sans-serif;
}

View File

@ -1,18 +1,18 @@
Implementing your own backend
=============================
Implementing cutom backends
===========================
API
~~~
---
To create your own backend, you must implement the following API:
The backend modules must respect the following API:
.. automodule:: ldapcherry.backend
:members:
.. autoclass:: ldapcherry.backend.Backend
:members: __init__, auth, add_user, del_user, set_attrs, add_to_groups, del_from_groups, search, get_user, get_groups
:undoc-members:
:show-inheritance:
Configuration
~~~~~~~~~~~~~
-------------
Configuration for your backend is declared in the main ini file, inside [backends] section:
@ -42,13 +42,31 @@ The following hash will be passed as configuration to the module constructor as
'param2': "my value 2",
}
After having set **self.config** to **config** in the constructor, parameters can be recovered
by **self.get_param**:
.. autoclass:: ldapcherry.backend.Backend
:members: get_param
:undoc-members:
:show-inheritance:
Exceptions
~~~~~~~~~~
----------
The following exception can be used in your module
*
*
*
*
.. automodule:: ldapcherry.exceptions
:members: UserDoesntExist, UserAlreadyExists, GroupDoesntExist
:undoc-members:
:show-inheritance:
These exceptions permit a nicer error handling and avoid a generic message to be thrown at the user.
Example
-------
Here is the ldap backend module that comes with LdapCherry:
.. literalinclude:: ../ldapcherry/backend/backendLdap.py
:language: python

View File

@ -92,6 +92,28 @@ If **type** is set to **stringlist** the parameter **values** must be filled wit
backends:
- <backend id>: <backend attribute name>
Key attribute:
^^^^^^^^^^^^^^
One attribute must be used as a unique key across all backends:
To set the key attribute, you must set **key** to **True** on this attribute.
Example:
.. sourcecode:: yaml
uid:
description: "UID of the user"
display_name: "UID"
search_displayed: True
key: True # defining the attribute as "key"
type: string
weight: 50
backends:
ldap: uid
ad: sAMAccountName
Authorize self modification
^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -115,7 +137,7 @@ In such case, the parameter **self** must set to **True**:
Autofill
^^^^^^^^
LdapCherry has the possibility to autofill fields from other fields,
LdapCherry has the possibility to auto-fill fields from other fields,
to use this functionnality **autofill** must be set.
Example:
@ -139,12 +161,12 @@ Example:
backends:
ldap: gidNumber
Arguments of the autofill function work as follow:
Arguments of the **autofill** function work as follow:
* if argument starts with **$**, for example **$my_field**, the value of form input **my_field** will be passed to the function.
* otherwise, it will be treated as a fixed argument.
Available autofill functions:
Available **autofill** functions:
* lcUid: generate 8 characters uid from 2 other fields (first letter of the first field, 7 first letters of the second):
@ -205,6 +227,84 @@ Available autofill functions:
Roles Configuration
~~~~~~~~~~~~~~~~~~~
The roles configuration is done in a yaml file (roles.yml by default).
Mandatory parameters
^^^^^^^^^^^^^^^^^^^^
Roles are seen as an aggregate of groups:
.. sourcecode:: yaml
<role id>:
display_name: <Role display name in LdapCherry>
description: <human readable role description>
backends_groups: # list of backends
<backend id 1>: # list of groups in backend
- <b1 group 1>
- <b1 group 2>
<backend id 2>:
- <b2 group 1>
- <b2 group 2>
.. warning:: <role id> must be unique, LdapCherry won't start if it's not
Defining LdapCherry Administrator role
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
One of the declared roles must be tagged to be LdapCherry administrators.
Doing so is done by setting **LC_admins** to **True** for the selected role:
.. sourcecode:: yaml
<role id>:
display_name: <Role display name in LdapCherry>
description: <human readable role description>
LC_admins: True
backends_groups: # list of backends
<backend id 1>: # list of groups in backend
- <b1 group 1>
- <b1 group 2>
<backend id 2>:
- <b2 group 1>
- <b2 group 2>
Nesting roles
^^^^^^^^^^^^^
LdapCherry handles roles nesting:
.. sourcecode:: yaml
parent_role:
display_name: Role parent
description: The parent role
backends_groups:
backend_id_1:
- b1_group_1
- b1_group_2
backend_id_2:
- b2_group_1
- b2_group_2
subroles:
child_role_1:
display_name: Child role 1
description: The first Child Role
backends_groups:
backend_id_1:
- b1_group_3
child_role_2:
display_name: Child role 2
description: The second Child Role
backends_groups:
backend_id_1:
- b1_group_4
In that case, child_role_1 and child_role_2 will contain all groups of parent_role plus their own specific groups.
Main Configuration
------------------
@ -259,6 +359,21 @@ example:
Backends
~~~~~~~~
Backends are configure in the **backends** section, the format is the following:
.. sourcecode:: ini
[backends]
# backend python module path
<backend id>.module = 'python.module.path'
# parameters of the module instance for backend <backend id>.
<backend id>.<param> = <value>
It's possible to instanciate the same module several times.
Authentication and sessions
~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -310,7 +425,7 @@ Logging
LdapCherry has two loggers, one for errors and applicative actions (login, del/add, logout...) and one for access logs.
Each logger can be configured to log to syslog, file or be desactivated.
Each logger can be configured to log to syslog, file or be disabled.
Logging parameters:
@ -346,6 +461,12 @@ Example:
Other LdapCherry parameters
~~~~~~~~~~~~~~~~~~~~~~~~~~~
+---------------+-----------+--------------------------------+------------------------+
| Parameter | Section | Description | Values |
+===============+===========+================================+========================+
| template_dir | resources | LdapCherry template directory | path to template dir |
+---------------+-----------+--------------------------------+------------------------+
.. sourcecode:: ini
# resources parameters
@ -353,19 +474,3 @@ Other LdapCherry parameters
# templates directory
template_dir = '/usr/share/ldapcherry/templates/'
LdapCherry full configuration file
----------------------------------
.. literalinclude:: ../conf/ldapcherry.ini
:language: ini
Init Script
-----------
Sample init script for Debian:
.. literalinclude:: ../goodies/init-debian
:language: bash
This init script is available in **goodies/init-debian**.

3
docs/forkme.rst Normal file
View File

@ -0,0 +1,3 @@
.. raw:: html
<a href="https://github.com/kakwa/ldapcherry"><img style="position: absolute; top: 0; right: 0; border: 0;" src="https://camo.githubusercontent.com/38ef81f8aca64bb9a64448d0d70f1308ef5341ab/68747470733a2f2f73332e616d617a6f6e6177732e636f6d2f6769746875622f726962626f6e732f666f726b6d655f72696768745f6461726b626c75655f3132313632312e706e67" alt="Fork me on GitHub" data-canonical-src="https://s3.amazonaws.com/github/ribbons/forkme_right_darkblue_121621.png"></a>

View File

@ -0,0 +1,23 @@
Full Configuration
==================
Main ini configuration file
---------------------------
.. literalinclude:: ../conf/ldapcherry.ini
:language: ini
Yaml Attributes configuration file
----------------------------------
.. literalinclude:: ../conf/attributes.yml
:language: yaml
Yaml Roles configuration file
-----------------------------
.. literalinclude:: ../conf/roles.yml
:language: yaml

34
docs/goodies.rst Normal file
View File

@ -0,0 +1,34 @@
Some Goodies
============
Here are some goodies that might help deploying LdapCherry
They are located in the **goodies/** directory.
Init Script
-----------
Sample init script for Debian:
.. literalinclude:: ../goodies/init-debian
:language: bash
This init script is available in **goodies/init-debian**.
Apache Vhost
------------
.. literalinclude:: ../goodies/apache.conf
:language: xml
Nginx Vhost
-----------
.. literalinclude:: ../goodies/nginx.conf
:language: yaml
Lighttpd Vhost
--------------
.. literalinclude:: ../goodies/lighttpd.conf
:language: yaml

View File

@ -7,8 +7,12 @@
install
deploy
full_configuration
backend_api
ppolicy_api
changelog
goodies
.. include:: ../README.rst
.. include:: forkme.rst

View File

@ -38,8 +38,8 @@ These directories can be changed by exporting the following variables before lau
.. sourcecode:: bash
#optional, default sys.prefix (/usr/ on most Linux)
$ export DATAROOTDIR=/usr/local/
#optional, default sys.prefix + 'share' (/usr/share/ on most Linux)
$ export DATAROOTDIR=/usr/local/share/
#optional, default /etc/
$ export SYSCONFDIR=/usr/local/etc/

View File

@ -1,7 +1,34 @@
Package ldapcherry.ppolicy
==========================
Implementing password policy modules
====================================
.. automodule:: ldapcherry.ppolicy
:members:
API
---
The password policy modules must respect following API:
.. autoclass:: ldapcherry.ppolicy.PPolicy
:members: check, info, __init__
:undoc-members:
:show-inheritance:
Configuration
-------------
Parameters are declared in the main configuration file, inside the **ppolicy** section.
After having set **self.config** to **config** in the constructor, parameters can be recovered
by **self.get_param**:
.. autoclass:: ldapcherry.ppolicy.PPolicy
:members: get_param
:undoc-members: check
:show-inheritance:
Example
-------
Here is the simple default ppolicy module that comes with LdapCherry:
.. literalinclude:: ../ldapcherry/ppolicy/simple.py
:language: python

8
goodies/apache.conf Normal file
View File

@ -0,0 +1,8 @@
<VirtualHost *:80>
<Location />
ProxyPass http://127.0.0.1:8080/
ProxyPassReverse http://127.0.0.1:8080/
</Location>
</VirtualHost>

93
goodies/init-debian Executable file
View File

@ -0,0 +1,93 @@
#! /bin/sh
### BEGIN INIT INFO
# Provides: ldapcherryd
# Required-Start: $remote_fs $network $syslog
# Required-Stop: $remote_fs $network $syslog
# Default-Start: 2 3 4 5
# Default-Stop:
# Short-Description: ldapcherry
### END INIT INFO
PIDFILE=/var/run/ldapcherryd/ldapcherryd.pid
CONF=/etc/ldapcherry/ldapcherry.ini
USER=www-data
GROUP=www-data
BIN=/usr/local/bin/ldapcherryd
OPTS="-d -c $CONF -p $PIDFILE"
. /lib/lsb/init-functions
if [ -f /etc/default/ldapcherryd ]; then
. /etc/default/ldapcherryd
fi
start_ldapcherryd(){
log_daemon_msg "Starting ldapcherryd" "ldapcherryd" || true
pidofproc -p $PIDFILE $BIN >/dev/null
status="$?"
if [ $status -eq 0 ]
then
log_end_msg 1
log_failure_msg \
"ldapcherryd already started"
return 1
fi
mkdir -p `dirname $PIDFILE` -m 750
chown $USER:$GROUP `dirname $PIDFILE`
if start-stop-daemon -c $USER:$GROUP --start \
--quiet --pidfile $PIDFILE \
--oknodo --exec $BIN -- $OPTS
then
log_end_msg 0 || true
return 0
else
log_end_msg 1 || true
return 1
fi
}
stop_ldapcherryd(){
log_daemon_msg "Stopping ldapcherryd" "ldapcherryd" || true
if start-stop-daemon --stop --quiet \
--pidfile $PIDFILE
then
log_end_msg 0 || true
return 0
else
log_end_msg 1 || true
return 1
fi
}
case "$1" in
start)
start_ldapcherryd
exit $?
;;
stop)
stop_ldapcherryd
exit $?
;;
restart)
stop_ldapcherryd
while pidofproc -p $PIDFILE $BIN >/dev/null
do
sleep 0.5
done
start_ldapcherryd
exit $?
;;
status)
status_of_proc -p $PIDFILE $BIN "ldapcherryd" \
&& exit 0 || exit $?
;;
*)
log_action_msg \
"Usage: /etc/init.d/ldapcherryd {start|stop|restart|status}" \
|| true
exit 1
esac
exit 0

7
goodies/lighttpd.conf Normal file
View File

@ -0,0 +1,7 @@
server.modules += ("mod_proxy")
$HTTP["host"] == "ldapcherry.kakwa.fr" {
proxy.server = ( "" =>
(( "host" => "127.0.0.1", "port" => 8080 ))
)
}

14
goodies/nginx.conf Normal file
View File

@ -0,0 +1,14 @@
server {
listen 80 default_server;
server_name $hostname;
#access_log /var/log/nginx/dnscherry_access_log;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-for $proxy_add_x_forwarded_for;
proxy_set_header Host $host:$server_port;
proxy_set_header X-Forwarded-Proto $remote_addr;
}
}

View File

@ -11,36 +11,124 @@ from ldapcherry.exceptions import MissingParameter
class Backend:
def __init__(self, config, logger, name, attrslist, key):
""" Initialize the backend
:param config: the configuration of the backend
:type config: dict {'config key': 'value'}
:param logger: the cherrypy error logger object
:type logger: python logger
:param name: id of the backend
:type name: string
:param attrslist: list of the backend attributes
:type attrslist: list of strings
:param key: the key attribute
:type key: string
"""
raise Exception()
def auth(self, username, password):
""" Check authentication against the backend
:param username: 'key' attribute of the user
:type username: string
:param password: password of the user
:type password: string
:rtype: boolean (True is authentication success, False otherwise)
"""
return False
def add_user(self, attrs):
""" Add a user to the backend
:param attrs: attributes of the user
:type attrs: dict ({<attr>: <value>})
.. warning:: raise UserAlreadyExists if user already exists
"""
pass
def del_user(self, username):
""" Delete a user from the backend
:param username: 'key' attribute of the user
:type username: string
"""
pass
def set_attrs(self, username, attrs):
""" Set a list of attributes for a given user
:param username: 'key' attribute of the user
:type username: string
:param attrs: attributes of the user
:type attrs: dict ({<attr>: <value>})
"""
pass
def add_to_groups(self, username, groups):
""" Add a user to a list of groups
:param username: 'key' attribute of the user
:type username: string
:param groups: list of groups
:type groups: list of strings
"""
pass
def del_from_groups(self, username, groups):
""" Delete a user from a list of groups
:param username: 'key' attribute of the user
:type username: string
:param groups: list of groups
:type groups: list of strings
.. warning:: raise GroupDoesntExist if group doesn't exist
"""
pass
def search(self, searchstring):
""" Search backend for users
:param searchstring: the search string
:type searchstring: string
:rtype: dict of dict ( {<user attr key>: {<attr>: <value>}} )
"""
return {}
def get_user(self, username):
""" Get a user's attributes
:param username: 'key' attribute of the user
:type username: string
:rtype: dict ( {<attr>: <value>} )
.. warning:: raise UserDoesntExist if user doesn't exist
"""
return {}
def get_groups(self, username):
""" Get a user's groups
:param username: 'key' attribute of the user
:type username: string
:rtype: list of groups
"""
return []
def get_param(self, param, default=None):
""" Get a parameter in config (handle default value)
:param param: name of the parameter to recover
:type param: string
:param default: the default value, raises an exception
if param is not in configuration and default
is None (which is the default value).
:type default: string or None
:rtype: the value of the parameter or the default value if
not set in configuration
"""
if param in self.config:
return self.config[param]
elif default is not None:

View File

@ -13,15 +13,18 @@ class PPolicy:
def __init__(self, config, logger):
""" Password policy constructor
:dict config: the configuration of the ppolicy
:logger logger: a python logger
:param config: the configuration of the ppolicy
:type config: dict {'config key': 'value'}
:param logger: the cherrypy error logger object
:type logger: python logger
"""
pass
def check(self, password):
""" Check if a password match the ppolicy
:str password: the password to check
:param password: the password to check
:type password: string
:rtype: dict with keys 'match' a boolean
(True if ppolicy matches, False otherwise)
and 'reason', an explaination string
@ -30,7 +33,7 @@ class PPolicy:
return ret
def info(self):
""" Gives information about the ppolicy
""" Give information about the ppolicy
:rtype: a string describing the ppolicy
"""
@ -39,10 +42,14 @@ class PPolicy:
def get_param(self, param, default=None):
""" Get a parameter in config (handle default value)
:str param: name of the paramter to recover
:str default: the default value, raises an exception
:param param: name of the parameter to recover
:type param: string
:param default: the default value, raises an exception
if param is not in configuration and default
is None (which is the default value).
:type default: string or None
:rtype: the value of the parameter or the default value if
not set in configuration
"""
if param in self.config:
return self.config[param]

View File

@ -136,6 +136,9 @@ setup(
'Operating System :: POSIX',
'Programming Language :: Python',
'Programming Language :: Python :: 2.7',
'Topic :: Internet :: LDAP'
"Topic :: System :: Systems Administration"
" :: Authentication/Directory :: LDAP",
"Topic :: System :: Systems Administration"
" :: Authentication/Directory",
],
)