Skip to content

Instantly share code, notes, and snippets.

@tnightingale
Last active November 7, 2017 19:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tnightingale/bbad5b0844d66d99e68a8870533cc28b to your computer and use it in GitHub Desktop.
Save tnightingale/bbad5b0844d66d99e68a8870533cc28b to your computer and use it in GitHub Desktop.

LDAP plugin for Nadine (312Main)

Requirements

  1. Users may use their username or any of their registered email addresses as an account identifier (UID) when logging in.
  2. Users may use the same credentials to log into Nadine (Django) or LDAP / LDAP-supporting systems (WiFi, shared network drives, etc)
  3. Needs to be an optional plugin for Nadine (only some deployments will make use of it).

Proposal

  • We can use django-auth-ldap for auth in Nadine.
  • We use django-ldapdb to create new user accounts in LDAP and propagate user account updates back to LDAP.

Limitations

With the proposed tools we can meet requirements (1) and (2) for Nadine/Django and it will set up the LDAP infrastructure for future integrations with other systems.

However meeting requirement (2) for non-Django/Nadine systems (WiFi, shared network drives, etc) is going to be dependant on what is supported by the specific system. Short of researching how other systems auth against LDAP, the best we can do is ensure our LDAP schema to follow common standards/conventions.

Damien is confident that if we stick to standard LDAP account & group objectClass (eg: inetOrgPerson & posixAccount) then we should be compatible with anything else that auths against LDAP.

Implementation

Setting up Nadine/Django LDAP auth is pretty straightforward, mostly configuration; need to provide a query to lookup the LDAP user by email and/or username/uid.

Handling new user registration & user management (updates) in Nadine and writing to LDAP will require hooking into Django/Nadine's user registration process.

Implementation details and example configuration for the various systems below:

LDAP

User accounts are inetOrgPerson|posixAccount objectClass. These classes meet the minimum required attributes for our accounts (username, password, multiple email addresses). Usernames and email addresses are enforced to be unique by default. They also benefit (according to Damien) from being standardized auth targets for other external systems.

Groups are implemented with posixGroup objectClass. This decision is fairly arbitrary at this point (there's a whole range of group class types available) but it is consistent with the decision posixAccount class for user accounts.

Example LDAP Schema
dc=312main,dc=ca
o=312 Main
objectClass=top
objectClass=dcObject
objectClass=organization

  cn=admin
  objectClass=simpleSecurityObject
  objectClass=organizationalRole

  ou=groups
  objectClass=organizationalUnit
  objectClass=top

    cn=members
    objectClass=posixGroup
    objectClass=top

    cn=users
    objectClass=posixGroup
    objectClass=top

  ou=users
  objectClass=organizationalUnit
  objectClass=top

    cn=<username>
    mail=<email1>
    mail=<email2>
    uid=<username>
    uidNumber={LDAP generated}
    objectClass=inetOrgPerson
    objectClass=posixAccount
    objectClass=top
django-auth-ldap config
AUTH_LDAP_BIND_DN = "cn=admin,dc=312main,dc=ca"
AUTH_LDAP_BIND_PASSWORD = "..."

# Search query for a user.
AUTH_LDAP_USER_SEARCH = LDAPSearch(
    # Look under 'users' organizational unit (ou)
    "ou=users,dc=312main,dc=ca",
    ldap.SCOPE_SUBTREE,
    # Match against the uid (alias: 'User Name') attribute
    # "(uid=%(user)s)"
    # TODO: Need to match against mail (alias: 'Email') attribute, this can
    #       contain multiple values in LDAP. The below check will end up
    #       creating a Django user for each value which is not what we want.
    #       Ideally we would be able to resolve the returned LDAP object to an
    #       existing field via a different (unique) attribute such as uid.
    # UPDATE:
    #       This can be done with a custom LDAPBackend that overrides
    #       get_or_create_user() (https://django-auth-ldap.readthedocs.io/en/1.2.x/reference.html#django_auth_ldap.backend.LDAPBackend.get_or_create_user).
    "(mail=%(user)s)"
)

# Specify the class to use when handling LDAP groups in Django
AUTH_LDAP_GROUP_TYPE = PosixGroupType(name_attr="cn")

# Search query to return all groups used by django-ldap-auth.
AUTH_LDAP_GROUP_SEARCH = LDAPSearch(
    "ou=groups,dc=312main,dc=ca",
    ldap.SCOPE_SUBTREE,
    # Grab everything that is a 'objectClass=posixGroup'
    "(objectClass=posixGroup)"
)

# Set arbitrary properties on Django User based on LDAP attributes & queries
AUTH_LDAP_USER_FLAGS_BY_GROUP = {
    "is_active": "cn=users,ou=groups,dc=tnightingale,dc=com",
    "is_staff": LDAPGroupQuery("cn=users,ou=groups,dc=tnightingale,dc=com")
}
django-ldapdb

We need to hook into the following events in Nadine:

  • new user creation
  • user password reset (user self-resetting or admin doing it)
  • User permission changes that affect ability to login (i.e. inclusion in or removal from members group, user disabled by admin, something like that).
  • (optional) update of user profile fields (if we save those in LDAP too... prob isn't much extra work)

For requirement (3) this integration must be implmenented in a pluggable way so as to make it optional per-deployment. Adding Django signals/events to relavent actions in Nadine will likely be sufficient.

Appendix:

Django + LDAP integration options

Unfortunately the Django + LDAP integration story is fragmented. I suspect this is due to LDAP's amorphous / "anything to anyone" nature.

Below are the main contenders that I have found appropriate for our integration:

Opting for django-auth-ldap due to more features & flexible configuration

This is the Django plugin Jacob tested

  • Django auth provider that talks to LDAP
  • Django users are created on-demand
  • Django users are updated from LDAP on each login
  • Provides command / cron task ldap_sync_users as alternative method to keep Django users up to date
  • Doesn't write to LDAP

Docs: https://django-auth-ldap.readthedocs.io/en/1.2.x/

  • Similar to django-python3-ldap but supports more elaborate methods for querying/syncing data from LDAP
  • Supports using LDAP groups to drive Django user access & permissions
  • Doesn't write to LDAP
  • Provides Django ORM database adapter for LDAP
  • Provides ability to create / query / update / delete LDAP objects via Django's ORM API
  • Does not include a Django auth provider
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment