Compare commits
347 Commits
Author | SHA1 | Date |
---|---|---|
Carpentier Pierre-Francois | dd331f948c | |
Carpentier Pierre-Francois | a2e985576b | |
kakwa | 6a2e6e56d0 | |
kakwa | 5d8fc08f5b | |
kakwa | 59855f0090 | |
kakwa | d1215bc56f | |
kakwa | ef11e6a7e3 | |
kakwa | 3596e14249 | |
kakwa | b94713acb6 | |
kakwa | de16df475f | |
kakwa | 242c9ab96e | |
kakwa | 48dbff983d | |
kakwa | 6413b782dd | |
Carpentier Pierre-Francois | 55c49b4eff | |
Carpentier Pierre-Francois | 53a4b6fd5e | |
dependabot[bot] | 0ccbe95d9a | |
Carpentier Pierre-Francois | 4da050236d | |
Carpentier Pierre-Francois | 135699bd48 | |
sohalt | 1549a172f7 | |
Boris Rybalkin | e2ab3e85d8 | |
Carpentier Pierre-Francois | b5907ce340 | |
Steffen Brandemann | 791895d4c3 | |
AndrewCz | b12add4a0f | |
Carpentier Pierre-Francois | 856157af79 | |
Carpentier Pierre-Francois | b5e7cb6a44 | |
smacz | d61c89460e | |
Johann Queuniet | 0a96ca61d5 | |
Carpentier Pierre-Francois | 3b58f1464e | |
Johann Queuniet | 50c6259035 | |
kakwa | 245bafb01c | |
kakwa | 5ee8a74040 | |
kakwa | 96acda7aa6 | |
kakwa | 30c28c5feb | |
kakwa | dc60300a29 | |
kakwa | 882a303474 | |
kakwa | d831b09293 | |
kakwa | 7ac7118c9a | |
kakwa | d690bbdc41 | |
kakwa | 73c02ccff4 | |
kakwa | 799ca2403f | |
kakwa | bbafafae60 | |
kakwa | 0cf5483785 | |
kakwa | df2746b996 | |
kakwa | e6bcf9d97d | |
kakwa | 57bcaaed66 | |
kakwa | b68214022c | |
kakwa | 932e7a8b40 | |
kakwa | abf1454278 | |
kakwa | 0793361d90 | |
kakwa | f824790849 | |
kakwa | 7390c931b9 | |
kakwa | 4a8aa1c655 | |
kakwa | e50df5dde3 | |
kakwa | fba2d32b44 | |
kakwa | 7a8468f8b1 | |
kakwa | 9d0d321e9b | |
kakwa | c5536bdc56 | |
kakwa | abfce4803a | |
kakwa | 046afbbe29 | |
kakwa | 98fca30fba | |
kakwa | f13961790f | |
kakwa | a56c491ee1 | |
kakwa | 02357d886a | |
kakwa | 263e6be547 | |
kakwa | 05aace0e9d | |
kakwa | baa3430e63 | |
kakwa | 90ff69586b | |
kakwa | 79983c078f | |
kakwa | 10747cff93 | |
kakwa | 979d4eeda8 | |
kakwa | fb6b0a5d31 | |
kakwa | bbfe96d4f7 | |
kakwa | b9437abefb | |
kakwa | 60d57d8530 | |
kakwa | 8c0bf94904 | |
kakwa | 42759f1cc4 | |
kakwa | 18fdeb483e | |
kakwa | 12c511b537 | |
kakwa | d25ceef2d3 | |
kakwa | 8b48a1f024 | |
kakwa | 7430af5ffc | |
kakwa | bc0f3aceb5 | |
kakwa | 9989f97091 | |
kakwa | fc98b1bd70 | |
kakwa | ab9cd664ec | |
kakwa | 13bfbdcbbc | |
kakwa | 70140f966a | |
kakwa | 8bd4afb235 | |
kakwa | 2a2864a306 | |
kakwa | c3feafdb2c | |
kakwa | 86fb6c1dd2 | |
kakwa | 9f6af580cd | |
kakwa | 5bdcc5522a | |
kakwa | c81429a870 | |
kakwa | 3d6e24eb73 | |
kakwa | be598b0129 | |
kakwa | ccc252965d | |
kakwa | 3beedc8d4d | |
kakwa | 74dc6c5894 | |
kakwa | 69526610f3 | |
kakwa | 921a0820f4 | |
kakwa | 2df56d2de2 | |
Carpentier Pierre-Francois | 5b0c72a572 | |
John Thiltges | c6cce54d5f | |
Carpentier Pierre-Francois | 1f79648d57 | |
Carpentier Pierre-Francois | 636400b75f | |
John Thiltges | 6f98076281 | |
Carpentier Pierre-Francois | 1ed654c91b | |
Carpentier Pierre-Francois | c329e53811 | |
Carpentier Pierre-Francois | 05e3a0d665 | |
kakwa | 4bd6314b3b | |
kakwa | c5dae7039a | |
kakwa | ca1f78173f | |
kakwa | 9ed6007b02 | |
kakwa | 4d696a29ef | |
kakwa | 45d64120ae | |
kakwa | 00a4d22dd9 | |
kakwa | 32c513f96e | |
kakwa | 7019cc2348 | |
kakwa | a404cf0b39 | |
kakwa | 9649803dd6 | |
kakwa | eecccac106 | |
kakwa | f357adcd9a | |
kakwa | e7998ced78 | |
kakwa | 8270988ed4 | |
kakwa | 2e2453f309 | |
kakwa | bbb13454bf | |
kakwa | 3378822d2e | |
kakwa | 6e526b6f15 | |
kakwa | 5b1803cb05 | |
kakwa | de5f760c37 | |
kakwa | a33a46e8b8 | |
kakwa | eb36830845 | |
kakwa | 3fd6dcee82 | |
kakwa | 55ce2bec5e | |
kakwa | e02a1a7f28 | |
kakwa | f9a3051328 | |
kakwa | e4effc64ec | |
Carpentier Pierre-Francois | b3a361afee | |
kakwa | a802ce772a | |
kakwa | 3a1966324d | |
kakwa | 819e575a28 | |
Carpentier Pierre-Francois | 12bb597903 | |
Carpentier Pierre-Francois | 7afe6c0ca7 | |
Stan Rudenko | e1a27aa0a7 | |
Stan Rudenko | f7f72c7e11 | |
kakwa | e37b88dbda | |
kakwa | d7303da85f | |
kakwa | 44024dbd02 | |
kakwa | 5a45a24055 | |
kakwa | 0a4db74f1f | |
kakwa | f747252585 | |
kakwa | 7f00264e32 | |
kakwa | d820cceeb6 | |
kakwa | d4235bc33c | |
kakwa | f21122b219 | |
kakwa | 01aaf476c1 | |
kakwa | fec09b1543 | |
kakwa | cf97f01245 | |
kakwa | 5ddd9a6bbf | |
kakwa | 07a60823ad | |
kakwa | 5ff62f0a8c | |
kakwa | a84ee528aa | |
pcarpent | 1aa4a0bd64 | |
pcarpent | 37925b196b | |
kakwa | f863b230dd | |
kakwa | dcdc260f33 | |
kakwa | 2e98e380df | |
kakwa | 52557afa6a | |
kakwa | f967630043 | |
kakwa | 9fb32f11be | |
kakwa | 64e0bba74c | |
kakwa | ff950dd88b | |
kakwa | 9367bc3288 | |
kakwa | d1ec945fe2 | |
kakwa | 0f28309344 | |
kakwa | 0263d52edf | |
kakwa | 127b106082 | |
kakwa | 6fd849fa50 | |
kakwa | ca974ab801 | |
kakwa | 9b3d232503 | |
kakwa | 1ccb4bf732 | |
kakwa | d484ee1ed0 | |
kakwa | bb05934284 | |
kakwa | cfe31ae62b | |
kakwa | 320f57ab76 | |
kakwa | 6ef44b9b2e | |
kakwa | d9973ddf36 | |
kakwa | 408f75c449 | |
kakwa | abf3d5dea9 | |
kakwa | b6fd601801 | |
kakwa | 50f573b9bd | |
kakwa | 14afde33b5 | |
kakwa | f3fabe502e | |
kakwa | c2f6b95fb0 | |
kakwa | 0beac119f9 | |
kakwa | 91a1f3e7e3 | |
kakwa | 59b9f4d3b7 | |
kakwa | 6c3fb4975d | |
kakwa | 9600f47e13 | |
kakwa | 2f90d0a2fa | |
kakwa | b34ac7be0a | |
kakwa | 685031ef15 | |
kakwa | 80fa310f37 | |
kakwa | ebc5b69374 | |
kakwa | c64bb11504 | |
kakwa | 1b7473d01c | |
kakwa | ec05d96b31 | |
kakwa | bdc86a6d8f | |
kakwa | 8b0e68d9db | |
kakwa | 5944b81aed | |
kakwa | 655ccabd79 | |
kakwa | ce09b60158 | |
kakwa | baee15c40f | |
kakwa | 3b6cf61b93 | |
Carpentier Pierre-Francois | caef6a889e | |
kakwa | 9edc7e545a | |
kakwa | 28479f7202 | |
kakwa | 9a5aa03de6 | |
kakwa | 43e4231be8 | |
kakwa | e45c0e862e | |
Carpentier Pierre-Francois | 8c50313b5a | |
Yuusuke KOUNOIKE | 268c8f935b | |
kakwa | c67969e2c1 | |
kakwa | 1fea551853 | |
kakwa | 44ebcc8f3a | |
kakwa | cf91e69eba | |
kakwa | 5f1074bb26 | |
kakwa | 90d92009e3 | |
kakwa | 569aaac5a6 | |
kakwa | ffac99994c | |
kakwa | ab81f4258e | |
kakwa | f7bbff4cec | |
kakwa | b8a65a44b6 | |
kakwa | d8631da7ba | |
kakwa | 9a882d3626 | |
kakwa | 921eef4b04 | |
kakwa | c969e730c4 | |
kakwa | c320fa9da6 | |
Carpentier Pierre-Francois | 88002cfa2c | |
kakwa | 3a6bc3a55e | |
kakwa | e981451431 | |
kakwa | af01f9bae3 | |
kakwa | fbd602fc13 | |
kakwa | 621445f1b6 | |
kakwa | d34b1b6188 | |
kakwa | 639554d539 | |
kakwa | 00db6ae4bc | |
kakwa | 13f9f73b33 | |
kakwa | 4f80ef6d7c | |
Carpentier Pierre-Francois | 5caedc91c3 | |
kakwa | 3872c49630 | |
kakwa | c71645ac99 | |
kakwa | 4e5c361e5e | |
kakwa | 97e0ae8cc3 | |
kakwa | a4fbbfc73a | |
Carpentier Pierre-Francois | e490f3f4e3 | |
kakwa | 71c6f78f74 | |
kakwa | 3fc9e5e68b | |
kakwa | 97769f62ff | |
kakwa | 576bc97d59 | |
kakwa | 6501096643 | |
kakwa | baeaf565cf | |
kakwa | eaa05e0877 | |
kakwa | dcf01e929c | |
kakwa | f23a77e6f2 | |
kakwa | a085868b2b | |
kakwa | a74346f7a7 | |
kakwa | 55cd8529c3 | |
kakwa | 7a1fb0dc8c | |
kakwa | 27e05ac7f2 | |
kakwa | 451c59e875 | |
kakwa | fdba64f9da | |
kakwa | 8833fe6df6 | |
kakwa | 93cd8a40f6 | |
kakwa | 7d18934a85 | |
kakwa | 396f5c2b65 | |
kakwa | 2451b2efdd | |
kakwa | bb8aa3f43c | |
kakwa | f52e9df902 | |
kakwa | 869ddd6549 | |
kakwa | 14df477366 | |
kakwa | 9c1dc8112a | |
kakwa | c397afab4a | |
kakwa | 1d97e01540 | |
kakwa | 1985408324 | |
kakwa | 27089f68ef | |
kakwa | 807ac93956 | |
kakwa | 9ecd97a8d0 | |
kakwa | 1fd76a9485 | |
kakwa | 2992cac1d7 | |
kakwa | 3402ba9613 | |
kakwa | 53676db341 | |
Carpentier Pierre-Francois | d32b0625ed | |
kakwa | 9fa2dc411c | |
kakwa | e0e682083c | |
kakwa | ffd67a4ff0 | |
kakwa | aa7db36efc | |
kakwa | 9074501155 | |
kakwa | 3f9e76908e | |
kakwa | 6cf9ab71aa | |
kakwa | 61df2094c4 | |
kakwa | f61c7908cb | |
kakwa | 94b35655dc | |
kakwa | 5d195eadb0 | |
kakwa | ffe7ab7a07 | |
Carpentier Pierre-Francois | 90b39b61de | |
kakwa | e4f1b92a69 | |
kakwa | 937bda8cfa | |
kakwa | ecbf112677 | |
kakwa | 64f34e3466 | |
kakwa | a8af29692e | |
kakwa | 5561a9f42b | |
kakwa | 76d8533991 | |
kakwa | efcaad54fc | |
kakwa | 8c0cbaac3b | |
kakwa | af4936f157 | |
kakwa | e8afdd553e | |
kakwa | 3ab5a2bd41 | |
Carpentier Pierre-Francois | 96a88a1453 | |
kakwa | 6ee0ff0354 | |
kakwa | 383c68f8b8 | |
kakwa | ff08e09598 | |
kakwa | 5d40cad342 | |
kakwa | 69700d0ce9 | |
kakwa | 3391ed9704 | |
kakwa | 49af82d308 | |
kakwa | 9ec7a3dfbe | |
kakwa | f2c1a6af44 | |
kakwa | 5459830269 | |
kakwa | 4e5591db7a | |
kakwa | b9903e62ff | |
kakwa | 95d6a0e6a4 | |
kakwa | e5d97cf8ff | |
kakwa | 27fca43ac6 | |
kakwa | e3fe0b4bfb | |
kakwa | ac0a3473f7 | |
kakwa | 0914e141ec | |
kakwa | 23b3dbfbe3 | |
kakwa | 2a4815e142 | |
kakwa | 7d55cb2d14 | |
kakwa | f0d43d9460 | |
kakwa | f6b1128274 | |
kakwa | 15740a8967 | |
kakwa | e067f6a60c | |
kakwa | 752ed8c29c | |
kakwa | 9b6ebd488f |
|
@ -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
|
|
@ -21,6 +21,8 @@ var/
|
|||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
.*.swp
|
||||
.eggs/
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
|
@ -52,3 +54,4 @@ coverage.xml
|
|||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
ldapcherry-dev.ini
|
||||
|
|
51
.travis.yml
51
.travis.yml
|
@ -1,35 +1,40 @@
|
|||
env:
|
||||
- TRAVIS="yes"
|
||||
sudo: required
|
||||
dist: xenial
|
||||
language: python
|
||||
|
||||
before_install:
|
||||
- curl https://ftp-master.debian.org/keys/archive-key-7.0.asc | sudo apt-key add -
|
||||
- echo "deb http://http.debian.net/debian wheezy-backports main" | sudo tee -a /etc/apt/sources.list
|
||||
- echo "deb http://http.debian.net/debian wheezy main" | sudo tee -a /etc/apt/sources.list
|
||||
- sudo /sbin/ifconfig
|
||||
- sudo apt-get update -qq
|
||||
- sudo rm /etc/dpkg/dpkg.cfg.d/multiarch
|
||||
- sudo ./tests/test_env/deploy.sh
|
||||
- '[ "$TEST_PEP8" == "1" ] || sudo ./tests/test_env/deploy.sh'
|
||||
|
||||
language: python
|
||||
python:
|
||||
- "2.7"
|
||||
# - "3.2"
|
||||
# - "3.3"
|
||||
# command to install dependencies
|
||||
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
|
||||
- "if [[ $TEST_HIREDIS == '1' ]]; then pip install hiredis; fi"
|
||||
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:
|
||||
|
|
|
@ -0,0 +1,162 @@
|
|||
Dev
|
||||
***
|
||||
|
||||
Version 1.1.1
|
||||
*************
|
||||
|
||||
* [fix ] fix double escaping issues introduced in 1.0.0
|
||||
* [fix ] fix missing url escaping in links with querystring parameters (delete and modify page mostly)
|
||||
* [fix ] fix log level not being honored in the backends
|
||||
* [impr] clarify the role of 'key: True' of attributes.yml in the documentation
|
||||
* [impr] add a few more comments in the .ini file to explain better the \*_filter_tmpl and group_attr parameters
|
||||
* [impr] add debug log to help debug ldap search filters
|
||||
|
||||
Version 1.1.0
|
||||
*************
|
||||
|
||||
* [feat] add stdout as a valid log method (useful when running with docker)
|
||||
|
||||
Version 1.0.1
|
||||
*************
|
||||
|
||||
* [fix ] fix error handling when adding user that already exists
|
||||
|
||||
Version 1.0.0
|
||||
*************
|
||||
|
||||
* [sec ] fix XSS injection in the url redirect in the login page (thanks to jthiltges)
|
||||
* [fix ] fix configuration consistency check for attribute file (error if a given backend is not declared in main .ini file but in attributes)
|
||||
* [fix ] remove a few deprecation warnings
|
||||
* [fix ] fix potential issue with group names containing non-ascii characters
|
||||
* [feat] support for python 3
|
||||
* [feat] support for python-ldap 3.X.X
|
||||
* [impr] better log error message if inconsistency between role, attribute and main .ini file for backends
|
||||
* [impr] more systematic use of html and url escaping in the html rendering to prevent against content injection (thanks to jthiltges)
|
||||
* [impr] more testing for various versions of python and python-ldap
|
||||
|
||||
Version 0.5.2
|
||||
*************
|
||||
|
||||
* [fix ] regression in 0.5.1, setup.py could not work without the dependencies already installed
|
||||
|
||||
Version 0.5.1
|
||||
*************
|
||||
|
||||
* [impr] cleaner align of labels (input-group-addon width)
|
||||
|
||||
Version 0.5.0
|
||||
*************
|
||||
|
||||
* [feat] add handling of textfielf (thanks to Stan Rudenko)
|
||||
* [fix ] fix ldap.group_attr.<attr> handling with attr different than dn (uid for example)
|
||||
* [impr] removing duplicate option in form select fields
|
||||
* [impr] add dynamic resizing to align form labels (input-group-addon width)
|
||||
|
||||
Version 0.4.0
|
||||
*************
|
||||
|
||||
* [impr] add unit test for multi backend setup
|
||||
* [fix ] notify on add in case if user is already in one backend
|
||||
* [fix ] notify on modify in case if user is not in every backend
|
||||
* [fix ] delete user in all backends even if it doesn't exist in one of them
|
||||
* [fix ] fix bad handling of = or & in passwords in ppolicy checker (js)
|
||||
* [fix ] fix many encoding errors in AD backend
|
||||
* [impr] add unit tests on AD backend
|
||||
* [impr] display the admin result page if searching as admin in navbar form
|
||||
|
||||
Version 0.3.5
|
||||
*************
|
||||
|
||||
* [fix ] fix error in ad backend when self modifying password
|
||||
|
||||
Version 0.3.4
|
||||
*************
|
||||
|
||||
* [impr] focus on first field for all forms
|
||||
* [impr] add icon in navbar to return on /
|
||||
|
||||
Version 0.3.3
|
||||
*************
|
||||
|
||||
* [fix ] add html escape for fields display
|
||||
* [impr] disable minimum search lenght for admin search
|
||||
|
||||
Version 0.3.2
|
||||
*************
|
||||
|
||||
* [fix ] fix many encoding errors on login and password
|
||||
|
||||
Version 0.3.1
|
||||
*************
|
||||
|
||||
* [fix ] better and "html" correct display of user's attributes
|
||||
|
||||
Version 0.3.0
|
||||
*************
|
||||
|
||||
* [impr] add focus on first input of forms
|
||||
* [impr] add 404 (default) handler and its error page
|
||||
* [feat] add a -D switch to ldapcherryd which enables logging to stderr in foreground
|
||||
* [feat] print user's attribute on index page
|
||||
|
||||
Version 0.2.5
|
||||
*************
|
||||
|
||||
* [fix ] encoding issues for passwords and cn in ad backend
|
||||
* [fix ] fix minimum lenght of 3 in search (no empty search, and server side check)
|
||||
* [impr] disable form autofilling (annoying in firefox), kind of a hack...
|
||||
|
||||
Version 0.2.4
|
||||
*************
|
||||
|
||||
* [fix ] use post instead of get for ppolicy validation
|
||||
* [fix ] impose a minimum lenght of 3 for searches
|
||||
* [fix ] fix the maxuid in uid calculation in js
|
||||
|
||||
Version 0.2.3
|
||||
*************
|
||||
|
||||
* [fix ] notifications missing in case of multiple notification waiting to be displayed
|
||||
* [fix ] password handling for Active Directory backend
|
||||
* [fix ] default attribute value handling
|
||||
* [fix ] corrections on exemple configuration
|
||||
* [impr] explicite mandatory attributes for Active Directory backend
|
||||
|
||||
Version 0.2.2
|
||||
*************
|
||||
|
||||
* [fix ] fix pypi release
|
||||
* [impr] better error/log messages
|
||||
|
||||
Version 0.2.1
|
||||
*************
|
||||
|
||||
* [fix ] fix doc
|
||||
|
||||
Version 0.2.0
|
||||
*************
|
||||
|
||||
|
||||
* [feat] custom error messages for ppolicy error in forms
|
||||
* [feat] add visual notifications after actions
|
||||
* [impr] code reorganization
|
||||
* [impr] better unit tests on the demo backend
|
||||
* [impr] better unit tests on authentication
|
||||
|
||||
Version 0.1.0
|
||||
*************
|
||||
|
||||
* [feat] add demo backend
|
||||
* [feat] add custom javascript hook
|
||||
* [feat] add documentation for backends
|
||||
* [feat] add the Active Directory backend
|
||||
* [feat] add display name parameter for backends
|
||||
* [fix ] fix many encoding error in LDAP backend
|
||||
* [fix ] fix dn renaming of an entry in LDAP backend
|
||||
* [impr] turn-off configuration monitoring
|
||||
* [impr] better exception handling and debugging logs
|
||||
|
||||
Version 0.0.1
|
||||
*************
|
||||
|
||||
* [misc] first release
|
|
@ -0,0 +1,7 @@
|
|||
include *.rst
|
||||
graft conf
|
||||
graft docs
|
||||
graft goodies
|
||||
graft resources
|
||||
graft tests
|
||||
include LICENSE
|
146
README.rst
146
README.rst
|
@ -2,31 +2,39 @@
|
|||
LdapCherry
|
||||
**************
|
||||
|
||||
.. image:: https://raw.githubusercontent.com/kakwa/ldapcherry/master/resources/static/img/apple-touch-icon-72-precomposed.png
|
||||
|
||||
Nice and simple application to manage users and groups in multiple directory services.
|
||||
|
||||
----
|
||||
|
||||
:Doc: `ldapcherry documentation on ReadTheDoc <http://ldapcherry.readthedocs.org/en/latest/>`_
|
||||
:Dev: `ldapcherry code on GitHub <https://github.com/kakwa/ldapcherry>`_
|
||||
:PyPI: `ldapcherry package on Pypi <http://pypi.python.org/pypi/ldapcherry>`_
|
||||
:License: MIT
|
||||
:Author: Pierre-Francois Carpentier - copyright © 2015
|
||||
|
||||
----
|
||||
|
||||
.. image:: https://travis-ci.org/kakwa/ldapcherry.svg?branch=master
|
||||
:target: https://travis-ci.org/kakwa/ldapcherry
|
||||
.. 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://coveralls.io/repos/kakwa/ldapcherry/badge.svg
|
||||
:target: https://coveralls.io/r/kakwa/ldapcherry
|
||||
.. image:: https://img.shields.io/pypi/v/ldapcherry.svg
|
||||
:target: https://pypi.python.org/pypi/ldapcherry
|
||||
:alt: PyPI version
|
||||
|
||||
.. image:: https://readthedocs.org/projects/ldapcherry/badge/?version=latest
|
||||
:target: http://ldapcherry.readthedocs.org/en/latest/?badge=latest
|
||||
:alt: Documentation Status
|
||||
|
||||
----
|
||||
|
||||
:Doc: `LdapCherry documentation on ReadTheDoc <http://ldapcherry.readthedocs.org/en/latest/>`_
|
||||
:Dev: `LdapCherry source code on GitHub <https://github.com/kakwa/ldapcherry>`_
|
||||
:PyPI: `LdapCherry package on Pypi <http://pypi.python.org/pypi/ldapcherry>`_
|
||||
:License: MIT
|
||||
:Author: Pierre-Francois Carpentier - copyright © 2016
|
||||
|
||||
----
|
||||
|
||||
****************
|
||||
Presentation
|
||||
****************
|
||||
|
||||
LdapCherry is CherryPY application to manage users and groups in multiple directory services.
|
||||
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")
|
||||
|
@ -42,78 +50,39 @@ through the proper plugin (provided that it is implemented ^^).
|
|||
LdapCherry also aims to be as simple as possible to deploy: no crazy dependencies,
|
||||
few configuration files, extensive debug logs and full documentation.
|
||||
|
||||
**************
|
||||
Screenshot
|
||||
**************
|
||||
The default backend plugins permit to manage Ldap and Active Directory.
|
||||
|
||||
.. raw:: html
|
||||
***************
|
||||
Screenshots
|
||||
***************
|
||||
|
||||
<style>
|
||||
#images {
|
||||
width: 800px;
|
||||
height: 600px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
|
||||
margin: 20px auto;
|
||||
}
|
||||
#images img {
|
||||
width: 800px;
|
||||
height: 600px;
|
||||
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -400px;
|
||||
z-index: 1;
|
||||
opacity: 0;
|
||||
|
||||
transition: all linear 500ms;
|
||||
-o-transition: all linear 500ms;
|
||||
-moz-transition: all linear 500ms;
|
||||
-webkit-transition: all linear 500ms;
|
||||
}
|
||||
#images img:target {
|
||||
left: 0;
|
||||
z-index: 9;
|
||||
opacity: 1;
|
||||
}
|
||||
#images img:first-child {
|
||||
left: 0;
|
||||
opacity: 1;
|
||||
}
|
||||
#slider {
|
||||
text-align: center;
|
||||
}
|
||||
#slider a {
|
||||
text-decoration: none;
|
||||
background: #E3F1FA;
|
||||
border: 1px solid #C6E4F2;
|
||||
padding: 4px 6px;
|
||||
color: #222;
|
||||
margin: 20px auto;
|
||||
}
|
||||
#slider a:hover {
|
||||
background: #C6E4F2;
|
||||
}
|
||||
</style>
|
||||
<div id="images">
|
||||
<img id="image1" src='https://raw.githubusercontent.com/kakwa/ldapcherry/master/docs/assets/sc/2015-07-06-093051_1438x1064_scrot.png' />
|
||||
<img id="image2" src='https://raw.githubusercontent.com/kakwa/ldapcherry/master/docs/assets/sc/2015-07-06-093130_1438x1064_scrot.png' />
|
||||
<img id="image3" src='https://raw.githubusercontent.com/kakwa/ldapcherry/master/docs/assets/sc/2015-07-06-093147_1438x1064_scrot.png' />
|
||||
<img id="image4" src='https://raw.githubusercontent.com/kakwa/ldapcherry/master/docs/assets/sc/2015-07-06-093152_1438x1064_scrot.png' />
|
||||
<img id="image5" src='https://raw.githubusercontent.com/kakwa/ldapcherry/master/docs/assets/sc/2015-07-06-093215_1438x1064_scrot.png' />
|
||||
<img id="image6" src='https://raw.githubusercontent.com/kakwa/ldapcherry/master/docs/assets/sc/2015-07-06-093234_1438x1064_scrot.png' />
|
||||
</div>
|
||||
<div id="slider">
|
||||
<a href="#image1">1</a>
|
||||
<a href="#image2">2</a>
|
||||
<a href="#image3">3</a>
|
||||
<a href="#image4">4</a>
|
||||
<a href="#image5">5</a>
|
||||
<a href="#image6">6</a>
|
||||
</div>
|
||||
`Screenshots <http://ldapcherry.readthedocs.org/en/latest/screenshots.html#image1>`_.
|
||||
|
||||
***********
|
||||
Try out
|
||||
***********
|
||||
|
||||
.. sourcecode:: bash
|
||||
|
||||
# clone the repository
|
||||
$ git clone https://github.com/kakwa/ldapcherry && cd ldapcherry
|
||||
|
||||
# change the directory where to put the configuration (default: /etc)
|
||||
$ export SYSCONFDIR=/etc
|
||||
# change the directory where to put the resource (default: /usr/share)
|
||||
$ export DATAROOTDIR=/usr/share/
|
||||
|
||||
# install ldapcherry
|
||||
$ python setup.py install
|
||||
|
||||
# edit configuration files
|
||||
$ vi /etc/ldapcherry/ldapcherry.ini
|
||||
$ vi /etc/ldapcherry/roles.yml
|
||||
$ vi /etc/ldapcherry/attributes.yml
|
||||
|
||||
# launch ldapcherry
|
||||
$ ldapcherryd -c /etc/ldapcherry/ldapcherry.ini -D
|
||||
|
||||
`Screenshots <http://ldapcherry.readthedocs.org/en/latest/#image1>`_
|
||||
|
||||
***********
|
||||
License
|
||||
|
@ -125,11 +94,10 @@ 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>`_
|
||||
|
||||
----
|
||||
|
||||
.. image:: docs/assets/python-powered.png
|
||||
.. image:: docs/assets/cherrypy.png
|
||||
|
||||
.. image:: https://raw.githubusercontent.com/kakwa/ldapcherry/master/docs/assets/python-powered.png
|
||||
.. image:: https://raw.githubusercontent.com/kakwa/ldapcherry/master/docs/assets/cherrypy.png
|
||||
|
|
|
@ -10,7 +10,7 @@ cn:
|
|||
- $name
|
||||
backends:
|
||||
ldap: cn
|
||||
# ad: CN
|
||||
# ad: cn
|
||||
first-name:
|
||||
description: "First name of the user"
|
||||
display_name: "First Name"
|
||||
|
@ -56,9 +56,11 @@ uid:
|
|||
args:
|
||||
- $first-name
|
||||
- $name
|
||||
- '10000'
|
||||
- '40000'
|
||||
backends:
|
||||
ldap: uid
|
||||
# ad: uid
|
||||
# ad: sAMAccountName
|
||||
uidNumber:
|
||||
description: "User ID Number of the user"
|
||||
display_name: "UID Number"
|
||||
|
@ -69,18 +71,20 @@ uidNumber:
|
|||
args:
|
||||
- $first-name
|
||||
- $name
|
||||
- '10000'
|
||||
- '40000'
|
||||
backends:
|
||||
ldap: uidNumber
|
||||
# ad: UIDNumber
|
||||
# ad: uidNumber
|
||||
gidNumber:
|
||||
description: "Group ID Number of the user"
|
||||
display_name: "GID Number"
|
||||
weight: 70
|
||||
type: int
|
||||
default: 10000
|
||||
default: '10000'
|
||||
backends:
|
||||
ldap: gidNumber
|
||||
# ad: GIDNumber
|
||||
# ad: gidNumber
|
||||
shell:
|
||||
description: "Shell of the user"
|
||||
display_name: "Shell"
|
||||
|
@ -93,7 +97,7 @@ shell:
|
|||
- /bin/sh
|
||||
backends:
|
||||
ldap: loginShell
|
||||
# ad: LOGINSHEL
|
||||
# ad: loginShell
|
||||
home:
|
||||
description: "Home user path"
|
||||
display_name: "Home"
|
||||
|
@ -107,7 +111,7 @@ home:
|
|||
- /home/
|
||||
backends:
|
||||
ldap: homeDirectory
|
||||
# ad: HOMEDIRECTORY
|
||||
# ad: homeDirectory
|
||||
password:
|
||||
description: "Password of the user"
|
||||
display_name: "Password"
|
||||
|
@ -116,4 +120,13 @@ password:
|
|||
type: password
|
||||
backends:
|
||||
ldap: userPassword
|
||||
# ad: PASSWORD
|
||||
# ad: unicodePwd
|
||||
|
||||
#logscript:
|
||||
# description: "Windows login script"
|
||||
# display_name: "Login script"
|
||||
# weight: 100
|
||||
# type: fix
|
||||
# value: login1.bat
|
||||
# backends:
|
||||
# ad: scriptPath
|
||||
|
|
|
@ -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 #
|
||||
#####################################
|
||||
|
@ -72,6 +84,8 @@ roles.file = '/etc/ldapcherry/roles.yml'
|
|||
|
||||
# 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'
|
||||
|
@ -92,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
|
||||
|
@ -110,10 +132,51 @@ ldap.dn_user_attr = 'uid'
|
|||
#####################################
|
||||
# configuration of ad backend #
|
||||
#####################################
|
||||
#
|
||||
#ad.module = 'ldapcherry.backend.backendSamba4'
|
||||
#ad.auth = 'Administrator'
|
||||
#ad.password = 'password'
|
||||
|
||||
## 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 = 'ldap://ldap.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'
|
||||
|
||||
#####################################
|
||||
# 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 = 'DnsAdmins'
|
||||
## 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'
|
||||
|
||||
[ppolicy]
|
||||
|
||||
|
@ -144,5 +207,21 @@ auth.mode = 'or'
|
|||
templates.dir = '/usr/share/ldapcherry/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 = '/usr/share/ldapcherry/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
|
||||
#tools.staticdir.dir = '/etc/ldapcherry/custom_js/'
|
||||
|
|
|
@ -9,8 +9,10 @@ admin-lv3:
|
|||
- cn=users,ou=Group,dc=example,dc=org
|
||||
# ad:
|
||||
# - Administrators
|
||||
# - Domain Controllers
|
||||
# - Domain Users
|
||||
# - Group Policy Creator Owners
|
||||
# - Enterprise Admins
|
||||
# - Schema Admins
|
||||
# - Domain Admins
|
||||
|
||||
admin-lv2:
|
||||
display_name: Administrators Level 2
|
||||
|
@ -21,15 +23,14 @@ admin-lv2:
|
|||
- cn=nagios admins,ou=Group,dc=example,dc=org
|
||||
- cn=users,ou=Group,dc=example,dc=org
|
||||
# ad:
|
||||
# - Domain Users
|
||||
# - Domain Controllers
|
||||
# - 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:
|
||||
|
@ -38,5 +39,3 @@ users:
|
|||
backends_groups:
|
||||
ldap:
|
||||
- cn=users,ou=Group,dc=example,dc=org
|
||||
# ad:
|
||||
# - Domain Users
|
||||
|
|
|
@ -68,5 +68,5 @@ Example
|
|||
|
||||
Here is the ldap backend module that comes with LdapCherry:
|
||||
|
||||
.. literalinclude:: ../ldapcherry/backend/backendLdap.py
|
||||
.. literalinclude:: ../ldapcherry/backend/backendDemo.py
|
||||
:language: python
|
||||
|
|
|
@ -0,0 +1,283 @@
|
|||
Backends
|
||||
========
|
||||
|
||||
Backend id prefix
|
||||
-----------------
|
||||
|
||||
Each parameter of a backend instance must be prefixed by a backend id.
|
||||
This backend id must be unique.
|
||||
|
||||
For example:
|
||||
|
||||
.. sourcecode:: ini
|
||||
|
||||
[backends]
|
||||
|
||||
# configuration of the bk1 backend
|
||||
bk1.module = 'my.backend.module'
|
||||
bk1.display_name = 'My backend module'
|
||||
bk1.param = 'value'
|
||||
|
||||
.. warning::
|
||||
For the rest of the backends documentation, this prefix is inferred.
|
||||
|
||||
Common backend parameters
|
||||
-------------------------
|
||||
|
||||
Every backend instance systematicaly has two parameters:
|
||||
|
||||
+---------------------+----------+------------------------------------+--------------------------+--------------------------------------------+
|
||||
| Parameter | Section | Description | Values | Comment |
|
||||
+=====================+==========+====================================+==========================+============================================+
|
||||
| module | backends | Library path to the module | Python library path | |
|
||||
+---------------------+----------+------------------------------------+--------------------------+--------------------------------------------+
|
||||
| display_name | backends | Display_name of the backend | Free text | |
|
||||
+---------------------+----------+------------------------------------+--------------------------+--------------------------------------------+
|
||||
|
||||
Ldap Backend
|
||||
------------
|
||||
|
||||
Class path
|
||||
^^^^^^^^^^
|
||||
|
||||
The class path for the ldap backend is **ldapcherry.backend.backendLdap**.
|
||||
|
||||
Configuration
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
The ldap backend exposes the following parameters:
|
||||
|
||||
+--------------------------+----------+------------------------------------+--------------------------+------------------------------------------------+
|
||||
| Parameter | Section | Description | Values | Comment |
|
||||
+==========================+==========+====================================+==========================+================================================+
|
||||
| uri | backends | The ldap uri to access | ldap uri | * use ldap:// for clear/starttls |
|
||||
| | | | | * use ldaps:// for ssl |
|
||||
| | | | | * custom port: ldap://<host>:<port> |
|
||||
+--------------------------+----------+------------------------------------+--------------------------+------------------------------------------------+
|
||||
| ca | backends | Path to the CA file | file path | optional |
|
||||
+--------------------------+----------+------------------------------------+--------------------------+------------------------------------------------+
|
||||
| starttls | backends | Use starttls | 'on' or 'off' | optional |
|
||||
+--------------------------+----------+------------------------------------+--------------------------+------------------------------------------------+
|
||||
| checkcert | backends | Check the server certificat | 'on' or 'off' | optional |
|
||||
+--------------------------+----------+------------------------------------+--------------------------+------------------------------------------------+
|
||||
| binddn | backends | The bind dn to use | ldap dn | This dn must have read/write permissions |
|
||||
+--------------------------+----------+------------------------------------+--------------------------+------------------------------------------------+
|
||||
| password | backends | The password of the bind dn | password | |
|
||||
+--------------------------+----------+------------------------------------+--------------------------+------------------------------------------------+
|
||||
| timeout | backends | Ldap connexion timeout | integer (second) | |
|
||||
+--------------------------+----------+------------------------------------+--------------------------+------------------------------------------------+
|
||||
| password | backends | The password of the bind dn | password | |
|
||||
+--------------------------+----------+------------------------------------+--------------------------+------------------------------------------------+
|
||||
| groupdn | backends | The ldap dn where groups are | ldap dn | |
|
||||
+--------------------------+----------+------------------------------------+--------------------------+------------------------------------------------+
|
||||
| userdn | backends | The ldap dn where users are | ldap dn | |
|
||||
+--------------------------+----------+------------------------------------+--------------------------+------------------------------------------------+
|
||||
| user_filter_tmpl | backends | The search filter template | ldap search filter | The user identifier is passed through |
|
||||
| | | to recover a given user | template | the **username** variable (*%(username)s*) |
|
||||
| | | | | |
|
||||
| | | | | **username** is the content of the |
|
||||
| | | | | the attribute marked by '**key: True**' |
|
||||
| | | | | in the **attributes.yml** file |
|
||||
+--------------------------+----------+------------------------------------+--------------------------+------------------------------------------------+
|
||||
| group_filter_tmpl | backends | The search filter template to | ldap search filter | The following variables are usable: |
|
||||
| | | recover the groups of a given user | template | |
|
||||
| | | recover the groups of a given user | template | * **username**: the user's key attribute |
|
||||
| | | | | * **userdn**: the user's ldap dn |
|
||||
+--------------------------+----------+------------------------------------+--------------------------+------------------------------------------------+
|
||||
| group_attr.<member attr> | backends | Member attribute template value | template | * <member attr> is the member attribute |
|
||||
| | | | | in groups dn entries |
|
||||
| | | | | * every user attributes are exposed |
|
||||
| | | | | in the template |
|
||||
| | | | | * multiple <memver attr> attributes |
|
||||
| | | | | can be set (ex: group_attr.member |
|
||||
| | | | | (ex: group_attr.member, group_attr.usermemb) |
|
||||
+--------------------------+----------+------------------------------------+--------------------------+------------------------------------------------+
|
||||
| objectclasses | backends | list of object classes for users | comma separated list | |
|
||||
+--------------------------+----------+------------------------------------+--------------------------+------------------------------------------------+
|
||||
| dn_user_attr | backends | attribute used in users dn | dn attribute | |
|
||||
+--------------------------+----------+------------------------------------+--------------------------+------------------------------------------------+
|
||||
|
||||
|
||||
Example
|
||||
^^^^^^^
|
||||
|
||||
.. sourcecode:: ini
|
||||
|
||||
[backends]
|
||||
|
||||
#####################################
|
||||
# configuration of ldap backend #
|
||||
#####################################
|
||||
|
||||
# name of the module
|
||||
ldap.module = 'ldapcherry.backend.backendLdap'
|
||||
# display name of the ldap
|
||||
ldap.display_name = 'My Ldap Directory'
|
||||
|
||||
# uri of the ldap directory
|
||||
ldap.uri = 'ldap://ldap.ldapcherry.org'
|
||||
# ca to use for ssl/tls connexion
|
||||
#ldap.ca = '/etc/dnscherry/TEST-cacert.pem'
|
||||
# use start tls
|
||||
#ldap.starttls = 'off'
|
||||
# check server certificate (for tls)
|
||||
#ldap.checkcert = 'off'
|
||||
# bind dn to the ldap
|
||||
ldap.binddn = 'cn=dnscherry,dc=example,dc=org'
|
||||
# password of the bind dn
|
||||
ldap.password = 'password'
|
||||
# timeout of ldap connexion (in second)
|
||||
ldap.timeout = 1
|
||||
|
||||
# groups dn
|
||||
ldap.groupdn = 'ou=group,dc=example,dc=org'
|
||||
# users dn
|
||||
ldap.userdn = 'ou=people,dc=example,dc=org'
|
||||
|
||||
# ldapsearch filter to get one specific user
|
||||
# %(username)s is content of the attribute marked 'key: True' in the attributes.file config file
|
||||
ldap.user_filter_tmpl = '(uid=%(username)s)'
|
||||
# ldapsearch filter to get groups of a user
|
||||
# %(username)s is content of the attribute marked 'key: True' in the attributes.file config file
|
||||
ldap.group_filter_tmpl = '(member=uid=%(username)s,ou=People,dc=example,dc=org)'
|
||||
# filter to search users
|
||||
# %(searchstring)s is the content passed through the search box
|
||||
ldap.search_filter_tmpl = '(|(uid=%(searchstring)s*)(sn=%(searchstring)s*))'
|
||||
|
||||
# ldap group attributes and how to fill them
|
||||
# 'member' is the name of the attribute
|
||||
# for the template, any of the user's ldap attributes can be user
|
||||
ldap.group_attr.member = "%(dn)s"
|
||||
# same with memverUid and the uid user's attribute
|
||||
#ldap.group_attr.memberUid = "%(uid)s"
|
||||
|
||||
# object classes of a user entry
|
||||
ldap.objectclasses = 'top, person, posixAccount, inetOrgPerson'
|
||||
# dn entry attribute for an ldap user
|
||||
ldap.dn_user_attr = 'uid'
|
||||
|
||||
|
||||
Active Directory Backend
|
||||
------------------------
|
||||
|
||||
.. warning:: This backend needs the **cn** and **unicodePwd** attributes to be declared in attributes.yml
|
||||
|
||||
Class path
|
||||
^^^^^^^^^^
|
||||
|
||||
The class path for the ldap backend is **ldapcherry.backend.backendAD**.
|
||||
|
||||
Configuration
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
+--------------------------+----------+------------------------------------+--------------------------+--------------------------------------------+
|
||||
| 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 |
|
||||
+--------------------------+----------+------------------------------------+--------------------------+--------------------------------------------+
|
||||
| domain | backends | Name of the domain | AD domain | |
|
||||
+--------------------------+----------+------------------------------------+--------------------------+--------------------------------------------+
|
||||
| login | backends | login used for connecting to AD | login | user used must have sufficient rights |
|
||||
+--------------------------+----------+------------------------------------+--------------------------+--------------------------------------------+
|
||||
| password | backends | password if binding user | password | |
|
||||
+--------------------------+----------+------------------------------------+--------------------------+--------------------------------------------+
|
||||
|
||||
Example
|
||||
^^^^^^^
|
||||
|
||||
.. sourcecode:: ini
|
||||
|
||||
|
||||
[backends]
|
||||
|
||||
# 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 = 'ldap://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'
|
||||
|
||||
Demo Backend
|
||||
------------
|
||||
|
||||
.. warning:: This backend is only meant for demo.
|
||||
|
||||
Class path
|
||||
^^^^^^^^^^
|
||||
|
||||
The class path for the ldap backend is **ldapcherry.backend.backendDemo**.
|
||||
|
||||
Configuration
|
||||
^^^^^^^^^^^^^
|
||||
+-------------------+----------+----------------------------+----------------------+----------------------------+
|
||||
| Parameter | Section | Description | Values | Comment |
|
||||
+===================+==========+============================+======================+============================+
|
||||
| admin.user | backends | Login for default admin | string | optional, default: 'admin' |
|
||||
+-------------------+----------+----------------------------+----------------------+----------------------------+
|
||||
| admin.password | backends | Password for default admin | string | optional, default: 'admin' |
|
||||
+-------------------+----------+----------------------------+----------------------+----------------------------+
|
||||
| admin.groups | backends | Groups for default admin | comma separated list | |
|
||||
+-------------------+----------+----------------------------+----------------------+----------------------------+
|
||||
| basic.user | backends | Login for default user | string | optional, default: 'user' |
|
||||
+-------------------+----------+----------------------------+----------------------+----------------------------+
|
||||
| basic.password | backends | Password for default user | string | optional, default: 'user' |
|
||||
+-------------------+----------+----------------------------+----------------------+----------------------------+
|
||||
| basic.groups | backends | Groups for default user | comma separated list | |
|
||||
+-------------------+----------+----------------------------+----------------------+----------------------------+
|
||||
| pwd_attr | backends | Password attribute name | string | |
|
||||
+-------------------+----------+----------------------------+----------------------+----------------------------+
|
||||
| search_attributes | backends | Attributes used for search | comma separated list | |
|
||||
+-------------------+----------+----------------------------+----------------------+----------------------------+
|
||||
|
||||
Example
|
||||
^^^^^^^
|
||||
|
||||
.. sourcecode:: ini
|
||||
|
||||
[backends]
|
||||
|
||||
# path to the module
|
||||
demo.module = 'ldapcherry.backend.backendDemo'
|
||||
# display name of the module
|
||||
demo.display_name = 'Demo Backend'
|
||||
|
||||
## admin user login (optional, default: 'admin')
|
||||
#demo.admin.user = 'admin'
|
||||
## admin user password (optional: default 'admin')
|
||||
#demo.admin.password = 'admin'
|
||||
# groups for the default admin user (comma separated)
|
||||
demo.admin.groups = 'DnsAdmins'
|
||||
|
||||
## basic user login (optional, default: 'user')
|
||||
#demo.basic.user = 'user'
|
||||
## admin user password (optional: default 'user')
|
||||
#demo.basic.password = 'user'
|
||||
# groups for the default basic user (comma separated)
|
||||
demo.basic.groups = 'Test 2, Test 1'
|
||||
|
||||
# password attribute used for auth
|
||||
demo.pwd_attr = 'userPassword'
|
||||
# attributes to search on
|
||||
demo.search_attributes = 'cn, sn, givenName, uid'
|
||||
|
|
@ -1,7 +1,5 @@
|
|||
Changelog
|
||||
=========
|
||||
|
||||
Version 0.0.1
|
||||
*************
|
||||
.. include:: ../ChangeLog.rst
|
||||
|
||||
* first release
|
||||
|
|
20
docs/conf.py
20
docs/conf.py
|
@ -17,7 +17,23 @@
|
|||
import os
|
||||
import sys
|
||||
|
||||
try:
|
||||
from mock import Mock as MagicMock
|
||||
except:
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
class Mock(MagicMock):
|
||||
@classmethod
|
||||
def __getattr__(cls, name):
|
||||
return Mock()
|
||||
|
||||
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 -----------------------------------------------------
|
||||
|
||||
|
@ -42,7 +58,7 @@ master_doc = 'index'
|
|||
|
||||
# General information about the project.
|
||||
project = u'LdapCherry - Directory Management Interface'
|
||||
copyright = u'2014, Pierre-Francois Carpentier'
|
||||
copyright = u'2016, Pierre-Francois Carpentier'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
|
@ -50,7 +66,7 @@ copyright = u'2014, Pierre-Francois Carpentier'
|
|||
#
|
||||
# The short X.Y version.
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = '0.0.1'
|
||||
release = version
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
|
|
100
docs/deploy.rst
100
docs/deploy.rst
|
@ -1,12 +1,14 @@
|
|||
Deploy
|
||||
======
|
||||
|
||||
LdapCherry aims at being as simple as possible to deploy.
|
||||
LdapCherry aims to be as simple as possible to deploy.
|
||||
The Application is constituted of:
|
||||
|
||||
* ldapcherryd: the daemon to lauch LdapCherry
|
||||
* one ini file (ldapcherry.ini by default): the entry point for the configuration, containing all the "technical" attributes
|
||||
* two yaml files (roles.yml and attributes by default): the files containing the roles and attributes definition
|
||||
* ldapcherryd: the daemon to lauch LdapCherry.
|
||||
* one ini file (ldapcherry.ini by default): the entry point for the configuration, containing all the "technical" attributes.
|
||||
* two yaml files (roles.yml and attributes by default): the files containing the roles and attributes definition.
|
||||
|
||||
The default configuration directory is **/etc/ldapcherry/**.
|
||||
|
||||
Launch
|
||||
------
|
||||
|
@ -19,9 +21,12 @@ LdapCherry is launched using the internal cherrypy server:
|
|||
# ldapcherryd help
|
||||
$ ldapcherryd -h
|
||||
|
||||
# launching ldapcherryd in the forground
|
||||
# launching ldapcherryd in the foreground
|
||||
$ ldapcherryd -c /etc/ldapcherry/ldapcherry.ini
|
||||
|
||||
# launching ldapcherryd in the foreground in debug mode
|
||||
$ ldapcherryd -c /etc/ldapcherry/ldapcherry.ini -D
|
||||
|
||||
# launching ldapcherryd as a daemon
|
||||
$ ldapcherryd -c /etc/ldapcherry/ldapcherry.ini -p /var/run/ldapcherry/ldapcherry.pid -d
|
||||
|
||||
|
@ -31,10 +36,10 @@ Roles and Attributes Configuration
|
|||
Entry point in main configuration
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The main configuration file (ldapcherry.ini by default) contains two parameters locating the roles and attributes configuration files:
|
||||
The main configuration file (**ldapcherry.ini** by default) contains two parameters locating the roles and attributes configuration files:
|
||||
|
||||
+-----------------+------------+-------------------------------+-------------------+
|
||||
| Parameter | Section | Description | Values |
|
||||
| Parameter | Section | Description | Values |
|
||||
+=================+============+===============================+===================+
|
||||
| attributes.file | attributes | Attributes configuration file | Path to conf file |
|
||||
+-----------------+------------+-------------------------------+-------------------+
|
||||
|
@ -44,7 +49,7 @@ The main configuration file (ldapcherry.ini by default) contains two parameters
|
|||
Attributes Configuration
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The attributes configuration is done in a yaml file (attributes.yml by default).
|
||||
The attributes configuration is done in a yaml file (**attributes.yml** by default).
|
||||
|
||||
Mandatory parameters
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
@ -57,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>
|
||||
|
@ -68,8 +73,20 @@ The mandatory parameters for an attribute, and their format are the following:
|
|||
|
||||
.. warning::
|
||||
|
||||
<backend id> (the backend id) must be defined in main configuration
|
||||
(ldapcherry.ini by default). LdapCherry won't start if it's not.
|
||||
<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
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
@ -95,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.
|
||||
|
||||
|
@ -117,7 +139,7 @@ Example:
|
|||
Authorize self modification
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
A user can modify some of it's attributes (self modification).
|
||||
A user can modify some of his attributes (self modification).
|
||||
In such case, the parameter **self** must set to **True**:
|
||||
|
||||
.. sourcecode:: yaml
|
||||
|
@ -168,7 +190,7 @@ Arguments of the **autofill** function work as follow:
|
|||
|
||||
Available **autofill** functions:
|
||||
|
||||
* lcUid: generate 8 characters uid from 2 other fields (first letter of the first field, 7 first letters of the second):
|
||||
* lcUid: generate 8 characters ascii uid from 2 other fields (first letter of the first field, 7 first letters of the second):
|
||||
|
||||
.. sourcecode:: yaml
|
||||
|
||||
|
@ -179,7 +201,7 @@ Available **autofill** functions:
|
|||
- $name
|
||||
|
||||
|
||||
* lcDisplayName: concatenate two fields
|
||||
* lcDisplayName: concatenate two fields (with a space as separator):
|
||||
|
||||
.. sourcecode:: yaml
|
||||
|
||||
|
@ -189,7 +211,7 @@ Available **autofill** functions:
|
|||
- $first-name
|
||||
- $name
|
||||
|
||||
* lcMail: generate an email address from 2 other fields and a domain (<uid>+domain)
|
||||
* lcMail: generate an email address from 2 other fields and a domain (<uid>+domain):
|
||||
|
||||
.. sourcecode:: yaml
|
||||
|
||||
|
@ -201,7 +223,7 @@ Available **autofill** functions:
|
|||
- '@example.com'
|
||||
|
||||
|
||||
* lcUidNumber: generate an uid number from 2 other fields and between a minimum and maximum value
|
||||
* lcUidNumber: generate an uid number from 2 other fields and between a minimum and maximum value:
|
||||
|
||||
.. sourcecode:: yaml
|
||||
|
||||
|
@ -213,7 +235,7 @@ Available **autofill** functions:
|
|||
- '10000'
|
||||
- '40000'
|
||||
|
||||
* lcHomeDir: generate an home directory from 2 other fields and a root (<root>+<uid>)
|
||||
* lcHomeDir: generate an home directory from 2 other fields and a root (<root>+<uid>):
|
||||
|
||||
.. sourcecode:: yaml
|
||||
|
||||
|
@ -227,7 +249,7 @@ Available **autofill** functions:
|
|||
Roles Configuration
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The roles configuration is done in a yaml file (roles.yml by default).
|
||||
The roles configuration is done in a yaml file (**roles.yml** by default).
|
||||
|
||||
Mandatory parameters
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
@ -237,7 +259,7 @@ Roles are seen as an aggregate of groups:
|
|||
.. sourcecode:: yaml
|
||||
|
||||
<role id>:
|
||||
display_name: <Role display name in LdapCherry>
|
||||
display_name: <role display name in LdapCherry>
|
||||
description: <human readable role description>
|
||||
backends_groups: # list of backends
|
||||
<backend id 1>: # list of groups in backend
|
||||
|
@ -252,7 +274,7 @@ Roles are seen as an aggregate of groups:
|
|||
Defining LdapCherry Administrator role
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
One of the declared roles must be tagged to be LdapCherry administrators.
|
||||
At least one of the declared roles must be tagged to be LdapCherry administrators.
|
||||
|
||||
Doing so is done by setting **LC_admins** to **True** for the selected role:
|
||||
|
||||
|
@ -367,7 +389,10 @@ Backends are configure in the **backends** section, the format is the following:
|
|||
[backends]
|
||||
|
||||
# backend python module path
|
||||
<backend id>.module = 'python.module.path'
|
||||
<backend id>.module = <python.module.path>
|
||||
|
||||
# display name of the backend in forms
|
||||
<backend id>.display_name = <display name of the backend>
|
||||
|
||||
# parameters of the module instance for backend <backend id>.
|
||||
<backend id>.<param> = <value>
|
||||
|
@ -425,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' |
|
||||
+--------------------+---------+---------------------------------+-------------------------------------------------+----------------------------------------+
|
||||
|
@ -457,6 +482,31 @@ 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
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
It's possible to add custom javascript to LdapCherry, mainly to add custom autofill functions.
|
||||
|
||||
Configuration:
|
||||
|
||||
+---------------------+---------+--------------------------------+--------------------------+------------------------------------------------------------+
|
||||
| Parameter | Section | Description | Values | Comment |
|
||||
+=====================+=========+================================+==========================+============================================================+
|
||||
| tools.staticdir.on | /custom | Serve custom js files through | True, False | These files could be server directly by an |
|
||||
| | | LdapCherry | | HTTP server for better performance. |
|
||||
+---------------------+---------+--------------------------------+--------------------------+------------------------------------------------------------+
|
||||
| tools.staticdir.dir | /custom | Directory containing custom js | Path to static resources | * custom js files must be put at the root if the directory |
|
||||
| | | files | | * only files ending with ".js" are taken into account |
|
||||
+---------------------+---------+--------------------------------+--------------------------+------------------------------------------------------------+
|
||||
|
||||
|
||||
Other LdapCherry parameters
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
CherryPy>=3.0.0
|
||||
PyYAML
|
||||
Mako
|
||||
mock
|
|
@ -0,0 +1,14 @@
|
|||
LdapCherry plugins list
|
||||
=======================
|
||||
|
||||
If you have developped OSS LdapCherry plugins, please fill an `Issue Here <https://github.com/kakwa/ldapcherry/issues>`_
|
||||
with a link to your plugin and a small description.
|
||||
|
||||
Password Policy plugins
|
||||
-----------------------
|
||||
|
||||
* `Cracklib PPolicy <https://github.com/kakwa/ldapcherry-ppolicy-cracklib>`_
|
||||
|
||||
Backend plugins
|
||||
---------------
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
.. raw:: html
|
||||
|
||||
<a href="https://github.com/kakwa/ldapcherry"><img style="position: absolute; top: 0; right: 0; border: 0;" src="https://camo.githubusercontent.com/38ef81f8aca64bb9a64448d0d70f1308ef5341ab/68747470733a2f2f73332e616d617a6f6e6177732e636f6d2f6769746875622f726962626f6e732f666f726b6d655f72696768745f6461726b626c75655f3132313632312e706e67" alt="Fork me on GitHub" data-canonical-src="https://s3.amazonaws.com/github/ribbons/forkme_right_darkblue_121621.png"></a>
|
|
@ -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
|
||||
|
|
|
@ -7,10 +7,15 @@
|
|||
|
||||
install
|
||||
deploy
|
||||
backends
|
||||
full_configuration
|
||||
external_plugins
|
||||
backend_api
|
||||
ppolicy_api
|
||||
changelog
|
||||
goodies
|
||||
screenshots
|
||||
|
||||
.. include:: ../README.rst
|
||||
|
||||
.. include:: forkme.rst
|
||||
|
|
|
@ -12,18 +12,13 @@ Download the latest release from `GitHub <https://github.com/kakwa/ldapcherry/re
|
|||
$ cd ldapcherry*
|
||||
$ python setup.py install
|
||||
|
||||
From Pypi
|
||||
---------
|
||||
|
||||
.. sourcecode:: bash
|
||||
|
||||
$ pip install ldapcherry
|
||||
|
||||
or
|
||||
Alternatively, you can install from git:
|
||||
|
||||
.. 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.
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
Screenshots
|
||||
===========
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<style>
|
||||
#images {
|
||||
width: 800px;
|
||||
height: 600px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
|
||||
margin: 20px auto;
|
||||
}
|
||||
#images img {
|
||||
width: 800px;
|
||||
height: 600px;
|
||||
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -400px;
|
||||
z-index: 1;
|
||||
opacity: 0;
|
||||
|
||||
transition: all linear 500ms;
|
||||
-o-transition: all linear 500ms;
|
||||
-moz-transition: all linear 500ms;
|
||||
-webkit-transition: all linear 500ms;
|
||||
}
|
||||
#images img:target {
|
||||
left: 0;
|
||||
z-index: 9;
|
||||
opacity: 1;
|
||||
}
|
||||
#images img:first-child {
|
||||
left: 0;
|
||||
opacity: 1;
|
||||
}
|
||||
#slider {
|
||||
text-align: center;
|
||||
}
|
||||
#slider a {
|
||||
text-decoration: none;
|
||||
background: #E3F1FA;
|
||||
border: 1px solid #C6E4F2;
|
||||
padding: 4px 6px;
|
||||
color: #222;
|
||||
margin: 20px auto;
|
||||
}
|
||||
#slider a:hover {
|
||||
background: #C6E4F2;
|
||||
}
|
||||
</style>
|
||||
<div id="images">
|
||||
<img id="image1" src='https://raw.githubusercontent.com/kakwa/ldapcherry/master/docs/assets/sc/2015-07-06-093051_1438x1064_scrot.png' />
|
||||
<img id="image2" src='https://raw.githubusercontent.com/kakwa/ldapcherry/master/docs/assets/sc/2015-07-06-093130_1438x1064_scrot.png' />
|
||||
<img id="image3" src='https://raw.githubusercontent.com/kakwa/ldapcherry/master/docs/assets/sc/2015-07-06-093147_1438x1064_scrot.png' />
|
||||
<img id="image4" src='https://raw.githubusercontent.com/kakwa/ldapcherry/master/docs/assets/sc/2015-07-06-093152_1438x1064_scrot.png' />
|
||||
<img id="image5" src='https://raw.githubusercontent.com/kakwa/ldapcherry/master/docs/assets/sc/2015-07-06-093215_1438x1064_scrot.png' />
|
||||
<img id="image6" src='https://raw.githubusercontent.com/kakwa/ldapcherry/master/docs/assets/sc/2015-07-06-093234_1438x1064_scrot.png' />
|
||||
</div>
|
||||
<div id="slider">
|
||||
<a href="#image1">1</a>
|
||||
<a href="#image2">2</a>
|
||||
<a href="#image3">3</a>
|
||||
<a href="#image4">4</a>
|
||||
<a href="#image5">5</a>
|
||||
<a href="#image6">6</a>
|
||||
</div>
|
|
@ -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
|
|
@ -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'
|
|
@ -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
|
|
@ -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
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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,9 +15,9 @@ import logging
|
|||
import logging.handlers
|
||||
from operator import itemgetter
|
||||
from socket import error as socket_error
|
||||
import base64
|
||||
|
||||
from exceptions import *
|
||||
from ldapcherry.exceptions import *
|
||||
from ldapcherry.lclogging import *
|
||||
from ldapcherry.roles import Roles
|
||||
from ldapcherry.attributes import Attributes
|
||||
|
||||
|
@ -28,65 +28,18 @@ 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'
|
||||
|
||||
|
||||
# Custom log function to overrige weird error.log function
|
||||
# of cherrypy
|
||||
def syslog_error(
|
||||
msg='',
|
||||
context='',
|
||||
severity=logging.INFO,
|
||||
traceback=False
|
||||
):
|
||||
|
||||
if traceback and msg == '':
|
||||
msg = 'Python Exception:'
|
||||
if context == '':
|
||||
cherrypy.log.error_log.log(severity, msg)
|
||||
else:
|
||||
cherrypy.log.error_log.log(
|
||||
severity,
|
||||
' '.join((context, msg))
|
||||
)
|
||||
if traceback:
|
||||
import traceback
|
||||
try:
|
||||
exc = sys.exc_info()
|
||||
if exc == (None, None, None):
|
||||
cherrypy.log.error_log.log(severity, msg)
|
||||
# log each line of the exception
|
||||
# in a separate log for lisibility
|
||||
for l in traceback.format_exception(*exc):
|
||||
cherrypy.log.error_log.log(severity, l)
|
||||
finally:
|
||||
del exc
|
||||
|
||||
|
||||
def exception_decorator(func):
|
||||
def ret(self, *args, **kwargs):
|
||||
try:
|
||||
return func(self, *args, **kwargs)
|
||||
except cherrypy.HTTPRedirect as e:
|
||||
raise e
|
||||
except cherrypy.HTTPError as e:
|
||||
raise e
|
||||
except Exception as e:
|
||||
self._handle_exception(e)
|
||||
username = self._check_session()
|
||||
if not username:
|
||||
return self.temp_service_unavailable.render()
|
||||
is_admin = self._check_admin()
|
||||
return self.temp_error.render(
|
||||
is_admin=is_admin,
|
||||
alert='danger',
|
||||
message="An error occured, please check logs for details"
|
||||
)
|
||||
return ret
|
||||
|
||||
|
||||
class LdapCherry(object):
|
||||
|
||||
def _handle_exception(self, e):
|
||||
|
@ -97,7 +50,7 @@ class LdapCherry(object):
|
|||
)
|
||||
else:
|
||||
cherrypy.log.error(
|
||||
msg="uncatched exception: [%(e)s]" % {'e': str(e)},
|
||||
msg="uncaught exception: [%(e)s]" % {'e': str(e)},
|
||||
severity=logging.ERROR
|
||||
)
|
||||
# log the traceback as 'debug'
|
||||
|
@ -165,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
|
||||
|
@ -176,6 +129,7 @@ class LdapCherry(object):
|
|||
"""
|
||||
self.backends_params = {}
|
||||
self.backends = {}
|
||||
self.backends_display_names = {}
|
||||
for entry in config['backends']:
|
||||
# split at the first dot
|
||||
backend, sep, param = entry.partition('.')
|
||||
|
@ -184,31 +138,53 @@ class LdapCherry(object):
|
|||
self.backends_params[backend] = {}
|
||||
self.backends_params[backend][param] = value
|
||||
for backend in self.backends_params:
|
||||
# get the backend display_name
|
||||
try:
|
||||
self.backends_display_names[backend] = \
|
||||
self.backends_params[backend]['display_name']
|
||||
except Exception as e:
|
||||
self.backends_display_names[backend] = backend
|
||||
self.backends_params[backend]['display_name'] = backend
|
||||
params = self.backends_params[backend]
|
||||
# Loading the backend module
|
||||
try:
|
||||
module = params['module']
|
||||
except:
|
||||
except Exception as e:
|
||||
raise MissingParameter('backends', backend + '.module')
|
||||
try:
|
||||
bc = __import__(module, globals(), locals(), ['Backend'], -1)
|
||||
except:
|
||||
bc = __import__(module, globals(), locals(), ['Backend'], 0)
|
||||
except Exception as e:
|
||||
self._handle_exception(e)
|
||||
raise BackendModuleLoadingFail(module)
|
||||
try:
|
||||
attrslist = self.attributes.get_backend_attributes(backend)
|
||||
key = self.attributes.get_backend_key(backend)
|
||||
self.backends[backend] = bc.Backend(
|
||||
params,
|
||||
cherrypy.log,
|
||||
cherrypy.log.error,
|
||||
backend,
|
||||
attrslist,
|
||||
key,
|
||||
)
|
||||
except MissingParameter as e:
|
||||
raise e
|
||||
except:
|
||||
raise
|
||||
except Exception as e:
|
||||
self._handle_exception(e)
|
||||
raise BackendModuleInitFail(module)
|
||||
|
||||
def _init_custom_js(self, config):
|
||||
self.custom_js = []
|
||||
if '/custom' not in config:
|
||||
return
|
||||
directory = self._get_param(
|
||||
'/custom',
|
||||
'tools.staticdir.dir',
|
||||
config,
|
||||
)
|
||||
for file in os.listdir(directory):
|
||||
if file.endswith(".js"):
|
||||
self.custom_js.append(file)
|
||||
|
||||
def _init_ppolicy(self, config):
|
||||
module = self._get_param(
|
||||
'ppolicy',
|
||||
|
@ -217,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']
|
||||
|
@ -236,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(
|
||||
|
@ -277,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
|
||||
|
@ -290,7 +275,7 @@ class LdapCherry(object):
|
|||
# set log level
|
||||
cherrypy.log.access_log.setLevel(level)
|
||||
|
||||
def _set_error_log(self, config, level):
|
||||
def _set_error_log(self, config, level, debug=False):
|
||||
""" Configure error logs
|
||||
"""
|
||||
error_handler = self._get_param(
|
||||
|
@ -322,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
|
||||
|
@ -335,31 +329,12 @@ class LdapCherry(object):
|
|||
# set log level
|
||||
cherrypy.log.error_log.setLevel(level)
|
||||
|
||||
def _get_loglevel(self, level):
|
||||
""" return logging level object
|
||||
corresponding to a given level passed as
|
||||
a string
|
||||
@str level: name of a syslog log level
|
||||
@rtype: logging, logging level from logging module
|
||||
"""
|
||||
if level == 'debug':
|
||||
return logging.DEBUG
|
||||
elif level == 'notice':
|
||||
return logging.INFO
|
||||
elif level == 'info':
|
||||
return logging.INFO
|
||||
elif level == 'warning' or level == 'warn':
|
||||
return logging.WARNING
|
||||
elif level == 'error' or level == 'err':
|
||||
return logging.ERROR
|
||||
elif level == 'critical' or level == 'crit':
|
||||
return logging.CRITICAL
|
||||
elif level == 'alert':
|
||||
return logging.CRITICAL
|
||||
elif level == 'emergency' or level == 'emerg':
|
||||
return logging.CRITICAL
|
||||
else:
|
||||
return logging.INFO
|
||||
if debug:
|
||||
cherrypy.log.error_log.handlers = []
|
||||
handler = logging.StreamHandler(sys.stderr)
|
||||
handler.setLevel(logging.DEBUG)
|
||||
cherrypy.log.error_log.addHandler(handler)
|
||||
cherrypy.log.error_log.setLevel(logging.DEBUG)
|
||||
|
||||
def _auth(self, user, password):
|
||||
""" authenticate a user
|
||||
|
@ -405,34 +380,19 @@ 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']
|
||||
)
|
||||
self.temp_index = \
|
||||
self.temp_lookup.get_template('index.tmpl')
|
||||
self.temp_error = \
|
||||
self.temp_lookup.get_template('error.tmpl')
|
||||
self.temp_login = \
|
||||
self.temp_lookup.get_template('login.tmpl')
|
||||
self.temp_searchadmin = \
|
||||
self.temp_lookup.get_template('searchadmin.tmpl')
|
||||
self.temp_searchuser = \
|
||||
self.temp_lookup.get_template('searchuser.tmpl')
|
||||
self.temp_adduser = \
|
||||
self.temp_lookup.get_template('adduser.tmpl')
|
||||
self.temp_roles = \
|
||||
self.temp_lookup.get_template('roles.tmpl')
|
||||
self.temp_groups = \
|
||||
self.temp_lookup.get_template('groups.tmpl')
|
||||
self.temp_form = \
|
||||
self.temp_lookup.get_template('form.tmpl')
|
||||
self.temp_selfmodify = \
|
||||
self.temp_lookup.get_template('selfmodify.tmpl')
|
||||
self.temp_modify = \
|
||||
self.temp_lookup.get_template('modify.tmpl')
|
||||
self.temp_service_unavailable = \
|
||||
self.temp_lookup.get_template('service_unavailable.tmpl')
|
||||
# load each template
|
||||
self.temp = {}
|
||||
for t in ('index.tmpl', 'error.tmpl', 'login.tmpl', '404.tmpl',
|
||||
'searchadmin.tmpl', 'searchuser.tmpl', 'adduser.tmpl',
|
||||
'roles.tmpl', 'groups.tmpl', 'form.tmpl', 'selfmodify.tmpl',
|
||||
'modify.tmpl', 'service_unavailable.tmpl'
|
||||
):
|
||||
self.temp[t] = self.temp_lookup.get_template(t)
|
||||
|
||||
def reload(self, config=None):
|
||||
def reload(self, config=None, debug=False):
|
||||
""" load/reload configuration
|
||||
@dict: configuration of ldapcherry
|
||||
"""
|
||||
|
@ -440,7 +400,7 @@ class LdapCherry(object):
|
|||
# log configuration handling
|
||||
# get log level
|
||||
# (if not in configuration file, log level is set to debug)
|
||||
level = self._get_loglevel(
|
||||
level = get_loglevel(
|
||||
self._get_param(
|
||||
'global',
|
||||
'log.level',
|
||||
|
@ -451,7 +411,7 @@ class LdapCherry(object):
|
|||
# configure access log
|
||||
self._set_access_log(config, level)
|
||||
# configure error log
|
||||
self._set_error_log(config, level)
|
||||
self._set_error_log(config, level, debug)
|
||||
|
||||
# load template files
|
||||
self._load_templates(config)
|
||||
|
@ -468,6 +428,8 @@ class LdapCherry(object):
|
|||
severity=logging.DEBUG
|
||||
)
|
||||
|
||||
self.notifications = {}
|
||||
|
||||
self.attributes = Attributes(self.attributes_file)
|
||||
|
||||
cherrypy.log.error(
|
||||
|
@ -480,6 +442,9 @@ class LdapCherry(object):
|
|||
# loading the ppolicy
|
||||
self._init_ppolicy(config)
|
||||
|
||||
# loading custom javascript
|
||||
self._init_custom_js(config)
|
||||
|
||||
cherrypy.log.error(
|
||||
msg="application started",
|
||||
severity=logging.INFO
|
||||
|
@ -493,6 +458,38 @@ class LdapCherry(object):
|
|||
)
|
||||
exit(1)
|
||||
|
||||
def _add_notification(self, message):
|
||||
""" add a notification in the notification queue of a user
|
||||
"""
|
||||
sess = cherrypy.session
|
||||
username = sess.get(SESSION_KEY, None)
|
||||
if username not in self.notifications:
|
||||
self.notifications[username] = []
|
||||
self.notifications[username].append(message)
|
||||
|
||||
def _empty_notification(self):
|
||||
""" empty and return list of message notification
|
||||
"""
|
||||
sess = cherrypy.session
|
||||
username = sess.get(SESSION_KEY, None)
|
||||
if username in self.notifications:
|
||||
ret = self.notifications[username]
|
||||
else:
|
||||
ret = []
|
||||
self.notifications[username] = []
|
||||
return ret
|
||||
|
||||
def _merge_user_attrs(self, attrs_backend, attrs_out, backend_name):
|
||||
""" merge attributes from one backend search to the attributes dict
|
||||
output
|
||||
|
||||
"""
|
||||
for attr in attrs_backend:
|
||||
if attr in self.attributes.backend_attributes[backend_name]:
|
||||
attrid = self.attributes.backend_attributes[backend_name][attr]
|
||||
if attrid not in attrs_out:
|
||||
attrs_out[attrid] = attrs_backend[attr]
|
||||
|
||||
def _search(self, searchstring):
|
||||
""" search users
|
||||
@str searchstring: search string
|
||||
|
@ -506,11 +503,7 @@ class LdapCherry(object):
|
|||
for u in tmp:
|
||||
if u not in ret:
|
||||
ret[u] = {}
|
||||
for attr in tmp[u]:
|
||||
if attr in self.attributes.backend_attributes[b]:
|
||||
attrid = self.attributes.backend_attributes[b][attr]
|
||||
if attr not in ret[u]:
|
||||
ret[u][attrid] = tmp[u][attr]
|
||||
self._merge_user_attrs(tmp[u], ret[u], b)
|
||||
return ret
|
||||
|
||||
def _get_user(self, username):
|
||||
|
@ -525,12 +518,9 @@ class LdapCherry(object):
|
|||
try:
|
||||
tmp = self.backends[b].get_user(username)
|
||||
except UserDoesntExist as e:
|
||||
break
|
||||
for attr in tmp:
|
||||
if attr in self.attributes.backend_attributes[b]:
|
||||
attrid = self.attributes.backend_attributes[b][attr]
|
||||
if attr not in ret:
|
||||
ret[attrid] = tmp[attr]
|
||||
self._handle_exception(e)
|
||||
tmp = {}
|
||||
self._merge_user_attrs(tmp, ret, b)
|
||||
|
||||
cherrypy.log.error(
|
||||
msg="user '" + username + "' attributes " + str(ret),
|
||||
|
@ -575,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
|
||||
|
@ -592,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
|
||||
raise cherrypy.HTTPRedirect(
|
||||
"/signin?url=%(url)s" % {'url': b64requrl},
|
||||
)
|
||||
# return to login page (with quoted url in query string)
|
||||
if redir_login:
|
||||
raise cherrypy.HTTPRedirect(
|
||||
"/signin?url=%(url)s" % {'url': quoted_requrl},
|
||||
)
|
||||
else:
|
||||
raise cherrypy.HTTPError(
|
||||
"403 Forbidden",
|
||||
"You must be logged in to access this ressource.",
|
||||
)
|
||||
|
||||
if 'connected' not in cherrypy.session \
|
||||
or not cherrypy.session['connected']:
|
||||
raise cherrypy.HTTPRedirect(
|
||||
"/signin?url=%(url)s" % {'url': b64requrl},
|
||||
)
|
||||
if redir_login:
|
||||
raise cherrypy.HTTPRedirect(
|
||||
"/signin?url=%(url)s" % {'url': quoted_requrl},
|
||||
)
|
||||
else:
|
||||
raise cherrypy.HTTPError(
|
||||
"403 Forbidden",
|
||||
"You must be logged in to access this ressource.",
|
||||
)
|
||||
|
||||
if cherrypy.session['connected'] and \
|
||||
not cherrypy.session['isadmin']:
|
||||
if must_admin:
|
||||
|
@ -615,13 +618,20 @@ class LdapCherry(object):
|
|||
)
|
||||
else:
|
||||
return username
|
||||
|
||||
if cherrypy.session['connected'] and \
|
||||
cherrypy.session['isadmin']:
|
||||
return username
|
||||
else:
|
||||
raise cherrypy.HTTPRedirect(
|
||||
"/signin?url=%(url)s" % {'url': b64requrl},
|
||||
)
|
||||
if redir_login:
|
||||
raise cherrypy.HTTPRedirect(
|
||||
"/signin?url=%(url)s" % {'url': quoted_requrl},
|
||||
)
|
||||
else:
|
||||
raise cherrypy.HTTPError(
|
||||
"403 Forbidden",
|
||||
"You must be logged in to access this ressource.",
|
||||
)
|
||||
|
||||
def _adduser(self, params):
|
||||
cherrypy.log.error(
|
||||
|
@ -646,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:
|
||||
self.backends[b].add_user(badd[b])
|
||||
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 = str(sess.get(SESSION_KEY, None))
|
||||
admin = sess.get(SESSION_KEY, 'unknown')
|
||||
|
||||
cherrypy.log.error(
|
||||
msg="user '" + username + "' added by '" + admin + "'",
|
||||
|
@ -669,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 " +
|
||||
|
@ -704,7 +724,13 @@ class LdapCherry(object):
|
|||
badd[b] = {}
|
||||
badd[b][backends[b]] = params['attrs'][attr]
|
||||
for b in badd:
|
||||
self.backends[b].set_attrs(username, badd[b])
|
||||
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):
|
||||
|
@ -713,7 +739,7 @@ class LdapCherry(object):
|
|||
severity=logging.DEBUG
|
||||
)
|
||||
sess = cherrypy.session
|
||||
username = str(sess.get(SESSION_KEY, None))
|
||||
username = sess.get(SESSION_KEY, None)
|
||||
badd = self._modify_attrs(
|
||||
params,
|
||||
self.attributes.get_selfattributes(),
|
||||
|
@ -743,7 +769,7 @@ class LdapCherry(object):
|
|||
)
|
||||
|
||||
sess = cherrypy.session
|
||||
admin = str(sess.get(SESSION_KEY, None))
|
||||
admin = sess.get(SESSION_KEY, 'unknown')
|
||||
|
||||
cherrypy.log.error(
|
||||
msg="user '" + username + "' modified by '" + admin + "'",
|
||||
|
@ -791,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 + "'",
|
||||
|
@ -808,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: " +
|
||||
|
@ -828,15 +854,25 @@ class LdapCherry(object):
|
|||
)
|
||||
|
||||
def _deleteuser(self, username):
|
||||
sess = cherrypy.session
|
||||
admin = sess.get(SESSION_KEY, 'unknown')
|
||||
|
||||
for b in self.backends:
|
||||
self.backends[b].del_user(username)
|
||||
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
|
||||
)
|
||||
|
||||
cherrypy.log.error(
|
||||
msg="User '" + username + "' deleted",
|
||||
msg="User '" + username + "' deleted by '" + admin + "'",
|
||||
severity=logging.INFO
|
||||
)
|
||||
|
||||
|
@ -848,7 +884,7 @@ class LdapCherry(object):
|
|||
def signin(self, url=None):
|
||||
"""simple signin page
|
||||
"""
|
||||
return self.temp_login.render(url=url)
|
||||
return self.temp['login.tmpl'].render(url=url)
|
||||
|
||||
@cherrypy.expose
|
||||
@exception_decorator
|
||||
|
@ -878,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'" % {
|
||||
|
@ -891,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
|
||||
|
@ -918,7 +954,19 @@ class LdapCherry(object):
|
|||
"""
|
||||
self._check_auth(must_admin=False)
|
||||
is_admin = self._check_admin()
|
||||
return self.temp_index.render(is_admin=is_admin)
|
||||
sess = cherrypy.session
|
||||
user = sess.get(SESSION_KEY, None)
|
||||
if self.auth_mode == 'none':
|
||||
user_attrs = None
|
||||
else:
|
||||
user_attrs = self._get_user(user)
|
||||
attrs_list = self.attributes.get_search_attributes()
|
||||
return self.temp['index.tmpl'].render(
|
||||
is_admin=is_admin,
|
||||
attrs_list=attrs_list,
|
||||
searchresult=user_attrs,
|
||||
notifications=self._empty_notification(),
|
||||
)
|
||||
|
||||
@cherrypy.expose
|
||||
@exception_decorator
|
||||
|
@ -926,33 +974,35 @@ class LdapCherry(object):
|
|||
""" search user page """
|
||||
self._check_auth(must_admin=False)
|
||||
is_admin = self._check_admin()
|
||||
if searchstring is not None:
|
||||
if searchstring is not None and len(searchstring) > 2:
|
||||
res = self._search(searchstring)
|
||||
else:
|
||||
res = None
|
||||
attrs_list = self.attributes.get_search_attributes()
|
||||
return self.temp_searchuser.render(
|
||||
return self.temp['searchuser.tmpl'].render(
|
||||
searchresult=res,
|
||||
attrs_list=attrs_list,
|
||||
is_admin=is_admin
|
||||
is_admin=is_admin,
|
||||
custom_js=self.custom_js,
|
||||
notifications=self._empty_notification(),
|
||||
)
|
||||
|
||||
@cherrypy.expose
|
||||
@exception_decorator
|
||||
def checkppolicy(self, **params):
|
||||
""" search user page """
|
||||
keys = params.keys()
|
||||
self._check_auth(must_admin=False, redir_login=False)
|
||||
keys = list(params.keys())
|
||||
if len(keys) != 1:
|
||||
cherrypy.response.status = 403
|
||||
cherrypy.response.status = 400
|
||||
return "bad argument"
|
||||
password = params[keys[0]]
|
||||
self._check_auth(must_admin=False)
|
||||
is_admin = self._check_admin()
|
||||
ret = self._checkppolicy(password)
|
||||
if ret['match']:
|
||||
cherrypy.response.status = 200
|
||||
else:
|
||||
cherrypy.response.status = 400
|
||||
cherrypy.response.status = 200
|
||||
return json.dumps(ret, separators=(',', ':'))
|
||||
|
||||
@cherrypy.expose
|
||||
|
@ -966,10 +1016,12 @@ class LdapCherry(object):
|
|||
else:
|
||||
res = None
|
||||
attrs_list = self.attributes.get_search_attributes()
|
||||
return self.temp_searchadmin.render(
|
||||
return self.temp['searchadmin.tmpl'].render(
|
||||
searchresult=res,
|
||||
attrs_list=attrs_list,
|
||||
is_admin=is_admin
|
||||
is_admin=is_admin,
|
||||
custom_js=self.custom_js,
|
||||
notifications=self._empty_notification(),
|
||||
)
|
||||
|
||||
@cherrypy.expose
|
||||
|
@ -980,13 +1032,9 @@ class LdapCherry(object):
|
|||
is_admin = self._check_admin()
|
||||
|
||||
if cherrypy.request.method.upper() == 'POST':
|
||||
notification = "<script type=\"text/javascript\">" \
|
||||
"$.notify('User Added')" \
|
||||
"</script>"
|
||||
params = self._parse_params(params)
|
||||
self._adduser(params)
|
||||
else:
|
||||
notification = ''
|
||||
self._add_notification("User added")
|
||||
|
||||
graph = {}
|
||||
for r in self.roles.graph:
|
||||
|
@ -998,25 +1046,31 @@ 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=(',', ':'))
|
||||
form = self.temp_form.render(
|
||||
attributes=self.attributes.attributes,
|
||||
values=None,
|
||||
modify=False,
|
||||
autofill=True
|
||||
)
|
||||
roles = self.temp_roles.render(
|
||||
roles=self.roles.flatten,
|
||||
graph=self.roles.graph,
|
||||
graph_js=graph_js,
|
||||
roles_js=roles_js,
|
||||
current_roles=None
|
||||
)
|
||||
return self.temp_adduser.render(
|
||||
form=form,
|
||||
roles=roles,
|
||||
is_admin=is_admin,
|
||||
notification=notification
|
||||
)
|
||||
try:
|
||||
form = self.temp['form.tmpl'].render(
|
||||
attributes=self.attributes.attributes,
|
||||
values=None,
|
||||
modify=False,
|
||||
autofill=True
|
||||
)
|
||||
roles = self.temp['roles.tmpl'].render(
|
||||
roles=self.roles.flatten,
|
||||
graph=self.roles.graph,
|
||||
graph_js=graph_js,
|
||||
roles_js=roles_js,
|
||||
current_roles=None,
|
||||
)
|
||||
return self.temp['adduser.tmpl'].render(
|
||||
form=form,
|
||||
roles=roles,
|
||||
is_admin=is_admin,
|
||||
custom_js=self.custom_js,
|
||||
notifications=self._empty_notification(),
|
||||
)
|
||||
except NameError:
|
||||
raise TemplateRenderError(
|
||||
exceptions.text_error_template().render()
|
||||
)
|
||||
|
||||
@cherrypy.expose
|
||||
@exception_decorator
|
||||
|
@ -1024,8 +1078,12 @@ class LdapCherry(object):
|
|||
""" remove user page """
|
||||
self._check_auth(must_admin=True)
|
||||
is_admin = self._check_admin()
|
||||
referer = cherrypy.request.headers['Referer']
|
||||
try:
|
||||
referer = cherrypy.request.headers['Referer']
|
||||
except Exception as e:
|
||||
referer = '/'
|
||||
self._deleteuser(user)
|
||||
self._add_notification('User Deleted')
|
||||
raise cherrypy.HTTPRedirect(referer)
|
||||
|
||||
@cherrypy.expose
|
||||
|
@ -1036,15 +1094,14 @@ class LdapCherry(object):
|
|||
is_admin = self._check_admin()
|
||||
|
||||
if cherrypy.request.method.upper() == 'POST':
|
||||
notification = "<script type=\"text/javascript\">" \
|
||||
"$.notify('User Modify')" \
|
||||
"</script>"
|
||||
params = self._parse_params(params)
|
||||
self._modify(params)
|
||||
referer = cherrypy.request.headers['Referer']
|
||||
self._add_notification("User modified")
|
||||
try:
|
||||
referer = cherrypy.request.headers['Referer']
|
||||
except Exception as e:
|
||||
referer = '/'
|
||||
raise cherrypy.HTTPRedirect(referer)
|
||||
else:
|
||||
notification = ''
|
||||
|
||||
graph = {}
|
||||
for r in self.roles.graph:
|
||||
|
@ -1055,38 +1112,71 @@ class LdapCherry(object):
|
|||
display_names = {}
|
||||
for r in self.roles.flatten:
|
||||
display_names[r] = self.roles.flatten[r]['display_name']
|
||||
user_attrs = self._get_user(user)
|
||||
if user_attrs == {}:
|
||||
return self.temp_error.render(
|
||||
|
||||
if user is None:
|
||||
cherrypy.response.status = 400
|
||||
return self.temp['error.tmpl'].render(
|
||||
is_admin=is_admin,
|
||||
alert='warning',
|
||||
message="User doesn't exist"
|
||||
message="No user requested"
|
||||
)
|
||||
|
||||
user_attrs = self._get_user(user)
|
||||
if user_attrs == {}:
|
||||
cherrypy.response.status = 400
|
||||
return self.temp['error.tmpl'].render(
|
||||
is_admin=is_admin,
|
||||
alert='warning',
|
||||
message="User '" + user + "' does not exist"
|
||||
)
|
||||
tmp = self._get_roles(user)
|
||||
user_roles = tmp['roles']
|
||||
user_lonely_groups = tmp['unusedgroups']
|
||||
standalone_groups = tmp['unusedgroups']
|
||||
roles_js = json.dumps(display_names, separators=(',', ':'))
|
||||
key = self.attributes.get_key()
|
||||
form = self.temp_form.render(
|
||||
attributes=self.attributes.attributes,
|
||||
values=user_attrs,
|
||||
modify=True,
|
||||
keyattr=key,
|
||||
autofill=False
|
||||
|
||||
try:
|
||||
form = self.temp['form.tmpl'].render(
|
||||
attributes=self.attributes.attributes,
|
||||
values=user_attrs,
|
||||
modify=True,
|
||||
keyattr=key,
|
||||
autofill=False
|
||||
)
|
||||
|
||||
roles = self.temp['roles.tmpl'].render(
|
||||
roles=self.roles.flatten,
|
||||
graph=self.roles.graph,
|
||||
graph_js=graph_js,
|
||||
roles_js=roles_js,
|
||||
current_roles=user_roles,
|
||||
)
|
||||
roles = self.temp_roles.render(
|
||||
roles=self.roles.flatten,
|
||||
graph=self.roles.graph,
|
||||
graph_js=graph_js,
|
||||
roles_js=roles_js,
|
||||
current_roles=user_roles
|
||||
|
||||
glued_template = self.temp['modify.tmpl'].render(
|
||||
form=form,
|
||||
roles=roles,
|
||||
is_admin=is_admin,
|
||||
standalone_groups=standalone_groups,
|
||||
backends_display_names=self.backends_display_names,
|
||||
custom_js=self.custom_js,
|
||||
notifications=self._empty_notification(),
|
||||
)
|
||||
return self.temp_modify.render(
|
||||
form=form,
|
||||
roles=roles,
|
||||
except NameError:
|
||||
raise TemplateRenderError(
|
||||
exceptions.text_error_template().render()
|
||||
)
|
||||
|
||||
return glued_template
|
||||
|
||||
@cherrypy.expose
|
||||
@exception_decorator
|
||||
def default(self, attr='', *args, **params):
|
||||
cherrypy.response.status = 404
|
||||
self._check_auth(must_admin=False)
|
||||
is_admin = self._check_admin()
|
||||
return self.temp['404.tmpl'].render(
|
||||
is_admin=is_admin,
|
||||
notification=notification,
|
||||
standalone_groups=user_lonely_groups
|
||||
notifications=self._empty_notification(),
|
||||
)
|
||||
|
||||
@cherrypy.expose
|
||||
|
@ -1096,9 +1186,9 @@ class LdapCherry(object):
|
|||
self._check_auth(must_admin=False)
|
||||
is_admin = self._check_admin()
|
||||
sess = cherrypy.session
|
||||
user = str(sess.get(SESSION_KEY, None))
|
||||
user = sess.get(SESSION_KEY, None)
|
||||
if self.auth_mode == 'none':
|
||||
return self.temp_error.render(
|
||||
return self.temp['error.tmpl'].render(
|
||||
is_admin=is_admin,
|
||||
alert='warning',
|
||||
message="Not accessible with authentication disabled."
|
||||
|
@ -1106,17 +1196,31 @@ class LdapCherry(object):
|
|||
if cherrypy.request.method.upper() == 'POST':
|
||||
params = self._parse_params(params)
|
||||
self._selfmodify(params)
|
||||
user_attrs = self._get_user(user)
|
||||
if user_attrs == {}:
|
||||
return self.temp_error.render(
|
||||
is_admin=is_admin,
|
||||
alert='warning',
|
||||
message="User doesn't exist"
|
||||
)
|
||||
form = self.temp_form.render(
|
||||
attributes=self.attributes.get_selfattributes(),
|
||||
values=user_attrs,
|
||||
modify=True,
|
||||
autofill=False
|
||||
self._add_notification(
|
||||
"Self modification done"
|
||||
)
|
||||
return self.temp_selfmodify.render(form=form, is_admin=is_admin)
|
||||
user_attrs = self._get_user(user)
|
||||
|
||||
try:
|
||||
if user_attrs == {}:
|
||||
return self.temp['error.tmpl'].render(
|
||||
is_admin=is_admin,
|
||||
alert='warning',
|
||||
message="User doesn't exist"
|
||||
)
|
||||
|
||||
form = self.temp['form.tmpl'].render(
|
||||
attributes=self.attributes.get_selfattributes(),
|
||||
values=user_attrs,
|
||||
modify=True,
|
||||
autofill=False
|
||||
)
|
||||
return self.temp['selfmodify.tmpl'].render(
|
||||
form=form,
|
||||
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:
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
from ldapcherry.exceptions import MissingParameter
|
||||
|
||||
|
||||
class Backend:
|
||||
class Backend(object):
|
||||
|
||||
def __init__(self, config, logger, name, attrslist, key):
|
||||
""" Initialize the backend
|
||||
|
|
|
@ -0,0 +1,280 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# vim:set expandtab tabstop=4 shiftwidth=4:
|
||||
#
|
||||
# License MIT
|
||||
# LdapCherry
|
||||
# Copyright (c) 2014 Carpentier Pierre-Francois
|
||||
|
||||
import ldapcherry.backend.backendLdap
|
||||
import cherrypy
|
||||
import ldap
|
||||
import ldap.modlist as modlist
|
||||
import ldap.filter
|
||||
import logging
|
||||
import ldapcherry.backend
|
||||
from ldapcherry.exceptions import UserDoesntExist, GroupDoesntExist
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
|
||||
class CaFileDontExist(Exception):
|
||||
def __init__(self, cafile):
|
||||
self.cafile = cafile
|
||||
self.log = "CA file %(cafile)s don't exist" % {'cafile': cafile}
|
||||
|
||||
|
||||
class MissingAttr(Exception):
|
||||
def __init__(self):
|
||||
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
|
||||
ALL_ATTRS = 3
|
||||
|
||||
# UserAccountControl Attribute/Flag Values
|
||||
# For details, look at:
|
||||
# https://support.microsoft.com/en-us/kb/305144
|
||||
SCRIPT = 0x0001
|
||||
ACCOUNTDISABLE = 0x0002
|
||||
HOMEDIR_REQUIRED = 0x0008
|
||||
LOCKOUT = 0x0010
|
||||
PASSWD_NOTREQD = 0x0020
|
||||
PASSWD_CANT_CHANGE = 0x0040
|
||||
ENCRYPTED_TEXT_PWD_ALLOWED = 0x0080
|
||||
TEMP_DUPLICATE_ACCOUNT = 0x0100
|
||||
NORMAL_ACCOUNT = 0x0200
|
||||
INTERDOMAIN_TRUST_ACCOUNT = 0x0800
|
||||
WORKSTATION_TRUST_ACCOUNT = 0x1000
|
||||
SERVER_TRUST_ACCOUNT = 0x2000
|
||||
DONT_EXPIRE_PASSWORD = 0x10000
|
||||
MNS_LOGON_ACCOUNT = 0x20000
|
||||
SMARTCARD_REQUIRED = 0x40000
|
||||
TRUSTED_FOR_DELEGATION = 0x80000
|
||||
NOT_DELEGATED = 0x100000
|
||||
USE_DES_KEY_ONLY = 0x200000
|
||||
DONT_REQ_PREAUTH = 0x400000
|
||||
PASSWORD_EXPIRED = 0x800000
|
||||
TRUSTED_TO_AUTH_FOR_DELEGATION = 0x1000000
|
||||
PARTIAL_SECRETS_ACCOUNT = 0x04000000
|
||||
# Generated by the followin command:
|
||||
|
||||
# samba-tool group list | \
|
||||
# while read line; \
|
||||
# do
|
||||
# ldapsearch -x -h localhost -D "administrator@dc.ldapcherry.org" \
|
||||
# -w qwertyP455 -b "dc=dc,dc=ldapcherry,dc=org" "(cn=$line)" dn; \
|
||||
# done | grep -e "dn: .*CN=Builtin" | \
|
||||
# sed "s/dn: CN=\(.*\),CN=.*/'\1',/"
|
||||
|
||||
AD_BUILTIN_GROUPS = [
|
||||
'Pre-Windows 2000 Compatible Access',
|
||||
'Windows Authorization Access Group',
|
||||
'Certificate Service DCOM Access',
|
||||
'Network Configuration Operators',
|
||||
'Terminal Server License Servers',
|
||||
'Incoming Forest Trust Builders',
|
||||
'Performance Monitor Users',
|
||||
'Cryptographic Operators',
|
||||
'Distributed COM Users',
|
||||
'Performance Log Users',
|
||||
'Remote Desktop Users',
|
||||
'Account Operators',
|
||||
'Event Log Readers',
|
||||
'Backup Operators',
|
||||
'Server Operators',
|
||||
'Print Operators',
|
||||
'Administrators',
|
||||
'Replicator',
|
||||
'IIS_IUSRS',
|
||||
'Guests',
|
||||
'Users',
|
||||
]
|
||||
|
||||
|
||||
class Backend(ldapcherry.backend.backendLdap.Backend):
|
||||
|
||||
def __init__(self, config, logger, name, attrslist, key):
|
||||
self.config = config
|
||||
self._logger = logger
|
||||
self.backend_name = name
|
||||
self.backend_display_name = self.get_param('display_name')
|
||||
self.domain = self.get_param('domain')
|
||||
self.login = self.get_param('login')
|
||||
basedn = 'dc=' + re.sub(r'\.', ',DC=', self.domain)
|
||||
self.binddn = self.get_param('login') + '@' + self.domain
|
||||
self.bindpassword = self.get_param('password')
|
||||
self.ca = self.get_param('ca', False)
|
||||
self.checkcert = self.get_param('checkcert', 'on')
|
||||
self.starttls = self.get_param('starttls', 'off')
|
||||
self.uri = self.get_param('uri')
|
||||
self.timeout = self.get_param('timeout', 1)
|
||||
self.userdn = 'CN=Users,' + basedn
|
||||
self.groupdn = self.userdn
|
||||
self.builtin = 'CN=Builtin,' + basedn
|
||||
self.user_filter_tmpl = '(sAMAccountName=%(username)s)'
|
||||
self.group_filter_tmpl = '(member=%(userdn)s)'
|
||||
self.search_filter_tmpl = '(&(|(sAMAccountName=%(searchstring)s)' \
|
||||
'(cn=%(searchstring)s*)' \
|
||||
'(name=%(searchstring)s*)' \
|
||||
'(sn=%(searchstring)s*)' \
|
||||
'(givenName=%(searchstring)s*)' \
|
||||
'(cn=%(searchstring)s*))' \
|
||||
'(&(objectClass=person)' \
|
||||
'(objectClass=user)' \
|
||||
'(!(objectClass=computer)))' \
|
||||
')'
|
||||
self.dn_user_attr = 'cn'
|
||||
self.key = 'sAMAccountName'
|
||||
self.objectclasses = [
|
||||
self._byte_p23('top'),
|
||||
self._byte_p23('person'),
|
||||
self._byte_p23('organizationalPerson'),
|
||||
self._byte_p23('user'),
|
||||
self._byte_p23('posixAccount'),
|
||||
]
|
||||
self.group_attrs = {
|
||||
'member': "%(dn)s"
|
||||
}
|
||||
|
||||
self.attrlist = []
|
||||
self.group_attrs_keys = []
|
||||
for a in attrslist:
|
||||
self.attrlist.append(self._byte_p2(a))
|
||||
|
||||
if self._byte_p2('cn') not in self.attrlist:
|
||||
raise MissingAttr()
|
||||
|
||||
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._byte_p2(searchfilter)
|
||||
ldap_client = self._bind()
|
||||
try:
|
||||
r = ldap_client.search_s(
|
||||
groupdn,
|
||||
ldap.SCOPE_SUBTREE,
|
||||
searchfilter,
|
||||
attrlist=['CN']
|
||||
)
|
||||
except Exception as e:
|
||||
ldap_client.unbind_s()
|
||||
self._exception_handler(e)
|
||||
|
||||
ldap_client.unbind_s()
|
||||
return r
|
||||
|
||||
def _build_groupdn(self, groups):
|
||||
ad_groups = []
|
||||
for group in groups:
|
||||
if group in AD_BUILTIN_GROUPS:
|
||||
ad_groups.append('cn=' + group + ',' + self.builtin)
|
||||
else:
|
||||
ad_groups.append('cn=' + group + ',' + self.groupdn)
|
||||
return ad_groups
|
||||
|
||||
def _set_password(self, name, password, by_cn=True):
|
||||
unicode_pass = '\"' + password + '\"'
|
||||
password_value = unicode_pass.encode('utf-16-le')
|
||||
|
||||
ldap_client = self._bind()
|
||||
|
||||
if by_cn:
|
||||
dn = self._byte_p2('CN=%(cn)s,%(user_dn)s' % {
|
||||
'cn': name,
|
||||
'user_dn': self.userdn
|
||||
})
|
||||
else:
|
||||
dn = self._byte_p2(name)
|
||||
|
||||
attrs = {}
|
||||
|
||||
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'] = self._modlist(
|
||||
self._tobyte(NORMAL_ACCOUNT)
|
||||
)
|
||||
ldif = modlist.modifyModlist({'UserAccountControl': 'tmp'}, attrs)
|
||||
ldap_client.modify_s(dn, ldif)
|
||||
|
||||
def add_user(self, attrs):
|
||||
password = attrs['unicodePwd']
|
||||
del(attrs['unicodePwd'])
|
||||
super(Backend, self).add_user(attrs)
|
||||
self._set_password(attrs['cn'], password)
|
||||
|
||||
def set_attrs(self, username, attrs):
|
||||
if 'unicodePwd' in attrs:
|
||||
password = attrs['unicodePwd']
|
||||
del(attrs['unicodePwd'])
|
||||
userdn = self._get_user(self._byte_p2(username), NO_ATTR)
|
||||
self._set_password(userdn, password, False)
|
||||
super(Backend, self).set_attrs(username, attrs)
|
||||
|
||||
def add_to_groups(self, username, groups):
|
||||
ad_groups = self._build_groupdn(groups)
|
||||
super(Backend, self).add_to_groups(username, ad_groups)
|
||||
|
||||
def del_from_groups(self, username, groups):
|
||||
ad_groups = self._build_groupdn(groups)
|
||||
super(Backend, self).del_from_groups(username, ad_groups)
|
||||
|
||||
def get_groups(self, username):
|
||||
username = ldap.filter.escape_filter_chars(username)
|
||||
userdn = self._get_user(self._byte_p2(username), NO_ATTR)
|
||||
|
||||
searchfilter = self.group_filter_tmpl % {
|
||||
'userdn': userdn,
|
||||
'username': username
|
||||
}
|
||||
|
||||
groups = self._search_group(searchfilter, self.groupdn)
|
||||
groups = groups + self._search_group(searchfilter, self.builtin)
|
||||
ret = []
|
||||
self._logger(
|
||||
severity=logging.DEBUG,
|
||||
msg="%(backend)s: groups of '%(user)s' are %(groups)s" % {
|
||||
'user': username,
|
||||
'groups': str(groups),
|
||||
'backend': self.backend_name
|
||||
}
|
||||
)
|
||||
|
||||
for entry in groups:
|
||||
ret.append(self._uni(entry[1]['cn'][0]))
|
||||
return ret
|
||||
|
||||
def auth(self, username, password):
|
||||
|
||||
binddn = username + '@' + self.domain
|
||||
if binddn is not None:
|
||||
ldap_client = self._connect()
|
||||
try:
|
||||
ldap_client.simple_bind_s(
|
||||
self._byte_p2(binddn),
|
||||
self._byte_p2(password)
|
||||
)
|
||||
except ldap.INVALID_CREDENTIALS:
|
||||
ldap_client.unbind_s()
|
||||
return False
|
||||
ldap_client.unbind_s()
|
||||
return True
|
||||
else:
|
||||
return False
|
|
@ -0,0 +1,204 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# vim:set expandtab tabstop=4 shiftwidth=4:
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
# LdapCherry
|
||||
# Copyright (c) 2014 Carpentier Pierre-Francois
|
||||
|
||||
# This is a demo backend
|
||||
|
||||
|
||||
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):
|
||||
|
||||
def __init__(self, config, logger, name, attrslist, key):
|
||||
""" Initialize the backend
|
||||
|
||||
:param config: the configuration of the backend
|
||||
:type config: dict {'config key': 'value'}
|
||||
:param logger: the cherrypy error logger object
|
||||
:type logger: python logger
|
||||
:param name: id of the backend
|
||||
:type name: string
|
||||
:param attrslist: list of the backend attributes
|
||||
:type attrslist: list of strings
|
||||
:param key: the key attribute
|
||||
:type key: string
|
||||
"""
|
||||
self.config = config
|
||||
self._logger = logger
|
||||
self.users = {}
|
||||
self.backend_name = name
|
||||
admin_user = self.get_param('admin.user', 'admin')
|
||||
admin_password = self.get_param('admin.password', 'admin')
|
||||
admin_groups = set(
|
||||
self._basic_splitter(self.get_param('admin.groups'))
|
||||
)
|
||||
basic_user = self.get_param('basic.user', 'user')
|
||||
basic_password = self.get_param('basic.password', 'user')
|
||||
basic_groups = set(
|
||||
self._basic_splitter(self.get_param('basic.groups'))
|
||||
)
|
||||
pwd_attr = self.get_param('pwd_attr')
|
||||
self.search_attrs = set(
|
||||
re.split(r'\W+', self.get_param('search_attributes')),
|
||||
)
|
||||
self.pwd_attr = pwd_attr
|
||||
self.admin_user = admin_user
|
||||
self.basic_user = basic_user
|
||||
self.key = key
|
||||
self.users[admin_user] = {
|
||||
key: admin_user,
|
||||
pwd_attr: admin_password,
|
||||
'groups': admin_groups,
|
||||
}
|
||||
self.users[basic_user] = {
|
||||
key: basic_user,
|
||||
pwd_attr: basic_password,
|
||||
'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')
|
||||
|
||||
def auth(self, username, password):
|
||||
""" Check authentication against the backend
|
||||
|
||||
:param username: 'key' attribute of the user
|
||||
:type username: string
|
||||
:param password: password of the user
|
||||
:type password: string
|
||||
:rtype: boolean (True is authentication success, False otherwise)
|
||||
"""
|
||||
if username not in self.users:
|
||||
return False
|
||||
elif self.users[username][self.pwd_attr] == password:
|
||||
return True
|
||||
return False
|
||||
|
||||
def add_user(self, attrs):
|
||||
""" Add a user to the backend
|
||||
|
||||
:param attrs: attributes of the user
|
||||
:type attrs: dict ({<attr>: <value>})
|
||||
|
||||
.. warning:: raise UserAlreadyExists if user already exists
|
||||
"""
|
||||
username = attrs[self.key]
|
||||
if username in self.users:
|
||||
raise UserAlreadyExists(username, self.backend_name)
|
||||
self.users[username] = attrs
|
||||
self.users[username]['groups'] = set([])
|
||||
|
||||
def del_user(self, username):
|
||||
""" Delete a user from the backend
|
||||
|
||||
:param username: 'key' attribute of the user
|
||||
:type username: string
|
||||
|
||||
"""
|
||||
self._check_fix_users(username)
|
||||
try:
|
||||
del self.users[username]
|
||||
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
|
||||
|
||||
:param username: 'key' attribute of the user
|
||||
:type username: string
|
||||
:param attrs: attributes of the user
|
||||
:type attrs: dict ({<attr>: <value>})
|
||||
"""
|
||||
self._check_fix_users(username)
|
||||
for attr in attrs:
|
||||
self.users[username][attr] = attrs[attr]
|
||||
|
||||
def add_to_groups(self, username, groups):
|
||||
""" Add a user to a list of groups
|
||||
|
||||
:param username: 'key' attribute of the user
|
||||
:type username: string
|
||||
:param groups: list of groups
|
||||
:type groups: list of strings
|
||||
"""
|
||||
self._check_fix_users(username)
|
||||
current_groups = self.users[username]['groups']
|
||||
new_groups = current_groups | set(groups)
|
||||
self.users[username]['groups'] = new_groups
|
||||
|
||||
def del_from_groups(self, username, groups):
|
||||
""" Delete a user from a list of groups
|
||||
|
||||
:param username: 'key' attribute of the user
|
||||
:type username: string
|
||||
:param groups: list of groups
|
||||
:type groups: list of strings
|
||||
|
||||
.. warning:: raise GroupDoesntExist if group doesn't exist
|
||||
"""
|
||||
self._check_fix_users(username)
|
||||
current_groups = self.users[username]['groups']
|
||||
new_groups = current_groups - set(groups)
|
||||
self.users[username]['groups'] = new_groups
|
||||
|
||||
def search(self, searchstring):
|
||||
""" Search backend for users
|
||||
|
||||
:param searchstring: the search string
|
||||
:type searchstring: string
|
||||
:rtype: dict of dict ( {<user attr key>: {<attr>: <value>}} )
|
||||
"""
|
||||
ret = {}
|
||||
for user in self.users:
|
||||
match = False
|
||||
for attr in self.search_attrs:
|
||||
if attr not in self.users[user]:
|
||||
pass
|
||||
elif re.search(searchstring + '.*', self.users[user][attr]):
|
||||
match = True
|
||||
if match:
|
||||
ret[user] = self.users[user]
|
||||
return ret
|
||||
|
||||
def get_user(self, username):
|
||||
""" Get a user's attributes
|
||||
|
||||
:param username: 'key' attribute of the user
|
||||
:type username: string
|
||||
:rtype: dict ( {<attr>: <value>} )
|
||||
|
||||
.. warning:: raise UserDoesntExist if user doesn't exist
|
||||
"""
|
||||
try:
|
||||
return self.users[username]
|
||||
except Exception as e:
|
||||
raise UserDoesntExist(username, self.backend_name)
|
||||
|
||||
def get_groups(self, username):
|
||||
""" Get a user's groups
|
||||
|
||||
:param username: 'key' attribute of the user
|
||||
:type username: string
|
||||
:rtype: list of groups
|
||||
"""
|
||||
try:
|
||||
return self.users[username]['groups']
|
||||
except Exception as e:
|
||||
raise UserDoesntExist(username, self.backend_name)
|
|
@ -11,22 +11,37 @@ import ldap.modlist as modlist
|
|||
import ldap.filter
|
||||
import logging
|
||||
import ldapcherry.backend
|
||||
from ldapcherry.exceptions import UserDoesntExist, GroupDoesntExist
|
||||
import sys
|
||||
from ldapcherry.exceptions import UserDoesntExist, \
|
||||
GroupDoesntExist, \
|
||||
UserAlreadyExists
|
||||
import os
|
||||
import re
|
||||
if sys.version < '3':
|
||||
from sets import Set as set
|
||||
|
||||
|
||||
class DelUserDontExists(Exception):
|
||||
def __init__(self, user):
|
||||
self.user = user
|
||||
self.log = "cannot remove user, user <%(user)s> does not exist" % \
|
||||
{'user': user}
|
||||
PYTHON_LDAP_MAJOR_VERSION = ldap.__version__[0]
|
||||
|
||||
|
||||
class CaFileDontExist(Exception):
|
||||
def __init__(self, cafile):
|
||||
self.cafile = cafile
|
||||
self.log = "CA file %(cafile)s don't exist" % {'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
|
||||
|
@ -37,9 +52,12 @@ ALL_ATTRS = 3
|
|||
class Backend(ldapcherry.backend.Backend):
|
||||
|
||||
def __init__(self, config, logger, name, attrslist, key):
|
||||
"""Backend initialization"""
|
||||
# Recover all the parameters
|
||||
self.config = config
|
||||
self._logger = logger
|
||||
self.backend_name = name
|
||||
self.backend_display_name = self.get_param('display_name')
|
||||
self.binddn = self.get_param('binddn')
|
||||
self.bindpassword = self.get_param('password')
|
||||
self.ca = self.get_param('ca', False)
|
||||
|
@ -55,19 +73,27 @@ class Backend(ldapcherry.backend.Backend):
|
|||
self.dn_user_attr = self.get_param('dn_user_attr')
|
||||
self.objectclasses = []
|
||||
self.key = key
|
||||
for o in re.split('\W+', self.get_param('objectclasses')):
|
||||
self.objectclasses.append(self._str(o))
|
||||
# objectclasses parameter is a coma separated list in configuration
|
||||
# split it to get a real list, and convert it to bytes
|
||||
for o in re.split(r'\W+', self.get_param('objectclasses')):
|
||||
self.objectclasses.append(self._byte_p23(o))
|
||||
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):
|
||||
""" Exception handling"""
|
||||
et = type(e)
|
||||
if et is ldap.OPERATIONS_ERROR:
|
||||
self._logger(
|
||||
|
@ -106,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,
|
||||
|
@ -117,10 +143,11 @@ class Backend(ldapcherry.backend.Backend):
|
|||
severity=logging.ERROR,
|
||||
msg="Access error on '" +
|
||||
self.backend_name +
|
||||
"' backend, please check your acls in this backend",
|
||||
"' backend, please check your acls in 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,
|
||||
|
@ -128,11 +155,44 @@ class Backend(ldapcherry.backend.Backend):
|
|||
else:
|
||||
self._logger(
|
||||
severity=logging.ERROR,
|
||||
msg="unknow ldap exception in ldap backend",
|
||||
msg="unknow exception in backend " + self.backend_name,
|
||||
)
|
||||
raise e
|
||||
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)
|
||||
ldap.set_option(ldap.OPT_REFERRALS, 0)
|
||||
ldap.set_option(ldap.OPT_TIMEOUT, self.timeout)
|
||||
|
@ -140,7 +200,9 @@ class Backend(ldapcherry.backend.Backend):
|
|||
ldap.set_option(ldap.OPT_X_TLS_DEMAND, True)
|
||||
else:
|
||||
ldap.set_option(ldap.OPT_X_TLS_DEMAND, False)
|
||||
# set the CA file if declared and if necessary
|
||||
if self.ca and self.checkcert == 'on':
|
||||
# check if the CA file actually exists
|
||||
if os.path.isfile(self.ca):
|
||||
ldap.set_option(ldap.OPT_X_TLS_CACERTFILE, self.ca)
|
||||
else:
|
||||
|
@ -176,6 +238,7 @@ class Backend(ldapcherry.backend.Backend):
|
|||
return ldap_client
|
||||
|
||||
def _bind(self):
|
||||
"""bind to the ldap with the technical account"""
|
||||
ldap_client = self._connect()
|
||||
try:
|
||||
ldap_client.simple_bind_s(self.binddn, self.bindpassword)
|
||||
|
@ -185,6 +248,7 @@ class Backend(ldapcherry.backend.Backend):
|
|||
return ldap_client
|
||||
|
||||
def _search(self, searchfilter, attrs, basedn):
|
||||
"""Generic search"""
|
||||
if attrs == NO_ATTR:
|
||||
attrlist = []
|
||||
elif attrs == DISPLAYED_ATTRS:
|
||||
|
@ -197,6 +261,17 @@ 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:
|
||||
r = ldap_client.search_s(
|
||||
|
@ -210,39 +285,128 @@ class Backend(ldapcherry.backend.Backend):
|
|||
self._exception_handler(e)
|
||||
|
||||
ldap_client.unbind_s()
|
||||
return r
|
||||
|
||||
# python-ldap doesn't know utf-8,
|
||||
# it treates everything as bytes.
|
||||
# So it's necessary to reencode
|
||||
# it's output in utf-8.
|
||||
ret = []
|
||||
for entry in r:
|
||||
uni_dn = self._uni(entry[0])
|
||||
uni_attrs = {}
|
||||
for attr in entry[1]:
|
||||
if type(entry[1][attr]) is list:
|
||||
tmp = []
|
||||
for value in entry[1][attr]:
|
||||
tmp.append(self._uni(value))
|
||||
else:
|
||||
tmp = self._uni(entry[1][attr])
|
||||
uni_attrs[self._uni(attr)] = tmp
|
||||
ret.append((uni_dn, uni_attrs))
|
||||
return ret
|
||||
|
||||
def _get_user(self, username, attrs=ALL_ATTRS):
|
||||
"""Get a user from the ldap"""
|
||||
|
||||
username = ldap.filter.escape_filter_chars(username)
|
||||
user_filter = self.user_filter_tmpl % {
|
||||
'username': username
|
||||
'username': self._uni(username)
|
||||
}
|
||||
|
||||
r = self._search(user_filter, attrs, self.userdn)
|
||||
r = self._search(self._byte_p2(user_filter), attrs, self.userdn)
|
||||
|
||||
if len(r) == 0:
|
||||
return None
|
||||
|
||||
# if NO_ATTR, only return the DN
|
||||
if attrs == NO_ATTR:
|
||||
dn_entry = r[0][0]
|
||||
# in other cases, return everything (dn + attributes)
|
||||
else:
|
||||
dn_entry = r[0]
|
||||
return dn_entry
|
||||
|
||||
def _str(self, s):
|
||||
# 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 _uni(self, s):
|
||||
return s.decode('utf-8')
|
||||
if sys.version < '3':
|
||||
def _byte_p2(self, s):
|
||||
"""unicode -> bytes conversion (python 2)"""
|
||||
if s is None:
|
||||
return None
|
||||
return s.encode('utf-8')
|
||||
|
||||
def _byte_p3(self, s):
|
||||
"""pass through (does something in python 3)"""
|
||||
return s
|
||||
|
||||
def _uni(self, s):
|
||||
"""bytes -> unicode conversion"""
|
||||
if s is None:
|
||||
return None
|
||||
return s.decode('utf-8', 'ignore')
|
||||
|
||||
def attrs_pretreatment(self, attrs):
|
||||
attrs_srt = {}
|
||||
for a in attrs:
|
||||
attrs_srt[self._byte_p2(a)] = self._modlist(
|
||||
self._byte_p2(attrs[a])
|
||||
)
|
||||
return attrs_srt
|
||||
else:
|
||||
def _byte_p2(self, s):
|
||||
"""pass through (does something in python 2)"""
|
||||
return s
|
||||
|
||||
def _byte_p3(self, s):
|
||||
"""unicode -> bytes conversion"""
|
||||
if s is None:
|
||||
return None
|
||||
return s.encode('utf-8')
|
||||
|
||||
def _uni(self, s):
|
||||
"""bytes -> unicode conversion"""
|
||||
if s is None:
|
||||
return None
|
||||
if type(s) is not str:
|
||||
return s.decode('utf-8', 'ignore')
|
||||
else:
|
||||
return s
|
||||
|
||||
def attrs_pretreatment(self, attrs):
|
||||
attrs_srt = {}
|
||||
for a in attrs:
|
||||
attrs_srt[self._byte_p2(a)] = self._modlist(
|
||||
self._byte_p3(attrs[a])
|
||||
)
|
||||
return attrs_srt
|
||||
|
||||
def auth(self, username, password):
|
||||
"""Authentication of a user"""
|
||||
|
||||
binddn = self._get_user(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(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
|
||||
|
@ -251,91 +415,149 @@ class Backend(ldapcherry.backend.Backend):
|
|||
else:
|
||||
return False
|
||||
|
||||
if PYTHON_LDAP_MAJOR_VERSION == '2':
|
||||
@staticmethod
|
||||
def _modlist(in_attr):
|
||||
return in_attr
|
||||
|
||||
else:
|
||||
@staticmethod
|
||||
def _modlist(in_attr):
|
||||
return [in_attr]
|
||||
|
||||
def add_user(self, attrs):
|
||||
"""add a user"""
|
||||
ldap_client = self._bind()
|
||||
attrs_str = {}
|
||||
for a in attrs:
|
||||
attrs_str[self._str(a)] = self._str(attrs[a])
|
||||
attrs_str['objectClass'] = self.objectclasses
|
||||
# encoding crap
|
||||
attrs_srt = self.attrs_pretreatment(attrs)
|
||||
|
||||
attrs_srt[self._byte_p2('objectClass')] = self.objectclasses
|
||||
# construct is DN
|
||||
dn = \
|
||||
self.dn_user_attr +\
|
||||
'=' +\
|
||||
attrs[self.dn_user_attr] +\
|
||||
',' +\
|
||||
self.userdn
|
||||
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:
|
||||
raise UserAlreadyExists(attrs[self.key], self.backend_name)
|
||||
except Exception as e:
|
||||
ldap_client.unbind_s()
|
||||
self._exception_handler(e)
|
||||
ldap_client.unbind_s()
|
||||
|
||||
def del_user(self, username):
|
||||
"""delete a user"""
|
||||
ldap_client = self._bind()
|
||||
dn = self._get_user(username, NO_ATTR)
|
||||
# recover the user dn
|
||||
dn = self._byte_p2(self._get_user(self._byte_p2(username), NO_ATTR))
|
||||
# delete
|
||||
if dn is not None:
|
||||
ldap_client.delete_s(dn)
|
||||
else:
|
||||
raise DelUserDontExists(username)
|
||||
ldap_client.unbind_s()
|
||||
raise UserDoesntExist(username, self.backend_name)
|
||||
ldap_client.unbind_s()
|
||||
|
||||
def set_attrs(self, username, attrs):
|
||||
""" set user attributes"""
|
||||
ldap_client = self._bind()
|
||||
tmp = self._get_user(username, ALL_ATTRS)
|
||||
dn = 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:
|
||||
content = self._str(attrs[attr])
|
||||
attr = self._str(attr)
|
||||
new = {attr: content}
|
||||
if attr in old_attrs:
|
||||
old = {attr: old_attrs[attr]}
|
||||
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(
|
||||
dn,
|
||||
ldap.dn.dn2str([[(battr, bcontent, 1)]])
|
||||
)
|
||||
dn = ldap.dn.dn2str(
|
||||
[[(battr, bcontent, 1)]] + ldap.dn.str2dn(dn)[1:]
|
||||
)
|
||||
else:
|
||||
old = {}
|
||||
ldif = modlist.modifyModlist(old, new)
|
||||
try:
|
||||
ldap_client.modify_s(dn, ldif)
|
||||
except Exception as e:
|
||||
ldap_client.unbind_s()
|
||||
self._exception_handler(e)
|
||||
# if attr is already set, replace the value
|
||||
# (see dict old passed to modifyModlist)
|
||||
if attr in old_attrs:
|
||||
if type(old_attrs[attr]) is list:
|
||||
tmp = []
|
||||
for value in old_attrs[attr]:
|
||||
tmp.append(self._byte_p2(value))
|
||||
bold_value = tmp
|
||||
else:
|
||||
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:
|
||||
ldap_client.unbind_s()
|
||||
self._exception_handler(e)
|
||||
|
||||
ldap_client.unbind_s()
|
||||
|
||||
def add_to_groups(self, username, groups):
|
||||
ldap_client = self._bind()
|
||||
tmp = self._get_user(username, ALL_ATTRS)
|
||||
# recover dn of the user and his attributes
|
||||
tmp = self._get_user(self._byte_p2(username), ALL_ATTRS)
|
||||
dn = tmp[0]
|
||||
attrs = tmp[1]
|
||||
attrs['dn'] = dn
|
||||
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:
|
||||
content = self._str(self.group_attrs[attr] % attrs)
|
||||
# fill the content template
|
||||
content = self._byte_p2(self.group_attrs[attr] % attrs)
|
||||
self._logger(
|
||||
severity=logging.DEBUG,
|
||||
msg="%(backend)s: adding user '%(user)s'"
|
||||
" with dn '%(dn)s' to group '%(group)s' by"
|
||||
" setting '%(attr)s' to '%(content)s'" % {
|
||||
'user': username,
|
||||
'dn': dn,
|
||||
'group': group,
|
||||
'dn': self._uni(dn),
|
||||
'group': self._uni(group),
|
||||
'attr': attr,
|
||||
'content': content,
|
||||
'content': self._uni(content),
|
||||
'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)
|
||||
except ldap.TYPE_OR_VALUE_EXISTS as e:
|
||||
# if already member, not a big deal, just log it and continue
|
||||
except (ldap.TYPE_OR_VALUE_EXISTS, ldap.ALREADY_EXISTS) as e:
|
||||
self._logger(
|
||||
severity=logging.INFO,
|
||||
msg="%(backend)s: user '%(user)s'"
|
||||
" already member of group '%(group)s'"
|
||||
"(attribute '%(attr)s')" % {
|
||||
" (attribute '%(attr)s')" % {
|
||||
'user': username,
|
||||
'group': group,
|
||||
'group': self._uni(group),
|
||||
'attr': attr,
|
||||
'backend': self.backend_name
|
||||
}
|
||||
|
@ -348,16 +570,23 @@ class Backend(ldapcherry.backend.Backend):
|
|||
ldap_client.unbind_s()
|
||||
|
||||
def del_from_groups(self, username, groups):
|
||||
"""Delete user from groups"""
|
||||
# it follows the same logic than add_to_groups
|
||||
# but with MOD_DELETE
|
||||
ldap_client = self._bind()
|
||||
tmp = self._get_user(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
|
||||
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:
|
||||
|
@ -367,7 +596,7 @@ class Backend(ldapcherry.backend.Backend):
|
|||
" wasn't member of group '%(group)s'"
|
||||
" (attribute '%(attr)s')" % {
|
||||
'user': username,
|
||||
'group': group,
|
||||
'group': self._uni(group),
|
||||
'attr': attr,
|
||||
'backend': self.backend_name
|
||||
}
|
||||
|
@ -378,43 +607,50 @@ class Backend(ldapcherry.backend.Backend):
|
|||
ldap_client.unbind_s()
|
||||
|
||||
def search(self, searchstring):
|
||||
ret = {}
|
||||
|
||||
searchstring = ldap.filter.escape_filter_chars(searchstring)
|
||||
"""Search users"""
|
||||
# escape special char to avoid injection
|
||||
searchstring = ldap.filter.escape_filter_chars(
|
||||
self._byte_p2(searchstring)
|
||||
)
|
||||
# fill the search string template
|
||||
searchfilter = self.search_filter_tmpl % {
|
||||
'searchstring': searchstring
|
||||
}
|
||||
|
||||
ret = {}
|
||||
# search an process the result a little
|
||||
for u in self._search(searchfilter, DISPLAYED_ATTRS, self.userdn):
|
||||
attrs = {}
|
||||
attrs_tmp = u[1]
|
||||
for attr in attrs_tmp:
|
||||
value_tmp = attrs_tmp[attr]
|
||||
if len(value_tmp) == 1:
|
||||
attrs[attr] = self._uni(value_tmp[0])
|
||||
attrs[attr] = value_tmp[0]
|
||||
else:
|
||||
attrs[attr] = map(self._uni, value_tmp)
|
||||
attrs[attr] = value_tmp
|
||||
|
||||
if self.key in attrs:
|
||||
ret[attrs[self.key]] = attrs
|
||||
return ret
|
||||
|
||||
def get_user(self, username):
|
||||
"""Gest a specific user"""
|
||||
ret = {}
|
||||
tmp = self._get_user(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]
|
||||
for attr in attrs_tmp:
|
||||
value_tmp = attrs_tmp[attr]
|
||||
if len(value_tmp) == 1:
|
||||
ret[attr] = self._uni(value_tmp[0])
|
||||
ret[attr] = value_tmp[0]
|
||||
else:
|
||||
ret[attr] = map(self._uni, value_tmp)
|
||||
ret[attr] = value_tmp
|
||||
return ret
|
||||
|
||||
def get_groups(self, username):
|
||||
|
||||
username = ldap.filter.escape_filter_chars(username)
|
||||
"""Get all groups of a user"""
|
||||
username = ldap.filter.escape_filter_chars(self._byte_p2(username))
|
||||
userdn = self._get_user(username, NO_ATTR)
|
||||
|
||||
searchfilter = self.group_filter_tmpl % {
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# vim:set expandtab tabstop=4 shiftwidth=4:
|
||||
#
|
||||
# License GPLv3
|
||||
# LdapCherry
|
||||
# Copyright (c) 2014 Carpentier Pierre-Francois
|
||||
|
||||
import ldapcherry.backend
|
||||
|
||||
|
||||
class Backend(ldapcherry.backend.Backend):
|
||||
|
||||
def __init__(self, config, logger, name, attrslist, key):
|
||||
pass
|
|
@ -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)
|
||||
|
@ -17,8 +17,8 @@ from ldapcherry import LdapCherry
|
|||
|
||||
|
||||
def start(configfile=None, daemonize=False, environment=None,
|
||||
fastcgi=False, scgi=False, pidfile=None, imports=None,
|
||||
cgi=False):
|
||||
fastcgi=False, scgi=False, pidfile=None,
|
||||
cgi=False, debug=False):
|
||||
"""Subscribe all engine plugins and start the engine."""
|
||||
sys.path = [''] + sys.path
|
||||
|
||||
|
@ -47,10 +47,13 @@ def start(configfile=None, daemonize=False, environment=None,
|
|||
instance = LdapCherry()
|
||||
app = cherrypy.tree.mount(instance, '/', configfile)
|
||||
cherrypy.config.update(configfile)
|
||||
instance.reload(app.config)
|
||||
instance.reload(app.config, debug)
|
||||
|
||||
engine = cherrypy.engine
|
||||
|
||||
# Turn off autoreload
|
||||
cherrypy.config.update({'engine.autoreload.on': False})
|
||||
|
||||
if environment is not None:
|
||||
cherrypy.config.update({'environment': environment})
|
||||
|
||||
|
@ -73,8 +76,6 @@ def start(configfile=None, daemonize=False, environment=None,
|
|||
"scgi options.", 'ENGINE')
|
||||
sys.exit(1)
|
||||
elif fastcgi or scgi or cgi:
|
||||
# Turn off autoreload when using *cgi.
|
||||
cherrypy.config.update({'engine.autoreload_on': False})
|
||||
# Turn off the default HTTP server (which is subscribed by default).
|
||||
cherrypy.server.unsubscribe()
|
||||
|
||||
|
@ -94,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()
|
||||
|
@ -122,6 +123,8 @@ if __name__ == '__main__':
|
|||
help="store the process id in the given file")
|
||||
p.add_option('-P', '--Path', action="append", dest='Path',
|
||||
help="add the given paths to sys.path")
|
||||
p.add_option('-D', '--debug', action="store_true", dest='debug',
|
||||
help="debug to stderr in foreground")
|
||||
options, args = p.parse_args()
|
||||
|
||||
if options.Path:
|
||||
|
@ -138,4 +141,8 @@ if __name__ == '__main__':
|
|||
|
||||
start(options.config, options.daemonize,
|
||||
options.environment, options.fastcgi, options.scgi,
|
||||
options.pidfile, options.cgi)
|
||||
options.pidfile, options.cgi, options.debug)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -6,6 +6,7 @@
|
|||
# Copyright (c) 2014 Carpentier Pierre-Francois
|
||||
|
||||
import string
|
||||
import cherrypy
|
||||
|
||||
|
||||
class MissingParameter(Exception):
|
||||
|
@ -45,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):
|
||||
|
@ -89,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):
|
||||
|
@ -126,7 +128,7 @@ class WrongParamValue(Exception):
|
|||
self.param = param
|
||||
possible_values_str = string.join(possible_values, ', ')
|
||||
self.log = \
|
||||
"wrong value for param '%(param)s' in section '%(section)s'"\
|
||||
"wrong value for param '%(param)s' in section '%(section)s'" \
|
||||
", possible values are [%(values)s]" % \
|
||||
{
|
||||
'param': param,
|
||||
|
@ -166,7 +168,7 @@ class PasswordAttributesCollision(Exception):
|
|||
self.key = key
|
||||
self.log = \
|
||||
"key '" + key + "' type is password," \
|
||||
" keys '" + key + "1' and '" + key + "2'"\
|
||||
" keys '" + key + "1' and '" + key + "2'" \
|
||||
" are reserved and cannot be used"
|
||||
|
||||
|
||||
|
@ -214,3 +216,55 @@ class GroupDoesntExist(Exception):
|
|||
"group '" + group + "'" \
|
||||
" does not exist" \
|
||||
" 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:
|
||||
return func(self, *args, **kwargs)
|
||||
except cherrypy.HTTPRedirect as e:
|
||||
raise e
|
||||
except cherrypy.HTTPError as e:
|
||||
raise e
|
||||
except Exception as e:
|
||||
cherrypy.response.status = 500
|
||||
self._handle_exception(e)
|
||||
username = self._check_session()
|
||||
if not username:
|
||||
return self.temp['service_unavailable.tmpl'].render()
|
||||
is_admin = self._check_admin()
|
||||
et = type(e)
|
||||
if et is UserDoesntExist:
|
||||
user = e.user
|
||||
return self.temp['error.tmpl'].render(
|
||||
is_admin=is_admin,
|
||||
alert='danger',
|
||||
message="User '" + user + "' does not exist"
|
||||
)
|
||||
elif et is UserAlreadyExists:
|
||||
user = e.user
|
||||
cherrypy.response.status = 400
|
||||
return self.temp['error.tmpl'].render(
|
||||
is_admin=is_admin,
|
||||
alert='warning',
|
||||
message="User '" + user + "' already exist"
|
||||
)
|
||||
elif et is GroupDoesntExist:
|
||||
group = e.group
|
||||
return self.temp['error.tmpl'].render(
|
||||
is_admin=is_admin,
|
||||
alert='danger',
|
||||
message="Missing group, please check logs for details"
|
||||
)
|
||||
else:
|
||||
return self.temp['error.tmpl'].render(
|
||||
is_admin=is_admin,
|
||||
alert='danger',
|
||||
message="An error occured, please check logs for details"
|
||||
)
|
||||
return ret
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# vim:set expandtab tabstop=4 shiftwidth=4:
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
# ldapCherry
|
||||
# Copyright (c) 2014 Carpentier Pierre-Francois
|
||||
|
||||
# Generic imports
|
||||
import sys
|
||||
import traceback
|
||||
import logging
|
||||
import logging.handlers
|
||||
import cherrypy
|
||||
|
||||
|
||||
# Custom log function to override weird error.log function
|
||||
# of cherrypy
|
||||
def syslog_error(
|
||||
msg='',
|
||||
context='',
|
||||
severity=logging.INFO,
|
||||
traceback=False
|
||||
):
|
||||
|
||||
if traceback and msg == '':
|
||||
msg = 'Python Exception:'
|
||||
if context == '':
|
||||
cherrypy.log.error_log.log(severity, msg)
|
||||
else:
|
||||
cherrypy.log.error_log.log(
|
||||
severity,
|
||||
' '.join((context, msg))
|
||||
)
|
||||
if traceback:
|
||||
import traceback
|
||||
try:
|
||||
exc = sys.exc_info()
|
||||
if exc == (None, None, None):
|
||||
cherrypy.log.error_log.log(severity, msg)
|
||||
# log each line of the exception
|
||||
# in a separate log for lisibility
|
||||
for l in traceback.format_exception(*exc):
|
||||
cherrypy.log.error_log.log(severity, l)
|
||||
finally:
|
||||
del exc
|
||||
|
||||
|
||||
def get_loglevel(level):
|
||||
""" return logging level object
|
||||
corresponding to a given level passed as
|
||||
a string
|
||||
@str level: name of a syslog log level
|
||||
@rtype: logging, logging level from logging module
|
||||
"""
|
||||
if level == 'debug':
|
||||
return logging.DEBUG
|
||||
elif level == 'notice':
|
||||
return logging.INFO
|
||||
elif level == 'info':
|
||||
return logging.INFO
|
||||
elif level == 'warning' or level == 'warn':
|
||||
return logging.WARNING
|
||||
elif level == 'error' or level == 'err':
|
||||
return logging.ERROR
|
||||
elif level == 'critical' or level == 'crit':
|
||||
return logging.CRITICAL
|
||||
elif level == 'alert':
|
||||
return logging.CRITICAL
|
||||
elif level == 'emergency' or level == 'emerg':
|
||||
return logging.CRITICAL
|
||||
else:
|
||||
return logging.INFO
|
|
@ -19,21 +19,21 @@ class PPolicy(ldapcherry.ppolicy.PPolicy):
|
|||
|
||||
def check(self, password):
|
||||
if len(password) < self.min_length:
|
||||
return {'match': False, 'reason': 'password too short'}
|
||||
return {'match': False, 'reason': 'Password too short'}
|
||||
if len(re.findall(r'[A-Z]', password)) < self.min_upper:
|
||||
return {
|
||||
'match': False,
|
||||
'reason': 'not enough upper case characters'
|
||||
'reason': 'Not enough upper case characters'
|
||||
}
|
||||
if len(re.findall(r'[0-9]', password)) < self.min_digit:
|
||||
return {'match': False, 'reason': 'not enough digits'}
|
||||
return {'match': False, 'reason': 'Not enough digits'}
|
||||
return {'match': True, 'reason': 'password ok'}
|
||||
|
||||
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
|
||||
|
|
|
@ -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'],
|
||||
}
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
CherryPy>=3.0.0
|
||||
PyYAML
|
||||
Mako
|
||||
python-ldap==3.4.0
|
|
@ -0,0 +1,4 @@
|
|||
CherryPy>=3.0.0
|
||||
PyYAML
|
||||
Mako
|
||||
python-ldap==3.4.0
|
|
@ -70,3 +70,13 @@ div#footer {
|
|||
font-size: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.form-control:-moz-read-only { /* For Firefox */
|
||||
background-color: white;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.form-control:read-only {
|
||||
background-color: white;
|
||||
cursor: default;
|
||||
}
|
||||
|
|
|
@ -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");
|
|
@ -24,9 +24,14 @@ function lcMail(firstname, lastname, domain){
|
|||
function lcUidNumber(firstname, lastname, minuid, maxuid){
|
||||
var iminuid = parseInt(minuid);
|
||||
var imaxuid = parseInt(maxuid);
|
||||
return (parseInt('0x'+sha1(firstname+lastname)) % imaxuid) + iminuid;
|
||||
return (parseInt('0x'+sha1(firstname+lastname)) % (imaxuid - iminuid)) + iminuid;
|
||||
}
|
||||
|
||||
function lcHomeDir(firstname, lastname, basedir){
|
||||
return basedir+lcUid(firstname, lastname);
|
||||
}
|
||||
|
||||
function lcCopy(value){
|
||||
return value;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
$('#form').validator({
|
||||
custom: {
|
||||
'ppolicy': function($el) {
|
||||
if(! $el.prop('required') && $el.val() == 0){
|
||||
return true;
|
||||
};
|
||||
var $ret = 'PPolicy error';
|
||||
$.ajax({
|
||||
url: '/checkppolicy',
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
async: false,
|
||||
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'];
|
||||
return $ret['match'];
|
||||
}
|
||||
},
|
||||
errors: {
|
||||
'ppolicy': 'PPolicy error',
|
||||
}
|
||||
})
|
||||
|
||||
// vim:set expandtab tabstop=4 shiftwidth=4:
|
|
@ -1,5 +1,5 @@
|
|||
/* ========================================================================
|
||||
* Bootstrap (plugin): validator.js v0.8.1
|
||||
* Bootstrap (plugin): validator.js v0.9.0
|
||||
* ========================================================================
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
|
@ -29,7 +29,6 @@
|
|||
+function ($) {
|
||||
'use strict';
|
||||
|
||||
var inputSelector = ':input:not([type="submit"], button):enabled:visible'
|
||||
// VALIDATOR CLASS DEFINITION
|
||||
// ==========================
|
||||
|
||||
|
@ -61,6 +60,8 @@
|
|||
})
|
||||
}
|
||||
|
||||
Validator.INPUT_SELECTOR = ':input:not([type="submit"], button):enabled:visible'
|
||||
|
||||
Validator.DEFAULTS = {
|
||||
delay: 500,
|
||||
html: false,
|
||||
|
@ -72,7 +73,7 @@
|
|||
},
|
||||
feedback: {
|
||||
success: 'glyphicon-ok',
|
||||
error: 'glyphicon-warning-sign'
|
||||
error: 'glyphicon-remove'
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -81,11 +82,11 @@
|
|||
var el = $el[0]
|
||||
return el.checkValidity ? el.checkValidity() : true
|
||||
},
|
||||
match: function ($el) {
|
||||
'match': function ($el) {
|
||||
var target = $el.data('match')
|
||||
return !$el.val() || $el.val() === $(target).val()
|
||||
},
|
||||
minlength: function ($el) {
|
||||
'minlength': function ($el) {
|
||||
var minlength = $el.data('minlength')
|
||||
return !$el.val() || $el.val().length >= minlength
|
||||
}
|
||||
|
@ -163,7 +164,7 @@
|
|||
var delay = this.options.delay
|
||||
|
||||
this.options.delay = 0
|
||||
this.$element.find(inputSelector).trigger('input.bs.validator')
|
||||
this.$element.find(Validator.INPUT_SELECTOR).trigger('input.bs.validator')
|
||||
this.options.delay = delay
|
||||
|
||||
return this
|
||||
|
@ -214,7 +215,7 @@
|
|||
return !!($(this).data('bs.validator.errors') || []).length
|
||||
}
|
||||
|
||||
return !!this.$element.find(inputSelector).filter(fieldErrors).length
|
||||
return !!this.$element.find(Validator.INPUT_SELECTOR).filter(fieldErrors).length
|
||||
}
|
||||
|
||||
Validator.prototype.isIncomplete = function () {
|
||||
|
@ -224,7 +225,7 @@
|
|||
$.trim(this.value) === ''
|
||||
}
|
||||
|
||||
return !!this.$element.find(inputSelector).filter('[required]').filter(fieldIncomplete).length
|
||||
return !!this.$element.find(Validator.INPUT_SELECTOR).filter('[required]').filter(fieldIncomplete).length
|
||||
}
|
||||
|
||||
Validator.prototype.onSubmit = function (e) {
|
||||
|
@ -234,11 +235,12 @@
|
|||
|
||||
Validator.prototype.toggleSubmit = function () {
|
||||
if(!this.options.disable) return
|
||||
|
||||
var $btn = $('button[type="submit"], input[type="submit"]')
|
||||
.filter('[form="' + this.$element.attr('id') + '"]')
|
||||
.add(this.$element.find('input[type="submit"], button[type="submit"]'))
|
||||
|
||||
$btn.toggleClass('disabled', this.isIncomplete() || this.hasErrors())
|
||||
.css({'pointer-events': 'all', 'cursor': 'pointer'})
|
||||
}
|
||||
|
||||
Validator.prototype.defer = function ($el, callback) {
|
||||
|
@ -254,7 +256,7 @@
|
|||
.removeData('bs.validator')
|
||||
.off('.bs.validator')
|
||||
|
||||
this.$element.find(inputSelector)
|
||||
this.$element.find(Validator.INPUT_SELECTOR)
|
||||
.off('.bs.validator')
|
||||
.removeData(['bs.validator.errors', 'bs.validator.deferred'])
|
||||
.each(function () {
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
## -*- coding: utf-8 -*-
|
||||
<%inherit file="navbar.tmpl"/>
|
||||
<%block name="core">
|
||||
<div class="alert alert-warning alert-dismissible" role="alert">
|
||||
<strong>404 Page not found,</strong> URL not available
|
||||
</div>
|
||||
</%block>
|
|
@ -6,14 +6,14 @@
|
|||
</div>
|
||||
<div class="col-md-12 column">
|
||||
<div class="well well-sm">
|
||||
<form method='POST' action='/adduser' role="form" class="form-signin" data-toggle="validator">
|
||||
<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">
|
||||
|
@ -22,6 +22,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<script type="text/javascript" src="/static/js/ppolicy.js"></script>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-2 column">
|
||||
|
|
|
@ -42,6 +42,11 @@
|
|||
<script type="text/javascript" src="/static/js/removediacritic.js"></script>
|
||||
<script type="text/javascript" src="/static/js/lc-filler.js"></script>
|
||||
<script type="text/javascript" src="/static/js/sha1.js"></script>
|
||||
% if custom_js:
|
||||
% for js in custom_js:
|
||||
<script type="text/javascript" src="/custom/${js}"></script>
|
||||
% endfor
|
||||
%endif
|
||||
|
||||
<script>
|
||||
$(function(){
|
||||
|
@ -51,14 +56,20 @@
|
|||
|
||||
</head>
|
||||
<body>
|
||||
% if notifications:
|
||||
% for notif in notifications:
|
||||
<script type="text/javascript">$.notify('${notif | n}')</script>
|
||||
% endfor
|
||||
% endif
|
||||
<div class="container">
|
||||
<%block name="navbar"/>
|
||||
<%block name="core" />
|
||||
</div>
|
||||
<div id="footer">
|
||||
<div class="container">
|
||||
<p class="muted credit"><a href="http://ldapcherry.readthedocs.org" target="_blank">LdapCherry</a> • © 2015 • Pierre-François Carpentier • Released under the MIT License</p>
|
||||
<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,40 +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]
|
||||
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 = 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" aria-describedby="basic-addon-${a}" ${required} ${value}>
|
||||
<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" placeholder="${attr['description']}" aria-describedby="basic-addon-${a}" ${required} ${value}>
|
||||
<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" placeholder="${attr['description']}" aria-describedby="basic-addon-${a}" ${required} ${value} data-error="email address is invalid">
|
||||
<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" placeholder="${attr['description']}" aria-describedby="basic-addon-${a}" ${required} ${value}>
|
||||
<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" aria-describedby="basic-addon-${a}" ${required} value="${attr['value']}">
|
||||
<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-error="Don't meet password policy" data-remote="/checkppolicy" name="attr.${a}1" id="${a}1" placeholder="${attr['description']}" ${required}>
|
||||
<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" placeholder="Confirm" ${required}>
|
||||
<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>
|
||||
|
@ -67,39 +77,40 @@ 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 'autofill' in attr:
|
||||
function = attr['autofill']['function']
|
||||
tuple = (field, function)
|
||||
if not tuple in functions:
|
||||
functions[tuple] = []
|
||||
for arg in attr['autofill']['args']:
|
||||
if arg[0] == '$':
|
||||
field_arg = 'attr.' + arg[1:]
|
||||
attr_set.add(field_arg)
|
||||
functions[tuple].append("fields['" + field_arg + "'].value")
|
||||
if not field_arg in attr_events:
|
||||
attr_events[field_arg] = []
|
||||
attr_events[field_arg].append(tuple)
|
||||
else:
|
||||
value = arg
|
||||
functions[tuple].append("'" + value + "'")
|
||||
if field not in attr_set:
|
||||
attr_set.append(field)
|
||||
if 'autofill' in attr:
|
||||
function = attr['autofill']['function']
|
||||
tuple = (field, function)
|
||||
if not tuple in functions:
|
||||
functions[tuple] = []
|
||||
for arg in attr['autofill']['args']:
|
||||
if arg[0] == '$':
|
||||
field_arg = 'attr.' + arg[1:]
|
||||
if field_arg not in attr_set:
|
||||
attr_set.append(field_arg)
|
||||
functions[tuple].append("fields['" + field_arg + "'].value")
|
||||
if not field_arg in attr_events:
|
||||
attr_events[field_arg] = []
|
||||
attr_events[field_arg].append(tuple)
|
||||
else:
|
||||
value = arg
|
||||
functions[tuple].append("'" + value + "'")
|
||||
%>
|
||||
<script>
|
||||
var fields = new Object();
|
||||
|
@ -112,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
|
||||
};
|
||||
|
@ -120,3 +131,8 @@ if (fields['${attrid}'] != null) {
|
|||
% endfor
|
||||
</script>
|
||||
% endif
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
$('form:eq(1) *:input[type!=hidden]:first').focus();
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -1,4 +1,38 @@
|
|||
## -*- coding: utf-8 -*-
|
||||
<%inherit file="navbar.tmpl"/>
|
||||
<%block name="core">
|
||||
<div class="row clearfix top-buffer bottom-buffer">
|
||||
<div class="col-md-2 column">
|
||||
</div>
|
||||
<div class="col-md-12 column">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">Your attributes:</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<table id="RecordTable" class="table table-hover table-condensed">
|
||||
% 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:
|
||||
<%
|
||||
value = searchresult[attr]
|
||||
if type(value) is list:
|
||||
value = ', '.join(value)
|
||||
%>
|
||||
<td><b>${attrs_list[attr]['display_name']}</b>:</td>
|
||||
<td>${value}</td>
|
||||
% endif
|
||||
</tr>
|
||||
% endfor
|
||||
</tbody>
|
||||
%endif
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-2 column">
|
||||
</div>
|
||||
</div>
|
||||
</%block>
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -6,14 +6,14 @@
|
|||
</div>
|
||||
<div class="col-md-12 column">
|
||||
<div class="well well-sm">
|
||||
<form method='POST' action='/modify' role="form" class="form-signin" data-toggle="validator">
|
||||
<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>
|
||||
|
@ -37,7 +37,7 @@
|
|||
% for group in standalone_groups[backend]:
|
||||
<tr>
|
||||
<td>
|
||||
${backend}
|
||||
${backends_display_names[backend]}
|
||||
</td>
|
||||
<td>
|
||||
${group}
|
||||
|
@ -62,6 +62,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<script type="text/javascript" src="/static/js/ppolicy.js"></script>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-2 column">
|
||||
|
|
|
@ -6,18 +6,26 @@
|
|||
<nav class="navbar navbar-inverse" role="navigation">
|
||||
<div class="navbar-header">
|
||||
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1"> <span class="sr-only">Toggle navigation</span><span class="icon-bar"></span><span class="icon-bar"></span><span class="icon-bar"></span></button>
|
||||
<a class="navbar-brand" href="/"><img src="/static/img/icon.png" alt="LdapCherry" height="22" width="22"></a>
|
||||
<a class="navbar-brand" href="/selfmodify">Self Modify</a>
|
||||
% if is_admin:
|
||||
<a class="navbar-brand" href="/adduser">Add User</a>
|
||||
<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>
|
||||
<form method='GET' action='/searchuser' class="navbar-form navbar-right" role="search">
|
||||
% 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];
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<%block name="core">
|
||||
<div class="row clearfix">
|
||||
<div class="col-md-12 column">
|
||||
<form method='get' action='/searchadmin' role="form" class="form-inline">
|
||||
<form method='get' action='/searchadmin' role="form" class="form-inline" data-toggle="validator">
|
||||
<div class="form-group">
|
||||
<label for="searchstring">Search user to modify/delete</label>
|
||||
<input type="text" class="form-control" id="searchstring" name="searchstring" placeholder="Search User">
|
||||
|
@ -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
|
||||
|
|
|
@ -3,10 +3,10 @@
|
|||
<%block name="core">
|
||||
<div class="row clearfix">
|
||||
<div class="col-md-12 column">
|
||||
<form method='get' action='/searchuser' role="form" class="form-inline">
|
||||
<form method='get' action='/searchuser' role="form" class="form-inline" data-toggle="validator">
|
||||
<div class="form-group">
|
||||
<label for="searchstring">Search user</label>
|
||||
<input type="text" id="searchstring" class="form-control" name="searchstring" placeholder="Search User">
|
||||
<input type="text" id="searchstring" data-minlength="3" data-error="Too short" class="form-control" name="searchstring" placeholder="Search User" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="submit">Submit</label>
|
||||
|
|
|
@ -6,9 +6,9 @@
|
|||
</div>
|
||||
<div class="col-md-12 column">
|
||||
<div class="well well-sm">
|
||||
<form method='POST' action='/selfmodify' role="form" class="form-signin" data-toggle="validator">
|
||||
<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">
|
||||
|
@ -17,6 +17,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<script type="text/javascript" src="/static/js/ppolicy.js"></script>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-2 column">
|
||||
|
|
15
run_test.sh
15
run_test.sh
|
@ -1,4 +1,15 @@
|
|||
#!/bin/sh
|
||||
|
||||
Red='\33[0;31m';
|
||||
Gre='\33[0;32m';
|
||||
RCol='\33[0m';
|
||||
|
||||
cd `dirname $0`
|
||||
python setup.py test &&\
|
||||
pep8 --repeat --show-source --exclude=.venv,.tox,dist,docs,build,*.egg,tests,misc .
|
||||
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"
|
||||
|
|
31
setup.py
31
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.0.1',
|
||||
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',
|
||||
|
@ -136,6 +144,9 @@ setup(
|
|||
'Operating System :: POSIX',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 2.7',
|
||||
'Topic :: Internet :: LDAP'
|
||||
"Topic :: System :: Systems Administration"
|
||||
" :: Authentication/Directory :: LDAP",
|
||||
"Topic :: System :: Systems Administration"
|
||||
" :: Authentication/Directory",
|
||||
],
|
||||
)
|
||||
|
|
|
@ -29,4 +29,11 @@ password:
|
|||
type: password
|
||||
backends:
|
||||
ldap: userPassword
|
||||
ad: userPassword
|
||||
ad: unicodePwd
|
||||
cn:
|
||||
description: "First Name and Display Name"
|
||||
display_name: "Display Name"
|
||||
type: string
|
||||
weight: 30
|
||||
backends:
|
||||
ad: cn
|
||||
|
|
|
@ -11,7 +11,7 @@ cn:
|
|||
- $name
|
||||
backends:
|
||||
ldap: cn
|
||||
ad: CN
|
||||
ad: cn
|
||||
|
||||
first-name:
|
||||
description: "First name of the user"
|
||||
|
@ -81,7 +81,7 @@ gidNumber:
|
|||
default: 10000
|
||||
backends:
|
||||
ldap: gidNumber
|
||||
ad: GIDNumber
|
||||
ad: gidNumber
|
||||
shell:
|
||||
description: "Shell of the user"
|
||||
display_name: "Shell"
|
||||
|
@ -117,7 +117,7 @@ password:
|
|||
type: password
|
||||
backends:
|
||||
ldap: userPassword
|
||||
ad: userPassword
|
||||
ad: unicodePwd
|
||||
logscript:
|
||||
description: "Windows login script"
|
||||
display_name: "Login script"
|
||||
|
|
|
@ -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
|
|
@ -9,7 +9,7 @@ cn:
|
|||
- $name
|
||||
backends:
|
||||
ldap: cn
|
||||
ad: CN
|
||||
ad: cn
|
||||
first-name:
|
||||
description: "First name of the user"
|
||||
display_name: "First Name"
|
||||
|
@ -102,7 +102,7 @@ password:
|
|||
type: password
|
||||
backends:
|
||||
ldap: userPassword
|
||||
ad: userPassword
|
||||
ad: unicodePwd
|
||||
logscript:
|
||||
description: "Windows login script"
|
||||
display_name: "Login script"
|
||||
|
|
|
@ -11,8 +11,7 @@ cn:
|
|||
- $name
|
||||
backends:
|
||||
ldap: cn
|
||||
ad: CN
|
||||
|
||||
ad: cn
|
||||
first-name:
|
||||
description: "First name of the user"
|
||||
display_name: "First Name"
|
||||
|
@ -21,3 +20,11 @@ first-name:
|
|||
backends:
|
||||
ldap: givenName
|
||||
ad: givenName
|
||||
password:
|
||||
description: "Password of the user"
|
||||
display_name: "Password"
|
||||
weight: 31
|
||||
self: True
|
||||
type: password
|
||||
backends:
|
||||
ad: unicodePwd
|
||||
|
|
|
@ -0,0 +1,484 @@
|
|||
[
|
||||
"good",
|
||||
"undefined",
|
||||
"undef",
|
||||
"null",
|
||||
"NULL",
|
||||
"(null)",
|
||||
"nil",
|
||||
"NIL",
|
||||
"true",
|
||||
"false",
|
||||
"True",
|
||||
"False",
|
||||
"None",
|
||||
"hasOwnProperty",
|
||||
"\\",
|
||||
"\\\\",
|
||||
"0",
|
||||
"1",
|
||||
"1.00",
|
||||
"$1.00",
|
||||
"1/2",
|
||||
"1E2",
|
||||
"1E02",
|
||||
"1E+02",
|
||||
"-1",
|
||||
"-1.00",
|
||||
"-$1.00",
|
||||
"-1/2",
|
||||
"-1E2",
|
||||
"-1E02",
|
||||
"-1E+02",
|
||||
"1/0",
|
||||
"0/0",
|
||||
"-2147483648/-1",
|
||||
"-9223372036854775808/-1",
|
||||
"0.00",
|
||||
"0..0",
|
||||
".",
|
||||
"0.0.0",
|
||||
"0,00",
|
||||
"0,,0",
|
||||
",",
|
||||
"0,0,0",
|
||||
"0.0/0",
|
||||
"1.0/0.0",
|
||||
"0.0/0.0",
|
||||
"1,0/0,0",
|
||||
"0,0/0,0",
|
||||
"--1",
|
||||
"-",
|
||||
"-.",
|
||||
"-,",
|
||||
"999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999",
|
||||
"NaN",
|
||||
"Infinity",
|
||||
"-Infinity",
|
||||
"INF",
|
||||
"1#INF",
|
||||
"-1#IND",
|
||||
"1#QNAN",
|
||||
"1#SNAN",
|
||||
"1#IND",
|
||||
"0x0",
|
||||
"0xffffffff",
|
||||
"0xffffffffffffffff",
|
||||
"0xabad1dea",
|
||||
"123456789012345678901234567890123456789",
|
||||
"1,000.00",
|
||||
"1 000.00",
|
||||
"1'000.00",
|
||||
"1,000,000.00",
|
||||
"1 000 000.00",
|
||||
"1'000'000.00",
|
||||
"1.000,00",
|
||||
"1 000,00",
|
||||
"1'000,00",
|
||||
"1.000.000,00",
|
||||
"1 000 000,00",
|
||||
"1'000'000,00",
|
||||
"01000",
|
||||
"08",
|
||||
"09",
|
||||
"2.2250738585072011e-308",
|
||||
",./;'[]\\-=",
|
||||
"<>?:\"{}|_+",
|
||||
"!@#$%^&*()`~",
|
||||
"Ω≈ç√∫˜µ≤≥÷",
|
||||
"åß∂ƒ©˙∆˚¬…æ",
|
||||
"œ∑´®†¥¨ˆøπ“‘",
|
||||
"¡™£¢∞§¶•ªº–≠",
|
||||
"¸˛Ç◊ı˜Â¯˘¿",
|
||||
"ÅÍÎÏ˝ÓÔÒÚÆ☃",
|
||||
"Œ„´‰ˇÁ¨ˆØ∏”’",
|
||||
"`⁄€‹›fifl‡°·‚—±",
|
||||
"⅛⅜⅝⅞",
|
||||
"ЁЂЃЄЅІЇЈЉЊЋЌЍЎЏАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюя",
|
||||
"٠١٢٣٤٥٦٧٨٩",
|
||||
"⁰⁴⁵",
|
||||
"₀₁₂",
|
||||
"⁰⁴⁵₀₁₂",
|
||||
"'",
|
||||
"\"",
|
||||
"''",
|
||||
"\"\"",
|
||||
"'\"'",
|
||||
"\"''''\"'\"",
|
||||
"\"'\"'\"''''\"",
|
||||
"<foo val=“bar” />",
|
||||
"<foo val=“bar” />",
|
||||
"<foo val=”bar“ />",
|
||||
"<foo val=`bar' />",
|
||||
"田中さんにあげて下さい",
|
||||
"パーティーへ行かないか",
|
||||
"和製漢語",
|
||||
"部落格",
|
||||
"사회과학원 어학연구소",
|
||||
"찦차를 타고 온 펲시맨과 쑛다리 똠방각하",
|
||||
"社會科學院語學研究所",
|
||||
"울란바토르",
|
||||
"𠜎𠜱𠝹𠱓𠱸𠲖𠳏",
|
||||
"ヽ༼ຈل͜ຈ༽ノ ヽ༼ຈل͜ຈ༽ノ ",
|
||||
"(。◕ ∀ ◕。)",
|
||||
"`ィ(´∀`∩",
|
||||
"__ロ(,_,*)",
|
||||
"・( ̄∀ ̄)・:*:",
|
||||
"゚・✿ヾ╲(。◕‿◕。)╱✿・゚",
|
||||
",。・:*:・゜’( ☻ ω ☻ )。・:*:・゜’",
|
||||
"(╯°□°)╯︵ ┻━┻) ",
|
||||
"(ノಥ益ಥ)ノ ┻━┻",
|
||||
"( ͡° ͜ʖ ͡°)",
|
||||
"😍",
|
||||
"👩🏽",
|
||||
"👾 🙇 💁 🙅 🙆 🙋 🙎 🙍 ",
|
||||
"🐵 🙈 🙉 🙊",
|
||||
"❤️ 💔 💌 💕 💞 💓 💗 💖 💘 💝 💟 💜 💛 💚 💙",
|
||||
"✋🏿 💪🏿 👐🏿 🙌🏿 👏🏿 🙏🏿",
|
||||
"🚾 🆒 🆓 🆕 🆖 🆗 🆙 🏧",
|
||||
"0️⃣ 1️⃣ 2️⃣ 3️⃣ 4️⃣ 5️⃣ 6️⃣ 7️⃣ 8️⃣ 9️⃣ 🔟",
|
||||
"🇺🇸🇷🇺🇸 🇦🇫🇦🇲🇸 ",
|
||||
"🇺🇸🇷🇺🇸🇦🇫🇦🇲",
|
||||
"🇺🇸🇷🇺🇸🇦",
|
||||
"123",
|
||||
"١٢٣",
|
||||
"ثم نفس سقطت وبالتحديد،, جزيرتي باستخدام أن دنو. إذ هنا؟ الستار وتنصيب كان. أهّل ايطاليا، بريطانيا-فرنسا قد أخذ. سليمان، إتفاقية بين ما, يذكر الحدود أي بعد, معاملة بولندا، الإطلاق عل إيو.",
|
||||
"בְּרֵאשִׁית, בָּרָא אֱלֹהִים, אֵת הַשָּׁמַיִם, וְאֵת הָאָרֶץ",
|
||||
"הָיְתָהtestالصفحات التّحول",
|
||||
"﷽",
|
||||
"ﷺ",
|
||||
"مُنَاقَشَةُ سُبُلِ اِسْتِخْدَامِ اللُّغَةِ فِي النُّظُمِ الْقَائِمَةِ وَفِيم يَخُصَّ التَّطْبِيقَاتُ الْحاسُوبِيَّةُ ",
|
||||
"",
|
||||
" ",
|
||||
"",
|
||||
" ",
|
||||
"",
|
||||
"␣",
|
||||
"␢",
|
||||
"␡",
|
||||
"test",
|
||||
"test",
|
||||
"
test
",
|
||||
"testtest",
|
||||
"test",
|
||||
"Ṱ̺̺̕o͞ ̷i̲̬͇̪͙n̝̗͕v̟̜̘̦͟o̶̙̰̠kè͚̮̺̪̹̱̤ ̖t̝͕̳̣̻̪͞h̼͓̲̦̳̘̲e͇̣̰̦̬͎ ̢̼̻̱̘h͚͎͙̜̣̲ͅi̦̲̣̰̤v̻͍e̺̭̳̪̰-m̢iͅn̖̺̞̲̯̰d̵̼̟͙̩̼̘̳ ̞̥̱̳̭r̛̗̘e͙p͠r̼̞̻̭̗e̺̠̣͟s̘͇̳͍̝͉e͉̥̯̞̲͚̬͜ǹ̬͎͎̟̖͇̤t͍̬̤͓̼̭͘ͅi̪̱n͠g̴͉ ͏͉ͅc̬̟h͡a̫̻̯͘o̫̟̖͍̙̝͉s̗̦̲.̨̹͈̣",
|
||||
"̡͓̞ͅI̗̘̦͝n͇͇͙v̮̫ok̲̫̙͈i̖͙̭̹̠̞n̡̻̮̣̺g̲͈͙̭͙̬͎ ̰t͔̦h̞̲e̢̤ ͍̬̲͖f̴̘͕̣è͖ẹ̥̩l͖͔͚i͓͚̦͠n͖͍̗͓̳̮g͍ ̨o͚̪͡f̘̣̬ ̖̘͖̟͙̮c҉͔̫͖͓͇͖ͅh̵̤̣͚͔á̗̼͕ͅo̼̣̥s̱͈̺̖̦̻͢.̛̖̞̠̫̰",
|
||||
"̗̺͖̹̯͓Ṯ̤͍̥͇͈h̲́e͏͓̼̗̙̼̣͔ ͇̜̱̠͓͍ͅN͕͠e̗̱z̘̝̜̺͙p̤̺̹͍̯͚e̠̻̠͜r̨̤͍̺̖͔̖̖d̠̟̭̬̝͟i̦͖̩͓͔̤a̠̗̬͉̙n͚͜ ̻̞̰͚ͅh̵͉i̳̞v̢͇ḙ͎͟-҉̭̩̼͔m̤̭̫i͕͇̝̦n̗͙ḍ̟ ̯̲͕͞ǫ̟̯̰̲͙̻̝f ̪̰̰̗̖̭̘͘c̦͍̲̞͍̩̙ḥ͚a̮͎̟̙͜ơ̩̹͎s̤.̝̝ ҉Z̡̖̜͖̰̣͉̜a͖̰͙̬͡l̲̫̳͍̩g̡̟̼̱͚̞̬ͅo̗͜.̟",
|
||||
"̦H̬̤̗̤͝e͜ ̜̥̝̻͍̟́w̕h̖̯͓o̝͙̖͎̱̮ ҉̺̙̞̟͈W̷̼̭a̺̪͍į͈͕̭͙̯̜t̶̼̮s̘͙͖̕ ̠̫̠B̻͍͙͉̳ͅe̵h̵̬͇̫͙i̹͓̳̳̮͎̫̕n͟d̴̪̜̖ ̰͉̩͇͙̲͞ͅT͖̼͓̪͢h͏͓̮̻e̬̝̟ͅ ̤̹̝W͙̞̝͔͇͝ͅa͏͓͔̹̼̣l̴͔̰̤̟͔ḽ̫.͕",
|
||||
"Z̮̞̠͙͔ͅḀ̗̞͈̻̗Ḷ͙͎̯̹̞͓G̻O̭̗̮",
|
||||
"˙ɐnbᴉlɐ ɐuƃɐɯ ǝɹolop ʇǝ ǝɹoqɐl ʇn ʇunpᴉpᴉɔuᴉ ɹodɯǝʇ poɯsnᴉǝ op pǝs 'ʇᴉlǝ ƃuᴉɔsᴉdᴉpɐ ɹnʇǝʇɔǝsuoɔ 'ʇǝɯɐ ʇᴉs ɹolop ɯnsdᴉ ɯǝɹo˥",
|
||||
"00˙Ɩ$-",
|
||||
"The quick brown fox jumps over the lazy dog",
|
||||
"𝐓𝐡𝐞 𝐪𝐮𝐢𝐜𝐤 𝐛𝐫𝐨𝐰𝐧 𝐟𝐨𝐱 𝐣𝐮𝐦𝐩𝐬 𝐨𝐯𝐞𝐫 𝐭𝐡𝐞 𝐥𝐚𝐳𝐲 𝐝𝐨𝐠",
|
||||
"𝕿𝖍𝖊 𝖖𝖚𝖎𝖈𝖐 𝖇𝖗𝖔𝖜𝖓 𝖋𝖔𝖝 𝖏𝖚𝖒𝖕𝖘 𝖔𝖛𝖊𝖗 𝖙𝖍𝖊 𝖑𝖆𝖟𝖞 𝖉𝖔𝖌",
|
||||
"𝑻𝒉𝒆 𝒒𝒖𝒊𝒄𝒌 𝒃𝒓𝒐𝒘𝒏 𝒇𝒐𝒙 𝒋𝒖𝒎𝒑𝒔 𝒐𝒗𝒆𝒓 𝒕𝒉𝒆 𝒍𝒂𝒛𝒚 𝒅𝒐𝒈",
|
||||
"𝓣𝓱𝓮 𝓺𝓾𝓲𝓬𝓴 𝓫𝓻𝓸𝔀𝓷 𝓯𝓸𝔁 𝓳𝓾𝓶𝓹𝓼 𝓸𝓿𝓮𝓻 𝓽𝓱𝓮 𝓵𝓪𝔃𝔂 𝓭𝓸𝓰",
|
||||
"𝕋𝕙𝕖 𝕢𝕦𝕚𝕔𝕜 𝕓𝕣𝕠𝕨𝕟 𝕗𝕠𝕩 𝕛𝕦𝕞𝕡𝕤 𝕠𝕧𝕖𝕣 𝕥𝕙𝕖 𝕝𝕒𝕫𝕪 𝕕𝕠𝕘",
|
||||
"𝚃𝚑𝚎 𝚚𝚞𝚒𝚌𝚔 𝚋𝚛𝚘𝚠𝚗 𝚏𝚘𝚡 𝚓𝚞𝚖𝚙𝚜 𝚘𝚟𝚎𝚛 𝚝𝚑𝚎 𝚕𝚊𝚣𝚢 𝚍𝚘𝚐",
|
||||
"⒯⒣⒠ ⒬⒰⒤⒞⒦ ⒝⒭⒪⒲⒩ ⒡⒪⒳ ⒥⒰⒨⒫⒮ ⒪⒱⒠⒭ ⒯⒣⒠ ⒧⒜⒵⒴ ⒟⒪⒢",
|
||||
"<script>alert(123)</script>",
|
||||
"<script>alert('123');</script>",
|
||||
"<img src=x onerror=alert(123) />",
|
||||
"<svg><script>123<1>alert(123)</script> ",
|
||||
"\"><script>alert(123)</script>",
|
||||
"'><script>alert(123)</script>",
|
||||
"><script>alert(123)</script>",
|
||||
"</script><script>alert(123)</script>",
|
||||
"< / script >< script >alert(123)< / script >",
|
||||
" onfocus=JaVaSCript:alert(123) autofocus ",
|
||||
"\" onfocus=JaVaSCript:alert(123) autofocus ",
|
||||
"' onfocus=JaVaSCript:alert(123) autofocus ",
|
||||
"<script>alert(123)</script>",
|
||||
"<sc<script>ript>alert(123)</sc</script>ript>",
|
||||
"--><script>alert(123)</script>",
|
||||
"\";alert(123);t=\"",
|
||||
"';alert(123);t='",
|
||||
"JavaSCript:alert(123)",
|
||||
";alert(123);",
|
||||
"src=JaVaSCript:prompt(132)",
|
||||
"\"><script>alert(123);</script x=\"",
|
||||
"'><script>alert(123);</script x='",
|
||||
"><script>alert(123);</script x=",
|
||||
"\" autofocus onkeyup=\"javascript:alert(123)",
|
||||
"' autofocus onkeyup='javascript:alert(123)",
|
||||
"<script\\x20type=\"text/javascript\">javascript:alert(1);</script>",
|
||||
"<script\\x3Etype=\"text/javascript\">javascript:alert(1);</script>",
|
||||
"<script\\x0Dtype=\"text/javascript\">javascript:alert(1);</script>",
|
||||
"<script\\x09type=\"text/javascript\">javascript:alert(1);</script>",
|
||||
"<script\\x0Ctype=\"text/javascript\">javascript:alert(1);</script>",
|
||||
"<script\\x2Ftype=\"text/javascript\">javascript:alert(1);</script>",
|
||||
"<script\\x0Atype=\"text/javascript\">javascript:alert(1);</script>",
|
||||
"'`\"><\\x3Cscript>javascript:alert(1)</script> ",
|
||||
"'`\"><\\x00script>javascript:alert(1)</script>",
|
||||
"ABC<div style=\"x\\x3Aexpression(javascript:alert(1)\">DEF",
|
||||
"ABC<div style=\"x:expression\\x5C(javascript:alert(1)\">DEF",
|
||||
"ABC<div style=\"x:expression\\x00(javascript:alert(1)\">DEF",
|
||||
"ABC<div style=\"x:exp\\x00ression(javascript:alert(1)\">DEF",
|
||||
"ABC<div style=\"x:exp\\x5Cression(javascript:alert(1)\">DEF",
|
||||
"ABC<div style=\"x:\\x0Aexpression(javascript:alert(1)\">DEF",
|
||||
"ABC<div style=\"x:\\x09expression(javascript:alert(1)\">DEF",
|
||||
"ABC<div style=\"x:\\xE3\\x80\\x80expression(javascript:alert(1)\">DEF",
|
||||
"ABC<div style=\"x:\\xE2\\x80\\x84expression(javascript:alert(1)\">DEF",
|
||||
"ABC<div style=\"x:\\xC2\\xA0expression(javascript:alert(1)\">DEF",
|
||||
"ABC<div style=\"x:\\xE2\\x80\\x80expression(javascript:alert(1)\">DEF",
|
||||
"ABC<div style=\"x:\\xE2\\x80\\x8Aexpression(javascript:alert(1)\">DEF",
|
||||
"ABC<div style=\"x:\\x0Dexpression(javascript:alert(1)\">DEF",
|
||||
"ABC<div style=\"x:\\x0Cexpression(javascript:alert(1)\">DEF",
|
||||
"ABC<div style=\"x:\\xE2\\x80\\x87expression(javascript:alert(1)\">DEF",
|
||||
"ABC<div style=\"x:\\xEF\\xBB\\xBFexpression(javascript:alert(1)\">DEF",
|
||||
"ABC<div style=\"x:\\x20expression(javascript:alert(1)\">DEF",
|
||||
"ABC<div style=\"x:\\xE2\\x80\\x88expression(javascript:alert(1)\">DEF",
|
||||
"ABC<div style=\"x:\\x00expression(javascript:alert(1)\">DEF",
|
||||
"ABC<div style=\"x:\\xE2\\x80\\x8Bexpression(javascript:alert(1)\">DEF",
|
||||
"ABC<div style=\"x:\\xE2\\x80\\x86expression(javascript:alert(1)\">DEF",
|
||||
"ABC<div style=\"x:\\xE2\\x80\\x85expression(javascript:alert(1)\">DEF",
|
||||
"ABC<div style=\"x:\\xE2\\x80\\x82expression(javascript:alert(1)\">DEF",
|
||||
"ABC<div style=\"x:\\x0Bexpression(javascript:alert(1)\">DEF",
|
||||
"ABC<div style=\"x:\\xE2\\x80\\x81expression(javascript:alert(1)\">DEF",
|
||||
"ABC<div style=\"x:\\xE2\\x80\\x83expression(javascript:alert(1)\">DEF",
|
||||
"ABC<div style=\"x:\\xE2\\x80\\x89expression(javascript:alert(1)\">DEF",
|
||||
"<a href=\"\\x0Bjavascript:javascript:alert(1)\" id=\"fuzzelement1\">test</a>",
|
||||
"<a href=\"\\x0Fjavascript:javascript:alert(1)\" id=\"fuzzelement1\">test</a>",
|
||||
"<a href=\"\\xC2\\xA0javascript:javascript:alert(1)\" id=\"fuzzelement1\">test</a>",
|
||||
"<a href=\"\\x05javascript:javascript:alert(1)\" id=\"fuzzelement1\">test</a>",
|
||||
"<a href=\"\\xE1\\xA0\\x8Ejavascript:javascript:alert(1)\" id=\"fuzzelement1\">test</a>",
|
||||
"<a href=\"\\x18javascript:javascript:alert(1)\" id=\"fuzzelement1\">test</a>",
|
||||
"<a href=\"\\x11javascript:javascript:alert(1)\" id=\"fuzzelement1\">test</a>",
|
||||
"<a href=\"\\xE2\\x80\\x88javascript:javascript:alert(1)\" id=\"fuzzelement1\">test</a>",
|
||||
"<a href=\"\\xE2\\x80\\x89javascript:javascript:alert(1)\" id=\"fuzzelement1\">test</a>",
|
||||
"<a href=\"\\xE2\\x80\\x80javascript:javascript:alert(1)\" id=\"fuzzelement1\">test</a>",
|
||||
"<a href=\"\\x17javascript:javascript:alert(1)\" id=\"fuzzelement1\">test</a>",
|
||||
"<a href=\"\\x03javascript:javascript:alert(1)\" id=\"fuzzelement1\">test</a>",
|
||||
"<a href=\"\\x0Ejavascript:javascript:alert(1)\" id=\"fuzzelement1\">test</a>",
|
||||
"<a href=\"\\x1Ajavascript:javascript:alert(1)\" id=\"fuzzelement1\">test</a>",
|
||||
"<a href=\"\\x00javascript:javascript:alert(1)\" id=\"fuzzelement1\">test</a>",
|
||||
"<a href=\"\\x10javascript:javascript:alert(1)\" id=\"fuzzelement1\">test</a>",
|
||||
"<a href=\"\\xE2\\x80\\x82javascript:javascript:alert(1)\" id=\"fuzzelement1\">test</a>",
|
||||
"<a href=\"\\x20javascript:javascript:alert(1)\" id=\"fuzzelement1\">test</a>",
|
||||
"<a href=\"\\x13javascript:javascript:alert(1)\" id=\"fuzzelement1\">test</a>",
|
||||
"<a href=\"\\x09javascript:javascript:alert(1)\" id=\"fuzzelement1\">test</a>",
|
||||
"<a href=\"\\xE2\\x80\\x8Ajavascript:javascript:alert(1)\" id=\"fuzzelement1\">test</a>",
|
||||
"<a href=\"\\x14javascript:javascript:alert(1)\" id=\"fuzzelement1\">test</a>",
|
||||
"<a href=\"\\x19javascript:javascript:alert(1)\" id=\"fuzzelement1\">test</a>",
|
||||
"<a href=\"\\xE2\\x80\\xAFjavascript:javascript:alert(1)\" id=\"fuzzelement1\">test</a>",
|
||||
"<a href=\"\\x1Fjavascript:javascript:alert(1)\" id=\"fuzzelement1\">test</a>",
|
||||
"<a href=\"\\xE2\\x80\\x81javascript:javascript:alert(1)\" id=\"fuzzelement1\">test</a>",
|
||||
"<a href=\"\\x1Djavascript:javascript:alert(1)\" id=\"fuzzelement1\">test</a>",
|
||||
"<a href=\"\\xE2\\x80\\x87javascript:javascript:alert(1)\" id=\"fuzzelement1\">test</a>",
|
||||
"<a href=\"\\x07javascript:javascript:alert(1)\" id=\"fuzzelement1\">test</a>",
|
||||
"<a href=\"\\xE1\\x9A\\x80javascript:javascript:alert(1)\" id=\"fuzzelement1\">test</a>",
|
||||
"<a href=\"\\xE2\\x80\\x83javascript:javascript:alert(1)\" id=\"fuzzelement1\">test</a>",
|
||||
"<a href=\"\\x04javascript:javascript:alert(1)\" id=\"fuzzelement1\">test</a>",
|
||||
"<a href=\"\\x01javascript:javascript:alert(1)\" id=\"fuzzelement1\">test</a>",
|
||||
"<a href=\"\\x08javascript:javascript:alert(1)\" id=\"fuzzelement1\">test</a>",
|
||||
"<a href=\"\\xE2\\x80\\x84javascript:javascript:alert(1)\" id=\"fuzzelement1\">test</a>",
|
||||
"<a href=\"\\xE2\\x80\\x86javascript:javascript:alert(1)\" id=\"fuzzelement1\">test</a>",
|
||||
"<a href=\"\\xE3\\x80\\x80javascript:javascript:alert(1)\" id=\"fuzzelement1\">test</a>",
|
||||
"<a href=\"\\x12javascript:javascript:alert(1)\" id=\"fuzzelement1\">test</a>",
|
||||
"<a href=\"\\x0Djavascript:javascript:alert(1)\" id=\"fuzzelement1\">test</a>",
|
||||
"<a href=\"\\x0Ajavascript:javascript:alert(1)\" id=\"fuzzelement1\">test</a>",
|
||||
"<a href=\"\\x0Cjavascript:javascript:alert(1)\" id=\"fuzzelement1\">test</a>",
|
||||
"<a href=\"\\x15javascript:javascript:alert(1)\" id=\"fuzzelement1\">test</a>",
|
||||
"<a href=\"\\xE2\\x80\\xA8javascript:javascript:alert(1)\" id=\"fuzzelement1\">test</a>",
|
||||
"<a href=\"\\x16javascript:javascript:alert(1)\" id=\"fuzzelement1\">test</a>",
|
||||
"<a href=\"\\x02javascript:javascript:alert(1)\" id=\"fuzzelement1\">test</a>",
|
||||
"<a href=\"\\x1Bjavascript:javascript:alert(1)\" id=\"fuzzelement1\">test</a>",
|
||||
"<a href=\"\\x06javascript:javascript:alert(1)\" id=\"fuzzelement1\">test</a>",
|
||||
"<a href=\"\\xE2\\x80\\xA9javascript:javascript:alert(1)\" id=\"fuzzelement1\">test</a>",
|
||||
"<a href=\"\\xE2\\x80\\x85javascript:javascript:alert(1)\" id=\"fuzzelement1\">test</a>",
|
||||
"<a href=\"\\x1Ejavascript:javascript:alert(1)\" id=\"fuzzelement1\">test</a>",
|
||||
"<a href=\"\\xE2\\x81\\x9Fjavascript:javascript:alert(1)\" id=\"fuzzelement1\">test</a>",
|
||||
"<a href=\"\\x1Cjavascript:javascript:alert(1)\" id=\"fuzzelement1\">test</a>",
|
||||
"<a href=\"javascript\\x00:javascript:alert(1)\" id=\"fuzzelement1\">test</a>",
|
||||
"<a href=\"javascript\\x3A:javascript:alert(1)\" id=\"fuzzelement1\">test</a>",
|
||||
"<a href=\"javascript\\x09:javascript:alert(1)\" id=\"fuzzelement1\">test</a>",
|
||||
"<a href=\"javascript\\x0D:javascript:alert(1)\" id=\"fuzzelement1\">test</a>",
|
||||
"<a href=\"javascript\\x0A:javascript:alert(1)\" id=\"fuzzelement1\">test</a>",
|
||||
"`\"'><img src=xxx:x \\x0Aonerror=javascript:alert(1)>",
|
||||
"`\"'><img src=xxx:x \\x22onerror=javascript:alert(1)>",
|
||||
"`\"'><img src=xxx:x \\x0Bonerror=javascript:alert(1)>",
|
||||
"`\"'><img src=xxx:x \\x0Donerror=javascript:alert(1)>",
|
||||
"`\"'><img src=xxx:x \\x2Fonerror=javascript:alert(1)>",
|
||||
"`\"'><img src=xxx:x \\x09onerror=javascript:alert(1)>",
|
||||
"`\"'><img src=xxx:x \\x0Conerror=javascript:alert(1)>",
|
||||
"`\"'><img src=xxx:x \\x00onerror=javascript:alert(1)>",
|
||||
"`\"'><img src=xxx:x \\x27onerror=javascript:alert(1)>",
|
||||
"`\"'><img src=xxx:x \\x20onerror=javascript:alert(1)>",
|
||||
"\"`'><script>\\x3Bjavascript:alert(1)</script>",
|
||||
"\"`'><script>\\x0Djavascript:alert(1)</script>",
|
||||
"\"`'><script>\\xEF\\xBB\\xBFjavascript:alert(1)</script>",
|
||||
"\"`'><script>\\xE2\\x80\\x81javascript:alert(1)</script>",
|
||||
"\"`'><script>\\xE2\\x80\\x84javascript:alert(1)</script>",
|
||||
"\"`'><script>\\xE3\\x80\\x80javascript:alert(1)</script>",
|
||||
"\"`'><script>\\x09javascript:alert(1)</script>",
|
||||
"\"`'><script>\\xE2\\x80\\x89javascript:alert(1)</script>",
|
||||
"\"`'><script>\\xE2\\x80\\x85javascript:alert(1)</script>",
|
||||
"\"`'><script>\\xE2\\x80\\x88javascript:alert(1)</script>",
|
||||
"\"`'><script>\\x00javascript:alert(1)</script>",
|
||||
"\"`'><script>\\xE2\\x80\\xA8javascript:alert(1)</script>",
|
||||
"\"`'><script>\\xE2\\x80\\x8Ajavascript:alert(1)</script>",
|
||||
"\"`'><script>\\xE1\\x9A\\x80javascript:alert(1)</script>",
|
||||
"\"`'><script>\\x0Cjavascript:alert(1)</script>",
|
||||
"\"`'><script>\\x2Bjavascript:alert(1)</script>",
|
||||
"\"`'><script>\\xF0\\x90\\x96\\x9Ajavascript:alert(1)</script>",
|
||||
"\"`'><script>-javascript:alert(1)</script>",
|
||||
"\"`'><script>\\x0Ajavascript:alert(1)</script>",
|
||||
"\"`'><script>\\xE2\\x80\\xAFjavascript:alert(1)</script>",
|
||||
"\"`'><script>\\x7Ejavascript:alert(1)</script>",
|
||||
"\"`'><script>\\xE2\\x80\\x87javascript:alert(1)</script>",
|
||||
"\"`'><script>\\xE2\\x81\\x9Fjavascript:alert(1)</script>",
|
||||
"\"`'><script>\\xE2\\x80\\xA9javascript:alert(1)</script>",
|
||||
"\"`'><script>\\xC2\\x85javascript:alert(1)</script>",
|
||||
"\"`'><script>\\xEF\\xBF\\xAEjavascript:alert(1)</script>",
|
||||
"\"`'><script>\\xE2\\x80\\x83javascript:alert(1)</script>",
|
||||
"\"`'><script>\\xE2\\x80\\x8Bjavascript:alert(1)</script>",
|
||||
"\"`'><script>\\xEF\\xBF\\xBEjavascript:alert(1)</script>",
|
||||
"\"`'><script>\\xE2\\x80\\x80javascript:alert(1)</script>",
|
||||
"\"`'><script>\\x21javascript:alert(1)</script>",
|
||||
"\"`'><script>\\xE2\\x80\\x82javascript:alert(1)</script>",
|
||||
"\"`'><script>\\xE2\\x80\\x86javascript:alert(1)</script>",
|
||||
"\"`'><script>\\xE1\\xA0\\x8Ejavascript:alert(1)</script>",
|
||||
"\"`'><script>\\x0Bjavascript:alert(1)</script>",
|
||||
"\"`'><script>\\x20javascript:alert(1)</script>",
|
||||
"\"`'><script>\\xC2\\xA0javascript:alert(1)</script>",
|
||||
"<img \\x00src=x onerror=\"alert(1)\">",
|
||||
"<img \\x47src=x onerror=\"javascript:alert(1)\">",
|
||||
"<img \\x11src=x onerror=\"javascript:alert(1)\">",
|
||||
"<img \\x12src=x onerror=\"javascript:alert(1)\">",
|
||||
"<img\\x47src=x onerror=\"javascript:alert(1)\">",
|
||||
"<img\\x10src=x onerror=\"javascript:alert(1)\">",
|
||||
"<img\\x13src=x onerror=\"javascript:alert(1)\">",
|
||||
"<img\\x32src=x onerror=\"javascript:alert(1)\">",
|
||||
"<img\\x47src=x onerror=\"javascript:alert(1)\">",
|
||||
"<img\\x11src=x onerror=\"javascript:alert(1)\">",
|
||||
"<img \\x47src=x onerror=\"javascript:alert(1)\">",
|
||||
"<img \\x34src=x onerror=\"javascript:alert(1)\">",
|
||||
"<img \\x39src=x onerror=\"javascript:alert(1)\">",
|
||||
"<img \\x00src=x onerror=\"javascript:alert(1)\">",
|
||||
"<img src\\x09=x onerror=\"javascript:alert(1)\">",
|
||||
"<img src\\x10=x onerror=\"javascript:alert(1)\">",
|
||||
"<img src\\x13=x onerror=\"javascript:alert(1)\">",
|
||||
"<img src\\x32=x onerror=\"javascript:alert(1)\">",
|
||||
"<img src\\x12=x onerror=\"javascript:alert(1)\">",
|
||||
"<img src\\x11=x onerror=\"javascript:alert(1)\">",
|
||||
"<img src\\x00=x onerror=\"javascript:alert(1)\">",
|
||||
"<img src\\x47=x onerror=\"javascript:alert(1)\">",
|
||||
"<img src=x\\x09onerror=\"javascript:alert(1)\">",
|
||||
"<img src=x\\x10onerror=\"javascript:alert(1)\">",
|
||||
"<img src=x\\x11onerror=\"javascript:alert(1)\">",
|
||||
"<img src=x\\x12onerror=\"javascript:alert(1)\">",
|
||||
"<img src=x\\x13onerror=\"javascript:alert(1)\">",
|
||||
"<img[a][b][c]src[d]=x[e]onerror=[f]\"alert(1)\">",
|
||||
"<img src=x onerror=\\x09\"javascript:alert(1)\">",
|
||||
"<img src=x onerror=\\x10\"javascript:alert(1)\">",
|
||||
"<img src=x onerror=\\x11\"javascript:alert(1)\">",
|
||||
"<img src=x onerror=\\x12\"javascript:alert(1)\">",
|
||||
"<img src=x onerror=\\x32\"javascript:alert(1)\">",
|
||||
"<img src=x onerror=\\x00\"javascript:alert(1)\">",
|
||||
"<a href=javascript:javascript:alert(1)>XXX</a>",
|
||||
"<img src=\"x` `<script>javascript:alert(1)</script>\"` `>",
|
||||
"<img src onerror /\" '\"= alt=javascript:alert(1)//\">",
|
||||
"<title onpropertychange=javascript:alert(1)></title><title title=>",
|
||||
"<a href=http://foo.bar/#x=`y></a><img alt=\"`><img src=x:x onerror=javascript:alert(1)></a>\">",
|
||||
"<!--[if]><script>javascript:alert(1)</script -->",
|
||||
"<!--[if<img src=x onerror=javascript:alert(1)//]> -->",
|
||||
"<script src=\"/\\%(jscript)s\"></script>",
|
||||
"<script src=\"\\\\%(jscript)s\"></script>",
|
||||
"<IMG \"\"\"><SCRIPT>alert(\"XSS\")</SCRIPT>\">",
|
||||
"<IMG SRC=javascript:alert(String.fromCharCode(88,83,83))>",
|
||||
"<IMG SRC=# onmouseover=\"alert('xxs')\">",
|
||||
"<IMG SRC= onmouseover=\"alert('xxs')\">",
|
||||
"<IMG onmouseover=\"alert('xxs')\">",
|
||||
"<IMG SRC=javascript:alert('XSS')>",
|
||||
"<IMG SRC=javascript:alert('XSS')>",
|
||||
"<IMG SRC=javascript:alert('XSS')>",
|
||||
"<IMG SRC=\"jav ascript:alert('XSS');\">",
|
||||
"<IMG SRC=\"jav	ascript:alert('XSS');\">",
|
||||
"<IMG SRC=\"jav
ascript:alert('XSS');\">",
|
||||
"<IMG SRC=\"jav
ascript:alert('XSS');\">",
|
||||
"perl -e 'print \"<IMG SRC=java\\0script:alert(\\\"XSS\\\")>\";' > out",
|
||||
"<IMG SRC=\"  javascript:alert('XSS');\">",
|
||||
"<SCRIPT/XSS SRC=\"http://ha.ckers.org/xss.js\"></SCRIPT>",
|
||||
"<BODY onload!#$%&()*~+-_.,:;?@[/|\\]^`=alert(\"XSS\")>",
|
||||
"<SCRIPT/SRC=\"http://ha.ckers.org/xss.js\"></SCRIPT>",
|
||||
"<<SCRIPT>alert(\"XSS\");//<</SCRIPT>",
|
||||
"<SCRIPT SRC=http://ha.ckers.org/xss.js?< B >",
|
||||
"<SCRIPT SRC=//ha.ckers.org/.j>",
|
||||
"<IMG SRC=\"javascript:alert('XSS')\"",
|
||||
"<iframe src=http://ha.ckers.org/scriptlet.html <",
|
||||
"\\\";alert('XSS');//",
|
||||
"<u oncopy=alert()> Copy me</u>",
|
||||
"<i onwheel=alert(1)> Scroll over me </i>",
|
||||
"<plaintext>",
|
||||
"http://a/%%30%30",
|
||||
"1;DROP TABLE users",
|
||||
"1'; DROP TABLE users-- 1",
|
||||
"' OR 1=1 -- 1",
|
||||
"' OR '1'='1",
|
||||
" ",
|
||||
"%",
|
||||
"_",
|
||||
"-",
|
||||
"--",
|
||||
"--version",
|
||||
"--help",
|
||||
"$USER",
|
||||
"/dev/null; touch /tmp/blns.fail ; echo",
|
||||
"`touch /tmp/blns.fail`",
|
||||
"$(touch /tmp/blns.fail)",
|
||||
"@{[system \"touch /tmp/blns.fail\"]}",
|
||||
"eval(\"puts 'hello world'\")",
|
||||
"System(\"ls -al /\")",
|
||||
"`ls -al /`",
|
||||
"Kernel.exec(\"ls -al /\")",
|
||||
"Kernel.exit(1)",
|
||||
"%x('ls -al /')",
|
||||
"<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?><!DOCTYPE foo [ <!ELEMENT foo ANY ><!ENTITY xxe SYSTEM \"file:///etc/passwd\" >]><foo>&xxe;</foo>",
|
||||
"$HOME",
|
||||
"$ENV{'HOME'}",
|
||||
"%d",
|
||||
"%s",
|
||||
"{0}",
|
||||
"%*.*s",
|
||||
"../../../../../../../../../../../etc/passwd%00",
|
||||
"../../../../../../../../../../../etc/hosts",
|
||||
"() { 0; }; touch /tmp/blns.shellshock1.fail;",
|
||||
"() { _; } >_[$($())] { touch /tmp/blns.shellshock2.fail; }",
|
||||
"CON",
|
||||
"PRN",
|
||||
"AUX",
|
||||
"CLOCK$",
|
||||
"NUL",
|
||||
"A:",
|
||||
"ZZ:",
|
||||
"COM1",
|
||||
"LPT1",
|
||||
"LPT2",
|
||||
"LPT3",
|
||||
"COM2",
|
||||
"COM3",
|
||||
"COM4",
|
||||
"Scunthorpe General Hospital",
|
||||
"Penistone Community Church",
|
||||
"Lightwater Country Park",
|
||||
"Jimmy Clitheroe",
|
||||
"Horniman Museum",
|
||||
"shitake mushrooms",
|
||||
"RomansInSussex.co.uk",
|
||||
"http://www.cum.qc.ca/",
|
||||
"Craig Cockburn, Software Specialist",
|
||||
"Linda Callahan",
|
||||
"Dr. Herman I. Libshitz",
|
||||
"magna cum laude",
|
||||
"Super Bowl XXX",
|
||||
"medieval erection of parapets",
|
||||
"evaluate",
|
||||
"mocha",
|
||||
"expression",
|
||||
"Arsenal canal",
|
||||
"classic",
|
||||
"Tyson Gay",
|
||||
"basement",
|
||||
"If you're reading this, you've been in a coma for almost 20 years now. We're trying a new technique. We don't know where this message will end up in your dream, but we hope it works. Please wake up, we miss you.",
|
||||
"Powerلُلُصّبُلُلصّبُررً ॣ ॣh ॣ ॣ冗"
|
||||
]
|
|
@ -82,9 +82,11 @@ ldap.objectclasses = 'top, person, organizationalPerson, user'
|
|||
ldap.dn_user_attr = 'uid'
|
||||
ldap.timeout = 1
|
||||
|
||||
ad.module = 'ldapcherry.backend.backendSamba4'
|
||||
ad.auth = 'Administrator'
|
||||
ad.password = 'password'
|
||||
ad.module = 'ldapcherry.backend.backendAD'
|
||||
ad.domain = 'dc.ldapcherry.org'
|
||||
ad.login = 'administrator'
|
||||
ad.password = 'qwertyP455'
|
||||
ad.uri = 'ldap://ldap.ldapcherry.org'
|
||||
|
||||
# authentification parameters
|
||||
[auth]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
||||
|
@ -21,25 +23,26 @@ class TestError(object):
|
|||
def testGetSelfAttributes(self):
|
||||
inv = Attributes('./tests/cfg/attributes.yml')
|
||||
ret = inv.get_selfattributes()
|
||||
expected = {'password': {'backends': {'ad': 'userPassword', 'ldap': 'userPassword'}, 'display_name': 'Password', 'description': 'Password of the user', 'weight': 31, 'self': True, 'type': 'password'}, 'shell': {'backends': {'ad': 'SHELL', 'ldap': 'shell'}, 'display_name': 'Shell', 'description': 'Shell of the user', 'weight': 80, 'values': ['/bin/bash', '/bin/zsh', '/bin/sh'], 'self': True, 'type': 'stringlist'}}
|
||||
expected = {'password': {'backends': {'ad': 'unicodePwd', 'ldap': 'userPassword'}, 'display_name': 'Password', 'description': 'Password of the user', 'weight': 31, 'self': True, 'type': 'password'}, 'shell': {'backends': {'ad': 'SHELL', 'ldap': 'shell'}, 'display_name': 'Shell', 'description': 'Shell of the user', 'weight': 80, 'values': ['/bin/bash', '/bin/zsh', '/bin/sh'], 'self': True, 'type': 'stringlist'}}
|
||||
assert ret == expected
|
||||
|
||||
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):
|
||||
inv = Attributes('./tests/cfg/attributes.yml')
|
||||
ret = inv.get_search_attributes()
|
||||
expected = {'first-name': {'backends': {'ad': 'givenName', 'ldap': 'givenName'}, 'display_name': 'First Name', 'description': 'First name of the user', 'weight': 20, 'search_displayed': True, 'type': 'string'}, 'cn': {'autofill': {'function': 'cn', 'args': ['$first-name', '$name']}, 'backends': {'ad': 'CN', 'ldap': 'cn'}, 'display_name': 'Display Name', 'description': 'Firt Name and Display Name', 'weight': 30, 'search_displayed': True, 'type': 'string'}, 'name': {'backends': {'ad': 'sn', 'ldap': 'sn'}, 'display_name': 'Name', 'description': 'Family name of the user', 'weight': 10, 'search_displayed': True, 'type': 'string'}, 'uid': {'display_name': 'UID', 'description': 'UID of the user', 'weight': 50, 'autofill': {'function': 'uid', 'args': ['$first-name', '$last-name']}, 'backends': {'ad': 'UID', 'ldap': 'uid'}, 'key': True, 'search_displayed': True, 'type': 'string'}}
|
||||
expected = {'first-name': {'backends': {'ad': 'givenName', 'ldap': 'givenName'}, 'display_name': 'First Name', 'description': 'First name of the user', 'weight': 20, 'search_displayed': True, 'type': 'string'}, 'cn': {'autofill': {'function': 'cn', 'args': ['$first-name', '$name']}, 'backends': {'ad': 'cn', 'ldap': 'cn'}, 'display_name': 'Display Name', 'description': 'Firt Name and Display Name', 'weight': 30, 'search_displayed': True, 'type': 'string'}, 'name': {'backends': {'ad': 'sn', 'ldap': 'sn'}, 'display_name': 'Name', 'description': 'Family name of the user', 'weight': 10, 'search_displayed': True, 'type': 'string'}, 'uid': {'display_name': 'UID', 'description': 'UID of the user', 'weight': 50, 'autofill': {'function': 'uid', 'args': ['$first-name', '$last-name']}, 'backends': {'ad': 'UID', 'ldap': 'uid'}, 'key': True, 'search_displayed': True, 'type': 'string'}}
|
||||
assert ret == expected
|
||||
|
||||
def testGetBackendAttributes(self):
|
||||
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):
|
||||
|
|
|
@ -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")
|
|
@ -0,0 +1,160 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import with_statement
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import pytest
|
||||
import sys
|
||||
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',
|
||||
'admin.groups': 'grp1, grp2',
|
||||
'basic.groups': 'grp1, grp2, grp3',
|
||||
'pwd_attr': 'userPassword',
|
||||
'search_attributes': 'uid',
|
||||
}
|
||||
|
||||
def syslog_error(msg='', context='',
|
||||
severity=logging.INFO, traceback=False):
|
||||
pass
|
||||
|
||||
cherrypy.log.error = syslog_error
|
||||
attr = ['shéll', 'shell', 'cn', 'uid', 'uidNumber', 'gidNumber', 'home', 'userPassword', 'givenName', 'email', 'sn']
|
||||
|
||||
default_user = {
|
||||
'uid': 'default_user',
|
||||
'sn': 'test',
|
||||
'cn': 'test',
|
||||
'userPassword': 'test',
|
||||
'uidNumber': '42',
|
||||
'gidNumber': '42',
|
||||
'homeDirectory': '/home/test/'
|
||||
}
|
||||
|
||||
default_user2 = {
|
||||
'uid': 'default_user2',
|
||||
'sn': 'test',
|
||||
'cn': 'test',
|
||||
'userPassword': 'test',
|
||||
'uidNumber': '42',
|
||||
'gidNumber': '42',
|
||||
'homeDirectory': '/home/test/'
|
||||
}
|
||||
|
||||
|
||||
|
||||
default_groups = ['grp1', 'grp2', 'grp3']
|
||||
|
||||
|
||||
class TestError(object):
|
||||
|
||||
def testNominal(self):
|
||||
inv = Backend(cfg, cherrypy.log, 'test', attr, 'uid')
|
||||
return True
|
||||
|
||||
def testAuthSuccess(self):
|
||||
inv = Backend(cfg, cherrypy.log, 'test', attr, 'uid')
|
||||
ret = inv.auth('admin', 'admin')
|
||||
assert ret == True
|
||||
|
||||
def testAuthFailure(self):
|
||||
inv = Backend(cfg, cherrypy.log, 'test', attr, 'uid')
|
||||
res = inv.auth('notauser', 'password') or inv.auth('default_user', 'notapassword')
|
||||
assert res == False
|
||||
|
||||
def testGetUser(self):
|
||||
inv = Backend(cfg, cherrypy.log, 'test', attr, 'uid')
|
||||
inv.add_user(default_user)
|
||||
inv.add_to_groups('default_user', default_groups)
|
||||
ret = inv.get_user('default_user')
|
||||
expected = default_user
|
||||
assert ret == expected
|
||||
|
||||
def testGetGroups(self):
|
||||
inv = Backend(cfg, cherrypy.log, 'test', attr, 'uid')
|
||||
inv.add_user(default_user)
|
||||
inv.add_to_groups('default_user', default_groups)
|
||||
ret = inv.get_groups('default_user')
|
||||
expected = set(default_groups)
|
||||
assert ret == expected
|
||||
|
||||
def testSearchUser(self):
|
||||
inv = Backend(cfg, cherrypy.log, 'test', attr, 'uid')
|
||||
inv.add_user(default_user)
|
||||
inv.add_user(default_user2)
|
||||
ret = inv.search('default')
|
||||
expected = ['default_user', 'default_user2']
|
||||
assert set(ret.keys()) == set(expected)
|
||||
|
||||
def testAddUser(self):
|
||||
try:
|
||||
inv.del_user(u'test☭')
|
||||
except:
|
||||
pass
|
||||
inv = Backend(cfg, cherrypy.log, 'test', attr, 'uid')
|
||||
user = {
|
||||
'uid': u'test☭',
|
||||
'sn': 'test',
|
||||
'cn': 'test',
|
||||
'userPassword': 'test',
|
||||
'uidNumber': '42',
|
||||
'gidNumber': '42',
|
||||
'homeDirectory': '/home/test/'
|
||||
}
|
||||
inv.add_user(user)
|
||||
inv.del_user(u'test☭')
|
||||
|
||||
def testModifyUser(self):
|
||||
inv = Backend(cfg, cherrypy.log, 'test', attr, 'uid')
|
||||
user = {
|
||||
'uid': u'test☭',
|
||||
'sn': 'test',
|
||||
'cn': 'test',
|
||||
'userPassword': 'test',
|
||||
'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☭')
|
||||
|
||||
def testAddUserDuplicate(self):
|
||||
inv = Backend(cfg, cherrypy.log, 'test', attr, 'uid')
|
||||
user = {
|
||||
'uid': 'test',
|
||||
'sn': 'test',
|
||||
'cn': 'test',
|
||||
'uidNumber': '42',
|
||||
'userPassword': 'test',
|
||||
'gidNumber': '42',
|
||||
'homeDirectory': '/home/test/'
|
||||
}
|
||||
try:
|
||||
inv.add_user(user)
|
||||
inv.add_user(user)
|
||||
except UserAlreadyExists:
|
||||
inv.del_user('test')
|
||||
return
|
||||
else:
|
||||
inv.del_user('test')
|
||||
raise AssertionError("expected an exception")
|
||||
|
||||
def testDelUserDontExists(self):
|
||||
inv = Backend(cfg, cherrypy.log, 'test', attr, 'uid')
|
||||
try:
|
||||
inv.del_user('test')
|
||||
inv.del_user('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,13 +6,14 @@ from __future__ import unicode_literals
|
|||
|
||||
import pytest
|
||||
import sys
|
||||
from sets import Set
|
||||
from ldapcherry.backend.backendLdap import Backend, DelUserDontExists, CaFileDontExist
|
||||
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',
|
||||
|
@ -31,6 +32,7 @@ cfg = {
|
|||
'dn_user_attr' : 'uid',
|
||||
'group_attr.member' : "%(dn)s",
|
||||
'timeout' : 10,
|
||||
'display_name' : 'My Test Ldap',
|
||||
}
|
||||
|
||||
def syslog_error(msg='', context='',
|
||||
|
@ -103,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")
|
||||
|
||||
|
@ -119,33 +122,23 @@ class TestError(object):
|
|||
|
||||
def testAuthSuccess(self):
|
||||
inv = Backend(cfg, cherrypy.log, 'ldap', attr, 'uid')
|
||||
ret = inv.auth('jwatson', 'passwordwatson')
|
||||
ret = inv.auth(u'jwatsoné', u'passwordwatsoné')
|
||||
assert ret == True
|
||||
|
||||
def testAuthFailure(self):
|
||||
inv = Backend(cfg, cherrypy.log, 'ldap', attr, 'uid')
|
||||
res = inv.auth('notauser', 'password') or inv.auth('jwatson', 'notapassword')
|
||||
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('jwatson')
|
||||
expected = {'uid': 'jwatson', 'cn': 'John Watson', 'sn': 'watson'}
|
||||
ret = inv.get_user(u'jwatsoné')
|
||||
expected = {'uid': u'jwatsoné', 'cn': 'John Watson', 'sn': 'watson'}
|
||||
assert ret == expected
|
||||
|
||||
def testGetGroups(self):
|
||||
inv = Backend(cfg, cherrypy.log, 'ldap', attr, 'uid')
|
||||
ret = inv.get_groups('jwatson')
|
||||
ret = inv.get_groups(u'jwatsoné')
|
||||
expected = ['cn=itpeople,ou=Groups,dc=example,dc=org']
|
||||
assert ret == expected
|
||||
|
||||
|
@ -155,14 +148,12 @@ class TestError(object):
|
|||
'cn=hrpeople,ou=Groups,dc=example,dc=org',
|
||||
'cn=itpeople,ou=Groups,dc=example,dc=org',
|
||||
]
|
||||
inv.add_to_groups('jwatson', groups)
|
||||
ret = inv.get_groups('jwatson')
|
||||
print ret
|
||||
inv.del_from_groups('jwatson', ['cn=hrpeople,ou=Groups,dc=example,dc=org'])
|
||||
inv.del_from_groups('jwatson', ['cn=hrpeople,ou=Groups,dc=example,dc=org'])
|
||||
inv.add_to_groups(u'jwatsoné', groups)
|
||||
ret = inv.get_groups(u'jwatsoné')
|
||||
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')
|
||||
|
@ -170,78 +161,82 @@ class TestError(object):
|
|||
assert ret == expected
|
||||
|
||||
def testAddUser(self):
|
||||
try:
|
||||
inv.del_user(u'test☭,cn=')
|
||||
except:
|
||||
pass
|
||||
inv = Backend(cfg, cherrypy.log, 'ldap', attr, 'uid')
|
||||
user = {
|
||||
'uid': '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('test')
|
||||
inv.del_user(u'test☭,cn=')
|
||||
|
||||
def testModifyUser(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☭',
|
||||
'uidNumber': '42',
|
||||
'gidNumber': '42',
|
||||
'homeDirectory': '/home/test/'
|
||||
}
|
||||
inv.add_user(user)
|
||||
inv.set_attrs('test', {'gecos': 'test2', 'homeDirectory': '/home/test/'})
|
||||
inv.del_user('test')
|
||||
inv.set_attrs(u'test☭', {'gecos': 'test2', 'homeDirectory': '/home/test/'})
|
||||
inv.del_user(u'test☭')
|
||||
|
||||
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/'
|
||||
}
|
||||
try:
|
||||
inv.add_user(user)
|
||||
inv.add_user(user)
|
||||
except ldap.ALREADY_EXISTS:
|
||||
inv.del_user('test')
|
||||
except UserAlreadyExists:
|
||||
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')
|
||||
except DelUserDontExists:
|
||||
inv.del_user(u'test☭')
|
||||
inv.del_user(u'test☭')
|
||||
except UserDoesntExist:
|
||||
return
|
||||
else:
|
||||
raise AssertionError("expected an exception")
|
||||
|
||||
def testGetUser(self):
|
||||
inv = Backend(cfg, cherrypy.log, 'ldap', attr, 'uid')
|
||||
ret = inv.get_user('jwatson')
|
||||
expected = {'uid': 'jwatson', 'objectClass': 'inetOrgPerson', 'carLicense': 'HERCAR 125', 'sn': 'watson', 'mail': 'j.watson@example.com', 'homePhone': '555-111-2225', 'cn': 'John Watson', 'userPassword': u'passwordwatson'}
|
||||
ret = inv.get_user(u'jwatsoné')
|
||||
expected = {'uid': u'jwatsoné', 'objectClass': 'inetOrgPerson', 'carLicense': 'HERCAR 125', 'sn': 'watson', 'mail': 'j.watson@example.com', 'homePhone': '555-111-2225', 'cn': 'John Watson', 'userPassword': u'passwordwatsoné'}
|
||||
assert ret == expected
|
||||
|
||||
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/'
|
||||
}
|
||||
|
@ -250,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,17 +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"""
|
||||
|
@ -54,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
|
||||
|
@ -103,12 +152,22 @@ 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)
|
||||
app._set_error_log(cfg, logging.DEBUG)
|
||||
|
||||
def testAuth(self):
|
||||
app = LdapCherry()
|
||||
loadconf('./tests/cfg/ldapcherry_test.ini', app)
|
||||
app.auth_mode = 'and'
|
||||
ret1 = app._auth('jsmith', 'passwordsmith')
|
||||
app.auth_mode = 'or'
|
||||
ret2 = app._auth('jsmith', 'passwordsmith')
|
||||
assert ret2 == {'connected': True, 'isadmin': False} and \
|
||||
ret1 == {'connected': True, 'isadmin': False}
|
||||
|
||||
def testPPolicy(self):
|
||||
app = LdapCherry()
|
||||
loadconf('./tests/cfg/ldapcherry.ini', app)
|
||||
|
@ -144,26 +203,70 @@ class TestError(object):
|
|||
|
||||
def testLogin(self):
|
||||
app = LdapCherry()
|
||||
loadconf('./tests/cfg/ldapcherry.ini', app)
|
||||
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")
|
||||
|
||||
def testLoginFailure(self):
|
||||
app = LdapCherry()
|
||||
loadconf('./tests/cfg/ldapcherry_test.ini', app)
|
||||
app.auth_mode = 'or'
|
||||
try:
|
||||
app.login(u'jwatsoné', u'wrongPasswordé')
|
||||
except cherrypy.HTTPRedirect as e:
|
||||
expected = 'http://127.0.0.1:8080/signin'
|
||||
assert e.urls[0] == expected
|
||||
else:
|
||||
raise AssertionError("expected an exception")
|
||||
|
||||
def testSearch(self):
|
||||
app = LdapCherry()
|
||||
loadconf('./tests/cfg/ldapcherry.ini', app)
|
||||
expected = {u'ssmith': {'password': u'passwordsmith', 'cn': u'Sheri Smith', 'name': u'smith', 'uid': u'ssmith'}, u'jsmith': {'password': u'passwordsmith', 'cn': u'John Smith', 'name': u'Smith', 'uid': u'jsmith'}}
|
||||
loadconf('./tests/cfg/ldapcherry_test.ini', app)
|
||||
expected = {
|
||||
u'ssmith': {
|
||||
'password': u'passwordsmith',
|
||||
'cn': u'Sheri Smith',
|
||||
'name': u'smith',
|
||||
'uid': u'ssmith',
|
||||
'email': [u's.smith@example.com',
|
||||
u'ssmith@example.com',
|
||||
u'sheri.smith@example.com'
|
||||
],
|
||||
},
|
||||
u'jsmith': {
|
||||
'password': u'passwordsmith',
|
||||
'cn': u'John Smith',
|
||||
'name': u'Smith',
|
||||
'uid': u'jsmith',
|
||||
'email': [
|
||||
'j.smith@example.com',
|
||||
'jsmith@example.com',
|
||||
'jsmith.smith@example.com'
|
||||
],
|
||||
}
|
||||
}
|
||||
ret = app._search('smith')
|
||||
assert expected == ret
|
||||
|
||||
def testGetUser(self):
|
||||
app = LdapCherry()
|
||||
loadconf('./tests/cfg/ldapcherry.ini', app)
|
||||
expected = {'password': u'passwordsmith', 'cn': u'Sheri Smith', 'uid': u'ssmith', 'name': u'smith'}
|
||||
loadconf('./tests/cfg/ldapcherry_test.ini', app)
|
||||
expected = {
|
||||
'password': u'passwordsmith',
|
||||
'cn': u'Sheri Smith',
|
||||
'uid': u'ssmith',
|
||||
'name': u'smith',
|
||||
'email': [u's.smith@example.com',
|
||||
u'ssmith@example.com',
|
||||
u'sheri.smith@example.com'
|
||||
],
|
||||
}
|
||||
ret = app._get_user('ssmith')
|
||||
assert expected == ret
|
||||
|
||||
|
@ -190,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)
|
||||
|
@ -206,16 +310,78 @@ class TestError(object):
|
|||
print(page)
|
||||
htmlvalidator(pages[page])
|
||||
|
||||
def testNoneType(self):
|
||||
app = LdapCherry()
|
||||
loadconf('./tests/cfg/ldapcherry_test.ini', app)
|
||||
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)
|
||||
with open('./tests/cfg/blns.json') as data_file:
|
||||
data = json.load(data_file)
|
||||
for attr in data:
|
||||
print('testing: ' + attr)
|
||||
# delete whatever is happening...
|
||||
try:
|
||||
app._deleteuser('test')
|
||||
except:
|
||||
pass
|
||||
form = {'groups': {}, 'attrs': {'password1': u'password☭', 'password2': u'password☭', 'cn': 'Test', 'name': attr, 'uidNumber': u'1000', 'gidNumber': u'1000', 'home': u'/home/test', 'first-name': u'Test ☭', 'email': u'test@test.fr', 'uid': 'test'}, 'roles': {'admin-lv3': u'on', 'admin-lv2': u'on', 'users': u'on'}}
|
||||
app._adduser(form)
|
||||
page = app.searchuser('test'),
|
||||
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)
|
||||
assert app._get_loglevel('debug') is logging.DEBUG and \
|
||||
app._get_loglevel('notice') is logging.INFO and \
|
||||
app._get_loglevel('info') is logging.INFO and \
|
||||
app._get_loglevel('warning') is logging.WARNING and \
|
||||
app._get_loglevel('err') is logging.ERROR and \
|
||||
app._get_loglevel('critical') is logging.CRITICAL and \
|
||||
app._get_loglevel('alert') is logging.CRITICAL and \
|
||||
app._get_loglevel('emergency') is logging.CRITICAL and \
|
||||
app._get_loglevel('notalevel') is logging.INFO
|
||||
|
||||
assert get_loglevel('debug') is logging.DEBUG and \
|
||||
get_loglevel('notice') is logging.INFO and \
|
||||
get_loglevel('info') is logging.INFO and \
|
||||
get_loglevel('warning') is logging.WARNING and \
|
||||
get_loglevel('err') is logging.ERROR and \
|
||||
get_loglevel('critical') is logging.CRITICAL and \
|
||||
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,39 +1,27 @@
|
|||
#!/bin/sh
|
||||
|
||||
if ! [ -z "$TRAVIS" ]
|
||||
then
|
||||
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 kpartx -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" -f -q -y
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install lsb-base libattr1 -t wheezy -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" -f -q -y
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install samba python-samba samba-vfs-modules -t wheezy-backports -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" -f -q -y
|
||||
truncate -s 512M file.img
|
||||
fdisk file.img <<EOF
|
||||
n
|
||||
p
|
||||
1
|
||||
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
|
||||
|
||||
w
|
||||
q
|
||||
EOF
|
||||
kpartx -a file.img
|
||||
mkfs.ext4 /dev/mapper/loop0p1
|
||||
mount /dev/mapper/loop0p1 /var/lib/samba/
|
||||
else
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install ldap-utils slapd samba python-samba samba-vfs-modules -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" -f -q -y
|
||||
fi
|
||||
|
||||
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
|
||||
|
@ -44,6 +32,8 @@ 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
|
||||
|
@ -53,32 +43,43 @@ 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} \
|
||||
--domain="${domain}" --realm="${realm}" --dns-backend="${sambadns}" \
|
||||
--targetdir="${targetdir}" --workgroup="${domain}" --use-rfc2307 \
|
||||
--targetdir="${targetdir}" --use-rfc2307 \
|
||||
--configfile="${smbconffile}" --server-role="${role}" -d 1 --adminpass="${adpass}"
|
||||
|
||||
|
||||
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
|
||||
if ! [ -z "$TRAVIS" ]
|
||||
then
|
||||
/usr/sbin/samba -D -s /etc/samba/smb.conf
|
||||
# /usr/sbin/smbd -D --option=server role check:inhibit=yes --foreground
|
||||
else
|
||||
sh -x /etc/init.d/samba start
|
||||
sh -x /etc/init.d/samba-ad-dc start
|
||||
sh -x /etc/init.d/smbd start
|
||||
sh -x /etc/init.d/nmbd start
|
||||
fi
|
||||
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
|
||||
|
|
|
@ -61,8 +61,8 @@ dn: cn=John Watson,ou=people,dc=example,dc=org
|
|||
objectclass: inetOrgPerson
|
||||
cn: John Watson
|
||||
sn: watson
|
||||
uid: jwatson
|
||||
userpassword: passwordwatson
|
||||
uid: jwatsoné
|
||||
userpassword: passwordwatsoné
|
||||
carlicense: HERCAR 125
|
||||
homephone: 555-111-2225
|
||||
mail: j.watson@example.com
|
||||
|
@ -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
|
|
@ -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
|
|
@ -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/'
|
|
@ -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…
Reference in New Issue