Merge branch 'release/0.6.0' into production

* release/0.6.0: (29 commits)
  Release preparation for 0.6.0
  Fix missing bind=True
  Implement automatic retry for LDAP errors
  Unify docker setup with gvaweb and gva
  Add Dockerfile and entrypoint script
  Reorganize package structure
  Fix compatibility with Django 2.2 and other dependencies
  Use Pipenv for dependency management
  Change gvacommon dependency URL
  Update vagrant setup to stretch64 with LXC
  Protect /etc/salt/grains
  Ignore PyCharm files
  Improve Vagrant setup
  Refactoring of ldaptasks
  Mark LDAP field names as byte strings
  Change salt minion id in Vagrantfile
  Add tests for delete tasks
  Add tests for ldaptasks functions
  Update Django and gvacommon dependencies
  Add unit tests
This commit is contained in:
Jan Dittberner 2020-03-03 15:08:00 +01:00
commit 99e22ab9dd
44 changed files with 1585 additions and 698 deletions

5
.gitignore vendored
View file

@ -40,3 +40,8 @@ Desktop.ini
.ropeproject
_build/
.vagrant/
.coverage
coverage-report/
.idea/
.env

52
Dockerfile Normal file
View file

@ -0,0 +1,52 @@
ARG DEBIAN_RELEASE=buster
FROM debian:$DEBIAN_RELEASE
LABEL maintainer="Jan Dittberner <jan@dittberner.info>"
ENV LC_ALL=C.UTF-8
ENV LANG=C.UTF-8
RUN apt-get update \
&& DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
build-essential \
dumb-init \
gettext \
git \
python3-dev \
python3-pip \
python3-setuptools \
python3-virtualenv \
python3-wheel \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*.*
RUN python3 -m pip install --prefix=/usr/local pipenv
RUN apt-get update \
&& DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
libldap2-dev \
libsasl2-dev \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*.*
ARG GVAGID=2000
ARG GVAUID=2000
ARG GVAAPP=gvaldap
WORKDIR /srv/$GVAAPP
COPY Pipfile Pipfile.lock /srv/$GVAAPP/
RUN addgroup --gid $GVAGID $GVAAPP ; \
adduser --home /home/$GVAAPP --shell /bin/bash --uid $GVAUID --gid $GVAGID --disabled-password --gecos "User for gnuviechadmin component $GVAAPP" $GVAAPP
USER $GVAAPP
RUN python3 -m virtualenv --python=python3 /home/$GVAAPP/$GVAAPP-venv ; \
/home/$GVAAPP/$GVAAPP-venv/bin/python3 -m pip install -U pip ; \
VIRTUAL_ENV=/home/$GVAAPP/$GVAAPP-venv pipenv install --deploy --ignore-pipfile --dev
VOLUME /srv/$GVAAPP
COPY gvaldap.sh /srv/
ENTRYPOINT ["dumb-init", "/srv/gvaldap.sh"]

View file

@ -1,4 +1,4 @@
Copyright (c) 2014, 2015 Jan Dittberner
Copyright (c) 2014-2020 Jan Dittberner
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation

33
Pipfile Normal file
View file

@ -0,0 +1,33 @@
[[source]]
name = "pypi"
url = "https://pypi.org/simple"
verify_ssl = true
[[source]]
url = "https://$PYPI_GNUVIECH_USERNAME:${PYPI_GNUVIECH_PASSWORD}@pypi.gnuviech-server.de/simple"
name = "gnuviech"
verify_ssl = true
[dev-packages]
Sphinx = "*"
coverage = "*"
django-debug-toolbar = "*"
releases = "==1.0.0"
volatildap = "*"
[packages]
gvacommon = {version = "*",index = "gnuviech"}
Django = "<3"
amqp = "*"
celery = "*"
django-braces = "*"
django-ldapdb = "*"
django-model-utils = "*"
kombu = "*"
passlib = "*"
python-ldap = "*"
pytz = "*"
redis = "*"
[requires]
python_version = "3.7"

462
Pipfile.lock generated Normal file
View file

@ -0,0 +1,462 @@
{
"_meta": {
"hash": {
"sha256": "447a8091237b0e5a54b560066ec2329cce2fc5d564bbc2b37e2f9ec0c1af8ea2"
},
"pipfile-spec": 6,
"requires": {
"python_version": "3.7"
},
"sources": [
{
"name": "pypi",
"url": "https://pypi.org/simple",
"verify_ssl": true
},
{
"name": "gnuviech",
"url": "https://$PYPI_GNUVIECH_USERNAME:${PYPI_GNUVIECH_PASSWORD}@pypi.gnuviech-server.de/simple",
"verify_ssl": true
}
]
},
"default": {
"amqp": {
"hashes": [
"sha256:6e649ca13a7df3faacdc8bbb280aa9a6602d22fd9d545336077e573a1f4ff3b8",
"sha256:77f1aef9410698d20eaeac5b73a87817365f457a507d82edf292e12cbb83b08d"
],
"index": "pypi",
"version": "==2.5.2"
},
"billiard": {
"hashes": [
"sha256:bff575450859a6e0fbc2f9877d9b715b0bbc07c3565bb7ed2280526a0cdf5ede",
"sha256:d91725ce6425f33a97dfa72fb6bfef0e47d4652acd98a032bd1a7fbf06d5fa6a"
],
"version": "==3.6.3.0"
},
"celery": {
"hashes": [
"sha256:3c5fcd6bfcf9a6323cb742cfc121d1790d50cfeddf300ba723cfa0b356413f07",
"sha256:a650525303ee866fb0c62c82f68681fcc2183eebbfafae552c27d30125fe518b"
],
"index": "pypi",
"version": "==4.4.1"
},
"django": {
"hashes": [
"sha256:1226168be1b1c7efd0e66ee79b0e0b58b2caa7ed87717909cd8a57bb13a7079a",
"sha256:9a4635813e2d498a3c01b10c701fe4a515d76dd290aaa792ccb65ca4ccb6b038"
],
"index": "pypi",
"version": "==2.2.10"
},
"django-braces": {
"hashes": [
"sha256:83705b78948de00804bfacf40c315d001bb39630f35bbdd8588211c2d5b4d43f",
"sha256:a6d9b34cf3e4949635e54884097c30410d7964fc7bec7231445ea7079b8c5722"
],
"index": "pypi",
"version": "==1.14.0"
},
"django-ldapdb": {
"hashes": [
"sha256:36990757f26c1bd7642bbb0ed88cc1a4d8fe945dfcae6094142b9889b976e3f8",
"sha256:6c2d3b645fab20f97f1d33d8924114b85f699fca1bdf2cdd251074fa0331d75e"
],
"index": "pypi",
"version": "==1.4.0"
},
"django-model-utils": {
"hashes": [
"sha256:9cf882e5b604421b62dbe57ad2b18464dc9c8f963fc3f9831badccae66c1139c",
"sha256:adf09e5be15122a7f4e372cb5a6dd512bbf8d78a23a90770ad0983ee9d909061"
],
"index": "pypi",
"version": "==4.0.0"
},
"gvacommon": {
"hashes": [
"sha256:adf1ebc824433196d112764c61d9ca869481d33f612818c2840069f57ab42c25"
],
"index": "gnuviech",
"version": "==0.5.0"
},
"importlib-metadata": {
"hashes": [
"sha256:06f5b3a99029c7134207dd882428a66992a9de2bef7c2b699b5641f9886c3302",
"sha256:b97607a1a18a5100839aec1dc26a1ea17ee0d93b20b0f008d80a5a050afb200b"
],
"markers": "python_version < '3.8'",
"version": "==1.5.0"
},
"kombu": {
"hashes": [
"sha256:2d1cda774126a044d91a7ff5fa6d09edf99f46924ab332a810760fe6740e9b76",
"sha256:598e7e749d6ab54f646b74b2d2df67755dee13894f73ab02a2a9feb8870c7cb2"
],
"index": "pypi",
"version": "==4.6.8"
},
"passlib": {
"hashes": [
"sha256:68c35c98a7968850e17f1b6892720764cc7eed0ef2b7cb3116a89a28e43fe177",
"sha256:8d666cef936198bc2ab47ee9b0410c94adf2ba798e5a84bf220be079ae7ab6a8"
],
"index": "pypi",
"version": "==1.7.2"
},
"pyasn1": {
"hashes": [
"sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d",
"sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba"
],
"version": "==0.4.8"
},
"pyasn1-modules": {
"hashes": [
"sha256:905f84c712230b2c592c19470d3ca8d552de726050d1d1716282a1f6146be65e",
"sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74"
],
"version": "==0.2.8"
},
"python-ldap": {
"hashes": [
"sha256:7d1c4b15375a533564aad3d3deade789221e450052b21ebb9720fb822eccdb8e"
],
"index": "pypi",
"version": "==3.2.0"
},
"pytz": {
"hashes": [
"sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d",
"sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be"
],
"index": "pypi",
"version": "==2019.3"
},
"redis": {
"hashes": [
"sha256:0dcfb335921b88a850d461dc255ff4708294943322bd55de6cfd68972490ca1f",
"sha256:b205cffd05ebfd0a468db74f0eedbff8df1a7bfc47521516ade4692991bb0833"
],
"index": "pypi",
"version": "==3.4.1"
},
"six": {
"hashes": [
"sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a",
"sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"
],
"version": "==1.14.0"
},
"sqlparse": {
"hashes": [
"sha256:022fb9c87b524d1f7862b3037e541f68597a730a8843245c349fc93e1643dc4e",
"sha256:e162203737712307dfe78860cc56c8da8a852ab2ee33750e33aeadf38d12c548"
],
"version": "==0.3.1"
},
"vine": {
"hashes": [
"sha256:133ee6d7a9016f177ddeaf191c1f58421a1dcc6ee9a42c58b34bed40e1d2cd87",
"sha256:ea4947cc56d1fd6f2095c8d543ee25dad966f78692528e68b4fada11ba3f98af"
],
"version": "==1.3.0"
},
"zipp": {
"hashes": [
"sha256:12248a63bbdf7548f89cb4c7cda4681e537031eda29c02ea29674bc6854460c2",
"sha256:7c0f8e91abc0dc07a5068f315c52cb30c66bfbc581e5b50704c8a2f6ebae794a"
],
"version": "==3.0.0"
}
},
"develop": {
"alabaster": {
"hashes": [
"sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359",
"sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"
],
"version": "==0.7.12"
},
"asgiref": {
"hashes": [
"sha256:7e06d934a7718bf3975acbf87780ba678957b87c7adc056f13b6215d610695a0",
"sha256:ea448f92fc35a0ef4b1508f53a04c4670255a3f33d22a81c8fc9c872036adbe5"
],
"version": "==3.2.3"
},
"babel": {
"hashes": [
"sha256:1aac2ae2d0d8ea368fa90906567f5c08463d98ade155c0c4bfedd6a0f7160e38",
"sha256:d670ea0b10f8b723672d3a6abeb87b565b244da220d76b4dba1b66269ec152d4"
],
"version": "==2.8.0"
},
"certifi": {
"hashes": [
"sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3",
"sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f"
],
"version": "==2019.11.28"
},
"chardet": {
"hashes": [
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
],
"version": "==3.0.4"
},
"coverage": {
"hashes": [
"sha256:15cf13a6896048d6d947bf7d222f36e4809ab926894beb748fc9caa14605d9c3",
"sha256:1daa3eceed220f9fdb80d5ff950dd95112cd27f70d004c7918ca6dfc6c47054c",
"sha256:1e44a022500d944d42f94df76727ba3fc0a5c0b672c358b61067abb88caee7a0",
"sha256:25dbf1110d70bab68a74b4b9d74f30e99b177cde3388e07cc7272f2168bd1477",
"sha256:3230d1003eec018ad4a472d254991e34241e0bbd513e97a29727c7c2f637bd2a",
"sha256:3dbb72eaeea5763676a1a1efd9b427a048c97c39ed92e13336e726117d0b72bf",
"sha256:5012d3b8d5a500834783689a5d2292fe06ec75dc86ee1ccdad04b6f5bf231691",
"sha256:51bc7710b13a2ae0c726f69756cf7ffd4362f4ac36546e243136187cfcc8aa73",
"sha256:527b4f316e6bf7755082a783726da20671a0cc388b786a64417780b90565b987",
"sha256:722e4557c8039aad9592c6a4213db75da08c2cd9945320220634f637251c3894",
"sha256:76e2057e8ffba5472fd28a3a010431fd9e928885ff480cb278877c6e9943cc2e",
"sha256:77afca04240c40450c331fa796b3eab6f1e15c5ecf8bf2b8bee9706cd5452fef",
"sha256:7afad9835e7a651d3551eab18cbc0fdb888f0a6136169fbef0662d9cdc9987cf",
"sha256:9bea19ac2f08672636350f203db89382121c9c2ade85d945953ef3c8cf9d2a68",
"sha256:a8b8ac7876bc3598e43e2603f772d2353d9931709345ad6c1149009fd1bc81b8",
"sha256:b0840b45187699affd4c6588286d429cd79a99d509fe3de0f209594669bb0954",
"sha256:b26aaf69713e5674efbde4d728fb7124e429c9466aeaf5f4a7e9e699b12c9fe2",
"sha256:b63dd43f455ba878e5e9f80ba4f748c0a2156dde6e0e6e690310e24d6e8caf40",
"sha256:be18f4ae5a9e46edae3f329de2191747966a34a3d93046dbdf897319923923bc",
"sha256:c312e57847db2526bc92b9bfa78266bfbaabac3fdcd751df4d062cd4c23e46dc",
"sha256:c60097190fe9dc2b329a0eb03393e2e0829156a589bd732e70794c0dd804258e",
"sha256:c62a2143e1313944bf4a5ab34fd3b4be15367a02e9478b0ce800cb510e3bbb9d",
"sha256:cc1109f54a14d940b8512ee9f1c3975c181bbb200306c6d8b87d93376538782f",
"sha256:cd60f507c125ac0ad83f05803063bed27e50fa903b9c2cfee3f8a6867ca600fc",
"sha256:d513cc3db248e566e07a0da99c230aca3556d9b09ed02f420664e2da97eac301",
"sha256:d649dc0bcace6fcdb446ae02b98798a856593b19b637c1b9af8edadf2b150bea",
"sha256:d7008a6796095a79544f4da1ee49418901961c97ca9e9d44904205ff7d6aa8cb",
"sha256:da93027835164b8223e8e5af2cf902a4c80ed93cb0909417234f4a9df3bcd9af",
"sha256:e69215621707119c6baf99bda014a45b999d37602cb7043d943c76a59b05bf52",
"sha256:ea9525e0fef2de9208250d6c5aeeee0138921057cd67fcef90fbed49c4d62d37",
"sha256:fca1669d464f0c9831fd10be2eef6b86f5ebd76c724d1e0706ebdff86bb4adf0"
],
"index": "pypi",
"version": "==5.0.3"
},
"django": {
"hashes": [
"sha256:1226168be1b1c7efd0e66ee79b0e0b58b2caa7ed87717909cd8a57bb13a7079a",
"sha256:9a4635813e2d498a3c01b10c701fe4a515d76dd290aaa792ccb65ca4ccb6b038"
],
"index": "pypi",
"version": "==2.2.10"
},
"django-debug-toolbar": {
"hashes": [
"sha256:eabbefe89881bbe4ca7c980ff102e3c35c8e8ad6eb725041f538988f2f39a943",
"sha256:ff94725e7aae74b133d0599b9bf89bd4eb8f5d2c964106e61d11750228c8774c"
],
"index": "pypi",
"version": "==2.2"
},
"docutils": {
"hashes": [
"sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af",
"sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc"
],
"version": "==0.16"
},
"idna": {
"hashes": [
"sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb",
"sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa"
],
"version": "==2.9"
},
"imagesize": {
"hashes": [
"sha256:6965f19a6a2039c7d48bca7dba2473069ff854c36ae6f19d2cde309d998228a1",
"sha256:b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1"
],
"version": "==1.2.0"
},
"jinja2": {
"hashes": [
"sha256:93187ffbc7808079673ef52771baa950426fd664d3aad1d0fa3e95644360e250",
"sha256:b0eaf100007721b5c16c1fc1eecb87409464edc10469ddc9a22a27a99123be49"
],
"version": "==2.11.1"
},
"markupsafe": {
"hashes": [
"sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473",
"sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161",
"sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235",
"sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5",
"sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42",
"sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff",
"sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b",
"sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1",
"sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e",
"sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183",
"sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66",
"sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b",
"sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1",
"sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15",
"sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1",
"sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e",
"sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b",
"sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905",
"sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735",
"sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d",
"sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e",
"sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d",
"sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c",
"sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21",
"sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2",
"sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5",
"sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b",
"sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6",
"sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f",
"sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f",
"sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2",
"sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7",
"sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"
],
"version": "==1.1.1"
},
"packaging": {
"hashes": [
"sha256:170748228214b70b672c581a3dd610ee51f733018650740e98c7df862a583f73",
"sha256:e665345f9eef0c621aa0bf2f8d78cf6d21904eef16a93f020240b704a57f1334"
],
"version": "==20.1"
},
"pygments": {
"hashes": [
"sha256:2a3fe295e54a20164a9df49c75fa58526d3be48e14aceba6d6b1e8ac0bfd6f1b",
"sha256:98c8aa5a9f778fcd1026a17361ddaf7330d1b7c62ae97c3bb0ae73e0b9b6b0fe"
],
"version": "==2.5.2"
},
"pyparsing": {
"hashes": [
"sha256:4c830582a84fb022400b85429791bc551f1f4871c33f23e44f353119e92f969f",
"sha256:c342dccb5250c08d45fd6f8b4a559613ca603b57498511740e65cd11a2e7dcec"
],
"version": "==2.4.6"
},
"pytz": {
"hashes": [
"sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d",
"sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be"
],
"index": "pypi",
"version": "==2019.3"
},
"releases": {
"hashes": [
"sha256:23792ba473dca124e1b60f3bb6428d775b84b05ee73c0bfd1ba8439b833c5749",
"sha256:a9e65295578bf2e352ccc86d33c12b6b7ba27e3da1342d2ffee7f3f7d602a604"
],
"index": "pypi",
"version": "==1.0.0"
},
"requests": {
"hashes": [
"sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee",
"sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6"
],
"version": "==2.23.0"
},
"six": {
"hashes": [
"sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a",
"sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"
],
"version": "==1.14.0"
},
"snowballstemmer": {
"hashes": [
"sha256:209f257d7533fdb3cb73bdbd24f436239ca3b2fa67d56f6ff88e86be08cc5ef0",
"sha256:df3bac3df4c2c01363f3dd2cfa78cce2840a79b9f1c2d2de9ce8d31683992f52"
],
"version": "==2.0.0"
},
"sphinx": {
"hashes": [
"sha256:776ff8333181138fae52df65be733127539623bb46cc692e7fa0fcfc80d7aa88",
"sha256:ca762da97c3b5107cbf0ab9e11d3ec7ab8d3c31377266fd613b962ed971df709"
],
"index": "pypi",
"version": "==2.4.3"
},
"sphinxcontrib-applehelp": {
"hashes": [
"sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a",
"sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"
],
"version": "==1.0.2"
},
"sphinxcontrib-devhelp": {
"hashes": [
"sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e",
"sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"
],
"version": "==1.0.2"
},
"sphinxcontrib-htmlhelp": {
"hashes": [
"sha256:3c0bc24a2c41e340ac37c85ced6dafc879ab485c095b1d65d2461ac2f7cca86f",
"sha256:e8f5bb7e31b2dbb25b9cc435c8ab7a79787ebf7f906155729338f3156d93659b"
],
"version": "==1.0.3"
},
"sphinxcontrib-jsmath": {
"hashes": [
"sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178",
"sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"
],
"version": "==1.0.1"
},
"sphinxcontrib-qthelp": {
"hashes": [
"sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72",
"sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"
],
"version": "==1.0.3"
},
"sphinxcontrib-serializinghtml": {
"hashes": [
"sha256:eaa0eccc86e982a9b939b2b82d12cc5d013385ba5eadcc7e4fed23f4405f77bc",
"sha256:f242a81d423f59617a8e5cf16f5d4d74e28ee9a66f9e5b637a18082991db5a9a"
],
"version": "==1.1.4"
},
"sqlparse": {
"hashes": [
"sha256:022fb9c87b524d1f7862b3037e541f68597a730a8843245c349fc93e1643dc4e",
"sha256:e162203737712307dfe78860cc56c8da8a852ab2ee33750e33aeadf38d12c548"
],
"version": "==0.3.1"
},
"urllib3": {
"hashes": [
"sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc",
"sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc"
],
"version": "==1.25.8"
},
"volatildap": {
"hashes": [
"sha256:7ef4cac02c4f907b87d5e3f5e872287a874d0c0d0a7c1054a8caae8e9c17b367",
"sha256:d9ef3ee7fcdf8d3ae1dd243e0a8c3892e404e4552caa60153fa0de97287bcfc6"
],
"index": "pypi",
"version": "==1.3.0"
}
}
}

View file

@ -10,4 +10,4 @@ customer management at `Jan Dittberner IT-Consulting & -Solutions
Read the :doc:`Installation instructions <install>` to get started locally.
The project page for gvaldap is at http://dev.gnuviech-server.de/gvaldap.
The project page for gvaldap is at http://git.dittberner.info/gnuviech/gvaldap.

24
Vagrantfile vendored Normal file
View file

@ -0,0 +1,24 @@
# -*- mode: ruby -*-
# vi: set ft=ruby :
Vagrant.configure(2) do |config|
config.vm.box = "debian/stretch64"
config.vm.hostname = "gvaldap.local"
config.vm.network "private_network", ip: "172.16.3.3", lxc__bridge_name: 'vlxcbr1'
config.vm.network "forwarded_port", guest: 8000, host: 8001
config.vm.synced_folder "../gvasalt/states/", "/srv/salt/"
config.vm.synced_folder "../gvasalt/pillar/", "/srv/pillar/"
config.vm.provision :salt do |salt|
salt.bootstrap_script = "salt/bootstrap.sh"
salt.minion_id = "gvaldap"
salt.masterless = true
salt.run_highstate = true
salt.verbose = true
salt.colorize = true
salt.log_level = "warning"
end
end

View file

@ -1,6 +1,12 @@
Changelog
=========
* :release:`0.6.0 <2020-03-03>`
* :support:`-` add Python 3 support
* :support:`-` upgrade to Django 2.2.10
* :support:`-` use Pipenv for dependency management
* :feature:`-` properly handle unavailable LDAP server
* :release:`0.5.2 <2015-01-29>`
* :bug:`-` fix minor log message issue

View file

@ -16,13 +16,6 @@ The project module :py:mod:`gvaldap`
.. automodule:: gvaldap
:py:mod:`celery <gvaldap.celery>`
---------------------------------
.. automodule:: gvaldap.celery
:members:
:py:mod:`urls <gvaldap.urls>`
-----------------------------
@ -41,27 +34,6 @@ The project module :py:mod:`gvaldap`
.. automodule:: gvaldap.settings
:py:mod:`base <gvaldap.settings.base>`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. automodule:: gvaldap.settings.base
:members:
:py:mod:`local <gvaldap.settings.local>`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. automodule:: gvaldap.settings.local
:py:mod:`production <gvaldap.settings.production>`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. automodule:: gvaldap.settings.production
:py:mod:`test <gvaldap.settings.test>`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. automodule:: gvaldap.settings.test
:py:mod:`ldapentities` app
==========================
@ -89,6 +61,13 @@ The project module :py:mod:`gvaldap`
.. automodule:: ldaptasks
:py:mod:`celery <ldaptasks.celery>`
-----------------------------------
.. automodule:: ldaptasks.celery
:members:
:py:mod:`tasks <ldaptasks.tasks>`
---------------------------------

View file

@ -21,6 +21,7 @@ import django
# documentation root, use os.path.abspath to make it absolute, like shown here.
sys.path.insert(0, os.path.abspath(os.path.join('..', 'gvaldap')))
os.environ['DJANGO_SETTINGS_MODULE'] = 'gvaldap.settings'
os.environ['GVALDAP_ALLOWED_HOSTS'] = 'localhost'
os.environ['GVALDAP_SERVER_EMAIL'] = 'root@localhost'
@ -35,13 +36,13 @@ django.setup()
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ['releases', 'sphinx.ext.autodoc', 'celery.contrib.sphinx']
# configuration for releases extension
releases_issue_uri = 'https://dev.gnuviech-server.de/gvaldap/ticket/%s'
releases_release_uri = 'https://dev.gnuviech-server.de/gvaldap/browser/?rev=%s'
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
releases_issue_uri = 'https://dev.gnuviech-server.de/gvaldap/ticket/%s'
releases_release_uri = 'https://dev.gnuviech-server.de/gvaldap/milestone/%s'
# The suffix of source filenames.
source_suffix = '.rst'
@ -53,16 +54,17 @@ master_doc = 'index'
# General information about the project.
project = u'gvaldap'
copyright = u'2014, 2015 Jan Dittberner'
copyright = u'2014-2020 Jan Dittberner'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = '0.5'
# The full version, including alpha/beta/rc tags.
release = '0.5.2'
from gvaldap import __version__ as release
# The short X.Y version.
version = ".".join(release.split('.')[:2])
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
@ -103,7 +105,7 @@ pygments_style = 'sphinx'
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
html_theme = 'default'
html_theme = 'alabaster'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the

View file

@ -7,4 +7,4 @@ of the following steps:
* installation of native dependencies
* setup of a virtualenv
* installation of gvaldap production dependencies inside the virtualenv
* setup of celery worker under control of supervisord
* setup of celery worker under control of systemd

View file

@ -7,65 +7,17 @@ Install
Working Environment
===================
You have several options in setting up your working environment. We recommend
using virtualenv to separate the dependencies of your project from your
system's python environment. If on Linux or Mac OS X, you can also use
virtualenvwrapper to help manage multiple virtualenvs across different
projects.
To get a running work environment use `pipenv`_.
.. index:: virtualenv
.. _pipenv: https://pipenv.kennethreitz.org/en/latest/
Virtualenv Only
---------------
First, make sure you are using `virtualenv`_. Once that's installed, create
your virtualenv:
To get started install `pip` and `pipenv` and use `pipenv install --dev`:
.. code-block:: sh
$ virtualenv --distribute gvaldap
.. _virtualenv: https://virtualenv.pypa.io/en/latest/
You will also need to ensure that the virtualenv has the project directory
added to the path. Adding the project directory will allow `django-admin.py` to
be able to change settings using the `--settings` flag.
.. index:: virtualenvwrapper
Virtualenv with virtualenvwrapper
------------------------------------
In Linux and Mac OSX, you can install `virtualenvwrapper
<http://virtualenvwrapper.readthedocs.org/en/latest/>`_, which will take care
of managing your virtual environments and adding the project path to the
`site-directory` for you:
.. code-block:: sh
$ mkdir gvaldap
$ mkvirtualenv -a gvaldap gvaldap-dev
$ cd gvaldap && add2virtualenv `pwd`
.. index:: pip, requirements, dependencies
Installation of Dependencies
=============================
Depending on where you are installing dependencies:
In development:
.. code-block:: sh
$ pip install -r requirements/local.txt
For production:
.. code-block:: sh
$ pip install -r requirements.txt
$ apt install python3-pip
$ python3 -m pip install --user -U pipenv
$ pipenv install --dev
.. index:: celery, worker, ldap queue
@ -79,6 +31,6 @@ into the gvaldap directory and run the celery worker with:
.. code-block:: sh
$ cd gvaldap
$ celery -A gvaldap worker -Q ldap -l info
$ pipenv run celery -A ldaptasks worker -Q web -l info
.. _Celery: http://www.celeryproject.org/

7
gvaldap.sh Executable file
View file

@ -0,0 +1,7 @@
#!/bin/sh
set -e
. /home/gvaldap/gvaldap-venv/bin/activate
cd /srv/gvaldap/gvaldap
celery -A ldaptasks worker -Q ldap -l info

View file

@ -1,3 +0,0 @@
.*.swp
*.pyc
.ropeproject/

View file

@ -1,15 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
class GvaRouter(object):
def route_for_task(self, task, args=None, kwargs=None):
for route in ['ldap', 'file', 'mysql', 'pgsql']:
if route in task:
return {
'exchange': route,
'exchange_type': 'direct',
'queue': route,
}
return None

View file

@ -1,3 +1,8 @@
"""
This is the gvaldap project module.
"""
__version__ = "0.6.0"
from ldaptasks.celery import app as celery_app
__all__ = ("celery_app",)

View file

@ -1,23 +0,0 @@
"""
This module defines the Celery_ app for gvaldap.
.. _Celery: http://www.celeryproject.org/
"""
from __future__ import absolute_import
import os
from celery import Celery
from django.conf import settings
os.environ.setdefault('DJANGO_SETTINGS_MODULE',
'gvaldap.settings.production')
#: The Celery application
app = Celery('gvaldap')
app.config_from_object('django.conf:settings')
app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)

315
gvaldap/gvaldap/settings.py Normal file
View file

@ -0,0 +1,315 @@
# -*- coding: utf-8 -*-
# pymode:lint_ignore=E501
"""
Common settings and globals.
"""
from os.path import abspath, basename, dirname, join, normpath
from sys import path
from gvacommon.settings_utils import get_env_variable
# ######### PATH CONFIGURATION
# Absolute filesystem path to the Django project directory:
DJANGO_ROOT = dirname(dirname(abspath(__file__)))
# Absolute filesystem path to the top-level project folder:
SITE_ROOT = dirname(DJANGO_ROOT)
# Site name:
SITE_NAME = basename(DJANGO_ROOT)
# Add our project to our pythonpath, this way we don't need to type our project
# name in our dotted import paths:
path.append(DJANGO_ROOT)
# ######### END PATH CONFIGURATION
# ######### DEBUG CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#debug
DEBUG = get_env_variable("GVALDAP_DEBUG", bool, False)
# ######### MANAGER CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#admins
ADMINS = (
(get_env_variable("GVALDAP_ADMIN_NAME"), get_env_variable("GVALDAP_ADMIN_EMAIL")),
)
# See: https://docs.djangoproject.com/en/dev/ref/settings/#managers
MANAGERS = ADMINS
# ######### END MANAGER CONFIGURATION
# ######### DATABASE CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#databases
DATABASES = {
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": normpath(join(DJANGO_ROOT, "default.db")),
"USER": "",
"PASSWORD": "",
"HOST": "",
"PORT": "",
},
"ldap": {
"ENGINE": "ldapdb.backends.ldap",
"NAME": get_env_variable("GVALDAP_LDAP_URL"),
"USER": get_env_variable("GVALDAP_LDAP_USER"),
"PASSWORD": get_env_variable("GVALDAP_LDAP_PASSWORD"),
},
}
DATABASE_ROUTERS = ["ldapdb.router.Router"]
# ######### END DATABASE CONFIGURATION
# ######### GENERAL CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#time-zone
TIME_ZONE = "Europe/Berlin"
# See: https://docs.djangoproject.com/en/dev/ref/settings/#language-code
LANGUAGE_CODE = "en-us"
# See: https://docs.djangoproject.com/en/dev/ref/settings/#site-id
SITE_ID = 1
# See: https://docs.djangoproject.com/en/dev/ref/settings/#use-i18n
USE_I18N = True
# See: https://docs.djangoproject.com/en/dev/ref/settings/#use-l10n
USE_L10N = True
# See: https://docs.djangoproject.com/en/dev/ref/settings/#use-tz
USE_TZ = True
# ######### END GENERAL CONFIGURATION
# ######### MEDIA CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#media-root
MEDIA_ROOT = normpath(join(SITE_ROOT, "media"))
# See: https://docs.djangoproject.com/en/dev/ref/settings/#media-url
MEDIA_URL = "/media/"
# ######### END MEDIA CONFIGURATION
# ######### STATIC FILE CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#static-url
STATIC_URL = "/static/"
# See:
# https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#staticfiles-finders # noqa
STATICFILES_FINDERS = ("django.contrib.staticfiles.finders.AppDirectoriesFinder",)
# ######### END STATIC FILE CONFIGURATION
# ######### SECRET CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#secret-key
# Note: This key should only be used for development and testing.
SECRET_KEY = get_env_variable("GVALDAP_SECRETKEY")
# ######### END SECRET CONFIGURATION
# ######### FIXTURE CONFIGURATION
# See:
# https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-FIXTURE_DIRS # noqa
FIXTURE_DIRS = (normpath(join(SITE_ROOT, "fixtures")),)
# ######### END FIXTURE CONFIGURATION
# ######### TEMPLATE CONFIGURATION
# See: https://docs.djangoproject.com/en/1.9/ref/settings/#std:setting-TEMPLATES # noqa
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [normpath(join(SITE_ROOT, "templates"))],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
"django.contrib.auth.context_processors.auth",
"django.template.context_processors.debug",
"django.template.context_processors.i18n",
"django.template.context_processors.media",
"django.template.context_processors.static",
"django.template.context_processors.tz",
"django.contrib.messages.context_processors.messages",
"django.template.context_processors.request",
]
},
}
]
# ######### END TEMPLATE CONFIGURATION
# ######### MIDDLEWARE CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#middleware-classes
MIDDLEWARE = (
# Default Django middleware.
"django.middleware.common.CommonMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.locale.LocaleMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
)
# ######### END MIDDLEWARE CONFIGURATION
# ######### URL CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#root-urlconf
ROOT_URLCONF = "%s.urls" % SITE_NAME
# ######### END URL CONFIGURATION
# ######### TEST RUNNER CONFIGURATION
TEST_RUNNER = "django.test.runner.DiscoverRunner"
# ######### END TEST RUNNER CONFIGURATION
# ######### APP CONFIGURATION
DJANGO_APPS = (
# Default Django apps:
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.sites",
"django.contrib.messages",
"django.contrib.staticfiles",
# Admin panel and documentation:
"django.contrib.admin",
)
# Apps specific for this project go here.
LOCAL_APPS = ("ldapentities", "ldaptasks")
# See: https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps
INSTALLED_APPS = DJANGO_APPS + LOCAL_APPS
# ######### END APP CONFIGURATION
# ######### LOGGING CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#logging
# A sample logging configuration. The only tangible logging
# performed by this configuration is to send an email to
# the site admins on every HTTP 500 error when DEBUG=False.
# See http://docs.djangoproject.com/en/dev/topics/logging for
# more details on how to customize your logging configuration.
LOGGING = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"verbose": {
"format": "%(levelname)s %(asctime)s %(name)s "
"%(module)s:%(lineno)d %(process)d %(thread)d %(message)s"
},
"simple": {"format": "%(levelname)s %(name)s:%(lineno)d %(message)s"},
},
"filters": {"require_debug_false": {"()": "django.utils.log.RequireDebugFalse"}},
"handlers": {
"mail_admins": {
"level": "ERROR",
"filters": ["require_debug_false"],
"class": "django.utils.log.AdminEmailHandler",
}
},
"loggers": {
"django.request": {
"handlers": ["mail_admins"],
"level": "ERROR",
"propagate": True,
}
},
}
# ######### END LOGGING CONFIGURATION
# ######### WSGI CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#wsgi-application
WSGI_APPLICATION = "%s.wsgi.application" % SITE_NAME
# ######### END WSGI CONFIGURATION
# ######### LDAP SETTINGS
GROUP_BASE_DN = get_env_variable("GVALDAP_BASEDN_GROUP")
USER_BASE_DN = get_env_variable("GVALDAP_BASEDN_USER")
# ######### END LDAP SETTINGS
# ######### CELERY CONFIGURATION
CELERY_BROKER_URL = get_env_variable("GVALDAP_BROKER_URL")
CELERY_RESULT_BACKEND = get_env_variable("GVALDAP_RESULTS_REDIS_URL")
CELERY_TASK_RESULT_EXPIRES = None
CELERY_ROUTES = ("gvacommon.celeryrouters.GvaRouter",)
CELERY_TIMEZONE = "Europe/Berlin"
CELERY_ENABLE_UTC = True
CELERY_ACCEPT_CONTENT = ["json"]
CELERY_TASK_SERIALIZER = "json"
CELERY_RESULT_SERIALIZER = "json"
# ######### END CELERY CONFIGURATION
if DEBUG:
TEMPLATES[0]["OPTIONS"]["debug"] = DEBUG
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
CACHES = {"default": {"BACKEND": "django.core.cache.backends.locmem.LocMemCache"}}
INSTALLED_APPS += ("debug_toolbar",)
MIDDLEWARE += ("debug_toolbar.middleware.DebugToolbarMiddleware",)
LOGGING["handlers"].update(
{
"console": {
"level": "DEBUG",
"class": "logging.StreamHandler",
"formatter": "simple",
}
}
)
LOGGING["loggers"].update(
dict(
[
(key, {"handlers": ["console"], "level": "DEBUG", "propagate": True})
for key in ["ldapentities", "ldaptasks"]
]
)
)
INTERNAL_IPS = get_env_variable("GVALDAP_INTERNAL_IPS", str, "127.0.0.1").split(",")
else:
ALLOWED_HOSTS = get_env_variable("GVALDAP_ALLOWED_HOSTS").split(",")
EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
EMAIL_SUBJECT_PREFIX = "[%s] " % SITE_NAME
DEFAULT_FROM_EMAIL = get_env_variable("GVALDAP_ADMIN_EMAIL")
SERVER_EMAIL = get_env_variable("GVALDAP_SERVER_EMAIL")
if get_env_variable("GVALDAP_TEST", bool, False):
PASSWORD_HASHERS = ("django.contrib.auth.hashers.MD5PasswordHasher",)
# ######### IN-MEMORY TEST DATABASE
DATABASES["default"] = {
"ENGINE": "django.db.backends.sqlite3",
"NAME": ":memory:",
"USER": "",
"PASSWORD": "",
"HOST": "",
"PORT": "",
}
LOGGING["handlers"].update(
{
"console": {
"level": "ERROR",
"class": "logging.StreamHandler",
"formatter": "simple",
}
}
)
LOGGING["loggers"].update(
dict(
[
(key, {"handlers": ["console"], "level": "ERROR", "propagate": True})
for key in ["ldapentities", "ldaptasks"]
]
)
)
CELERY_BROKER_URL = CELERY_BROKER_URL + "_test"
CELERY_RESULT_PERSISTENT = False

View file

@ -1,3 +0,0 @@
"""
This module contains settings for various environments.
"""

View file

@ -1,301 +0,0 @@
# -*- coding: utf-8 -*-
# pymode:lint_ignore=E501
"""
Common settings and globals.
"""
from os.path import abspath, basename, dirname, join, normpath
from sys import path
from os import environ
# Normally you should not import ANYTHING from Django directly
# into your settings, but ImproperlyConfigured is an exception.
from django.core.exceptions import ImproperlyConfigured
def get_env_setting(setting):
"""
Get the environment setting or return exception.
:param str setting: name of an environment setting
:raises ImproperlyConfigured: if the environment setting is not defined
:return: environment setting value
:rtype: str
"""
try:
return environ[setting]
except KeyError:
error_msg = "Set the %s env variable" % setting
raise ImproperlyConfigured(error_msg)
########## PATH CONFIGURATION
# Absolute filesystem path to the Django project directory:
DJANGO_ROOT = dirname(dirname(abspath(__file__)))
# Absolute filesystem path to the top-level project folder:
SITE_ROOT = dirname(DJANGO_ROOT)
# Site name:
SITE_NAME = basename(DJANGO_ROOT)
# Add our project to our pythonpath, this way we don't need to type our project
# name in our dotted import paths:
path.append(DJANGO_ROOT)
########## END PATH CONFIGURATION
########## DEBUG CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#debug
DEBUG = False
# See: https://docs.djangoproject.com/en/dev/ref/settings/#template-debug
TEMPLATE_DEBUG = DEBUG
########## END DEBUG CONFIGURATION
########## MANAGER CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#admins
ADMINS = (
(get_env_setting('GVALDAP_ADMIN_NAME'), get_env_setting('GVALDAP_ADMIN_EMAIL')),
)
# See: https://docs.djangoproject.com/en/dev/ref/settings/#managers
MANAGERS = ADMINS
########## END MANAGER CONFIGURATION
########## DATABASE CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': normpath(join(DJANGO_ROOT, 'default.db')),
'USER': '',
'PASSWORD': '',
'HOST': '',
'PORT': '',
},
'ldap': {
'ENGINE': 'ldapdb.backends.ldap',
'NAME': get_env_setting('GVALDAP_LDAP_URL'),
'USER': get_env_setting('GVALDAP_LDAP_USER'),
'PASSWORD': get_env_setting('GVALDAP_LDAP_PASSWORD'),
}
}
DATABASE_ROUTERS = ['ldapdb.router.Router']
########## END DATABASE CONFIGURATION
########## GENERAL CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#time-zone
TIME_ZONE = 'Europe/Berlin'
# See: https://docs.djangoproject.com/en/dev/ref/settings/#language-code
LANGUAGE_CODE = 'en-us'
# See: https://docs.djangoproject.com/en/dev/ref/settings/#site-id
SITE_ID = 1
# See: https://docs.djangoproject.com/en/dev/ref/settings/#use-i18n
USE_I18N = True
# See: https://docs.djangoproject.com/en/dev/ref/settings/#use-l10n
USE_L10N = True
# See: https://docs.djangoproject.com/en/dev/ref/settings/#use-tz
USE_TZ = True
########## END GENERAL CONFIGURATION
########## MEDIA CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#media-root
MEDIA_ROOT = normpath(join(SITE_ROOT, 'media'))
# See: https://docs.djangoproject.com/en/dev/ref/settings/#media-url
MEDIA_URL = '/media/'
########## END MEDIA CONFIGURATION
########## STATIC FILE CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#static-root
STATIC_ROOT = normpath(join(SITE_ROOT, 'assets'))
# See: https://docs.djangoproject.com/en/dev/ref/settings/#static-url
STATIC_URL = '/static/'
# See: https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#std:setting-STATICFILES_DIRS
STATICFILES_DIRS = (
normpath(join(SITE_ROOT, 'static')),
)
# See: https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#staticfiles-finders
STATICFILES_FINDERS = (
'django.contrib.staticfiles.finders.FileSystemFinder',
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
)
########## END STATIC FILE CONFIGURATION
########## SECRET CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#secret-key
# Note: This key should only be used for development and testing.
SECRET_KEY = get_env_setting('GVALDAP_SECRETKEY')
########## END SECRET CONFIGURATION
########## SITE CONFIGURATION
# Hosts/domain names that are valid for this site
# See https://docs.djangoproject.com/en/1.5/ref/settings/#allowed-hosts
ALLOWED_HOSTS = []
########## END SITE CONFIGURATION
########## FIXTURE CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-FIXTURE_DIRS
FIXTURE_DIRS = (
normpath(join(SITE_ROOT, 'fixtures')),
)
########## END FIXTURE CONFIGURATION
########## TEMPLATE CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#template-context-processors
TEMPLATE_CONTEXT_PROCESSORS = (
'django.contrib.auth.context_processors.auth',
'django.core.context_processors.debug',
'django.core.context_processors.i18n',
'django.core.context_processors.media',
'django.core.context_processors.static',
'django.core.context_processors.tz',
'django.contrib.messages.context_processors.messages',
'django.core.context_processors.request',
)
# See: https://docs.djangoproject.com/en/dev/ref/settings/#template-loaders
TEMPLATE_LOADERS = (
'django.template.loaders.filesystem.Loader',
'django.template.loaders.app_directories.Loader',
)
# See: https://docs.djangoproject.com/en/dev/ref/settings/#template-dirs
TEMPLATE_DIRS = (
normpath(join(SITE_ROOT, 'templates')),
)
########## END TEMPLATE CONFIGURATION
########## MIDDLEWARE CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#middleware-classes
MIDDLEWARE_CLASSES = (
# Default Django middleware.
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
)
########## END MIDDLEWARE CONFIGURATION
########## URL CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#root-urlconf
ROOT_URLCONF = '%s.urls' % SITE_NAME
########## END URL CONFIGURATION
########## TEST RUNNER CONFIGURATION
TEST_RUNNER = 'django.test.runner.DiscoverRunner'
########## END TEST RUNNER CONFIGURATION
########## APP CONFIGURATION
DJANGO_APPS = (
# Default Django apps:
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.messages',
'django.contrib.staticfiles',
# Useful template tags:
# 'django.contrib.humanize',
# Admin panel and documentation:
'django.contrib.admin',
# 'django.contrib.admindocs',
)
# Apps specific for this project go here.
LOCAL_APPS = (
'ldapentities',
'ldaptasks',
)
# See: https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps
INSTALLED_APPS = DJANGO_APPS + LOCAL_APPS
########## END APP CONFIGURATION
########## LOGGING CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#logging
# A sample logging configuration. The only tangible logging
# performed by this configuration is to send an email to
# the site admins on every HTTP 500 error when DEBUG=False.
# See http://docs.djangoproject.com/en/dev/topics/logging for
# more details on how to customize your logging configuration.
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'filters': {
'require_debug_false': {
'()': 'django.utils.log.RequireDebugFalse'
}
},
'handlers': {
'mail_admins': {
'level': 'ERROR',
'filters': ['require_debug_false'],
'class': 'django.utils.log.AdminEmailHandler'
}
},
'loggers': {
'django.request': {
'handlers': ['mail_admins'],
'level': 'ERROR',
'propagate': True,
},
}
}
########## END LOGGING CONFIGURATION
########## WSGI CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#wsgi-application
WSGI_APPLICATION = '%s.wsgi.application' % SITE_NAME
########## END WSGI CONFIGURATION
########## LDAP SETTINGS
GROUP_BASE_DN = get_env_setting('GVALDAP_BASEDN_GROUP')
USER_BASE_DN = get_env_setting('GVALDAP_BASEDN_USER')
########## END LDAP SETTINGS
########## CELERY CONFIGURATION
BROKER_URL = get_env_setting('GVALDAP_BROKER_URL')
CELERY_RESULT_BACKEND = 'amqp'
CELERY_RESULT_PERSISTENT = True
CELERY_TASK_RESULT_EXPIRES = None
CELERY_ROUTES = (
'gvacommon.celeryrouters.GvaRouter',
)
CELERY_TIMEZONE = 'Europe/Berlin'
CELERY_ENABLE_UTC = True
CELERY_ACCEPT_CONTENT = ['json']
CELERY_TASK_SERAILIZER = 'json'
CELERY_RESULT_SERIALIZER = 'json'
########## END CELERY CONFIGURATION

View file

@ -1,51 +0,0 @@
# pymode:lint_ignore=W0401,E501
"""
Development settings and globals based on :py:mod:`gvaldap.settings.base`.
"""
from __future__ import absolute_import
from .base import *
########## DEBUG CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#debug
DEBUG = True
# See: https://docs.djangoproject.com/en/dev/ref/settings/#template-debug
TEMPLATE_DEBUG = DEBUG
########## END DEBUG CONFIGURATION
########## EMAIL CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#email-backend
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
########## END EMAIL CONFIGURATION
########## CACHE CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#caches
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
}
}
########## END CACHE CONFIGURATION
########## TOOLBAR CONFIGURATION
# See: http://django-debug-toolbar.readthedocs.org/en/latest/installation.html#explicit-setup
INSTALLED_APPS += (
'debug_toolbar',
)
MIDDLEWARE_CLASSES += (
'debug_toolbar.middleware.DebugToolbarMiddleware',
)
DEBUG_TOOLBAR_PATCH_SETTINGS = False
# http://django-debug-toolbar.readthedocs.org/en/latest/installation.html
INTERNAL_IPS = ('127.0.0.1',)
########## END TOOLBAR CONFIGURATION

View file

@ -1,50 +0,0 @@
# pymode:lint_ignore=W0401,E501
"""
Production settings and globals based on :py:mod:`gvaldap.settings.base`.
"""
from __future__ import absolute_import
from .base import *
########## HOST CONFIGURATION
# See: https://docs.djangoproject.com/en/1.5/releases/1.5/#allowed-hosts-required-in-production
ALLOWED_HOSTS = get_env_setting('GVALDAP_ALLOWED_HOSTS').split(',')
########## END HOST CONFIGURATION
########## EMAIL CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#email-backend
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
# See: https://docs.djangoproject.com/en/dev/ref/settings/#email-host
#EMAIL_HOST = environ.get('EMAIL_HOST', 'smtp.gmail.com')
# See: https://docs.djangoproject.com/en/dev/ref/settings/#email-host-password
#EMAIL_HOST_PASSWORD = environ.get('EMAIL_HOST_PASSWORD', '')
# See: https://docs.djangoproject.com/en/dev/ref/settings/#email-host-user
#EMAIL_HOST_USER = environ.get('EMAIL_HOST_USER', 'your_email@example.com')
# See: https://docs.djangoproject.com/en/dev/ref/settings/#email-port
#EMAIL_PORT = environ.get('EMAIL_PORT', 587)
# See: https://docs.djangoproject.com/en/dev/ref/settings/#email-subject-prefix
EMAIL_SUBJECT_PREFIX = '[%s] ' % SITE_NAME
# See: https://docs.djangoproject.com/en/dev/ref/settings/#email-use-tls
#EMAIL_USE_TLS = True
# See: https://docs.djangoproject.com/en/dev/ref/settings/#server-email
SERVER_EMAIL = get_env_setting('GVALDAP_SERVER_EMAIL')
########## END EMAIL CONFIGURATION
########## DATABASE CONFIGURATION
#DATABASES = {}
########## END DATABASE CONFIGURATION
########## CACHE CONFIGURATION
# See: https://docs.djangoproject.com/en/dev/ref/settings/#caches
#CACHES = {}
########## END CACHE CONFIGURATION

View file

@ -1,20 +0,0 @@
# pymode:lint_ignore=W0401
"""
Test settings based on :py:mod:`gvaldap.settings.base`.
"""
from __future__ import absolute_import
from .base import *
########## IN-MEMORY TEST DATABASE
DATABASES = {
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": ":memory:",
"USER": "",
"PASSWORD": "",
"HOST": "",
"PORT": "",
},
}

View file

@ -0,0 +1,12 @@
"""
This module provides tests for :py:mod:`gvaldap.wsgi`.
"""
from unittest import TestCase
class WSGITest(TestCase):
def test_wsgi_application(self):
from gvaldap import wsgi
self.assertIsNotNone(wsgi.application)

View file

@ -3,23 +3,27 @@ This module defines the main URLConf for gvaldap.
"""
from django.conf.urls import patterns, include, url
from django.conf import settings
# Uncomment the next two lines to enable the admin:
from django.conf.urls import include, url
from django.contrib import admin
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
admin.autodiscover()
urlpatterns = patterns(
'',
url(r'^admin/', include(admin.site.urls)),
)
urlpatterns = [
url(r'^admin/', admin.site.urls),
]
# Uncomment the next line to serve media files in dev.
# urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
if settings.DEBUG:
if settings.DEBUG: # pragma: no cover
import debug_toolbar
urlpatterns += patterns('',
urlpatterns = (
[
url(r'^__debug__/', include(debug_toolbar.urls)),
]
+ staticfiles_urlpatterns()
+ urlpatterns
)

View file

@ -17,6 +17,11 @@ import os
from os.path import abspath, dirname
from sys import path
# This application object is used by any WSGI server configured to use this
# file. This includes Django's development server, if the WSGI_APPLICATION
# setting points here.
from django.core.wsgi import get_wsgi_application # noqa
SITE_ROOT = dirname(dirname(abspath(__file__)))
path.append(SITE_ROOT)
@ -24,12 +29,8 @@ path.append(SITE_ROOT)
# if running multiple sites in the same mod_wsgi process. To fix this, use
# mod_wsgi daemon mode with each site in its own daemon process, or use
# os.environ["DJANGO_SETTINGS_MODULE"] = "jajaja.settings"
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "gvaldap.settings.production")
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "gvaldap.settings")
from django.core.wsgi import get_wsgi_application
# This application object is used by any WSGI server configured to use this
# file. This includes Django's development server, if the WSGI_APPLICATION
# setting points here.
application = get_wsgi_application()
# Apply WSGI middleware here.

View file

@ -7,7 +7,7 @@ Admin classes for easy `django admin`_ based administration of LDAP entities.
from django.contrib import admin
from .models import (
from ldapentities.models import (
LdapGroup,
LdapUser,
)

View file

@ -6,9 +6,9 @@ The models are based on :py:class:`ldapmodels.Model` from `django-ldapdb`_.
.. _django-ldapdb: https://github.com/jlaine/django-ldapdb#readme
"""
from __future__ import unicode_literals
from django.conf import settings
from django.utils.encoding import python_2_unicode_compatible
from ldapdb.models.fields import (
CharField,
IntegerField,
@ -19,7 +19,6 @@ import ldapdb.models as ldapmodels
from passlib.hash import ldap_salted_sha1
@python_2_unicode_compatible
class LdapGroup(ldapmodels.Model):
"""
Class for representing an LDAP group entity with objectClass `posixGroup`.
@ -56,7 +55,6 @@ class LdapGroup(ldapmodels.Model):
return self.name
@python_2_unicode_compatible
class LdapUser(ldapmodels.Model):
"""
Class for representing an LDAP user entity with objectClasses `account` and

View file

View file

@ -0,0 +1,69 @@
"""
This module provides tests for :py:mod:`ldapentities.admin`.
"""
from __future__ import absolute_import
import volatildap
from django.conf import settings
from django.test import TestCase
from django.contrib.auth import get_user_model
from django.urls import reverse
User = get_user_model()
TEST_USER = 'admin'
TEST_EMAIL = 'admin@example.org'
TEST_PASSWORD = 'secret'
admin = (settings.DATABASES['ldap']['USER'], {
'objectClass': ['person'],
'userPassword': [
settings.DATABASES['ldap'][
'PASSWORD']],
'sn': 'Admin',
})
groups = (settings.GROUP_BASE_DN, {
'objectClass': ['top', 'organizationalUnit'], 'ou': ['groups']})
users = (
settings.USER_BASE_DN, {
'objectClass': ['top', 'organizationalUnit'], 'ou': ['users']})
class LdapUserAdminTest(TestCase):
databases = ["default", "ldap"]
directory = dict([admin, groups, users])
@classmethod
def setUpClass(cls):
super(LdapUserAdminTest, cls).setUpClass()
cls.ldap_server = volatildap.LdapServer(
initial_data=cls.directory,
schemas=['core.schema', 'cosine.schema', 'inetorgperson.schema',
'nis.schema'],
)
settings.DATABASES['ldap']['USER'] = cls.ldap_server.rootdn
settings.DATABASES['ldap']['PASSWORD'] = cls.ldap_server.rootpw
settings.DATABASES['ldap']['NAME'] = cls.ldap_server.uri
@classmethod
def tearDownClass(cls):
cls.ldap_server.stop()
super(LdapUserAdminTest, cls).tearDownClass()
def setUp(self):
User.objects.create_superuser(
TEST_USER, email=TEST_EMAIL, password=TEST_PASSWORD)
self.client.login(username=TEST_USER, password=TEST_PASSWORD)
self.ldap_server.start()
def test_can_administer_groups(self):
response = self.client.get(reverse(
'admin:ldapentities_ldapgroup_changelist'))
self.assertEqual(response.status_code, 200)
def test_can_administer_users(self):
response = self.client.get(reverse(
'admin:ldapentities_ldapuser_changelist'))
self.assertEqual(response.status_code, 200)

View file

@ -0,0 +1,37 @@
"""
This model provides tests for :py:mod:`ldapentities.models`.
"""
from __future__ import absolute_import, unicode_literals
from django.test import TestCase
from passlib.hash import ldap_salted_sha1
from ldapentities.models import LdapGroup, LdapUser
class LdapGroupTest(TestCase):
def test___str__(self):
ldapgroup = LdapGroup(
gid=5000, name='test', description='test group')
self.assertEqual(str(ldapgroup), 'test')
class LdapUserTest(TestCase):
def test___str__(self):
ldapuser = LdapUser(
uid=5000, group=5000, gecos="a test user",
home_directory='/home/test', login_shell='/bin/bash',
username='test', password='test', common_name='Test')
self.assertEqual(str(ldapuser), 'test')
def test_set_password(self):
ldapuser = LdapUser(
uid=5000, group=5000, gecos="a test user",
home_directory='/home/test', login_shell='/bin/bash',
username='test', password='test', common_name='Test')
self.assertEqual(ldapuser.password, 'test')
ldapuser.set_password('test2')
self.assertTrue(ldap_salted_sha1.verify('test2', ldapuser.password))

View file

@ -0,0 +1,17 @@
"""
This module defines the Celery_ app for gvaldap.
.. _Celery: http://www.celeryproject.org/
"""
import os
from celery import Celery
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "gvaldap.settings")
app = Celery("ldaptasks")
app.config_from_object("django.conf:settings", namespace="CELERY")
app.autodiscover_tasks()

View file

@ -7,22 +7,22 @@ This module defines `Celery`_ tasks to manage LDAP entities.
from __future__ import absolute_import
from django.core.exceptions import ObjectDoesNotExist
from copy import deepcopy
from celery import shared_task
from celery.utils.log import get_task_logger
from celery.exceptions import Reject
from celery.utils.log import get_task_logger
from ldapentities.models import (
LdapGroup,
LdapUser,
)
from django.core.exceptions import ObjectDoesNotExist
from django.db.utils import Error as DjangoDBUtilsError
from ldapentities.models import LdapGroup, LdapUser
_LOGGER = get_task_logger(__name__)
@shared_task
def create_ldap_group(groupname, gid, descr):
@shared_task(autoretry_for=(DjangoDBUtilsError,), default_retry_delay=10)
def create_ldap_group(groupname, gid, description):
"""
This task creates an :py:class:`LDAP group <ldapentities.models.LdapGroup>`
if it does not exist yet.
@ -32,26 +32,34 @@ def create_ldap_group(groupname, gid, descr):
:param str groupname: the group name
:param int gid: the group id
:param str descr: description text for the group
:return: the distinguished name of the group
:rtype: str
:param str description: description text for the group
:return: dictionary containing groupname, gid, description and
:py:const:`group_dn` set to the distinguished name of the newly created
or existing LDAP group
:rtype: dict
"""
try:
ldapgroup = LdapGroup.objects.get(name=groupname)
_LOGGER.info(
'LDAP group %s with groupname %s already exists',
ldapgroup.dn, groupname)
"LDAP group %s with groupname %s already exists", ldapgroup.dn, groupname
)
ldapgroup.gid = gid
except LdapGroup.DoesNotExist:
ldapgroup = LdapGroup(gid=gid, name=groupname)
_LOGGER.info('created LDAP group %s', ldapgroup.dn)
ldapgroup.description = descr
_LOGGER.info("created LDAP group %s", ldapgroup.dn)
ldapgroup.description = description
ldapgroup.save()
_LOGGER.info('set description of LDAP group %s', ldapgroup.dn)
return ldapgroup.dn
_LOGGER.info("set description of LDAP group %s", ldapgroup.dn)
return {
"groupname": groupname,
"gid": gid,
"description": description,
"group_dn": ldapgroup.dn,
}
@shared_task
@shared_task(autoretry_for=(DjangoDBUtilsError,), default_retry_delay=10)
def create_ldap_user(username, uid, gid, gecos, homedir, shell, password):
"""
This task creates an :py:class:`LDAP user <ldapentities.models.LdapUser>`
@ -71,21 +79,23 @@ def create_ldap_user(username, uid, gid, gecos, homedir, shell, password):
is passed the password is not touched
:raises celery.exceptions.Reject: if the specified primary group does not
exist
:return: the distinguished name of the user
:rtype: str
:return: dictionary containing username, uid, gid, gecos, homedir, shell,
password and :py:const:`user_dn` set to the distinguished name of the
newly created or existing LDAP user
:rtype: dict
"""
try:
ldapuser = LdapUser.objects.get(username=username)
_LOGGER.info(
'LDAP user %s with username %s already exists',
ldapuser.dn, username)
"LDAP user %s with username %s already exists", ldapuser.dn, username
)
except LdapUser.DoesNotExist:
ldapuser = LdapUser(username=username)
try:
ldapgroup = LdapGroup.objects.get(gid=gid)
except ObjectDoesNotExist as exc:
_LOGGER.error('LDAP group with gid %d does not exist', gid)
_LOGGER.error("LDAP group with gid %d does not exist", gid)
raise Reject(exc, requeue=False)
ldapuser.uid = uid
ldapuser.group = gid
@ -96,46 +106,58 @@ def create_ldap_user(username, uid, gid, gecos, homedir, shell, password):
ldapuser.common_name = username
if password is not None:
ldapuser.set_password(password)
_LOGGER.info('set password for LDAP user %s', ldapuser.dn)
_LOGGER.info("set password for LDAP user %s", ldapuser.dn)
ldapuser.save()
_LOGGER.info('LDAP user %s created', ldapuser.dn)
_LOGGER.info("LDAP user %s created", ldapuser.dn)
if ldapuser.username in ldapgroup.members:
_LOGGER.info(
'LDAP user %s is already member of LDAP group %s',
ldapuser.dn, ldapgroup.dn)
"LDAP user %s is already member of LDAP group %s", ldapuser.dn, ldapgroup.dn
)
else:
ldapgroup.members.append(ldapuser.username)
ldapgroup.save()
_LOGGER.info(
'LDAP user %s has been added to LDAP group %s',
ldapuser.dn, ldapgroup.dn)
return ldapuser.dn
"LDAP user %s has been added to LDAP group %s", ldapuser.dn, ldapgroup.dn
)
return {
"username": username,
"uid": uid,
"gid": gid,
"gecos": gecos,
"homedir": homedir,
"shell": shell,
"user_dn": ldapuser.dn,
}
@shared_task(bind=True)
def set_ldap_user_password(self, username, password):
@shared_task(autoretry_for=(DjangoDBUtilsError,), default_retry_delay=10)
def set_ldap_user_password(username, password):
"""
This task sets the password of an existing :py:class:`LDAP user
<ldapentities.models.LdapUser>`.
:param str username: the user name
:param str password: teh clear text password
:return: :py:const:`True` if the password has been set, :py:const:`False`
if the user does not exist.
:return: dictionary containing the username and a flag
:py:const:`password_set` that is set to :py:const:`True` if the
password has been set, :py:const:`False` if the user does not exist.
:rtype: dict
"""
retval = {"username": username, "password_set": False}
try:
ldapuser = LdapUser.objects.get(username=username)
except LdapUser.DoesNotExist:
_LOGGER.info('there is no LDAP user with username %s', username)
return False
_LOGGER.info("there is no LDAP user with username %s", username)
return retval
ldapuser.set_password(password)
ldapuser.save()
_LOGGER.info("set new password for LDAP user %s", ldapuser.dn)
return True
retval["password_set"] = True
return retval
@shared_task(bind=True)
@shared_task(bind=True, autoretry_for=(DjangoDBUtilsError,), default_retry_delay=10)
def add_ldap_user_to_group(self, username, groupname):
"""
This task adds the specified user to the given group.
@ -146,146 +168,198 @@ def add_ldap_user_to_group(self, username, groupname):
:param str groupname: the group name
:raises celery.exceptions.Retry: if the user does not exist yet,
:py:func:`create_ldap_user` should be called before
:return: True if the user has been added to the group otherwise False
:rtype: boolean
:return: dictionary containing the username, groupname and a flag
:py:const`added` that is as a :py:const:`True` if the user has been
added to the group otherwise to :py:const:`False`
:rtype: dict
"""
retval = {"username": username, "groupname": groupname, "added": False}
try:
ldapgroup = LdapGroup.objects.get(name=groupname)
ldapuser = LdapUser.objects.get(username=username)
except LdapGroup.DoesNotExist:
_LOGGER.error('LDAP group with groupname %s does not exist', groupname)
_LOGGER.error("LDAP group with groupname %s does not exist", groupname)
except LdapUser.DoesNotExist as exc:
_LOGGER.error('LDAP user with username %s does not exist', username)
_LOGGER.error("LDAP user with username %s does not exist", username)
self.retry(exc=exc, time_limit=5)
else:
if ldapuser.username not in ldapgroup.members:
ldapgroup.members.append(ldapuser.username)
ldapgroup.save()
_LOGGER.info(
'LDAP user %s has been added to LDAP group %s',
ldapuser.username, ldapgroup.dn)
"LDAP user %s has been added to LDAP group %s",
ldapuser.username,
ldapgroup.dn,
)
else:
_LOGGER.info(
'LDAP user %s is already in LDAP group %s',
ldapuser.username, ldapgroup.dn)
return True
return False
"LDAP user %s is already in LDAP group %s",
ldapuser.username,
ldapgroup.dn,
)
retval["added"] = True
return retval
@shared_task
@shared_task(autoretry_for=(DjangoDBUtilsError,), default_retry_delay=10)
def remove_ldap_user_from_group(username, groupname):
"""
This task removes the given user from the given group.
:param str username: the user name
:param str groupname: the group name
:return: True if the user has been removed, False otherwise
:rtype: boolean
:return: dictionary containing the input parameters and a flag
:py:const:`removed` that is set to :py:const:`True` if the user has
been removed, False otherwise
:rtype: dict
"""
retval = {"username": username, "groupname": groupname, "removed": False}
try:
ldapgroup = LdapGroup.objects.get(name=groupname)
ldapuser = LdapUser.objects.get(username=username)
except LdapGroup.DoesNotExist:
_LOGGER.error('LDAP group with groupname %s does not exist', groupname)
_LOGGER.error("LDAP group with groupname %s does not exist", groupname)
except LdapUser.DoesNotExist:
_LOGGER.error('LDAP user with username %s does not exist', username)
_LOGGER.error("LDAP user with username %s does not exist", username)
else:
if ldapuser.username in ldapgroup.members:
ldapgroup.members.remove(ldapuser.username)
_LOGGER.info(
'removed LDAP user %s from LDAP group %s',
ldapuser.dn, ldapgroup.dn)
"removed LDAP user %s from LDAP group %s", ldapuser.dn, ldapgroup.dn
)
ldapgroup.save()
return True
retval["removed"] = True
else:
_LOGGER.info(
'LDAP user %s is not a member of LDAP group %s',
ldapuser.dn, ldapgroup.dn)
return False
"LDAP user %s is not a member of LDAP group %s",
ldapuser.dn,
ldapgroup.dn,
)
return retval
@shared_task
def delete_ldap_user(username):
@shared_task(autoretry_for=(DjangoDBUtilsError,), default_retry_delay=10)
def delete_ldap_user(username, *args, **kwargs):
"""
This task deletes the given user.
:param str username: the user name
:return: True if the user has been deleted, False otherwise
:rtype: boolean
:return: dictionary containing the username and a flag :py:const:`deleted`
that is set to :py:const:`True` if the user has been deleted and is set
to :py:const:`False` otherwise
:rtype: dict
.. note::
This variant can only be used at the beginning of a Celery task chain
or as a standalone task.
Use :py:func:`ldaptasks.tasks.delete_ldap_user_chained` at other
positions in the task chain
"""
retval = {"username": username, "deleted": False}
try:
ldapuser = LdapUser.objects.get(username=username)
except LdapUser.DoesNotExist:
_LOGGER.info('there is no LDAP user with username %s', username)
_LOGGER.info("there is no LDAP user with username %s", username)
else:
try:
ldapgroup = LdapGroup.objects.get(gid=ldapuser.group)
except LdapGroup.DoesNotExist:
_LOGGER.info(
'LDAP group %s of LDAP user %s does not exist',
ldapuser.group, ldapuser.dn)
"LDAP group %s of LDAP user %s does not exist",
ldapuser.group,
ldapuser.dn,
)
else:
if ldapuser.username in ldapgroup.members:
ldapgroup.members.remove(ldapuser.username)
ldapgroup.save()
_LOGGER.info(
'removed LDAP user %s from LDAP group %s',
ldapuser.dn, ldapgroup.dn)
"removed LDAP user %s from LDAP group %s", ldapuser.dn, ldapgroup.dn
)
userdn = ldapuser.dn
ldapuser.delete()
_LOGGER.info('deleted LDAP user %s', userdn)
return True
return False
_LOGGER.info("deleted LDAP user %s", userdn)
retval["deleted"] = True
return retval
@shared_task
@shared_task(autoretry_for=(DjangoDBUtilsError,), default_retry_delay=10)
def delete_ldap_user_chained(previous_result, *args, **kwargs):
"""
This task deletes the given user.
:param dict previous_result: a dictionary describing the result of the
previous step in the Celery task chain. This dictionary must contain a
:py:const:`username` key
:return: a copy of the :py:obj:`previous_result` dictionary with a new
:py:const:`deleted` key set to :py:const:`True` if the user has been
deleted and set to :py:const:`False` otherwise
:rtype: dict
"""
username = previous_result["username"]
retval = deepcopy(previous_result)
retval.update(delete_ldap_user(username))
return retval
@shared_task(autoretry_for=(DjangoDBUtilsError,), default_retry_delay=10)
def delete_ldap_group_if_empty(groupname):
"""
This task deletes the given group.
This task deletes the given group if it is empty.
:param str groupname: the group name
:return: True if the user has been deleted, False otherwise
:rtype: boolean
:return: dictionary that contains the groupname and a flag
:py:const:`deleted` that is set to :py:const:`True` if the group has
been deleted and is set to :py:const:`False` otherwise
:rtype: dict
"""
retval = {"groupname": groupname, "deleted": False}
try:
ldapgroup = LdapGroup.objects.get(name=groupname)
except LdapGroup.DoesNotExist:
_LOGGER.info('LDAP group with groupname %s does not exist', groupname)
_LOGGER.info("LDAP group with groupname %s does not exist", groupname)
else:
if len(ldapgroup.members) == 0:
groupdn = ldapgroup.dn
ldapgroup.delete()
_LOGGER.info(
'deleted LDAP group %s', groupdn)
return True
_LOGGER.info("deleted LDAP group %s", groupdn)
retval["deleted"] = True
else:
_LOGGER.info(
'LDAP group %s has not been deleted. It still has %d members',
ldapgroup.dn, len(ldapgroup.members))
return False
"LDAP group %s has not been deleted. It still has %d members",
ldapgroup.dn,
len(ldapgroup.members),
)
return retval
@shared_task
@shared_task(autoretry_for=(DjangoDBUtilsError,), default_retry_delay=10)
def delete_ldap_group(groupname):
"""
This taks deletes the given group.
This task deletes the given group.
:param str groupname: the group name
:return: True if the user has been deleted, False otherwise
:rtype: boolean
:return: dictionary that contains the groupname and a flag
:py:const:`deleted` that is set to :py:const:`True` if the group has
been deleted and is set to :py:const:`False` otherwise
:rtype: dict
"""
retval = {"groupname": groupname, "deleted": False}
try:
ldapgroup = LdapGroup.objects.get(name=groupname)
except LdapGroup.DoesNotExist:
_LOGGER.info('LDAP group with name %s does not exist', groupname)
_LOGGER.info("LDAP group with name %s does not exist", groupname)
else:
groupdn = ldapgroup.dn
ldapgroup.delete()
_LOGGER.info('deleted LDAP group %s', groupdn)
return True
return False
_LOGGER.info("deleted LDAP group %s", groupdn)
retval["deleted"] = True
return retval

View file

View file

@ -0,0 +1,264 @@
"""
This module provides tests for :py:mod:`ldaptasks.tasks`.
"""
import volatildap
from celery.exceptions import Reject
from django.conf import settings
from django.test import TestCase
from ldapentities.models import LdapUser
from ldaptasks.tasks import (
add_ldap_user_to_group,
create_ldap_group,
create_ldap_user,
delete_ldap_group,
delete_ldap_group_if_empty,
delete_ldap_user,
delete_ldap_user_chained,
remove_ldap_user_from_group,
set_ldap_user_password,
)
class LdapTaskTestCase(TestCase):
databases = ["default", "ldap"]
directory = {
settings.DATABASES['ldap']['USER']: {
'objectClass': ['person'],
'userPassword': [settings.DATABASES['ldap']['PASSWORD']],
'sn': 'Admin',
},
settings.GROUP_BASE_DN: {
'objectClass': ['top', 'organizationalUnit'],
'ou': ['groups']
},
settings.USER_BASE_DN: {
'objectClass': ['top', 'organizationalUnit'],
'ou': ['users']
},
'cn=existing,' + settings.GROUP_BASE_DN: {
'objectClass': ['posixGroup'],
'gidNumber': ['4711'],
'cn': ['existing'],
'description': ['existing test group'],
'memberUid': ['existing'],
},
'uid=existing,' + settings.USER_BASE_DN: {
'objectClass': ['account', 'posixAccount'],
'uidNumber': ['815'],
'gidNumber': ['4711'],
'gecos': ['existing test user'],
'homeDirectory': ['/home/existing'],
'loginShell': ['/bin/bash'],
'uid': ['existing'],
'userPassword': ['secret'],
'cn': ['existing']
}
}
@classmethod
def setUpClass(cls):
super(LdapTaskTestCase, cls).setUpClass()
cls.ldap_server = volatildap.LdapServer(
initial_data=cls.directory,
schemas=['core.schema', 'cosine.schema', 'inetorgperson.schema',
'nis.schema'],
)
settings.DATABASES['ldap']['USER'] = cls.ldap_server.rootdn
settings.DATABASES['ldap']['PASSWORD'] = cls.ldap_server.rootpw
settings.DATABASES['ldap']['NAME'] = cls.ldap_server.uri
@classmethod
def tearDownClass(cls):
cls.ldap_server.stop()
super(LdapTaskTestCase, cls).tearDownClass()
def setUp(self):
self.ldap_server.start()
def test_create_ldap_group(self):
result = create_ldap_group('test', 5000, 'test group')
self.assertEqual({
'groupname': 'test', 'gid': 5000, 'description': 'test group',
'group_dn': 'cn=test,%s' % settings.GROUP_BASE_DN
}, result)
def test_create_ldap_group_existing(self):
result = create_ldap_group('existing', 4711, 'existing test group')
self.assertEqual({
'groupname': 'existing', 'gid': 4711,
'description': 'existing test group',
'group_dn': 'cn=existing,%s' % settings.GROUP_BASE_DN
}, result)
def test_create_ldap_group_existing_modify(self):
result = create_ldap_group(
'existing', 4711, 'change existing test group')
self.assertEqual({
'groupname': 'existing', 'gid': 4711,
'description': 'change existing test group',
'group_dn': 'cn=existing,%s' % settings.GROUP_BASE_DN
}, result)
def test_create_ldap_user(self):
result = create_ldap_user(
'test', 5000, 4711, 'Test User', '/home/test', '/bin/bash',
'secret')
self.assertEqual({
'username': 'test', 'uid': 5000, 'gid': 4711, 'gecos': 'Test User',
'homedir': '/home/test', 'shell': '/bin/bash',
'user_dn': 'uid=test,%s' % settings.USER_BASE_DN
}, result)
def test_create_ldap_user_invalid_group(self):
with self.assertRaises(Reject):
create_ldap_user(
'test', 5000, 5000, 'Test User', '/home/test', '/bin/bash',
'secret')
def test_create_ldap_user_no_password(self):
result = create_ldap_user(
'test', 5000, 4711, 'Test User', '/home/test', '/bin/bash',
None)
self.assertEqual({
'username': 'test', 'uid': 5000, 'gid': 4711, 'gecos': 'Test User',
'homedir': '/home/test', 'shell': '/bin/bash',
'user_dn': 'uid=test,%s' % settings.USER_BASE_DN
}, result)
def test_create_ldap_user_existing(self):
result = create_ldap_user(
'existing', 815, 4711, 'existing test user', '/home/existing',
'/bin/bash', 'secret'
)
self.assertEqual({
'username': 'existing', 'uid': 815, 'gid': 4711,
'gecos': 'existing test user', 'homedir': '/home/existing',
'shell': '/bin/bash',
'user_dn': u'uid=existing,%s' % settings.USER_BASE_DN
}, result)
def test_set_ldap_user_password_existing(self):
result = set_ldap_user_password('existing', 'newpassword')
self.assertEqual({
'username': 'existing', 'password_set': True
}, result)
def test_set_ldap_user_password_missing(self):
result = set_ldap_user_password('missing', 'newpassword')
self.assertEqual({
'username': 'missing', 'password_set': False
}, result)
def test_add_ldap_user_to_group_existing(self):
result = add_ldap_user_to_group('existing', 'existing')
self.assertEqual({
'username': 'existing', 'groupname': 'existing', 'added': True
}, result)
def test_add_ldap_user_to_group_new_user(self):
create_ldap_group('test', 5000, 'test group')
result = add_ldap_user_to_group('existing', 'test')
self.assertEqual({
'username': 'existing', 'groupname': 'test', 'added': True
}, result)
def test_add_ldap_user_to_group_no_group(self):
result = add_ldap_user_to_group('existing', 'test')
self.assertEqual({
'username': 'existing', 'groupname': 'test', 'added': False
}, result)
def test_add_ldap_user_to_group_no_user(self):
with self.assertRaises(LdapUser.DoesNotExist):
add_ldap_user_to_group('test', 'existing')
def test_remove_ldap_user_from_group_existing(self):
result = remove_ldap_user_from_group('existing', 'existing')
self.assertEqual({
'username': 'existing', 'groupname': 'existing', 'removed': True
}, result)
self.assertNotIn('memberUid', self.ldap_server.get(
'cn=existing,' + settings.GROUP_BASE_DN))
def test_remove_ldap_user_from_group_not_in_group(self):
create_ldap_group('test', 5000, 'test group')
result = remove_ldap_user_from_group('existing', 'test')
self.assertEqual({
'username': 'existing', 'groupname': 'test', 'removed': False
}, result)
def test_remove_ldap_user_from_group_no_group(self):
result = remove_ldap_user_from_group('existing', 'test')
self.assertEqual({
'username': 'existing', 'groupname': 'test', 'removed': False
}, result)
def test_remove_ldap_user_from_group_no_user(self):
result = remove_ldap_user_from_group('test', 'existing')
self.assertEqual({
'username': 'test', 'groupname': 'existing', 'removed': False
}, result)
def test_delete_ldap_user_existing(self):
result = delete_ldap_user('existing')
self.assertEqual({'username': 'existing', 'deleted': True}, result)
with self.assertRaises(KeyError):
self.ldap_server.get('uid=existing,' + settings.USER_BASE_DN)
self.assertNotIn('memberUid', self.ldap_server.get(
'cn=existing,' + settings.GROUP_BASE_DN))
def test_delete_ldap_user_missing(self):
result = delete_ldap_user('missing')
self.assertEqual({'username': 'missing', 'deleted': False}, result)
def test_delete_ldap_user_no_group(self):
self.ldap_server.get('uid=existing,' + settings.USER_BASE_DN)[
'gidNumber'] = '5000'
result = delete_ldap_user('existing')
self.assertEqual({'username': 'existing', 'deleted': True}, result)
with self.assertRaises(KeyError):
self.ldap_server.get('uid=existing,' + settings.USER_BASE_DN)
def test_delete_ldap_user_chained_exsting(self):
result = delete_ldap_user_chained({'username': 'existing'})
self.assertEqual({'username': 'existing', 'deleted': True}, result)
with self.assertRaises(KeyError):
self.ldap_server.get('uid=existing,' + settings.USER_BASE_DN)
group_object = self.ldap_server.get('cn=existing,' + settings.GROUP_BASE_DN)
self.assertNotIn('memberUid', group_object)
def test_delete_ldap_group_if_empty_nonempty(self):
result = delete_ldap_group_if_empty('existing')
self.assertEqual({'groupname': 'existing', 'deleted': False}, result)
ldap_object = self.ldap_server.get('cn=existing,' + settings.GROUP_BASE_DN)
self.assertIsNotNone(ldap_object)
def test_delete_ldap_group_if_empty_missing(self):
result = delete_ldap_group_if_empty('missing')
self.assertEqual({'groupname': 'missing', 'deleted': False}, result)
def test_delete_ldap_group_if_empty_empty(self):
self.ldap_server.add({'cn=emptygroup,' + settings.GROUP_BASE_DN: {
'objectClass': ['posixGroup'],
'gidNumber': ['4712'],
'cn': ['existing'],
'description': ['existing test group'],
}})
result = delete_ldap_group_if_empty('emptygroup')
self.assertEqual({'groupname': 'emptygroup', 'deleted': True}, result)
with self.assertRaises(KeyError):
self.ldap_server.get('cn=emptygroup,' + settings.GROUP_BASE_DN)
def test_delete_ldap_group_existing(self):
result = delete_ldap_group('existing')
self.assertEqual({'groupname': 'existing', 'deleted': True}, result)
with self.assertRaises(KeyError):
self.ldap_server.get('cn=existing,' + settings.GROUP_BASE_DN)
def test_delete_ldap_group_missing(self):
result = delete_ldap_group('missing')
self.assertEqual({'groupname': 'missing', 'deleted': False}, result)

View file

@ -3,7 +3,7 @@ import os
import sys
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "gvaldap.settings.local")
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "gvaldap.settings")
from django.core.management import execute_from_command_line

25
gvaldap/setup.cfg Normal file
View file

@ -0,0 +1,25 @@
[pep8]
exclude = migrations
[flake8]
exclude = migrations
[coverage:run]
source = gvaldap,ldapentities,ldaptasks
branch = True
omit = */migrations/*,*/tests/*.py,*/tests.py,gvaldap.py
relative_files = True
[coverage:report]
show_missing = True
[coverage:html]
directory = ../coverage-report
[isort]
multi_line_output = 3
line_length = 88
known_django = django
known_third_party = celery,volatildap
include_trailing_comma = True
sections = FUTURE,STDLIB,THIRDPARTY,DJANGO,FIRSTPARTY,LOCALFOLDER

View file

@ -1,3 +0,0 @@
# This file is here because many Platforms as a Service look for
# requirements.txt in the root directory of a project.
-r requirements/production.txt

View file

@ -1,9 +0,0 @@
Django==1.7.4
django-ldapdb==0.3.2
bpython==0.13.2
django-braces==1.4.0
django-model-utils==2.2
logutils==0.3.3
celery==3.1.17
passlib==1.6.2
requests==2.5.1

View file

@ -1,8 +0,0 @@
# Local development dependencies go here
-r base.txt
coverage==3.7.1
django-debug-toolbar==1.2.2
Sphinx==1.2.3
sqlparse==0.1.14
releases==0.7.0
Pygments==2.0.2

View file

@ -1,3 +0,0 @@
# Pro-tip: Try not to put anything here. There should be no dependency in
# production that isn't in development.
-r base.txt

View file

@ -1,3 +0,0 @@
# Test dependencies go here.
-r base.txt
coverage==3.7.1

36
salt/bootstrap.sh Executable file
View file

@ -0,0 +1,36 @@
#!/bin/sh -
apt-get update
apt-get install -y python-cryptography
# We just download the bootstrap script by default and execute that.
if [ -x /usr/bin/fetch ]; then
/usr/bin/fetch -o - https://raw.githubusercontent.com/saltstack/salt-bootstrap/stable/bootstrap-salt.sh | sh -s -- "$@"
elif [ -x /usr/bin/curl ]; then
/usr/bin/curl -L https://raw.githubusercontent.com/saltstack/salt-bootstrap/stable/bootstrap-salt.sh | sh -s -- "$@"
else
python \
-c 'import urllib; print urllib.urlopen("https://raw.githubusercontent.com/saltstack/salt-bootstrap/stable/bootstrap-salt.sh").read()' \
| sh -s -- "$@"
fi
cat >/etc/salt/minion <<EOF
file_client: local
file_roots:
base:
- /srv/salt/
pillar_roots:
base:
- /srv/pillar
log_file: file:///dev/log
EOF
umask 077
cat >/etc/salt/grains <<EOF
roles:
- ldapserver
- gnuviechadmin.gvaldap
EOF