From 4ab4b18e965ebc072a904697431b2e55e2803f5c Mon Sep 17 00:00:00 2001 From: Dan Scott Date: Wed, 7 Sep 2011 03:12:56 -0400 Subject: [PATCH] Get working OpenSRF integration, genericize a bit Still need to teach the beast how to provision barcodes, then write those back to LDAP as well. Pushing the LDAP mapping into the User class init method seems like a reasonable way to genericize this a bit; sites could replace the User class implementation. Signed-off-by: Dan Scott --- tools/patron-load/ldap_osrf_sync | 284 +++++++++++++++++++++++++++------------ 1 file changed, 196 insertions(+), 88 deletions(-) diff --git a/tools/patron-load/ldap_osrf_sync b/tools/patron-load/ldap_osrf_sync index f2855b3ecb..8786add17f 100644 --- a/tools/patron-load/ldap_osrf_sync +++ b/tools/patron-load/ldap_osrf_sync @@ -17,6 +17,7 @@ file (credentials.py) and imported to avoid storing credentials in the VCS. 3. Dump the output of new ident_value + barcode in CSV format somewhere. """ +import os import sys import ldap @@ -25,11 +26,126 @@ import oils.utils.idl import oils.utils.utils import osrf.gateway import osrf.json +import osrf.net_obj import tempfile import urllib2 import credentials +class User: + """ + Holds data mapped from LDAP schema to Evergreen attributes + + Less formal than an actual 'au' object + """ + + def __init__(self, raw_ldap): + """ + Map core LDAP schema attributes to Evergreen user bits + + Easier to replace hard-coded mappings with calls to functions + """ + + self.raw_ldap = raw_ldap + + if 'mail' not in raw_ldap: + print >> sys.stderr, 'mail not found for %s' % raw_ldap['cn'] + return None + + # Strip leading/ending whitespace; LDAP data can be dirty + + # Using email for username deliberately here + self.usrname = raw_ldap['mail'][0].strip().lower() + self.email = raw_ldap['mail'][0].strip().lower() + self.family_name = raw_ldap['sn'][0].strip() + self.ident_type, self.ident_value = self.get_identity() + self.home_ou = self.get_home_ou() + + # Randomized password, assuming user will use "Forgot password" + # function to set their own password + self.passwd = oils.utils.utils.md5sum(os.urandom(10)) + + if 'givenName' in raw_ldap: + self.first_given_name = raw_ldap['givenName'][0].strip() + else: + self.first_given_name('LDAP_NULL') + print >> sys.stderr, 'No givenName for %s' % (self.usrname) + + self.profile = self.get_profile() + + def get_identity(self): + """ + Map LDAP record to Evergreen identity type and value + """ + + ident_value = self.raw_ldap['lulColleagueId'][0].strip().lower() + if len(ident_value) != 7: + print >> sys.stderr, 'Datatel number not 7 chars for %s (%s)' % ( + self.usrname, ident_value + ) + if len(ident_value) == 6: + ident_value = '0%s' % ident_value + elif len(ident_value) == 5: + ident_value = '00%s' % ident_value + + return 2, ident_value + + def get_profile(self): + """ + Map LDAP record to Evergreen profile + """ + + if 'lulStudentLevel' in self.raw_ldap: + affiliation = self.raw_ldap['lulStudentLevel'][0].strip().lower() + elif 'lulPrimaryAffiliation' in self.raw_ldap: + affiliation = self.raw_ldap['lulPrimaryAffiliation'][0].strip().lower() + else: + affiliation = r'\N' + + if affiliation == 'ug' or affiliation == 'student': + return 13 + elif affiliation == 'gr': + return 12 + elif affiliation == 'al': + return 14 + elif affiliation == 'faculty': + return 11 + elif affiliation == 'staff': + return 15 + + def get_home_ou(self): + """ + Map LDAP record to Evergreen home library + """ + + if 'laurentian.ca' or 'laurentienne.ca' in self.email: + return 103 + elif 'huntingtonu.ca' in self.email: + return 104 + elif 'usudbury.ca' in self.email: + return 107 + + # Default to Laurentian + return 103 + +class AuthException(Exception): + """ + Exceptions for authentication events + """ + + def __init__(self, msg=''): + """ + Initialize the authentication exception + """ + Exception.__init__(self) + self.msg = msg + + def __str__(self): + """ + Stringify the authentication exception + """ + return 'AuthException: %s' % self.msg + def load_idl(): """ Loads the fieldmapper IDL, registering class hints for the defined objects @@ -62,23 +178,24 @@ def load_idl(): parser.set_IDL(idlfile) parser.parse_IDL() -def login(username, password, workstation=None): +def osrf_login(username, password, workstation=None): """ Login to the server and get back an authtoken """ __authtoken = None - print("attempting login with user " + username) - - seed = request( - 'open-ils.auth', - 'open-ils.auth.authenticate.init', username).send() + try: + seed = osrf_request( + 'open-ils.auth', + 'open-ils.auth.authenticate.init', username).send() + except Exception, exc: + print exc # generate the hashed password password = oils.utils.utils.md5sum(seed + oils.utils.utils.md5sum(password)) - result = request( + result = osrf_request( 'open-ils.auth', 'open-ils.auth.authenticate.complete', { 'workstation' : workstation, @@ -94,7 +211,7 @@ def login(username, password, workstation=None): __authtoken = result['payload']['authtoken'] return __authtoken -def request(service, method, *args): +def osrf_request(service, method, *args): """ Make a JSON request to the OpenSRF gateway @@ -108,24 +225,6 @@ def request(service, method, *args): req.setPath(credentials.GATEWAY_URL) return req -class AuthException(Exception): - """ - Exceptions for authentication events - """ - - def __init__(self, msg=''): - """ - Initialize the authentication exception - """ - Exception.__init__(self) - self.msg = msg - - def __str__(self): - """ - Stringify the authentication exception - """ - return 'AuthException: %s' % self.msg - def datatel_to_barcode(datatel): """ Converts a Datatel Colleague ID into a barcode @@ -182,7 +281,7 @@ def mod10_checksum(barcode): return 10 - rem return rem -def find_new_ldap_users(con, attributes, create_date): +def find_new_ldap_users(con, attributes, create_date, auth): """ Retrieve personnel accounts from LDAP directory and process'em """ @@ -200,65 +299,63 @@ def find_new_ldap_users(con, attributes, create_date): break else: # dump_data(result_data) - create_evergreen_user(result_data[0][1]) - except ldap.LDAPError, e: - print >> sys.stderr, e + create_evergreen_user(result_data[0][1], auth) + except ldap.LDAPError, exc: + print >> sys.stderr, exc -def create_evergreen_user(result_data): +def create_evergreen_user(result_data, auth): """ - Generate statements to push data into the staging table + Map LDAP record to Evergreen user """ - if 'mail' not in result_data: - print >> sys.stderr, 'mail not found for %s' % result_data['cn'] + user = User(result_data) + if not user: return - newau = oils.utils.idl.IDLParser.get_class('au')() + newau = osrf.net_obj.new_object_from_hint('au') + newau.isnew(True) + newau.usrname(user.usrname) + newau.profile(user.profile) + newau.email(user.email) + newau.family_name(user.family_name) + newau.first_given_name(user.first_given_name) + newau.ident_type(user.ident_type) + newau.ident_value(user.ident_value) + newau.home_ou(user.home_ou) + newau.passwd(user.passwd) + + # Create the user + try: + result = osrf_request( + 'open-ils.actor', 'open-ils.actor.patron.update', auth, newau + ).send() + except Exception, exc: + print exc - # Strip leading/ending whitespace - newau.usrname(result_data['mail'][0].strip().lower()) - newau.email(result_data['mail'][0].strip().lower()) - newau.family_name(result_data['sn'][0].strip().lower()) - newau.ident_value(result_data['lulColleagueId'][0].strip().lower()) - - if 'givenName' in result_data: - newau.given_name(result_data['givenName'][0].strip()) - else: - newau.given_name('LDAP_NULL') - print >> sys.stderr, 'No givenName for %s' % (newau.usrname()) - - if len(newau.ident_value()) != 7: - print >> sys.stderr, 'Datatel number not 7 chars for %s (%s)' % ( - newau.usrname(), newau.datatel() - ) - if len(newau.datatel()) == 6: - newau.datatel('0%s' % newau.datatel()) - elif len(newau.datatel()) == 5: - newau.datatel('00%s' % newau.datatel()) + evt = oils.event.Event.parse_event(result) + if evt and not evt.success: + raise AuthException(evt.text_code) + + # XXX Create barcode... how? + # Trigger on actor.usr? New custom API? + + create_stat_cats(newau, result_data) + + print(newau) + +def create_stat_cats(user, result_data): + """ + Map statistical categories + """ + # Reasonable default + lang = 'E' if 'preferredLanguage' in result_data: lang = result_data['preferredLanguage'][0].strip() - else: - lang = r'\N' - - if 'lulStudentLevel' in result_data: - affiliation = result_data['lulStudentLevel'][0].strip().lower() - elif 'lulPrimaryAffiliation' in result_data: - affiliation = result_data['lulPrimaryAffiliation'][0].strip().lower() - else: - affiliation = r'\N' - - if affiliation == 'ug' or affiliation == 'student': - newau.profile(13) - elif affiliation == 'gr': - newau.profile(12) - elif affiliation == 'al': - newau.profile(14) - elif affiliation == 'faculty': - newau.profile(11) - elif affiliation == 'staff': - newau.profile(15) + + # XXX Now we should generate stat cats eh? + def dump_data(result_data): """ @@ -270,24 +367,27 @@ def dump_data(result_data): for key in result_data[0][1]: print(key, result_data[0][1][key]) -def generate_ldap_sql(create_date): +def ldap_create_by_date(create_date, auth): """ - Generate the SQL required to create and update Evergreen accounts + Connect to LDAP directory and process users created since a given date """ con = ldap.initialize(credentials.LDAP_HOST) con.set_option(ldap.OPT_REFERRALS, 0) try: - attributes = ['lulStudentLevel', 'lulPrimaryAffiliation', 'cn', 'mail', 'givenName', 'sn', 'lulColleagueId', 'preferredLanguage'] + attributes = [ + 'lulStudentLevel', 'lulPrimaryAffiliation', 'cn', 'mail', + 'givenName', 'sn', 'lulColleagueId', 'preferredLanguage' + ] con.simple_bind_s(credentials.LDAP_DN, credentials.LDAP_PW) - find_new_ldap_users(con, attributes, create_date) - except ldap.LDAPError, e: - print >> sys.stderr, "Could not connect: " + e.message['info'] - if type(e.message) == dict and e.message.has_key('desc'): - print >> sys.stderr, e.message['desc'] + find_new_ldap_users(con, attributes, create_date, auth) + except ldap.LDAPError, exc: + print >> sys.stderr, "Could not connect: " + exc.message['info'] + if type(exc.message) == dict and exc.message.has_key('desc'): + print >> sys.stderr, exc.message['desc'] else: - print >> sys.stderr, e + print >> sys.stderr, exc sys.exit() finally: con.unbind() @@ -295,7 +395,6 @@ def generate_ldap_sql(create_date): if __name__ == '__main__': import doctest doctest.testmod() - exit() # Set the host for our requests osrf.gateway.GatewayRequest.setDefaultHost(credentials.OSRF_HOST) @@ -304,8 +403,17 @@ if __name__ == '__main__': load_idl() # Log in and get an authtoken - authtoken = login(credentials.OSRF_USER, credentials.OSRF_PW) - - generate_ldap_sql('20110701') + AUTHTOKEN = osrf_login(credentials.OSRF_USER, credentials.OSRF_PW) + + UDATA = { + 'mail': ['dan@example.com'], + 'givenName': ['Dan'], + 'sn': ['Scott'], + 'lulColleagueId': ['0123456'], + 'lulStudentLevel': ['GR'], + } + create_evergreen_user(UDATA, AUTHTOKEN) + +# ldap_create_by_date('20110701') # vim: et:ts=4:sw=4:tw=78: -- 2.11.0