Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Active Directory LDAP Auth support #697

Merged
merged 1 commit into from
Feb 10, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions nipap/nipap.conf.dist
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,40 @@ db_path = /etc/nipap/local_auth.db ; path to SQLite database used
#
#basedn = dc=test,dc=com ; base DN
#uri = ldaps://ldap.test.com ; LDAP server URI
#
# LDAP style
#binddn_fmt = uid={},dc=test,dc=com
#search = uid={} ; LDAP search filter
#
# Active Directory (UPN) style
#binddn_fmt = {}@test.com
#search = sAMAccountName={}
#
# {} in binddn_fmt is replaced by the username of the authenticating user.
#
## Group permissions
# Non-empty values for rw_group/ro_group requires the memberOf attribute to be
# present in LDAP.
#
# Examples:
#
# Everyone with an account can login and gets read/write access:
#rw_group =
#ro_group =
#
# Users in rw_group gets read/write access, everyone else gets read only access:
#rw_group = cn=tech,dc=test,dc=com ; Users in this group get rw access
#ro_group =
#
# Users in rw_group gets read/write access, users in ro_group get read
# only access. You need to be in either group to authenticate at all:
#rw_group = cn=tech,dc=test,dc=com ; Users in this group get rw access
#ro_group = cn=staff,dc=test,dc=com ; Users in this group get ro access
#
# Users get read/write access by default, users in ro_group gets read
# only access:
#rw_group =
#ro_group = cn=untrusted,dc=test,dc=com ; Users in this group get ro access

#
# Options for the WWW UI
Expand Down
46 changes: 41 additions & 5 deletions nipap/nipap/authlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,10 @@ class LdapAuth(BaseAuth):

_ldap_uri = None
_ldap_basedn = None
_ldap_binddn_fmt = None
_ldap_search = None
_ldap_rw_group = None
_ldap_ro_group = None
_ldap_conn = None
_authenticated = None

Expand Down Expand Up @@ -294,6 +298,10 @@ def __init__(self, name, username, password, authoritative_source, auth_options=
BaseAuth.__init__(self, username, password, authoritative_source, name, auth_options)
self._ldap_uri = self._cfg.get('auth.backends.' + self.auth_backend, 'uri')
self._ldap_basedn = self._cfg.get('auth.backends.' + self.auth_backend, 'basedn')
self._ldap_binddn_fmt = self._cfg.get('auth.backends.' + self.auth_backend, 'binddn_fmt')
self._ldap_search = self._cfg.get('auth.backends.' + self.auth_backend, 'search')
self._ldap_ro_group = self._cfg.get('auth.backends.' + self.auth_backend, 'ro_group')
self._ldap_rw_group = self._cfg.get('auth.backends.' + self.auth_backend, 'rw_group')

self._logger.debug('Creating LdapAuth instance')

Expand All @@ -314,7 +322,7 @@ def authenticate(self):
return self._authenticated

try:
self._ldap_conn.simple_bind_s('uid=' + self.username + ',' + self._ldap_basedn, self.password)
self._ldap_conn.simple_bind_s(self._ldap_binddn_fmt.format(ldap.dn.escape_dn_chars(self.username)), self.password)
except ldap.SERVER_DOWN as exc:
raise AuthError('Could not connect to LDAP server')
except (ldap.INVALID_CREDENTIALS, ldap.INVALID_DN_SYNTAX,
Expand All @@ -327,17 +335,45 @@ def authenticate(self):

# auth succeeded
self.authenticated_as = self.username
self._authenticated = True
self.trusted = False
self.readonly = False

try:
res = self._ldap_conn.search_s(self._ldap_basedn, ldap.SCOPE_SUBTREE, 'uid=' + self.username, ['cn'])
res = self._ldap_conn.search_s(self._ldap_basedn, ldap.SCOPE_SUBTREE, self._ldap_search.format(ldap.dn.escape_dn_chars(self.username)), ['cn','memberOf'])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This search should not be performed if both ro_group and rw_group are empty. That would make it possible to use empty values for ro_group and rw_group to provide everyone with read/write access if you don't have the memberOf attribute in your LDAP instance.

Currently, the search_s call would raise an NO_SUCH_ATTRIBUTE exception, meaning all authenticated users would get read only access instead.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure I follow, that's exactly what is intended, if both ro_group and rw_group is empty, no group member security is used, and everyone will always get read/write access no matter if memberOf is present or not.

When I was testing I did not get NO_SUCH_ATTRIBUTE from the search_s if the memberOf attribute was missing, however I do get KeyError on 'if self._ldap_ro_group in res[0][1]['memberOf']:'

But sure, we can do a separate search for memberOf only if at least one of ro_group or rw_group is non empty.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Never mind then. From a cursory look at the python-ldap it seemed like it would raise the NO_SUCH_ATTRIBUTE exception when you request an attribute that doesn't exist, but since it doesn't, it doesn't matter. I also overlooked that the search result was used to get the name of the user, so it seemed as if the result wasn't used at all if groups weren't non-empty.

Just disregard my previous comment - doing just one search is fine.

Adding a specific except clause for the KeyError, that raises an appropriate exception would probably be a good idea though (since it indicates misconfiguration).

self.full_name = res[0][1]['cn'][0]
except:
# check for ro_group membership if ro_group is configured
if self._ldap_ro_group:
if self._ldap_ro_group in res[0][1]['memberOf']:
self.readonly = True
# check for rw_group membership if rw_group is configured
if self._ldap_rw_group:
if self._ldap_rw_group in res[0][1]['memberOf']:
self.readonly = False
else:
# if ro_group is configured, and the user is a member of
# neither the ro_group nor the rw_group, fail authentication.
if self._ldap_ro_group:
if self._ldap_ro_group not in res[0][1]['memberOf']:
self._authenticated = False
return self._authenticated
else:
self.readonly = True

except (ldap.NO_SUCH_OBJECT,ldap.OPERATIONS_ERROR,ldap.FILTER_ERROR,ldap.INVALID_DN_SYNTAX,ldap.SERVER_DOWN) as exc:
raise AuthError(exc)
except KeyError:
raise AuthError('LDAP attribute missing')
except IndexError:
self.full_name = ''
# authentication fails if either ro_group or rw_group are configured
# and the user is not found.
if self._ldap_rw_group or self._ldap_ro_group:
self._authenticated = False
return self._authenticated

self._logger.debug('successfully authenticated as %s, username %s' % (self.authenticated_as, self.username))
self._authenticated = True

self._logger.debug('successfully authenticated as %s, username %s, full_name %s, readonly %s' % (self.authenticated_as, self.username, self.full_name, str(self.readonly)))
return self._authenticated


Expand Down