1
0
mirror of https://github.com/kakwa/ldapcherry synced 2024-06-03 01:28:06 +02:00

Compare commits

..

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

79 changed files with 884 additions and 2418 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,74 +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
*************
* [feat] add handling of textfielf (thanks to Stan Rudenko)
* [fix ] fix ldap.group_attr.<attr> handling with attr different than dn (uid for example)
* [impr] removing duplicate option in form select fields
* [impr] add dynamic resizing to align form labels (input-group-addon width)
Version 0.4.0
*************
* [impr] add unit test for multi backend setup
* [fix ] notify on add in case if user is already in one backend
* [fix ] notify on modify in case if user is not in every backend
* [fix ] delete user in all backends even if it doesn't exist in one of them
* [fix ] fix bad handling of = or & in passwords in ppolicy checker (js)
* [fix ] fix many encoding errors in AD backend
* [impr] add unit tests on AD backend
* [impr] display the admin result page if searching as admin in navbar form
Version 0.3.5
*************
* [fix ] fix error in ad backend when self modifying password
Version 0.3.4
*************

View File

@ -6,9 +6,15 @@
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/dm/ldapcherry.svg
:target: https://pypi.python.org/pypi/ldapcherry
:alt: Number of PyPI downloads
.. image:: https://img.shields.io/pypi/v/ldapcherry.svg
:target: https://pypi.python.org/pypi/ldapcherry
@ -34,7 +40,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 +70,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 +82,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 +95,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.3.4'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.

View File

@ -62,7 +62,7 @@ The mandatory parameters for an attribute, and their format are the following:
description: <Human readable description of the attribute> # (free text)
display_name: <Display name in LdapCherry forms> # (free text)
weight: <weight controlling the display order of the attributes, lower is first> # (integer)
type: <type of the attributes> # (in ['int', 'string', 'email', 'stringlist', 'fix', 'textfield'])
type: <type of the attributes> # (in ['int', 'string', 'email', 'stringlist', 'fix'])
backends: # (list of backend attributes name)
- <backend id 1>: <backend 1 attribute name>
- <backend id 2>: <backend 2 attribute name>
@ -76,18 +76,6 @@ The mandatory parameters for an attribute, and their format are the following:
<backend id> (the backend id) must be defined in main ini configuration file.
LdapCherry won't start if it's not.
Type listing
^^^^^^^^^^^^
The following **type** are supported:
* **int**: an integer (ex: uid)
* **string**: a string (ex: first name)
* **stringlist**: a string to choose from a given list of strings (ex: one of /bin/sh, /bin/bash /bin/zsh for a shell)
* **textfield**: free multiline text (ex: an SSH key)
* **email**: an email address
* **fix**: a fix value, only present shown information purposes
Type stringlist values
^^^^^^^^^^^^^^^^^^^^^^
@ -112,12 +100,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 +433,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 +465,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
@ -28,14 +30,7 @@ from cherrypy.lib.httputil import parse_query_string
# Mako template engines imports
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 +55,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 +143,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 +167,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 +177,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 +186,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 +218,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 +237,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 +278,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 +323,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 +337,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 +386,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 = {}
@ -565,9 +570,9 @@ class LdapCherry(object):
return 'anonymous'
return cherrypy.session.get(SESSION_KEY)
def _check_auth(self, must_admin, redir_login=True):
def _check_auth(self, must_admin):
""" 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,32 +587,19 @@ 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)
if redir_login:
raise cherrypy.HTTPRedirect(
"/signin?url=%(url)s" % {'url': quoted_requrl},
)
else:
raise cherrypy.HTTPError(
"403 Forbidden",
"You must be logged in to access this ressource.",
)
# return to login page (with base64 of the url in query string
raise cherrypy.HTTPRedirect(
"/signin?url=%(url)s" % {'url': b64requrl},
)
if 'connected' not in cherrypy.session \
or not cherrypy.session['connected']:
if redir_login:
raise cherrypy.HTTPRedirect(
"/signin?url=%(url)s" % {'url': quoted_requrl},
)
else:
raise cherrypy.HTTPError(
"403 Forbidden",
"You must be logged in to access this ressource.",
)
raise cherrypy.HTTPRedirect(
"/signin?url=%(url)s" % {'url': b64requrl},
)
if cherrypy.session['connected'] and \
not cherrypy.session['isadmin']:
if must_admin:
@ -618,20 +610,13 @@ class LdapCherry(object):
)
else:
return username
if cherrypy.session['connected'] and \
cherrypy.session['isadmin']:
return username
else:
if redir_login:
raise cherrypy.HTTPRedirect(
"/signin?url=%(url)s" % {'url': quoted_requrl},
)
else:
raise cherrypy.HTTPError(
"403 Forbidden",
"You must be logged in to access this ressource.",
)
raise cherrypy.HTTPRedirect(
"/signin?url=%(url)s" % {'url': b64requrl},
)
def _adduser(self, params):
cherrypy.log.error(
@ -656,23 +641,13 @@ class LdapCherry(object):
if b not in badd:
badd[b] = {}
badd[b][backends[b]] = params['attrs'][attr]
added = False
for b in badd:
try:
self.backends[b].add_user(badd[b])
added = True
except UserAlreadyExists as e:
self._add_notification(
'User already exists in backend "' + b + '"'
)
return
if not added:
raise e
self.backends[b].add_user(badd[b])
key = self.attributes.get_key()
username = params['attrs'][key]
sess = cherrypy.session
admin = sess.get(SESSION_KEY, 'unknown')
admin = sess.get(SESSION_KEY, None)
cherrypy.log.error(
msg="user '" + username + "' added by '" + admin + "'",
@ -689,7 +664,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 " +
@ -724,13 +699,7 @@ class LdapCherry(object):
badd[b] = {}
badd[b][backends[b]] = params['attrs'][attr]
for b in badd:
try:
self.backends[b].set_attrs(username, badd[b])
except UserDoesntExist as e:
self._add_notification(
'User does not exist in backend "' + b + '"'
)
self.backends[b].set_attrs(username, badd[b])
return badd
def _selfmodify(self, params):
@ -769,7 +738,7 @@ class LdapCherry(object):
)
sess = cherrypy.session
admin = sess.get(SESSION_KEY, 'unknown')
admin = sess.get(SESSION_KEY, None)
cherrypy.log.error(
msg="user '" + username + "' modified by '" + admin + "'",
@ -817,10 +786,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 +803,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: " +
@ -855,17 +824,10 @@ class LdapCherry(object):
def _deleteuser(self, username):
sess = cherrypy.session
admin = sess.get(SESSION_KEY, 'unknown')
admin = sess.get(SESSION_KEY, None)
for b in self.backends:
try:
self.backends[b].del_user(username)
except UserDoesntExist as e:
cherrypy.log.error(
msg="User '" + username +
"' didn't exist in backend '" + b + "'",
severity=logging.INFO
)
self.backends[b].del_user(username)
cherrypy.log.error(
msg="user '" + username + "' deleted from backend '" + b + "'",
severity=logging.DEBUG
@ -914,7 +876,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 +889,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 +926,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 +942,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,
@ -991,8 +953,8 @@ class LdapCherry(object):
@exception_decorator
def checkppolicy(self, **params):
""" search user page """
self._check_auth(must_admin=False, redir_login=False)
keys = list(params.keys())
self._check_auth(must_admin=False)
keys = params.keys()
if len(keys) != 1:
cherrypy.response.status = 400
return "bad argument"
@ -1017,7 +979,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,
@ -1046,31 +1008,26 @@ class LdapCherry(object):
for r in self.roles.flatten:
display_names[r] = self.roles.flatten[r]['display_name']
roles_js = json.dumps(display_names, separators=(',', ':'))
try:
form = self.temp['form.tmpl'].render(
attributes=self.attributes.attributes,
values=None,
modify=False,
autofill=True
)
roles = self.temp['roles.tmpl'].render(
roles=self.roles.flatten,
graph=self.roles.graph,
graph_js=graph_js,
roles_js=roles_js,
current_roles=None,
)
return self.temp['adduser.tmpl'].render(
form=form,
roles=roles,
is_admin=is_admin,
custom_js=self.custom_js,
notifications=self._empty_notification(),
)
except NameError:
raise TemplateRenderError(
exceptions.text_error_template().render()
)
form = self.temp['form.tmpl'].render(
attributes=self.attributes.attributes,
values=None,
modify=False,
autofill=True
)
roles = self.temp['roles.tmpl'].render(
roles=self.roles.flatten,
graph=self.roles.graph,
graph_js=graph_js,
roles_js=roles_js,
current_roles=None,
)
return self.temp['adduser.tmpl'].render(
form=form,
roles=roles,
is_admin=is_admin,
custom_js=self.custom_js,
notifications=self._empty_notification(),
)
@cherrypy.expose
@exception_decorator
@ -1080,7 +1037,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 +1056,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)
@ -1112,15 +1069,6 @@ class LdapCherry(object):
display_names = {}
for r in self.roles.flatten:
display_names[r] = self.roles.flatten[r]['display_name']
if user is None:
cherrypy.response.status = 400
return self.temp['error.tmpl'].render(
is_admin=is_admin,
alert='warning',
message="No user requested"
)
user_attrs = self._get_user(user)
if user_attrs == {}:
cherrypy.response.status = 400
@ -1134,43 +1082,33 @@ class LdapCherry(object):
standalone_groups = tmp['unusedgroups']
roles_js = json.dumps(display_names, separators=(',', ':'))
key = self.attributes.get_key()
try:
form = self.temp['form.tmpl'].render(
attributes=self.attributes.attributes,
values=user_attrs,
modify=True,
keyattr=key,
autofill=False
)
roles = self.temp['roles.tmpl'].render(
roles=self.roles.flatten,
graph=self.roles.graph,
graph_js=graph_js,
roles_js=roles_js,
current_roles=user_roles,
form = self.temp['form.tmpl'].render(
attributes=self.attributes.attributes,
values=self._escape(user_attrs, 'attr_list'),
modify=True,
keyattr=key,
autofill=False
)
glued_template = self.temp['modify.tmpl'].render(
form=form,
roles=roles,
is_admin=is_admin,
standalone_groups=standalone_groups,
backends_display_names=self.backends_display_names,
custom_js=self.custom_js,
notifications=self._empty_notification(),
roles = self.temp['roles.tmpl'].render(
roles=self.roles.flatten,
graph=self.roles.graph,
graph_js=graph_js,
roles_js=roles_js,
current_roles=user_roles,
)
return self.temp['modify.tmpl'].render(
form=form,
roles=roles,
is_admin=is_admin,
standalone_groups=self._escape(standalone_groups, 'lonely_groups'),
backends_display_names=self.backends_display_names,
custom_js=self.custom_js,
notifications=self._empty_notification(),
)
except NameError:
raise TemplateRenderError(
exceptions.text_error_template().render()
)
return glued_template
@cherrypy.expose
@exception_decorator
def default(self, attr='', *args, **params):
def default(self, attr=''):
cherrypy.response.status = 404
self._check_auth(must_admin=False)
is_admin = self._check_admin()
@ -1200,27 +1138,20 @@ class LdapCherry(object):
"Self modification done"
)
user_attrs = self._get_user(user)
try:
if user_attrs == {}:
return self.temp['error.tmpl'].render(
is_admin=is_admin,
alert='warning',
message="User doesn't exist"
)
form = self.temp['form.tmpl'].render(
attributes=self.attributes.get_selfattributes(),
values=user_attrs,
modify=True,
autofill=False
)
return self.temp['selfmodify.tmpl'].render(
form=form,
if user_attrs == {}:
return self.temp['error.tmpl'].render(
is_admin=is_admin,
notifications=self._empty_notification(),
alert='warning',
message="User doesn't exist"
)
except NameError:
raise TemplateRenderError(
exceptions.text_error_template().render()
)
form = self.temp['form.tmpl'].render(
attributes=self.attributes.get_selfattributes(),
values=self._escape(user_attrs, 'attr_list'),
modify=True,
autofill=False
)
return self.temp['selfmodify.tmpl'].render(
form=form,
is_admin=is_admin,
notifications=self._empty_notification(),
)

View File

@ -12,28 +12,25 @@ 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']
types = ['string', 'email', 'int', 'stringlist', 'fix', 'password']
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 +68,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:
@ -84,8 +81,6 @@ class Attributes:
attr_type = self.attributes[attrid]['type']
if attr_type == 'string':
return
elif attr_type == 'textfield':
return
elif attr_type == 'email':
if self._is_email(value):
return
@ -130,9 +125,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,38 +128,28 @@ 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"
}
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(
@ -186,31 +174,26 @@ class Backend(ldapcherry.backend.backendLdap.Backend):
ad_groups.append('cn=' + group + ',' + self.groupdn)
return ad_groups
def _set_password(self, name, password, by_cn=True):
def _set_password(self, cn, password):
unicode_pass = '\"' + password + '\"'
password_value = unicode_pass.encode('utf-16-le')
ldap_client = self._bind()
if by_cn:
dn = self._byte_p2('CN=%(cn)s,%(user_dn)s' % {
'cn': name,
'user_dn': self.userdn
})
else:
dn = self._byte_p2(name)
dn = self._str('CN=%(cn)s,%(user_dn)s' % {
'cn': cn,
'user_dn': self.userdn
})
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,8 +207,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)
self._set_password(userdn, password, False)
self._set_password(attrs['cn'], password)
super(Backend, self).set_attrs(username, attrs)
def add_to_groups(self, username, groups):
@ -238,7 +220,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(username, NO_ATTR)
searchfilter = self.group_filter_tmpl % {
'userdn': userdn,
@ -258,7 +240,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):
@ -267,10 +249,7 @@ class Backend(ldapcherry.backend.backendLdap.Backend):
if binddn is not None:
ldap_client = self._connect()
try:
ldap_client.simple_bind_s(
self._byte_p2(binddn),
self._byte_p2(password)
)
ldap_client.simple_bind_s(binddn, password)
except ldap.INVALID_CREDENTIALS:
ldap_client.unbind_s()
return False

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,11 @@ import ldap.modlist as modlist
import ldap.filter
import logging
import ldapcherry.backend
import sys
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):
@ -28,21 +23,6 @@ class CaFileDontExist(Exception):
self.cafile = cafile
self.log = "CA file %(cafile)s does not exist" % {'cafile': cafile}
class MissingGroupAttr(Exception):
def __init__(self, attr):
self.attr = attr
self.log = "User doesn't have %(attr)s in its attributes" \
", cannot use it to set group" % {'attr': attr}
class MultivaluedGroupAttr(Exception):
def __init__(self, attr):
self.attr = cafile
self.log = "User's attribute '%(attr)s' is multivalued" \
", cannot use it to set group" % {'attr': attr}
NO_ATTR = 0
DISPLAYED_ATTRS = 1
LISTED_ATTRS = 2
@ -75,21 +55,17 @@ 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([])
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._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 +108,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 +123,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,
@ -159,38 +135,6 @@ class Backend(ldapcherry.backend.Backend):
)
raise
def _extract_format_keys(self, fmt_string):
"""Extract the keys of a format string
(the 'stuff' in '%(stuff)s'
"""
class AccessSaver:
def __init__(self):
self.keys = []
def __getitem__(self, key):
self.keys.append(key)
a = AccessSaver()
fmt_string % a
return a.keys
def _normalize_group_attrs(self, attrs):
"""Normalize the attributes used to set groups
If it's a list of one element, it just become this
element.
It raises an error if the attribute doesn't exist
or if it's multivaluated.
"""
for key in self.group_attrs_keys:
if key not in attrs:
raise MissingGroupAttr(key)
if type(attrs[key]) is list and len(attrs[key]) == 1:
attrs[key] = attrs[key][0]
if type(attrs[key]) is list and len(attrs[key]) != 1:
raise MultivaluedGroupAttr(key)
def _connect(self):
"""Initialize an ldap client"""
ldap_client = ldap.initialize(self.uri)
@ -261,16 +205,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 +246,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 +258,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 +294,28 @@ 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._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.dn_user_attr) + \
'=' + \
self._str(attrs[self.dn_user_attr]) + \
',' + \
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 +329,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 +339,15 @@ 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)
if tmp is None:
raise UserDoesntExist(username, self.backend_name)
dn = self._byte_p2(tmp[0])
tmp = self._get_user(self._str(username), ALL_ATTRS)
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 +364,38 @@ 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 +409,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 +437,16 @@ 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)
if tmp is None:
raise UserDoesntExist(username, self.backend_name)
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])
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 +469,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 +494,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 +508,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 +519,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):
@ -91,10 +90,10 @@ class PPolicyError(Exception):
class MissingMainFile(Exception):
def __init__(self, config):
self.config = config
self.rolefile = rolefile
self.log = \
"fail to open main file '%(config)s'" % \
{'config': config}
{'rolefile': rolefile}
class MissingAttributesFile(Exception):
@ -218,11 +217,6 @@ class GroupDoesntExist(Exception):
" in backend '" + backend + "'"
class TemplateRenderError(Exception):
def __init__(self, error):
self.log = "Template Render Error: " + error
def exception_decorator(func):
def ret(self, *args, **kwargs):
try:

View File

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

View File

@ -31,9 +31,9 @@ class PPolicy(ldapcherry.ppolicy.PPolicy):
def info(self):
return \
"* Minimum length: %(len)d\n" \
"* Minimum number of uppercase characters: %(upper)d\n" \
"* Minimum number of digits: %(digit)d" % {
"* Minimum length: %(len)n\n" \
"* Minimum number of uppercase characters: %(upper)n\n" \
"* Minimum number of digits: %(digit)n" % {
'upper': self.min_upper,
'len': self.min_length,
'digit': self.min_digit

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
@ -8,6 +8,7 @@ import pytest
import sys
from sets import Set
from ldapcherry.backend.backendLdap import Backend
from ldapcherry import syslog_error
from ldapcherry.exceptions import *
import cherrypy
import logging

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,25 +0,0 @@
var classes = ["lcform-col-1", "lcform-col-2"];
var j, lenj = classes.length;
for(j=0; j < lenj; j++){
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 longest = 0;
for(i=0; i < len; i++){
if (InputGroups[i].id !== "basic-addon-password2"){
longest = longest < InputGroups[i].clientWidth ? InputGroups[i].clientWidth : longest;
}
}
for(i=0; i < len; i++){
InputGroups[i].style.minWidth = (longest + 0) + "px";
}
//console.log(longest);
}
}
//console.log("end_re");

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

@ -10,24 +10,9 @@ $('#form').validator({
type: 'POST',
dataType: 'json',
async: false,
data: 'pwd=' + encodeURIComponent($el.val()),
data: 'pwd=' + $el.val(),
success: function(data) {
$ret = data;
},
error: function(jqXHR, exception) {
switch (jqXHR.status) {
case 400:
$ret = {"reason":"Javascript ppolicy.js error","match":false};
break;
case 403:
$ret = {"reason":"Session expired, you must reconnect","match":false};
break;
case 500:
$ret = {"reason":"Server error","match":false};
break;
default:
$ret = {"reason":"Unknown error [" + jqXHR.status + "], check logs","match":false};
}
}
});
this.options.errors['ppolicy'] = $ret['reason'];

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,5 @@
<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>
</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:
@ -27,49 +26,44 @@ for a in sorted(attributes.keys(), key=lambda attr: attributes[attr]['weight']):
required = ' required '
if not values is None and a in values:
if type(values[a]) is list:
raw_value = values[a][0]
tmp = values[a][0]
else:
raw_value = values[a]
if raw_value is None:
raw_value = ''
value = Markup(' value="{}"').format(raw_value)
value2 = Markup('<option>{}</option>').format(raw_value)
tmp = values[a]
if tmp is None:
tmp = ''
value = ' value="'+ tmp + '"'
value2 = '<option>'+ tmp +'</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');">
<span class="form-control" aria-describedby="basic-addon-${a}">${raw_value}</span>
<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}">${tmp}</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>
%endif
%endfor
</select>
% elif attr['type'] == 'password':
<input type="password" class="form-control" data-ppolicy="ppolicy" name="attr.${a}1" id="${a}1" autocomplete='off' placeholder="${attr['description']}" ${required} readonly onfocus="this.removeAttribute('readonly');">
<span class="input-group-addon" id="basic-addon-${a}2">Retype ${attr['display_name']}</span>
<input type="password" class="form-control" data-match="#${a}1" data-match-error="Passwords don't match" name="attr.${a}2" id="#${a}2" autocomplete='off' placeholder="Confirm" ${required} readonly onfocus="this.removeAttribute('readonly');">
% elif attr['type'] == 'textfield':
<textarea id="attr.${a}" name="attr.${a}" class="form-control" placeholder="${attr['description']}">${raw_value}</textarea>
% endif
</div>
<div class="help-block with-errors"></div>
@ -77,40 +71,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">
${form_col(lc1)}
</div>
<div class="col-md-6 column lcform-col-2" style="display:none;">
<div class="col-md-6 column">
${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 +116,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

@ -13,19 +13,12 @@
<a class="navbar-brand" href="/searchadmin">Delete/Modify User</a>
% endif
</div>
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<a class="navbar-brand navbar-right" href='/logout'><span class="glyphicon glyphicon-off"></span> Logout</a>
% if is_admin:
<form method='GET' action='/searchadmin' class="navbar-form navbar-right" role="search" data-toggle="validator">
% else:
<form method='GET' action='/searchuser' class="navbar-form navbar-right" role="search" data-toggle="validator">
% endif
<div class="form-group">
% if is_admin:
<input type="text" class="form-control" name="searchstring" placeholder="Search User">
% else:
<input type="text" class="form-control" data-minlength="3" name="searchstring" placeholder="Search User" data-error="Too short" required>
% endif
</div>
<button type="submit" class="btn btn-default">Submit</button>
</form>

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)
@ -97,6 +90,7 @@ resources_files = get_list_files(
data_dir,
)
as_option_root
# add the configuration files if they don't exist
if as_option_root() or not os.path.exists(
config_dir):
@ -115,7 +109,7 @@ if as_option_root() or not os.path.exists(
setup(
name='ldapcherry',
zip_safe=False,
version=version,
version='0.3.4',
author='Pierre-Francois Carpentier',
author_email='carpentier.pf@gmail.com',
packages=[
@ -124,18 +118,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

@ -81,7 +81,7 @@ gidNumber:
default: 10000
backends:
ldap: gidNumber
ad: gidNumber
ad: GIDNumber
shell:
description: "Shell of the user"
display_name: "Shell"

View File

@ -1,136 +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
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'
- '30000'
backends:
ldap: uidNumber
ad: uidNumber
gidNumber:
description: "Group ID Number of the user"
display_name: "GID Number"
weight: 70
type: int
autofill:
function: lcUidNumber
args:
- $first-name
- $name
- '10000'
- '30000'
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,186 +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 = 'none'
# logger none for error and ldapcherry log
#log.error_handler = 'none'
# log level
log.level = 'info'
# session configuration
# activate session
tools.sessions.on = True
# session timeout
tools.sessions.timeout = 7200
# 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 = './tests/cfg/attributes_adldap.yml'
[roles]
# file listing roles
roles.file = './tests/cfg/roles_adldap.yml'
[search]
# minimum lenght for search forms
min.lenght = 0
[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:390'
# 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'
#####################################
# configuration of ad backend #
#####################################
## Name of the backend
ad.module = 'ldapcherry.backend.backendAD'
# display name of the ldap
ad.display_name = 'My Active Directory'
# ad domain
ad.domain = 'DC.LDAPCHERRY.ORG'
# ad login
ad.login = 'administrator'
# ad password
ad.password = 'qwertyP455'
# ad uri
ad.uri = 'ldaps://ad.ldapcherry.org'
# ca to use for ssl/tls connexion
#ad.ca = '/etc/dnscherry/TEST-cacert.pem'
# use start tls
ad.starttls = 'off'
# check server certificate (for tls)
ad.checkcert = 'off'
[ppolicy]
# password policy module
ppolicy.module = 'ldapcherry.ppolicy.simple'
# parameters of the module
min_length = 8
min_upper = 1
min_digit = 1
# authentification parameters
[auth]
# Auth mode
# * and: user must authenticate on all backends
# * or: user must authenticate on one of the backend
# * none: disable authentification
# * custom: custom authentification module (need auth.module param)
auth.mode = 'or'
# custom auth module to load
#auth.module = 'ldapcherry.auth.modNone'
# resources parameters
[resources]
# templates directory
templates.dir = './resources/templates/'
[/static]
# enable serving static file through ldapcherry
# set to False if files served directly by an
# http server for better performance
tools.staticdir.on = True
# static resources directory (js, css, images...)
tools.staticdir.dir = './resources/static/'
## custom javascript files
#[/custom]
#
## enable serving static file through ldapcherry
## set to False if files served directly by an
## http server for better performance
#tools.staticdir.on = True
## path to directory containing js files
## use it to add custom auto-fill functions

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

@ -1,41 +0,0 @@
admin-lv3:
display_name: Administrators Level 3
description: Super administrators of the system
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
- Group Policy Creator Owners
admin-lv2:
display_name: Administrators Level 2
description: Basic administrators of the system
LC_admins: True
backends_groups:
# ldap:
# - cn=nagios admins,ou=Group,dc=example,dc=org
# - cn=users,ou=Group,dc=example,dc=org
ad:
- Administrators
#developers:
# display_name: Developpers
# description: Developpers of the system
# 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: Basic users of the system
# backends_groups:
## ldap:
## - cn=users,ou=Group,dc=example,dc=org
# ad:
# - Domain 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,16 +1,8 @@
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:
return f
def slow_disabled(f):
def _decorator(f):
print('test has been disabled by env var LCNOSLOW')
if 'LCNOSLOW' in os.environ and os.environ['LCNOSLOW'] == 'yes':
return _decorator
else:
return f

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,204 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from __future__ import with_statement
from __future__ import unicode_literals
import pytest
import sys
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☭',
'domain': 'DC.LDAPCHERRY.ORG',
'login': 'Administrator',
'password': 'qwertyP455',
'uri': 'ldaps://ad.ldapcherry.org',
'checkcert': 'off',
}
def syslog_error(msg='', context='',
severity=logging.INFO, traceback=False):
pass
cherrypy.log.error = syslog_error
attr = ['shell', 'cn', 'sAMAccountName', 'uidNumber', 'gidNumber', 'home', 'unicodePwd', 'givenName', 'email', 'sn']
default_user = {
'sAMAccountName': u'☭default_user',
'sn': u'test☭1',
'cn': u'test☭2',
'unicodePwd': u'test☭P666',
'uidNumber': '42',
'gidNumber': '42',
'homeDirectory': '/home/test/'
}
default_user2 = {
'sAMAccountName': u'☭default_user2',
'sn': u'test☭ 2',
'cn': u'test☭ 2',
'unicodePwd': u'test☭P666',
'uidNumber': '42',
'gidNumber': '42',
'homeDirectory': '/home/test/'
}
default_groups = ['Domain Admins', 'Backup Operators']
class TestError(object):
@travis_disabled
def testNominal(self):
inv = Backend(cfg, cherrypy.log, u'test☭', attr, 'sAMAccountName')
return True
@travis_disabled
def testAuthSuccess(self):
inv = Backend(cfg, cherrypy.log, u'test☭', attr, 'sAMAccountName')
ret = inv.auth('Administrator', 'qwertyP455')
assert ret == True
@travis_disabled
def testAuthFailure(self):
inv = Backend(cfg, cherrypy.log, u'test☭', attr, 'sAMAccountName')
res = inv.auth('notauser', 'password') or inv.auth(u'☭default_user', 'notapassword')
assert res == False
@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$'})
ret = inv.auth(u'☭default_user', u'test☭P66642$')
inv.del_user(u'☭default_user')
assert ret == True
@travis_disabled
def testGetUser(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
ret = inv.get_user(u'☭default_user')
expected = default_user
inv.del_user(u'☭default_user')
for i in default_user:
if i != 'unicodePwd':
assert ret[i] == expected[i]
@travis_disabled
def testGetGroups(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
ret = inv.get_groups(u'☭default_user')
expected = ['Domain Admins', 'Backup Operators']
inv.del_user(u'☭default_user')
assert ret == expected
@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
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)
@travis_disabled
def testAddUser(self):
try:
inv.del_user(u'test☭')
except:
pass
inv = Backend(cfg, cherrypy.log, u'test☭', attr, 'sAMAccountName')
user = {
'sAMAccountName': u'test☭',
'sn': u'test☭',
'cn': u'test☭',
'unicodePwd': u'test☭0918M',
'uidNumber': '42',
'gidNumber': '42',
'homeDirectory': '/home/test/'
}
inv.add_user(user)
inv.del_user(u'test☭')
@travis_disabled
def testModifyUser(self):
inv = Backend(cfg, cherrypy.log, u'test☭', attr, 'sAMAccountName')
user = {
'sAMAccountName': u'test☭',
'sn': u'test☭',
'cn': u'test☭',
'unicodePwd': u'test☭Aowo87',
'uidNumber': '42',
'gidNumber': '42',
'homeDirectory': '/home/test/'
}
inv.add_user(user)
inv.set_attrs(u'test☭', {'gecos': 'test2', 'homeDirectory': '/home/test/'})
inv.del_user(u'test☭')
@travis_disabled
def testAddUserDuplicate(self):
inv = Backend(cfg, cherrypy.log, u'test☭', attr, 'sAMAccountName')
user = {
'sAMAccountName': u'test☭',
'sn': u'test☭',
'cn': u'test☭',
'uidNumber': '42',
'unicodePwd': u'test☭aqosJK87',
'gidNumber': '42',
'homeDirectory': '/home/test/'
}
try:
inv.add_user(user.copy())
inv.add_user(user.copy())
except UserAlreadyExists:
inv.del_user(u'test☭')
return
else:
inv.del_user(u'test☭')
raise AssertionError("expected an exception")
@travis_disabled
def testDelUserDontExists(self):
inv = Backend(cfg, cherrypy.log, u'test☭', attr, 'sAMAccountName')
try:
inv.del_user(u'test☭')
inv.del_user(u'test☭')
except UserDoesntExist:
return
else:
raise AssertionError("expected an exception")

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,10 +158,12 @@ 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']
def testSearchUser(self):
inv = Backend(cfg, cherrypy.log, 'ldap', attr, 'uid')
ret = inv.search('smith')
@ -162,29 +172,29 @@ class TestError(object):
def testAddUser(self):
try:
inv.del_user(u'test☭,cn=')
inv.del_user(u'test☭')
except:
pass
inv = Backend(cfg, cherrypy.log, 'ldap', attr, 'uid')
user = {
'uid': u'test☭,cn=',
'sn': u'test',
'cn': u'test',
'userPassword': u'test',
'uid': u'test☭',
'sn': 'test',
'cn': 'test',
'userPassword': 'test',
'uidNumber': '42',
'gidNumber': '42',
'homeDirectory': '/home/test/'
}
inv.add_user(user)
inv.del_user(u'test☭,cn=')
inv.del_user(u'test☭')
def testModifyUser(self):
inv = Backend(cfg, cherrypy.log, 'ldap', attr, 'uid')
user = {
'uid': u'test☭',
'sn': u'test',
'cn': u'test',
'userPassword': u'test',
'sn': 'test',
'cn': 'test',
'userPassword': 'test',
'uidNumber': '42',
'gidNumber': '42',
'homeDirectory': '/home/test/'
@ -196,11 +206,11 @@ class TestError(object):
def testAddUserDuplicate(self):
inv = Backend(cfg, cherrypy.log, 'ldap', attr, 'uid')
user = {
'uid': u'test',
'sn': u'test',
'cn': u'test',
'uid': 'test',
'sn': 'test',
'cn': 'test',
'uidNumber': '42',
'userPassword': u'test',
'userPassword': 'test',
'gidNumber': '42',
'homeDirectory': '/home/test/'
}
@ -208,17 +218,17 @@ class TestError(object):
inv.add_user(user)
inv.add_user(user)
except UserAlreadyExists:
inv.del_user(u'test')
inv.del_user('test')
return
else:
inv.del_user(u'test')
inv.del_user('test')
raise AssertionError("expected an exception")
def testDelUserDontExists(self):
inv = Backend(cfg, cherrypy.log, 'ldap', attr, 'uid')
try:
inv.del_user(u'test')
inv.del_user(u'test')
inv.del_user('test')
inv.del_user('test')
except UserDoesntExist:
return
else:
@ -233,10 +243,10 @@ class TestError(object):
def testAddUserMissingMustattribute(self):
inv = Backend(cfg, cherrypy.log, 'ldap', attr, 'uid')
user = {
'uid': u'test',
'sn': u'test',
'cn': u'test',
'userPassword': u'test',
'uid': 'test',
'sn': 'test',
'cn': 'test',
'userPassword': 'test',
'gidNumber': '42',
'homeDirectory': '/home/test/'
}
@ -245,5 +255,5 @@ class TestError(object):
except ldap.OBJECT_CLASS_VIOLATION:
return
else:
inv.del_user(u'test')
inv.del_user('test')
raise AssertionError("expected an exception")

View File

@ -1,7 +1,8 @@
#!/usr/bin/env python3
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import with_statement
from __future__ import unicode_literals
import pytest
import sys
@ -9,44 +10,19 @@ 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
import ldapcherry.backend.backendAD
import cherrypy
from cherrypy.process import plugins, servers
from cherrypy import Application
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 = {}
adcfg = {
'display_name': u'test☭',
'domain': 'DC.LDAPCHERRY.ORG',
'login': 'Administrator',
'password': 'qwertyP455',
'uri': 'ldaps://ad.ldapcherry.org',
'checkcert': 'off',
}
adattr = ['shell', 'cn', 'sAMAccountName', 'uidNumber', 'gidNumber', 'home', 'unicodePwd', 'givenName', 'email', 'sn']
addefault_user = {
'sAMAccountName': u'☭default_user',
'sn': u'test☭1',
'cn': u'test☭2',
'unicodePwd': u'test☭P666',
'uidNumber': '42',
'gidNumber': '42',
'homeDirectory': '/home/test/'
}
# monkey patching cherrypy to disable config interpolation
def new_as_dict(self, raw=True, vars=None):
"""Convert an INI file to a dictionary"""
@ -80,42 +56,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 +105,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)
@ -206,10 +159,10 @@ class TestError(object):
loadconf('./tests/cfg/ldapcherry_test.ini', app)
app.auth_mode = 'or'
try:
app.login(u'jwatsoné', u'passwordwatsoné')
app.login('jwatsoné', '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")
@ -218,10 +171,10 @@ class TestError(object):
loadconf('./tests/cfg/ldapcherry_test.ini', app)
app.auth_mode = 'or'
try:
app.login(u'jwatsoné', u'wrongPasswordé')
app.login('jwatsoné', '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")
@ -293,7 +246,6 @@ class TestError(object):
app._modify(modify_form)
app._deleteuser('test')
@slow_disabled
def testHtml(self):
app = LdapCherry()
loadconf('./tests/cfg/ldapcherry_test.ini', app)
@ -313,14 +265,8 @@ class TestError(object):
def testNoneType(self):
app = LdapCherry()
loadconf('./tests/cfg/ldapcherry_test.ini', app)
app.modify('ssmith')
app.modify('ssmith'),
def testNoneModify(self):
app = LdapCherry()
loadconf('./tests/cfg/ldapcherry_test.ini', app)
app.modify(user=None)
@slow_disabled
def testNaughtyStrings(self):
app = LdapCherry()
loadconf('./tests/cfg/ldapcherry_test.ini', app)
@ -339,40 +285,6 @@ class TestError(object):
app._deleteuser('test')
htmlvalidator(page[0])
@travis_disabled
def testDeleteUserOneBackend(self):
app = LdapCherry()
loadconf('./tests/cfg/ldapcherry_adldap.cfg', app)
inv = ldapcherry.backend.backendAD.Backend(adcfg, cherrypy.log, u'test☭', adattr, 'sAMAccountName')
inv.add_user(addefault_user.copy())
app._deleteuser(u'☭default_user')
@travis_disabled
def testAddUserOneBackend(self):
app = LdapCherry()
loadconf('./tests/cfg/ldapcherry_adldap.cfg', app)
inv = ldapcherry.backend.backendAD.Backend(adcfg, cherrypy.log, u'test☭', adattr, 'sAMAccountName')
inv.add_user(addefault_user.copy())
form = {'groups': {}, 'attrs': {'password1': u'password☭P455', 'password2': u'password☭P455', 'cn': u'Test ☭ Test', 'name': u'Test ☭', 'uidNumber': u'1000', 'gidNumber': u'1000', 'home': u'/home/test', 'first-name': u'Test ☭', 'email': u'test@test.fr', 'uid': u'☭default_user'}, 'roles': {'admin-lv3': u'on', 'admin-lv2': u'on', 'users': u'on'}}
app._adduser(form)
app._deleteuser(u'☭default_user')
@travis_disabled
def testModifyUserOneBackend(self):
app = LdapCherry()
loadconf('./tests/cfg/ldapcherry_adldap.cfg', app)
inv = ldapcherry.backend.backendAD.Backend(adcfg, cherrypy.log, u'test☭', adattr, 'sAMAccountName')
try:
app._deleteuser(u'☭default_user')
except:
pass
inv.add_user(addefault_user.copy())
modify_form = { 'attrs': {'first-name': u'Test42 ☭', 'uid': u'☭default_user'}, 'roles': { 'admin-lv3': u'on'}}
app._modify(modify_form)
app._deleteuser(u'☭default_user')
def testLogger(self):
app = LdapCherry()
loadconf('./tests/cfg/ldapcherry.ini', app)
@ -385,3 +297,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 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
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 w3c-markup-validator -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" -f -q -y
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,8 +24,6 @@ cat /etc/hosts
df -h
find /var/log/samba/ -type f -exec rm -f {} \;
smbconffile=/etc/samba/smb.conf
domain=dc
realm=dc.ldapcherry.org
@ -43,18 +33,6 @@ role=dc
sambacmd=samba-tool
adpass=qwertyP455
systemctl unmask samba-ad-dc
hostname ad.ldapcherry.org
pkill -9 dnsmasq
pkill -9 samba
kill -9 `cat /var/run/samba/smbd.pid`
rm -f /var/run/samba/smbd.pid
kill -9 `cat /var/run/samba/nmbd.pid`
rm -f /var/run/samba/nmbd.pid
rm -rf /var/run/samba
echo "deploy AD"
printf '' > "${smbconffile}" && \
${sambacmd} domain provision ${hostip} \
@ -66,20 +44,16 @@ printf '' > "${smbconffile}" && \
echo "Move configuration"
mv "${targetdir}/etc/smb.conf" "${smbconffile}"
cat ${smbconffile}
mv /var/lib/samba/private/krb5.conf /etc/krb5.conf
sleep 15
systemctl restart samba-ad-dc
/etc/init.d/samba-ad-dc restart
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
sh -x /etc/init.d/samba stop
sh -x /etc/init.d/samba-ad-dc restart
#sh -x /etc/init.d/smbd restart
#sh -x /etc/init.d/nmbd restart
sleep 5
netstat -apn

View File

@ -103,22 +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
dn: cn=posixdev,ou=group,dc=example,dc=org
objectclass: posixGroup
cn: posixdev
description: Developpers
gidNumber: 10002
memberUid: ssmith
dn: cn=posixadm,ou=group,dc=example,dc=org
objectclass: posixGroup
cn: posixadm
description: Administration
gidNumber: 10001
memberUid: ssmith

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