Compare commits

...

294 Commits

Author SHA1 Message Date
Carpentier Pierre-Francois dd331f948c
Update README.rst 2024-03-13 01:13:02 +01:00
Carpentier Pierre-Francois a2e985576b
Update README.rst 2024-03-13 00:57:26 +01:00
kakwa 6a2e6e56d0 fix CI badge and remove the coverage one 2024-03-13 00:35:48 +01:00
kakwa 5d8fc08f5b re-enable ssl/tls 2024-03-13 00:29:50 +01:00
kakwa 59855f0090 just rename stuff 2024-03-13 00:21:04 +01:00
kakwa d1215bc56f fix package name 2024-03-13 00:18:47 +01:00
kakwa ef11e6a7e3 more deps 2024-03-13 00:17:33 +01:00
kakwa 3596e14249 adding stuff to install 2024-03-13 00:13:11 +01:00
kakwa b94713acb6 sudo for CI 2024-03-13 00:10:06 +01:00
kakwa de16df475f add basic gh action tests 2024-03-13 00:06:44 +01:00
kakwa 242c9ab96e update test ldap configuration 2024-03-13 00:00:14 +01:00
kakwa 48dbff983d update shebang 2024-03-13 00:00:06 +01:00
kakwa 6413b782dd update shebang 2024-03-12 23:59:46 +01:00
Carpentier Pierre-Francois 55c49b4eff
Merge pull request #68 from kakwa/dependabot/pip/python-ldap-3.4.0
Bump python-ldap from 2.4.15 to 3.4.0
2024-03-12 22:39:47 +01:00
Carpentier Pierre-Francois 53a4b6fd5e
Merge pull request #59 from cyberb/master
lcCopy
2024-03-12 22:36:33 +01:00
dependabot[bot] 0ccbe95d9a
Bump python-ldap from 2.4.15 to 3.4.0
Bumps [python-ldap](https://github.com/python-ldap/python-ldap) from 2.4.15 to 3.4.0.
- [Release notes](https://github.com/python-ldap/python-ldap/releases)
- [Commits](https://github.com/python-ldap/python-ldap/commits/python-ldap-3.4.0)

---
updated-dependencies:
- dependency-name: python-ldap
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-29 18:00:11 +00:00
Carpentier Pierre-Francois 4da050236d
Merge pull request #49 from smacz42/andrewcz-ldapcherry-issue34
Add demo backend configuration files in goodies
2020-05-20 19:24:55 +02:00
Carpentier Pierre-Francois 135699bd48
Merge pull request #62 from Sohalt/master
fix typo
2020-04-07 20:58:09 +02:00
sohalt 1549a172f7 fix typo 2020-04-07 19:15:31 +02:00
Boris Rybalkin e2ab3e85d8 lcCopy 2020-01-10 22:10:28 +00:00
Carpentier Pierre-Francois b5907ce340
Merge pull request #55 from StbX/master
add missing import
2019-10-23 21:09:28 +02:00
Steffen Brandemann 791895d4c3 add missing os import 2019-10-21 23:17:20 +02:00
AndrewCz b12add4a0f Add demo backend configuration files in goodies 2019-07-21 20:06:19 -04:00
Carpentier Pierre-Francois 856157af79
Merge pull request #24 from jqueuniet/fix_default_handler
Fix default handler arguments
2019-05-03 12:09:13 +02:00
Carpentier Pierre-Francois b5e7cb6a44
Merge pull request #33 from smacz42/install-in-readme
Add install command to setup in README
2019-04-23 19:56:51 +02:00
smacz d61c89460e Add install command to setup in README 2019-04-23 11:24:17 -04:00
Johann Queuniet 0a96ca61d5
Fix default handler arguments 2019-03-26 11:05:39 +01:00
Carpentier Pierre-Francois 3b58f1464e
Merge pull request #23 from jqueuniet/fix_email_regexp
Change the email regular expression to fix a few validation issues
2019-03-20 18:33:19 +01:00
Johann Queuniet 50c6259035
Fix email regexp 2019-03-20 15:57:45 +01:00
kakwa 245bafb01c typo 2019-02-13 09:44:47 +01:00
kakwa 5ee8a74040 update documentation 2019-02-13 09:41:17 +01:00
kakwa 96acda7aa6 fix formatting 2019-02-13 09:19:08 +01:00
kakwa 30c28c5feb slightly more robust unit tests
pre-cleanup to avoid contamination if other tests fail
2019-02-12 23:28:52 +01:00
kakwa dc60300a29 version + changelog 2019-02-12 23:28:41 +01:00
kakwa 882a303474 fix crash due to encoding in python 2 2019-02-12 23:27:30 +01:00
kakwa d831b09293 improve documentation
* improve documentation for key: True flag in attributes.yml
* improve documentation for the ldap filters and their templating
* improve comment in the .ini file
2019-02-12 23:06:42 +01:00
kakwa 7ac7118c9a adding a debug log to help figure out issues with filters. 2019-02-12 22:43:03 +01:00
kakwa d690bbdc41 passing the correct logger to the backend
previously, the default logger was passed, this logger was using the
default configuration and log level, not honoring log level in
particular.
As a consequence, it was impossible to get debug logs from the backend.
This is now working as expected.
2019-02-12 22:40:42 +01:00
kakwa 73c02ccff4 disable default logger if running in debug mode (-D)
Debug mode can lead to log sensitive data (like password changes).
Having -D to log exclusively to stderr is a safer option.
2019-02-12 22:36:16 +01:00
kakwa 799ca2403f fix the urls for modify and delete
The id of the user is passed through the querystring in this page.
But the id was not properly escaped to be included as a querystring
parameter leading to weird issues like.
2019-02-12 21:24:24 +01:00
kakwa bbafafae60 remove the double escaping.
Now the escaping is done by in the templates.
We need to remove the previous escaping done by hand in the code.
Otherwise, we end-up with double escaping and funky displaying of
fields.
2019-02-12 21:18:45 +01:00
kakwa 0cf5483785 add warning in documentation for log level 'debug' 2019-02-10 18:19:55 +01:00
kakwa df2746b996 version bump + changelog 2019-02-10 18:15:07 +01:00
kakwa e6bcf9d97d adding the possibility to log to stdout 2019-02-10 18:12:45 +01:00
kakwa 57bcaaed66 changelog and version bump 2019-02-09 20:49:23 +01:00
kakwa b68214022c fix error handling when adding user that already exists 2019-02-09 20:47:34 +01:00
kakwa 932e7a8b40 adding (mostly) working configuration example 2019-02-09 20:24:32 +01:00
kakwa abf1454278 changelog+version bump 2019-02-09 20:21:29 +01:00
kakwa 0793361d90 switch to "stable" in setup.py troves 2019-02-09 20:19:57 +01:00
kakwa f824790849 another exception 2019-02-09 19:42:48 +01:00
kakwa 7390c931b9 another exception 2019-02-09 19:37:52 +01:00
kakwa 4a8aa1c655 another exception 2019-02-09 19:32:50 +01:00
kakwa e50df5dde3 wider exception for <input> attribute "type" has invalid value 2019-02-09 19:26:31 +01:00
kakwa fba2d32b44 another exception for todylib 2019-02-09 19:21:41 +01:00
kakwa 7a8468f8b1 adding another ignore 2019-02-09 19:15:31 +01:00
kakwa 9d0d321e9b another ignore for tidylib 2019-02-09 19:09:32 +01:00
kakwa c5536bdc56 adding a fffew other exception in tidylib 2019-02-09 19:04:40 +01:00
kakwa abfce4803a fix typo 2019-02-09 18:57:50 +01:00
kakwa 046afbbe29 html_tidy cleanup 2019-02-09 18:54:35 +01:00
kakwa 98fca30fba ignoring another nav error 2019-02-09 18:44:51 +01:00
kakwa f13961790f adding exception for <nav> tags in html validation 2019-02-09 18:40:48 +01:00
kakwa a56c491ee1 cleanup in html template + tidylib
* few small cleanup in html template (avoid empty tbody, put id between
quotes)
* switch to tidylib to validate the html instead of the previous hack
calling an external service (https://html5.validator.nu/)
* remove the previous validator script
* add exception for tidylib on empty <span> (these are required by
bootstrap)
2019-02-09 18:31:37 +01:00
kakwa 02357d886a remove debug print 2019-02-09 18:18:58 +01:00
kakwa 263e6be547 fix html validator test for python 3 2019-02-09 17:40:43 +01:00
kakwa 05aace0e9d force the groups in flatten roles to be sorted
* sorting the groups helps debuggability and also permits testing
that doesn't rely on python ordering (which is different between 2 and
3).
2019-02-09 17:36:01 +01:00
kakwa baa3430e63 fix test and exception handling in code
With python 2 it was possible to do exception[0][...] to recover
details about an exception.
It's no longer authorized with python 3.
Now, we must do something like exception.args or exception.urls.
fortunately this syntax also works with python 2.
So we use it for both.
2019-02-09 17:12:39 +01:00
kakwa 90ff69586b remove deprecation warning for html escape
in python 2, (html) escape is part of the cgi module
in python 3, it's part of the html module

we now do a conditional import depending on the version, and name the
function html_escape.
2019-02-09 16:29:16 +01:00
kakwa 79983c078f fix behavior of get_attributes()
* make sure it returns an ordered list in both python 2 and python 3
2019-02-09 16:22:42 +01:00
kakwa 10747cff93 add some python 3 support in the LDAP and AD backends
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)
2019-02-09 16:08:18 +01:00
kakwa 979d4eeda8 disable ppolicy in samba test deployment 2019-02-09 15:42:48 +01:00
kakwa fb6b0a5d31 limit cherrypy to < 18.0.0 in setup.py
cherrypy dropped support for python2 with 18.0.0, 17.X is the last
version usable with python 2.
2019-02-09 12:12:24 +01:00
kakwa bbfe96d4f7 pep8 2019-02-09 12:05:09 +01:00
kakwa b9437abefb * support for python-ldap 2 and 3
* python-ldap 3 is slightly different than 2 on how it handles modify
the modified attributes used to be transmitted as a dict, now it should
be transmitted as a list of dict)
2019-02-09 11:58:09 +01:00
kakwa 60d57d8530 changelog 2019-02-08 20:47:15 +01:00
kakwa 8c0bf94904 better log+fix in conf checking + fix in ppolicy handler
* log where the backend is declared (role or attribute) when
inconsistency with main .ini file
* fix check of configuration, only role file was checked 2 times instead
on checking role one time and attribute one time
* <dict>.keys() seems to have a different behavior between 2 (return
"list") and 3 (return "dict_keys"), casting to "list" to avoid that.
2019-02-08 20:38:29 +01:00
kakwa 42759f1cc4 pep8 2019-02-08 20:38:03 +01:00
kakwa 18fdeb483e better handling of the str/byte mess for python3
* add dedicated methods for python 3 in handling of bytearrays/strings
* using them to compare attributes checks in AD backend
2019-02-08 20:33:58 +01:00
kakwa 12c511b537 switch to explicit bytearray for checking missing params 2019-02-08 00:11:01 +01:00
kakwa d25ceef2d3 trying to fix samba/AD setup 2019-02-07 23:46:10 +01:00
kakwa 8b48a1f024 cleanup in travis file 2019-02-07 22:59:57 +01:00
kakwa 7430af5ffc adding another samba package in test env 2019-02-07 22:44:51 +01:00
kakwa bc0f3aceb5 adding another dependency for the samba/ad test 2019-02-07 22:42:30 +01:00
kakwa 9989f97091 remove python3 test env for el7 and stretch (no python3-ldap lib anyway) 2019-02-07 22:38:30 +01:00
kakwa fc98b1bd70 fixing the test env deploy script + small fix in unit tests 2019-02-07 22:34:47 +01:00
kakwa ab9cd664ec fix pip install 2019-02-07 22:12:49 +01:00
kakwa 13bfbdcbbc add requirements files for simulating RHEL 7 and Debian 9 2019-02-07 22:07:28 +01:00
kakwa 70140f966a pep 8 2019-02-07 21:09:42 +01:00
kakwa 8bd4afb235 remove scripts from pycodestyle 2019-02-07 21:07:36 +01:00
kakwa 2a2864a306 porting the tests over to python3 2019-02-07 20:55:50 +01:00
kakwa c3feafdb2c pep8 2019-02-07 20:48:06 +01:00
kakwa 86fb6c1dd2 adding an update as the first step of the deploy script 2019-02-07 20:44:19 +01:00
kakwa 9f6af580cd remove env that doesn't exist 2019-02-07 20:41:22 +01:00
kakwa 5bdcc5522a switch to xenial in travis configuration 2019-02-07 20:40:22 +01:00
kakwa c81429a870 few tweaks for python3 support
* switch from script to entry_points in setup.py
* move the cli script in ldapcherry (to be used as a module)
* put the __main__ code in a dedicated function constituting the entry
point
* add a few python3 environments in travis file
2019-02-07 20:34:49 +01:00
kakwa 3d6e24eb73 pep8 2019-02-07 20:16:39 +01:00
kakwa be598b0129 slightly cleaner testenv deploy script 2019-02-06 23:55:03 +01:00
kakwa ccc252965d fix another __import__ 2019-02-06 23:04:23 +01:00
kakwa 3beedc8d4d add an ignore on the local dev conf file 2019-02-06 23:03:55 +01:00
kakwa 74dc6c5894 various changes to support python3
* changes in urllib imports since quote_plus in urllib with python 2 and
in urllib.parse in python 3
* changes in imports for Sets since set is a native type in python 3 and
doesn't requires an import
* fix in __import__, '-1' level for module path discovery is not supported
anymore, switching to 0 (absolute import only).
2019-02-06 22:32:40 +01:00
kakwa 69526610f3 add a small script to generate a local dev config 2019-02-06 22:30:59 +01:00
kakwa 921a0820f4 switch to using lists in templates
Sets are not available in mako templates when using python3.
Reverting to using lists with 'if not in' checks to avoid duplication.
2019-02-06 22:26:46 +01:00
kakwa 2df56d2de2 fix template over-escaping + python 3 support
The templates were html escaping the generated js code for the
autofill and the role management. This was breaking these features.
It's okay to not escape these as they are coming from a trusted source
(configuration file).

Also make the templates python3 compatible (not need to import Set in
python 3)
2019-02-06 21:38:11 +01:00
Carpentier Pierre-Francois 5b0c72a572
Merge pull request #17 from jthiltges/escfix
Escape form values with markupsafe
2019-01-03 23:39:53 +01:00
John Thiltges c6cce54d5f Escape form values with markupsafe
- Use markupsafe to format escaped HTML fragments
- Correct the formatting problems introduced with the XSS fixes
2019-01-03 13:12:53 -06:00
Carpentier Pierre-Francois 1f79648d57
Update ChangeLog.rst 2019-01-02 23:59:03 +01:00
Carpentier Pierre-Francois 636400b75f
Merge pull request #16 from jthiltges/escape
Protect against XSS vulnerabilities in URL redirection
2019-01-02 23:54:42 +01:00
John Thiltges 6f98076281 Protect against XSS vulnerabilities in URL redirection
- Switch from base64 to URL encoding for the passing the URL, using the built-in Mako filtering
- Apply HTML filtering to Mako output by default
- Disable HTML filtering for nested templates in adduser, modify, and selfmodify
2019-01-02 14:31:10 -06:00
Carpentier Pierre-Francois 1ed654c91b
Update README.rst 2018-02-07 19:54:23 +01:00
Carpentier Pierre-Francois c329e53811
Update README.rst 2018-02-07 19:52:29 +01:00
Carpentier Pierre-Francois 05e3a0d665 Update README.rst 2017-10-26 10:08:16 +02:00
kakwa 4bd6314b3b remove useless tests 2017-06-12 19:50:42 +02:00
kakwa c5dae7039a remove duplicated import in docs conf.py 2017-06-12 19:47:43 +02:00
kakwa ca1f78173f better documenation 2017-06-09 23:40:23 +02:00
kakwa 9ed6007b02 including fastcgi configuration example in the documentation 2017-06-09 23:25:58 +02:00
kakwa 4d696a29ef adding example for unix socket in defautl conf 2017-06-09 23:24:20 +02:00
kakwa 45d64120ae adding an nginx configuration exmaple for fastcgi 2017-06-09 23:09:11 +02:00
kakwa 00a4d22dd9 remove pip install method 2017-04-06 21:53:58 +02:00
kakwa 32c513f96e change install method (pip install just doesn't work) 2017-04-06 21:37:02 +02:00
kakwa 7019cc2348 fix setup.py 2017-04-06 20:58:20 +02:00
kakwa a404cf0b39 add auto message for tagging script 2017-04-06 20:57:42 +02:00
kakwa 9649803dd6 changelog 2017-04-06 20:52:55 +02:00
kakwa eecccac106 fix import of version in docs/conf.py and setup.py 2017-04-06 20:46:58 +02:00
kakwa f357adcd9a put version in standalone file
this way, it avoids error due to missing imports
2017-04-06 20:34:32 +02:00
kakwa e7998ced78 adding a simple tagging script 2017-04-06 20:28:44 +02:00
kakwa 8270988ed4 changelog + version bump + factorize version 2017-04-06 20:21:31 +02:00
kakwa 2e2453f309 fix camelcase 2017-04-06 01:26:54 +02:00
kakwa bbb13454bf more warning removal 2017-04-06 01:21:57 +02:00
kakwa 3378822d2e fix some warnings 2017-04-06 01:20:51 +02:00
kakwa 6e526b6f15 hack to have a cleaner resize 2017-04-06 00:32:24 +02:00
kakwa 5b1803cb05 changelog + version bump 2017-04-05 23:48:08 +02:00
kakwa de5f760c37 removing duplicate option in form select fields 2017-04-05 23:37:41 +02:00
kakwa a33a46e8b8 add dynamic resizing to align input-group-addon
* add class to identify the 2 form columns
* add a js that calculate max width and resize all input-group-addon
spans
* load the js in the base template
2017-04-05 23:24:19 +02:00
kakwa eb36830845 fixes 2017-03-16 03:03:59 +01:00
kakwa 3fd6dcee82 fix issue related to python-ldap returning lists
Before, no particular treatment was done on the user attributes.
This caused some issues because python-ldap systematically returns
the attribute value as a list (even if it's mono-valuated).

Now we recover the attributes used in the group attr templates,
and we "normalize" the user attributes before using it in add_to_groups
and del_from_groups.

By normalize, we mean, transforming the list to it's unique value.
In case the attribute doesn't exist or is multi-valuated, it raises an
error.
2017-03-16 02:45:23 +01:00
kakwa 55ce2bec5e small cleaning 2017-03-16 02:40:23 +01:00
kakwa e02a1a7f28 adding posixGroups in test ldap 2017-03-16 02:39:41 +01:00
kakwa f9a3051328 Merge branch 'master' of https://github.com/kakwa/ldapcherry 2017-03-12 17:46:49 +01:00
kakwa e4effc64ec fixing log errors in auth "none" mode
replacing None by unknown as a default value in order to avoid
error in generating log msg because None is not a string
2017-03-12 17:45:01 +01:00
Carpentier Pierre-Francois b3a361afee remove broken download stats badge 2017-03-10 00:43:51 +01:00
kakwa a802ce772a adding documention of textfielf and better documenation for other types 2017-03-07 23:21:27 +01:00
kakwa 3a1966324d adding more try catch for template debugging 2017-03-07 22:34:05 +01:00
kakwa 819e575a28 pep8ification 2017-03-07 22:23:11 +01:00
Carpentier Pierre-Francois 12bb597903 Merge pull request #6 from rooty0/feat/template-parse-error
adding support for display template parse error
2017-03-07 22:19:15 +01:00
Carpentier Pierre-Francois 7afe6c0ca7 Merge pull request #5 from rooty0/feat/add-textarea
adding textarea
2017-03-07 22:14:18 +01:00
Stan Rudenko e1a27aa0a7 adding support for display template parse error 2017-03-02 19:06:54 -08:00
Stan Rudenko f7f72c7e11 adding textarea 2017-03-02 18:47:49 -08:00
kakwa e37b88dbda fix some errors in unused code 2017-01-31 20:59:49 +01:00
kakwa d7303da85f fix test configuration 2017-01-24 03:06:48 +01:00
kakwa 44024dbd02 trying to add test on AD/DC for travis 2017-01-24 02:52:16 +01:00
kakwa 5a45a24055 proper exception in ldap backend
adding proper management of none existant user in group function
if user doesn't exist.
2016-08-01 19:57:51 +02:00
kakwa 0a4db74f1f version bump 2016-07-31 13:14:31 +02:00
kakwa f747252585 add changelog 2016-07-31 12:30:54 +02:00
kakwa 7f00264e32 improve robustness if user dn attribute contains something like ,cn= 2016-07-31 12:21:26 +02:00
kakwa d820cceeb6 pep8 2016-07-31 11:41:42 +02:00
kakwa d4235bc33c better behavior if user doesn't exist in one backend 2016-07-31 11:39:28 +02:00
kakwa f21122b219 fix AD test configuration 2016-07-31 11:32:02 +02:00
kakwa 01aaf476c1 code multi-backends setups tests + new unit test
* add a ldap+ad setup and code for direct, only AD manipulation
* add unit test for _deleteuser if it doesn't exist in all backends
2016-07-31 10:10:51 +02:00
kakwa fec09b1543 don't stop on UserDoesntExist exception on delete
previously, if a user didn't exist in one backend, the _deleteuser
function in ldapcherry would stop on the UserDoesntExist exception,
potentially leaving the user in other backends.
Now it logs it and continues to delete the user in other backend.
2016-07-31 10:06:17 +02:00
kakwa cf97f01245 adding configuration for AD + LDAP setup 2016-07-31 10:05:29 +02:00
kakwa 5ddd9a6bbf complete changelog 2016-07-29 23:08:02 +02:00
kakwa 07a60823ad Merge branch 'master' of https://github.com/kakwa/ldapcherry 2016-07-29 23:06:09 +02:00
kakwa 5ff62f0a8c fix ppolicy client side javascript checker
Due to a missing encodeURIComponent, the post arguments of the
http query on /checkppolicy could be interpreted as several argument if
caracters like & or = were present.
This commit also adds error handling on http return codes in the
checker.
2016-07-29 23:00:16 +02:00
kakwa a84ee528aa 403 flag for _check_auth + fix default 404 page
_check_auth can now return a 403 error instead of redirected to
the login page if needed (in case of the checkppolicy for example).
The default page handles post data and querystring better now.
2016-07-29 22:56:56 +02:00
pcarpent 1aa4a0bd64 adding change logs for next version 2016-07-29 11:17:44 +02:00
pcarpent 37925b196b admin search if admin in navbar
Now LdapCherry Administrator are displayed the searchadmin page
(the one displaying the modify and remove buttons) when they use
the navbar search.
2016-07-29 11:12:30 +02:00
kakwa f863b230dd fix pep8 error 2016-07-29 07:41:42 +02:00
kakwa dcdc260f33 disable unit tests for AD on travis
Setting up a test env with samba 4 as AD doesn't seem possible on
travis. there is no other choice than disabling the AD unit tests
if we are on travis
2016-07-29 07:37:25 +02:00
kakwa 2e98e380df adding slow_disabled decorator on html check unit tests 2016-07-29 07:36:32 +02:00
kakwa 52557afa6a adding a decorator to disable slow tests
if env var LCNOSLOW=yes, the slowest tests are disable
2016-07-29 07:34:58 +02:00
kakwa f967630043 adding unitests for backend AD 2016-07-28 21:07:40 +02:00
kakwa 9fb32f11be fix many encoding errors 2016-07-28 21:07:10 +02:00
kakwa 64e0bba74c trying again... 2016-07-28 19:53:57 +02:00
kakwa ff950dd88b more unicode testing 2016-07-28 19:53:32 +02:00
kakwa 9367bc3288 fix unit tests 2016-07-28 07:32:12 +02:00
kakwa d1ec945fe2 adding possible missing winbind 2016-07-28 06:44:02 +02:00
kakwa 0f28309344 trying something else... 2016-07-28 06:34:48 +02:00
kakwa 0263d52edf killing dnsmasq 2016-07-27 21:42:15 +02:00
kakwa 127b106082 trying again to have a test env... 2016-07-27 21:13:56 +02:00
kakwa 6fd849fa50 trying again to have a test env... 2016-07-27 21:13:18 +02:00
kakwa ca974ab801 trying to stop dnsmasq (to use samba internal) 2016-07-27 21:10:02 +02:00
kakwa 9b3d232503 trying to set hostname to kerberos realm 2016-07-27 21:06:31 +02:00
kakwa 1ccb4bf732 test killing samba processes before ad-dc start 2016-07-22 21:38:40 +02:00
kakwa d484ee1ed0 more debugging 2016-07-22 21:31:58 +02:00
kakwa bb05934284 more debuging for ad-dc setup 2016-07-22 21:19:38 +02:00
kakwa cfe31ae62b version bump 2016-07-21 07:45:40 +02:00
kakwa 320f57ab76 fix self user password modification in AD Backend
In some forms, the 'cn' attribute might not be accessible.
The _set_password method relied on 'cn' to build the user dn.
Now it accepts the cn or the dn (by_cn switch).
2016-07-21 07:33:14 +02:00
kakwa 6ef44b9b2e try to enable samba ad in test env 2016-07-10 09:53:09 +02:00
kakwa d9973ddf36 version bump 2016-07-10 09:00:41 +02:00
kakwa 408f75c449 add ldapcherry icon in navbar linked to / 2016-07-10 08:58:16 +02:00
kakwa abf3d5dea9 focus on first field for all forms
previously, only the add form had focus on first field. Now every form
(add, modifify, selfmodify) have focus on first field.
2016-07-10 08:54:59 +02:00
kakwa b6fd601801 clean changelog 2016-07-10 08:44:01 +02:00
kakwa 50f573b9bd version bump 2016-07-10 08:42:38 +02:00
kakwa 14afde33b5 fix pep 8 2016-07-10 08:29:40 +02:00
kakwa f3fabe502e disable min search lenght for admin search 2016-07-10 08:10:19 +02:00
kakwa c2f6b95fb0 fix html escape for lonely groups 2016-07-10 07:40:56 +02:00
kakwa 0beac119f9 fix many error with html unescaped attributes 2016-07-08 21:46:00 +02:00
kakwa 91a1f3e7e3 debuging travis test env 2016-07-08 07:19:34 +02:00
kakwa 59b9f4d3b7 version bump 2016-07-08 07:07:19 +02:00
kakwa 6c3fb4975d fix many encoding errors on login and password 2016-07-07 20:22:33 +02:00
kakwa 9600f47e13 ignore .eggs directory 2016-07-07 20:21:10 +02:00
kakwa 2f90d0a2fa version bump 2016-07-07 08:19:10 +02:00
kakwa b34ac7be0a changelog 2016-07-07 08:18:35 +02:00
kakwa 685031ef15 bolding the user's attributes 2016-07-07 08:15:50 +02:00
kakwa 80fa310f37 change the display form of the user's attributes 2016-07-07 08:11:50 +02:00
kakwa ebc5b69374 implement the display of users attributes on index page 2016-07-07 08:01:57 +02:00
kakwa c64bb11504 add a default handler for 404 requests 2016-07-06 22:50:59 +02:00
kakwa 1b7473d01c version bump 2016-07-06 22:01:14 +02:00
kakwa ec05d96b31 changelog for -D switch 2016-07-06 21:59:38 +02:00
kakwa bdc86a6d8f adding documention for -D switch 2016-07-06 21:58:58 +02:00
kakwa 8b0e68d9db implementing debug mode in console 2016-07-06 21:54:08 +02:00
kakwa 5944b81aed changelog 2016-07-06 21:24:43 +02:00
kakwa 655ccabd79 force focus on the first input of forms 2016-07-06 21:22:48 +02:00
kakwa ce09b60158 prepare for new version 2016-07-06 20:53:36 +02:00
kakwa baee15c40f properly implementing minimum lenght limit in search 2016-07-06 20:47:33 +02:00
kakwa 3b6cf61b93 grotesque hacks to disable firefox autofilling of the forms 2016-07-06 20:37:24 +02:00
Carpentier Pierre-Francois caef6a889e Fix encoding issues in AD backend 2016-07-06 14:00:36 +02:00
kakwa 9edc7e545a increment version + changelog + copyright to 2016 2016-07-05 20:33:06 +02:00
kakwa 28479f7202 Merge branch 'master' of https://github.com/kakwa/ldapcherry 2016-07-05 20:28:04 +02:00
kakwa 9a5aa03de6 adding a little js for min lenght search
* impose a min lenght of 3 chars on searches, at least on the client
side...
2016-07-05 20:26:29 +02:00
kakwa 43e4231be8 using POST instead of default GET... oups... sorry 2016-07-05 20:02:58 +02:00
kakwa e45c0e862e reindent javascript 2016-07-05 19:57:35 +02:00
Carpentier Pierre-Francois 8c50313b5a Merge pull request #3 from kounoike/pr/maxuidnumber
maxuid is not 'max' in lcUidNumber
2016-06-19 11:25:59 +02:00
Yuusuke KOUNOIKE 268c8f935b maxuid is not 'max'. 2016-06-19 01:48:50 +09:00
kakwa c67969e2c1 better README 2016-06-17 20:34:54 +02:00
kakwa 1fea551853 adding precision in documentation on mandatory attributes to declare for AD 2016-06-17 20:28:29 +02:00
kakwa 44ebcc8f3a prepare for new release 2016-06-17 08:18:57 +02:00
kakwa cf91e69eba fix restart of samba in test env 2016-06-17 08:09:44 +02:00
kakwa 5f1074bb26 fixing samba DC deployment for newer version of samba 2016-06-17 08:03:10 +02:00
kakwa 90d92009e3 more cleanup in test_env deploy.sh 2016-06-17 07:58:53 +02:00
kakwa 569aaac5a6 fix unittest because of new mandatory param for Active Directory 2016-06-17 07:52:49 +02:00
kakwa ffac99994c Merge branch 'master' of https://github.com/kakwa/ldapcherry 2016-06-17 07:47:07 +02:00
kakwa ab81f4258e fix test configurations to take into account mandatory params for Active Directory 2016-06-17 07:46:19 +02:00
kakwa f7bbff4cec trying to cleanup travis build env 2016-06-17 00:15:14 +02:00
kakwa b8a65a44b6 pepify the source code 2016-06-17 00:10:02 +02:00
kakwa d8631da7ba making some attributes explicitly mandatory for Active Directory backend 2016-06-16 22:32:44 +02:00
kakwa 9a882d3626 fixing default configuration 2016-06-16 22:12:19 +02:00
kakwa 921eef4b04 implementing default value in form 2016-06-16 22:11:48 +02:00
kakwa c969e730c4 fix password setting with Active Directory 2016-06-16 21:49:48 +02:00
kakwa c320fa9da6 fix default configuration 2016-06-16 09:23:56 +02:00
Carpentier Pierre-Francois 88002cfa2c Update screenshots.rst 2016-02-15 20:42:41 +01:00
kakwa 3a6bc3a55e typo in doc 2015-11-12 09:40:00 +01:00
kakwa e981451431 fix notification adding error
The test on whether the user notification queue existed or not was just wrong.
2015-11-08 20:15:48 +01:00
kakwa af01f9bae3 Merge branch 'master' of https://github.com/kakwa/ldapcherry 2015-11-07 10:05:32 +01:00
kakwa fbd602fc13 adding a page referencing external plugins 2015-11-07 10:04:52 +01:00
kakwa 621445f1b6 adding configuration of demo backend in default config file 2015-11-05 01:22:21 +01:00
kakwa d34b1b6188 Merge branch 'master' of https://github.com/kakwa/ldapcherry 2015-11-05 00:53:39 +01:00
kakwa 639554d539 crap 2015-11-05 00:53:22 +01:00
kakwa 00db6ae4bc Merge branch 'master' of https://github.com/kakwa/ldapcherry 2015-11-05 00:39:50 +01:00
kakwa 13f9f73b33 version bump 2015-11-03 22:27:08 +01:00
kakwa 4f80ef6d7c adding manifest 2015-11-03 22:24:27 +01:00
Carpentier Pierre-Francois 5caedc91c3 pep8 compliance 2015-11-03 09:14:03 +01:00
kakwa 3872c49630 adding changelog 2015-11-03 08:55:54 +01:00
kakwa c71645ac99 better log on user deletion (adding deteled **by user**) 2015-11-03 08:54:17 +01:00
kakwa 4e5c361e5e fix error messages/logs in ldap backend 2015-11-03 08:53:46 +01:00
kakwa 97e0ae8cc3 fix icons 2015-11-03 01:59:12 +01:00
kakwa a4fbbfc73a adding rtfd badge 2015-11-03 01:56:21 +01:00
Carpentier Pierre-Francois e490f3f4e3 Update README.rst 2015-11-03 01:52:06 +01:00
kakwa 71c6f78f74 adding log in README.rst 2015-11-03 01:50:07 +01:00
kakwa 3fc9e5e68b better try out 2015-11-03 01:47:47 +01:00
kakwa 97769f62ff increment version 2015-11-03 01:44:50 +01:00
kakwa 576bc97d59 better README.rst (try out doc) 2015-11-03 01:44:22 +01:00
kakwa 6501096643 trying to fix doc build... again 2015-11-03 01:35:36 +01:00
kakwa baeaf565cf adding mock for readthedoc 2015-11-03 01:28:38 +01:00
kakwa eaa05e0877 adding a special requirements for readthedocs 2015-11-03 01:15:56 +01:00
kakwa dcf01e929c trying to fix conf error 2015-11-03 01:07:54 +01:00
kakwa f23a77e6f2 increment version 2015-11-03 00:59:21 +01:00
kakwa a085868b2b adding entry in changelogs 2015-11-03 00:58:12 +01:00
kakwa a74346f7a7 very small improvements on ppolicy.simple 2015-11-02 23:47:59 +01:00
kakwa 55cd8529c3 adding special treatment if field is not required for ppolicy 2015-11-02 23:43:51 +01:00
kakwa 7a1fb0dc8c modifying the templates to include custom ppolicy error message 2015-11-02 23:25:01 +01:00
kakwa 27e05ac7f2 adding piece of javascript to display custom ppolicy messages 2015-11-02 23:24:35 +01:00
kakwa 451c59e875 it's more logical to do the auth check before anything... 2015-11-02 23:17:49 +01:00
kakwa fdba64f9da changing http returns for checkppolicy 2015-11-02 23:16:26 +01:00
kakwa 8833fe6df6 update validator.js 2015-11-02 23:12:12 +01:00
kakwa 93cd8a40f6 adding notification for user deletion 2015-10-29 07:36:14 +01:00
kakwa 7d18934a85 adding tags to changelog entries 2015-10-28 23:21:44 +01:00
kakwa 396f5c2b65 fill changelog 2015-10-28 23:18:23 +01:00
kakwa 2451b2efdd adding notification after an action is performed 2015-10-28 23:11:23 +01:00
kakwa bb8aa3f43c fix logic inversion in travis.yml 2015-10-20 23:01:20 +02:00
kakwa f52e9df902 traying to fix travis.yml... again 2015-10-20 22:58:07 +02:00
kakwa 869ddd6549 fix travis.yml 2015-10-20 22:54:32 +02:00
kakwa 14df477366 trying to remove preparation for pep8 check 2015-10-20 22:49:06 +02:00
kakwa 9c1dc8112a add unit test for login failure 2015-10-20 22:42:29 +02:00
kakwa c397afab4a fix service unavailable template 2015-10-20 22:42:10 +02:00
kakwa 1d97e01540 adding unit tests for demo backend 2015-10-20 22:17:23 +02:00
kakwa 1985408324 better exceptions handling for demo backend 2015-10-20 22:17:00 +02:00
kakwa 27089f68ef fix pep8 2015-10-20 20:26:41 +02:00
kakwa 807ac93956 better unit tests on authentication 2015-10-20 20:22:38 +02:00
kakwa 9ecd97a8d0 cleaner way to merge user's attributes from different backends 2015-10-20 20:05:22 +02:00
kakwa 1fd76a9485 fix decorator handling the exceptions 2015-10-20 20:04:52 +02:00
kakwa 2992cac1d7 slightly cleaner way to handle templates loading 2015-10-19 20:36:37 +02:00
kakwa 3402ba9613 pep8 compliance 2015-10-19 20:36:18 +02:00
kakwa 53676db341 small code reorganization
* extraction of exception decorator
* extraction of log handling
2015-10-19 20:14:49 +02:00
Carpentier Pierre-Francois d32b0625ed Update README.rst 2015-08-10 09:49:04 +02:00
92 changed files with 3774 additions and 1100 deletions

19
.github/workflows/tests.yml vendored Normal file
View File

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

2
.gitignore vendored
View File

@ -22,6 +22,7 @@ var/
.installed.cfg
*.egg
.*.swp
.eggs/
# Installer logs
pip-log.txt
@ -53,3 +54,4 @@ coverage.xml
# Sphinx documentation
docs/_build/
ldapcherry-dev.ini

View File

@ -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:

View File

@ -1,20 +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
*************
* add demo backend
* add custom javascript hook
* add documentation for backends
* add the Active Directory backend
* add display name parameter for backends
* fix many encoding error in LDAP backend
* fix dn renaming of an entry in LDAP backend
* turn-off configuration monitoring
* better exception handling and debugging logs
* [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
*************
* first release
* [misc] first release

7
MANIFEST.in Normal file
View File

@ -0,0 +1,7 @@
include *.rst
graft conf
graft docs
graft goodies
graft resources
graft tests
include LICENSE

View File

@ -2,29 +2,29 @@
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.
.. image:: https://travis-ci.org/kakwa/ldapcherry.svg?branch=master
:target: https://travis-ci.org/kakwa/ldapcherry
.. image:: https://coveralls.io/repos/kakwa/ldapcherry/badge.svg
:target: https://coveralls.io/r/kakwa/ldapcherry
.. image:: https://img.shields.io/pypi/dm/ldapcherry.svg
:target: https://pypi.python.org/pypi/ldapcherry
:alt: Number of PyPI downloads
.. image:: https://github.com/kakwa/ldapcherry/actions/workflows/tests.yml/badge.svg
:target: https://github.com/kakwa/ldapcherry/actions/workflows/tests.yml
:alt: CI
.. image:: https://img.shields.io/pypi/v/ldapcherry.svg
:target: https://pypi.python.org/pypi/ldapcherry
: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>`_
: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 © 2015
:Author: Pierre-Francois Carpentier - copyright © 2016
----
@ -34,7 +34,7 @@ Nice and simple application to manage users and groups in multiple directory ser
LdapCherry is a CherryPY application to manage users and groups in multiple directory services.
It's main features are:
Its main features are:
* manage multiple directories/databases backends in an unified way
* roles management (as in "groups of groups")
@ -50,12 +50,40 @@ 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.
The default backend plugins permit to manage Ldap and Active Directory.
***************
Screenshots
***************
`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
***********
License
***********
@ -66,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

View File

@ -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,6 +56,8 @@ uid:
args:
- $first-name
- $name
- '10000'
- '40000'
backends:
ldap: uid
# ad: sAMAccountName
@ -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,7 +120,7 @@ password:
type: password
backends:
ldap: userPassword
# ad: PASSWORD
# ad: unicodePwd
#logscript:
# description: "Windows login script"

View File

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

View File

@ -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

View File

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

View File

@ -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'2015, 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'2015, Pierre-Francois Carpentier'
#
# The short X.Y version.
# The full version, including alpha/beta/rc tags.
release = '0.1.0'
release = version
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.

View File

@ -21,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
@ -59,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>
@ -73,6 +76,18 @@ The mandatory parameters for an attribute, and their format are the following:
<backend id> (the backend id) must be defined in main ini configuration file.
LdapCherry won't start if it's not.
Type listing
^^^^^^^^^^^^
The following **type** are supported:
* **int**: an integer (ex: uid)
* **string**: a string (ex: first name)
* **stringlist**: a string to choose from a given list of strings (ex: one of /bin/sh, /bin/bash /bin/zsh for a shell)
* **textfield**: free multiline text (ex: an SSH key)
* **email**: an email address
* **fix**: a fix value, only present shown information purposes
Type stringlist values
^^^^^^^^^^^^^^^^^^^^^^
@ -97,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.
@ -430,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' |
+--------------------+---------+---------------------------------+-------------------------------------------------+----------------------------------------+
@ -462,6 +482,14 @@ Example:
log.level = 'info'
.. warning::
'debug' should not be used in production.
It tends to log a lot.
More significantly can represent a security issue,
as things like passwords will be logged 'clear text'.
Custom javascript
~~~~~~~~~~~~~~~~~

View File

@ -0,0 +1,4 @@
CherryPy>=3.0.0
PyYAML
Mako
mock

14
docs/external_plugins.rst Normal file
View File

@ -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
---------------

View File

@ -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

View File

@ -9,6 +9,7 @@
deploy
backends
full_configuration
external_plugins
backend_api
ppolicy_api
changelog

View File

@ -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.

View File

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

View File

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

View File

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

18
goodies/gen-dev-conf.sh Executable file
View File

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

View File

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

8
goodies/tag.sh Executable file
View File

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

View File

@ -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,90 +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 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 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.render()
is_admin = self._check_admin()
et = type(e)
if et is UserDoesntExist:
user = e.user
return self.temp_error.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.render(
is_admin=is_admin,
alert='warning',
message="User '" + user + "' already exist"
)
elif et is GroupDoesntExist:
group = e.group
return self.temp_error.render(
is_admin=is_admin,
alert='danger',
message="Missing group, please check logs for details"
)
else:
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):
@ -122,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'
@ -190,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
@ -214,7 +142,7 @@ class LdapCherry(object):
try:
self.backends_display_names[backend] = \
self.backends_params[backend]['display_name']
except:
except Exception as e:
self.backends_display_names[backend] = backend
self.backends_params[backend]['display_name'] = backend
params = self.backends_params[backend]
@ -224,7 +152,7 @@ class LdapCherry(object):
except Exception as e:
raise MissingParameter('backends', backend + '.module')
try:
bc = __import__(module, globals(), locals(), ['Backend'], -1)
bc = __import__(module, globals(), locals(), ['Backend'], 0)
except Exception as e:
self._handle_exception(e)
raise BackendModuleLoadingFail(module)
@ -233,7 +161,7 @@ class LdapCherry(object):
key = self.attributes.get_backend_key(backend)
self.backends[backend] = bc.Backend(
params,
cherrypy.log,
cherrypy.log.error,
backend,
attrslist,
key,
@ -265,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']
@ -284,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(
@ -325,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
@ -338,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(
@ -370,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
@ -383,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
@ -453,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
"""
@ -488,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',
@ -499,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)
@ -516,6 +428,8 @@ class LdapCherry(object):
severity=logging.DEBUG
)
self.notifications = {}
self.attributes = Attributes(self.attributes_file)
cherrypy.log.error(
@ -544,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
@ -557,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):
@ -578,11 +520,7 @@ class LdapCherry(object):
except UserDoesntExist as e:
self._handle_exception(e)
tmp = {}
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._merge_user_attrs(tmp, ret, b)
cherrypy.log.error(
msg="user '" + username + "' attributes " + str(ret),
@ -627,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
@ -644,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:
@ -667,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(
@ -698,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 + "'",
@ -721,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 " +
@ -756,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):
@ -765,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(),
@ -795,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 + "'",
@ -843,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 + "'",
@ -860,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: " +
@ -880,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
)
@ -900,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
@ -930,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'" % {
@ -943,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
@ -970,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
@ -978,34 +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,
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
@ -1019,11 +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,
custom_js=self.custom_js,
notifications=self._empty_notification(),
)
@cherrypy.expose
@ -1034,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:
@ -1052,26 +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,
custom_js=self.custom_js,
)
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
@ -1081,9 +1080,10 @@ class LdapCherry(object):
is_admin = self._check_admin()
try:
referer = cherrypy.request.headers['Referer']
except:
except Exception as e:
referer = '/'
self._deleteuser(user)
self._add_notification('User Deleted')
raise cherrypy.HTTPRedirect(referer)
@cherrypy.expose
@ -1094,18 +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)
self._add_notification("User modified")
try:
referer = cherrypy.request.headers['Referer']
except:
except Exception as e:
referer = '/'
raise cherrypy.HTTPRedirect(referer)
else:
notification = ''
graph = {}
for r in self.roles.graph:
@ -1116,41 +1112,71 @@ class LdapCherry(object):
display_names = {}
for r in self.roles.flatten:
display_names[r] = self.roles.flatten[r]['display_name']
if user is None:
cherrypy.response.status = 400
return self.temp['error.tmpl'].render(
is_admin=is_admin,
alert='warning',
message="No user requested"
)
user_attrs = self._get_user(user)
if user_attrs == {}:
cherrypy.response.status = 400
return self.temp_error.render(
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,
backends_display_names=self.backends_display_names,
custom_js=self.custom_js,
notifications=self._empty_notification(),
)
@cherrypy.expose
@ -1160,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."
@ -1170,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()
)

View File

@ -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:

View File

@ -15,6 +15,7 @@ import ldapcherry.backend
from ldapcherry.exceptions import UserDoesntExist, GroupDoesntExist
import os
import re
import sys
class CaFileDontExist(Exception):
@ -23,12 +24,42 @@ class CaFileDontExist(Exception):
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 | \
@ -99,22 +130,38 @@ class Backend(ldapcherry.backend.backendLdap.Backend):
self.dn_user_attr = 'cn'
self.key = 'sAMAccountName'
self.objectclasses = [
'top',
'person',
'organizationalPerson',
'user',
'posixAccount',
self._byte_p23('top'),
self._byte_p23('person'),
self._byte_p23('organizationalPerson'),
self._byte_p23('user'),
self._byte_p23('posixAccount'),
]
self.group_attrs = {
'member': "%(dn)s"
}
self.attrlist = []
self.group_attrs_keys = []
for a in attrslist:
self.attrlist.append(self._str(a))
self.attrlist.append(self._byte_p2(a))
if 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._str(searchfilter)
searchfilter = self._byte_p2(searchfilter)
ldap_client = self._bind()
try:
r = ldap_client.search_s(
@ -139,6 +186,48 @@ class Backend(ldapcherry.backend.backendLdap.Backend):
ad_groups.append('cn=' + group + ',' + self.groupdn)
return ad_groups
def _set_password(self, name, password, by_cn=True):
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)
@ -149,7 +238,7 @@ class Backend(ldapcherry.backend.backendLdap.Backend):
def get_groups(self, username):
username = ldap.filter.escape_filter_chars(username)
userdn = self._get_user(username, NO_ATTR)
userdn = self._get_user(self._byte_p2(username), NO_ATTR)
searchfilter = self.group_filter_tmpl % {
'userdn': userdn,
@ -169,7 +258,7 @@ class Backend(ldapcherry.backend.backendLdap.Backend):
)
for entry in groups:
ret.append(entry[1]['cn'][0])
ret.append(self._uni(entry[1]['cn'][0]))
return ret
def auth(self, username, password):
@ -178,7 +267,10 @@ class Backend(ldapcherry.backend.backendLdap.Backend):
if binddn is not None:
ldap_client = self._connect()
try:
ldap_client.simple_bind_s(binddn, password)
ldap_client.simple_bind_s(
self._byte_p2(binddn),
self._byte_p2(password)
)
except ldap.INVALID_CREDENTIALS:
ldap_client.unbind_s()
return False

View File

@ -7,10 +7,15 @@
# This is a demo backend
from ldapcherry.exceptions import MissingParameter
from sets import Set
import sys
import ldapcherry.backend
from ldapcherry.exceptions import UserDoesntExist, \
GroupDoesntExist, MissingParameter, \
UserAlreadyExists
import re
if sys.version < '3':
from sets import Set as set
class Backend(ldapcherry.backend.Backend):
@ -35,13 +40,17 @@ class Backend(ldapcherry.backend.Backend):
self.backend_name = name
admin_user = self.get_param('admin.user', 'admin')
admin_password = self.get_param('admin.password', 'admin')
admin_groups = Set(re.split('\W+', self.get_param('admin.groups')))
admin_groups = set(
self._basic_splitter(self.get_param('admin.groups'))
)
basic_user = self.get_param('basic.user', 'user')
basic_password = self.get_param('basic.password', 'user')
basic_groups = Set(re.split('\W+', self.get_param('basic.groups')))
basic_groups = set(
self._basic_splitter(self.get_param('basic.groups'))
)
pwd_attr = self.get_param('pwd_attr')
self.search_attrs = Set(
re.split('\W+', self.get_param('search_attributes')),
self.search_attrs = set(
re.split(r'\W+', self.get_param('search_attributes')),
)
self.pwd_attr = pwd_attr
self.admin_user = admin_user
@ -58,6 +67,11 @@ class Backend(ldapcherry.backend.Backend):
'groups': basic_groups,
}
@staticmethod
def _basic_splitter(in_str):
return [re.sub(r'(?<!\\)\\', '', x)
for x in re.split(r'(?<!\\),\W*', in_str)]
def _check_fix_users(self, username):
if self.admin_user == username or self.basic_user == username:
raise Exception('User cannot be modified')
@ -89,7 +103,7 @@ class Backend(ldapcherry.backend.Backend):
if username in self.users:
raise UserAlreadyExists(username, self.backend_name)
self.users[username] = attrs
self.users[username]['groups'] = Set([])
self.users[username]['groups'] = set([])
def del_user(self, username):
""" Delete a user from the backend
@ -99,10 +113,13 @@ class Backend(ldapcherry.backend.Backend):
"""
self._check_fix_users(username)
del self.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
""" set a list of attributes for a given user
:param username: 'key' attribute of the user
:type username: string
@ -123,7 +140,7 @@ class Backend(ldapcherry.backend.Backend):
"""
self._check_fix_users(username)
current_groups = self.users[username]['groups']
new_groups = current_groups | Set(groups)
new_groups = current_groups | set(groups)
self.users[username]['groups'] = new_groups
def del_from_groups(self, username, groups):
@ -138,7 +155,7 @@ class Backend(ldapcherry.backend.Backend):
"""
self._check_fix_users(username)
current_groups = self.users[username]['groups']
new_groups = current_groups - Set(groups)
new_groups = current_groups - set(groups)
self.users[username]['groups'] = new_groups
def search(self, searchstring):
@ -169,7 +186,10 @@ class Backend(ldapcherry.backend.Backend):
.. warning:: raise UserDoesntExist if user doesn't exist
"""
return self.users[username]
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
@ -178,4 +198,7 @@ class Backend(ldapcherry.backend.Backend):
:type username: string
:rtype: list of groups
"""
return self.users[username]['groups']
try:
return self.users[username]['groups']
except Exception as e:
raise UserDoesntExist(username, self.backend_name)

View File

@ -11,17 +11,37 @@ import ldap.modlist as modlist
import ldap.filter
import logging
import ldapcherry.backend
import sys
from ldapcherry.exceptions import UserDoesntExist, \
GroupDoesntExist, \
UserAlreadyExists
import os
import re
if sys.version < '3':
from sets import Set as set
PYTHON_LDAP_MAJOR_VERSION = ldap.__version__[0]
class CaFileDontExist(Exception):
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
@ -55,17 +75,21 @@ class Backend(ldapcherry.backend.Backend):
self.key = key
# objectclasses parameter is a coma separated list in configuration
# split it to get a real list, and convert it to bytes
for o in re.split('\W+', self.get_param('objectclasses')):
self.objectclasses.append(self._str(o))
for o in re.split(r'\W+', self.get_param('objectclasses')):
self.objectclasses.append(self._byte_p23(o))
self.group_attrs = {}
self.group_attrs_keys = set([])
for param in config:
name, sep, group = param.partition('.')
if name == 'group_attr':
self.group_attrs[group] = self.get_param(param)
self.group_attrs_keys |= set(
self._extract_format_keys(self.get_param(param))
)
self.attrlist = []
for a in attrslist:
self.attrlist.append(self._str(a))
self.attrlist.append(self._byte_p2(a))
# exception handler (mainly to log something meaningful)
def _exception_handler(self, e):
@ -108,8 +132,8 @@ class Backend(ldapcherry.backend.Backend):
".groupdn'",
)
elif et is ldap.OBJECT_CLASS_VIOLATION:
info = e[0]['info']
desc = e[0]['desc']
info = e.args[0]['info']
desc = e.args[0]['desc']
self._logger(
severity=logging.ERROR,
msg="Configuration error, " + desc + ", " + info,
@ -123,7 +147,7 @@ class Backend(ldapcherry.backend.Backend):
self.backend_name,
)
elif et is ldap.ALREADY_EXISTS:
desc = e[0]['desc']
desc = e.args[0]['desc']
self._logger(
severity=logging.ERROR,
msg="adding user failed, " + desc,
@ -135,6 +159,38 @@ class Backend(ldapcherry.backend.Backend):
)
raise
def _extract_format_keys(self, fmt_string):
"""Extract the keys of a format string
(the 'stuff' in '%(stuff)s'
"""
class AccessSaver:
def __init__(self):
self.keys = []
def __getitem__(self, key):
self.keys.append(key)
a = AccessSaver()
fmt_string % a
return a.keys
def _normalize_group_attrs(self, attrs):
"""Normalize the attributes used to set groups
If it's a list of one element, it just become this
element.
It raises an error if the attribute doesn't exist
or if it's multivaluated.
"""
for key in self.group_attrs_keys:
if key not in attrs:
raise MissingGroupAttr(key)
if type(attrs[key]) is list and len(attrs[key]) == 1:
attrs[key] = attrs[key][0]
if type(attrs[key]) is list and len(attrs[key]) != 1:
raise MultivaluedGroupAttr(key)
def _connect(self):
"""Initialize an ldap client"""
ldap_client = ldap.initialize(self.uri)
@ -205,6 +261,16 @@ class Backend(ldapcherry.backend.Backend):
else:
attrlist = None
self._logger(
severity=logging.DEBUG,
msg="%(backend)s: executing search "
"with filter '%(filter)s' in DN '%(dn)s'" % {
'backend': self.backend_name,
'dn': basedn,
'filter': self._uni(searchfilter)
}
)
# bind and search the ldap
ldap_client = self._bind()
try:
@ -244,10 +310,9 @@ class Backend(ldapcherry.backend.Backend):
username = ldap.filter.escape_filter_chars(username)
user_filter = self.user_filter_tmpl % {
'username': username
'username': self._uni(username)
}
user_filter = self._str(user_filter)
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
@ -264,27 +329,84 @@ class Backend(ldapcherry.backend.Backend):
# 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
def _str(self, s):
#
# 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):
"""bytes -> unicode conversion"""
if s is None:
return None
return s.decode('utf-8', 'ignore')
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._str(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
@ -293,24 +415,35 @@ 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 = {}
# encoding crap
for a in attrs:
attrs_str[self._str(a)] = self._str(attrs[a])
attrs_srt = self.attrs_pretreatment(attrs)
attrs_str['objectClass'] = self.objectclasses
attrs_srt[self._byte_p2('objectClass')] = self.objectclasses
# construct is DN
dn = \
self._str(self.dn_user_attr) +\
'=' +\
self._str(attrs[self.dn_user_attr]) +\
',' +\
self._str(self.userdn)
# gen the ldif fir add_s and add the user
ldif = modlist.addModlist(attrs_str)
self._byte_p2(self.dn_user_attr) + \
self._byte_p2('=') + \
self._byte_p2(ldap.dn.escape_dn_chars(
attrs[self.dn_user_attr]
)
) + \
self._byte_p2(',') + \
self._byte_p2(self.userdn)
# gen the ldif first add_s and add the user
ldif = modlist.addModlist(attrs_srt)
try:
ldap_client.add_s(dn, ldif)
except ldap.ALREADY_EXISTS as e:
@ -324,7 +457,7 @@ class Backend(ldapcherry.backend.Backend):
"""delete a user"""
ldap_client = self._bind()
# recover the user dn
dn = self._str(self._get_user(username, NO_ATTR))
dn = self._byte_p2(self._get_user(self._byte_p2(username), NO_ATTR))
# delete
if dn is not None:
ldap_client.delete_s(dn)
@ -334,15 +467,17 @@ class Backend(ldapcherry.backend.Backend):
ldap_client.unbind_s()
def set_attrs(self, username, attrs):
""" Set user attributes"""
""" set user attributes"""
ldap_client = self._bind()
tmp = self._get_user(username, ALL_ATTRS)
dn = self._str(tmp[0])
tmp = self._get_user(self._byte_p2(username), ALL_ATTRS)
if tmp is None:
raise UserDoesntExist(username, self.backend_name)
dn = self._byte_p2(tmp[0])
old_attrs = tmp[1]
for attr in attrs:
bcontent = self._str(attrs[attr])
battr = self._str(attr)
new = {battr: bcontent}
bcontent = self._byte_p2(attrs[attr])
battr = self._byte_p2(attr)
new = {battr: self._modlist(self._byte_p3(bcontent))}
# if attr is dn entry, use rename
if attr.lower() == self.dn_user_attr.lower():
ldap_client.rename_s(
@ -359,38 +494,42 @@ class Backend(ldapcherry.backend.Backend):
if type(old_attrs[attr]) is list:
tmp = []
for value in old_attrs[attr]:
tmp.append(self._str(value))
tmp.append(self._byte_p2(value))
bold_value = tmp
else:
bold_value = self._str(old_attrs[attr])
bold_value = self._modlist(
self._byte_p3(old_attrs[attr])
)
old = {battr: bold_value}
# attribute is not set, just add it
else:
old = {}
ldif = modlist.modifyModlist(old, new)
try:
ldap_client.modify_s(dn, ldif)
except Exception as e:
ldap_client.unbind_s()
self._exception_handler(e)
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()
# recover dn of the user and his attributes
tmp = self._get_user(username, ALL_ATTRS)
tmp = self._get_user(self._byte_p2(username), ALL_ATTRS)
dn = tmp[0]
attrs = tmp[1]
attrs['dn'] = dn
dn = self._str(tmp[0])
self._normalize_group_attrs(attrs)
dn = self._byte_p2(tmp[0])
# add user to all groups
for group in groups:
group = self._str(group)
group = self._byte_p2(group)
# iterate on group membership attributes
for attr in self.group_attrs:
# fill the content template
content = self._str(self.group_attrs[attr] % attrs)
content = self._byte_p2(self.group_attrs[attr] % attrs)
self._logger(
severity=logging.DEBUG,
msg="%(backend)s: adding user '%(user)s'"
@ -404,16 +543,19 @@ class Backend(ldapcherry.backend.Backend):
'backend': self.backend_name
}
)
ldif = modlist.modifyModlist({}, {attr: content})
ldif = modlist.modifyModlist(
{},
{attr: self._modlist(self._byte_p3(content))}
)
try:
ldap_client.modify_s(group, ldif)
# if already member, not a big deal, just log it and continue
except ldap.TYPE_OR_VALUE_EXISTS as e:
except (ldap.TYPE_OR_VALUE_EXISTS, ldap.ALREADY_EXISTS) as e:
self._logger(
severity=logging.INFO,
msg="%(backend)s: user '%(user)s'"
" already member of group '%(group)s'"
"(attribute '%(attr)s')" % {
" (attribute '%(attr)s')" % {
'user': username,
'group': self._uni(group),
'attr': attr,
@ -432,16 +574,19 @@ class Backend(ldapcherry.backend.Backend):
# it follows the same logic than add_to_groups
# but with MOD_DELETE
ldap_client = self._bind()
tmp = self._get_user(username, ALL_ATTRS)
tmp = self._get_user(self._byte_p2(username), ALL_ATTRS)
if tmp is None:
raise UserDoesntExist(username, self.backend_name)
dn = tmp[0]
attrs = tmp[1]
attrs['dn'] = dn
dn = self._str(tmp[0])
self._normalize_group_attrs(attrs)
dn = self._byte_p2(tmp[0])
for group in groups:
group = self._str(group)
group = self._byte_p2(group)
for attr in self.group_attrs:
content = self._str(self.group_attrs[attr] % attrs)
ldif = [(ldap.MOD_DELETE, attr, content)]
content = self._byte_p2(self.group_attrs[attr] % attrs)
ldif = [(ldap.MOD_DELETE, attr, self._byte_p3(content))]
try:
ldap_client.modify_s(group, ldif)
except ldap.NO_SUCH_ATTRIBUTE as e:
@ -464,7 +609,9 @@ class Backend(ldapcherry.backend.Backend):
def search(self, searchstring):
"""Search users"""
# escape special char to avoid injection
searchstring = ldap.filter.escape_filter_chars(searchstring)
searchstring = ldap.filter.escape_filter_chars(
self._byte_p2(searchstring)
)
# fill the search string template
searchfilter = self.search_filter_tmpl % {
'searchstring': searchstring
@ -489,7 +636,7 @@ class Backend(ldapcherry.backend.Backend):
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]
@ -503,7 +650,7 @@ class Backend(ldapcherry.backend.Backend):
def get_groups(self, username):
"""Get all groups of a user"""
username = ldap.filter.escape_filter_chars(username)
username = ldap.filter.escape_filter_chars(self._byte_p2(username))
userdn = self._get_user(username, NO_ATTR)
searchfilter = self.group_filter_tmpl % {
@ -514,5 +661,5 @@ class Backend(ldapcherry.backend.Backend):
groups = self._search(searchfilter, NO_ATTR, self.groupdn)
ret = []
for entry in groups:
ret.append(entry[0])
ret.append(self._uni(entry[0]))
return ret

View File

@ -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,7 +47,7 @@ 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
@ -95,14 +95,14 @@ def start(configfile=None, daemonize=False, environment=None,
# Always start the engine; this will start all other services
try:
engine.start()
except:
except Exception as e:
# Assume the error has been logged already via bus.log.
sys.exit(1)
else:
engine.block()
if __name__ == '__main__':
def main():
from optparse import OptionParser
p = OptionParser()
@ -123,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:
@ -139,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()

View File

@ -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

72
ldapcherry/lclogging.py Normal file
View File

@ -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

View File

@ -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

View File

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

8
ldapcherry/version.py Normal file
View File

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

View File

@ -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

View File

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

4
requirements-el7.txt Normal file
View File

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

4
requirements-stretch.txt Normal file
View File

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

View File

@ -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;
}

View File

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

View File

@ -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;
}

View File

@ -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:

View File

@ -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 () {

View File

@ -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>

View File

@ -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">

View File

@ -56,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>

View File

@ -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>

View File

@ -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>

View File

@ -4,13 +4,13 @@
<div class="row clearfix" style="margin-top:30px">
<div class="col-md-4 column"></div>
<div class="col-md-4 column well">
<%
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">

View File

@ -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>
@ -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">

View File

@ -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>

View File

@ -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];

View File

@ -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

View File

@ -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>

View File

@ -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">

View File

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

View File

@ -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.1.0',
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',

View File

@ -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

View File

@ -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"

View File

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

View File

@ -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"

View File

@ -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

484
tests/cfg/blns.json Normal file
View File

@ -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⃣ 🔟",
"🇺🇸🇷🇺🇸 🇦🇫🇦🇲🇸 ",
"🇺🇸🇷🇺🇸🇦🇫🇦🇲",
"🇺🇸🇷🇺🇸🇦",
"",
"١٢٣",
"ثم نفس سقطت وبالتحديد،, جزيرتي باستخدام أن دنو. إذ هنا؟ الستار وتنصيب كان. أهّل ايطاليا، بريطانيا-فرنسا قد أخذ. سليمان، إتفاقية بين ما, يذكر الحدود أي بعد, معاملة بولندا، الإطلاق عل إيو.",
"בְּרֵאשִׁית, בָּרָא אֱלֹהִים, אֵת הַשָּׁמַיִם, וְאֵת הָאָרֶץ",
"הָיְתָה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˙Ɩ$-",
" ",
"𝐓𝐡𝐞 𝐪𝐮𝐢𝐜𝐤 𝐛𝐫𝐨𝐰𝐧 𝐟𝐨𝐱 𝐣𝐮𝐦𝐩𝐬 𝐨𝐯𝐞𝐫 𝐭𝐡𝐞 𝐥𝐚𝐳𝐲 𝐝𝐨𝐠",
"𝕿𝖍𝖊 𝖖𝖚𝖎𝖈𝖐 𝖇𝖗𝖔𝖜𝖓 𝖋𝖔𝖝 𝖏𝖚𝖒𝖕𝖘 𝖔𝖛𝖊𝖗 𝖙𝖍𝖊 𝖑𝖆𝖟𝖞 𝖉𝖔𝖌",
"𝑻𝒉𝒆 𝒒𝒖𝒊𝒄𝒌 𝒃𝒓𝒐𝒘𝒏 𝒇𝒐𝒙 𝒋𝒖𝒎𝒑𝒔 𝒐𝒗𝒆𝒓 𝒕𝒉𝒆 𝒍𝒂𝒛𝒚 𝒅𝒐𝒈",
"𝓣𝓱𝓮 𝓺𝓾𝓲𝓬𝓴 𝓫𝓻𝓸𝔀𝓷 𝓯𝓸𝔁 𝓳𝓾𝓶𝓹𝓼 𝓸𝓿𝓮𝓻 𝓽𝓱𝓮 𝓵𝓪𝔃𝔂 𝓭𝓸𝓰",
"𝕋𝕙𝕖 𝕢𝕦𝕚𝕔𝕜 𝕓𝕣𝕠𝕨𝕟 𝕗𝕠𝕩 𝕛𝕦𝕞𝕡𝕤 𝕠𝕧𝕖𝕣 𝕥𝕙𝕖 𝕝𝕒𝕫𝕪 𝕕𝕠𝕘",
"𝚃𝚑𝚎 𝚚𝚞𝚒𝚌𝚔 𝚋𝚛𝚘𝚠𝚗 𝚏𝚘𝚡 𝚓𝚞𝚖𝚙𝚜 𝚘𝚟𝚎𝚛 𝚝𝚑𝚎 𝚕𝚊𝚣𝚢 𝚍𝚘𝚐",
"⒯⒣⒠ ⒬⒰⒤⒞⒦ ⒝⒭⒪⒲⒩ ⒡⒪⒳ ⒥⒰⒨⒫⒮ ⒪⒱⒠⒭ ⒯⒣⒠ ⒧⒜⒵⒴ ⒟⒪⒢",
"<script>alert(123)</script>",
"&lt;script&gt;alert(&#39;123&#39;);&lt;/script&gt;",
"<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 ",
"scriptalert(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&#1&#2&#3&#4&#5&#6&#7&#8&#11&#12script: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=&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;&#58;&#97;&#108;&#101;&#114;&#116;&#40;&#39;&#88;&#83;&#83;&#39;&#41;>",
"<IMG SRC=&#0000106&#0000097&#0000118&#0000097&#0000115&#0000099&#0000114&#0000105&#0000112&#0000116&#0000058&#0000097&#0000108&#0000101&#0000114&#0000116&#0000040&#0000039&#0000088&#0000083&#0000083&#0000039&#0000041>",
"<IMG SRC=&#x6A&#x61&#x76&#x61&#x73&#x63&#x72&#x69&#x70&#x74&#x3A&#x61&#x6C&#x65&#x72&#x74&#x28&#x27&#x58&#x53&#x53&#x27&#x29>",
"<IMG SRC=\"jav ascript:alert('XSS');\">",
"<IMG SRC=\"jav&#x09;ascript:alert('XSS');\">",
"<IMG SRC=\"jav&#x0A;ascript:alert('XSS');\">",
"<IMG SRC=\"jav&#x0D;ascript:alert('XSS');\">",
"perl -e 'print \"<IMG SRC=java\\0script:alert(\\\"XSS\\\")>\";' > out",
"<IMG SRC=\" &#14; 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 ॣ ॣ冗"
]

View File

@ -0,0 +1,186 @@
# global parameters
[global]
# listing interface
server.socket_host = '127.0.0.1'
# port
server.socket_port = 8080
# number of threads
server.thread_pool = 8
#don't show traceback on error
request.show_tracebacks = False
# log configuration
# /!\ you can't have multiple log handlers
#####################################
# configuration to log in files #
#####################################
## logger 'file' for access log
#log.access_handler = 'file'
## logger syslog for error and ldapcherry log
#log.error_handler = 'file'
## access log file
#log.access_file = '/tmp/ldapcherry_access.log'
## error and ldapcherry log file
#log.error_file = '/tmp/ldapcherry_error.log'
#####################################
# configuration to log in syslog #
#####################################
# logger syslog for access log
#log.access_handler = 'syslog'
## logger syslog for error and ldapcherry log
log.error_handler = 'syslog'
#####################################
# configuration to not log at all #
#####################################
# logger none for access log
log.access_handler = 'none'
# logger none for error and ldapcherry log
#log.error_handler = 'none'
# log level
log.level = 'info'
# session configuration
# activate session
tools.sessions.on = True
# session timeout
tools.sessions.timeout = 7200
# file session storage(to use if multiple processes,
# default is in RAM and per process)
#tools.sessions.storage_type = "file"
# session
#tools.sessions.storage_path = "/var/lib/ldapcherry/sessions"
[attributes]
# file discribing form content
attributes.file = './tests/cfg/attributes_adldap.yml'
[roles]
# file listing roles
roles.file = './tests/cfg/roles_adldap.yml'
[search]
# minimum lenght for search forms
min.lenght = 0
[backends]
#####################################
# configuration of ldap backend #
#####################################
# name of the module
ldap.module = 'ldapcherry.backend.backendLdap'
# display name of the ldap
ldap.display_name = 'My Ldap Directory'
# uri of the ldap directory
ldap.uri = 'ldap://ldap.ldapcherry.org:390'
# ca to use for ssl/tls connexion
#ldap.ca = '/etc/dnscherry/TEST-cacert.pem'
# use start tls
#ldap.starttls = 'off'
# check server certificate (for tls)
#ldap.checkcert = 'off'
# bind dn to the ldap
ldap.binddn = 'cn=dnscherry,dc=example,dc=org'
# password of the bind dn
ldap.password = 'password'
# timeout of ldap connexion (in second)
ldap.timeout = 1
# groups dn
ldap.groupdn = 'ou=group,dc=example,dc=org'
# users dn
ldap.userdn = 'ou=people,dc=example,dc=org'
# ldapsearch filter to get a user
ldap.user_filter_tmpl = '(uid=%(username)s)'
# ldapsearch filter to get groups of a user
ldap.group_filter_tmpl = '(member=uid=%(username)s,ou=People,dc=example,dc=org)'
# filter to search users
ldap.search_filter_tmpl = '(|(uid=%(searchstring)s*)(sn=%(searchstring)s*))'
# ldap group attributes and how to fill them
ldap.group_attr.member = "%(dn)s"
#ldap.group_attr.memberUid = "%(uid)s"
# object classes of a user entry
ldap.objectclasses = 'top, person, posixAccount, inetOrgPerson'
# dn entry attribute for an ldap user
ldap.dn_user_attr = 'uid'
#####################################
# configuration of ad backend #
#####################################
## Name of the backend
ad.module = 'ldapcherry.backend.backendAD'
# display name of the ldap
ad.display_name = 'My Active Directory'
# ad domain
ad.domain = 'DC.LDAPCHERRY.ORG'
# ad login
ad.login = 'administrator'
# ad password
ad.password = 'qwertyP455'
# ad uri
ad.uri = 'ldaps://ad.ldapcherry.org'
# ca to use for ssl/tls connexion
#ad.ca = '/etc/dnscherry/TEST-cacert.pem'
# use start tls
ad.starttls = 'off'
# check server certificate (for tls)
ad.checkcert = 'off'
[ppolicy]
# password policy module
ppolicy.module = 'ldapcherry.ppolicy.simple'
# parameters of the module
min_length = 8
min_upper = 1
min_digit = 1
# authentification parameters
[auth]
# Auth mode
# * and: user must authenticate on all backends
# * or: user must authenticate on one of the backend
# * none: disable authentification
# * custom: custom authentification module (need auth.module param)
auth.mode = 'or'
# custom auth module to load
#auth.module = 'ldapcherry.auth.modNone'
# resources parameters
[resources]
# templates directory
templates.dir = './resources/templates/'
[/static]
# enable serving static file through ldapcherry
# set to False if files served directly by an
# http server for better performance
tools.staticdir.on = True
# static resources directory (js, css, images...)
tools.staticdir.dir = './resources/static/'
## custom javascript files
#[/custom]
#
## enable serving static file through ldapcherry
## set to False if files served directly by an
## http server for better performance
#tools.staticdir.on = True
## path to directory containing js files
## use it to add custom auto-fill functions

View File

@ -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: {}

View File

@ -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

View File

@ -0,0 +1,41 @@
admin-lv3:
display_name: Administrators Level 3
description: Super administrators of the system
backends_groups:
# ldap:
# - cn=dns admins,ou=Group,dc=example,dc=org
# - cn=nagios admins,ou=Group,dc=example,dc=org
# - cn=puppet admins,ou=Group,dc=example,dc=org
# - cn=users,ou=Group,dc=example,dc=org
ad:
- Administrators
- Domain Controllers
- Group Policy Creator Owners
admin-lv2:
display_name: Administrators Level 2
description: Basic administrators of the system
LC_admins: True
backends_groups:
# ldap:
# - cn=nagios admins,ou=Group,dc=example,dc=org
# - cn=users,ou=Group,dc=example,dc=org
ad:
- Administrators
#developers:
# display_name: Developpers
# description: Developpers of the system
# backends_groups:
# ldap:
# - cn=developers,ou=Group,dc=example,dc=org
# - cn=users,ou=Group,dc=example,dc=org
#users:
# display_name: Simple Users
# description: Basic users of the system
# backends_groups:
## ldap:
## - cn=users,ou=Group,dc=example,dc=org
# ad:
# - Domain Users

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -17,12 +17,12 @@ admin-lv2:
- cn=nagios admins,ou=Group,dc=example,dc=org
- cn=users,ou=Group,dc=example,dc=org
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:

View File

@ -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

View File

@ -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()

View File

@ -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):

204
tests/test_BackendAD.py Normal file
View File

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

160
tests/test_BackendDemo.py Normal file
View File

@ -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")

View File

@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from __future__ import with_statement
@ -6,13 +6,14 @@ from __future__ import unicode_literals
import pytest
import sys
from sets import Set
from ldapcherry.backend.backendLdap import Backend, CaFileDontExist
from ldapcherry.exceptions import *
from disable import travis_disabled
import cherrypy
import logging
import ldap
if sys.version < '3':
from sets import Set as set
cfg = {
'module' : 'ldapcherry.backend.ldap',
@ -104,7 +105,8 @@ class TestError(object):
try:
ldapc.simple_bind_s(inv.binddn, inv.bindpassword)
except ldap.SERVER_DOWN as e:
assert e[0]['info'] == 'TLS: hostname does not match CN in peer certificate'
assert e.args[0]['info'] == 'TLS: hostname does not match CN in peer certificate' or \
e.args[0]['info'] == '(unknown error code)'
else:
raise AssertionError("expected an exception")
@ -120,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
@ -156,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')
@ -172,29 +162,29 @@ class TestError(object):
def testAddUser(self):
try:
inv.del_user(u'test☭')
inv.del_user(u'test☭,cn=')
except:
pass
inv = Backend(cfg, cherrypy.log, 'ldap', attr, 'uid')
user = {
'uid': u'test☭',
'sn': 'test',
'cn': 'test',
'userPassword': 'test',
'uid': u'test☭,cn=',
'sn': u'test',
'cn': u'test',
'userPassword': u'test',
'uidNumber': '42',
'gidNumber': '42',
'homeDirectory': '/home/test/'
}
inv.add_user(user)
inv.del_user(u'test☭')
inv.del_user(u'test☭,cn=')
def testModifyUser(self):
inv = Backend(cfg, cherrypy.log, 'ldap', attr, 'uid')
user = {
'uid': u'test☭',
'sn': 'test',
'cn': 'test',
'userPassword': 'test',
'sn': u'test',
'cn': u'test',
'userPassword': u'test',
'uidNumber': '42',
'gidNumber': '42',
'homeDirectory': '/home/test/'
@ -206,11 +196,11 @@ class TestError(object):
def testAddUserDuplicate(self):
inv = Backend(cfg, cherrypy.log, 'ldap', attr, 'uid')
user = {
'uid': 'test',
'sn': 'test',
'cn': 'test',
'uid': u'test',
'sn': u'test',
'cn': u'test',
'uidNumber': '42',
'userPassword': 'test',
'userPassword': u'test',
'gidNumber': '42',
'homeDirectory': '/home/test/'
}
@ -218,17 +208,17 @@ class TestError(object):
inv.add_user(user)
inv.add_user(user)
except UserAlreadyExists:
inv.del_user('test')
inv.del_user(u'test')
return
else:
inv.del_user('test')
inv.del_user(u'test')
raise AssertionError("expected an exception")
def testDelUserDontExists(self):
inv = Backend(cfg, cherrypy.log, 'ldap', attr, 'uid')
try:
inv.del_user('test')
inv.del_user('test')
inv.del_user(u'test')
inv.del_user(u'test')
except UserDoesntExist:
return
else:
@ -236,17 +226,17 @@ class TestError(object):
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/'
}
@ -255,5 +245,5 @@ class TestError(object):
except ldap.OBJECT_CLASS_VIOLATION:
return
else:
inv.del_user('test')
inv.del_user(u'test')
raise AssertionError("expected an exception")

View File

@ -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)
@ -145,11 +204,24 @@ class TestError(object):
def testLogin(self):
app = LdapCherry()
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")
@ -221,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)
@ -237,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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

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

View File

@ -0,0 +1,125 @@
# global parameters
[global]
# listing interface
server.socket_host = '127.0.0.1'
# port
server.socket_port = 8080
# number of threads
server.thread_pool = 8
#don't show traceback on error
request.show_tracebacks = False
# log configuration
# /!\ you can't have multiple log handlers
#####################################
# configuration to log in files #
#####################################
## logger 'file' for access log
#log.access_handler = 'file'
## logger syslog for error and ldapcherry log
#log.error_handler = 'file'
## access log file
#log.access_file = '/tmp/ldapcherry_access.log'
## error and ldapcherry log file
#log.error_file = '/tmp/ldapcherry_error.log'
#####################################
# configuration to log in syslog #
#####################################
# logger syslog for access log
#log.access_handler = 'syslog'
## logger syslog for error and ldapcherry log
log.error_handler = 'syslog'
#####################################
# configuration to not log at all #
#####################################
# logger none for access log
log.access_handler = 'syslog'
# logger none for error and ldapcherry log
#log.error_handler = 'none'
# log level
log.level = 'debug'
# session configuration
# activate session
tools.sessions.on = True
# session timeout
tools.sessions.timeout = 10
# file session storage(to use if multiple processes,
# default is in RAM and per process)
#tools.sessions.storage_type = "file"
# session
#tools.sessions.storage_path = "/var/lib/ldapcherry/sessions"
[attributes]
# file discribing form content
attributes.file = '/etc/ldapcherry/attributes.yml'
[roles]
# file listing roles
roles.file = '/etc/ldapcherry/roles.yml'
[backends]
ldap.module = 'ldapcherry.backend.backendLdap'
ldap.groupdn = 'ou=Group,dc=example,dc=org'
ldap.userdn = 'ou=people,dc=example,dc=org'
ldap.binddn = 'cn=dnscherry,dc=example,dc=org'
ldap.password = 'password'
ldap.uri = 'ldap://ldap.ldapcherry.org:390'
ldap.ca = '/etc/dnscherry/TEST-cacert.pem'
ldap.starttls = 'off'
ldap.checkcert = 'off'
ldap.user_filter_tmpl = '(uid=%(username)s)'
ldap.group_filter_tmpl = '(member=%(userdn)s)'
ldap.search_filter_tmpl = '(|(uid=%(searchstring)s*)(sn=%(searchstring)s*))'
ldap.group_attr.member = "%(dn)s"
#ldap.objectclasses = 'top, person, organizationalPerson, user'
ldap.objectclasses = 'top, person, posixAccount, inetOrgPerson'
ldap.dn_user_attr = 'uid'
ldap.timeout = 1
ad.module = 'ldapcherry.backend.backendAD'
ad.domain = 'dc.ldapcherry.org'
ad.login = 'administrator'
ad.password = 'qwertyP455'
ad.uri = 'ldaps://ldap.ldapcherry.org:636'
ad.checkcert = 'off'
# authentification parameters
[auth]
# Auth mode
# * and: user must authenticate on all backends
# * or: user must authenticate on one of the backend
# * none: disable authentification
# * custom: custom authentification module (need auth.module param)
auth.mode = 'none'
# custom auth module to load
#auth.module = 'ldapcherry.auth.modNone'
[ppolicy]
# password policy module
ppolicy.module = 'ldapcherry.ppolicy.simple'
# parameters of the module
min_length = 2
min_upper = 0
min_digit = 0
# resources parameters
[resources]
# templates directory
templates.dir = './resources/templates/'
[/static]
tools.staticdir.on = True
tools.staticdir.dir = './resources/static/'

View File

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