mirror of
https://github.com/kakwa/ldapcherry
synced 2025-03-29 11:01:13 +01:00
Compare commits
185 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
662e09eccf | ||
![]() |
dd331f948c | ||
![]() |
a2e985576b | ||
![]() |
6a2e6e56d0 | ||
![]() |
5d8fc08f5b | ||
![]() |
59855f0090 | ||
![]() |
d1215bc56f | ||
![]() |
ef11e6a7e3 | ||
![]() |
3596e14249 | ||
![]() |
b94713acb6 | ||
![]() |
de16df475f | ||
![]() |
242c9ab96e | ||
![]() |
48dbff983d | ||
![]() |
6413b782dd | ||
![]() |
55c49b4eff | ||
![]() |
53a4b6fd5e | ||
![]() |
0ccbe95d9a | ||
![]() |
4da050236d | ||
![]() |
135699bd48 | ||
![]() |
1549a172f7 | ||
![]() |
e2ab3e85d8 | ||
![]() |
b5907ce340 | ||
![]() |
791895d4c3 | ||
![]() |
b12add4a0f | ||
![]() |
856157af79 | ||
![]() |
b5e7cb6a44 | ||
![]() |
d61c89460e | ||
![]() |
0a96ca61d5 | ||
![]() |
3b58f1464e | ||
![]() |
50c6259035 | ||
![]() |
245bafb01c | ||
![]() |
5ee8a74040 | ||
![]() |
96acda7aa6 | ||
![]() |
30c28c5feb | ||
![]() |
dc60300a29 | ||
![]() |
882a303474 | ||
![]() |
d831b09293 | ||
![]() |
7ac7118c9a | ||
![]() |
d690bbdc41 | ||
![]() |
73c02ccff4 | ||
![]() |
799ca2403f | ||
![]() |
bbafafae60 | ||
![]() |
0cf5483785 | ||
![]() |
df2746b996 | ||
![]() |
e6bcf9d97d | ||
![]() |
57bcaaed66 | ||
![]() |
b68214022c | ||
![]() |
932e7a8b40 | ||
![]() |
abf1454278 | ||
![]() |
0793361d90 | ||
![]() |
f824790849 | ||
![]() |
7390c931b9 | ||
![]() |
4a8aa1c655 | ||
![]() |
e50df5dde3 | ||
![]() |
fba2d32b44 | ||
![]() |
7a8468f8b1 | ||
![]() |
9d0d321e9b | ||
![]() |
c5536bdc56 | ||
![]() |
abfce4803a | ||
![]() |
046afbbe29 | ||
![]() |
98fca30fba | ||
![]() |
f13961790f | ||
![]() |
a56c491ee1 | ||
![]() |
02357d886a | ||
![]() |
263e6be547 | ||
![]() |
05aace0e9d | ||
![]() |
baa3430e63 | ||
![]() |
90ff69586b | ||
![]() |
79983c078f | ||
![]() |
10747cff93 | ||
![]() |
979d4eeda8 | ||
![]() |
fb6b0a5d31 | ||
![]() |
bbfe96d4f7 | ||
![]() |
b9437abefb | ||
![]() |
60d57d8530 | ||
![]() |
8c0bf94904 | ||
![]() |
42759f1cc4 | ||
![]() |
18fdeb483e | ||
![]() |
12c511b537 | ||
![]() |
d25ceef2d3 | ||
![]() |
8b48a1f024 | ||
![]() |
7430af5ffc | ||
![]() |
bc0f3aceb5 | ||
![]() |
9989f97091 | ||
![]() |
fc98b1bd70 | ||
![]() |
ab9cd664ec | ||
![]() |
13bfbdcbbc | ||
![]() |
70140f966a | ||
![]() |
8bd4afb235 | ||
![]() |
2a2864a306 | ||
![]() |
c3feafdb2c | ||
![]() |
86fb6c1dd2 | ||
![]() |
9f6af580cd | ||
![]() |
5bdcc5522a | ||
![]() |
c81429a870 | ||
![]() |
3d6e24eb73 | ||
![]() |
be598b0129 | ||
![]() |
ccc252965d | ||
![]() |
3beedc8d4d | ||
![]() |
74dc6c5894 | ||
![]() |
69526610f3 | ||
![]() |
921a0820f4 | ||
![]() |
2df56d2de2 | ||
![]() |
5b0c72a572 | ||
![]() |
c6cce54d5f | ||
![]() |
1f79648d57 | ||
![]() |
636400b75f | ||
![]() |
6f98076281 | ||
![]() |
1ed654c91b | ||
![]() |
c329e53811 | ||
![]() |
05e3a0d665 | ||
![]() |
4bd6314b3b | ||
![]() |
c5dae7039a | ||
![]() |
ca1f78173f | ||
![]() |
9ed6007b02 | ||
![]() |
4d696a29ef | ||
![]() |
45d64120ae | ||
![]() |
00a4d22dd9 | ||
![]() |
32c513f96e | ||
![]() |
7019cc2348 | ||
![]() |
a404cf0b39 | ||
![]() |
9649803dd6 | ||
![]() |
eecccac106 | ||
![]() |
f357adcd9a | ||
![]() |
e7998ced78 | ||
![]() |
8270988ed4 | ||
![]() |
2e2453f309 | ||
![]() |
bbb13454bf | ||
![]() |
3378822d2e | ||
![]() |
6e526b6f15 | ||
![]() |
5b1803cb05 | ||
![]() |
de5f760c37 | ||
![]() |
a33a46e8b8 | ||
![]() |
eb36830845 | ||
![]() |
3fd6dcee82 | ||
![]() |
55ce2bec5e | ||
![]() |
e02a1a7f28 | ||
![]() |
f9a3051328 | ||
![]() |
e4effc64ec | ||
![]() |
b3a361afee | ||
![]() |
a802ce772a | ||
![]() |
3a1966324d | ||
![]() |
819e575a28 | ||
![]() |
12bb597903 | ||
![]() |
7afe6c0ca7 | ||
![]() |
e1a27aa0a7 | ||
![]() |
f7f72c7e11 | ||
![]() |
e37b88dbda | ||
![]() |
d7303da85f | ||
![]() |
44024dbd02 | ||
![]() |
5a45a24055 | ||
![]() |
0a4db74f1f | ||
![]() |
f747252585 | ||
![]() |
7f00264e32 | ||
![]() |
d820cceeb6 | ||
![]() |
d4235bc33c | ||
![]() |
f21122b219 | ||
![]() |
01aaf476c1 | ||
![]() |
fec09b1543 | ||
![]() |
cf97f01245 | ||
![]() |
5ddd9a6bbf | ||
![]() |
07a60823ad | ||
![]() |
5ff62f0a8c | ||
![]() |
a84ee528aa | ||
![]() |
1aa4a0bd64 | ||
![]() |
37925b196b | ||
![]() |
f863b230dd | ||
![]() |
dcdc260f33 | ||
![]() |
2e98e380df | ||
![]() |
52557afa6a | ||
![]() |
f967630043 | ||
![]() |
9fb32f11be | ||
![]() |
64e0bba74c | ||
![]() |
ff950dd88b | ||
![]() |
9367bc3288 | ||
![]() |
d1ec945fe2 | ||
![]() |
0f28309344 | ||
![]() |
0263d52edf | ||
![]() |
127b106082 | ||
![]() |
6fd849fa50 | ||
![]() |
ca974ab801 | ||
![]() |
9b3d232503 | ||
![]() |
1ccb4bf732 | ||
![]() |
d484ee1ed0 | ||
![]() |
bb05934284 |
19
.github/workflows/tests.yml
vendored
Normal file
19
.github/workflows/tests.yml
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
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
1
.gitignore
vendored
@ -54,3 +54,4 @@ coverage.xml
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
ldapcherry-dev.ini
|
||||
|
39
.travis.yml
39
.travis.yml
@ -1,31 +1,40 @@
|
||||
sudo: required
|
||||
dist: trusty
|
||||
dist: xenial
|
||||
language: python
|
||||
|
||||
env:
|
||||
- TRAVIS="yes"
|
||||
|
||||
before_install:
|
||||
- '[ "$TEST_PEP8" == "1" ] || sudo ./tests/test_env/deploy.sh'
|
||||
|
||||
python:
|
||||
- "2.7"
|
||||
|
||||
install:
|
||||
- pip install -e .
|
||||
- "if [[ $TEST_PEP8 == '1' ]]; then pip install pep8; fi"
|
||||
- "pip install -e . -r $REQ_FILE"
|
||||
- "if [[ $TEST_PEP8 == '1' ]]; then pip install pycodestyle; fi"
|
||||
- pip install passlib
|
||||
- pip install coveralls
|
||||
|
||||
# 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"
|
||||
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"
|
||||
matrix:
|
||||
include:
|
||||
- python: "2.7"
|
||||
env: TEST_PEP8=1
|
||||
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
|
||||
|
||||
after_success:
|
||||
- coveralls
|
||||
after_failure:
|
||||
|
@ -1,6 +1,69 @@
|
||||
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
|
||||
*************
|
||||
|
||||
|
29
README.rst
29
README.rst
@ -6,15 +6,9 @@
|
||||
|
||||
Nice and simple application to manage users and groups in multiple directory services.
|
||||
|
||||
.. image:: https://travis-ci.org/kakwa/ldapcherry.svg?branch=master
|
||||
:target: https://travis-ci.org/kakwa/ldapcherry
|
||||
|
||||
.. image:: https://coveralls.io/repos/kakwa/ldapcherry/badge.svg
|
||||
:target: https://coveralls.io/r/kakwa/ldapcherry
|
||||
|
||||
.. image:: https://img.shields.io/pypi/dm/ldapcherry.svg
|
||||
:target: https://pypi.python.org/pypi/ldapcherry
|
||||
:alt: Number of PyPI downloads
|
||||
.. image:: https://github.com/kakwa/ldapcherry/actions/workflows/tests.yml/badge.svg
|
||||
:target: https://github.com/kakwa/ldapcherry/actions/workflows/tests.yml
|
||||
:alt: CI
|
||||
|
||||
.. image:: https://img.shields.io/pypi/v/ldapcherry.svg
|
||||
:target: https://pypi.python.org/pypi/ldapcherry
|
||||
@ -40,7 +34,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.
|
||||
|
||||
It's main features are:
|
||||
Its main features are:
|
||||
|
||||
* manage multiple directories/databases backends in an unified way
|
||||
* roles management (as in "groups of groups")
|
||||
@ -70,11 +64,16 @@ 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=<sys conf dir>
|
||||
$ export SYSCONFDIR=/etc
|
||||
# change the directory where to put the resource (default: /usr/share)
|
||||
$ export DATAROOTDIR=/usr/share/
|
||||
|
||||
# install ldapcherry
|
||||
$ pip install ldapcherry
|
||||
$ python setup.py install
|
||||
|
||||
# edit configuration files
|
||||
$ vi /etc/ldapcherry/ldapcherry.ini
|
||||
@ -82,9 +81,11 @@ The default backend plugins permit to manage Ldap and Active Directory.
|
||||
$ vi /etc/ldapcherry/attributes.yml
|
||||
|
||||
# launch ldapcherry
|
||||
$ ldapcherryd -c /etc/ldapcherry/ldapcherry.ini
|
||||
$ ldapcherryd -c /etc/ldapcherry/ldapcherry.ini -D
|
||||
|
||||
|
||||
Debian and RPM packages are also available here: `https://github.com/kakwa/kakwalab-pkg` (package name ``ldapcherry``).
|
||||
|
||||
***********
|
||||
License
|
||||
***********
|
||||
@ -95,7 +96,7 @@ LdapCherry is published under the MIT Public License.
|
||||
Discussion / Help / Updates
|
||||
*******************************
|
||||
|
||||
* IRC: `Freenode <http://freenode.net/>`_ ``#ldapcherry`` channel
|
||||
* IRC: `Libera <https://libera.chat/>`_ ``#ldapcherry`` channel
|
||||
* Bugtracker: `Github <https://github.com/kakwa/ldapcherry/issues>`_
|
||||
|
||||
----
|
||||
|
@ -5,6 +5,10 @@
|
||||
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
|
||||
@ -24,6 +28,14 @@ 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 #
|
||||
#####################################
|
||||
@ -94,16 +106,24 @@ 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 a user
|
||||
|
||||
# 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
|
||||
|
@ -25,12 +25,12 @@ admin-lv2:
|
||||
# ad:
|
||||
# - Administrators
|
||||
|
||||
developpers:
|
||||
developers:
|
||||
display_name: Developpers
|
||||
description: Developpers of the system
|
||||
backends_groups:
|
||||
ldap:
|
||||
- cn=developpers,ou=Group,dc=example,dc=org
|
||||
- cn=developers,ou=Group,dc=example,dc=org
|
||||
- cn=users,ou=Group,dc=example,dc=org
|
||||
|
||||
users:
|
||||
|
@ -47,48 +47,55 @@ 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*).|
|
||||
+--------------------------+----------+------------------------------------+--------------------------+--------------------------------------------+
|
||||
| | | 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 | * **username**: the user key attribute |
|
||||
| | | | | * **userdn**: the user ldap dn |
|
||||
+--------------------------+----------+------------------------------------+--------------------------+--------------------------------------------+
|
||||
| | | 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 attributes can be set |
|
||||
+--------------------------+----------+------------------------------------+--------------------------+--------------------------------------------+
|
||||
| | | | | * 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 | |
|
||||
+--------------------------+----------+------------------------------------+--------------------------+--------------------------------------------+
|
||||
+--------------------------+----------+------------------------------------+--------------------------+------------------------------------------------+
|
||||
|
||||
|
||||
Example
|
||||
@ -98,6 +105,10 @@ Example
|
||||
|
||||
[backends]
|
||||
|
||||
#####################################
|
||||
# configuration of ldap backend #
|
||||
#####################################
|
||||
|
||||
# name of the module
|
||||
ldap.module = 'ldapcherry.backend.backendLdap'
|
||||
# display name of the ldap
|
||||
@ -122,16 +133,24 @@ Example
|
||||
ldap.groupdn = 'ou=group,dc=example,dc=org'
|
||||
# users dn
|
||||
ldap.userdn = 'ou=people,dc=example,dc=org'
|
||||
# ldapsearch filter to get a user
|
||||
|
||||
# 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
|
||||
|
@ -16,7 +16,6 @@
|
||||
# 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
|
||||
@ -32,6 +31,9 @@ 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 -----------------------------------------------------
|
||||
|
||||
@ -64,7 +66,7 @@ copyright = u'2016, Pierre-Francois Carpentier'
|
||||
#
|
||||
# The short X.Y version.
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = '0.3.5'
|
||||
release = version
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
|
@ -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'])
|
||||
type: <type of the attributes> # (in ['int', 'string', 'email', 'stringlist', 'fix', 'textfield'])
|
||||
backends: # (list of backend attributes name)
|
||||
- <backend id 1>: <backend 1 attribute name>
|
||||
- <backend id 2>: <backend 2 attribute name>
|
||||
@ -76,6 +76,18 @@ 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
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
@ -100,7 +112,12 @@ 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:
|
||||
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.
|
||||
|
||||
To set the key attribute, you must set **key** to **True** on this attribute.
|
||||
|
||||
@ -433,16 +450,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 or be disabled.
|
||||
Each logger can be configured to log to **syslog**, **file**, **stdout** or be disabled.
|
||||
|
||||
Logging parameters:
|
||||
|
||||
+--------------------+---------+---------------------------------+-------------------------------------------------+----------------------------------------+
|
||||
| Parameter | Section | Description | Values | Comment |
|
||||
+====================+=========+=================================+=================================================+========================================+
|
||||
| log.access_handler | global | Logger type for access log | 'syslog', 'file', 'none' | |
|
||||
| log.access_handler | global | Logger type for access log | 'syslog', 'file', 'stdout', 'none' | |
|
||||
+--------------------+---------+---------------------------------+-------------------------------------------------+----------------------------------------+
|
||||
| log.error_handler | global | Logger type for applicative log | 'syslog', 'file', 'none' | |
|
||||
| log.error_handler | global | Logger type for applicative log | 'syslog', 'file', 'stdout', 'none' | |
|
||||
+--------------------+---------+---------------------------------+-------------------------------------------------+----------------------------------------+
|
||||
| log.access_file | global | log file for access log | path to log file | only used if log.access_handler='file' |
|
||||
+--------------------+---------+---------------------------------+-------------------------------------------------+----------------------------------------+
|
||||
@ -465,6 +482,14 @@ 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
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
@ -18,17 +18,49 @@ 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
|
||||
|
@ -12,18 +12,13 @@ Download the latest release from `GitHub <https://github.com/kakwa/ldapcherry/re
|
||||
$ cd ldapcherry*
|
||||
$ python setup.py install
|
||||
|
||||
From Pypi
|
||||
---------
|
||||
Alternatively, you can install from git:
|
||||
|
||||
.. sourcecode:: bash
|
||||
|
||||
$ pip install ldapcherry
|
||||
|
||||
or
|
||||
|
||||
.. sourcecode:: bash
|
||||
|
||||
$ easy_install ldapcherry
|
||||
$ git clone https://github.com/kakwa/ldapcherry
|
||||
$ cd ldapcherry
|
||||
$ python setup.py install
|
||||
|
||||
Installed files
|
||||
---------------
|
||||
@ -38,8 +33,12 @@ 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.
|
||||
|
112
goodies/demo_backend_configs/attributes.yml
Normal file
112
goodies/demo_backend_configs/attributes.yml
Normal file
@ -0,0 +1,112 @@
|
||||
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
|
25
goodies/demo_backend_configs/ldapcherry.ini
Normal file
25
goodies/demo_backend_configs/ldapcherry.ini
Normal file
@ -0,0 +1,25 @@
|
||||
[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'
|
36
goodies/demo_backend_configs/roles.yml
Normal file
36
goodies/demo_backend_configs/roles.yml
Normal file
@ -0,0 +1,36 @@
|
||||
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
|
18
goodies/gen-dev-conf.sh
Executable file
18
goodies/gen-dev-conf.sh
Executable file
@ -0,0 +1,18 @@
|
||||
#!/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
|
25
goodies/nginx-fastcgi.conf
Normal file
25
goodies/nginx-fastcgi.conf
Normal file
@ -0,0 +1,25 @@
|
||||
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;
|
||||
}
|
||||
}
|
8
goodies/tag.sh
Executable file
8
goodies/tag.sh
Executable file
@ -0,0 +1,8 @@
|
||||
#!/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"
|
@ -1,4 +1,3 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim:set expandtab tabstop=4 shiftwidth=4:
|
||||
#
|
||||
@ -8,6 +7,7 @@
|
||||
|
||||
# Generic imports
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
import traceback
|
||||
import json
|
||||
@ -15,10 +15,8 @@ import logging
|
||||
import logging.handlers
|
||||
from operator import itemgetter
|
||||
from socket import error as socket_error
|
||||
import base64
|
||||
import cgi
|
||||
|
||||
from exceptions import *
|
||||
from ldapcherry.exceptions import *
|
||||
from ldapcherry.lclogging import *
|
||||
from ldapcherry.roles import Roles
|
||||
from ldapcherry.attributes import Attributes
|
||||
@ -30,7 +28,14 @@ from cherrypy.lib.httputil import parse_query_string
|
||||
# Mako template engines imports
|
||||
from mako.template import Template
|
||||
from mako import lookup
|
||||
from sets import Set
|
||||
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
|
||||
|
||||
SESSION_KEY = '_cp_username'
|
||||
|
||||
@ -55,36 +60,6 @@ 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
|
||||
@ -143,10 +118,10 @@ class LdapCherry(object):
|
||||
backends = self.backends_params.keys()
|
||||
for b in self.roles.get_backends():
|
||||
if b not in backends:
|
||||
raise MissingBackend(b)
|
||||
for b in self.roles.get_backends():
|
||||
raise MissingBackend(b, 'role')
|
||||
for b in self.attributes.get_backends():
|
||||
if b not in backends:
|
||||
raise MissingBackend(b)
|
||||
raise MissingBackend(b, 'attribute')
|
||||
|
||||
def _init_backends(self, config):
|
||||
""" Init all backends
|
||||
@ -167,7 +142,7 @@ class LdapCherry(object):
|
||||
try:
|
||||
self.backends_display_names[backend] = \
|
||||
self.backends_params[backend]['display_name']
|
||||
except:
|
||||
except Exception as e:
|
||||
self.backends_display_names[backend] = backend
|
||||
self.backends_params[backend]['display_name'] = backend
|
||||
params = self.backends_params[backend]
|
||||
@ -177,7 +152,7 @@ class LdapCherry(object):
|
||||
except Exception as e:
|
||||
raise MissingParameter('backends', backend + '.module')
|
||||
try:
|
||||
bc = __import__(module, globals(), locals(), ['Backend'], -1)
|
||||
bc = __import__(module, globals(), locals(), ['Backend'], 0)
|
||||
except Exception as e:
|
||||
self._handle_exception(e)
|
||||
raise BackendModuleLoadingFail(module)
|
||||
@ -186,7 +161,7 @@ class LdapCherry(object):
|
||||
key = self.attributes.get_backend_key(backend)
|
||||
self.backends[backend] = bc.Backend(
|
||||
params,
|
||||
cherrypy.log,
|
||||
cherrypy.log.error,
|
||||
backend,
|
||||
attrslist,
|
||||
key,
|
||||
@ -218,8 +193,8 @@ class LdapCherry(object):
|
||||
'ldapcherry.ppolicy'
|
||||
)
|
||||
try:
|
||||
pp = __import__(module, globals(), locals(), ['PPolicy'], -1)
|
||||
except:
|
||||
pp = __import__(module, globals(), locals(), ['PPolicy'], 0)
|
||||
except Exception as e:
|
||||
raise BackendModuleLoadingFail(module)
|
||||
if 'ppolicy' in config:
|
||||
ppcfg = config['ppolicy']
|
||||
@ -237,7 +212,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'], -1)
|
||||
auth = __import__(auth_module, globals(), locals(), ['Auth'], 0)
|
||||
self.auth = auth.Auth(config['auth'], cherrypy.log)
|
||||
else:
|
||||
raise WrongParamValue(
|
||||
@ -278,6 +253,15 @@ 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
|
||||
@ -323,6 +307,15 @@ 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
|
||||
@ -337,6 +330,7 @@ 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)
|
||||
@ -386,7 +380,8 @@ class LdapCherry(object):
|
||||
)
|
||||
# preload templates
|
||||
self.temp_lookup = lookup.TemplateLookup(
|
||||
directories=self.template_dir, input_encoding='utf-8'
|
||||
directories=self.template_dir, input_encoding='utf-8',
|
||||
default_filters=['unicode', 'h']
|
||||
)
|
||||
# load each template
|
||||
self.temp = {}
|
||||
@ -570,9 +565,9 @@ class LdapCherry(object):
|
||||
return 'anonymous'
|
||||
return cherrypy.session.get(SESSION_KEY)
|
||||
|
||||
def _check_auth(self, must_admin):
|
||||
def _check_auth(self, must_admin, redir_login=True):
|
||||
""" check if a user is autheticated and, optionnaly an administrator
|
||||
if user not authentifaced -> redirection to login page (with base64
|
||||
if user not authenticated -> redirect to login page (with escaped URL
|
||||
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
|
||||
@ -587,19 +582,32 @@ class LdapCherry(object):
|
||||
qs = ''
|
||||
else:
|
||||
qs = '?' + cherrypy.request.query_string
|
||||
# base64 of the requested URL
|
||||
b64requrl = base64.b64encode(cherrypy.url() + qs)
|
||||
# Escaped version of the requested URL
|
||||
quoted_requrl = quote_plus(cherrypy.url() + qs)
|
||||
if not username:
|
||||
# return to login page (with base64 of the url in query string
|
||||
# return to login page (with quoted url in query string)
|
||||
if redir_login:
|
||||
raise cherrypy.HTTPRedirect(
|
||||
"/signin?url=%(url)s" % {'url': b64requrl},
|
||||
"/signin?url=%(url)s" % {'url': quoted_requrl},
|
||||
)
|
||||
else:
|
||||
raise cherrypy.HTTPError(
|
||||
"403 Forbidden",
|
||||
"You must be logged in to access this ressource.",
|
||||
)
|
||||
|
||||
if 'connected' not in cherrypy.session \
|
||||
or not cherrypy.session['connected']:
|
||||
if redir_login:
|
||||
raise cherrypy.HTTPRedirect(
|
||||
"/signin?url=%(url)s" % {'url': b64requrl},
|
||||
"/signin?url=%(url)s" % {'url': quoted_requrl},
|
||||
)
|
||||
else:
|
||||
raise cherrypy.HTTPError(
|
||||
"403 Forbidden",
|
||||
"You must be logged in to access this ressource.",
|
||||
)
|
||||
|
||||
if cherrypy.session['connected'] and \
|
||||
not cherrypy.session['isadmin']:
|
||||
if must_admin:
|
||||
@ -610,12 +618,19 @@ 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': b64requrl},
|
||||
"/signin?url=%(url)s" % {'url': quoted_requrl},
|
||||
)
|
||||
else:
|
||||
raise cherrypy.HTTPError(
|
||||
"403 Forbidden",
|
||||
"You must be logged in to access this ressource.",
|
||||
)
|
||||
|
||||
def _adduser(self, params):
|
||||
@ -641,13 +656,23 @@ 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
|
||||
|
||||
key = self.attributes.get_key()
|
||||
username = params['attrs'][key]
|
||||
sess = cherrypy.session
|
||||
admin = sess.get(SESSION_KEY, None)
|
||||
admin = sess.get(SESSION_KEY, 'unknown')
|
||||
|
||||
cherrypy.log.error(
|
||||
msg="user '" + username + "' added by '" + admin + "'",
|
||||
@ -664,7 +689,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 " +
|
||||
@ -699,7 +724,13 @@ 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 + '"'
|
||||
)
|
||||
|
||||
return badd
|
||||
|
||||
def _selfmodify(self, params):
|
||||
@ -738,7 +769,7 @@ class LdapCherry(object):
|
||||
)
|
||||
|
||||
sess = cherrypy.session
|
||||
admin = sess.get(SESSION_KEY, None)
|
||||
admin = sess.get(SESSION_KEY, 'unknown')
|
||||
|
||||
cherrypy.log.error(
|
||||
msg="user '" + username + "' modified by '" + admin + "'",
|
||||
@ -786,10 +817,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 + "'",
|
||||
@ -803,11 +834,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: " +
|
||||
@ -824,10 +855,17 @@ class LdapCherry(object):
|
||||
|
||||
def _deleteuser(self, username):
|
||||
sess = cherrypy.session
|
||||
admin = sess.get(SESSION_KEY, None)
|
||||
admin = sess.get(SESSION_KEY, 'unknown')
|
||||
|
||||
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
|
||||
)
|
||||
cherrypy.log.error(
|
||||
msg="user '" + username + "' deleted from backend '" + b + "'",
|
||||
severity=logging.DEBUG
|
||||
@ -876,7 +914,7 @@ class LdapCherry(object):
|
||||
if url is None:
|
||||
redirect = "/"
|
||||
else:
|
||||
redirect = base64.b64decode(url)
|
||||
redirect = url
|
||||
raise cherrypy.HTTPRedirect(redirect)
|
||||
else:
|
||||
message = "login failed for user '%(user)s'" % {
|
||||
@ -889,7 +927,7 @@ class LdapCherry(object):
|
||||
if url is None:
|
||||
qs = ''
|
||||
else:
|
||||
qs = '?url=' + url
|
||||
qs = '?url=' + quote_plus(url)
|
||||
raise cherrypy.HTTPRedirect("/signin" + qs)
|
||||
|
||||
@cherrypy.expose
|
||||
@ -926,7 +964,7 @@ class LdapCherry(object):
|
||||
return self.temp['index.tmpl'].render(
|
||||
is_admin=is_admin,
|
||||
attrs_list=attrs_list,
|
||||
searchresult=self._escape(user_attrs, 'attr_list'),
|
||||
searchresult=user_attrs,
|
||||
notifications=self._empty_notification(),
|
||||
)
|
||||
|
||||
@ -942,7 +980,7 @@ class LdapCherry(object):
|
||||
res = None
|
||||
attrs_list = self.attributes.get_search_attributes()
|
||||
return self.temp['searchuser.tmpl'].render(
|
||||
searchresult=self._escape(res, 'search_list'),
|
||||
searchresult=res,
|
||||
attrs_list=attrs_list,
|
||||
is_admin=is_admin,
|
||||
custom_js=self.custom_js,
|
||||
@ -953,8 +991,8 @@ class LdapCherry(object):
|
||||
@exception_decorator
|
||||
def checkppolicy(self, **params):
|
||||
""" search user page """
|
||||
self._check_auth(must_admin=False)
|
||||
keys = params.keys()
|
||||
self._check_auth(must_admin=False, redir_login=False)
|
||||
keys = list(params.keys())
|
||||
if len(keys) != 1:
|
||||
cherrypy.response.status = 400
|
||||
return "bad argument"
|
||||
@ -979,7 +1017,7 @@ class LdapCherry(object):
|
||||
res = None
|
||||
attrs_list = self.attributes.get_search_attributes()
|
||||
return self.temp['searchadmin.tmpl'].render(
|
||||
searchresult=self._escape(res, 'search_list'),
|
||||
searchresult=res,
|
||||
attrs_list=attrs_list,
|
||||
is_admin=is_admin,
|
||||
custom_js=self.custom_js,
|
||||
@ -1008,6 +1046,7 @@ 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,
|
||||
@ -1028,6 +1067,10 @@ class LdapCherry(object):
|
||||
custom_js=self.custom_js,
|
||||
notifications=self._empty_notification(),
|
||||
)
|
||||
except NameError:
|
||||
raise TemplateRenderError(
|
||||
exceptions.text_error_template().render()
|
||||
)
|
||||
|
||||
@cherrypy.expose
|
||||
@exception_decorator
|
||||
@ -1037,7 +1080,7 @@ class LdapCherry(object):
|
||||
is_admin = self._check_admin()
|
||||
try:
|
||||
referer = cherrypy.request.headers['Referer']
|
||||
except:
|
||||
except Exception as e:
|
||||
referer = '/'
|
||||
self._deleteuser(user)
|
||||
self._add_notification('User Deleted')
|
||||
@ -1056,7 +1099,7 @@ class LdapCherry(object):
|
||||
self._add_notification("User modified")
|
||||
try:
|
||||
referer = cherrypy.request.headers['Referer']
|
||||
except:
|
||||
except Exception as e:
|
||||
referer = '/'
|
||||
raise cherrypy.HTTPRedirect(referer)
|
||||
|
||||
@ -1069,6 +1112,15 @@ 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
|
||||
@ -1082,13 +1134,16 @@ 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=self._escape(user_attrs, 'attr_list'),
|
||||
values=user_attrs,
|
||||
modify=True,
|
||||
keyattr=key,
|
||||
autofill=False
|
||||
)
|
||||
|
||||
roles = self.temp['roles.tmpl'].render(
|
||||
roles=self.roles.flatten,
|
||||
graph=self.roles.graph,
|
||||
@ -1096,19 +1151,26 @@ class LdapCherry(object):
|
||||
roles_js=roles_js,
|
||||
current_roles=user_roles,
|
||||
)
|
||||
return self.temp['modify.tmpl'].render(
|
||||
|
||||
glued_template = self.temp['modify.tmpl'].render(
|
||||
form=form,
|
||||
roles=roles,
|
||||
is_admin=is_admin,
|
||||
standalone_groups=self._escape(standalone_groups, 'lonely_groups'),
|
||||
standalone_groups=standalone_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=''):
|
||||
def default(self, attr='', *args, **params):
|
||||
cherrypy.response.status = 404
|
||||
self._check_auth(must_admin=False)
|
||||
is_admin = self._check_admin()
|
||||
@ -1138,15 +1200,18 @@ 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=self._escape(user_attrs, 'attr_list'),
|
||||
values=user_attrs,
|
||||
modify=True,
|
||||
autofill=False
|
||||
)
|
||||
@ -1155,3 +1220,7 @@ class LdapCherry(object):
|
||||
is_admin=is_admin,
|
||||
notifications=self._empty_notification(),
|
||||
)
|
||||
except NameError:
|
||||
raise TemplateRenderError(
|
||||
exceptions.text_error_template().render()
|
||||
)
|
||||
|
@ -12,25 +12,28 @@ 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', 'email', 'int', 'stringlist', 'fix', 'password']
|
||||
types = ['string', 'textfield', '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:
|
||||
except Exception as e:
|
||||
raise MissingAttributesFile(attributes_file)
|
||||
try:
|
||||
self.attributes = loadNoDump(stream)
|
||||
@ -68,7 +71,7 @@ class Attributes:
|
||||
raise MissingUserKey()
|
||||
|
||||
def _is_email(self, email):
|
||||
pattern = '[\.\w]{1,}[@]\w+[.]\w+'
|
||||
pattern = r'[\+\.\w]+@[-\.\w]+\.\w+'
|
||||
if re.match(pattern, email):
|
||||
return True
|
||||
else:
|
||||
@ -81,6 +84,8 @@ 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
|
||||
@ -125,7 +130,9 @@ class Attributes:
|
||||
def get_backend_attributes(self, backend):
|
||||
if backend not in self.backends:
|
||||
raise WrongBackend(backend)
|
||||
return self.backend_attributes[backend].keys()
|
||||
ret = list(self.backend_attributes[backend].keys())
|
||||
ret.sort()
|
||||
return ret
|
||||
|
||||
def get_backend_key(self, backend):
|
||||
if backend not in self.backends:
|
||||
|
@ -15,6 +15,7 @@ import ldapcherry.backend
|
||||
from ldapcherry.exceptions import UserDoesntExist, GroupDoesntExist
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
|
||||
class CaFileDontExist(Exception):
|
||||
@ -28,6 +29,7 @@ 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
|
||||
@ -128,28 +130,38 @@ class Backend(ldapcherry.backend.backendLdap.Backend):
|
||||
self.dn_user_attr = 'cn'
|
||||
self.key = 'sAMAccountName'
|
||||
self.objectclasses = [
|
||||
'top',
|
||||
'person',
|
||||
'organizationalPerson',
|
||||
'user',
|
||||
'posixAccount',
|
||||
self._byte_p23('top'),
|
||||
self._byte_p23('person'),
|
||||
self._byte_p23('organizationalPerson'),
|
||||
self._byte_p23('user'),
|
||||
self._byte_p23('posixAccount'),
|
||||
]
|
||||
self.group_attrs = {
|
||||
'member': "%(dn)s"
|
||||
}
|
||||
|
||||
self.attrlist = []
|
||||
self.group_attrs_keys = []
|
||||
for a in attrslist:
|
||||
self.attrlist.append(self._str(a))
|
||||
self.attrlist.append(self._byte_p2(a))
|
||||
|
||||
if 'cn' not in self.attrlist:
|
||||
if self._byte_p2('cn') not in self.attrlist:
|
||||
raise MissingAttr()
|
||||
|
||||
if 'unicodePwd' not in self.attrlist:
|
||||
if self._byte_p2('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._str(searchfilter)
|
||||
searchfilter = self._byte_p2(searchfilter)
|
||||
ldap_client = self._bind()
|
||||
try:
|
||||
r = ldap_client.search_s(
|
||||
@ -181,22 +193,24 @@ class Backend(ldapcherry.backend.backendLdap.Backend):
|
||||
ldap_client = self._bind()
|
||||
|
||||
if by_cn:
|
||||
dn = self._str('CN=%(cn)s,%(user_dn)s' % {
|
||||
dn = self._byte_p2('CN=%(cn)s,%(user_dn)s' % {
|
||||
'cn': name,
|
||||
'user_dn': self.userdn
|
||||
})
|
||||
else:
|
||||
dn = name
|
||||
dn = self._byte_p2(name)
|
||||
|
||||
attrs = {}
|
||||
|
||||
attrs['unicodePwd'] = self._str(password_value)
|
||||
attrs['unicodePwd'] = self._modlist(self._byte_p2(password_value))
|
||||
|
||||
ldif = modlist.modifyModlist({'unicodePwd': 'tmp'}, attrs)
|
||||
ldap_client.modify_s(dn, ldif)
|
||||
|
||||
del(attrs['unicodePwd'])
|
||||
attrs['UserAccountControl'] = str(NORMAL_ACCOUNT)
|
||||
attrs['UserAccountControl'] = self._modlist(
|
||||
self._tobyte(NORMAL_ACCOUNT)
|
||||
)
|
||||
ldif = modlist.modifyModlist({'UserAccountControl': 'tmp'}, attrs)
|
||||
ldap_client.modify_s(dn, ldif)
|
||||
|
||||
@ -204,14 +218,14 @@ class Backend(ldapcherry.backend.backendLdap.Backend):
|
||||
password = attrs['unicodePwd']
|
||||
del(attrs['unicodePwd'])
|
||||
super(Backend, self).add_user(attrs)
|
||||
userdn = self._get_user(username, NO_ATTR)
|
||||
self._set_password(userdn, password, False)
|
||||
self._set_password(attrs['cn'], password)
|
||||
|
||||
def set_attrs(self, username, attrs):
|
||||
if 'unicodePwd' in attrs:
|
||||
password = attrs['unicodePwd']
|
||||
del(attrs['unicodePwd'])
|
||||
self._set_password(attrs['cn'], password)
|
||||
userdn = self._get_user(self._byte_p2(username), NO_ATTR)
|
||||
self._set_password(userdn, password, False)
|
||||
super(Backend, self).set_attrs(username, attrs)
|
||||
|
||||
def add_to_groups(self, username, groups):
|
||||
@ -224,7 +238,7 @@ class Backend(ldapcherry.backend.backendLdap.Backend):
|
||||
|
||||
def get_groups(self, username):
|
||||
username = ldap.filter.escape_filter_chars(username)
|
||||
userdn = self._get_user(username, NO_ATTR)
|
||||
userdn = self._get_user(self._byte_p2(username), NO_ATTR)
|
||||
|
||||
searchfilter = self.group_filter_tmpl % {
|
||||
'userdn': userdn,
|
||||
@ -244,7 +258,7 @@ class Backend(ldapcherry.backend.backendLdap.Backend):
|
||||
)
|
||||
|
||||
for entry in groups:
|
||||
ret.append(entry[1]['cn'][0])
|
||||
ret.append(self._uni(entry[1]['cn'][0]))
|
||||
return ret
|
||||
|
||||
def auth(self, username, password):
|
||||
@ -253,7 +267,10 @@ class Backend(ldapcherry.backend.backendLdap.Backend):
|
||||
if binddn is not None:
|
||||
ldap_client = self._connect()
|
||||
try:
|
||||
ldap_client.simple_bind_s(binddn, password)
|
||||
ldap_client.simple_bind_s(
|
||||
self._byte_p2(binddn),
|
||||
self._byte_p2(password)
|
||||
)
|
||||
except ldap.INVALID_CREDENTIALS:
|
||||
ldap_client.unbind_s()
|
||||
return False
|
||||
|
@ -7,12 +7,15 @@
|
||||
|
||||
# This is a demo backend
|
||||
|
||||
from sets import Set
|
||||
|
||||
import sys
|
||||
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):
|
||||
@ -37,13 +40,17 @@ 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(re.split('\W+', self.get_param('admin.groups')))
|
||||
admin_groups = set(
|
||||
self._basic_splitter(self.get_param('admin.groups'))
|
||||
)
|
||||
basic_user = self.get_param('basic.user', 'user')
|
||||
basic_password = self.get_param('basic.password', 'user')
|
||||
basic_groups = Set(re.split('\W+', self.get_param('basic.groups')))
|
||||
basic_groups = set(
|
||||
self._basic_splitter(self.get_param('basic.groups'))
|
||||
)
|
||||
pwd_attr = self.get_param('pwd_attr')
|
||||
self.search_attrs = Set(
|
||||
re.split('\W+', self.get_param('search_attributes')),
|
||||
self.search_attrs = set(
|
||||
re.split(r'\W+', self.get_param('search_attributes')),
|
||||
)
|
||||
self.pwd_attr = pwd_attr
|
||||
self.admin_user = admin_user
|
||||
@ -60,6 +67,11 @@ 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')
|
||||
@ -91,7 +103,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
|
||||
@ -103,11 +115,11 @@ class Backend(ldapcherry.backend.Backend):
|
||||
self._check_fix_users(username)
|
||||
try:
|
||||
del self.users[username]
|
||||
except:
|
||||
except Exception as e:
|
||||
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
|
||||
@ -128,7 +140,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):
|
||||
@ -143,7 +155,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):
|
||||
@ -176,7 +188,7 @@ class Backend(ldapcherry.backend.Backend):
|
||||
"""
|
||||
try:
|
||||
return self.users[username]
|
||||
except:
|
||||
except Exception as e:
|
||||
raise UserDoesntExist(username, self.backend_name)
|
||||
|
||||
def get_groups(self, username):
|
||||
@ -188,5 +200,5 @@ class Backend(ldapcherry.backend.Backend):
|
||||
"""
|
||||
try:
|
||||
return self.users[username]['groups']
|
||||
except:
|
||||
except Exception as e:
|
||||
raise UserDoesntExist(username, self.backend_name)
|
||||
|
@ -11,11 +11,16 @@ 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):
|
||||
@ -23,6 +28,21 @@ 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
|
||||
@ -55,17 +75,21 @@ class Backend(ldapcherry.backend.Backend):
|
||||
self.key = key
|
||||
# objectclasses parameter is a coma separated list in configuration
|
||||
# split it to get a real list, and convert it to bytes
|
||||
for o in re.split('\W+', self.get_param('objectclasses')):
|
||||
self.objectclasses.append(self._str(o))
|
||||
for o in re.split(r'\W+', self.get_param('objectclasses')):
|
||||
self.objectclasses.append(self._byte_p23(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._str(a))
|
||||
self.attrlist.append(self._byte_p2(a))
|
||||
|
||||
# exception handler (mainly to log something meaningful)
|
||||
def _exception_handler(self, e):
|
||||
@ -108,8 +132,8 @@ class Backend(ldapcherry.backend.Backend):
|
||||
".groupdn'",
|
||||
)
|
||||
elif et is ldap.OBJECT_CLASS_VIOLATION:
|
||||
info = e[0]['info']
|
||||
desc = e[0]['desc']
|
||||
info = e.args[0]['info']
|
||||
desc = e.args[0]['desc']
|
||||
self._logger(
|
||||
severity=logging.ERROR,
|
||||
msg="Configuration error, " + desc + ", " + info,
|
||||
@ -123,7 +147,7 @@ class Backend(ldapcherry.backend.Backend):
|
||||
self.backend_name,
|
||||
)
|
||||
elif et is ldap.ALREADY_EXISTS:
|
||||
desc = e[0]['desc']
|
||||
desc = e.args[0]['desc']
|
||||
self._logger(
|
||||
severity=logging.ERROR,
|
||||
msg="adding user failed, " + desc,
|
||||
@ -135,6 +159,38 @@ 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)
|
||||
@ -205,6 +261,16 @@ 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:
|
||||
@ -246,7 +312,7 @@ class Backend(ldapcherry.backend.Backend):
|
||||
user_filter = self.user_filter_tmpl % {
|
||||
'username': self._uni(username)
|
||||
}
|
||||
r = self._search(self._str(user_filter), attrs, self.userdn)
|
||||
r = self._search(self._byte_p2(user_filter), attrs, self.userdn)
|
||||
|
||||
if len(r) == 0:
|
||||
return None
|
||||
@ -258,12 +324,56 @@ 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):
|
||||
"""unicode -> bytes conversion"""
|
||||
if s is None:
|
||||
return None
|
||||
return s.encode('utf-8')
|
||||
|
||||
def _str(self, s):
|
||||
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
|
||||
@ -273,18 +383,29 @@ class Backend(ldapcherry.backend.Backend):
|
||||
"""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 auth(self, username, password):
|
||||
"""Authentication of a user"""
|
||||
|
||||
binddn = self._get_user(self._str(username), NO_ATTR)
|
||||
binddn = self._get_user(self._byte_p2(username), NO_ATTR)
|
||||
if binddn is not None:
|
||||
ldap_client = self._connect()
|
||||
try:
|
||||
ldap_client.simple_bind_s(
|
||||
self._str(binddn),
|
||||
self._str(password)
|
||||
self._byte_p2(binddn),
|
||||
self._byte_p2(password)
|
||||
)
|
||||
except ldap.INVALID_CREDENTIALS:
|
||||
ldap_client.unbind_s()
|
||||
@ -294,28 +415,35 @@ class Backend(ldapcherry.backend.Backend):
|
||||
else:
|
||||
return False
|
||||
|
||||
def attrs_pretreatment(self, attrs):
|
||||
attrs_str = {}
|
||||
for a in attrs:
|
||||
attrs_str[self._str(a)] = self._str(attrs[a])
|
||||
return attrs_str
|
||||
if PYTHON_LDAP_MAJOR_VERSION == '2':
|
||||
@staticmethod
|
||||
def _modlist(in_attr):
|
||||
return in_attr
|
||||
|
||||
else:
|
||||
@staticmethod
|
||||
def _modlist(in_attr):
|
||||
return [in_attr]
|
||||
|
||||
def add_user(self, attrs):
|
||||
"""add a user"""
|
||||
ldap_client = self._bind()
|
||||
# encoding crap
|
||||
attrs_str = self.attrs_pretreatment(attrs)
|
||||
attrs_srt = self.attrs_pretreatment(attrs)
|
||||
|
||||
attrs_str['objectClass'] = self.objectclasses
|
||||
attrs_srt[self._byte_p2('objectClass')] = self.objectclasses
|
||||
# construct is DN
|
||||
dn = \
|
||||
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)
|
||||
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)
|
||||
try:
|
||||
ldap_client.add_s(dn, ldif)
|
||||
except ldap.ALREADY_EXISTS as e:
|
||||
@ -329,7 +457,7 @@ class Backend(ldapcherry.backend.Backend):
|
||||
"""delete a user"""
|
||||
ldap_client = self._bind()
|
||||
# recover the user dn
|
||||
dn = self._str(self._get_user(self._str(username), NO_ATTR))
|
||||
dn = self._byte_p2(self._get_user(self._byte_p2(username), NO_ATTR))
|
||||
# delete
|
||||
if dn is not None:
|
||||
ldap_client.delete_s(dn)
|
||||
@ -339,15 +467,17 @@ class Backend(ldapcherry.backend.Backend):
|
||||
ldap_client.unbind_s()
|
||||
|
||||
def set_attrs(self, username, attrs):
|
||||
""" Set user attributes"""
|
||||
""" set user attributes"""
|
||||
ldap_client = self._bind()
|
||||
tmp = self._get_user(self._str(username), ALL_ATTRS)
|
||||
dn = self._str(tmp[0])
|
||||
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])
|
||||
old_attrs = tmp[1]
|
||||
for attr in attrs:
|
||||
bcontent = self._str(attrs[attr])
|
||||
battr = self._str(attr)
|
||||
new = {battr: bcontent}
|
||||
bcontent = self._byte_p2(attrs[attr])
|
||||
battr = self._byte_p2(attr)
|
||||
new = {battr: self._modlist(self._byte_p3(bcontent))}
|
||||
# if attr is dn entry, use rename
|
||||
if attr.lower() == self.dn_user_attr.lower():
|
||||
ldap_client.rename_s(
|
||||
@ -364,15 +494,18 @@ class Backend(ldapcherry.backend.Backend):
|
||||
if type(old_attrs[attr]) is list:
|
||||
tmp = []
|
||||
for value in old_attrs[attr]:
|
||||
tmp.append(self._str(value))
|
||||
tmp.append(self._byte_p2(value))
|
||||
bold_value = tmp
|
||||
else:
|
||||
bold_value = self._str(old_attrs[attr])
|
||||
bold_value = self._modlist(
|
||||
self._byte_p3(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:
|
||||
@ -384,18 +517,19 @@ class Backend(ldapcherry.backend.Backend):
|
||||
def add_to_groups(self, username, groups):
|
||||
ldap_client = self._bind()
|
||||
# recover dn of the user and his attributes
|
||||
tmp = self._get_user(self._str(username), ALL_ATTRS)
|
||||
tmp = self._get_user(self._byte_p2(username), ALL_ATTRS)
|
||||
dn = tmp[0]
|
||||
attrs = tmp[1]
|
||||
attrs['dn'] = dn
|
||||
dn = self._str(tmp[0])
|
||||
self._normalize_group_attrs(attrs)
|
||||
dn = self._byte_p2(tmp[0])
|
||||
# add user to all groups
|
||||
for group in groups:
|
||||
group = self._str(group)
|
||||
group = self._byte_p2(group)
|
||||
# iterate on group membership attributes
|
||||
for attr in self.group_attrs:
|
||||
# fill the content template
|
||||
content = self._str(self.group_attrs[attr] % attrs)
|
||||
content = self._byte_p2(self.group_attrs[attr] % attrs)
|
||||
self._logger(
|
||||
severity=logging.DEBUG,
|
||||
msg="%(backend)s: adding user '%(user)s'"
|
||||
@ -409,11 +543,14 @@ class Backend(ldapcherry.backend.Backend):
|
||||
'backend': self.backend_name
|
||||
}
|
||||
)
|
||||
ldif = modlist.modifyModlist({}, {attr: content})
|
||||
ldif = modlist.modifyModlist(
|
||||
{},
|
||||
{attr: self._modlist(self._byte_p3(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 as e:
|
||||
except (ldap.TYPE_OR_VALUE_EXISTS, ldap.ALREADY_EXISTS) as e:
|
||||
self._logger(
|
||||
severity=logging.INFO,
|
||||
msg="%(backend)s: user '%(user)s'"
|
||||
@ -437,16 +574,19 @@ class Backend(ldapcherry.backend.Backend):
|
||||
# it follows the same logic than add_to_groups
|
||||
# but with MOD_DELETE
|
||||
ldap_client = self._bind()
|
||||
tmp = self._get_user(self._str(username), ALL_ATTRS)
|
||||
tmp = self._get_user(self._byte_p2(username), ALL_ATTRS)
|
||||
if tmp is None:
|
||||
raise UserDoesntExist(username, self.backend_name)
|
||||
dn = tmp[0]
|
||||
attrs = tmp[1]
|
||||
attrs['dn'] = dn
|
||||
dn = self._str(tmp[0])
|
||||
self._normalize_group_attrs(attrs)
|
||||
dn = self._byte_p2(tmp[0])
|
||||
for group in groups:
|
||||
group = self._str(group)
|
||||
group = self._byte_p2(group)
|
||||
for attr in self.group_attrs:
|
||||
content = self._str(self.group_attrs[attr] % attrs)
|
||||
ldif = [(ldap.MOD_DELETE, attr, content)]
|
||||
content = self._byte_p2(self.group_attrs[attr] % attrs)
|
||||
ldif = [(ldap.MOD_DELETE, attr, self._byte_p3(content))]
|
||||
try:
|
||||
ldap_client.modify_s(group, ldif)
|
||||
except ldap.NO_SUCH_ATTRIBUTE as e:
|
||||
@ -469,7 +609,9 @@ class Backend(ldapcherry.backend.Backend):
|
||||
def search(self, searchstring):
|
||||
"""Search users"""
|
||||
# escape special char to avoid injection
|
||||
searchstring = ldap.filter.escape_filter_chars(self._str(searchstring))
|
||||
searchstring = ldap.filter.escape_filter_chars(
|
||||
self._byte_p2(searchstring)
|
||||
)
|
||||
# fill the search string template
|
||||
searchfilter = self.search_filter_tmpl % {
|
||||
'searchstring': searchstring
|
||||
@ -494,7 +636,7 @@ class Backend(ldapcherry.backend.Backend):
|
||||
def get_user(self, username):
|
||||
"""Gest a specific user"""
|
||||
ret = {}
|
||||
tmp = self._get_user(self._str(username), ALL_ATTRS)
|
||||
tmp = self._get_user(self._byte_p2(username), ALL_ATTRS)
|
||||
if tmp is None:
|
||||
raise UserDoesntExist(username, self.backend_name)
|
||||
attrs_tmp = tmp[1]
|
||||
@ -508,7 +650,7 @@ class Backend(ldapcherry.backend.Backend):
|
||||
|
||||
def get_groups(self, username):
|
||||
"""Get all groups of a user"""
|
||||
username = ldap.filter.escape_filter_chars(self._str(username))
|
||||
username = ldap.filter.escape_filter_chars(self._byte_p2(username))
|
||||
userdn = self._get_user(username, NO_ATTR)
|
||||
|
||||
searchfilter = self.group_filter_tmpl % {
|
||||
@ -519,5 +661,5 @@ class Backend(ldapcherry.backend.Backend):
|
||||
groups = self._search(searchfilter, NO_ATTR, self.groupdn)
|
||||
ret = []
|
||||
for entry in groups:
|
||||
ret.append(entry[0])
|
||||
ret.append(self._uni(entry[0]))
|
||||
return ret
|
||||
|
@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
# -*- 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:
|
||||
except Exception as e:
|
||||
# Assume the error has been logged already via bus.log.
|
||||
sys.exit(1)
|
||||
else:
|
||||
engine.block()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
def main():
|
||||
from optparse import OptionParser
|
||||
|
||||
p = OptionParser()
|
||||
@ -142,3 +142,7 @@ if __name__ == '__main__':
|
||||
start(options.config, options.daemonize,
|
||||
options.environment, options.fastcgi, options.scgi,
|
||||
options.pidfile, options.cgi, options.debug)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -46,11 +46,12 @@ class MissingRole(Exception):
|
||||
|
||||
|
||||
class MissingBackend(Exception):
|
||||
def __init__(self, backend):
|
||||
def __init__(self, backend, type_conf):
|
||||
self.backend = backend
|
||||
self.log = \
|
||||
"backend '%(backend)s' does not exist in main config file" % \
|
||||
{'backend': backend}
|
||||
"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}
|
||||
|
||||
|
||||
class WrongBackend(Exception):
|
||||
@ -90,10 +91,10 @@ class PPolicyError(Exception):
|
||||
|
||||
class MissingMainFile(Exception):
|
||||
def __init__(self, config):
|
||||
self.rolefile = rolefile
|
||||
self.config = config
|
||||
self.log = \
|
||||
"fail to open main file '%(config)s'" % \
|
||||
{'rolefile': rolefile}
|
||||
{'config': config}
|
||||
|
||||
|
||||
class MissingAttributesFile(Exception):
|
||||
@ -217,6 +218,11 @@ 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:
|
||||
|
@ -1,4 +1,3 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim:set expandtab tabstop=4 shiftwidth=4:
|
||||
#
|
||||
|
@ -31,9 +31,9 @@ class PPolicy(ldapcherry.ppolicy.PPolicy):
|
||||
|
||||
def info(self):
|
||||
return \
|
||||
"* Minimum length: %(len)n\n" \
|
||||
"* Minimum number of uppercase characters: %(upper)n\n" \
|
||||
"* Minimum number of digits: %(digit)n" % {
|
||||
"* Minimum length: %(len)d\n" \
|
||||
"* Minimum number of uppercase characters: %(upper)d\n" \
|
||||
"* Minimum number of digits: %(digit)d" % {
|
||||
'upper': self.min_upper,
|
||||
'len': self.min_length,
|
||||
'digit': self.min_digit
|
||||
|
@ -9,12 +9,14 @@ 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"
|
||||
@ -27,10 +29,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:
|
||||
except Exception as e:
|
||||
raise MissingRolesFile(role_file)
|
||||
try:
|
||||
self.roles_raw = loadNoDump(stream)
|
||||
@ -51,11 +53,12 @@ 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):
|
||||
@ -134,8 +137,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
|
||||
@ -147,7 +150,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] = []
|
||||
@ -223,7 +226,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)
|
||||
|
||||
@ -254,11 +257,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:
|
||||
@ -267,7 +270,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)
|
||||
|
||||
@ -284,12 +287,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)
|
||||
@ -298,10 +301,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 = {}
|
||||
@ -316,7 +319,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
|
||||
|
8
ldapcherry/version.py
Normal file
8
ldapcherry/version.py
Normal file
@ -0,0 +1,8 @@
|
||||
# -*- 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'
|
@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import with_statement
|
||||
@ -8,7 +8,6 @@ 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
|
||||
|
@ -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=developpers,ou=group,dc=example,dc=com',
|
||||
'cn=developers,ou=group,dc=example,dc=com',
|
||||
],
|
||||
'toto': ['not a group'],
|
||||
}
|
||||
|
4
requirements-el7.txt
Normal file
4
requirements-el7.txt
Normal file
@ -0,0 +1,4 @@
|
||||
CherryPy>=3.0.0
|
||||
PyYAML
|
||||
Mako
|
||||
python-ldap==3.4.0
|
4
requirements-stretch.txt
Normal file
4
requirements-stretch.txt
Normal file
@ -0,0 +1,4 @@
|
||||
CherryPy>=3.0.0
|
||||
PyYAML
|
||||
Mako
|
||||
python-ldap==3.4.0
|
25
resources/static/js/alignforms.js
Normal file
25
resources/static/js/alignforms.js
Normal file
@ -0,0 +1,25 @@
|
||||
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");
|
@ -30,3 +30,8 @@ function lcUidNumber(firstname, lastname, minuid, maxuid){
|
||||
function lcHomeDir(firstname, lastname, basedir){
|
||||
return basedir+lcUid(firstname, lastname);
|
||||
}
|
||||
|
||||
function lcCopy(value){
|
||||
return value;
|
||||
}
|
||||
|
||||
|
@ -10,9 +10,24 @@ $('#form').validator({
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
async: false,
|
||||
data: 'pwd=' + $el.val(),
|
||||
data: 'pwd=' + encodeURIComponent($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'];
|
||||
|
@ -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}
|
||||
${form | n}
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>Enable/Disable user's roles:</legend>
|
||||
${roles}
|
||||
${roles | n}
|
||||
</fieldset>
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
|
@ -58,7 +58,7 @@
|
||||
<body>
|
||||
% if notifications:
|
||||
% for notif in notifications:
|
||||
<script type="text/javascript">$.notify('${notif}')</script>
|
||||
<script type="text/javascript">$.notify('${notif | n}')</script>
|
||||
% endfor
|
||||
% endif
|
||||
<div class="container">
|
||||
@ -70,5 +70,6 @@
|
||||
<p class="muted credit"><a href="http://ldapcherry.readthedocs.org" target="_blank">LdapCherry</a> • © 2016 • Pierre-François Carpentier • Released under the MIT License</p>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript" src="/static/js/alignforms.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -1,5 +1,6 @@
|
||||
## -*- coding: utf-8 -*-
|
||||
<%
|
||||
from markupsafe import Markup
|
||||
len_attr = len(attributes)
|
||||
switch = len_attr / 2
|
||||
if not switch * 2 == len_attr:
|
||||
@ -26,44 +27,49 @@ 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:
|
||||
tmp = values[a][0]
|
||||
raw_value = values[a][0]
|
||||
else:
|
||||
tmp = values[a]
|
||||
if tmp is None:
|
||||
tmp = ''
|
||||
value = ' value="'+ tmp + '"'
|
||||
value2 = '<option>'+ tmp +'</option>'
|
||||
raw_value = values[a]
|
||||
if raw_value is None:
|
||||
raw_value = ''
|
||||
value = Markup(' value="{}"').format(raw_value)
|
||||
value2 = Markup('<option>{}</option>').format(raw_value)
|
||||
else:
|
||||
raw_value = ''
|
||||
value = ''
|
||||
value2 = ''
|
||||
if 'default' in attr and value == '':
|
||||
value = ' value="'+ attr['default'] + '"'
|
||||
value = Markup(' value="{}"').format(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} readonly onfocus="this.removeAttribute('readonly');">
|
||||
<span class="form-control" aria-describedby="basic-addon-${a}">${tmp}</span>
|
||||
<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>
|
||||
% 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} 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 | n} 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} 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 | n} 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} 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 | n} 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}
|
||||
${value2 | n}
|
||||
%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>
|
||||
@ -71,23 +77,23 @@ for a in sorted(attributes.keys(), key=lambda attr: attributes[attr]['weight']):
|
||||
% endfor
|
||||
</%def>
|
||||
<div class="row">
|
||||
<div class="col-md-6 column">
|
||||
<div class="col-md-6 column lcform-col-1" style="display:none;">
|
||||
${form_col(lc1)}
|
||||
</div>
|
||||
<div class="col-md-6 column">
|
||||
<div class="col-md-6 column lcform-col-2" style="display:none;">
|
||||
${form_col(lc2)}
|
||||
</div>
|
||||
</div>
|
||||
% if autofill:
|
||||
<%
|
||||
from sets import Set
|
||||
attr_set = Set([])
|
||||
attr_set = []
|
||||
attr_events = {}
|
||||
functions = {}
|
||||
for attrid in attributes:
|
||||
attr = attributes[attrid]
|
||||
field = 'attr.' + attrid
|
||||
attr_set.add(field)
|
||||
if field not in attr_set:
|
||||
attr_set.append(field)
|
||||
if 'autofill' in attr:
|
||||
function = attr['autofill']['function']
|
||||
tuple = (field, function)
|
||||
@ -96,7 +102,8 @@ for attrid in attributes:
|
||||
for arg in attr['autofill']['args']:
|
||||
if arg[0] == '$':
|
||||
field_arg = 'attr.' + arg[1:]
|
||||
attr_set.add(field_arg)
|
||||
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] = []
|
||||
@ -116,7 +123,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]}(${', '.join(functions[tuple])});
|
||||
fields['${tuple[0]}'].value = ${tuple[1] | n}(${', '.join(functions[tuple]) | n});
|
||||
}
|
||||
% endfor
|
||||
};
|
||||
|
@ -11,8 +11,8 @@
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<table id="RecordTable" class="table table-hover table-condensed">
|
||||
<tbody>
|
||||
% if not searchresult is None:
|
||||
<tbody>
|
||||
%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
|
||||
%endif
|
||||
</tbody>
|
||||
%endif
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -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">
|
||||
<%
|
||||
if url is None:
|
||||
qs=''
|
||||
else:
|
||||
qs='?url=' + url
|
||||
%>
|
||||
<form method='POST' action='/login${qs}' role="form" class="form-signin">
|
||||
<form method='POST' role="form" class="form-signin"
|
||||
% if url:
|
||||
action='login?url=${url | u}'
|
||||
% else:
|
||||
action='login'
|
||||
% endif
|
||||
>
|
||||
<div class="form-group">
|
||||
<h2 class="form-signin-heading">Please sign in</h2>
|
||||
<div class="input-group">
|
||||
|
@ -9,11 +9,11 @@
|
||||
<form method='POST' action='/modify' role="form" class="form-signin" id="form">
|
||||
<fieldset>
|
||||
<legend>Modify user's attributes:</legend>
|
||||
${form}
|
||||
${form | n}
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>Enable/Disable user's roles:</legend>
|
||||
${roles}
|
||||
${roles | n}
|
||||
</fieldset>
|
||||
% if len(standalone_groups) != 0:
|
||||
<fieldset>
|
||||
|
@ -13,12 +13,19 @@
|
||||
<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>
|
||||
|
@ -1,7 +1,7 @@
|
||||
## -*- coding: utf-8 -*-
|
||||
<script type="text/javascript">
|
||||
var graph = ${graph_js};
|
||||
var roles = ${roles_js};
|
||||
var graph = ${graph_js | n};
|
||||
var roles = ${roles_js | n};
|
||||
function enableParentRoles(roleid){
|
||||
var parentRoles = graph[roleid]['parent_roles'];
|
||||
var DnRole = roles[roleid];
|
||||
|
@ -52,10 +52,10 @@
|
||||
</td>
|
||||
% endfor
|
||||
<td>
|
||||
<a href="/modify?user=${user}" class="btn btn-xs blue pad" ><span class="glyphicon glyphicon-cog"></span> Modify</a>
|
||||
<a href="/modify?user=${user | n,u}" class="btn btn-xs blue pad" ><span class="glyphicon glyphicon-cog"></span> Modify</a>
|
||||
</td>
|
||||
<td>
|
||||
<a href="/delete?user=${user}" data-toggle='confirmation-delete' class="btn btn-xs red pad"><span class="glyphicon glyphicon-remove-sign"></span> Delete</a>
|
||||
<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>
|
||||
</td>
|
||||
</tr>
|
||||
% endfor
|
||||
|
@ -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}
|
||||
${form | n}
|
||||
</fieldset>
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
|
16
run_test.sh
16
run_test.sh
@ -5,11 +5,11 @@ Gre='\33[0;32m';
|
||||
RCol='\33[0m';
|
||||
|
||||
cd `dirname $0`
|
||||
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"
|
||||
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"
|
||||
|
26
setup.py
26
setup.py
@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim:set expandtab tabstop=4 shiftwidth=4:
|
||||
|
||||
@ -17,17 +17,24 @@ 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)
|
||||
@ -90,7 +97,6 @@ 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):
|
||||
@ -109,7 +115,7 @@ if as_option_root() or not os.path.exists(
|
||||
setup(
|
||||
name='ldapcherry',
|
||||
zip_safe=False,
|
||||
version='0.3.5',
|
||||
version=version,
|
||||
author='Pierre-Francois Carpentier',
|
||||
author_email='carpentier.pf@gmail.com',
|
||||
packages=[
|
||||
@ -118,16 +124,18 @@ setup(
|
||||
'ldapcherry.ppolicy'
|
||||
],
|
||||
data_files=resources_files,
|
||||
scripts=['scripts/ldapcherryd'],
|
||||
entry_points = {
|
||||
'console_scripts': ['ldapcherryd = ldapcherry.cli:main']
|
||||
},
|
||||
url='https://github.com/kakwa/ldapcherry',
|
||||
license=license,
|
||||
description=small_description,
|
||||
long_description=description,
|
||||
install_requires=install_requires,
|
||||
tests_require=['pytest', 'pep8'],
|
||||
tests_require=['pytest', 'pep8', 'pytidylib'],
|
||||
cmdclass={'test': PyTest},
|
||||
classifiers=[
|
||||
'Development Status :: 3 - Alpha',
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
'Environment :: Web Environment',
|
||||
'Framework :: CherryPy',
|
||||
'Intended Audience :: System Administrators',
|
||||
|
@ -81,7 +81,7 @@ gidNumber:
|
||||
default: 10000
|
||||
backends:
|
||||
ldap: gidNumber
|
||||
ad: GIDNumber
|
||||
ad: gidNumber
|
||||
shell:
|
||||
description: "Shell of the user"
|
||||
display_name: "Shell"
|
||||
|
136
tests/cfg/attributes_adldap.yml
Normal file
136
tests/cfg/attributes_adldap.yml
Normal file
@ -0,0 +1,136 @@
|
||||
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
|
186
tests/cfg/ldapcherry_adldap.cfg
Normal file
186
tests/cfg/ldapcherry_adldap.cfg
Normal file
@ -0,0 +1,186 @@
|
||||
# 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
|
@ -21,10 +21,10 @@ users:
|
||||
display_name: Administrators Level 3
|
||||
description: description
|
||||
subroles: {}
|
||||
developpers:
|
||||
developers:
|
||||
backends_groups:
|
||||
ad: [Domain Users]
|
||||
ldap: ['cn=developpers,ou=group,dc=example,dc=com']
|
||||
ldap: ['cn=developers,ou=group,dc=example,dc=com']
|
||||
display_name: Developpers
|
||||
description: description
|
||||
subroles: {}
|
||||
|
@ -23,12 +23,12 @@ admin-lv2:
|
||||
ad:
|
||||
- Domain Users
|
||||
|
||||
developpers:
|
||||
developers:
|
||||
display_name: Developpers
|
||||
description: description
|
||||
backends_groups:
|
||||
ldap:
|
||||
- cn=developpers,ou=group,dc=example,dc=com
|
||||
- cn=developers,ou=group,dc=example,dc=com
|
||||
- cn=users,ou=group,dc=example,dc=com
|
||||
ad:
|
||||
- Domain Users
|
||||
|
41
tests/cfg/roles_adldap.yml
Normal file
41
tests/cfg/roles_adldap.yml
Normal file
@ -0,0 +1,41 @@
|
||||
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
|
@ -23,12 +23,12 @@ admin -lv2:
|
||||
ad:
|
||||
- Domain Users
|
||||
|
||||
developpers:
|
||||
developers:
|
||||
display_name: Developpers
|
||||
description: description
|
||||
backends_groups:
|
||||
ldap:
|
||||
- cn=developpers,ou=group,dc=example,dc=com
|
||||
- cn=developers,ou=group,dc=example,dc=com
|
||||
- cn=users,ou=group,dc=example,dc=com
|
||||
ad:
|
||||
- Domain Users
|
||||
|
@ -23,12 +23,12 @@ admin -lv3:
|
||||
ad:
|
||||
- Domain Users
|
||||
|
||||
developpers:
|
||||
developers:
|
||||
display_name: Developpers
|
||||
description: description
|
||||
backends_groups:
|
||||
ldap:
|
||||
- cn=developpers,ou=group,dc=example,dc=com
|
||||
- cn=developers,ou=group,dc=example,dc=com
|
||||
- cn=users,ou=group,dc=example,dc=com
|
||||
ad:
|
||||
- Domain Users
|
||||
|
@ -17,12 +17,12 @@ admin-lv2:
|
||||
display_name: Administrators Level 2
|
||||
description: description
|
||||
|
||||
developpers:
|
||||
developers:
|
||||
display_name: Developpers
|
||||
description: description
|
||||
backends_groups:
|
||||
ldap:
|
||||
- cn=developpers,ou=group,dc=example,dc=com
|
||||
- cn=developers,ou=group,dc=example,dc=com
|
||||
- cn=users,ou=group,dc=example,dc=com
|
||||
ad:
|
||||
- Domain Users
|
||||
|
@ -22,12 +22,12 @@ admin-lv2:
|
||||
ad:
|
||||
- Domain Users
|
||||
|
||||
developpers:
|
||||
developers:
|
||||
display_name: Developpers
|
||||
description: description
|
||||
backends_groups:
|
||||
ldap:
|
||||
- cn=developpers,ou=group,dc=example,dc=com
|
||||
- cn=developers,ou=group,dc=example,dc=com
|
||||
- cn=users,ou=group,dc=example,dc=com
|
||||
ad:
|
||||
- Domain Users
|
||||
|
@ -17,12 +17,12 @@ admin-lv2:
|
||||
- cn=nagios admins,ou=Group,dc=example,dc=org
|
||||
- cn=users,ou=Group,dc=example,dc=org
|
||||
|
||||
developpers:
|
||||
developers:
|
||||
display_name: Developpers
|
||||
description: Developpers of the system
|
||||
backends_groups:
|
||||
ldap:
|
||||
- cn=developpers,ou=Group,dc=example,dc=org
|
||||
- cn=developers,ou=Group,dc=example,dc=org
|
||||
- cn=users,ou=Group,dc=example,dc=org
|
||||
|
||||
users:
|
||||
|
@ -1,8 +1,16 @@
|
||||
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
|
||||
|
@ -1,243 +0,0 @@
|
||||
#!/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()
|
@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import with_statement
|
||||
@ -6,10 +6,12 @@ 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):
|
||||
|
||||
@ -27,7 +29,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):
|
||||
@ -40,6 +42,7 @@ 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):
|
||||
|
204
tests/test_BackendAD.py
Normal file
204
tests/test_BackendAD.py
Normal file
@ -0,0 +1,204 @@
|
||||
#!/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")
|
@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import with_statement
|
||||
@ -6,12 +6,14 @@ 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',
|
||||
@ -69,16 +71,6 @@ 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)
|
||||
@ -92,7 +84,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):
|
||||
@ -101,7 +93,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:
|
||||
|
@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import with_statement
|
||||
@ -6,13 +6,14 @@ 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',
|
||||
@ -104,7 +105,8 @@ class TestError(object):
|
||||
try:
|
||||
ldapc.simple_bind_s(inv.binddn, inv.bindpassword)
|
||||
except ldap.SERVER_DOWN as e:
|
||||
assert e[0]['info'] == 'TLS: hostname does not match CN in peer certificate'
|
||||
assert e.args[0]['info'] == 'TLS: hostname does not match CN in peer certificate' or \
|
||||
e.args[0]['info'] == '(unknown error code)'
|
||||
else:
|
||||
raise AssertionError("expected an exception")
|
||||
|
||||
@ -128,16 +130,6 @@ 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é')
|
||||
@ -158,12 +150,10 @@ 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')
|
||||
@ -172,29 +162,29 @@ class TestError(object):
|
||||
|
||||
def testAddUser(self):
|
||||
try:
|
||||
inv.del_user(u'test☭')
|
||||
inv.del_user(u'test☭,cn=')
|
||||
except:
|
||||
pass
|
||||
inv = Backend(cfg, cherrypy.log, 'ldap', attr, 'uid')
|
||||
user = {
|
||||
'uid': u'test☭',
|
||||
'sn': 'test',
|
||||
'cn': 'test',
|
||||
'userPassword': 'test',
|
||||
'uid': u'test☭,cn=',
|
||||
'sn': u'test☭',
|
||||
'cn': u'test☭',
|
||||
'userPassword': u'test☭',
|
||||
'uidNumber': '42',
|
||||
'gidNumber': '42',
|
||||
'homeDirectory': '/home/test/'
|
||||
}
|
||||
inv.add_user(user)
|
||||
inv.del_user(u'test☭')
|
||||
inv.del_user(u'test☭,cn=')
|
||||
|
||||
def testModifyUser(self):
|
||||
inv = Backend(cfg, cherrypy.log, 'ldap', attr, 'uid')
|
||||
user = {
|
||||
'uid': u'test☭',
|
||||
'sn': 'test',
|
||||
'cn': 'test',
|
||||
'userPassword': 'test',
|
||||
'sn': u'test☭',
|
||||
'cn': u'test☭',
|
||||
'userPassword': u'test☭',
|
||||
'uidNumber': '42',
|
||||
'gidNumber': '42',
|
||||
'homeDirectory': '/home/test/'
|
||||
@ -206,11 +196,11 @@ class TestError(object):
|
||||
def testAddUserDuplicate(self):
|
||||
inv = Backend(cfg, cherrypy.log, 'ldap', attr, 'uid')
|
||||
user = {
|
||||
'uid': 'test',
|
||||
'sn': 'test',
|
||||
'cn': 'test',
|
||||
'uid': u'test☭',
|
||||
'sn': u'test☭',
|
||||
'cn': u'test☭',
|
||||
'uidNumber': '42',
|
||||
'userPassword': 'test',
|
||||
'userPassword': u'test☭',
|
||||
'gidNumber': '42',
|
||||
'homeDirectory': '/home/test/'
|
||||
}
|
||||
@ -218,17 +208,17 @@ class TestError(object):
|
||||
inv.add_user(user)
|
||||
inv.add_user(user)
|
||||
except UserAlreadyExists:
|
||||
inv.del_user('test')
|
||||
inv.del_user(u'test☭')
|
||||
return
|
||||
else:
|
||||
inv.del_user('test')
|
||||
inv.del_user(u'test☭')
|
||||
raise AssertionError("expected an exception")
|
||||
|
||||
def testDelUserDontExists(self):
|
||||
inv = Backend(cfg, cherrypy.log, 'ldap', attr, 'uid')
|
||||
try:
|
||||
inv.del_user('test')
|
||||
inv.del_user('test')
|
||||
inv.del_user(u'test☭')
|
||||
inv.del_user(u'test☭')
|
||||
except UserDoesntExist:
|
||||
return
|
||||
else:
|
||||
@ -243,10 +233,10 @@ class TestError(object):
|
||||
def testAddUserMissingMustattribute(self):
|
||||
inv = Backend(cfg, cherrypy.log, 'ldap', attr, 'uid')
|
||||
user = {
|
||||
'uid': 'test',
|
||||
'sn': 'test',
|
||||
'cn': 'test',
|
||||
'userPassword': 'test',
|
||||
'uid': u'test☭',
|
||||
'sn': u'test☭',
|
||||
'cn': u'test☭',
|
||||
'userPassword': u'test☭',
|
||||
'gidNumber': '42',
|
||||
'homeDirectory': '/home/test/'
|
||||
}
|
||||
@ -255,5 +245,5 @@ class TestError(object):
|
||||
except ldap.OBJECT_CLASS_VIOLATION:
|
||||
return
|
||||
else:
|
||||
inv.del_user('test')
|
||||
inv.del_user(u'test☭')
|
||||
raise AssertionError("expected an exception")
|
||||
|
@ -1,8 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import with_statement
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import pytest
|
||||
import sys
|
||||
@ -10,19 +9,44 @@ 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"""
|
||||
@ -56,19 +80,42 @@ 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()
|
||||
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)
|
||||
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)
|
||||
|
||||
class BadModule():
|
||||
pass
|
||||
@ -105,7 +152,7 @@ class TestError(object):
|
||||
def testLog(self):
|
||||
app = LdapCherry()
|
||||
cfg = { 'global' : {}}
|
||||
for t in ['none', 'file', 'syslog']:
|
||||
for t in ['none', 'file', 'syslog', 'stdout']:
|
||||
cfg['global']['log.access_handler']=t
|
||||
cfg['global']['log.error_handler']=t
|
||||
app._set_access_log(cfg, logging.DEBUG)
|
||||
@ -159,10 +206,10 @@ class TestError(object):
|
||||
loadconf('./tests/cfg/ldapcherry_test.ini', app)
|
||||
app.auth_mode = 'or'
|
||||
try:
|
||||
app.login('jwatsoné', 'passwordwatsoné')
|
||||
app.login(u'jwatsoné', u'passwordwatsoné')
|
||||
except cherrypy.HTTPRedirect as e:
|
||||
expected = 'http://127.0.0.1:8080/'
|
||||
assert e[0][0] == expected
|
||||
assert e.urls[0] == expected
|
||||
else:
|
||||
raise AssertionError("expected an exception")
|
||||
|
||||
@ -171,10 +218,10 @@ class TestError(object):
|
||||
loadconf('./tests/cfg/ldapcherry_test.ini', app)
|
||||
app.auth_mode = 'or'
|
||||
try:
|
||||
app.login('jwatsoné', 'wrongPasswordé')
|
||||
app.login(u'jwatsoné', u'wrongPasswordé')
|
||||
except cherrypy.HTTPRedirect as e:
|
||||
expected = 'http://127.0.0.1:8080/signin'
|
||||
assert e[0][0] == expected
|
||||
assert e.urls[0] == expected
|
||||
else:
|
||||
raise AssertionError("expected an exception")
|
||||
|
||||
@ -246,6 +293,7 @@ class TestError(object):
|
||||
app._modify(modify_form)
|
||||
app._deleteuser('test')
|
||||
|
||||
@slow_disabled
|
||||
def testHtml(self):
|
||||
app = LdapCherry()
|
||||
loadconf('./tests/cfg/ldapcherry_test.ini', app)
|
||||
@ -265,8 +313,14 @@ 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)
|
||||
@ -285,6 +339,40 @@ 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)
|
||||
@ -297,4 +385,3 @@ class TestError(object):
|
||||
get_loglevel('alert') is logging.CRITICAL and \
|
||||
get_loglevel('emergency') is logging.CRITICAL and \
|
||||
get_loglevel('notalevel') is logging.INFO
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import with_statement
|
||||
@ -6,16 +6,17 @@ 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):
|
||||
@ -64,7 +65,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):
|
||||
@ -78,7 +79,50 @@ class TestError(object):
|
||||
|
||||
def testNested(self):
|
||||
inv = Roles('./tests/cfg/nested.yml')
|
||||
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'}}
|
||||
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'
|
||||
}
|
||||
}
|
||||
assert expected == inv.flatten
|
||||
|
||||
def testGetGroupMissingRole(self):
|
||||
@ -108,13 +152,13 @@ class TestError(object):
|
||||
def testGetAllRoles(self):
|
||||
inv = Roles('./tests/cfg/roles.yml')
|
||||
res = inv.get_allroles()
|
||||
expected = ['developpers', 'admin-lv3', 'admin-lv2', 'users']
|
||||
expected = ['developers', '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):
|
||||
@ -143,9 +187,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=developpers,ou=group,dc=example,dc=com',
|
||||
'cn=developers,ou=group,dc=example,dc=com',
|
||||
],
|
||||
'toto': ['not a group'],
|
||||
}
|
||||
expected = {'unusedgroups': {'toto': Set(['not a group']), 'ad': Set(['Domain Users 2'])}, 'roles': Set(['developpers', 'admin-lv2', 'users'])}
|
||||
expected = {'unusedgroups': {'toto': set(['not a group']), 'ad': set(['Domain Users 2'])}, 'roles': set(['developers', 'admin-lv2', 'users'])}
|
||||
assert inv.get_roles(groups) == expected
|
||||
|
@ -1,19 +1,27 @@
|
||||
#!/bin/sh
|
||||
|
||||
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 -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
|
||||
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
|
||||
|
||||
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
|
||||
if grep -q '127.0.0.1' /etc/hosts && ! grep -q 'ldap.ldapcherry.org' /etc/hosts
|
||||
then
|
||||
sed -i "s/\(127.0.0.1.*\)/\1 ldap.ldapcherry.org ad.ldapcherry.org ldap.dnscherry.org/" /etc/hosts
|
||||
else
|
||||
@ -24,15 +32,29 @@ 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
|
||||
sambadns=NONE
|
||||
sambadns=SAMBA_INTERNAL
|
||||
targetdir=/var/lib/samba/
|
||||
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} \
|
||||
@ -44,18 +66,20 @@ 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
|
||||
|
||||
sleep 5
|
||||
|
||||
/etc/init.d/samba stop
|
||||
/etc/init.d/smbd stop
|
||||
/etc/init.d/nmbd stop
|
||||
systemctl restart samba-ad-dc
|
||||
/etc/init.d/samba-ad-dc restart
|
||||
|
||||
cat /var/log/samba/*
|
||||
|
||||
sleep 5
|
||||
|
||||
netstat -apn
|
||||
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
|
||||
|
@ -103,9 +103,22 @@ cn: users
|
||||
description: Basic Users
|
||||
member: cn=Sheri Smith,ou=people,dc=example,dc=org
|
||||
|
||||
dn: cn=developpers,ou=group,dc=example,dc=org
|
||||
dn: cn=developers,ou=group,dc=example,dc=org
|
||||
objectclass: groupofnames
|
||||
cn: developpers
|
||||
cn: developers
|
||||
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
|
||||
|
@ -1,4 +1,5 @@
|
||||
# AUTO-GENERATED FILE - DO NOT EDIT!! Use ldapmodify.
|
||||
# CRC32 7a6099db
|
||||
dn: cn=config
|
||||
objectClass: olcGlobal
|
||||
cn: config
|
||||
@ -7,12 +8,12 @@ olcLogLevel: none
|
||||
olcPidFile: /var/run/slapd/slapd.pid
|
||||
olcToolThreads: 1
|
||||
structuralObjectClass: olcGlobal
|
||||
entryUUID: 2964261c-6754-1033-8d48-1703270f04bd
|
||||
entryUUID: 38579c70-750a-103e-8489-9578878139e2
|
||||
creatorsName: cn=config
|
||||
createTimestamp: 20140503211805Z
|
||||
entryCSN: 20140503211805.658522Z#000000#000#000000
|
||||
createTimestamp: 20240312221838Z
|
||||
entryCSN: 20240312221838.644900Z#000000#000#000000
|
||||
modifiersName: cn=config
|
||||
modifyTimestamp: 20140503211805Z
|
||||
modifyTimestamp: 20240312221838Z
|
||||
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
|
||||
|
@ -1,14 +1,14 @@
|
||||
# AUTO-GENERATED FILE - DO NOT EDIT!! Use ldapmodify.
|
||||
# CRC32 d8758c75
|
||||
# CRC32 baf86138
|
||||
dn: cn=module{0}
|
||||
objectClass: olcModuleList
|
||||
cn: module{0}
|
||||
olcModulePath: /usr/lib/ldap
|
||||
olcModuleLoad: {0}back_hdb
|
||||
olcModuleLoad: {0}back_mdb
|
||||
structuralObjectClass: olcModuleList
|
||||
entryUUID: 2964fd58-6754-1033-8d50-1703270f04bd
|
||||
entryUUID: 3857cd3a-750a-103e-8491-9578878139e2
|
||||
creatorsName: cn=admin,cn=config
|
||||
createTimestamp: 20140503211805Z
|
||||
entryCSN: 20140503211805.664149Z#000000#000#000000
|
||||
createTimestamp: 20240312221838Z
|
||||
entryCSN: 20240312221838.646168Z#000000#000#000000
|
||||
modifiersName: cn=admin,cn=config
|
||||
modifyTimestamp: 20140503211805Z
|
||||
modifyTimestamp: 20240312221838Z
|
||||
|
@ -1,12 +1,12 @@
|
||||
# AUTO-GENERATED FILE - DO NOT EDIT!! Use ldapmodify.
|
||||
# CRC32 db8c9607
|
||||
# CRC32 fff51a11
|
||||
dn: cn=schema
|
||||
objectClass: olcSchemaConfig
|
||||
cn: schema
|
||||
structuralObjectClass: olcSchemaConfig
|
||||
entryUUID: 29644a02-6754-1033-8d4b-1703270f04bd
|
||||
entryUUID: 3857a42c-750a-103e-848c-9578878139e2
|
||||
creatorsName: cn=admin,cn=config
|
||||
createTimestamp: 20140503211805Z
|
||||
entryCSN: 20140503211805.659557Z#000000#000#000000
|
||||
createTimestamp: 20240312221838Z
|
||||
entryCSN: 20240312221838.645118Z#000000#000#000000
|
||||
modifiersName: cn=admin,cn=config
|
||||
modifyTimestamp: 20140503211805Z
|
||||
modifyTimestamp: 20240312221838Z
|
||||
|
@ -1,12 +0,0 @@
|
||||
# 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
|
@ -1,5 +1,5 @@
|
||||
# AUTO-GENERATED FILE - DO NOT EDIT!! Use ldapmodify.
|
||||
# CRC32 511a173e
|
||||
# CRC32 e8f0a13c
|
||||
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: 296430d0-6754-1033-8d49-1703270f04bd
|
||||
entryUUID: 38579ebe-750a-103e-848a-9578878139e2
|
||||
creatorsName: cn=config
|
||||
createTimestamp: 20140503211805Z
|
||||
entryCSN: 20140503211805.658911Z#000000#000#000000
|
||||
createTimestamp: 20240312221838Z
|
||||
entryCSN: 20240312221838.644978Z#000000#000#000000
|
||||
modifiersName: cn=config
|
||||
modifyTimestamp: 20140503211805Z
|
||||
modifyTimestamp: 20240312221838Z
|
||||
|
@ -1,5 +1,5 @@
|
||||
# AUTO-GENERATED FILE - DO NOT EDIT!! Use ldapmodify.
|
||||
# CRC32 fc130d0e
|
||||
# CRC32 caed0a01
|
||||
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: 29644142-6754-1033-8d4a-1703270f04bd
|
||||
entryUUID: 3857a274-750a-103e-848b-9578878139e2
|
||||
creatorsName: cn=config
|
||||
createTimestamp: 20140503211805Z
|
||||
entryCSN: 20140503211805.659332Z#000000#000#000000
|
||||
createTimestamp: 20240312221838Z
|
||||
entryCSN: 20240312221838.645074Z#000000#000#000000
|
||||
modifiersName: cn=config
|
||||
modifyTimestamp: 20140503211805Z
|
||||
modifyTimestamp: 20240312221838Z
|
||||
|
@ -1,7 +1,9 @@
|
||||
dn: olcDatabase={1}hdb
|
||||
# AUTO-GENERATED FILE - DO NOT EDIT!! Use ldapmodify.
|
||||
# CRC32 91d29b33
|
||||
dn: olcDatabase={1}mdb
|
||||
objectClass: olcDatabaseConfig
|
||||
objectClass: olcHdbConfig
|
||||
olcDatabase: {1}hdb
|
||||
objectClass: olcMdbConfig
|
||||
olcDatabase: {1}mdb
|
||||
olcDbDirectory: /var/lib/ldap
|
||||
olcSuffix: dc=example,dc=org
|
||||
olcAccess: {0}to attrs=userPassword,shadowLastChange by self write by anonymou
|
||||
@ -13,15 +15,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
|
||||
structuralObjectClass: olcHdbConfig
|
||||
entryUUID: 2965af5a-6754-1033-8d52-1703270f04bd
|
||||
olcDbIndex: cn,uid eq
|
||||
olcDbIndex: uidNumber,gidNumber eq
|
||||
olcDbIndex: member,memberUid eq
|
||||
olcDbMaxSize: 1073741824
|
||||
structuralObjectClass: olcMdbConfig
|
||||
entryUUID: 3857d7ee-750a-103e-8492-9578878139e2
|
||||
creatorsName: cn=admin,cn=config
|
||||
createTimestamp: 20140503211805Z
|
||||
entryCSN: 20140503211805.668708Z#000000#000#000000
|
||||
createTimestamp: 20240312221838Z
|
||||
entryCSN: 20240312221838.646442Z#000000#000#000000
|
||||
modifiersName: cn=admin,cn=config
|
||||
modifyTimestamp: 20140503211805Z
|
||||
modifyTimestamp: 20240312221838Z
|
132
tests/test_env/etc/ldapcherry/attributes.yml
Normal file
132
tests/test_env/etc/ldapcherry/attributes.yml
Normal file
@ -0,0 +1,132 @@
|
||||
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
|
125
tests/test_env/etc/ldapcherry/ldapcherry.ini
Normal file
125
tests/test_env/etc/ldapcherry/ldapcherry.ini
Normal file
@ -0,0 +1,125 @@
|
||||
# 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/'
|
36
tests/test_env/etc/ldapcherry/roles.yml
Normal file
36
tests/test_env/etc/ldapcherry/roles.yml
Normal file
@ -0,0 +1,36 @@
|
||||
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
|
Loading…
x
Reference in New Issue
Block a user