Get working OpenSRF integration, genericize a bit
authorDan Scott <dan@coffeecode.net>
Wed, 7 Sep 2011 07:12:56 +0000 (03:12 -0400)
committerDan Scott <dscott@laurentian.ca>
Tue, 7 May 2013 18:38:28 +0000 (14:38 -0400)
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 <dscott@laurentian.ca>
tools/patron-load/ldap_osrf_sync

index f2855b3..8786add 100644 (file)
@@ -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: