Merge branch 'ldap'
This commit is contained in:
commit
6ab575ec9e
|
@ -0,0 +1,22 @@
|
|||
# CSC Member Information Schema
|
||||
|
||||
attributetype ( 1.3.6.1.4.1.27934.1.1.1 NAME 'term'
|
||||
EQUALITY caseIgnoreIA5Match
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{5} )
|
||||
|
||||
attributetype ( 1.3.6.1.4.1.27934.1.1.2 NAME 'program'
|
||||
EQUALITY caseIgnoreIA5Match
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{1024} SINGLE-VALUE )
|
||||
|
||||
attributetype ( 1.3.6.1.4.1.27934.1.1.3 NAME 'studentid'
|
||||
EQUALITY caseIgnoreIA5Match
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{8} SINGLE-VALUE )
|
||||
|
||||
objectclass ( 1.3.6.1.4.1.27934.1.2.1 NAME 'member'
|
||||
SUP top AUXILIARY
|
||||
MUST ( cn $ uid $ studentid )
|
||||
MAY ( program $ term $ description ) )
|
||||
|
||||
objectclass ( 1.3.6.1.4.1.27934.1.2.2 NAME 'club'
|
||||
SUP top AUXILIARY
|
||||
MUST ( cn $ uid ) )
|
|
@ -190,8 +190,15 @@ def create(username, name, minimum_id, maximum_id, home, password=None, descript
|
|||
|
||||
### User creation ###
|
||||
|
||||
# create the LDAP entry
|
||||
ldap_connection.user_add(username, name, userid, gid, home, shell, gecos, description)
|
||||
if not ldap_connection.user_lookup(username):
|
||||
|
||||
# create the LDAP entry
|
||||
ldap_connection.account_add(username, name, userid, gid, home, shell, gecos, description)
|
||||
|
||||
else:
|
||||
|
||||
# add the required attribute to the LDAP entry
|
||||
ldap_connection.member_add_account(username, userid, gid, home, shell, gecos)
|
||||
|
||||
# create a user group if no other group was specified
|
||||
if not group:
|
||||
|
@ -257,7 +264,7 @@ def status(username):
|
|||
Returns: a boolean 2-tuple (exists, has_password)
|
||||
"""
|
||||
|
||||
ldap_state = ldap_connection.user_lookup(username)
|
||||
ldap_state = ldap_connection.account_lookup(username)
|
||||
krb_state = krb_connection.get_principal(username)
|
||||
return (ldap_state is not None, krb_state is not None)
|
||||
|
||||
|
@ -786,9 +793,9 @@ def check_name_usage(name):
|
|||
"""
|
||||
|
||||
# see if user exists in LDAP
|
||||
if ldap_connection.user_lookup(name):
|
||||
if ldap_connection.account_lookup(name):
|
||||
raise NameConflict(name, "account", "LDAP")
|
||||
|
||||
|
||||
# see if group exists in LDAP
|
||||
if ldap_connection.group_lookup(name):
|
||||
raise NameConflict(name, "group", "LDAP")
|
||||
|
@ -815,10 +822,10 @@ def check_name_usage(name):
|
|||
|
||||
def check_account_status(username, require_ldap=True, require_krb=False):
|
||||
"""Helper function to verify that an account exists."""
|
||||
|
||||
if not connected():
|
||||
|
||||
if not connected():
|
||||
raise AccountException("Not connected to LDAP and Kerberos")
|
||||
if require_ldap and not ldap_connection.user_lookup(username):
|
||||
if require_ldap and not ldap_connection.account_lookup(username):
|
||||
raise NoSuchAccount(username, "LDAP")
|
||||
if require_krb and not krb_connection.get_principal(username):
|
||||
raise NoSuchAccount(username, "KRB")
|
||||
|
|
|
@ -11,8 +11,9 @@ must also be moved into this module.
|
|||
"""
|
||||
import re
|
||||
from csc.adm import terms
|
||||
from csc.backends import db
|
||||
from csc.backends import db, ldapi
|
||||
from csc.common import conf
|
||||
from csc.common.excep import InvalidArgument
|
||||
|
||||
|
||||
### Configuration ###
|
||||
|
@ -25,7 +26,8 @@ def load_configuration():
|
|||
"""Load Members Configuration"""
|
||||
|
||||
string_fields = [ 'studentid_regex', 'realname_regex', 'server',
|
||||
'database', 'user', 'password' ]
|
||||
'database', 'user', 'password', 'server_url', 'users_base',
|
||||
'groups_base', 'admin_bind_dn', 'admin_bind_pw' ]
|
||||
|
||||
# read configuration file
|
||||
cfg_tmp = conf.read(CONFIG_FILE)
|
||||
|
@ -86,41 +88,46 @@ class NoSuchMember(MemberException):
|
|||
### Connection Management ###
|
||||
|
||||
# global database connection
|
||||
connection = db.DBConnection()
|
||||
db_connection = db.DBConnection()
|
||||
|
||||
# global directory connection
|
||||
ldap_connection = ldapi.LDAPConnection()
|
||||
|
||||
def connect():
|
||||
"""Connect to PostgreSQL."""
|
||||
|
||||
|
||||
load_configuration()
|
||||
connection.connect(cfg['server'], cfg['database'])
|
||||
|
||||
db_connection.connect(cfg['server'], cfg['database'])
|
||||
ldap_connection.connect(cfg['server_url'], cfg['admin_bind_dn'], cfg['admin_bind_pw'], cfg['users_base'], cfg['groups_base'])
|
||||
|
||||
|
||||
def disconnect():
|
||||
"""Disconnect from PostgreSQL."""
|
||||
|
||||
connection.disconnect()
|
||||
|
||||
db_connection.disconnect()
|
||||
ldap_connection.disconnect()
|
||||
|
||||
|
||||
def connected():
|
||||
"""Determine whether the connection has been established."""
|
||||
"""Determine whether the db_connection has been established."""
|
||||
|
||||
return connection.connected()
|
||||
return db_connection.connected() and ldap_connection.connected()
|
||||
|
||||
|
||||
|
||||
### Member Table ###
|
||||
|
||||
def new(realname, studentid=None, program=None, mtype='user', userid=None):
|
||||
def new(uid, realname, studentid=None, program=None, mtype='user'):
|
||||
"""
|
||||
Registers a new CSC member. The member is added to the members table
|
||||
and registered for the current term.
|
||||
|
||||
Parameters:
|
||||
uid - the initial user id
|
||||
realname - the full real name of the member
|
||||
studentid - the student id number of the member
|
||||
program - the program of study of the member
|
||||
mtype - a string describing the type of member ('user', 'club')
|
||||
userid - the initial user id
|
||||
|
||||
Returns: the memberid of the new member
|
||||
|
||||
|
@ -135,7 +142,7 @@ def new(realname, studentid=None, program=None, mtype='user', userid=None):
|
|||
# blank attributes should be NULL
|
||||
if studentid == '': studentid = None
|
||||
if program == '': program = None
|
||||
if userid == '': userid = None
|
||||
if uid == '': uid = None
|
||||
if mtype == '': mtype = None
|
||||
|
||||
# check the student id format
|
||||
|
@ -147,18 +154,33 @@ def new(realname, studentid=None, program=None, mtype='user', userid=None):
|
|||
raise InvalidRealName(realname)
|
||||
|
||||
# check for duplicate student id
|
||||
member = connection.select_member_by_studentid(studentid)
|
||||
member = db_connection.select_member_by_studentid(studentid) or \
|
||||
ldap_connection.member_search_studentid(studentid)
|
||||
if member:
|
||||
raise DuplicateStudentID(studentid)
|
||||
|
||||
# add the member
|
||||
memberid = connection.insert_member(realname, studentid, program)
|
||||
# check for duplicate userid
|
||||
member = db_connection.select_member_by_userid(uid) or \
|
||||
ldap_connection.user_lookup(uid)
|
||||
if member:
|
||||
raise InvalidArgument("uid", uid, "duplicate uid")
|
||||
|
||||
# register them for this term
|
||||
connection.insert_term(memberid, terms.current())
|
||||
# add the member to the database
|
||||
memberid = db_connection.insert_member(realname, studentid, program, userid=uid)
|
||||
|
||||
# commit the transaction
|
||||
connection.commit()
|
||||
# add the member to the directory
|
||||
ldap_connection.member_add(uid, realname, studentid, program)
|
||||
|
||||
# register them for this term in the database
|
||||
db_connection.insert_term(memberid, terms.current())
|
||||
|
||||
# register them for this term in the directory
|
||||
member = ldap_connection.member_lookup(uid)
|
||||
member['term'] = [ terms.current() ]
|
||||
ldap_connection.user_modify(uid, member)
|
||||
|
||||
# commit the database transaction
|
||||
db_connection.commit()
|
||||
|
||||
return memberid
|
||||
|
||||
|
@ -177,7 +199,7 @@ def get(memberid):
|
|||
}
|
||||
"""
|
||||
|
||||
return connection.select_member_by_id(memberid)
|
||||
return db_connection.select_member_by_id(memberid)
|
||||
|
||||
|
||||
def get_userid(userid):
|
||||
|
@ -197,7 +219,7 @@ def get_userid(userid):
|
|||
}
|
||||
"""
|
||||
|
||||
return connection.select_member_by_userid(userid)
|
||||
return db_connection.select_member_by_userid(userid)
|
||||
|
||||
|
||||
def get_studentid(studentid):
|
||||
|
@ -217,7 +239,7 @@ def get_studentid(studentid):
|
|||
}
|
||||
"""
|
||||
|
||||
return connection.select_member_by_studentid(studentid)
|
||||
return db_connection.select_member_by_studentid(studentid)
|
||||
|
||||
|
||||
def list_term(term):
|
||||
|
@ -237,10 +259,10 @@ def list_term(term):
|
|||
"""
|
||||
|
||||
# retrieve a list of memberids in term
|
||||
memberlist = connection.select_members_by_term(term)
|
||||
memberlist = db_connection.select_members_by_term(term)
|
||||
|
||||
return memberlist.values()
|
||||
|
||||
|
||||
|
||||
def list_name(name):
|
||||
"""
|
||||
|
@ -259,7 +281,7 @@ def list_name(name):
|
|||
"""
|
||||
|
||||
# retrieve a list of memberids matching name
|
||||
memberlist = connection.select_members_by_name(name)
|
||||
memberlist = db_connection.select_members_by_name(name)
|
||||
|
||||
return memberlist.values()
|
||||
|
||||
|
@ -272,7 +294,7 @@ def list_all():
|
|||
"""
|
||||
|
||||
# retrieve a list of members
|
||||
memberlist = connection.select_all_members()
|
||||
memberlist = db_connection.select_all_members()
|
||||
|
||||
return memberlist.values()
|
||||
|
||||
|
@ -292,18 +314,23 @@ def delete(memberid):
|
|||
"""
|
||||
|
||||
# save member data
|
||||
member = connection.select_member_by_id(memberid)
|
||||
member = db_connection.select_member_by_id(memberid)
|
||||
|
||||
# bail if not found
|
||||
if not member:
|
||||
raise NoSuchMember(memberid)
|
||||
|
||||
term_list = connection.select_terms(memberid)
|
||||
term_list = db_connection.select_terms(memberid)
|
||||
|
||||
# remove data from the db
|
||||
connection.delete_term_all(memberid)
|
||||
connection.delete_member(memberid)
|
||||
connection.commit()
|
||||
db_connection.delete_term_all(memberid)
|
||||
db_connection.delete_member(memberid)
|
||||
db_connection.commit()
|
||||
|
||||
# remove data from the directory
|
||||
if member and member['userid']:
|
||||
uid = member['userid']
|
||||
ldap_connection.user_delete(uid)
|
||||
|
||||
return (member, term_list)
|
||||
|
||||
|
@ -334,7 +361,7 @@ def update(member):
|
|||
raise InvalidStudentID(studentid)
|
||||
|
||||
# check for duplicate student id
|
||||
dupmember = connection.select_member_by_studentid(studentid)
|
||||
dupmember = db_connection.select_member_by_studentid(studentid)
|
||||
if dupmember:
|
||||
raise DuplicateStudentID(studentid)
|
||||
|
||||
|
@ -346,12 +373,12 @@ def update(member):
|
|||
# see if member exists
|
||||
if not get(memberid):
|
||||
raise NoSuchMember(memberid)
|
||||
|
||||
|
||||
# do the update
|
||||
connection.update_member(member)
|
||||
db_connection.update_member(member)
|
||||
|
||||
# commit the transaction
|
||||
connection.commit()
|
||||
db_connection.commit()
|
||||
|
||||
|
||||
|
||||
|
@ -376,16 +403,31 @@ def register(memberid, term_list):
|
|||
if type(term_list) in (str, unicode):
|
||||
term_list = [ term_list ]
|
||||
|
||||
ldap_member = None
|
||||
db_member = get(memberid)
|
||||
if db_member['userid']:
|
||||
uid = db_member['userid']
|
||||
ldap_member = ldap_connection.member_lookup(uid)
|
||||
if ldap_member and 'term' not in ldap_member:
|
||||
ldap_member['term'] = []
|
||||
|
||||
for term in term_list:
|
||||
|
||||
|
||||
# check term syntax
|
||||
if not re.match('^[wsf][0-9]{4}$', term):
|
||||
raise InvalidTerm(term)
|
||||
|
||||
# add term to database
|
||||
connection.insert_term(memberid, term)
|
||||
|
||||
connection.commit()
|
||||
# add term to database
|
||||
db_connection.insert_term(memberid, term)
|
||||
|
||||
# add the term to the directory
|
||||
if ldap_member:
|
||||
ldap_member['term'].append(term)
|
||||
|
||||
if ldap_member:
|
||||
ldap_connection.user_modify(uid, ldap_member)
|
||||
|
||||
db_connection.commit()
|
||||
|
||||
|
||||
def registered(memberid, term):
|
||||
|
@ -402,7 +444,7 @@ def registered(memberid, term):
|
|||
Example: registered(3349, "f2006") -> True
|
||||
"""
|
||||
|
||||
return connection.select_term(memberid, term) is not None
|
||||
return db_connection.select_term(memberid, term) is not None
|
||||
|
||||
|
||||
def member_terms(memberid):
|
||||
|
@ -418,7 +460,7 @@ def member_terms(memberid):
|
|||
Example: registered(0) -> 's1993'
|
||||
"""
|
||||
|
||||
terms_list = connection.select_terms(memberid)
|
||||
terms_list = db_connection.select_terms(memberid)
|
||||
terms_list.sort(terms.compare)
|
||||
return terms_list
|
||||
|
||||
|
@ -432,18 +474,19 @@ if __name__ == '__main__':
|
|||
|
||||
# t=test m=member s=student u=updated
|
||||
tmname = 'Test Member'
|
||||
tmuid = 'testmember'
|
||||
tmprogram = 'Metaphysics'
|
||||
tmsid = '00000000'
|
||||
tm2name = 'Test Member 2'
|
||||
tm2uid = 'testmember2'
|
||||
tm2sid = '00000001'
|
||||
tm2uname = 'Test Member II'
|
||||
tm2usid = '00000002'
|
||||
tm2uprogram = 'Pseudoscience'
|
||||
tm2uuserid = 'testmember'
|
||||
|
||||
tmdict = {'name': tmname, 'userid': None, 'program': tmprogram, 'type': 'user', 'studentid': tmsid }
|
||||
tm2dict = {'name': tm2name, 'userid': None, 'program': None, 'type': 'user', 'studentid': tm2sid }
|
||||
tm2udict = {'name': tm2uname, 'userid': tm2uuserid, 'program': tm2uprogram, 'type': 'user', 'studentid': tm2usid }
|
||||
tmdict = {'name': tmname, 'userid': tmuid, 'program': tmprogram, 'type': 'user', 'studentid': tmsid }
|
||||
tm2dict = {'name': tm2name, 'userid': tm2uid, 'program': None, 'type': 'user', 'studentid': tm2sid }
|
||||
tm2udict = {'name': tm2uname, 'userid': tm2uid, 'program': tm2uprogram, 'type': 'user', 'studentid': tm2usid }
|
||||
|
||||
thisterm = terms.current()
|
||||
nextterm = terms.next(thisterm)
|
||||
|
@ -464,8 +507,8 @@ if __name__ == '__main__':
|
|||
if dmid: delete(dmid['memberid'])
|
||||
|
||||
test(new)
|
||||
tmid = new(tmname, tmsid, tmprogram)
|
||||
tm2id = new(tm2name, tm2sid)
|
||||
tmid = new(tmuid, tmname, tmsid, tmprogram)
|
||||
tm2id = new(tm2uid, tm2name, tm2sid)
|
||||
success()
|
||||
|
||||
tmdict['memberid'] = tmid
|
||||
|
@ -495,7 +538,7 @@ if __name__ == '__main__':
|
|||
success()
|
||||
|
||||
test(register)
|
||||
register(tmid, terms.next(terms.current()))
|
||||
register(tmid, nextterm)
|
||||
assert_equal(True, registered(tmid, nextterm))
|
||||
success()
|
||||
|
||||
|
@ -517,7 +560,7 @@ if __name__ == '__main__':
|
|||
success()
|
||||
|
||||
test(get_userid)
|
||||
assert_equal(tm2udict, get_userid(tm2uuserid))
|
||||
assert_equal(tm2udict, get_userid(tm2uid))
|
||||
success()
|
||||
|
||||
test(get_studentid)
|
||||
|
|
|
@ -12,6 +12,7 @@ This frontend is poorly documented, deprecated, and undoubtedly full of bugs.
|
|||
import curses.ascii, re, os
|
||||
from helpers import menu, inputbox, msgbox, reset
|
||||
from csc.adm import accounts, members, terms
|
||||
from csc.common.excep import InvalidArgument
|
||||
|
||||
# color of the ceo border
|
||||
BORDER_COLOR = curses.COLOR_RED
|
||||
|
@ -20,7 +21,7 @@ BORDER_COLOR = curses.COLOR_RED
|
|||
def action_new_member(wnd):
|
||||
"""Interactively add a new member."""
|
||||
|
||||
studentid, program = None, ''
|
||||
userid, studentid, program = '', None, ''
|
||||
|
||||
# read the name
|
||||
prompt = " Name: "
|
||||
|
@ -50,12 +51,21 @@ def action_new_member(wnd):
|
|||
if program is None or program.lower() == 'exit':
|
||||
return False
|
||||
|
||||
# read user id
|
||||
prompt = "Userid:"
|
||||
while userid == '':
|
||||
userid = inputbox(wnd, prompt, 18)
|
||||
|
||||
# user abort
|
||||
if userid is None or userid.lower() == 'exit':
|
||||
return False
|
||||
|
||||
# connect the members module to its backend if necessary
|
||||
if not members.connected(): members.connect()
|
||||
|
||||
# attempt to create the member
|
||||
try:
|
||||
memberid = members.new(realname, studentid, program)
|
||||
memberid = members.new(userid, realname, studentid, program)
|
||||
|
||||
msgbox(wnd, "Success! Your memberid is %s. You are now registered\n"
|
||||
% memberid + "for the " + terms.current() + " term.")
|
||||
|
@ -69,6 +79,12 @@ def action_new_member(wnd):
|
|||
except members.InvalidRealName:
|
||||
msgbox(wnd, 'Invalid real name: "%s"' % realname)
|
||||
return False
|
||||
except InvalidArgument, e:
|
||||
if e.argname == 'uid' and e.explanation == 'duplicate uid':
|
||||
msgbox(wnd, 'A member with this user ID exists.')
|
||||
return False
|
||||
else:
|
||||
raise
|
||||
|
||||
|
||||
def action_term_register(wnd):
|
||||
|
@ -196,7 +212,7 @@ def repair_account(wnd, memberid, userid):
|
|||
password = input_password(wnd)
|
||||
accounts.create_member(userid, password, member['name'], memberid)
|
||||
msgbox(wnd, "Account created (where the hell did it go, anyway?)\n"
|
||||
"If you're homedir still exists, it will not be inaccessible to you,\n"
|
||||
"If your homedir still exists, it will not be inaccessible to you,\n"
|
||||
"please contact systems-committee@csclub.uwaterloo.ca to get this resolved.\n")
|
||||
|
||||
elif not haspw:
|
||||
|
@ -267,7 +283,8 @@ def action_create_account(wnd):
|
|||
return False
|
||||
|
||||
# member already has an account?
|
||||
if member['userid']:
|
||||
if not accounts.connected(): accounts.connect()
|
||||
if member['userid'] and accounts.status(member['userid'])[0]:
|
||||
|
||||
userid = member['userid']
|
||||
msgbox(wnd, "Member " + str(memberid) + " already has an account: " + member['userid'] + "\n"
|
||||
|
@ -278,6 +295,9 @@ def action_create_account(wnd):
|
|||
return False
|
||||
|
||||
|
||||
if member['userid']:
|
||||
userid = member['userid']
|
||||
|
||||
# read user id
|
||||
prompt = "Userid:"
|
||||
while userid == '':
|
||||
|
|
|
@ -98,7 +98,7 @@ class LDAPConnection(object):
|
|||
|
||||
### Helper Methods ###
|
||||
|
||||
def lookup(self, dn):
|
||||
def lookup(self, dn, objectClass=None):
|
||||
"""
|
||||
Helper method to retrieve the attributes of an entry.
|
||||
|
||||
|
@ -113,7 +113,11 @@ class LDAPConnection(object):
|
|||
|
||||
# search for the specified dn
|
||||
try:
|
||||
matches = self.ldap.search_s(dn, ldap.SCOPE_BASE)
|
||||
if objectClass:
|
||||
search_filter = '(objectClass=%s)' % self.escape(objectClass)
|
||||
matches = self.ldap.search_s(dn, ldap.SCOPE_BASE, search_filter)
|
||||
else:
|
||||
matches = self.ldap.search_s(dn, ldap.SCOPE_BASE)
|
||||
except ldap.NO_SUCH_OBJECT:
|
||||
return None
|
||||
except ldap.LDAPError, e:
|
||||
|
@ -122,109 +126,160 @@ class LDAPConnection(object):
|
|||
# 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:
|
||||
return None
|
||||
|
||||
# return the attributes of the single successful match
|
||||
else:
|
||||
match = matches[0]
|
||||
match_dn, match_attributes = match
|
||||
return match_attributes
|
||||
match = matches[0]
|
||||
match_dn, match_attributes = match
|
||||
return match_attributes
|
||||
|
||||
|
||||
|
||||
|
||||
### User-related Methods ###
|
||||
|
||||
def user_lookup(self, uid):
|
||||
def user_lookup(self, uid, objectClass=None):
|
||||
"""
|
||||
Retrieve the attributes of a user.
|
||||
|
||||
Parameters:
|
||||
uid - the UNIX username to look up
|
||||
uid - the uid to look up
|
||||
|
||||
Returns: attributes of user with uid
|
||||
|
||||
Example: connection.user_lookup('mspang') ->
|
||||
{ 'uid': 'mspang', 'uidNumber': 21292 ...}
|
||||
"""
|
||||
|
||||
dn = 'uid=' + uid + ',' + self.user_base
|
||||
return self.lookup(dn)
|
||||
return self.lookup(dn, objectClass)
|
||||
|
||||
|
||||
def user_search(self, search_filter):
|
||||
def user_search(self, search_filter, params):
|
||||
"""
|
||||
Helper for user searches.
|
||||
Search for users with a filter.
|
||||
|
||||
Parameters:
|
||||
search_filter - LDAP filter string to match users against
|
||||
|
||||
Returns: the list of uids matched (usernames)
|
||||
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)
|
||||
|
||||
# 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)
|
||||
|
||||
# list for uids found
|
||||
uids = []
|
||||
|
||||
|
||||
results = {}
|
||||
for match in matches:
|
||||
dn, attributes = match
|
||||
|
||||
# uid is a required attribute of posixAccount
|
||||
if not attributes.has_key('uid'):
|
||||
raise LDAPException(dn + ' (posixAccount) has no uid')
|
||||
|
||||
# do not handle the case of multiple usernames in one entry (yet)
|
||||
elif len(attributes['uid']) > 1:
|
||||
raise LDAPException(dn + ' (posixAccount) has multiple uids')
|
||||
|
||||
# append the sole uid of this match to the list
|
||||
uids.append( attributes['uid'][0] )
|
||||
dn, attrs = match
|
||||
uid = attrs['uid'][0]
|
||||
results[uid] = attrs
|
||||
|
||||
return uids
|
||||
return results
|
||||
|
||||
|
||||
def user_search_id(self, uidNumber):
|
||||
def user_modify(self, uid, attrs):
|
||||
"""
|
||||
Retrieves a list of users with a certain UNIX uid number.
|
||||
Update user attributes in the directory.
|
||||
|
||||
LDAP (or passwd for that matter) does not enforce any
|
||||
restriction on the number of accounts that can have
|
||||
a certain UID. Therefore this method returns a list of matches.
|
||||
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:
|
||||
|
||||
# build list of modifications to make
|
||||
changes = ldap.modlist.modifyModlist(old_user, attrs)
|
||||
|
||||
# apply changes
|
||||
self.ldap.modify_s(dn, changes)
|
||||
|
||||
except ldap.LDAPError, e:
|
||||
raise LDAPException("unable to modify: %s" % e)
|
||||
|
||||
|
||||
def user_delete(self, uid):
|
||||
"""
|
||||
Removes a user from the directory.
|
||||
|
||||
Example: connection.user_delete('mspang')
|
||||
"""
|
||||
|
||||
try:
|
||||
dn = 'uid=' + uid + ',' + self.user_base
|
||||
self.ldap.delete_s(dn)
|
||||
except ldap.LDAPError, e:
|
||||
raise LDAPException("unable to delete: %s" % e)
|
||||
|
||||
|
||||
|
||||
### Account-related Methods ###
|
||||
|
||||
def account_lookup(self, uid):
|
||||
"""
|
||||
Retrieve the attributes of an account.
|
||||
|
||||
Parameters:
|
||||
uid - the uid to look up
|
||||
|
||||
Returns: attributes of user with uid
|
||||
"""
|
||||
|
||||
return self.user_lookup(uid, 'posixAccount')
|
||||
|
||||
|
||||
def account_search_id(self, uidNumber):
|
||||
"""
|
||||
Retrieves a list of accounts with a certain UNIX uid number.
|
||||
|
||||
LDAP (or passwd for that matter) does not enforce any restriction on
|
||||
the number of accounts that can have a certain UID number. Therefore
|
||||
this method returns a list of matches.
|
||||
|
||||
Parameters:
|
||||
uidNumber - the user id of the accounts desired
|
||||
|
||||
Returns: the list of uids matched (usernames)
|
||||
Returns: a dictionary mapping uids to attributes
|
||||
|
||||
Example: connection.user_search_id(21292) -> ['mspang']
|
||||
Example: connection.account_search_id(21292) -> {'mspang': { ... }}
|
||||
"""
|
||||
|
||||
# search for posixAccount entries with the specified uidNumber
|
||||
search_filter = '(&(objectClass=posixAccount)(uidNumber=%d))' % uidNumber
|
||||
return self.user_search(search_filter)
|
||||
search_filter = '(&(objectClass=posixAccount)(uidNumber=%s))'
|
||||
return self.user_search(search_filter, [ uidNumber ])
|
||||
|
||||
|
||||
def user_search_gid(self, gidNumber):
|
||||
def account_search_gid(self, gidNumber):
|
||||
"""
|
||||
Retrieves a list of users with a certain UNIX gid
|
||||
Retrieves a list of accounts with a certain UNIX gid
|
||||
number (search by default group).
|
||||
|
||||
Returns: the list of uids matched (usernames)
|
||||
Returns: a dictionary mapping uids to attributes
|
||||
"""
|
||||
|
||||
# search for posixAccount entries with the specified gidNumber
|
||||
search_filter = '(&(objectClass=posixAccount)(gidNumber=%d))' % gidNumber
|
||||
return self.user_search(search_filter)
|
||||
search_filter = '(&(objectClass=posixAccount)(gidNumber=%s))'
|
||||
return self.user_search(search_filter, [ gidNumber ])
|
||||
|
||||
|
||||
def user_add(self, uid, cn, uidNumber, gidNumber, homeDirectory, loginShell=None, gecos=None, description=None):
|
||||
def account_add(self, uid, cn, uidNumber, gidNumber, homeDirectory, loginShell=None, gecos=None, description=None, update=False):
|
||||
"""
|
||||
Adds a user to the directory.
|
||||
Adds a user account to the directory.
|
||||
|
||||
Parameters:
|
||||
uid - the UNIX username for the account
|
||||
|
@ -235,6 +290,7 @@ class LDAPConnection(object):
|
|||
loginShell - login shell for the user
|
||||
gecos - comment field (usually stores name etc)
|
||||
description - description field (optional and unimportant)
|
||||
update - if True, will update existing entries
|
||||
|
||||
Example: connection.user_add('mspang', 'Michael Spang',
|
||||
21292, 100, '/users/mspang', '/bin/bash',
|
||||
|
@ -254,69 +310,35 @@ class LDAPConnection(object):
|
|||
'homeDirectory': [ homeDirectory ],
|
||||
'gecos': [ gecos ],
|
||||
}
|
||||
|
||||
|
||||
if loginShell:
|
||||
attrs['loginShell'] = loginShell
|
||||
attrs['loginShell'] = [ loginShell ]
|
||||
if description:
|
||||
attrs['description'] = [ description ]
|
||||
|
||||
try:
|
||||
modlist = ldap.modlist.addModlist(attrs)
|
||||
self.ldap.add_s(dn, modlist)
|
||||
|
||||
old_entry = self.user_lookup(uid)
|
||||
if old_entry and 'posixAccount' not in old_entry['objectClass'] and update:
|
||||
|
||||
attrs.update(old_entry)
|
||||
attrs['objectClass'] = list(attrs['objectClass'])
|
||||
attrs['objectClass'].append('posixAccount')
|
||||
if not 'shadowAccount' in attrs['objectClass']:
|
||||
attrs['objectClass'].append('shadowAccount')
|
||||
|
||||
modlist = ldap.modlist.modifyModlist(old_entry, attrs)
|
||||
self.ldap.modify_s(dn, modlist)
|
||||
|
||||
else:
|
||||
|
||||
modlist = ldap.modlist.addModlist(attrs)
|
||||
self.ldap.add_s(dn, modlist)
|
||||
|
||||
except ldap.LDAPError, e:
|
||||
raise LDAPException("unable to add: %s" % e)
|
||||
|
||||
|
||||
def user_modify(self, uid, attrs):
|
||||
"""
|
||||
Update user attributes in the directory.
|
||||
|
||||
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)
|
||||
"""
|
||||
|
||||
if not self.connected(): raise LDAPException("Not connected!")
|
||||
|
||||
# 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:
|
||||
|
||||
# build list of modifications to make
|
||||
changes = ldap.modlist.modifyModlist(old_user, attrs)
|
||||
|
||||
# apply changes
|
||||
self.ldap.modify_s(dn, changes)
|
||||
|
||||
except ldap.LDAPError, e:
|
||||
raise LDAPException("unable to modify: %s" % e)
|
||||
|
||||
|
||||
def user_delete(self, uid):
|
||||
"""
|
||||
Removes a user from the directory.
|
||||
|
||||
Example: connection.user_delete('mspang')
|
||||
"""
|
||||
|
||||
if not self.connected(): raise LDAPException("Not connected!")
|
||||
|
||||
try:
|
||||
dn = 'uid=' + uid + ',' + self.user_base
|
||||
self.ldap.delete_s(dn)
|
||||
except ldap.LDAPError, e:
|
||||
raise LDAPException("unable to delete: %s" % e)
|
||||
|
||||
|
||||
|
||||
### Group-related Methods ###
|
||||
|
||||
|
@ -355,27 +377,19 @@ class LDAPConnection(object):
|
|||
try:
|
||||
search_filter = '(&(objectClass=posixGroup)(gidNumber=%d))' % gidNumber
|
||||
matches = self.ldap.search_s(self.group_base, ldap.SCOPE_SUBTREE, search_filter)
|
||||
except ldap.LDAPError,e :
|
||||
except ldap.LDAPError, e:
|
||||
raise LDAPException("group search failed: %s" % e)
|
||||
|
||||
# list for groups found
|
||||
group_cns = []
|
||||
|
||||
results = {}
|
||||
for match in matches:
|
||||
dn, attributes = match
|
||||
dn, attrs = match
|
||||
uid = attrs['cn'][0]
|
||||
results[uid] = attrs
|
||||
|
||||
# cn is a required attribute of posixGroup
|
||||
if not attributes.has_key('cn'):
|
||||
raise LDAPException(dn + ' (posixGroup) has no cn')
|
||||
|
||||
# do not handle the case of multiple cns for one group (yet)
|
||||
elif len(attributes['cn']) > 1:
|
||||
raise LDAPException(dn + ' (posixGroup) has multiple cns')
|
||||
|
||||
# append the sole uid of this match to the list
|
||||
group_cns.append( attributes['cn'][0] )
|
||||
|
||||
return group_cns
|
||||
return results
|
||||
|
||||
|
||||
def group_add(self, cn, gidNumber, description=None):
|
||||
|
@ -457,8 +471,129 @@ class LDAPConnection(object):
|
|||
raise LDAPException("unable to delete group: %s" % e)
|
||||
|
||||
|
||||
|
||||
### 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_studentid(self, studentid):
|
||||
"""
|
||||
Retrieves a list of members with a certain studentid.
|
||||
|
||||
Returns: a dictionary mapping uids to attributes
|
||||
"""
|
||||
|
||||
search_filter = '(&(objectClass=member)(studentid=%s))'
|
||||
return self.user_search(search_filter, [ studentid ] )
|
||||
|
||||
|
||||
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, studentid, 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
|
||||
studentid - the member's student ID number
|
||||
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 ],
|
||||
'studentid': [ studentid ],
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
|
||||
def member_add_account(self, uid, uidNumber, gidNumber, homeDirectory, loginShell=None, gecos=None):
|
||||
"""
|
||||
Adds login privileges to a member.
|
||||
"""
|
||||
|
||||
return self.account_add(uid, None, uidNumber, gidNumber, homeDirectory, loginShell, gecos, None, True)
|
||||
|
||||
|
||||
|
||||
### Miscellaneous Methods ###
|
||||
|
||||
def escape(self, value):
|
||||
"""
|
||||
Escapes special characters in a value so that it may be safely inserted
|
||||
into an LDAP search filter.
|
||||
"""
|
||||
|
||||
value = str(value)
|
||||
value = value.replace('\\', '\\5c').replace('*', '\\2a')
|
||||
value = value.replace('(', '\\28').replace(')', '\\29')
|
||||
value = value.replace('\x00', '\\00')
|
||||
return value
|
||||
|
||||
|
||||
def used_uids(self, minimum=None, maximum=None):
|
||||
"""
|
||||
Compiles a list of used UIDs in a range.
|
||||
|
@ -543,9 +678,17 @@ if __name__ == '__main__':
|
|||
tushell = '/bin/false'
|
||||
tugecos = 'Test User,,,'
|
||||
tgname = 'testgroup'
|
||||
tmname = 'testmember'
|
||||
tmrname = 'Test Member'
|
||||
tmstudentid = '99999999'
|
||||
tmprogram = 'UBW'
|
||||
tmdesc = 'Test Description'
|
||||
cushell = '/bin/true'
|
||||
cuhome = '/home/changed'
|
||||
curname = 'Test Modified User'
|
||||
cmhome = '/home/testmember'
|
||||
cmshell = '/bin/false'
|
||||
cmgecos = 'Test Member,,,'
|
||||
|
||||
test(LDAPConnection)
|
||||
connection = LDAPConnection()
|
||||
|
@ -563,6 +706,7 @@ if __name__ == '__main__':
|
|||
|
||||
try:
|
||||
connection.user_delete(tuname)
|
||||
connection.user_delete(tmname)
|
||||
connection.group_delete(tgname)
|
||||
except LDAPException:
|
||||
pass
|
||||
|
@ -596,8 +740,20 @@ if __name__ == '__main__':
|
|||
'cn': [ turname ]
|
||||
}
|
||||
|
||||
test(LDAPConnection.user_add)
|
||||
connection.user_add(tuname, turname, tuuid, tugid, tuhome, tushell, tugecos)
|
||||
test(LDAPConnection.account_add)
|
||||
connection.account_add(tuname, turname, tuuid, tugid, tuhome, tushell, tugecos)
|
||||
success()
|
||||
|
||||
emdata = {
|
||||
'uid': [ tmname ],
|
||||
'cn': [ tmrname ],
|
||||
'studentid': [ tmstudentid ],
|
||||
'program': [ tmprogram ],
|
||||
'description': [ tmdesc ],
|
||||
}
|
||||
|
||||
test(LDAPConnection.member_add)
|
||||
connection.member_add(tmname, tmrname, tmstudentid, tmprogram, tmdesc)
|
||||
success()
|
||||
|
||||
tggid = unusedids.pop()
|
||||
|
@ -610,41 +766,95 @@ if __name__ == '__main__':
|
|||
connection.group_add(tgname, tggid)
|
||||
success()
|
||||
|
||||
test(LDAPConnection.account_lookup)
|
||||
udata = connection.account_lookup(tuname)
|
||||
if udata: del udata['objectClass']
|
||||
assert_equal(eudata, udata)
|
||||
success()
|
||||
|
||||
test(LDAPConnection.member_lookup)
|
||||
mdata = connection.member_lookup(tmname)
|
||||
if mdata: del mdata['objectClass']
|
||||
assert_equal(emdata, mdata)
|
||||
success()
|
||||
|
||||
test(LDAPConnection.user_lookup)
|
||||
udata = connection.user_lookup(tuname)
|
||||
del udata['objectClass']
|
||||
mdata = connection.user_lookup(tmname)
|
||||
if udata: del udata['objectClass']
|
||||
if mdata: del mdata['objectClass']
|
||||
assert_equal(eudata, udata)
|
||||
assert_equal(emdata, mdata)
|
||||
success()
|
||||
|
||||
test(LDAPConnection.group_lookup)
|
||||
gdata = connection.group_lookup(tgname)
|
||||
del gdata['objectClass']
|
||||
if gdata: del gdata['objectClass']
|
||||
assert_equal(egdata, gdata)
|
||||
success()
|
||||
|
||||
test(LDAPConnection.user_search_id)
|
||||
test(LDAPConnection.account_search_id)
|
||||
eulist = [ tuname ]
|
||||
ulist = connection.user_search_id(tuuid)
|
||||
ulist = connection.account_search_id(tuuid).keys()
|
||||
assert_equal(eulist, ulist)
|
||||
success()
|
||||
|
||||
test(LDAPConnection.user_search_gid)
|
||||
ulist = connection.user_search_gid(tugid)
|
||||
test(LDAPConnection.account_search_gid)
|
||||
ulist = connection.account_search_gid(tugid)
|
||||
if tuname not in ulist:
|
||||
fail("(%s) not in (%s)" % (tuname, ulist))
|
||||
fail("%s not in %s" % (tuname, ulist))
|
||||
success()
|
||||
|
||||
ecudata = connection.user_lookup(tuname)
|
||||
test(LDAPConnection.member_search_studentid)
|
||||
mlist = connection.member_search_studentid(tmstudentid).keys()
|
||||
emlist = [ tmname ]
|
||||
assert_equal(emlist, mlist)
|
||||
success()
|
||||
|
||||
test(LDAPConnection.member_search_name)
|
||||
mlist = connection.member_search_name(tmrname)
|
||||
if tmname not in mlist:
|
||||
fail("%s not in %s" % (tmname, mlist))
|
||||
success()
|
||||
|
||||
test(LDAPConnection.member_search_program)
|
||||
mlist = connection.member_search_program(tmprogram)
|
||||
if tmname not in mlist:
|
||||
fail("%s not in %s" % (tmname, mlist))
|
||||
success()
|
||||
|
||||
test(LDAPConnection.group_search_id)
|
||||
glist = connection.group_search_id(tggid).keys()
|
||||
eglist = [ tgname ]
|
||||
assert_equal(eglist, glist)
|
||||
success()
|
||||
|
||||
ecudata = connection.account_lookup(tuname)
|
||||
ecudata['loginShell'] = [ cushell ]
|
||||
ecudata['homeDirectory'] = [ cuhome ]
|
||||
ecudata['cn'] = [ curname ]
|
||||
|
||||
test(LDAPConnection.user_modify)
|
||||
connection.user_modify(tuname, ecudata)
|
||||
cudata = connection.user_lookup(tuname)
|
||||
cudata = connection.account_lookup(tuname)
|
||||
assert_equal(ecudata, cudata)
|
||||
success()
|
||||
|
||||
tmuid = unusedids.pop()
|
||||
tmgid = unusedids.pop()
|
||||
emadata = emdata.copy()
|
||||
emadata.update({
|
||||
'loginShell': [ cmshell ],
|
||||
'uidNumber': [ str(tmuid) ],
|
||||
'gidNumber': [ str(tmgid) ],
|
||||
'gecos': [ cmgecos ],
|
||||
'homeDirectory': [ cmhome ],
|
||||
})
|
||||
|
||||
test(LDAPConnection.member_add_account)
|
||||
connection.member_add_account(tmname, tmuid, tmuid, cmhome, cmshell, cmgecos)
|
||||
success()
|
||||
|
||||
ecgdata = connection.group_lookup(tgname)
|
||||
ecgdata['memberUid'] = [ tuname ]
|
||||
|
||||
|
|
Loading…
Reference in New Issue