#django-adldap-sync
Overhaul of django-ldap-sync.
django-adldap-sync provides a Django management command that synchronizes LDAP
users, groups and memberships from an Active Directory server.
This synchronization is performed each time the management command is run and
can be fired manually on demand, via an automatic cron script or as a periodic
Celery_ task.
Features
- 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
- Tested against Active Directory on Windows Server 2008R2. Must work on any other AD, and maybe another generic LDAP server.
- One way read-only sync from Active Directory/LDAP to Django, with AD/LDAP server failover.
- Synchronizes Users, Groups and Group Memberships. AD Primary Group is added via a manual config in settings.py.
- Allows incremental synchronization, based on the last timestamp. This reduces the sync time to the minimum.
- Synchronizes AD User Data to both User Model and User Profiles (without external callbacks).
- Synchronizes thumbnailPhoto or jpegPhoto to an ImageField.
Installation
pip install django_adldap_sync
You must add the app configuration on your settings.py
file (at least the minimal config as below).
Then update your database:
python manage.py makemigrations adldap_sync
python manage.py migrate
It should create a new table called adldap_sync
. It keeps track of the last time the system was sync'ed
Minimal config on settings.py
Be sure that USE_TZ = True
. Incremental Sync uses TimeZone
On settings.py
add this at the end of the file, and configure the values:
INSTALLED_APPS.append('adldap_sync');
LDAP_SYNC_BIND_URI = ["ldap://dc1.example.com:389","ldap://dc2.example.com:389",]
LDAP_SYNC_BIND_SEARCH = "DC=example,DC=com"
LDAP_SYNC_BIND_DN = "CN=Django,OU=Users,DC=example,DC=com"
LDAP_SYNC_BIND_PASS = "MyPassword"
With that you have a Synchronization from AD to Django (Users, Groups and Memberships), with a Full import each 5 incrementals.
Minimal config with an User Profile
Add this to the previous settings.py
LDAP_SYNC_USER_EXTRA_ATTRIBUTES = ['userAccountControl','company','department','distinguishedName','division',\
'extensionName','manager','mobile','physicalDeliveryOfficename','title','thumbnailPhoto']
LDAP_SYNC_USER_EXTRA_PROFILES = [adldap_sync.Employee]
LDAP_SYNC_USER_CHANGE_FIELDCASE = "lower"
LDAP_SYNC_USER_THUMBNAILPHOTO_NAME = "{username}_{uuid4}.jpg"
Change the values to the one you need. On models.py
and admin.py
there are samples of a working User Profile.
You can add the AD Fields you need. By default AD fields are camelCase, it's preferred to lowercase them to fit
Django best practices.
The model MUST use the same names as the AD fields, only lowercased (if LDAP_SYNC_USER_CHANGE_FIELDCASE = "lower"
is used).
Manual Sync (and cron.d
scheduling)
python manage.py syncldap
To force a full search:
python manage.py syncldap full
To force an incremental search:
python manage.py syncldap incremental
The first synchronization will always be FULL
Scheduled Sync on settings.py
from datetime import timedelta
LDAP_SYNC_INCREMENTAL_BETWEEN_FULL = 120
CELERYBEAT_SCHEDULE = {
'synchronize_local_users': {
'task': 'adldap_sync.tasks.syncldap',
'schedule': timedelta(minutes=60),
}
}
Full config settings
LDAP_SYNC_BIND_URI = []
LDAP_SYNC_BIND_DN = ''
LDAP_SYNC_BIND_PASS = ''
LDAP_SYNC_BIND_SEARCH = ''
LDAP_SYNC_BIND_PAGESIZE = 200
LDAP_SYNC_USER = True
LDAP_SYNC_USER_INCREMENTAL = True
LDAP_SYNC_USER_SEARCH = ''
LDAP_SYNC_USER_FILTER = '(&(objectCategory=person)(objectClass=user))'
LDAP_SYNC_USER_FILTER_INCREMENTAL = '(&(objectCategory=person)(objectClass=user)(whenchanged>=?))'
LDAP_SYNC_USER_ATTRIBUTES = {
"sAMAccountName": "username",
"givenName": "first_name",
"sn":"last_name",
"mail": "email",
}
LDAP_SYNC_USER_EXTRA_ATTRIBUTES = []
LDAP_SYNC_USER_EXTRA_PROFILES = []
LDAP_SYNC_USER_EXEMPT_FROM_SYNC = ['admin','administrator','guest']
LDAP_SYNC_USER_CALLBACKS = []
LDAP_SYNC_USER_SET_UNUSABLE_PASSWORD = True
LDAP_SYNC_USER_SHOW_PROGRESS = True
LDAP_SYNC_USER_THUMBNAILPHOTO_NAME = "{username}_{uuid4}.jpg"
LDAP_SYNC_USER_CHANGE_FIELDCASE = "lower"
LDAP_SYNC_MULTIVALUE_SEPARATOR = "|"
LDAP_SYNC_USERNAME_FIELD = None
LDAP_SYNC_REMOVED_USER_CALLBACKS = ['adldap_sync.callbacks.removed_user_deactivate']
LDAP_SYNC_GROUP = True
LDAP_SYNC_GROUP_INCREMENTAL = True
LDAP_SYNC_GROUP_SEARCH = ''
LDAP_SYNC_GROUP_FILTER = '(objectClass=group)'
LDAP_SYNC_GROUP_FILTER_INCREMENTAL = '(&(objectClass=group)(whenchanged>=?))'
LDAP_SYNC_GROUP_ATTRIBUTES = { "cn": "name"}
LDAP_SYNC_GROUP_MEMBERSHIP = True
LDAP_SYNC_GROUP_MEMBERSHIP_DN_FIELD = 'distinguishedName'
LDAP_SYNC_GROUP_MEMBERSHIP_FILTER = '(member:1.2.840.113556.1.4.1941:={distinguishedName})'
LDAP_SYNC_GROUP_MEMBERSHIP_CREATE_IF_NOT_EXISTS = True
LDAP_SYNC_GROUP_MEMBERSHIP_ADD_DEFAULT = []
LDAP_SYNC_INCREMENTAL_BETWEEN_FULL = 5
LDAP_SYNC_INCREMENTAL_TIME_OFFSET = 10
LDAP_SYNC_INCREMENTAL_TIMESTAMPFORMAT = "%Y%m%d%H%M%S.0Z"
Dependencies
pyldap
TODO
- Documentation::
- Create PIP Installer::
- LDAPs ::
- Unit Tests ::
FAQ
- Why Full Sync is so slow?:
Because of Group Memberships, without memberships you only need 2 LDAP queries, but with memberships the system makes 2+N queries,
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).
- Why the module needs a table on database?:
To keep track of whenChanged, the timestamp needed to do an incremental synchronization
I'm not a Python guy, I tried to keep PEP8 and Django guidelines.
The main exception are in User Profile. Profile fields must match the ones in AD (only lowercased), so words are not separated with _
I know, but all the configurable settings are there, with their default values.
- Can I have more than 1 User Profile?:
Yes, you can sync it either directly (adding it to the LDAP_SYNC_USER_EXTRA_PROFILES list) or via callbacks.
LDAP_SYNC_USER_CALLBACKS is more flexible as you may have any field name, and do extra checking.
But for me the EXTRA PROFILES option works ok, and is way easier.
- Can I add more than 1 Group Membership in LDAP_SYNC_GROUP_MEMBERSHIP_ADD_DEFAULT?:
Yes. The main use is to overcome the AD Primary Group limitation (it's weird to retrieve), but can be used to add more groups.
- I don't see the thumbnailPhoto on my system:
Maybe you don't have correctly configured the media or static folder, see Django Managing static files
Then on templates you can use {{ request.user.employee.thumbnailphoto.url }}
to link it