{ "info": { "author": "Marchete", "author_email": "undisclosed@gmail.com", "bugtrack_url": null, "classifiers": [ "Development Status :: 3 - Alpha", "Environment :: Web Environment", "Framework :: Django", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python", "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: System :: Systems Administration :: Authentication/Directory" ], "description": "#django-adldap-sync\n\n\nOverhaul of [django-ldap-sync].\n\ndjango-adldap-sync provides a Django management command that synchronizes LDAP\nusers, groups and memberships from an Active Directory server. \n\nThis synchronization is performed each time the management command is run and\ncan be fired manually on demand, via an automatic cron script or as a periodic\n[Celery]_ task.\n\n### Features\n - Works on Python 3.5 and Django 1.10, tested both on Windows 10 and Centos 7.2. Untested on other Python versions or Django\n - Tested against Active Directory on Windows Server 2008R2. Must work on any other AD, and maybe another generic LDAP server.\n - One way read-only sync from Active Directory/LDAP to Django, with AD/LDAP server failover.\n - Synchronizes Users, Groups and Group Memberships. AD Primary Group is added via a manual config in settings.py.\n - Allows incremental synchronization, based on the last timestamp. This reduces the sync time to the minimum.\n - Synchronizes AD User Data to both User Model and User Profiles (without external callbacks).\n - Synchronizes thumbnailPhoto or jpegPhoto to an ImageField.\n\n### Installation\n```sh\npip install django_adldap_sync\n```\nYou must add the app configuration on your `settings.py` file (at least the minimal config as below). \nThen update your database:\n```sh\npython manage.py makemigrations adldap_sync\npython manage.py migrate\n```\nIt should create a new table called `adldap_sync`. It keeps track of the last time the system was sync'ed\n\n### Minimal config on `settings.py` \nBe sure that `USE_TZ = True` . Incremental Sync uses TimeZone\n\nOn `settings.py` add this at the end of the file, and configure the values:\n```python\nINSTALLED_APPS.append('adldap_sync');\nLDAP_SYNC_BIND_URI = [\"ldap://dc1.example.com:389\",\"ldap://dc2.example.com:389\",] \n#You need at least 1, but is open to additional failovers. Incremental syncs are bound to the server URI\n#This is because the whenChanged attribute on AD is server-based, it doesn't replicate on others.\nLDAP_SYNC_BIND_SEARCH = \"DC=example,DC=com\"\nLDAP_SYNC_BIND_DN = \"CN=Django,OU=Users,DC=example,DC=com\" #User's distinguishedName to sync data.\nLDAP_SYNC_BIND_PASS = \"MyPassword\"\n#Important note about the User!! Please don't use a Domain Admin here. Just create a limited AD User \n# and add delegation rights to read group/user info!!!! Using a Domain Admin to Sync data is a terrible\n# bad practice, you are warned.\n``` \n\nWith that you have a Synchronization from AD to Django (Users, Groups and Memberships), with a Full import each 5 incrementals.\n\n### Minimal config with an User Profile \nAdd this to the previous `settings.py`\n```python\nLDAP_SYNC_USER_EXTRA_ATTRIBUTES = ['userAccountControl','company','department','distinguishedName','division',\\\n 'extensionName','manager','mobile','physicalDeliveryOfficename','title','thumbnailPhoto']\n#Or the ones you need from the AD, and create a model accordly\nLDAP_SYNC_USER_EXTRA_PROFILES = [adldap_sync.Employee] # appname.modelname, like adldap_sync.Employee\nLDAP_SYNC_USER_CHANGE_FIELDCASE = \"lower\" #None,\"lower\",\"upper\"\nLDAP_SYNC_USER_THUMBNAILPHOTO_NAME = \"{username}_{uuid4}.jpg\" \n #It allows the parameters {username}, {uuid4} and datetime.strftime\n```\nChange the values to the one you need. On `models.py` and `admin.py` there are samples of a working User Profile.\nYou can add the AD Fields you need. By default AD fields are camelCase, it's preferred to lowercase them to fit\nDjango best practices.\nThe model MUST use the same names as the AD fields, only lowercased (if `LDAP_SYNC_USER_CHANGE_FIELDCASE = \"lower\"`\nis used).\n\n\n### Manual Sync (and `cron.d` scheduling)\n\n```sh\npython manage.py syncldap\n```\nTo force a full search:\n```sh\npython manage.py syncldap full\n```\nTo force an incremental search:\n```sh\npython manage.py syncldap incremental\n```\nThe first synchronization will always be FULL\n\n### Scheduled Sync on `settings.py`\n```python\nfrom datetime import timedelta\n #One full sync each 5 days: 1sync/hour x 24 hours x 5 days = 120 syncs\n LDAP_SYNC_INCREMENTAL_BETWEEN_FULL = 120\n CELERYBEAT_SCHEDULE = {\n 'synchronize_local_users': {\n 'task': 'adldap_sync.tasks.syncldap',\n 'schedule': timedelta(minutes=60),\n }\n }\n``` \n### Full config settings\n```python\n LDAP_SYNC_BIND_URI = [] \n #A string or an array for failover, i.e. [\"ldap://dc1.example.com:389\",\"ldap://dc2.example.com:389\",]\n LDAP_SYNC_BIND_DN = '' #AD User to search. DON'T USE AN ADMIN ACCOUNT!!!!!\n LDAP_SYNC_BIND_PASS = '' #The ldap user password\n LDAP_SYNC_BIND_SEARCH = '' #I.e. \"OU=Department,DC=example,DC=com\"\n LDAP_SYNC_BIND_PAGESIZE = 200 #Used on PagedResultsSearchObject, for paging LDAP queries\n\n #USERS\n LDAP_SYNC_USER = True #With False it will NOT Sync either users or group memberships\n LDAP_SYNC_USER_INCREMENTAL = True #False to disable incremental sync\n LDAP_SYNC_USER_SEARCH = '' \n #I.e. \"OU=Department,DC=example,DC=com\" If you don't setup any, LDAP_SYNC_BIND_SEARCH is used. \n LDAP_SYNC_USER_FILTER = '(&(objectCategory=person)(objectClass=user))'\n LDAP_SYNC_USER_FILTER_INCREMENTAL = '(&(objectCategory=person)(objectClass=user)(whenchanged>=?))'\n # The ? is replaced by the whenChanged datetime, in UTC format\n LDAP_SYNC_USER_ATTRIBUTES = {\n \"sAMAccountName\": \"username\",\n \"givenName\": \"first_name\",\n \"sn\":\"last_name\",\n \"mail\": \"email\",\n } \n # Default ones, leave it as it is\n LDAP_SYNC_USER_EXTRA_ATTRIBUTES = [] \n # ['userAccountControl','company','department','distinguishedName','division','extensionName',\\\n # 'manager','mobile','physicalDeliveryOfficename','title','thumbnailPhoto']\n LDAP_SYNC_USER_EXTRA_PROFILES = [] \n # appname.modelname, like adldap_sync.Employee, you have one example in models.py\n LDAP_SYNC_USER_EXEMPT_FROM_SYNC = ['admin','administrator','guest']\n #These users won't be created or synced\n LDAP_SYNC_USER_CALLBACKS = [] #You can manually populate your User Profiles via callbacks\n LDAP_SYNC_USER_SET_UNUSABLE_PASSWORD = True\n LDAP_SYNC_USER_SHOW_PROGRESS = True \n #It will show the user sync progress, useful on large AD setups to check the % progress\n LDAP_SYNC_USER_THUMBNAILPHOTO_NAME = \"{username}_{uuid4}.jpg\" \n #It allows the parameters {username}, {uuid4} and datetime.strftime\n LDAP_SYNC_USER_CHANGE_FIELDCASE = \"lower\" #None,\"lower\",\"upper\"\n LDAP_SYNC_MULTIVALUE_SEPARATOR = \"|\" \n #If an AD attribute is multivalued, it will be joined on one string as \"value1|value2|value3\"\n LDAP_SYNC_USERNAME_FIELD = None \n LDAP_SYNC_REMOVED_USER_CALLBACKS = ['adldap_sync.callbacks.removed_user_deactivate']\n #Also you can use 'adldap_sync.callbacks.removed_user_delete' to completely delete the Django user\n \n #GROUPS\n LDAP_SYNC_GROUP = True\n LDAP_SYNC_GROUP_INCREMENTAL = True\n LDAP_SYNC_GROUP_SEARCH = '' \n #I.e. \"OU=Department,DC=example,DC=com\" If you don't setup any, LDAP_SYNC_BIND_SEARCH is used. \n LDAP_SYNC_GROUP_FILTER = '(objectClass=group)'\n LDAP_SYNC_GROUP_FILTER_INCREMENTAL = '(&(objectClass=group)(whenchanged>=?))'\n LDAP_SYNC_GROUP_ATTRIBUTES = { \"cn\": \"name\"}\n\n #GROUP MEMBERSHIP\n LDAP_SYNC_GROUP_MEMBERSHIP = True\n LDAP_SYNC_GROUP_MEMBERSHIP_DN_FIELD = 'distinguishedName'\n LDAP_SYNC_GROUP_MEMBERSHIP_FILTER = '(member:1.2.840.113556.1.4.1941:={distinguishedName})' \n #Recursive group search on AD. If Group B is memberof Group A, and user is memberof Group B,\n # it will have membership on both Groups.\n LDAP_SYNC_GROUP_MEMBERSHIP_CREATE_IF_NOT_EXISTS = True\n #Create Groups if don't exist in Django. Useful if some of your group are out of search scope.\n LDAP_SYNC_GROUP_MEMBERSHIP_ADD_DEFAULT = [] \n # [('CN=Domain Users,CN=Users,DC=example,DC=com', {'cn': [b'Domain Users']}),]\n #IMPORTANT! AD behaves a bit weird with the Primary Group. There is no easy way to sync Primary \n # group so you will always have 1 group less than expected. So I manually add it to all users,\n # pretty awful but enough for me, and way easier than dealing with SIDs on AD\n \n #INCREMENTAL\n LDAP_SYNC_INCREMENTAL_BETWEEN_FULL = 5\n #Each N incrementals the command will try a Full sync. This is to avoid drifting of changes, \n # for any reason. It's a sanity check. With one each 7 days is enough, it depends on how often\n # you scheduled the incremental syncs.\n LDAP_SYNC_INCREMENTAL_TIME_OFFSET = 10\n #Incrementals are very sensitive to date and time, so to avoid clock skew problems I substract\n # 10 minutes to the datetime on query\n LDAP_SYNC_INCREMENTAL_TIMESTAMPFORMAT = \"%Y%m%d%H%M%S.0Z\"\n #AD time format, leave as it is.\n```\n\n \n### Dependencies\npyldap\n\n### TODO\n\n- Documentation::\n- Create PIP Installer::\n- LDAPs ::\n- Unit Tests ::\n\nFAQ\n----------\n- Why Full Sync is so slow?:\n\nBecause of Group Memberships, without memberships you only need 2 LDAP queries, but with memberships the system makes 2+N queries,\n where N is the number of users. I need a query per user to make a recursive group search (member of a subgroup of another group).\n\n- Why the module needs a table on database?:\n\nTo keep track of whenChanged, the timestamp needed to do an incremental synchronization\n\n- Weird coding style:\n\nI'm not a Python guy, I tried to keep PEP8 and Django guidelines. \nThe main exception are in User Profile. Profile fields must match the ones in AD (only lowercased), so words are not separated with _\n\n- Horrible Documentation:\n\nI know, but all the configurable settings are there, with their default values. \n\n- Can I have more than 1 User Profile?:\n\nYes, you can sync it either directly (adding it to the LDAP_SYNC_USER_EXTRA_PROFILES list) or via callbacks.\nLDAP_SYNC_USER_CALLBACKS is more flexible as you may have any field name, and do extra checking.\nBut for me the EXTRA PROFILES option works ok, and is way easier.\n\n- Can I add more than 1 Group Membership in LDAP_SYNC_GROUP_MEMBERSHIP_ADD_DEFAULT?:\n\nYes. The main use is to overcome the AD Primary Group limitation (it's weird to retrieve), but can be used to add more groups.\n\n- I don't see the thumbnailPhoto on my system:\n\nMaybe you don't have correctly configured the media or static folder, see [Django Managing static files]\nThen on templates you can use `{{ request.user.employee.thumbnailphoto.url }}` to link it\n\n[django-ldap-sync]: https://github.com/jbittel/django-ldap-sync\n[Celery]: http://www.celeryproject.org\n[Django Managing static files]: https://docs.djangoproject.com/es/1.10/howto/static-files/", "description_content_type": null, "docs_url": null, "download_url": "https://github.com/marchete/django-adldap-sync", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/marchete/django-adldap-sync", "keywords": "django,ldap,active directory,synchronize,sync", "license": "BSD", "maintainer": null, "maintainer_email": null, "name": "django-adldap-sync", "package_url": "https://pypi.org/project/django-adldap-sync/", "platform": "UNKNOWN", "project_url": "https://pypi.org/project/django-adldap-sync/", "project_urls": { "Download": "https://github.com/marchete/django-adldap-sync", "Homepage": "https://github.com/marchete/django-adldap-sync" }, "release_url": "https://pypi.org/project/django-adldap-sync/0.5.2/", "requires_dist": null, "requires_python": null, "summary": "A Django application for synchronizing LDAP users, groups and group memberships", "version": "0.5.2" }, "last_serial": 2339196, "releases": { "0.5.1": [ { "comment_text": "", "digests": { "md5": "bc8c725eff2209f51a3e0cd7f8fdee52", "sha256": "1965889c3bef694a44c0d62b9ce51d8053d4ea7492be3f983e4a67a464140224" }, "downloads": -1, "filename": "django-adldap-sync-0.5.1.zip", "has_sig": false, "md5_digest": "bc8c725eff2209f51a3e0cd7f8fdee52", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 40982, "upload_time": "2016-08-31T17:23:57", "url": "https://files.pythonhosted.org/packages/a0/c0/7ffbf80673dc7481222487966a176fab6af3d993a71a8034808337c18eed/django-adldap-sync-0.5.1.zip" } ], "0.5.2": [ { "comment_text": "", "digests": { "md5": "592af4e64e87e37a1469289e927b723c", "sha256": "37e1c4cfa08f2a5b7240c790953f34720f90021ec7ea21611e8ed25abbf01df6" }, "downloads": -1, "filename": "django-adldap-sync-0.5.2.zip", "has_sig": false, "md5_digest": "592af4e64e87e37a1469289e927b723c", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 41108, "upload_time": "2016-09-12T23:59:16", "url": "https://files.pythonhosted.org/packages/7d/1e/6d9b0bd8a0678d96903b6773076dabf801325308bbd239f6788be2ad711b/django-adldap-sync-0.5.2.zip" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "592af4e64e87e37a1469289e927b723c", "sha256": "37e1c4cfa08f2a5b7240c790953f34720f90021ec7ea21611e8ed25abbf01df6" }, "downloads": -1, "filename": "django-adldap-sync-0.5.2.zip", "has_sig": false, "md5_digest": "592af4e64e87e37a1469289e927b723c", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 41108, "upload_time": "2016-09-12T23:59:16", "url": "https://files.pythonhosted.org/packages/7d/1e/6d9b0bd8a0678d96903b6773076dabf801325308bbd239f6788be2ad711b/django-adldap-sync-0.5.2.zip" } ] }