1
0
mirror of https://github.com/kakwa/ldapcherry synced 2024-06-12 21:59:52 +02:00

Compare commits

..

No commits in common. "master" and "0.5.0" have entirely different histories.

73 changed files with 805 additions and 1433 deletions

View File

@ -1,19 +0,0 @@
name: CI
on:
push:
pull_request:
workflow_dispatch:
jobs:
build_and_test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install packages and setup ldap/AD
run: sudo ./tests/test_env/deploy.sh
- name: Test
run: python3 setup.py test

1
.gitignore vendored
View File

@ -54,4 +54,3 @@ coverage.xml
# Sphinx documentation
docs/_build/
ldapcherry-dev.ini

View File

@ -1,40 +1,31 @@
sudo: required
dist: xenial
dist: trusty
language: python
#env:
# - TRAVIS="yes"
before_install:
- '[ "$TEST_PEP8" == "1" ] || sudo ./tests/test_env/deploy.sh'
python:
- "2.7"
install:
- "pip install -e . -r $REQ_FILE"
- "if [[ $TEST_PEP8 == '1' ]]; then pip install pycodestyle; fi"
- pip install -e .
- "if [[ $TEST_PEP8 == '1' ]]; then pip install pep8; fi"
- pip install passlib
- pip install coveralls
script: "if [[ $TEST_PEP8 == '1' ]]; then pycodestyle --repeat --show-source --exclude=.venv,.tox,dist,docs,build,*.egg,tests,misc,setup.py .; else coverage run --source=ldapcherry setup.py test; fi"
# command to run tests
#
#script:
# - coverage run --source=ldapcherry setup.py test
script: "if [[ $TEST_PEP8 == '1' ]]; then pep8 --repeat --show-source --exclude=.venv,.tox,dist,docs,build,*.egg,tests,misc . scripts/ldapcherryd; else coverage run --source=ldapcherry setup.py test; fi"
matrix:
include:
- python: "2.7"
env:
TEST_PEP8=1
REQ_FILE=requirements.txt
- python: "2.7"
env:
TEST_PEP8=0
REQ_FILE=requirements-el7.txt
- python: "2.7"
env:
TEST_PEP8=0
REQ_FILE=requirements-stretch.txt
- python: "2.7"
env:
TEST_PEP8=0
REQ_FILE=requirements.txt
- python: "3.6"
env:
TEST_PEP8=0
REQ_FILE=requirements.txt
env: TEST_PEP8=1
after_success:
- coveralls
after_failure:

View File

@ -1,49 +1,6 @@
Dev
***
Version 1.1.1
*************
* [fix ] fix double escaping issues introduced in 1.0.0
* [fix ] fix missing url escaping in links with querystring parameters (delete and modify page mostly)
* [fix ] fix log level not being honored in the backends
* [impr] clarify the role of 'key: True' of attributes.yml in the documentation
* [impr] add a few more comments in the .ini file to explain better the \*_filter_tmpl and group_attr parameters
* [impr] add debug log to help debug ldap search filters
Version 1.1.0
*************
* [feat] add stdout as a valid log method (useful when running with docker)
Version 1.0.1
*************
* [fix ] fix error handling when adding user that already exists
Version 1.0.0
*************
* [sec ] fix XSS injection in the url redirect in the login page (thanks to jthiltges)
* [fix ] fix configuration consistency check for attribute file (error if a given backend is not declared in main .ini file but in attributes)
* [fix ] remove a few deprecation warnings
* [fix ] fix potential issue with group names containing non-ascii characters
* [feat] support for python 3
* [feat] support for python-ldap 3.X.X
* [impr] better log error message if inconsistency between role, attribute and main .ini file for backends
* [impr] more systematic use of html and url escaping in the html rendering to prevent against content injection (thanks to jthiltges)
* [impr] more testing for various versions of python and python-ldap
Version 0.5.2
*************
* [fix ] regression in 0.5.1, setup.py could not work without the dependencies already installed
Version 0.5.1
*************
* [impr] cleaner align of labels (input-group-addon width)
Version 0.5.0
*************

View File

@ -6,9 +6,11 @@
Nice and simple application to manage users and groups in multiple directory services.
.. image:: https://github.com/kakwa/ldapcherry/actions/workflows/tests.yml/badge.svg
:target: https://github.com/kakwa/ldapcherry/actions/workflows/tests.yml
:alt: CI
.. 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/v/ldapcherry.svg
:target: https://pypi.python.org/pypi/ldapcherry
@ -34,7 +36,7 @@ Nice and simple application to manage users and groups in multiple directory ser
LdapCherry is a CherryPY application to manage users and groups in multiple directory services.
Its main features are:
It's main features are:
* manage multiple directories/databases backends in an unified way
* roles management (as in "groups of groups")
@ -64,16 +66,11 @@ The default backend plugins permit to manage Ldap and Active Directory.
.. sourcecode:: bash
# clone the repository
$ git clone https://github.com/kakwa/ldapcherry && cd ldapcherry
# change the directory where to put the configuration (default: /etc)
$ export SYSCONFDIR=/etc
# change the directory where to put the resource (default: /usr/share)
$ export DATAROOTDIR=/usr/share/
$ export SYSCONFDIR=<sys conf dir>
# install ldapcherry
$ python setup.py install
$ pip install ldapcherry
# edit configuration files
$ vi /etc/ldapcherry/ldapcherry.ini
@ -81,7 +78,7 @@ The default backend plugins permit to manage Ldap and Active Directory.
$ vi /etc/ldapcherry/attributes.yml
# launch ldapcherry
$ ldapcherryd -c /etc/ldapcherry/ldapcherry.ini -D
$ ldapcherryd -c /etc/ldapcherry/ldapcherry.ini
***********
@ -94,7 +91,7 @@ LdapCherry is published under the MIT Public License.
Discussion / Help / Updates
*******************************
* IRC: `Libera <https://libera.chat/>`_ ``#ldapcherry`` channel
* IRC: `Freenode <http://freenode.net/>`_ ``#ldapcherry`` channel
* Bugtracker: `Github <https://github.com/kakwa/ldapcherry/issues>`_
----

View File

@ -5,10 +5,6 @@
server.socket_host = '127.0.0.1'
# port
server.socket_port = 8080
# it's also possible to run bound to a unix socket
#server.socket_file = '/tmp/lc.sock'
# number of threads
server.thread_pool = 8
#don't show traceback on error
@ -28,14 +24,6 @@ request.show_tracebacks = False
## error and ldapcherry log file
#log.error_file = '/tmp/ldapcherry_error.log'
#####################################
# configuration to log to stdout #
#####################################
## logger stdout for access log
#log.access_handler = 'stdout'
## logger stdout for error and ldapcherry log
#log.error_handler = 'stdout'
#####################################
# configuration to log in syslog #
#####################################
@ -106,24 +94,16 @@ ldap.timeout = 1
ldap.groupdn = 'ou=group,dc=example,dc=org'
# users dn
ldap.userdn = 'ou=people,dc=example,dc=org'
# ldapsearch filter to get one specific user
# %(username)s is content of the attribute marked 'key: True' in the attributes.file config file
# ldapsearch filter to get a user
ldap.user_filter_tmpl = '(uid=%(username)s)'
# ldapsearch filter to get groups of a user
# %(username)s is content of the attribute marked 'key: True' in the attributes.file config file
ldap.group_filter_tmpl = '(member=uid=%(username)s,ou=People,dc=example,dc=org)'
# filter to search users
# %(searchstring)s is the content passed through the search box
ldap.search_filter_tmpl = '(|(uid=%(searchstring)s*)(sn=%(searchstring)s*))'
# ldap group attributes and how to fill them
# 'member' is the name of the attribute
# for the template, any of the user's ldap attributes can be user
ldap.group_attr.member = "%(dn)s"
# same with memverUid and the uid user's attribute
#ldap.group_attr.memberUid = "%(uid)s"
# object classes of a user entry
ldap.objectclasses = 'top, person, posixAccount, inetOrgPerson'
# dn entry attribute for an ldap user

View File

@ -25,12 +25,12 @@ admin-lv2:
# ad:
# - Administrators
developers:
developpers:
display_name: Developpers
description: Developpers of the system
backends_groups:
ldap:
- cn=developers,ou=Group,dc=example,dc=org
- cn=developpers,ou=Group,dc=example,dc=org
- cn=users,ou=Group,dc=example,dc=org
users:

View File

@ -47,55 +47,48 @@ Configuration
The ldap backend exposes the following parameters:
+--------------------------+----------+------------------------------------+--------------------------+------------------------------------------------+
| Parameter | Section | Description | Values | Comment |
+==========================+==========+====================================+==========================+================================================+
| uri | backends | The ldap uri to access | ldap uri | * use ldap:// for clear/starttls |
| | | | | * use ldaps:// for ssl |
| | | | | * custom port: ldap://<host>:<port> |
+--------------------------+----------+------------------------------------+--------------------------+------------------------------------------------+
| ca | backends | Path to the CA file | file path | optional |
+--------------------------+----------+------------------------------------+--------------------------+------------------------------------------------+
| starttls | backends | Use starttls | 'on' or 'off' | optional |
+--------------------------+----------+------------------------------------+--------------------------+------------------------------------------------+
| checkcert | backends | Check the server certificat | 'on' or 'off' | optional |
+--------------------------+----------+------------------------------------+--------------------------+------------------------------------------------+
| binddn | backends | The bind dn to use | ldap dn | This dn must have read/write permissions |
+--------------------------+----------+------------------------------------+--------------------------+------------------------------------------------+
| password | backends | The password of the bind dn | password | |
+--------------------------+----------+------------------------------------+--------------------------+------------------------------------------------+
| timeout | backends | Ldap connexion timeout | integer (second) | |
+--------------------------+----------+------------------------------------+--------------------------+------------------------------------------------+
| password | backends | The password of the bind dn | password | |
+--------------------------+----------+------------------------------------+--------------------------+------------------------------------------------+
| groupdn | backends | The ldap dn where groups are | ldap dn | |
+--------------------------+----------+------------------------------------+--------------------------+------------------------------------------------+
| userdn | backends | The ldap dn where users are | ldap dn | |
+--------------------------+----------+------------------------------------+--------------------------+------------------------------------------------+
| user_filter_tmpl | backends | The search filter template | ldap search filter | The user identifier is passed through |
| | | to recover a given user | template | the **username** variable (*%(username)s*) |
| | | | | |
| | | | | **username** is the content of the |
| | | | | the attribute marked by '**key: True**' |
| | | | | in the **attributes.yml** file |
+--------------------------+----------+------------------------------------+--------------------------+------------------------------------------------+
| group_filter_tmpl | backends | The search filter template to | ldap search filter | The following variables are usable: |
| | | recover the groups of a given user | template | |
| | | recover the groups of a given user | template | * **username**: the user's key attribute |
| | | | | * **userdn**: the user's ldap dn |
+--------------------------+----------+------------------------------------+--------------------------+------------------------------------------------+
| group_attr.<member attr> | backends | Member attribute template value | template | * <member attr> is the member attribute |
| | | | | in groups dn entries |
| | | | | * every user attributes are exposed |
| | | | | in the template |
| | | | | * multiple <memver attr> attributes |
| | | | | can be set (ex: group_attr.member |
| | | | | (ex: group_attr.member, group_attr.usermemb) |
+--------------------------+----------+------------------------------------+--------------------------+------------------------------------------------+
| objectclasses | backends | list of object classes for users | comma separated list | |
+--------------------------+----------+------------------------------------+--------------------------+------------------------------------------------+
| dn_user_attr | backends | attribute used in users dn | dn attribute | |
+--------------------------+----------+------------------------------------+--------------------------+------------------------------------------------+
+--------------------------+----------+------------------------------------+--------------------------+--------------------------------------------+
| Parameter | Section | Description | Values | Comment |
+==========================+==========+====================================+==========================+============================================+
| uri | backends | The ldap uri to access | ldap uri | * use ldap:// for clear/starttls |
| | | | | * use ldaps:// for ssl |
| | | | | * custom port: ldap://<host>:<port> |
+--------------------------+----------+------------------------------------+--------------------------+--------------------------------------------+
| ca | backends | Path to the CA file | file path | optional |
+--------------------------+----------+------------------------------------+--------------------------+--------------------------------------------+
| starttls | backends | Use starttls | 'on' or 'off' | optional |
+--------------------------+----------+------------------------------------+--------------------------+--------------------------------------------+
| checkcert | backends | Check the server certificat | 'on' or 'off' | optional |
+--------------------------+----------+------------------------------------+--------------------------+--------------------------------------------+
| binddn | backends | The bind dn to use | ldap dn | This dn must have read/write permissions |
+--------------------------+----------+------------------------------------+--------------------------+--------------------------------------------+
| password | backends | The password of the bind dn | password | |
+--------------------------+----------+------------------------------------+--------------------------+--------------------------------------------+
| timeout | backends | Ldap connexion timeout | integer (second) | |
+--------------------------+----------+------------------------------------+--------------------------+--------------------------------------------+
| password | backends | The password of the bind dn | password | |
+--------------------------+----------+------------------------------------+--------------------------+--------------------------------------------+
| groupdn | backends | The ldap dn where groups are | ldap dn | |
+--------------------------+----------+------------------------------------+--------------------------+--------------------------------------------+
| userdn | backends | The ldap dn where users are | ldap dn | |
+--------------------------+----------+------------------------------------+--------------------------+--------------------------------------------+
| user_filter_tmpl | backends | The search filter template | ldap search filter | The user identifier is passed through |
| | | to recover a given user | template | the **username** variable (*%(username)s*).|
+--------------------------+----------+------------------------------------+--------------------------+--------------------------------------------+
| group_filter_tmpl | backends | The search filter template to | ldap search filter | The following variables are usable: |
| | | recover the groups of a given user | template | * **username**: the user key attribute |
| | | | | * **userdn**: the user ldap dn |
+--------------------------+----------+------------------------------------+--------------------------+--------------------------------------------+
| group_attr.<member attr> | backends | Member attribute template value | template | * <member attr> is the member attribute |
| | | | | in groups dn entries |
| | | | | * every user attributes are exposed |
| | | | | in the template |
| | | | | * multiple attributes can be set |
+--------------------------+----------+------------------------------------+--------------------------+--------------------------------------------+
| objectclasses | backends | list of object classes for users | comma separated list | |
+--------------------------+----------+------------------------------------+--------------------------+--------------------------------------------+
| dn_user_attr | backends | attribute used in users dn | dn attribute | |
+--------------------------+----------+------------------------------------+--------------------------+--------------------------------------------+
Example
@ -103,58 +96,46 @@ Example
.. sourcecode:: ini
[backends]
[backends]
#####################################
# configuration of ldap backend #
#####################################
# name of the module
ldap.module = 'ldapcherry.backend.backendLdap'
# display name of the ldap
ldap.display_name = 'My Ldap Directory'
# uri of the ldap directory
ldap.uri = 'ldap://ldap.ldapcherry.org'
# ca to use for ssl/tls connexion
#ldap.ca = '/etc/dnscherry/TEST-cacert.pem'
# use start tls
#ldap.starttls = 'off'
# check server certificate (for tls)
#ldap.checkcert = 'off'
# bind dn to the ldap
ldap.binddn = 'cn=dnscherry,dc=example,dc=org'
# password of the bind dn
ldap.password = 'password'
# timeout of ldap connexion (in second)
ldap.timeout = 1
# groups dn
ldap.groupdn = 'ou=group,dc=example,dc=org'
# users dn
ldap.userdn = 'ou=people,dc=example,dc=org'
# ldapsearch filter to get one specific user
# %(username)s is content of the attribute marked 'key: True' in the attributes.file config file
ldap.user_filter_tmpl = '(uid=%(username)s)'
# ldapsearch filter to get groups of a user
# %(username)s is content of the attribute marked 'key: True' in the attributes.file config file
ldap.group_filter_tmpl = '(member=uid=%(username)s,ou=People,dc=example,dc=org)'
# filter to search users
# %(searchstring)s is the content passed through the search box
ldap.search_filter_tmpl = '(|(uid=%(searchstring)s*)(sn=%(searchstring)s*))'
# ldap group attributes and how to fill them
# 'member' is the name of the attribute
# for the template, any of the user's ldap attributes can be user
ldap.group_attr.member = "%(dn)s"
# same with memverUid and the uid user's attribute
#ldap.group_attr.memberUid = "%(uid)s"
# object classes of a user entry
ldap.objectclasses = 'top, person, posixAccount, inetOrgPerson'
# dn entry attribute for an ldap user
ldap.dn_user_attr = 'uid'
# name of the module
ldap.module = 'ldapcherry.backend.backendLdap'
# display name of the ldap
ldap.display_name = 'My Ldap Directory'
# uri of the ldap directory
ldap.uri = 'ldap://ldap.ldapcherry.org'
# ca to use for ssl/tls connexion
#ldap.ca = '/etc/dnscherry/TEST-cacert.pem'
# use start tls
#ldap.starttls = 'off'
# check server certificate (for tls)
#ldap.checkcert = 'off'
# bind dn to the ldap
ldap.binddn = 'cn=dnscherry,dc=example,dc=org'
# password of the bind dn
ldap.password = 'password'
# timeout of ldap connexion (in second)
ldap.timeout = 1
# groups dn
ldap.groupdn = 'ou=group,dc=example,dc=org'
# users dn
ldap.userdn = 'ou=people,dc=example,dc=org'
# ldapsearch filter to get a user
ldap.user_filter_tmpl = '(uid=%(username)s)'
# ldapsearch filter to get groups of a user
ldap.group_filter_tmpl = '(member=uid=%(username)s,ou=People,dc=example,dc=org)'
# filter to search users
ldap.search_filter_tmpl = '(|(uid=%(searchstring)s*)(sn=%(searchstring)s*))'
# ldap group attributes and how to fill them
ldap.group_attr.member = "%(dn)s"
#ldap.group_attr.memberUid = "%(uid)s"
# object classes of a user entry
ldap.objectclasses = 'top, person, posixAccount, inetOrgPerson'
# dn entry attribute for an ldap user
ldap.dn_user_attr = 'uid'
Active Directory Backend

View File

@ -16,6 +16,7 @@
# documentation root, use os.path.abspath to make it absolute, like shown here.
import os
import sys
import sys
try:
from mock import Mock as MagicMock
@ -31,9 +32,6 @@ MOCK_MODULES = ['ldap']
sys.modules.update((mod_name, Mock()) for mod_name in MOCK_MODULES)
sys.path.insert(0, os.path.abspath('..'))
sys.path.insert(0, os.path.abspath('../ldapcherry'))
from version import version
# -- General configuration -----------------------------------------------------
@ -66,7 +64,7 @@ copyright = u'2016, Pierre-Francois Carpentier'
#
# The short X.Y version.
# The full version, including alpha/beta/rc tags.
release = version
release = '0.5.0'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.

View File

@ -112,12 +112,7 @@ If **type** is set to **stringlist** the parameter **values** must be filled wit
Key attribute:
^^^^^^^^^^^^^^
One attribute must be used as a unique key across all backends.
It acts as a reconciliation key.
It also marks which attribute must be used within ldapcherry (ex: querysting parameter in links)
to point to one given user.
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.
@ -450,16 +445,16 @@ 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**, **stdout** or be disabled.
Each logger can be configured to log to syslog, file or be disabled.
Logging parameters:
+--------------------+---------+---------------------------------+-------------------------------------------------+----------------------------------------+
| Parameter | Section | Description | Values | Comment |
+====================+=========+=================================+=================================================+========================================+
| log.access_handler | global | Logger type for access log | 'syslog', 'file', 'stdout', 'none' | |
| log.access_handler | global | Logger type for access log | 'syslog', 'file', 'none' | |
+--------------------+---------+---------------------------------+-------------------------------------------------+----------------------------------------+
| log.error_handler | global | Logger type for applicative log | 'syslog', 'file', 'stdout', 'none' | |
| log.error_handler | global | Logger type for applicative log | 'syslog', 'file', 'none' | |
+--------------------+---------+---------------------------------+-------------------------------------------------+----------------------------------------+
| log.access_file | global | log file for access log | path to log file | only used if log.access_handler='file' |
+--------------------+---------+---------------------------------+-------------------------------------------------+----------------------------------------+
@ -482,14 +477,6 @@ Example:
log.level = 'info'
.. warning::
'debug' should not be used in production.
It tends to log a lot.
More significantly can represent a security issue,
as things like passwords will be logged 'clear text'.
Custom javascript
~~~~~~~~~~~~~~~~~

View File

@ -18,49 +18,17 @@ This init script is available in **goodies/init-debian**.
Apache Vhost
------------
Basic Apache Vhost:
.. literalinclude:: ../goodies/apache.conf
:language: xml
Nginx Vhost
-----------
Basic Nginx Vhost:
.. literalinclude:: ../goodies/nginx.conf
:language: yaml
Nginx Vhost (FastCGI)
---------------------
Nginx Vhost in FastCGI mode:
.. literalinclude:: ../goodies/nginx-fastcgi.conf
:language: yaml
.. warning::
LdapCherry requires the python flup module to run in FastCGI
Lighttpd Vhost
--------------
Basic Lighttpd Vhost
.. literalinclude:: ../goodies/lighttpd.conf
:language: yaml
Demo Backend Configuration Files
--------------------------------
The files here are the ones that are used at the demo site at `ldapcherry.kakwalab.ovh <https://ldapcherry.kakwalab.ovh/>`_ and can be used for a self-hosted demo backend:
.. literalinclude:: ../goodies/demo_backend_configs/attributes.yml
:language: yaml
.. literalinclude:: ../goodies/demo_backend_configs/roles.yml
:language: yaml
.. literalinclude:: ../goodies/demo_backend_configs/ldapcherry.ini
:language: ini

View File

@ -12,13 +12,18 @@ Download the latest release from `GitHub <https://github.com/kakwa/ldapcherry/re
$ cd ldapcherry*
$ python setup.py install
Alternatively, you can install from git:
From Pypi
---------
.. sourcecode:: bash
$ pip install ldapcherry
or
.. sourcecode:: bash
$ git clone https://github.com/kakwa/ldapcherry
$ cd ldapcherry
$ python setup.py install
$ easy_install ldapcherry
Installed files
---------------
@ -33,12 +38,8 @@ These directories can be changed by exporting the following variables before lau
.. sourcecode:: bash
# optional, default sys.prefix + 'share' (/usr/share/ on most Linux)
#optional, default sys.prefix + 'share' (/usr/share/ on most Linux)
$ export DATAROOTDIR=/usr/local/share/
# optional, default /etc/
#optional, default /etc/
$ export SYSCONFDIR=/usr/local/etc/
.. note:: if --root is passed, the install prefix is honored for these directories
.. warning:: If you change these directories, **templates.dir** and **tools.staticdir.dir** in *ldapcherry.ini* need to be modified accordingly.

View File

@ -1,112 +0,0 @@
description: "First Name and Display Name"
display_name: "Display Name"
type: string
weight: 30
autofill:
function: lcDisplayName
args:
- $first-name
- $name
backends:
demo: cn
first-name:
description: "First name of the user"
display_name: "First Name"
search_displayed: True
type: string
weight: 20
backends:
demo: givenName
name:
description: "Family name of the user"
display_name: "Name"
search_displayed: True
weight: 10
type: string
backends:
demo: sn
email:
description: "Email of the user"
display_name: "Email"
search_displayed: True
type: email
weight: 40
autofill:
function: lcMail
args:
- $first-name
- $name
- '@example.com'
backends:
demo: mail
uid:
description: "UID of the user"
display_name: "UID"
search_displayed: True
key: True
type: string
weight: 50
autofill:
function: lcUid
args:
- $first-name
- $name
- '10000'
- '40000'
backends:
demo: uid
uidNumber:
description: "User ID Number of the user"
display_name: "UID Number"
weight: 60
type: int
autofill:
function: lcUidNumber
args:
- $first-name
- $name
- '10000'
- '40000'
backends:
demo: uidNumber
gidNumber:
description: "Group ID Number of the user"
display_name: "GID Number"
weight: 70
type: int
default: '10000'
backends:
demo: gidNumber
shell:
description: "Shell of the user"
display_name: "Shell"
weight: 80
self: True
type: stringlist
values:
- /bin/bash
- /bin/zsh
- /bin/sh
backends:
demo: loginShell
home:
description: "Home user path"
display_name: "Home"
weight: 90
type: string
autofill:
function: lcHomeDir
args:
- $first-name
- $name
- /home/
backends:
demo: homeDirectory
password:
description: "Password of the user"
display_name: "Password"
weight: 31
self: True
type: password
backends:
demo: userPassword

View File

@ -1,25 +0,0 @@
[backends]
#####################################
# configuration of demo backend #
#####################################
# Name of the backend
demo.module = 'ldapcherry.backend.backendDemo'
# Display name of the Backend
demo.display_name = 'Demo Backend'
# Groups of admin user
demo.admin.groups = 'SECOFF'
# Groups of basic user
demo.basic.groups = 'Test 2, Test 1'
# Password attribute name
demo.pwd_attr = 'userPassword'
# Attribute to use for the search
demo.search_attributes = 'cn, sn, givenName, uid'
# Login of default admin user
demo.admin.user = 'admin'
# Password of default admin user
demo.admin.password = 'admin'
# Login of default basic user
demo.basic.user = 'user'
# Password of default basic user
demo.basic.password = 'user'

View File

@ -1,36 +0,0 @@
sec-officer:
display_name: Security Officer
description: Security officer of the system
LC_admins: True
backends_groups:
demo:
- SECOFF
admin-lv3:
display_name: Administrators Level 3
description: Super administrators of the system
backends_groups:
demo:
- cn=dns admins,ou=Group,dc=example,dc=org
- cn=nagios admins,ou=Group,dc=example,dc=org
- cn=puppet admins,ou=Group,dc=example,dc=org
- cn=users,ou=Group,dc=example,dc=org
admin-lv2:
display_name: Administrators Level 2
description: Basic administrators of the system
backends_groups:
demo:
- cn=nagios admins,ou=Group,dc=example,dc=org
- cn=users,ou=Group,dc=example,dc=org
developpers:
display_name: Developpers
description: Developpers of the system
backends_groups:
demo:
- cn=developpers,ou=Group,dc=example,dc=org
- cn=users,ou=Group,dc=example,dc=org
users:
display_name: Simple Users
description: Basic users of the system
backends_groups:
demo:
- cn=users,ou=Group,dc=example,dc=org

View File

@ -1,18 +0,0 @@
#!/bin/sh
ROOT=$(readlink -f $(dirname $0)/../)
cp $ROOT/conf/ldapcherry.ini $ROOT/ldapcherry-dev.ini
sed -i "s|/etc/ldapcherry/|$ROOT/conf/|" $ROOT/ldapcherry-dev.ini
sed -i "s|/usr/share/ldapcherry/|$ROOT/resources/|" $ROOT/ldapcherry-dev.ini
sed -i "s|^ldap\.|#ldap.|" $ROOT/ldapcherry-dev.ini
sed -i "s|#demo\.|ldap.|" $ROOT/ldapcherry-dev.ini
GROUPS='cn=nagios admins\\,ou=Group\\,dc=example\\,dc=org, cn=users\\,ou=Group\\,dc=example\\,dc=org'
sed -i "s|ldap.admin.groups.*|ldap.admin.groups = '$GROUPS'|" $ROOT/ldapcherry-dev.ini
sed -i "s|^min_length.*|min_length = 3|" $ROOT/ldapcherry-dev.ini
sed -i "s|^min_upper.*|min_upper = 0|" $ROOT/ldapcherry-dev.ini
sed -i "s|^min_digit.*|min_digit = 0|" $ROOT/ldapcherry-dev.ini

View File

@ -1,25 +0,0 @@
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
location / {
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param QUERY_STRING $query_string;
fastcgi_param CONTENT_TYPE $content_type;
fastcgi_param CONTENT_LENGTH $content_length;
fastcgi_param GATEWAY_INTERFACE CGI/1.1;
fastcgi_param SERVER_SOFTWARE nginx/$nginx_version;
fastcgi_param REMOTE_ADDR $remote_addr;
fastcgi_param REMOTE_PORT $remote_port;
fastcgi_param SERVER_ADDR $server_addr;
fastcgi_param SERVER_PORT $server_port;
fastcgi_param SERVER_NAME $server_name;
fastcgi_param SERVER_PROTOCOL $server_protocol;
fastcgi_param SCRIPT_FILENAME $fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_script_name;
fastcgi_pass 127.0.0.1:8080;
}
}

View File

@ -1,8 +0,0 @@
#!/bin/sh
cd `dirname $0`/../
version=`sed -e "s/version\ * = \ *'\(.*\)'.*/\1/;tx;d;:x" ./ldapcherry/version.py`
git tag "$version" -m "version $version"
git push origin "$version"

View File

@ -1,3 +1,4 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# vim:set expandtab tabstop=4 shiftwidth=4:
#
@ -7,7 +8,6 @@
# Generic imports
import sys
import os
import re
import traceback
import json
@ -15,8 +15,10 @@ import logging
import logging.handlers
from operator import itemgetter
from socket import error as socket_error
import base64
import cgi
from ldapcherry.exceptions import *
from exceptions import *
from ldapcherry.lclogging import *
from ldapcherry.roles import Roles
from ldapcherry.attributes import Attributes
@ -29,13 +31,7 @@ from cherrypy.lib.httputil import parse_query_string
from mako.template import Template
from mako import lookup
from mako import exceptions
if sys.version < '3':
from sets import Set as set
from urllib import quote_plus
else:
from urllib.parse import quote_plus
from sets import Set
SESSION_KEY = '_cp_username'
@ -60,6 +56,36 @@ class LdapCherry(object):
traceback=True
)
def _escape_list(self, data):
ret = []
for i in data:
ret.append(cgi.escape(i, True))
return ret
def _escape_dict(self, data):
for d in data:
if isinstance(data[d], list):
data[d] = self._escape_list(data[d])
elif isinstance(data[d], dict):
data[d] = self._escape_dict(data[d])
elif isinstance(data[d], Set):
data[d] = Set(self._escape_list(data[d]))
else:
data[d] = cgi.escape(data[d], True)
return data
def _escape(self, data, dtype):
if data is None:
return None
elif dtype == 'search_list':
for d in data:
data[d] = self._escape_dict(data[d])
elif dtype == 'attr_list':
data = self._escape_dict(data)
elif dtype == 'lonely_groups':
data = self._escape_dict(data)
return data
def _get_param(self, section, key, config, default=None):
""" Get configuration parameter "key" from config
@str section: the section of the config file
@ -118,10 +144,10 @@ class LdapCherry(object):
backends = self.backends_params.keys()
for b in self.roles.get_backends():
if b not in backends:
raise MissingBackend(b, 'role')
for b in self.attributes.get_backends():
raise MissingBackend(b)
for b in self.roles.get_backends():
if b not in backends:
raise MissingBackend(b, 'attribute')
raise MissingBackend(b)
def _init_backends(self, config):
""" Init all backends
@ -142,7 +168,7 @@ class LdapCherry(object):
try:
self.backends_display_names[backend] = \
self.backends_params[backend]['display_name']
except Exception as e:
except:
self.backends_display_names[backend] = backend
self.backends_params[backend]['display_name'] = backend
params = self.backends_params[backend]
@ -152,7 +178,7 @@ class LdapCherry(object):
except Exception as e:
raise MissingParameter('backends', backend + '.module')
try:
bc = __import__(module, globals(), locals(), ['Backend'], 0)
bc = __import__(module, globals(), locals(), ['Backend'], -1)
except Exception as e:
self._handle_exception(e)
raise BackendModuleLoadingFail(module)
@ -161,7 +187,7 @@ class LdapCherry(object):
key = self.attributes.get_backend_key(backend)
self.backends[backend] = bc.Backend(
params,
cherrypy.log.error,
cherrypy.log,
backend,
attrslist,
key,
@ -193,8 +219,8 @@ class LdapCherry(object):
'ldapcherry.ppolicy'
)
try:
pp = __import__(module, globals(), locals(), ['PPolicy'], 0)
except Exception as e:
pp = __import__(module, globals(), locals(), ['PPolicy'], -1)
except:
raise BackendModuleLoadingFail(module)
if 'ppolicy' in config:
ppcfg = config['ppolicy']
@ -212,7 +238,7 @@ class LdapCherry(object):
elif self.auth_mode == 'custom':
# load custom auth module
auth_module = self._get_param('auth', 'auth.module', config)
auth = __import__(auth_module, globals(), locals(), ['Auth'], 0)
auth = __import__(auth_module, globals(), locals(), ['Auth'], -1)
self.auth = auth.Auth(config['auth'], cherrypy.log)
else:
raise WrongParamValue(
@ -253,15 +279,6 @@ class LdapCherry(object):
handler.setFormatter(syslog_formatter)
cherrypy.log.access_log.addHandler(handler)
# if stdout, open a logger on stdout
elif access_handler == 'stdout':
cherrypy.log.access_log.handlers = []
handler = logging.StreamHandler(sys.stdout)
formatter = logging.Formatter(
'ldapcherry.access - %(levelname)s - %(message)s'
)
handler.setFormatter(formatter)
cherrypy.log.access_log.addHandler(handler)
# if file, we keep the default
elif access_handler == 'file':
pass
@ -307,15 +324,6 @@ class LdapCherry(object):
handler.setFormatter(syslog_formatter)
cherrypy.log.error_log.addHandler(handler)
# if stdout, open a logger on stdout
elif error_handler == 'stdout':
cherrypy.log.error_log.handlers = []
handler = logging.StreamHandler(sys.stdout)
formatter = logging.Formatter(
'ldapcherry.app - %(levelname)s - %(message)s'
)
handler.setFormatter(formatter)
cherrypy.log.error_log.addHandler(handler)
# if file, we keep the default
elif error_handler == 'file':
pass
@ -330,7 +338,6 @@ class LdapCherry(object):
cherrypy.log.error_log.setLevel(level)
if debug:
cherrypy.log.error_log.handlers = []
handler = logging.StreamHandler(sys.stderr)
handler.setLevel(logging.DEBUG)
cherrypy.log.error_log.addHandler(handler)
@ -380,8 +387,7 @@ class LdapCherry(object):
)
# preload templates
self.temp_lookup = lookup.TemplateLookup(
directories=self.template_dir, input_encoding='utf-8',
default_filters=['unicode', 'h']
directories=self.template_dir, input_encoding='utf-8'
)
# load each template
self.temp = {}
@ -567,7 +573,7 @@ class LdapCherry(object):
def _check_auth(self, must_admin, redir_login=True):
""" check if a user is autheticated and, optionnaly an administrator
if user not authenticated -> redirect to login page (with escaped URL
if user not authentifaced -> redirection to login page (with base64
of the originaly requested page (redirection after login)
if user authenticated, not admin and must_admin enabled -> 403 error
@boolean must_admin: flag "user must be an administrator to access
@ -582,13 +588,13 @@ class LdapCherry(object):
qs = ''
else:
qs = '?' + cherrypy.request.query_string
# Escaped version of the requested URL
quoted_requrl = quote_plus(cherrypy.url() + qs)
# base64 of the requested URL
b64requrl = base64.b64encode(cherrypy.url() + qs)
if not username:
# return to login page (with quoted url in query string)
# return to login page (with base64 of the url in query string
if redir_login:
raise cherrypy.HTTPRedirect(
"/signin?url=%(url)s" % {'url': quoted_requrl},
"/signin?url=%(url)s" % {'url': b64requrl},
)
else:
raise cherrypy.HTTPError(
@ -600,7 +606,7 @@ class LdapCherry(object):
or not cherrypy.session['connected']:
if redir_login:
raise cherrypy.HTTPRedirect(
"/signin?url=%(url)s" % {'url': quoted_requrl},
"/signin?url=%(url)s" % {'url': b64requrl},
)
else:
raise cherrypy.HTTPError(
@ -625,7 +631,7 @@ class LdapCherry(object):
else:
if redir_login:
raise cherrypy.HTTPRedirect(
"/signin?url=%(url)s" % {'url': quoted_requrl},
"/signin?url=%(url)s" % {'url': b64requrl},
)
else:
raise cherrypy.HTTPError(
@ -665,7 +671,6 @@ class LdapCherry(object):
self._add_notification(
'User already exists in backend "' + b + '"'
)
return
if not added:
raise e
@ -689,7 +694,7 @@ class LdapCherry(object):
roles.append(r)
groups = self.roles.get_groups(roles)
for b in groups:
self.backends[b].add_to_groups(username, set(groups[b]))
self.backends[b].add_to_groups(username, Set(groups[b]))
cherrypy.log.error(
msg="user '" + username + "' made member of " +
@ -817,10 +822,10 @@ class LdapCherry(object):
if b not in g:
g[b] = []
tmp = \
set(groups_add[b]) - \
set(groups_keep[b]) - \
set(groups_current[b]) - \
set(lonely_groups[b])
Set(groups_add[b]) - \
Set(groups_keep[b]) - \
Set(groups_current[b]) - \
Set(lonely_groups[b])
cherrypy.log.error(
msg="user '" + username + "' added to groups: " +
str(list(tmp)) + " in backend '" + b + "'",
@ -834,11 +839,11 @@ class LdapCherry(object):
g[b] = []
tmp = \
(
(set(groups_rm[b]) | set(groups_remove[b])) -
(set(groups_keep[b]) | set(groups_add[b]))
(Set(groups_rm[b]) | Set(groups_remove[b])) -
(Set(groups_keep[b]) | Set(groups_add[b]))
) & \
(
set(groups_current[b]) | set(lonely_groups[b])
Set(groups_current[b]) | Set(lonely_groups[b])
)
cherrypy.log.error(
msg="user '" + username + "' removed from groups: " +
@ -914,7 +919,7 @@ class LdapCherry(object):
if url is None:
redirect = "/"
else:
redirect = url
redirect = base64.b64decode(url)
raise cherrypy.HTTPRedirect(redirect)
else:
message = "login failed for user '%(user)s'" % {
@ -927,7 +932,7 @@ class LdapCherry(object):
if url is None:
qs = ''
else:
qs = '?url=' + quote_plus(url)
qs = '?url=' + url
raise cherrypy.HTTPRedirect("/signin" + qs)
@cherrypy.expose
@ -964,7 +969,7 @@ class LdapCherry(object):
return self.temp['index.tmpl'].render(
is_admin=is_admin,
attrs_list=attrs_list,
searchresult=user_attrs,
searchresult=self._escape(user_attrs, 'attr_list'),
notifications=self._empty_notification(),
)
@ -980,7 +985,7 @@ class LdapCherry(object):
res = None
attrs_list = self.attributes.get_search_attributes()
return self.temp['searchuser.tmpl'].render(
searchresult=res,
searchresult=self._escape(res, 'search_list'),
attrs_list=attrs_list,
is_admin=is_admin,
custom_js=self.custom_js,
@ -992,7 +997,7 @@ class LdapCherry(object):
def checkppolicy(self, **params):
""" search user page """
self._check_auth(must_admin=False, redir_login=False)
keys = list(params.keys())
keys = params.keys()
if len(keys) != 1:
cherrypy.response.status = 400
return "bad argument"
@ -1017,7 +1022,7 @@ class LdapCherry(object):
res = None
attrs_list = self.attributes.get_search_attributes()
return self.temp['searchadmin.tmpl'].render(
searchresult=res,
searchresult=self._escape(res, 'search_list'),
attrs_list=attrs_list,
is_admin=is_admin,
custom_js=self.custom_js,
@ -1080,7 +1085,7 @@ class LdapCherry(object):
is_admin = self._check_admin()
try:
referer = cherrypy.request.headers['Referer']
except Exception as e:
except:
referer = '/'
self._deleteuser(user)
self._add_notification('User Deleted')
@ -1099,7 +1104,7 @@ class LdapCherry(object):
self._add_notification("User modified")
try:
referer = cherrypy.request.headers['Referer']
except Exception as e:
except:
referer = '/'
raise cherrypy.HTTPRedirect(referer)
@ -1138,7 +1143,7 @@ class LdapCherry(object):
try:
form = self.temp['form.tmpl'].render(
attributes=self.attributes.attributes,
values=user_attrs,
values=self._escape(user_attrs, 'attr_list'),
modify=True,
keyattr=key,
autofill=False
@ -1156,7 +1161,10 @@ class LdapCherry(object):
form=form,
roles=roles,
is_admin=is_admin,
standalone_groups=standalone_groups,
standalone_groups=self._escape(
standalone_groups,
'lonely_groups'
),
backends_display_names=self.backends_display_names,
custom_js=self.custom_js,
notifications=self._empty_notification(),
@ -1170,7 +1178,7 @@ class LdapCherry(object):
@cherrypy.expose
@exception_decorator
def default(self, attr='', *args, **params):
def default(self, attr='', **params):
cherrypy.response.status = 404
self._check_auth(must_admin=False)
is_admin = self._check_admin()
@ -1211,7 +1219,7 @@ class LdapCherry(object):
form = self.temp['form.tmpl'].render(
attributes=self.attributes.get_selfattributes(),
values=user_attrs,
values=self._escape(user_attrs, 'attr_list'),
modify=True,
autofill=False
)

View File

@ -12,11 +12,9 @@ import re
from ldapcherry.pyyamlwrapper import loadNoDump
from ldapcherry.pyyamlwrapper import DumplicatedKey
from ldapcherry.exceptions import *
from sets import Set
import yaml
if sys.version < '3':
from sets import Set as set
# List of available types for form
types = ['string', 'textfield', 'email', 'int', 'stringlist',
'fix', 'password']
@ -26,14 +24,14 @@ class Attributes:
def __init__(self, attributes_file):
self.attributes_file = attributes_file
self.backends = set([])
self.backends = Set([])
self.self_attributes = {}
self.backend_attributes = {}
self.displayed_attributes = {}
self.key = None
try:
stream = open(attributes_file, 'r')
except Exception as e:
except:
raise MissingAttributesFile(attributes_file)
try:
self.attributes = loadNoDump(stream)
@ -71,7 +69,7 @@ class Attributes:
raise MissingUserKey()
def _is_email(self, email):
pattern = r'[\+\.\w]+@[-\.\w]+\.\w+'
pattern = '[\.\w]{1,}[@]\w+[.]\w+'
if re.match(pattern, email):
return True
else:
@ -130,9 +128,7 @@ class Attributes:
def get_backend_attributes(self, backend):
if backend not in self.backends:
raise WrongBackend(backend)
ret = list(self.backend_attributes[backend].keys())
ret.sort()
return ret
return self.backend_attributes[backend].keys()
def get_backend_key(self, backend):
if backend not in self.backends:

View File

@ -15,7 +15,6 @@ import ldapcherry.backend
from ldapcherry.exceptions import UserDoesntExist, GroupDoesntExist
import os
import re
import sys
class CaFileDontExist(Exception):
@ -29,7 +28,6 @@ class MissingAttr(Exception):
self.log = 'attributes "cn" and "unicodePwd" must be declared ' \
'in attributes.yml for all Active Directory backends.'
NO_ATTR = 0
DISPLAYED_ATTRS = 1
LISTED_ATTRS = 2
@ -130,11 +128,11 @@ class Backend(ldapcherry.backend.backendLdap.Backend):
self.dn_user_attr = 'cn'
self.key = 'sAMAccountName'
self.objectclasses = [
self._byte_p23('top'),
self._byte_p23('person'),
self._byte_p23('organizationalPerson'),
self._byte_p23('user'),
self._byte_p23('posixAccount'),
'top',
'person',
'organizationalPerson',
'user',
'posixAccount',
]
self.group_attrs = {
'member': "%(dn)s"
@ -143,25 +141,16 @@ class Backend(ldapcherry.backend.backendLdap.Backend):
self.attrlist = []
self.group_attrs_keys = []
for a in attrslist:
self.attrlist.append(self._byte_p2(a))
self.attrlist.append(self._str(a))
if self._byte_p2('cn') not in self.attrlist:
if 'cn' not in self.attrlist:
raise MissingAttr()
if self._byte_p2('unicodePwd') not in self.attrlist:
if 'unicodePwd' not in self.attrlist:
raise MissingAttr()
if sys.version < '3':
@staticmethod
def _tobyte(in_int):
return str(in_int)
else:
@staticmethod
def _tobyte(in_int):
return in_int.to_bytes(4, byteorder='big')
def _search_group(self, searchfilter, groupdn):
searchfilter = self._byte_p2(searchfilter)
searchfilter = self._str(searchfilter)
ldap_client = self._bind()
try:
r = ldap_client.search_s(
@ -193,24 +182,22 @@ class Backend(ldapcherry.backend.backendLdap.Backend):
ldap_client = self._bind()
if by_cn:
dn = self._byte_p2('CN=%(cn)s,%(user_dn)s' % {
dn = self._str('CN=%(cn)s,%(user_dn)s' % {
'cn': name,
'user_dn': self.userdn
})
else:
dn = self._byte_p2(name)
dn = self._str(name)
attrs = {}
attrs['unicodePwd'] = self._modlist(self._byte_p2(password_value))
attrs['unicodePwd'] = self._str(password_value)
ldif = modlist.modifyModlist({'unicodePwd': 'tmp'}, attrs)
ldap_client.modify_s(dn, ldif)
del(attrs['unicodePwd'])
attrs['UserAccountControl'] = self._modlist(
self._tobyte(NORMAL_ACCOUNT)
)
attrs['UserAccountControl'] = str(NORMAL_ACCOUNT)
ldif = modlist.modifyModlist({'UserAccountControl': 'tmp'}, attrs)
ldap_client.modify_s(dn, ldif)
@ -224,7 +211,7 @@ class Backend(ldapcherry.backend.backendLdap.Backend):
if 'unicodePwd' in attrs:
password = attrs['unicodePwd']
del(attrs['unicodePwd'])
userdn = self._get_user(self._byte_p2(username), NO_ATTR)
userdn = self._get_user(self._str(username), NO_ATTR)
self._set_password(userdn, password, False)
super(Backend, self).set_attrs(username, attrs)
@ -238,7 +225,7 @@ class Backend(ldapcherry.backend.backendLdap.Backend):
def get_groups(self, username):
username = ldap.filter.escape_filter_chars(username)
userdn = self._get_user(self._byte_p2(username), NO_ATTR)
userdn = self._get_user(self._str(username), NO_ATTR)
searchfilter = self.group_filter_tmpl % {
'userdn': userdn,
@ -258,7 +245,7 @@ class Backend(ldapcherry.backend.backendLdap.Backend):
)
for entry in groups:
ret.append(self._uni(entry[1]['cn'][0]))
ret.append(entry[1]['cn'][0])
return ret
def auth(self, username, password):
@ -268,8 +255,8 @@ class Backend(ldapcherry.backend.backendLdap.Backend):
ldap_client = self._connect()
try:
ldap_client.simple_bind_s(
self._byte_p2(binddn),
self._byte_p2(password)
self._str(binddn),
self._str(password)
)
except ldap.INVALID_CREDENTIALS:
ldap_client.unbind_s()

View File

@ -7,15 +7,12 @@
# This is a demo backend
import sys
from sets import Set
import ldapcherry.backend
from ldapcherry.exceptions import UserDoesntExist, \
GroupDoesntExist, MissingParameter, \
UserAlreadyExists
import re
if sys.version < '3':
from sets import Set as set
class Backend(ldapcherry.backend.Backend):
@ -40,17 +37,13 @@ class Backend(ldapcherry.backend.Backend):
self.backend_name = name
admin_user = self.get_param('admin.user', 'admin')
admin_password = self.get_param('admin.password', 'admin')
admin_groups = set(
self._basic_splitter(self.get_param('admin.groups'))
)
admin_groups = Set(re.split('\W+', self.get_param('admin.groups')))
basic_user = self.get_param('basic.user', 'user')
basic_password = self.get_param('basic.password', 'user')
basic_groups = set(
self._basic_splitter(self.get_param('basic.groups'))
)
basic_groups = Set(re.split('\W+', self.get_param('basic.groups')))
pwd_attr = self.get_param('pwd_attr')
self.search_attrs = set(
re.split(r'\W+', self.get_param('search_attributes')),
self.search_attrs = Set(
re.split('\W+', self.get_param('search_attributes')),
)
self.pwd_attr = pwd_attr
self.admin_user = admin_user
@ -67,11 +60,6 @@ class Backend(ldapcherry.backend.Backend):
'groups': basic_groups,
}
@staticmethod
def _basic_splitter(in_str):
return [re.sub(r'(?<!\\)\\', '', x)
for x in re.split(r'(?<!\\),\W*', in_str)]
def _check_fix_users(self, username):
if self.admin_user == username or self.basic_user == username:
raise Exception('User cannot be modified')
@ -103,7 +91,7 @@ class Backend(ldapcherry.backend.Backend):
if username in self.users:
raise UserAlreadyExists(username, self.backend_name)
self.users[username] = attrs
self.users[username]['groups'] = set([])
self.users[username]['groups'] = Set([])
def del_user(self, username):
""" Delete a user from the backend
@ -115,11 +103,11 @@ class Backend(ldapcherry.backend.Backend):
self._check_fix_users(username)
try:
del self.users[username]
except Exception as e:
except:
raise UserDoesntExist(username, self.backend_name)
def set_attrs(self, username, attrs):
""" set a list of attributes for a given user
""" Set a list of attributes for a given user
:param username: 'key' attribute of the user
:type username: string
@ -140,7 +128,7 @@ class Backend(ldapcherry.backend.Backend):
"""
self._check_fix_users(username)
current_groups = self.users[username]['groups']
new_groups = current_groups | set(groups)
new_groups = current_groups | Set(groups)
self.users[username]['groups'] = new_groups
def del_from_groups(self, username, groups):
@ -155,7 +143,7 @@ class Backend(ldapcherry.backend.Backend):
"""
self._check_fix_users(username)
current_groups = self.users[username]['groups']
new_groups = current_groups - set(groups)
new_groups = current_groups - Set(groups)
self.users[username]['groups'] = new_groups
def search(self, searchstring):
@ -188,7 +176,7 @@ class Backend(ldapcherry.backend.Backend):
"""
try:
return self.users[username]
except Exception as e:
except:
raise UserDoesntExist(username, self.backend_name)
def get_groups(self, username):
@ -200,5 +188,5 @@ class Backend(ldapcherry.backend.Backend):
"""
try:
return self.users[username]['groups']
except Exception as e:
except:
raise UserDoesntExist(username, self.backend_name)

View File

@ -11,16 +11,12 @@ import ldap.modlist as modlist
import ldap.filter
import logging
import ldapcherry.backend
import sys
from sets import Set
from ldapcherry.exceptions import UserDoesntExist, \
GroupDoesntExist, \
UserAlreadyExists
import os
import re
if sys.version < '3':
from sets import Set as set
PYTHON_LDAP_MAJOR_VERSION = ldap.__version__[0]
class CaFileDontExist(Exception):
@ -75,21 +71,21 @@ class Backend(ldapcherry.backend.Backend):
self.key = key
# objectclasses parameter is a coma separated list in configuration
# split it to get a real list, and convert it to bytes
for o in re.split(r'\W+', self.get_param('objectclasses')):
self.objectclasses.append(self._byte_p23(o))
for o in re.split('\W+', self.get_param('objectclasses')):
self.objectclasses.append(self._str(o))
self.group_attrs = {}
self.group_attrs_keys = set([])
self.group_attrs_keys = Set([])
for param in config:
name, sep, group = param.partition('.')
if name == 'group_attr':
self.group_attrs[group] = self.get_param(param)
self.group_attrs_keys |= set(
self.group_attrs_keys |= Set(
self._extract_format_keys(self.get_param(param))
)
self.attrlist = []
for a in attrslist:
self.attrlist.append(self._byte_p2(a))
self.attrlist.append(self._str(a))
# exception handler (mainly to log something meaningful)
def _exception_handler(self, e):
@ -132,8 +128,8 @@ class Backend(ldapcherry.backend.Backend):
".groupdn'",
)
elif et is ldap.OBJECT_CLASS_VIOLATION:
info = e.args[0]['info']
desc = e.args[0]['desc']
info = e[0]['info']
desc = e[0]['desc']
self._logger(
severity=logging.ERROR,
msg="Configuration error, " + desc + ", " + info,
@ -147,7 +143,7 @@ class Backend(ldapcherry.backend.Backend):
self.backend_name,
)
elif et is ldap.ALREADY_EXISTS:
desc = e.args[0]['desc']
desc = e[0]['desc']
self._logger(
severity=logging.ERROR,
msg="adding user failed, " + desc,
@ -261,16 +257,6 @@ class Backend(ldapcherry.backend.Backend):
else:
attrlist = None
self._logger(
severity=logging.DEBUG,
msg="%(backend)s: executing search "
"with filter '%(filter)s' in DN '%(dn)s'" % {
'backend': self.backend_name,
'dn': basedn,
'filter': self._uni(searchfilter)
}
)
# bind and search the ldap
ldap_client = self._bind()
try:
@ -312,7 +298,7 @@ class Backend(ldapcherry.backend.Backend):
user_filter = self.user_filter_tmpl % {
'username': self._uni(username)
}
r = self._search(self._byte_p2(user_filter), attrs, self.userdn)
r = self._search(self._str(user_filter), attrs, self.userdn)
if len(r) == 0:
return None
@ -324,88 +310,33 @@ class Backend(ldapcherry.backend.Backend):
else:
dn_entry = r[0]
return dn_entry
# python-ldap talks in bytes,
# as the rest of ldapcherry talks in unicode utf-8:
# * everything passed to python-ldap must be converted to bytes
# * everything coming from python-ldap must be converted to unicode
#
# The previous statement was true for python-ldap < version 3.X.
# With versions > 3.0.0 and python 3, it gets tricky,
# some parts of python-ldap takes string, specially the filters/escaper.
#
# so we have now:
# *_byte_p2 (unicode -> bytes conversion for python 2)
# *_byte_p3 (unicode -> bytes conversion for python 3)
# *_byte_p23 (unicode -> bytes conversion for python AND 3)
def _byte_p23(self, s):
def _str(self, s):
"""unicode -> bytes conversion"""
if s is None:
return None
return s.encode('utf-8')
if sys.version < '3':
def _byte_p2(self, s):
"""unicode -> bytes conversion (python 2)"""
if s is None:
return None
return s.encode('utf-8')
def _byte_p3(self, s):
"""pass through (does something in python 3)"""
return s
def _uni(self, s):
"""bytes -> unicode conversion"""
if s is None:
return None
return s.decode('utf-8', 'ignore')
def attrs_pretreatment(self, attrs):
attrs_srt = {}
for a in attrs:
attrs_srt[self._byte_p2(a)] = self._modlist(
self._byte_p2(attrs[a])
)
return attrs_srt
else:
def _byte_p2(self, s):
"""pass through (does something in python 2)"""
return s
def _byte_p3(self, s):
"""unicode -> bytes conversion"""
if s is None:
return None
return s.encode('utf-8')
def _uni(self, s):
"""bytes -> unicode conversion"""
if s is None:
return None
if type(s) is not str:
return s.decode('utf-8', 'ignore')
else:
return s
def attrs_pretreatment(self, attrs):
attrs_srt = {}
for a in attrs:
attrs_srt[self._byte_p2(a)] = self._modlist(
self._byte_p3(attrs[a])
)
return attrs_srt
def _uni(self, s):
"""bytes -> unicode conversion"""
if s is None:
return None
return s.decode('utf-8', 'ignore')
def auth(self, username, password):
"""Authentication of a user"""
binddn = self._get_user(self._byte_p2(username), NO_ATTR)
binddn = self._get_user(self._str(username), NO_ATTR)
if binddn is not None:
ldap_client = self._connect()
try:
ldap_client.simple_bind_s(
self._byte_p2(binddn),
self._byte_p2(password)
self._str(binddn),
self._str(password)
)
except ldap.INVALID_CREDENTIALS:
ldap_client.unbind_s()
@ -415,35 +346,30 @@ class Backend(ldapcherry.backend.Backend):
else:
return False
if PYTHON_LDAP_MAJOR_VERSION == '2':
@staticmethod
def _modlist(in_attr):
return in_attr
else:
@staticmethod
def _modlist(in_attr):
return [in_attr]
def attrs_pretreatment(self, attrs):
attrs_str = {}
for a in attrs:
attrs_str[self._str(a)] = self._str(attrs[a])
return attrs_str
def add_user(self, attrs):
"""add a user"""
ldap_client = self._bind()
# encoding crap
attrs_srt = self.attrs_pretreatment(attrs)
attrs_str = self.attrs_pretreatment(attrs)
attrs_srt[self._byte_p2('objectClass')] = self.objectclasses
attrs_str['objectClass'] = self.objectclasses
# construct is DN
dn = \
self._byte_p2(self.dn_user_attr) + \
self._byte_p2('=') + \
self._byte_p2(ldap.dn.escape_dn_chars(
attrs[self.dn_user_attr]
)
self._str(self.dn_user_attr) + \
'=' + \
ldap.dn.escape_dn_chars(
self._str(attrs[self.dn_user_attr])
) + \
self._byte_p2(',') + \
self._byte_p2(self.userdn)
# gen the ldif first add_s and add the user
ldif = modlist.addModlist(attrs_srt)
',' + \
self._str(self.userdn)
# gen the ldif fir add_s and add the user
ldif = modlist.addModlist(attrs_str)
try:
ldap_client.add_s(dn, ldif)
except ldap.ALREADY_EXISTS as e:
@ -457,7 +383,7 @@ class Backend(ldapcherry.backend.Backend):
"""delete a user"""
ldap_client = self._bind()
# recover the user dn
dn = self._byte_p2(self._get_user(self._byte_p2(username), NO_ATTR))
dn = self._str(self._get_user(self._str(username), NO_ATTR))
# delete
if dn is not None:
ldap_client.delete_s(dn)
@ -467,17 +393,17 @@ class Backend(ldapcherry.backend.Backend):
ldap_client.unbind_s()
def set_attrs(self, username, attrs):
""" set user attributes"""
""" Set user attributes"""
ldap_client = self._bind()
tmp = self._get_user(self._byte_p2(username), ALL_ATTRS)
tmp = self._get_user(self._str(username), ALL_ATTRS)
if tmp is None:
raise UserDoesntExist(username, self.backend_name)
dn = self._byte_p2(tmp[0])
dn = self._str(tmp[0])
old_attrs = tmp[1]
for attr in attrs:
bcontent = self._byte_p2(attrs[attr])
battr = self._byte_p2(attr)
new = {battr: self._modlist(self._byte_p3(bcontent))}
bcontent = self._str(attrs[attr])
battr = self._str(attr)
new = {battr: bcontent}
# if attr is dn entry, use rename
if attr.lower() == self.dn_user_attr.lower():
ldap_client.rename_s(
@ -494,42 +420,39 @@ class Backend(ldapcherry.backend.Backend):
if type(old_attrs[attr]) is list:
tmp = []
for value in old_attrs[attr]:
tmp.append(self._byte_p2(value))
tmp.append(self._str(value))
bold_value = tmp
else:
bold_value = self._modlist(
self._byte_p3(old_attrs[attr])
)
bold_value = self._str(old_attrs[attr])
old = {battr: bold_value}
# attribute is not set, just add it
else:
old = {}
ldif = modlist.modifyModlist(old, new)
if ldif:
try:
ldap_client.modify_s(dn, ldif)
except Exception as e:
ldap_client.unbind_s()
self._exception_handler(e)
try:
ldap_client.modify_s(dn, ldif)
except Exception as e:
ldap_client.unbind_s()
self._exception_handler(e)
ldap_client.unbind_s()
def add_to_groups(self, username, groups):
ldap_client = self._bind()
# recover dn of the user and his attributes
tmp = self._get_user(self._byte_p2(username), ALL_ATTRS)
tmp = self._get_user(self._str(username), ALL_ATTRS)
dn = tmp[0]
attrs = tmp[1]
attrs['dn'] = dn
self._normalize_group_attrs(attrs)
dn = self._byte_p2(tmp[0])
dn = self._str(tmp[0])
# add user to all groups
for group in groups:
group = self._byte_p2(group)
group = self._str(group)
# iterate on group membership attributes
for attr in self.group_attrs:
# fill the content template
content = self._byte_p2(self.group_attrs[attr] % attrs)
content = self._str(self.group_attrs[attr] % attrs)
self._logger(
severity=logging.DEBUG,
msg="%(backend)s: adding user '%(user)s'"
@ -543,14 +466,11 @@ class Backend(ldapcherry.backend.Backend):
'backend': self.backend_name
}
)
ldif = modlist.modifyModlist(
{},
{attr: self._modlist(self._byte_p3(content))}
)
ldif = modlist.modifyModlist({}, {attr: content})
try:
ldap_client.modify_s(group, ldif)
# if already member, not a big deal, just log it and continue
except (ldap.TYPE_OR_VALUE_EXISTS, ldap.ALREADY_EXISTS) as e:
except ldap.TYPE_OR_VALUE_EXISTS as e:
self._logger(
severity=logging.INFO,
msg="%(backend)s: user '%(user)s'"
@ -574,19 +494,19 @@ class Backend(ldapcherry.backend.Backend):
# it follows the same logic than add_to_groups
# but with MOD_DELETE
ldap_client = self._bind()
tmp = self._get_user(self._byte_p2(username), ALL_ATTRS)
tmp = self._get_user(self._str(username), ALL_ATTRS)
if tmp is None:
raise UserDoesntExist(username, self.backend_name)
dn = tmp[0]
attrs = tmp[1]
attrs['dn'] = dn
self._normalize_group_attrs(attrs)
dn = self._byte_p2(tmp[0])
dn = self._str(tmp[0])
for group in groups:
group = self._byte_p2(group)
group = self._str(group)
for attr in self.group_attrs:
content = self._byte_p2(self.group_attrs[attr] % attrs)
ldif = [(ldap.MOD_DELETE, attr, self._byte_p3(content))]
content = self._str(self.group_attrs[attr] % attrs)
ldif = [(ldap.MOD_DELETE, attr, content)]
try:
ldap_client.modify_s(group, ldif)
except ldap.NO_SUCH_ATTRIBUTE as e:
@ -609,9 +529,7 @@ class Backend(ldapcherry.backend.Backend):
def search(self, searchstring):
"""Search users"""
# escape special char to avoid injection
searchstring = ldap.filter.escape_filter_chars(
self._byte_p2(searchstring)
)
searchstring = ldap.filter.escape_filter_chars(self._str(searchstring))
# fill the search string template
searchfilter = self.search_filter_tmpl % {
'searchstring': searchstring
@ -636,7 +554,7 @@ class Backend(ldapcherry.backend.Backend):
def get_user(self, username):
"""Gest a specific user"""
ret = {}
tmp = self._get_user(self._byte_p2(username), ALL_ATTRS)
tmp = self._get_user(self._str(username), ALL_ATTRS)
if tmp is None:
raise UserDoesntExist(username, self.backend_name)
attrs_tmp = tmp[1]
@ -650,7 +568,7 @@ class Backend(ldapcherry.backend.Backend):
def get_groups(self, username):
"""Get all groups of a user"""
username = ldap.filter.escape_filter_chars(self._byte_p2(username))
username = ldap.filter.escape_filter_chars(self._str(username))
userdn = self._get_user(username, NO_ATTR)
searchfilter = self.group_filter_tmpl % {
@ -661,5 +579,5 @@ class Backend(ldapcherry.backend.Backend):
groups = self._search(searchfilter, NO_ATTR, self.groupdn)
ret = []
for entry in groups:
ret.append(self._uni(entry[0]))
ret.append(entry[0])
return ret

View File

@ -46,12 +46,11 @@ class MissingRole(Exception):
class MissingBackend(Exception):
def __init__(self, backend, type_conf):
def __init__(self, backend):
self.backend = backend
self.log = \
"backend '%(backend)s' does not exist in main config file " \
"but is still declared in '%(type_conf)s' file" % \
{'backend': backend, 'type_conf': type_conf}
"backend '%(backend)s' does not exist in main config file" % \
{'backend': backend}
class WrongBackend(Exception):

View File

@ -1,3 +1,4 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# vim:set expandtab tabstop=4 shiftwidth=4:
#

View File

@ -9,14 +9,12 @@ import os
import sys
import copy
from sets import Set
from ldapcherry.pyyamlwrapper import loadNoDump
from ldapcherry.pyyamlwrapper import DumplicatedKey
from ldapcherry.exceptions import *
import yaml
if sys.version < '3':
from sets import Set as set
class CustomDumper(yaml.SafeDumper):
"A custom YAML dumper that never emits aliases"
@ -29,10 +27,10 @@ class Roles:
def __init__(self, role_file):
self.role_file = role_file
self.backends = set([])
self.backends = Set([])
try:
stream = open(role_file, 'r')
except Exception as e:
except:
raise MissingRolesFile(role_file)
try:
self.roles_raw = loadNoDump(stream)
@ -53,12 +51,11 @@ class Roles:
for backends in backends_list:
for b in backends:
if b not in ret:
ret[b] = set([])
ret[b] = Set([])
for group in backends[b]:
ret[b].add(group)
for b in ret:
ret[b] = list(ret[b])
ret[b].sort()
return ret
def _flatten(self, roles=None, groups=None):
@ -137,8 +134,8 @@ class Roles:
if roleid not in self.graph:
self.graph[roleid] = {
'parent_roles': set([]),
'sub_roles': set([])
'parent_roles': Set([]),
'sub_roles': Set([])
}
# Create the nested groups
@ -150,7 +147,7 @@ class Roles:
if b not in self.group2roles:
self.group2roles[b] = {}
if g not in self.group2roles[b]:
self.group2roles[b][g] = set([])
self.group2roles[b][g] = Set([])
self.group2roles[b][g].add(roleid)
parent_roles[roleid] = []
@ -226,7 +223,7 @@ class Roles:
# add groups of the role to usedgroups
for b in self.roles[role]['backends_groups']:
if b not in usedgroups:
usedgroups[b] = set([])
usedgroups[b] = Set([])
for g in self.roles[role]['backends_groups'][b]:
usedgroups[b].add(g)
@ -257,11 +254,11 @@ class Roles:
"""get groups to remove from list of
roles to remove and current roles
"""
current_roles = set(current_roles)
current_roles = Set(current_roles)
ret = {}
roles_to_remove = set(roles_to_remove)
tmp = set([])
roles_to_remove = Set(roles_to_remove)
tmp = Set([])
# get sub roles of the role to remove that the user belongs to
# if we remove a role, there is no reason to keep the sub roles
for r in roles_to_remove:
@ -270,7 +267,7 @@ class Roles:
tmp.add(sr)
roles_to_remove = roles_to_remove.union(tmp)
roles = current_roles.difference(set(roles_to_remove))
roles = current_roles.difference(Set(roles_to_remove))
groups_roles = self._get_groups(roles)
groups_roles_to_remove = self._get_groups(roles_to_remove)
@ -287,12 +284,12 @@ class Roles:
for b in self.flatten[r]['backends_groups']:
groups = self.flatten[r]['backends_groups'][b]
if b not in ret:
ret[b] = set(groups)
ret[b] = ret[b].union(set(groups))
ret[b] = Set(groups)
ret[b] = ret[b].union(Set(groups))
return ret
def _get_subroles(self, role):
ret = set([])
ret = Set([])
for sr in self.graph[role]['sub_roles']:
tmp = self._get_subroles(sr)
tmp.add(sr)
@ -301,10 +298,10 @@ class Roles:
def get_roles(self, groups):
"""get list of roles and list of standalone groups"""
roles = set([])
parentroles = set([])
notroles = set([])
tmp = set([])
roles = Set([])
parentroles = Set([])
notroles = Set([])
tmp = Set([])
usedgroups = {}
unusedgroups = {}
ret = {}
@ -319,7 +316,7 @@ class Roles:
for g in groups[b]:
if b not in usedgroups or g not in usedgroups[b]:
if b not in unusedgroups:
unusedgroups[b] = set([])
unusedgroups[b] = Set([])
unusedgroups[b].add(g)
ret['roles'] = roles

View File

@ -1,8 +0,0 @@
# -*- coding: utf-8 -*-
# vim:set expandtab tabstop=4 shiftwidth=4:
#
# The MIT License (MIT)
# ldapCherry
# Copyright (c) 2014 Carpentier Pierre-Francois
version = '1.1.1'

View File

@ -1,4 +1,4 @@
#!/usr/bin/env python3
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import with_statement

View File

@ -30,7 +30,7 @@ groups = {
'ad' : ['Domain Users', 'Domain Users 2'],
'ldap': ['cn=users,ou=group,dc=example,dc=com',
'cn=nagios admins,ou=group,dc=example,dc=com',
'cn=developers,ou=group,dc=example,dc=com',
'cn=developpers,ou=group,dc=example,dc=com',
],
'toto': ['not a group'],
}

View File

@ -1,4 +0,0 @@
CherryPy>=3.0.0
PyYAML
Mako
python-ldap==3.4.0

View File

@ -1,4 +0,0 @@
CherryPy>=3.0.0
PyYAML
Mako
python-ldap==3.4.0

View File

@ -1,23 +1,21 @@
var classes = ["lcform-col-1", "lcform-col-2"];
var j, lenj = classes.length;
for(j=0; j < lenj; j++){
classes = ['lcform-col-1', 'lcform-col-2'];
for(var j in classes){
var formSelector = classes[j];
var forms = document.getElementsByClassName(formSelector);
//console.log(formSelector);
//console.log(forms);
if (forms.length > 0){
forms[0].style.removeProperty("display");
var InputGroups = forms[0].getElementsByClassName("input-group-addon");
//console.log(InputGroups);
var i, len = InputGroups.length;
var in_groups = forms[0].getElementsByClassName('input-group-addon');
//console.log(in_groups);
var i, len = in_groups.length;
var longest = 0;
for(i=0; i < len; i++){
if (InputGroups[i].id !== "basic-addon-password2"){
longest = longest < InputGroups[i].clientWidth ? InputGroups[i].clientWidth : longest;
if (in_groups[i].id != 'basic-addon-password2'){
longest = longest < in_groups[i].clientWidth ? in_groups[i].clientWidth : longest;
}
}
for(i=0; i < len; i++){
InputGroups[i].style.minWidth = (longest + 0) + "px";
in_groups[i].style.minWidth = (longest + 0) + 'px';
}
//console.log(longest);
}

View File

@ -30,8 +30,3 @@ function lcUidNumber(firstname, lastname, minuid, maxuid){
function lcHomeDir(firstname, lastname, basedir){
return basedir+lcUid(firstname, lastname);
}
function lcCopy(value){
return value;
}

View File

@ -6,14 +6,14 @@
</div>
<div class="col-md-12 column">
<div class="well well-sm">
<form method='POST' autocomplete="off" action='/adduser' role="form" class="form-signin" id="form">
<form method='POST' autocomplete="off" action='/adduser' role="form" class="form-signin" id=form>
<fieldset>
<legend>Fill new user's attributes:</legend>
${form | n}
${form}
</fieldset>
<fieldset>
<legend>Enable/Disable user's roles:</legend>
${roles | n}
${roles}
</fieldset>
<div class="form-group">
<div class="input-group">

View File

@ -58,7 +58,7 @@
<body>
% if notifications:
% for notif in notifications:
<script type="text/javascript">$.notify('${notif | n}')</script>
<script type="text/javascript">$.notify('${notif}')</script>
% endfor
% endif
<div class="container">
@ -70,6 +70,6 @@
<p class="muted credit"><a href="http://ldapcherry.readthedocs.org" target="_blank">LdapCherry</a> • © 2016 • Pierre-François Carpentier • Released under the MIT License</p>
</div>
</div>
<script type="text/javascript" src="/static/js/alignforms.js"></script>
</body>
<script type="text/javascript" src="/static/js/alignforms.js"></script>
</html>

View File

@ -1,6 +1,5 @@
## -*- coding: utf-8 -*-
<%
from markupsafe import Markup
len_attr = len(attributes)
switch = len_attr / 2
if not switch * 2 == len_attr:
@ -32,32 +31,32 @@ for a in sorted(attributes.keys(), key=lambda attr: attributes[attr]['weight']):
raw_value = values[a]
if raw_value is None:
raw_value = ''
value = Markup(' value="{}"').format(raw_value)
value2 = Markup('<option>{}</option>').format(raw_value)
value = ' value="'+ raw_value + '"'
value2 = '<option>'+ raw_value +'</option>'
else:
raw_value = ''
value = ''
value2 = ''
if 'default' in attr and value == '':
value = Markup(' value="{}"').format(attr['default'])
value = ' value="'+ attr['default'] + '"'
%>
<span class="input-group-addon" id="basic-addon-${a}">${attr['display_name']}</span>
% if modify and a == keyattr:
<input type="hidden" id="attr.${a}" name="attr.${a}" class="form-control" autocomplete='off' aria-describedby="basic-addon-${a}" ${required} ${value | n} readonly onfocus="this.removeAttribute('readonly');">
<input type="hidden" id="attr.${a}" name="attr.${a}" class="form-control" autocomplete='off' aria-describedby="basic-addon-${a}" ${required} ${value} readonly onfocus="this.removeAttribute('readonly');">
<span class="form-control" aria-describedby="basic-addon-${a}">${raw_value}</span>
% elif attr['type'] == 'string':
<input type="text" id="attr.${a}" name="attr.${a}" class="form-control" autocomplete='off' placeholder="${attr['description']}" aria-describedby="basic-addon-${a}" ${required} ${value | n} readonly onfocus="this.removeAttribute('readonly');">
<input type="text" id="attr.${a}" name="attr.${a}" class="form-control" autocomplete='off' placeholder="${attr['description']}" aria-describedby="basic-addon-${a}" ${required} ${value} readonly onfocus="this.removeAttribute('readonly');">
% elif attr['type'] == 'email':
<input type="email" id="attr.${a}" name="attr.${a}" class="form-control" autocomplete='off' placeholder="${attr['description']}" aria-describedby="basic-addon-${a}" ${required} ${value | n} data-error="email address is invalid" readonly onfocus="this.removeAttribute('readonly');">
<input type="email" id="attr.${a}" name="attr.${a}" class="form-control" autocomplete='off' placeholder="${attr['description']}" aria-describedby="basic-addon-${a}" ${required} ${value} data-error="email address is invalid" readonly onfocus="this.removeAttribute('readonly');">
% elif attr['type'] == 'int':
<input type="number" id="attr.${a}" name="attr.${a}" class="form-control" autocomplete='off' placeholder="${attr['description']}" aria-describedby="basic-addon-${a}" ${required} ${value | n} readonly onfocus="this.removeAttribute('readonly');">
<input type="number" id="attr.${a}" name="attr.${a}" class="form-control" autocomplete='off' placeholder="${attr['description']}" aria-describedby="basic-addon-${a}" ${required} ${value} readonly onfocus="this.removeAttribute('readonly');">
% elif attr['type'] == 'fix':
<input type="hidden" id="attr.${a}" name="attr.${a}" class="form-control" autocomplete='off' aria-describedby="basic-addon-${a}" ${required} value="${attr['value']}" readonly onfocus="this.removeAttribute('readonly');">
<span class="form-control" placeholder="${attr['description']}" aria-describedby="basic-addon-${a}">${attr['value']}</span>
% elif attr['type'] == 'stringlist':
<select class="form-control" id="attr.${a}" name="attr.${a}">
${value2 | n}
${value2}
%for val in attr['values']:
%if '<option>' + val + '</option>' != value2:
<option>${val}</option>
@ -77,40 +76,39 @@ for a in sorted(attributes.keys(), key=lambda attr: attributes[attr]['weight']):
% endfor
</%def>
<div class="row">
<div class="col-md-6 column lcform-col-1" style="display:none;">
<div class="col-md-6 column lcform-col-1">
${form_col(lc1)}
</div>
<div class="col-md-6 column lcform-col-2" style="display:none;">
<div class="col-md-6 column lcform-col-2">
${form_col(lc2)}
</div>
</div>
% if autofill:
<%
attr_set = []
from sets import Set
attr_set = Set([])
attr_events = {}
functions = {}
for attrid in attributes:
attr = attributes[attrid]
field = 'attr.' + attrid
if field not in attr_set:
attr_set.append(field)
if 'autofill' in attr:
function = attr['autofill']['function']
tuple = (field, function)
if not tuple in functions:
functions[tuple] = []
for arg in attr['autofill']['args']:
if arg[0] == '$':
field_arg = 'attr.' + arg[1:]
if field_arg not in attr_set:
attr_set.append(field_arg)
functions[tuple].append("fields['" + field_arg + "'].value")
if not field_arg in attr_events:
attr_events[field_arg] = []
attr_events[field_arg].append(tuple)
else:
value = arg
functions[tuple].append("'" + value + "'")
attr_set.add(field)
if 'autofill' in attr:
function = attr['autofill']['function']
tuple = (field, function)
if not tuple in functions:
functions[tuple] = []
for arg in attr['autofill']['args']:
if arg[0] == '$':
field_arg = 'attr.' + arg[1:]
attr_set.add(field_arg)
functions[tuple].append("fields['" + field_arg + "'].value")
if not field_arg in attr_events:
attr_events[field_arg] = []
attr_events[field_arg].append(tuple)
else:
value = arg
functions[tuple].append("'" + value + "'")
%>
<script>
var fields = new Object();
@ -123,7 +121,7 @@ if (fields['${attrid}'] != null) {
fields['${attrid}'].onchange = function () {
% for tuple in attr_events[attrid]:
if (typeof(${tuple[1]}) == "function") {
fields['${tuple[0]}'].value = ${tuple[1] | n}(${', '.join(functions[tuple]) | n});
fields['${tuple[0]}'].value = ${tuple[1]}(${', '.join(functions[tuple])});
}
% endfor
};

View File

@ -11,8 +11,8 @@
</div>
<div class="panel-body">
<table id="RecordTable" class="table table-hover table-condensed">
% if not searchresult is None:
<tbody>
% if not searchresult is None:
%for attr in sorted(attrs_list.keys(), key=lambda attr: attrs_list[attr]['weight']):
<tr>
% if attr in searchresult:
@ -26,8 +26,8 @@
% endif
</tr>
% endfor
</tbody>
%endif
</tbody>
</table>
</div>
</div>

View File

@ -4,13 +4,13 @@
<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' role="form" class="form-signin"
% if url:
action='login?url=${url | u}'
% else:
action='login'
% endif
>
<%
if url is None:
qs=''
else:
qs='?url=' + url
%>
<form method='POST' action='/login${qs}' role="form" class="form-signin">
<div class="form-group">
<h2 class="form-signin-heading">Please sign in</h2>
<div class="input-group">

View File

@ -9,11 +9,11 @@
<form method='POST' action='/modify' role="form" class="form-signin" id="form">
<fieldset>
<legend>Modify user's attributes:</legend>
${form | n}
${form}
</fieldset>
<fieldset>
<legend>Enable/Disable user's roles:</legend>
${roles | n}
${roles}
</fieldset>
% if len(standalone_groups) != 0:
<fieldset>

View File

@ -1,7 +1,7 @@
## -*- coding: utf-8 -*-
<script type="text/javascript">
var graph = ${graph_js | n};
var roles = ${roles_js | n};
var graph = ${graph_js};
var roles = ${roles_js};
function enableParentRoles(roleid){
var parentRoles = graph[roleid]['parent_roles'];
var DnRole = roles[roleid];

View File

@ -52,10 +52,10 @@
</td>
% endfor
<td>
<a href="/modify?user=${user | n,u}" class="btn btn-xs blue pad" ><span class="glyphicon glyphicon-cog"></span> Modify</a>
<a href="/modify?user=${user}" class="btn btn-xs blue pad" ><span class="glyphicon glyphicon-cog"></span> Modify</a>
</td>
<td>
<a href="/delete?user=${user | n,u}" data-toggle='confirmation-delete' class="btn btn-xs red pad"><span class="glyphicon glyphicon-remove-sign"></span> Delete</a>
<a href="/delete?user=${user}" data-toggle='confirmation-delete' class="btn btn-xs red pad"><span class="glyphicon glyphicon-remove-sign"></span> Delete</a>
</td>
</tr>
% endfor

View File

@ -8,7 +8,7 @@
<div class="well well-sm">
<form method='POST' action='/selfmodify' autocomplete="off" role="form" class="form-signin" id="form">
<legend>Modify your attributes:</legend>
${form | n}
${form}
</fieldset>
<div class="form-group">
<div class="input-group">

View File

@ -5,11 +5,11 @@ Gre='\33[0;32m';
RCol='\33[0m';
cd `dirname $0`
python3 setup.py test #&&\
#printf "\nPEP 8 compliance check:\n\n"
#pep8 \
# --recurssive ./ \
# --show-source \
# --exclude=.venv,.tox,dist,docs,build,*.egg,tests,misc . && \
# printf "[${Gre}Passed${RCol}] Yeah! everything is clean\n\n" || \
# printf "[${Red}Failed${RCol}] Oh No! there is some mess to fix\n\n"
python setup.py test &&\
printf "\nPEP 8 compliance check:\n\n"
pep8 \
--repeat \
--show-source \
--exclude=.venv,.tox,dist,docs,build,*.egg,tests,misc . && \
printf "[${Gre}Passed${RCol}] Yeah! everything is clean\n\n" || \
printf "[${Red}Failed${RCol}] Oh No! there is some mess to fix\n\n"

View File

@ -1,4 +1,4 @@
#!/usr/bin/env python3
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# vim:set expandtab tabstop=4 shiftwidth=4:
# The MIT License (MIT)
@ -95,14 +95,14 @@ def start(configfile=None, daemonize=False, environment=None,
# Always start the engine; this will start all other services
try:
engine.start()
except Exception as e:
except:
# Assume the error has been logged already via bus.log.
sys.exit(1)
else:
engine.block()
def main():
if __name__ == '__main__':
from optparse import OptionParser
p = OptionParser()
@ -142,7 +142,3 @@ def main():
start(options.config, options.daemonize,
options.environment, options.fastcgi, options.scgi,
options.pidfile, options.cgi, options.debug)
if __name__ == '__main__':
main()

View File

@ -1,4 +1,4 @@
#!/usr/bin/env python3
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# vim:set expandtab tabstop=4 shiftwidth=4:
@ -17,24 +17,17 @@ data_dir = os.path.join(datarootdir, 'ldapcherry')
config_dir = os.path.join(sysconfdir, 'ldapcherry')
small_description = 'A simple web application to manage Ldap entries'
sys.path.append('ldapcherry/')
from version import version
# change requirements according to python version
if sys.version_info[0] == 2:
install_requires = [
'CherryPy >= 3.0.0,< 18.0.0',
'python-ldap',
'PyYAML',
'Mako'
],
elif sys.version_info[0] == 3:
install_requires = [
'CherryPy >= 3.0.0',
'python-ldap',
'PyYAML',
'Mako'
],
elif sys.version_info[0] == 3:
print('unsupported version')
exit(1)
else:
print('unsupported version')
exit(1)
@ -115,7 +108,7 @@ if as_option_root() or not os.path.exists(
setup(
name='ldapcherry',
zip_safe=False,
version=version,
version='0.5.0',
author='Pierre-Francois Carpentier',
author_email='carpentier.pf@gmail.com',
packages=[
@ -124,18 +117,16 @@ setup(
'ldapcherry.ppolicy'
],
data_files=resources_files,
entry_points = {
'console_scripts': ['ldapcherryd = ldapcherry.cli:main']
},
scripts=['scripts/ldapcherryd'],
url='https://github.com/kakwa/ldapcherry',
license=license,
description=small_description,
long_description=description,
install_requires=install_requires,
tests_require=['pytest', 'pep8', 'pytidylib'],
tests_require=['pytest', 'pep8'],
cmdclass={'test': PyTest},
classifiers=[
'Development Status :: 5 - Production/Stable',
'Development Status :: 3 - Alpha',
'Environment :: Web Environment',
'Framework :: CherryPy',
'Intended Audience :: System Administrators',

View File

@ -21,10 +21,10 @@ users:
display_name: Administrators Level 3
description: description
subroles: {}
developers:
developpers:
backends_groups:
ad: [Domain Users]
ldap: ['cn=developers,ou=group,dc=example,dc=com']
ldap: ['cn=developpers,ou=group,dc=example,dc=com']
display_name: Developpers
description: description
subroles: {}

View File

@ -23,12 +23,12 @@ admin-lv2:
ad:
- Domain Users
developers:
developpers:
display_name: Developpers
description: description
backends_groups:
ldap:
- cn=developers,ou=group,dc=example,dc=com
- cn=developpers,ou=group,dc=example,dc=com
- cn=users,ou=group,dc=example,dc=com
ad:
- Domain Users

View File

@ -23,12 +23,12 @@ admin-lv2:
ad:
- Administrators
#developers:
#developpers:
# display_name: Developpers
# description: Developpers of the system
# backends_groups:
# ldap:
# - cn=developers,ou=Group,dc=example,dc=org
# - cn=developpers,ou=Group,dc=example,dc=org
# - cn=users,ou=Group,dc=example,dc=org
#users:

View File

@ -23,12 +23,12 @@ admin -lv2:
ad:
- Domain Users
developers:
developpers:
display_name: Developpers
description: description
backends_groups:
ldap:
- cn=developers,ou=group,dc=example,dc=com
- cn=developpers,ou=group,dc=example,dc=com
- cn=users,ou=group,dc=example,dc=com
ad:
- Domain Users

View File

@ -23,12 +23,12 @@ admin -lv3:
ad:
- Domain Users
developers:
developpers:
display_name: Developpers
description: description
backends_groups:
ldap:
- cn=developers,ou=group,dc=example,dc=com
- cn=developpers,ou=group,dc=example,dc=com
- cn=users,ou=group,dc=example,dc=com
ad:
- Domain Users

View File

@ -17,12 +17,12 @@ admin-lv2:
display_name: Administrators Level 2
description: description
developers:
developpers:
display_name: Developpers
description: description
backends_groups:
ldap:
- cn=developers,ou=group,dc=example,dc=com
- cn=developpers,ou=group,dc=example,dc=com
- cn=users,ou=group,dc=example,dc=com
ad:
- Domain Users

View File

@ -22,12 +22,12 @@ admin-lv2:
ad:
- Domain Users
developers:
developpers:
display_name: Developpers
description: description
backends_groups:
ldap:
- cn=developers,ou=group,dc=example,dc=com
- cn=developpers,ou=group,dc=example,dc=com
- cn=users,ou=group,dc=example,dc=com
ad:
- Domain Users

View File

@ -17,12 +17,12 @@ admin-lv2:
- cn=nagios admins,ou=Group,dc=example,dc=org
- cn=users,ou=Group,dc=example,dc=org
developers:
developpers:
display_name: Developpers
description: Developpers of the system
backends_groups:
ldap:
- cn=developers,ou=Group,dc=example,dc=org
- cn=developpers,ou=Group,dc=example,dc=org
- cn=users,ou=Group,dc=example,dc=org
users:

View File

@ -1,7 +1,7 @@
import os
def travis_disabled(f):
def _decorator(f):
print('test has been disabled on travis')
print 'test has been disabled on travis'
if 'TRAVIS' in os.environ and os.environ['TRAVIS'] == 'yes':
return _decorator
else:
@ -9,7 +9,7 @@ def travis_disabled(f):
def slow_disabled(f):
def _decorator(f):
print('test has been disabled by env var LCNOSLOW')
print 'test has been disabled by env var LCNOSLOW'
if 'LCNOSLOW' in os.environ and os.environ['LCNOSLOW'] == 'yes':
return _decorator
else:

243
tests/html_validator.py Executable file
View File

@ -0,0 +1,243 @@
#!/usr/bin/python
# Copyright (c) 2007-2008 Mozilla Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE.
from __future__ import print_function, with_statement
import os
import sys
import re
import string
import gzip
# Several "try" blocks for python2/3 differences (@secretrobotron)
try:
import httplib
except ImportError:
import http.client as httplib
try:
import urlparse
except ImportError:
import urllib.parse as urlparse
try:
from BytesIO import BytesIO
except ImportError:
from io import BytesIO
try:
maketrans = str.maketrans
except AttributeError:
maketrans = string.maketrans
#
# Begin
#
extPat = re.compile(r'^.*\.([A-Za-z]+)$')
extDict = {
'html' : 'text/html',
'htm' : 'text/html',
'xhtml' : 'application/xhtml+xml',
'xht' : 'application/xhtml+xml',
'xml' : 'application/xml',
}
forceXml = False
forceHtml = False
gnu = False
errorsOnly = False
encoding = None
fileName = None
contentType = None
inputHandle = None
service = 'https://html5.validator.nu/'
argv = sys.argv[1:]
#
# Parse command line input
#
for arg in argv:
if '--help' == arg:
print('-h : force text/html')
print('-x : force application/xhtml+xml')
print('-g : GNU output')
print('-e : errors only (no info or warnings)')
print('--encoding=foo : declare encoding foo')
print('--service=url : the address of the HTML5 validator')
print('One file argument allowed. Leave out to read from stdin.')
sys.exit(0)
elif arg.startswith('--encoding='):
encoding = arg[11:]
elif arg.startswith('--service='):
service = arg[10:]
elif arg.startswith('--'):
sys.stderr.write('Unknown argument %s.\n' % arg)
sys.exit(2)
elif arg.startswith('-'):
for c in arg[1:]:
if 'x' == c:
forceXml = True
elif 'h' == c:
forceHtml = True
elif 'g' == c:
gnu = True
elif 'e' == c:
errorsOnly = True
else:
sys.stderr.write('Unknown argument %s.\n' % arg)
sys.exit(3)
else:
if fileName:
sys.stderr.write('Cannot have more than one input file.\n')
sys.exit(1)
fileName = arg
#
# Ensure a maximum of one forced output type
#
if forceXml and forceHtml:
sys.stderr.write('Cannot force HTML and XHTML at the same time.\n')
sys.exit(2)
#
# Set contentType
#
if forceXml:
contentType = 'application/xhtml+xml'
elif forceHtml:
contentType = 'text/html'
elif fileName:
m = extPat.match(fileName)
if m:
ext = m.group(1)
ext = ext.translate(maketrans(string.ascii_uppercase, string.ascii_lowercase))
if ext in extDict:
contentType = extDict[ext]
else:
sys.stderr.write('Unable to guess Content-Type from file name. Please force the type.\n')
sys.exit(3)
else:
sys.stderr.write('Could not extract a filename extension. Please force the type.\n')
sys.exit(6)
else:
sys.stderr.write('Need to force HTML or XHTML when reading from stdin.\n')
sys.exit(4)
if encoding:
contentType = '%s; charset=%s' % (contentType, encoding)
#
# Read the file argument (or STDIN)
#
if fileName:
inputHandle = fileName
else:
inputHandle = sys.stdin
with open(inputHandle, mode='rb') as inFile:
data = inFile.read()
with BytesIO() as buf:
# we could use another with block here, but it requires Python 2.7+
zipFile = gzip.GzipFile(fileobj=buf, mode='wb')
zipFile.write(data)
zipFile.close()
gzippeddata = buf.getvalue()
#
# Prepare the request
#
url = service
if gnu:
url = url + '?out=gnu'
else:
url = url + '?out=text'
if errorsOnly:
url = url + '&level=error'
connection = None
response = None
status = 302
redirectCount = 0
#
# Make the request
#
while status in (302,301,307) and redirectCount < 10:
if redirectCount > 0:
url = response.getheader('Location')
parsed = urlparse.urlsplit(url)
if redirectCount > 0:
connection.close() # previous connection
print('Redirecting to %s' % url)
print('Please press enter to continue or type \'stop\' followed by enter to stop.')
if raw_input() != '':
sys.exit(0)
if parsed.scheme == 'https':
connection = httplib.HTTPSConnection(parsed[1])
else:
connection = httplib.HTTPConnection(parsed[1])
headers = {
'Accept-Encoding': 'gzip',
'Content-Type': contentType,
'Content-Encoding': 'gzip',
'Content-Length': len(gzippeddata),
}
urlSuffix = '%s?%s' % (parsed[2], parsed[3])
connection.connect()
connection.request('POST', urlSuffix, body=gzippeddata, headers=headers)
response = connection.getresponse()
status = response.status
redirectCount += 1
#
# Handle the response
#
if status != 200:
sys.stderr.write('%s %s\n' % (status, response.reason))
sys.exit(5)
if response.getheader('Content-Encoding', 'identity').lower() == 'gzip':
response = gzip.GzipFile(fileobj=BytesIO(response.read()))
if fileName and gnu:
quotedName = '"%s"' % fileName.replace("'", '\\042')
for line in response.read().split('\n'):
if line:
sys.stdout.write(quotedName)
sys.stdout.write(line + '\n')
else:
output = response.read()
# python2/3 difference in output's type
if not isinstance(output, str):
output = output.decode('utf-8')
sys.stdout.write(output)
connection.close()

View File

@ -1,4 +1,4 @@
#!/usr/bin/env python3
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import with_statement
@ -6,12 +6,10 @@ from __future__ import unicode_literals
import pytest
import sys
from sets import Set
from ldapcherry.attributes import Attributes
from ldapcherry.exceptions import *
from ldapcherry.pyyamlwrapper import DumplicatedKey, RelationError
if sys.version < '3':
from sets import Set as set
class TestError(object):
@ -29,7 +27,7 @@ class TestError(object):
def testGetSelfAttributes(self):
inv = Attributes('./tests/cfg/attributes.yml')
ret = inv.get_backends()
expected = set(['ldap', 'ad'])
expected = Set(['ldap', 'ad'])
assert ret == expected
def testGetSearchAttributes(self):
@ -42,7 +40,6 @@ class TestError(object):
inv = Attributes('./tests/cfg/attributes.yml')
ret = inv.get_backend_attributes('ldap')
expected = ['shell', 'cn', 'userPassword', 'uidNumber', 'gidNumber', 'sn', 'home', 'givenName', 'email', 'uid']
expected.sort()
assert ret == expected
def testGetKey(self):

View File

@ -1,4 +1,4 @@
#!/usr/bin/env python3
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import with_statement
@ -6,14 +6,12 @@ from __future__ import unicode_literals
import pytest
import sys
from sets import Set
from ldapcherry.backend.backendAD import Backend
from ldapcherry.exceptions import *
from disable import travis_disabled
import cherrypy
import logging
if sys.version < '3':
from sets import Set as set
cfg = {
'display_name': u'test☭',
@ -76,14 +74,25 @@ class TestError(object):
assert res == False
@travis_disabled
def testsetPassword(self):
def testMissingParam(self):
cfg2 = {}
return True
try:
inv = Backend(cfg2, cherrypy.log, u'test☭', attr, 'sAMAccountName')
except MissingKey:
return
else:
raise AssertionError("expected an exception")
@travis_disabled
def testSetPassword(self):
inv = Backend(cfg, cherrypy.log, u'test☭', attr, 'sAMAccountName')
try:
inv.add_user(default_user.copy())
inv.add_to_groups(u'☭default_user', default_groups)
except:
pass
inv.set_attrs(u'☭default_user', {'unicodePwd': u'test☭P66642$'})
inv.set_attrs(u'☭default_user', {'unicodePwd': u'test☭P66642$'})
ret = inv.auth(u'☭default_user', u'test☭P66642$')
inv.del_user(u'☭default_user')
assert ret == True
@ -119,21 +128,16 @@ class TestError(object):
@travis_disabled
def testSearchUser(self):
inv = Backend(cfg, cherrypy.log, u'test☭', attr, 'sAMAccountName')
try:
inv.del_user(u'☭default_user')
except: pass
try:
inv.del_user(u'☭default_user2')
except: pass
try:
inv.add_user(default_user.copy())
except: pass
except:
pass
inv.add_user(default_user2.copy())
ret = inv.search(u'test☭')
expected = [u'☭default_user', u'☭default_user2']
inv.del_user(u'☭default_user')
inv.del_user(u'☭default_user2')
assert set(ret.keys()) == set(expected)
assert Set(ret.keys()) == Set(expected)
@travis_disabled
def testAddUser(self):

View File

@ -1,4 +1,4 @@
#!/usr/bin/env python3
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import with_statement
@ -6,14 +6,12 @@ from __future__ import unicode_literals
import pytest
import sys
from sets import Set
from ldapcherry.backend.backendDemo import Backend
from ldapcherry.exceptions import *
from disable import travis_disabled
import cherrypy
import logging
if sys.version < '3':
from sets import Set as set
cfg = {
'display_name': 'test',
@ -71,6 +69,16 @@ class TestError(object):
res = inv.auth('notauser', 'password') or inv.auth('default_user', 'notapassword')
assert res == False
def testMissingParam(self):
cfg2 = {}
return True
try:
inv = Backend(cfg2, cherrypy.log, 'test', attr, 'uid')
except MissingKey:
return
else:
raise AssertionError("expected an exception")
def testGetUser(self):
inv = Backend(cfg, cherrypy.log, 'test', attr, 'uid')
inv.add_user(default_user)
@ -84,7 +92,7 @@ class TestError(object):
inv.add_user(default_user)
inv.add_to_groups('default_user', default_groups)
ret = inv.get_groups('default_user')
expected = set(default_groups)
expected = Set(default_groups)
assert ret == expected
def testSearchUser(self):
@ -93,7 +101,7 @@ class TestError(object):
inv.add_user(default_user2)
ret = inv.search('default')
expected = ['default_user', 'default_user2']
assert set(ret.keys()) == set(expected)
assert Set(ret.keys()) == Set(expected)
def testAddUser(self):
try:

View File

@ -1,4 +1,4 @@
#!/usr/bin/env python3
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import with_statement
@ -6,14 +6,13 @@ from __future__ import unicode_literals
import pytest
import sys
from sets import Set
from ldapcherry.backend.backendLdap import Backend, CaFileDontExist
from ldapcherry.exceptions import *
from disable import travis_disabled
import cherrypy
import logging
import ldap
if sys.version < '3':
from sets import Set as set
cfg = {
'module' : 'ldapcherry.backend.ldap',
@ -105,8 +104,7 @@ class TestError(object):
try:
ldapc.simple_bind_s(inv.binddn, inv.bindpassword)
except ldap.SERVER_DOWN as e:
assert e.args[0]['info'] == 'TLS: hostname does not match CN in peer certificate' or \
e.args[0]['info'] == '(unknown error code)'
assert e[0]['info'] == 'TLS: hostname does not match CN in peer certificate'
else:
raise AssertionError("expected an exception")
@ -130,6 +128,16 @@ class TestError(object):
res = inv.auth('notauser', 'password') or inv.auth(u'jwatsoné', 'notapasswordé')
assert res == False
def testMissingParam(self):
cfg2 = {}
return True
try:
inv = Backend(cfg2, cherrypy.log, 'ldap', attr, 'uid')
except MissingKey:
return
else:
raise AssertionError("expected an exception")
def testGetUser(self):
inv = Backend(cfg, cherrypy.log, 'ldap', attr, 'uid')
ret = inv.get_user(u'jwatsoné')
@ -150,6 +158,7 @@ class TestError(object):
]
inv.add_to_groups(u'jwatsoné', groups)
ret = inv.get_groups(u'jwatsoné')
print ret
inv.del_from_groups(u'jwatsoné', ['cn=hrpeople,ou=Groups,dc=example,dc=org'])
inv.del_from_groups(u'jwatsoné', ['cn=hrpeople,ou=Groups,dc=example,dc=org'])
assert ret == ['cn=itpeople,ou=Groups,dc=example,dc=org', 'cn=hrpeople,ou=Groups,dc=example,dc=org']

View File

@ -1,4 +1,4 @@
#!/usr/bin/env python3
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import with_statement
@ -9,6 +9,7 @@ import subprocess
from tempfile import NamedTemporaryFile as tempfile
import re
from sets import Set
from ldapcherry import LdapCherry
from ldapcherry.exceptions import *
from ldapcherry.pyyamlwrapper import DumplicatedKey, RelationError
@ -20,9 +21,6 @@ import logging
from ldapcherry.lclogging import *
from disable import *
import json
from tidylib import tidy_document
if sys.version < '3':
from sets import Set as set
cherrypy.session = {}
@ -80,42 +78,19 @@ class HtmlValidationFailed(Exception):
def __init__(self, out):
self.errors = out
def _is_html_error(line):
for p in [
r'.*Warning: trimming empty <span>.*',
r'.*Error: <nav> is not recognized!.*',
r'.*Warning: discarding unexpected <nav>.*',
r'.*Warning: discarding unexpected </nav>.*',
r'.*Warning: <meta> proprietary attribute "charset".*',
r'.*Warning: <meta> lacks "content" attribute.*',
r'.*Warning: <link> inserting "type" attribute.*',
r'.*Warning: <link> proprietary attribute.*',
r'.*Warning: <input> proprietary attribute.*',
r'.*Warning: <button> proprietary attribute.*',
r'.*Warning: <form> proprietary attribute.*',
r'.*Warning: <table> lacks "summary" attribute.*',
r'.*Warning: <script> inserting "type" attribute.*',
r'.*Warning: <input> attribute "id" has invalid value.*',
r'.*Warning: <a> proprietary attribute.*',
r'.*Warning: <input> attribute "type" has invalid value.*',
r'.*Warning: <span> proprietary attribute.*',
]:
if re.match(p, line):
return False
return True
def htmlvalidator(page):
document, errors = tidy_document(page,
options={'numeric-entities':1})
f = tempfile()
for line in errors.splitlines():
if _is_html_error(line):
print("################")
print("Blocking error: '%s'" % line)
print("all tidy_document errors:")
print(errors)
print("################")
raise HtmlValidationFailed(line)
stdout = tempfile()
f.write(page.encode("utf-8"))
f.seek(0)
ret = subprocess.call(['./tests/html_validator.py', '-h', f.name], stdout=stdout)
stdout.seek(0)
out = stdout.read()
f.close()
stdout.close()
print(out)
if not re.search(r'Error:.*', out) is None:
raise HtmlValidationFailed(out)
class BadModule():
pass
@ -152,7 +127,7 @@ class TestError(object):
def testLog(self):
app = LdapCherry()
cfg = { 'global' : {}}
for t in ['none', 'file', 'syslog', 'stdout']:
for t in ['none', 'file', 'syslog']:
cfg['global']['log.access_handler']=t
cfg['global']['log.error_handler']=t
app._set_access_log(cfg, logging.DEBUG)
@ -209,7 +184,7 @@ class TestError(object):
app.login(u'jwatsoné', u'passwordwatsoné')
except cherrypy.HTTPRedirect as e:
expected = 'http://127.0.0.1:8080/'
assert e.urls[0] == expected
assert e[0][0] == expected
else:
raise AssertionError("expected an exception")
@ -221,7 +196,7 @@ class TestError(object):
app.login(u'jwatsoné', u'wrongPasswordé')
except cherrypy.HTTPRedirect as e:
expected = 'http://127.0.0.1:8080/signin'
assert e.urls[0] == expected
assert e[0][0] == expected
else:
raise AssertionError("expected an exception")
@ -362,7 +337,7 @@ class TestError(object):
app = LdapCherry()
loadconf('./tests/cfg/ldapcherry_adldap.cfg', app)
inv = ldapcherry.backend.backendAD.Backend(adcfg, cherrypy.log, u'test☭', adattr, 'sAMAccountName')
try:
try:
app._deleteuser(u'☭default_user')
except:
pass
@ -385,3 +360,4 @@ class TestError(object):
get_loglevel('alert') is logging.CRITICAL and \
get_loglevel('emergency') is logging.CRITICAL and \
get_loglevel('notalevel') is logging.INFO

View File

@ -1,4 +1,4 @@
#!/usr/bin/env python3
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import with_statement
@ -6,17 +6,16 @@ from __future__ import unicode_literals
import pytest
import sys
from sets import Set
from ldapcherry.roles import Roles
from ldapcherry.exceptions import DumplicateRoleKey, MissingKey, DumplicateRoleContent, MissingRolesFile, MissingRole
from ldapcherry.pyyamlwrapper import DumplicatedKey, RelationError
if sys.version < '3':
from sets import Set as set
class TestError(object):
def testNominal(self):
inv = Roles('./tests/cfg/roles.yml')
print(inv.roles)
print inv.roles
return True
def testMissingDisplayName(self):
@ -65,7 +64,7 @@ class TestError(object):
['admin-lv2', 'admin-lv3', 'users'],
['admin-lv2']
)
expected = {'ad': set(['Administrators', 'Domain Controllers']), 'ldap': set(['cn=nagios admins,ou=group,dc=example,dc=com', 'cn=puppet admins,ou=group,dc=example,dc=com', 'cn=dns admins,ou=group,dc=example,dc=com'])}
expected = {'ad': Set(['Administrators', 'Domain Controllers']), 'ldap': Set(['cn=nagios admins,ou=group,dc=example,dc=com', 'cn=puppet admins,ou=group,dc=example,dc=com', 'cn=dns admins,ou=group,dc=example,dc=com'])}
assert groups == expected
def testGetGroup(self):
@ -79,50 +78,7 @@ class TestError(object):
def testNested(self):
inv = Roles('./tests/cfg/nested.yml')
expected = {
'admin-lv2': {
'LC_admins': True,
'backends_groups': {
'ad': ['Domain Users'],
'ldap': ['cn=nagios '
'admins,ou=group,dc=example,dc=com',
'cn=users,ou=group,dc=example,dc=com']
},
'description': 'description',
'display_name': 'Administrators Level 2'
},
'admin-lv3': {
'backends_groups': {
'ad': ['Administrators',
'Domain Controllers',
'Domain Users'],
'ldap': ['cn=dns '
'admins,ou=group,dc=example,dc=com',
'cn=nagios '
'admins,ou=group,dc=example,dc=com',
'cn=puppet '
'admins,ou=group,dc=example,dc=com',
'cn=users,ou=group,dc=example,dc=com']
},
'description': 'description',
'display_name': 'Administrators Level 3'
},
'developers': {
'backends_groups': {
'ad': ['Domain Users'],
'ldap': ['cn=developers,ou=group,dc=example,dc=com',
'cn=users,ou=group,dc=example,dc=com']},
'description': 'description',
'display_name': 'Developpers'
},
'users': {
'backends_groups': {
'ad': ['Domain Users'],
'ldap': ['cn=users,ou=group,dc=example,dc=com']},
'description': 'description',
'display_name': 'Simple Users'
}
}
expected = {'developpers': {'backends_groups': {'ad': ['Domain Users'], 'ldap': ['cn=developpers,ou=group,dc=example,dc=com', 'cn=users,ou=group,dc=example,dc=com']}, 'display_name': 'Developpers', 'description': 'description'}, 'admin-lv3': {'backends_groups': {'ad': ['Domain Users', 'Administrators', 'Domain Controllers'], 'ldap': ['cn=nagios admins,ou=group,dc=example,dc=com', 'cn=users,ou=group,dc=example,dc=com', 'cn=puppet admins,ou=group,dc=example,dc=com', 'cn=dns admins,ou=group,dc=example,dc=com']}, 'display_name': 'Administrators Level 3', 'description': 'description'}, 'admin-lv2': {'backends_groups': {'ad': ['Domain Users'], 'ldap': ['cn=nagios admins,ou=group,dc=example,dc=com', 'cn=users,ou=group,dc=example,dc=com']}, 'display_name': 'Administrators Level 2', 'description': 'description', 'LC_admins': True}, 'users': {'backends_groups': {'ad': ['Domain Users'], 'ldap': ['cn=users,ou=group,dc=example,dc=com']}, 'display_name': 'Simple Users', 'description': 'description'}}
assert expected == inv.flatten
def testGetGroupMissingRole(self):
@ -152,13 +108,13 @@ class TestError(object):
def testGetAllRoles(self):
inv = Roles('./tests/cfg/roles.yml')
res = inv.get_allroles()
expected = ['developers', 'admin-lv3', 'admin-lv2', 'users']
expected = ['developpers', 'admin-lv3', 'admin-lv2', 'users']
assert res == expected
def testGetAllRoles(self):
inv = Roles('./tests/cfg/roles.yml')
res = inv.get_backends()
expected = set(['ad', 'ldap'])
expected = Set(['ad', 'ldap'])
assert res == expected
def testDumpNested(self):
@ -187,9 +143,9 @@ class TestError(object):
'ad' : ['Domain Users', 'Domain Users 2'],
'ldap': ['cn=users,ou=group,dc=example,dc=com',
'cn=nagios admins,ou=group,dc=example,dc=com',
'cn=developers,ou=group,dc=example,dc=com',
'cn=developpers,ou=group,dc=example,dc=com',
],
'toto': ['not a group'],
}
expected = {'unusedgroups': {'toto': set(['not a group']), 'ad': set(['Domain Users 2'])}, 'roles': set(['developers', 'admin-lv2', 'users'])}
expected = {'unusedgroups': {'toto': Set(['not a group']), 'ad': Set(['Domain Users 2'])}, 'roles': Set(['developpers', 'admin-lv2', 'users'])}
assert inv.get_roles(groups) == expected

View File

@ -1,27 +1,19 @@
#!/bin/sh
apt update
DEBIAN_FRONTEND=noninteractive apt-get install ldap-utils slapd -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" -f -q -y
DEBIAN_FRONTEND=noninteractive apt-get install samba-dsdb-modules samba-vfs-modules samba -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" -f -q -y
DEBIAN_FRONTEND=noninteractive apt-get install samba -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" -f -q -y
DEBIAN_FRONTEND=noninteractive apt-get install winbind -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" -f -q -y
DEBIAN_FRONTEND=noninteractive apt-get install build-essential python3-dev libsasl2-dev slapd ldap-utils tox lcov valgrind libtidy-dev libldap-dev python3-cherrypy3 python3-ldap python3-mako -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" -f -q -y
[ -e '/etc/default/slapd' ] && rm -rf /etc/default/slapd
cp -r `dirname $0`/etc/default/slapd /etc/default/slapd
[ -e '/etc/ldap' ] && rm -rf /etc/ldap
cp -r `dirname $0`/etc/ldap /etc/ldap
[ -e '/etc/ldapcherry' ] && rm -rf /etc/ldapcherry
cp -r `dirname $0`/etc/ldapcherry /etc/ldapcherry
rsync -a `dirname $0`/ /
cd `dirname $0`/../../
sudo sed -i "s%template_dir.*%template_dir = '`pwd`/resources/templates/'%" /etc/ldapcherry/ldapcherry.ini
sudo sed -i "s%tools.staticdir.dir.*%tools.staticdir.dir = '`pwd`/resources/static/'%" /etc/ldapcherry/ldapcherry.ini
chown -R openldap:openldap /etc/ldap/
rm /etc/ldap/slapd.d/cn\=config/*mdb*
/etc/init.d/slapd restart
ldapadd -c -H ldap://localhost:390 -x -D "cn=admin,dc=example,dc=org" -f /etc/ldap/content.ldif -w password
if grep -q '127.0.0.1' /etc/hosts && ! grep -q 'ldap.ldapcherry.org' /etc/hosts
if grep -q '127.0.0.1' /etc/hosts
then
sed -i "s/\(127.0.0.1.*\)/\1 ldap.ldapcherry.org ad.ldapcherry.org ldap.dnscherry.org/" /etc/hosts
else
@ -32,6 +24,11 @@ cat /etc/hosts
df -h
/etc/init.d/samba stop
/etc/init.d/smbd stop
/etc/init.d/nmbd stop
/etc/init.d/samba-ad-dc stop
find /var/log/samba/ -type f -exec rm -f {} \;
smbconffile=/etc/samba/smb.conf
@ -43,11 +40,9 @@ role=dc
sambacmd=samba-tool
adpass=qwertyP455
systemctl unmask samba-ad-dc
hostname ad.ldapcherry.org
/etc/init.d/dnsmasq stop
pkill -9 dnsmasq
pkill -9 samba
kill -9 `cat /var/run/samba/smbd.pid`
rm -f /var/run/samba/smbd.pid
@ -70,16 +65,12 @@ cat ${smbconffile}
mv /var/lib/samba/private/krb5.conf /etc/krb5.conf
sleep 15
sleep 5
systemctl restart samba-ad-dc
/etc/init.d/samba-ad-dc restart
/etc/init.d/samba-ad-dc start
cat /var/log/samba/*
sleep 5
samba-tool domain passwordsettings set -d 1 --complexity off
samba-tool domain passwordsettings set -d 1 --min-pwd-length 0
systemctl status samba-ad-dc
ss -apn | grep samba
netstat -apn | grep samba

View File

@ -103,9 +103,9 @@ cn: users
description: Basic Users
member: cn=Sheri Smith,ou=people,dc=example,dc=org
dn: cn=developers,ou=group,dc=example,dc=org
dn: cn=developpers,ou=group,dc=example,dc=org
objectclass: groupofnames
cn: developers
cn: developpers
description: Developpers
member: cn=Sheri Smith,ou=people,dc=example,dc=org

View File

@ -1,5 +1,4 @@
# AUTO-GENERATED FILE - DO NOT EDIT!! Use ldapmodify.
# CRC32 7a6099db
dn: cn=config
objectClass: olcGlobal
cn: config
@ -8,12 +7,12 @@ olcLogLevel: none
olcPidFile: /var/run/slapd/slapd.pid
olcToolThreads: 1
structuralObjectClass: olcGlobal
entryUUID: 38579c70-750a-103e-8489-9578878139e2
entryUUID: 2964261c-6754-1033-8d48-1703270f04bd
creatorsName: cn=config
createTimestamp: 20240312221838Z
entryCSN: 20240312221838.644900Z#000000#000#000000
createTimestamp: 20140503211805Z
entryCSN: 20140503211805.658522Z#000000#000#000000
modifiersName: cn=config
modifyTimestamp: 20240312221838Z
modifyTimestamp: 20140503211805Z
olcTLSCACertificateFile: /etc/ldap/ssl/TEST-cacert.pem
olcTLSCertificateFile: /etc/ldap/ssl/ldap@dnscherry.org-cert.pem
olcTLSCertificateKeyFile: /etc/ldap/ssl/ldap@dnscherry.org-key.pem

View File

@ -1,14 +1,14 @@
# AUTO-GENERATED FILE - DO NOT EDIT!! Use ldapmodify.
# CRC32 baf86138
# CRC32 d8758c75
dn: cn=module{0}
objectClass: olcModuleList
cn: module{0}
olcModulePath: /usr/lib/ldap
olcModuleLoad: {0}back_mdb
olcModuleLoad: {0}back_hdb
structuralObjectClass: olcModuleList
entryUUID: 3857cd3a-750a-103e-8491-9578878139e2
entryUUID: 2964fd58-6754-1033-8d50-1703270f04bd
creatorsName: cn=admin,cn=config
createTimestamp: 20240312221838Z
entryCSN: 20240312221838.646168Z#000000#000#000000
createTimestamp: 20140503211805Z
entryCSN: 20140503211805.664149Z#000000#000#000000
modifiersName: cn=admin,cn=config
modifyTimestamp: 20240312221838Z
modifyTimestamp: 20140503211805Z

View File

@ -1,12 +1,12 @@
# AUTO-GENERATED FILE - DO NOT EDIT!! Use ldapmodify.
# CRC32 fff51a11
# CRC32 db8c9607
dn: cn=schema
objectClass: olcSchemaConfig
cn: schema
structuralObjectClass: olcSchemaConfig
entryUUID: 3857a42c-750a-103e-848c-9578878139e2
entryUUID: 29644a02-6754-1033-8d4b-1703270f04bd
creatorsName: cn=admin,cn=config
createTimestamp: 20240312221838Z
entryCSN: 20240312221838.645118Z#000000#000#000000
createTimestamp: 20140503211805Z
entryCSN: 20140503211805.659557Z#000000#000#000000
modifiersName: cn=admin,cn=config
modifyTimestamp: 20240312221838Z
modifyTimestamp: 20140503211805Z

View File

@ -0,0 +1,12 @@
# AUTO-GENERATED FILE - DO NOT EDIT!! Use ldapmodify.
# CRC32 94d41de9
dn: olcBackend={0}hdb
objectClass: olcBackendConfig
olcBackend: {0}hdb
structuralObjectClass: olcBackendConfig
entryUUID: 2965a6f4-6754-1033-8d51-1703270f04bd
creatorsName: cn=admin,cn=config
createTimestamp: 20140503211805Z
entryCSN: 20140503211805.668489Z#000000#000#000000
modifiersName: cn=admin,cn=config
modifyTimestamp: 20140503211805Z

View File

@ -1,5 +1,5 @@
# AUTO-GENERATED FILE - DO NOT EDIT!! Use ldapmodify.
# CRC32 e8f0a13c
# CRC32 511a173e
dn: olcDatabase={-1}frontend
objectClass: olcDatabaseConfig
objectClass: olcFrontendConfig
@ -10,9 +10,9 @@ olcAccess: {1}to dn.exact="" by * read
olcAccess: {2}to dn.base="cn=Subschema" by * read
olcSizeLimit: 500
structuralObjectClass: olcDatabaseConfig
entryUUID: 38579ebe-750a-103e-848a-9578878139e2
entryUUID: 296430d0-6754-1033-8d49-1703270f04bd
creatorsName: cn=config
createTimestamp: 20240312221838Z
entryCSN: 20240312221838.644978Z#000000#000#000000
createTimestamp: 20140503211805Z
entryCSN: 20140503211805.658911Z#000000#000#000000
modifiersName: cn=config
modifyTimestamp: 20240312221838Z
modifyTimestamp: 20140503211805Z

View File

@ -1,5 +1,5 @@
# AUTO-GENERATED FILE - DO NOT EDIT!! Use ldapmodify.
# CRC32 caed0a01
# CRC32 fc130d0e
dn: olcDatabase={0}config
objectClass: olcDatabaseConfig
olcDatabase: {0}config
@ -7,9 +7,9 @@ olcAccess: {0}to * by dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=external
,cn=auth manage by * break
olcRootDN: cn=admin,cn=config
structuralObjectClass: olcDatabaseConfig
entryUUID: 3857a274-750a-103e-848b-9578878139e2
entryUUID: 29644142-6754-1033-8d4a-1703270f04bd
creatorsName: cn=config
createTimestamp: 20240312221838Z
entryCSN: 20240312221838.645074Z#000000#000#000000
createTimestamp: 20140503211805Z
entryCSN: 20140503211805.659332Z#000000#000#000000
modifiersName: cn=config
modifyTimestamp: 20240312221838Z
modifyTimestamp: 20140503211805Z

View File

@ -1,9 +1,7 @@
# AUTO-GENERATED FILE - DO NOT EDIT!! Use ldapmodify.
# CRC32 91d29b33
dn: olcDatabase={1}mdb
dn: olcDatabase={1}hdb
objectClass: olcDatabaseConfig
objectClass: olcMdbConfig
olcDatabase: {1}mdb
objectClass: olcHdbConfig
olcDatabase: {1}hdb
olcDbDirectory: /var/lib/ldap
olcSuffix: dc=example,dc=org
olcAccess: {0}to attrs=userPassword,shadowLastChange by self write by anonymou
@ -15,15 +13,15 @@ olcLastMod: TRUE
olcRootDN: cn=admin,dc=example,dc=org
olcRootPW: {SSHA}Fp+rSxe5eFsj0DGITJts4DwdSDFDZG9P
olcDbCheckpoint: 512 30
olcDbConfig: {0}set_cachesize 0 2097152 0
olcDbConfig: {1}set_lk_max_objects 1500
olcDbConfig: {2}set_lk_max_locks 1500
olcDbConfig: {3}set_lk_max_lockers 1500
olcDbIndex: objectClass eq
olcDbIndex: cn,uid eq
olcDbIndex: uidNumber,gidNumber eq
olcDbIndex: member,memberUid eq
olcDbMaxSize: 1073741824
structuralObjectClass: olcMdbConfig
entryUUID: 3857d7ee-750a-103e-8492-9578878139e2
structuralObjectClass: olcHdbConfig
entryUUID: 2965af5a-6754-1033-8d52-1703270f04bd
creatorsName: cn=admin,cn=config
createTimestamp: 20240312221838Z
entryCSN: 20240312221838.646442Z#000000#000#000000
createTimestamp: 20140503211805Z
entryCSN: 20140503211805.668708Z#000000#000#000000
modifiersName: cn=admin,cn=config
modifyTimestamp: 20240312221838Z
modifyTimestamp: 20140503211805Z

View File

@ -1,132 +0,0 @@
cn:
description: "First Name and Display Name"
display_name: "Display Name"
type: string
weight: 30
autofill:
function: lcDisplayName
args:
- $first-name
- $name
backends:
ldap: cn
ad: cn
first-name:
description: "First name of the user"
display_name: "First Name"
search_displayed: True
type: string
weight: 20
backends:
ldap: givenName
ad: givenName
name:
description: "Family name of the user"
display_name: "Name"
search_displayed: True
weight: 10
type: string
backends:
ldap: sn
ad: sn
email:
description: "Email of the user"
display_name: "Email"
search_displayed: True
type: email
weight: 40
autofill:
function: lcMail
args:
- $first-name
- $name
- '@example.com'
backends:
ldap: mail
ad: mail
uid:
description: "UID of the user"
display_name: "UID"
search_displayed: True
key: True
type: string
weight: 50
autofill:
function: lcUid
args:
- $first-name
- $name
- '10000'
- '40000'
backends:
ldap: uid
ad: sAMAccountName
uidNumber:
description: "User ID Number of the user"
display_name: "UID Number"
weight: 60
type: int
autofill:
function: lcUidNumber
args:
- $first-name
- $name
- '10000'
- '40000'
backends:
ldap: uidNumber
ad: uidNumber
gidNumber:
description: "Group ID Number of the user"
display_name: "GID Number"
weight: 70
type: int
default: '10000'
backends:
ldap: gidNumber
ad: gidNumber
shell:
description: "Shell of the user"
display_name: "Shell"
weight: 80
self: True
type: stringlist
values:
- /bin/bash
- /bin/zsh
- /bin/sh
backends:
ldap: loginShell
ad: loginShell
home:
description: "Home user path"
display_name: "Home"
weight: 90
type: string
autofill:
function: lcHomeDir
args:
- $first-name
- $name
- /home/
backends:
ldap: homeDirectory
ad: homeDirectory
password:
description: "Password of the user"
display_name: "Password"
weight: 31
self: True
type: password
backends:
ldap: userPassword
ad: unicodePwd
#logscript:
# description: "Windows login script"
# display_name: "Login script"
# weight: 100
# type: fix
# value: login1.bat
# backends:
# ad: scriptPath

View File

@ -1,125 +0,0 @@
# 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 = 'syslog'
# logger none for error and ldapcherry log
#log.error_handler = 'none'
# log level
log.level = 'debug'
# 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.backend.backendLdap'
ldap.groupdn = 'ou=Group,dc=example,dc=org'
ldap.userdn = 'ou=people,dc=example,dc=org'
ldap.binddn = 'cn=dnscherry,dc=example,dc=org'
ldap.password = 'password'
ldap.uri = 'ldap://ldap.ldapcherry.org:390'
ldap.ca = '/etc/dnscherry/TEST-cacert.pem'
ldap.starttls = 'off'
ldap.checkcert = 'off'
ldap.user_filter_tmpl = '(uid=%(username)s)'
ldap.group_filter_tmpl = '(member=%(userdn)s)'
ldap.search_filter_tmpl = '(|(uid=%(searchstring)s*)(sn=%(searchstring)s*))'
ldap.group_attr.member = "%(dn)s"
#ldap.objectclasses = 'top, person, organizationalPerson, user'
ldap.objectclasses = 'top, person, posixAccount, inetOrgPerson'
ldap.dn_user_attr = 'uid'
ldap.timeout = 1
ad.module = 'ldapcherry.backend.backendAD'
ad.domain = 'dc.ldapcherry.org'
ad.login = 'administrator'
ad.password = 'qwertyP455'
ad.uri = 'ldaps://ldap.ldapcherry.org:636'
ad.checkcert = 'off'
# 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 = 'none'
# custom auth module to load
#auth.module = 'ldapcherry.auth.modNone'
[ppolicy]
# password policy module
ppolicy.module = 'ldapcherry.ppolicy.simple'
# parameters of the module
min_length = 2
min_upper = 0
min_digit = 0
# resources parameters
[resources]
# templates directory
templates.dir = './resources/templates/'
[/static]
tools.staticdir.on = True
tools.staticdir.dir = './resources/static/'

View File

@ -1,36 +0,0 @@
admin-lv3:
display_name: Administrators Level 3
description: description
backends_groups:
ldap:
- cn=dns admins,ou=Group,dc=example,dc=org
- cn=nagios admins,ou=Group,dc=example,dc=org
- cn=puppet admins,ou=Group,dc=example,dc=org
- cn=users,ou=Group,dc=example,dc=org
ad:
- Administrators
- Domain Controllers
admin-lv2:
display_name: Administrators Level 2
description: description
LC_admins: True
backends_groups:
ldap:
- cn=nagios admins,ou=Group,dc=example,dc=org
- cn=users,ou=Group,dc=example,dc=org
developers:
display_name: Developpers
description: description
backends_groups:
ldap:
- cn=developers,ou=Group,dc=example,dc=org
- cn=users,ou=Group,dc=example,dc=org
users:
display_name: Simple Users
description: description
backends_groups:
ldap:
- cn=users,ou=Group,dc=example,dc=org