diff --git a/README.rst b/README.rst
index 1a580a8..5f9d9df 100644
--- a/README.rst
+++ b/README.rst
@@ -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 `_
@@ -14,12 +28,6 @@
----
-.. 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 @@
* 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.
diff --git a/docs/assets/nature.css b/docs/assets/nature.css
index f1e9316..a969f7a 100644
--- a/docs/assets/nature.css
+++ b/docs/assets/nature.css
@@ -158,10 +158,23 @@
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;
padding: 0 4px 0 4px;
@@ -226,14 +239,6 @@
-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;
}
diff --git a/docs/backend_api.rst b/docs/backend_api.rst
index 9af8c00..7344769 100644
--- a/docs/backend_api.rst
+++ b/docs/backend_api.rst
@@ -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 @@
'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
diff --git a/docs/deploy.rst b/docs/deploy.rst
index 3553d55..00a481d 100644
--- a/docs/deploy.rst
+++ b/docs/deploy.rst
@@ -92,6 +92,28 @@
backends:
- :
+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 @@
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 @@
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 @@
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
+
+ :
+ display_name:
+ description:
+ backends_groups: # list of backends
+ : # list of groups in backend
+ -
+ -
+ :
+ -
+ -
+
+.. warning:: 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
+
+ :
+ display_name:
+ description:
+
+ LC_admins: True
+
+ backends_groups: # list of backends
+ : # list of groups in backend
+ -
+ -
+ :
+ -
+ -
+
+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 @@
Backends
~~~~~~~~
+Backends are configure in the **backends** section, the format is the following:
+
+
+.. sourcecode:: ini
+
+ [backends]
+
+ # backend python module path
+ .module = 'python.module.path'
+
+ # parameters of the module instance for backend .
+ . =
+
+It's possible to instanciate the same module several times.
+
Authentication and sessions
~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -310,7 +425,7 @@
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 @@
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 @@
# 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**.
diff --git a/docs/forkme.rst b/docs/forkme.rst
new file mode 100644
index 0000000..ec88f77
--- /dev/null
+++ b/docs/forkme.rst
@@ -0,0 +1,3 @@
+.. raw:: html
+
+
diff --git a/docs/full_configuration.rst b/docs/full_configuration.rst
new file mode 100644
index 0000000..88fe3a8
--- /dev/null
+++ b/docs/full_configuration.rst
@@ -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
+
diff --git a/docs/goodies.rst b/docs/goodies.rst
new file mode 100644
index 0000000..8028989
--- /dev/null
+++ b/docs/goodies.rst
@@ -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
diff --git a/docs/index.rst b/docs/index.rst
index 437deef..2193b7a 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -7,8 +7,12 @@
install
deploy
+ full_configuration
backend_api
ppolicy_api
changelog
+ goodies
.. include:: ../README.rst
+
+.. include:: forkme.rst
diff --git a/docs/install.rst b/docs/install.rst
index efc73e8..dc9371c 100644
--- a/docs/install.rst
+++ b/docs/install.rst
@@ -38,8 +38,8 @@
.. 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/
diff --git a/docs/ppolicy_api.rst b/docs/ppolicy_api.rst
index a6d2364..9679111 100644
--- a/docs/ppolicy_api.rst
+++ b/docs/ppolicy_api.rst
@@ -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
+
diff --git a/goodies/apache.conf b/goodies/apache.conf
new file mode 100644
index 0000000..2c0a6b7
--- /dev/null
+++ b/goodies/apache.conf
@@ -0,0 +1,8 @@
+
+
+
+ ProxyPass http://127.0.0.1:8080/
+ ProxyPassReverse http://127.0.0.1:8080/
+
+
+
diff --git a/goodies/init-debian b/goodies/init-debian
new file mode 100755
index 0000000..1d8f056
--- /dev/null
+++ b/goodies/init-debian
@@ -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
diff --git a/goodies/lighttpd.conf b/goodies/lighttpd.conf
new file mode 100644
index 0000000..e5173ad
--- /dev/null
+++ b/goodies/lighttpd.conf
@@ -0,0 +1,7 @@
+server.modules += ("mod_proxy")
+
+$HTTP["host"] == "ldapcherry.kakwa.fr" {
+ proxy.server = ( "" =>
+ (( "host" => "127.0.0.1", "port" => 8080 ))
+ )
+}
diff --git a/goodies/nginx.conf b/goodies/nginx.conf
new file mode 100644
index 0000000..9789f93
--- /dev/null
+++ b/goodies/nginx.conf
@@ -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;
+ }
+}
diff --git a/ldapcherry/backend/__init__.py b/ldapcherry/backend/__init__.py
index 1957589..77a6ed1 100644
--- a/ldapcherry/backend/__init__.py
+++ b/ldapcherry/backend/__init__.py
@@ -11,36 +11,124 @@
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 ({: })
+
+ .. 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 ({: })
+ """
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 ( {: {: }} )
+ """
return {}
def get_user(self, username):
+ """ Get a user's attributes
+
+ :param username: 'key' attribute of the user
+ :type username: string
+ :rtype: dict ( {: } )
+
+ .. 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:
diff --git a/ldapcherry/ppolicy/__init__.py b/ldapcherry/ppolicy/__init__.py
index 5b4f066..4461487 100644
--- a/ldapcherry/ppolicy/__init__.py
+++ b/ldapcherry/ppolicy/__init__.py
@@ -13,15 +13,18 @@
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 @@
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 @@
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]
diff --git a/setup.py b/setup.py
index 4b6b317..7a63178 100755
--- a/setup.py
+++ b/setup.py
@@ -136,6 +136,9 @@
'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",
],
)