Use python-ldap directly in members
This leaves only utility functions in ldapi.
This commit is contained in:
parent
b8be0f8149
commit
217c9806f1
319
ceo/ldapi.py
319
ceo/ldapi.py
|
@ -1,139 +1,38 @@
|
|||
"""
|
||||
LDAP Backend Interface
|
||||
|
||||
This module is intended to be a thin wrapper around LDAP operations.
|
||||
Methods on the connection object correspond in a straightforward way
|
||||
to LDAP queries and updates.
|
||||
|
||||
A LDAP entry is the most important component of a CSC UNIX account.
|
||||
The entry contains the username, user id number, real name, shell,
|
||||
and other important information. All non-local UNIX accounts must
|
||||
have an LDAP entry, even if the account does not log in directly.
|
||||
LDAP Utilities
|
||||
|
||||
This module makes use of python-ldap, a Python module with bindings
|
||||
to libldap, OpenLDAP's native C client library.
|
||||
"""
|
||||
import ldap.modlist
|
||||
from subprocess import Popen, PIPE
|
||||
|
||||
|
||||
class LDAPException(Exception):
|
||||
"""Exception class for LDAP-related errors."""
|
||||
|
||||
|
||||
class LDAPConnection(object):
|
||||
"""
|
||||
Connection to the LDAP directory. All directory
|
||||
queries and updates are made via this class.
|
||||
|
||||
Exceptions: (all methods)
|
||||
LDAPException - on directory query failure
|
||||
|
||||
Example:
|
||||
connection = LDAPConnection()
|
||||
connection.connect(...)
|
||||
|
||||
# make queries and updates, e.g.
|
||||
connection.user_delete('mspang')
|
||||
|
||||
connection.disconnect()
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.ldap = None
|
||||
|
||||
|
||||
def connect_anon(self, uri, user_base, group_base):
|
||||
"""
|
||||
Establish a connection to the LDAP Server.
|
||||
|
||||
Parameters:
|
||||
uri - connection string (e.g. ldap://foo.com, ldaps://bar.com)
|
||||
user_base - base of the users subtree
|
||||
group_base - baes of the group subtree
|
||||
|
||||
Example: connect('ldaps:///', 'cn=ceo,dc=csclub,dc=uwaterloo,dc=ca',
|
||||
'secret', 'ou=People,dc=csclub,dc=uwaterloo,dc=ca',
|
||||
'ou=Group,dc=csclub,dc=uwaterloo,dc=ca')
|
||||
|
||||
"""
|
||||
def connect_sasl(uri, mech, realm):
|
||||
|
||||
# open the connection
|
||||
self.ldap = ldap.initialize(uri)
|
||||
|
||||
# authenticate
|
||||
self.ldap.simple_bind_s('', '')
|
||||
|
||||
self.user_base = user_base
|
||||
self.group_base = group_base
|
||||
|
||||
|
||||
def connect_sasl(self, uri, mech, realm, user_base, group_base):
|
||||
|
||||
# open the connection
|
||||
self.ldap = ldap.initialize(uri)
|
||||
ld = ldap.initialize(uri)
|
||||
|
||||
# authenticate
|
||||
sasl = Sasl(mech, realm)
|
||||
self.ldap.sasl_interactive_bind_s('', sasl)
|
||||
ld.sasl_interactive_bind_s('', sasl)
|
||||
|
||||
self.user_base = user_base
|
||||
self.group_base = group_base
|
||||
return ld
|
||||
|
||||
|
||||
def disconnect(self):
|
||||
"""Close the connection to the LDAP server."""
|
||||
|
||||
if self.ldap:
|
||||
|
||||
# close connection
|
||||
try:
|
||||
self.ldap.unbind_s()
|
||||
self.ldap = None
|
||||
except ldap.LDAPError, e:
|
||||
raise LDAPException("unable to disconnect: %s" % e)
|
||||
|
||||
|
||||
def connected(self):
|
||||
"""Determine whether the connection has been established."""
|
||||
|
||||
return self.ldap is not None
|
||||
|
||||
|
||||
|
||||
### Helper Methods ###
|
||||
|
||||
def lookup(self, dn, objectClass=None):
|
||||
"""
|
||||
Helper method to retrieve the attributes of an entry.
|
||||
|
||||
Parameters:
|
||||
dn - the distinguished name of the directory entry
|
||||
|
||||
Returns: a dictionary of attributes of the matched dn, or
|
||||
None of the dn does not exist in the directory
|
||||
"""
|
||||
|
||||
if not self.connected(): raise LDAPException("Not connected!")
|
||||
def abslookup(ld, dn, objectclass=None):
|
||||
|
||||
# search for the specified dn
|
||||
try:
|
||||
if objectClass:
|
||||
search_filter = '(objectClass=%s)' % self.escape(objectClass)
|
||||
matches = self.ldap.search_s(dn, ldap.SCOPE_BASE, search_filter)
|
||||
if objectclass:
|
||||
search_filter = '(objectclass=%s)' % escape(objectclass)
|
||||
matches = ld.search_s(dn, ldap.SCOPE_BASE, search_filter)
|
||||
else:
|
||||
matches = self.ldap.search_s(dn, ldap.SCOPE_BASE)
|
||||
matches = ld.search_s(dn, ldap.SCOPE_BASE)
|
||||
except ldap.NO_SUCH_OBJECT:
|
||||
return None
|
||||
except ldap.LDAPError, e:
|
||||
raise LDAPException("unable to lookup dn %s: %s" % (dn, e))
|
||||
|
||||
# this should never happen due to the nature of DNs
|
||||
if len(matches) > 1:
|
||||
raise LDAPException("duplicate dn in ldap: " + dn)
|
||||
|
||||
# dn was found, but didn't match the objectClass filter
|
||||
elif len(matches) < 1:
|
||||
# dn was found, but didn't match the objectclass filter
|
||||
if len(matches) < 1:
|
||||
return None
|
||||
|
||||
# return the attributes of the single successful match
|
||||
|
@ -142,196 +41,46 @@ class LDAPConnection(object):
|
|||
return match_attributes
|
||||
|
||||
|
||||
|
||||
### User-related Methods ###
|
||||
|
||||
def user_lookup(self, uid, objectClass=None):
|
||||
"""
|
||||
Retrieve the attributes of a user.
|
||||
|
||||
Parameters:
|
||||
uid - the uid to look up
|
||||
|
||||
Returns: attributes of user with uid
|
||||
"""
|
||||
|
||||
dn = 'uid=' + uid + ',' + self.user_base
|
||||
return self.lookup(dn, objectClass)
|
||||
def lookup(ld, rdntype, rdnval, base, objectclass=None):
|
||||
dn = '%s=%s,%s' % (rdntype, escape(rdnval), base)
|
||||
return abslookup(ld, dn, objectclass)
|
||||
|
||||
|
||||
def user_search(self, search_filter, params):
|
||||
"""
|
||||
Search for users with a filter.
|
||||
def search(ld, base, search_filter, params, scope=ldap.SCOPE_SUBTREE, attrlist=None, attrsonly=0):
|
||||
|
||||
Parameters:
|
||||
search_filter - LDAP filter string to match users against
|
||||
|
||||
Returns: a dictionary mapping uids to attributes
|
||||
"""
|
||||
|
||||
if not self.connected(): raise LDAPException("Not connected!")
|
||||
|
||||
search_filter = search_filter % tuple(self.escape(x) for x in params)
|
||||
real_filter = search_filter % tuple(escape(x) for x in params)
|
||||
|
||||
# search for entries that match the filter
|
||||
try:
|
||||
matches = self.ldap.search_s(self.user_base, ldap.SCOPE_SUBTREE, search_filter)
|
||||
except ldap.LDAPError, e:
|
||||
raise LDAPException("user search failed: %s" % e)
|
||||
|
||||
results = {}
|
||||
for match in matches:
|
||||
dn, attrs = match
|
||||
uid = attrs['uid'][0]
|
||||
results[uid] = attrs
|
||||
|
||||
return results
|
||||
matches = ld.search_s(base, scope, real_filter, attrlist, attrsonly)
|
||||
return matches
|
||||
|
||||
|
||||
def user_modify(self, uid, attrs):
|
||||
"""
|
||||
Update user attributes in the directory.
|
||||
def modify(ld, rdntype, rdnval, base, mlist):
|
||||
dn = '%s=%s,%s' % (rdntype, escape(rdnval), base)
|
||||
ld.modify_s(dn, mlist)
|
||||
|
||||
Parameters:
|
||||
uid - username of the user to modify
|
||||
attrs - dictionary as returned by user_lookup() with changes to make.
|
||||
omitted attributes are DELETED.
|
||||
|
||||
Example: user = user_lookup('mspang')
|
||||
user['uidNumber'] = [ '0' ]
|
||||
connection.user_modify('mspang', user)
|
||||
"""
|
||||
|
||||
# distinguished name of the entry to modify
|
||||
dn = 'uid=' + uid + ',' + self.user_base
|
||||
|
||||
# retrieve current state of user
|
||||
old_user = self.user_lookup(uid)
|
||||
|
||||
try:
|
||||
def modify_attrs(ld, rdntype, rdnval, base, old, attrs):
|
||||
dn = '%s=%s,%s' % (rdntype, escape(rdnval), base)
|
||||
|
||||
# build list of modifications to make
|
||||
changes = ldap.modlist.modifyModlist(old_user, attrs)
|
||||
changes = ldap.modlist.modifyModlist(old, attrs)
|
||||
|
||||
# apply changes
|
||||
self.ldap.modify_s(dn, changes)
|
||||
|
||||
except ldap.LDAPError, e:
|
||||
raise LDAPException("unable to modify: %s" % e)
|
||||
ld.modify_s(dn, changes)
|
||||
|
||||
|
||||
def modify_diff(ld, rdntype, rdnval, base, old, new):
|
||||
dn = '%s=%s,%s' % (rdntype, escape(rdnval), base)
|
||||
|
||||
### Group-related Methods ###
|
||||
# build list of modifications to make
|
||||
changes = make_modlist(old, new)
|
||||
|
||||
def group_lookup(self, cn):
|
||||
"""
|
||||
Retrieves the attributes of a group.
|
||||
|
||||
Parameters:
|
||||
cn - the UNIX group name to lookup
|
||||
|
||||
Returns: attributes of the group's LDAP entry
|
||||
|
||||
Example: connection.group_lookup('office') -> {
|
||||
'cn': 'office',
|
||||
'gidNumber', '1001',
|
||||
...
|
||||
}
|
||||
"""
|
||||
|
||||
dn = 'cn=' + cn + ',' + self.group_base
|
||||
return self.lookup(dn, 'posixGroup')
|
||||
# apply changes
|
||||
ld.modify_s(dn, changes)
|
||||
|
||||
|
||||
### Member-related Methods ###
|
||||
|
||||
def member_lookup(self, uid):
|
||||
"""
|
||||
Retrieve the attributes of a member. This method will only return
|
||||
results that have the objectClass 'member'.
|
||||
|
||||
Parameters:
|
||||
uid - the username to look up
|
||||
|
||||
Returns: attributes of member with uid
|
||||
|
||||
Example: connection.member_lookup('mspang') ->
|
||||
{ 'uid': 'mspang', 'uidNumber': 21292 ...}
|
||||
"""
|
||||
|
||||
if not self.connected(): raise LDAPException("Not connected!")
|
||||
|
||||
dn = 'uid=' + uid + ',' + self.user_base
|
||||
return self.lookup(dn, 'member')
|
||||
|
||||
|
||||
def member_search_name(self, name):
|
||||
"""
|
||||
Retrieves a list of members with the specified name (fuzzy).
|
||||
|
||||
Returns: a dictionary mapping uids to attributes
|
||||
"""
|
||||
|
||||
search_filter = '(&(objectClass=member)(cn~=%s))'
|
||||
return self.user_search(search_filter, [ name ] )
|
||||
|
||||
|
||||
def member_search_term(self, term):
|
||||
"""
|
||||
Retrieves a list of members who were registered in a certain term.
|
||||
|
||||
Returns: a dictionary mapping uids to attributes
|
||||
"""
|
||||
|
||||
search_filter = '(&(objectClass=member)(term=%s))'
|
||||
return self.user_search(search_filter, [ term ])
|
||||
|
||||
|
||||
def member_search_program(self, program):
|
||||
"""
|
||||
Retrieves a list of members in a certain program (fuzzy).
|
||||
|
||||
Returns: a dictionary mapping uids to attributes
|
||||
"""
|
||||
|
||||
search_filter = '(&(objectClass=member)(program~=%s))'
|
||||
return self.user_search(search_filter, [ program ])
|
||||
|
||||
|
||||
def member_add(self, uid, cn, program=None, description=None):
|
||||
"""
|
||||
Adds a member to the directory.
|
||||
|
||||
Parameters:
|
||||
uid - the UNIX username for the member
|
||||
cn - the real name of the member
|
||||
program - the member's program of study
|
||||
description - a description for the entry
|
||||
"""
|
||||
|
||||
dn = 'uid=' + uid + ',' + self.user_base
|
||||
attrs = {
|
||||
'objectClass': [ 'top', 'account', 'member' ],
|
||||
'uid': [ uid ],
|
||||
'cn': [ cn ],
|
||||
}
|
||||
|
||||
if program:
|
||||
attrs['program'] = [ program ]
|
||||
if description:
|
||||
attrs['description'] = [ description ]
|
||||
|
||||
try:
|
||||
modlist = ldap.modlist.addModlist(attrs)
|
||||
self.ldap.add_s(dn, modlist)
|
||||
except ldap.LDAPError, e:
|
||||
raise LDAPException("unable to add: %s" % e)
|
||||
|
||||
|
||||
|
||||
### Miscellaneous Methods ###
|
||||
|
||||
def escape(self, value):
|
||||
def escape(value):
|
||||
"""
|
||||
Escapes special characters in a value so that it may be safely inserted
|
||||
into an LDAP search filter.
|
||||
|
@ -344,7 +93,7 @@ class LDAPConnection(object):
|
|||
return value
|
||||
|
||||
|
||||
def make_modlist(self, old, new):
|
||||
def make_modlist(old, new):
|
||||
keys = set(old.keys()).union(set(new))
|
||||
mlist = []
|
||||
for key in keys:
|
||||
|
|
|
@ -10,7 +10,7 @@ Future changes to the members database that need to be atomic
|
|||
must also be moved into this module.
|
||||
"""
|
||||
import re, subprocess, ldap
|
||||
from ceo import conf, excep, ldapi
|
||||
from ceo import conf, ldapi
|
||||
from ceo.excep import InvalidArgument
|
||||
|
||||
|
||||
|
@ -43,8 +43,8 @@ def configure():
|
|||
|
||||
### Exceptions ###
|
||||
|
||||
LDAPException = ldap.LDAPError
|
||||
ConfigurationException = conf.ConfigurationException
|
||||
LDAPException = ldapi.LDAPException
|
||||
|
||||
class MemberException(Exception):
|
||||
"""Base exception class for member-related errors."""
|
||||
|
@ -76,26 +76,30 @@ class ChildFailed(MemberException):
|
|||
### Connection Management ###
|
||||
|
||||
# global directory connection
|
||||
ldap_connection = ldapi.LDAPConnection()
|
||||
ld = None
|
||||
|
||||
def connect():
|
||||
"""Connect to LDAP."""
|
||||
|
||||
configure()
|
||||
|
||||
ldap_connection.connect_sasl(cfg['server_url'], cfg['sasl_mech'],
|
||||
cfg['sasl_realm'], cfg['users_base'], cfg['groups_base'])
|
||||
global ld
|
||||
ld = ldapi.connect_sasl(cfg['server_url'],
|
||||
cfg['sasl_mech'], cfg['sasl_realm'])
|
||||
|
||||
|
||||
def disconnect():
|
||||
"""Disconnect from LDAP."""
|
||||
|
||||
ldap_connection.disconnect()
|
||||
global ld
|
||||
ld.unbind_s()
|
||||
ld = None
|
||||
|
||||
|
||||
def connected():
|
||||
"""Determine whether the connection has been established."""
|
||||
|
||||
return ldap_connection.connected()
|
||||
return ld and ld.connected()
|
||||
|
||||
|
||||
|
||||
|
@ -149,7 +153,7 @@ def get(userid):
|
|||
}
|
||||
"""
|
||||
|
||||
return ldap_connection.user_lookup(userid)
|
||||
return ldapi.lookup(ld, 'uid', userid, cfg['users_base'])
|
||||
|
||||
|
||||
def list_term(term):
|
||||
|
@ -168,7 +172,10 @@ def list_term(term):
|
|||
}
|
||||
"""
|
||||
|
||||
return ldap_connection.member_search_term(term)
|
||||
members = ldapi.search(ld, cfg['users_base'],
|
||||
'(&(objectClass=member)(term=%s))', [ term ])
|
||||
|
||||
return dict([(member['uid'], member) for member in members])
|
||||
|
||||
|
||||
def list_name(name):
|
||||
|
@ -177,7 +184,8 @@ def list_name(name):
|
|||
|
||||
Parameters:
|
||||
name - the name to match members against
|
||||
Returns: a list of member dictionaries
|
||||
|
||||
Returns: a list of member dictionaries
|
||||
|
||||
Example: list_name('Spang'): -> {
|
||||
'mspang': { 'cn': 'Michael Spang', ... },
|
||||
|
@ -185,7 +193,10 @@ Returns: a list of member dictionaries
|
|||
]
|
||||
"""
|
||||
|
||||
return ldap_connection.member_search_name(name)
|
||||
members = ldapi.search(ld, cfg['users_base'],
|
||||
'(&(objectClass=member)(cn~=%s))', [ name ])
|
||||
|
||||
return dict([(member['uid'], member) for member in members])
|
||||
|
||||
|
||||
def list_group(group):
|
||||
|
@ -225,10 +236,7 @@ def list_positions():
|
|||
]
|
||||
"""
|
||||
|
||||
ceo_ldap = ldap_connection.ldap
|
||||
user_base = ldap_connection.user_base
|
||||
|
||||
members = ceo_ldap.search_s(user_base, ldap.SCOPE_SUBTREE, '(position=*)')
|
||||
members = ld.search_s(cfg['users_base'], ldap.SCOPE_SUBTREE, '(position=*)')
|
||||
positions = {}
|
||||
for (_, member) in members:
|
||||
for position in member['position']:
|
||||
|
@ -237,6 +245,7 @@ def list_positions():
|
|||
positions[position][member['uid'][0]] = member
|
||||
return positions
|
||||
|
||||
|
||||
def set_position(position, members):
|
||||
"""
|
||||
Sets a position
|
||||
|
@ -248,12 +257,8 @@ def set_position(position, members):
|
|||
Example: set_position('president', ['dtbartle'])
|
||||
"""
|
||||
|
||||
ceo_ldap = ldap_connection.ldap
|
||||
user_base = ldap_connection.user_base
|
||||
escape = ldap_connection.escape
|
||||
|
||||
res = ceo_ldap.search_s(user_base, ldap.SCOPE_SUBTREE,
|
||||
'(&(objectClass=member)(position=%s))' % escape(position))
|
||||
res = ld.search_s(cfg['users_base'], ldap.SCOPE_SUBTREE,
|
||||
'(&(objectClass=member)(position=%s))' % ldapi.escape(position))
|
||||
old = set([ member['uid'][0] for (_, member) in res ])
|
||||
new = set(members)
|
||||
mods = {
|
||||
|
@ -265,7 +270,7 @@ def set_position(position, members):
|
|||
|
||||
for action in ['del', 'add']:
|
||||
for userid in mods[action]:
|
||||
dn = 'uid=%s,%s' % (escape(userid), user_base)
|
||||
dn = 'uid=%s,%s' % (ldapi.escape(userid), cfg['users_base'])
|
||||
entry1 = {'position' : [position]}
|
||||
entry2 = {} #{'position' : []}
|
||||
entry = ()
|
||||
|
@ -273,19 +278,13 @@ def set_position(position, members):
|
|||
entry = (entry1, entry2)
|
||||
elif action == 'add':
|
||||
entry = (entry2, entry1)
|
||||
mlist = ldap_connection.make_modlist(entry[0], entry[1])
|
||||
ceo_ldap.modify_s(dn, mlist)
|
||||
mlist = ldapi.make_modlist(entry[0], entry[1])
|
||||
ld.modify_s(dn, mlist)
|
||||
|
||||
|
||||
def change_group_member(action, group, userid):
|
||||
|
||||
ceo_ldap = ldap_connection.ldap
|
||||
user_base = ldap_connection.user_base
|
||||
group_base = ldap_connection.group_base
|
||||
escape = ldap_connection.escape
|
||||
|
||||
user_dn = 'uid=%s,%s' % (escape(userid), user_base)
|
||||
group_dn = 'cn=%s,%s' % (escape(group), group_base)
|
||||
user_dn = 'uid=%s,%s' % (ldapi.escape(userid), cfg['users_base'])
|
||||
group_dn = 'cn=%s,%s' % (ldapi.escape(group), cfg['groups_base'])
|
||||
entry1 = {'uniqueMember' : []}
|
||||
entry2 = {'uniqueMember' : [user_dn]}
|
||||
entry = []
|
||||
|
@ -295,8 +294,8 @@ def change_group_member(action, group, userid):
|
|||
entry = (entry2, entry1)
|
||||
else:
|
||||
raise InvalidArgument("action", action, "invalid action")
|
||||
mlist = ldap_connection.make_modlist(entry[0], entry[1])
|
||||
ceo_ldap.modify_s(group_dn, mlist)
|
||||
mlist = ldapi.make_modlist(entry[0], entry[1])
|
||||
ld.modify_s(group_dn, mlist)
|
||||
|
||||
|
||||
|
||||
|
@ -350,15 +349,12 @@ def register(userid, term_list):
|
|||
Example: register(3349, ["w2007", "s2007"])
|
||||
"""
|
||||
|
||||
ceo_ldap = ldap_connection.ldap
|
||||
user_base = ldap_connection.user_base
|
||||
escape = ldap_connection.escape
|
||||
user_dn = 'uid=%s,%s' % (escape(userid), user_base)
|
||||
user_dn = 'uid=%s,%s' % (ldapi.escape(userid), cfg['users_base'])
|
||||
|
||||
if type(term_list) in (str, unicode):
|
||||
term_list = [ term_list ]
|
||||
|
||||
ldap_member = ldap_connection.member_lookup(userid)
|
||||
ldap_member = get(userid)
|
||||
if ldap_member and 'term' not in ldap_member:
|
||||
ldap_member['term'] = []
|
||||
|
||||
|
@ -378,8 +374,8 @@ def register(userid, term_list):
|
|||
if not term in ldap_member['term']:
|
||||
new_member['term'].append(term)
|
||||
|
||||
mlist = ldap_connection.make_modlist(ldap_member, new_member)
|
||||
ceo_ldap.modify_s(user_dn, mlist)
|
||||
mlist = ldapi.make_modlist(ldap_member, new_member)
|
||||
ld.modify_s(user_dn, mlist)
|
||||
|
||||
|
||||
def registered(userid, term):
|
||||
|
@ -396,7 +392,7 @@ def registered(userid, term):
|
|||
Example: registered("mspang", "f2006") -> True
|
||||
"""
|
||||
|
||||
member = ldap_connection.member_lookup(userid)
|
||||
member = get(userid)
|
||||
return 'term' in member and term in member['term']
|
||||
|
||||
|
||||
|
@ -406,7 +402,8 @@ def group_members(group):
|
|||
Returns a list of group members
|
||||
"""
|
||||
|
||||
group = ldap_connection.group_lookup(group)
|
||||
group = ldapi.lookup(ld, 'cn', group, cfg['groups_base'])
|
||||
|
||||
if group:
|
||||
if 'uniqueMember' in group:
|
||||
r = re.compile('^uid=([^,]*)')
|
||||
|
|
Loading…
Reference in New Issue