Customizing LUMP
LUMP, the backend code for LORLS 6/7, has quite a few features, but it can’t live in a vacuum. You need to be able to integrate LORLS 6/7 with other parts of your organisation’s IT infrastructure. For example we could have just provided a standalone authentication system, but most sites already have some form of network capable logins, be it an ActiveDirectory or a home grown LDAP based system. It would be nice if LORLS6/7’s LUMP could work with that system to let users login with existing credentials.
How you handle local configurations/modifications really does depend on what your local infrastructure is, how it is set up to work and what your local work flows are. We as developers have little idea of the resources you have to hand and what you need to do to make your particular mix of systems work. However this page tries to give you some broad hints and tips to help in the customization of the LUMP element of LORLS 6/7. Hopefully you’ll find it helpful in customizing and localizing the LUMP backend of your LORLS 6/7 installation.
User Authentication
At Loughborough, we authenticate LUMP users against our Microsoft Active Directory (AD). The actual authentication is done using the Kerberos protocol. To get this working, we first have to ensure that the server (a Redhat Enterprise Linux box in our case) is configured to allow Kerberos tickets to be picked up from the AD. The main guts of this are in the /etc/krb5.conf file – ours looks like this at the moment:
[logging]
default = FILE:/var/log/krb5libs.log
kdc = FILE:/var/log/krb5kdc.log
admin_server = FILE:/var/log/kadmind.log
[libdefaults]
default_realm = LUNET.LBORO.AC.UK
dns_lookup_realm = false
dns_lookup_kdc = true
ticket_lifetime = 24h
forwardable = yes
[realms]
LUNET.LBORO.AC.UK= {
kdc = lunet.lboro.ac.uk:88
admin_server = lunet.lboro.ac.uk:749
default_domain = lunet.lboro.ac.uk
}
[domain_realm]
lunet.lboro.ac.uk = LUNET.LBORO.AC.UK
.lunet.lboro.ac.uk = LUNET.LBORO.AC.UK
[appdefaults]
pam = {
debug = false
ticket_lifetime = 36000
renew_lifetime = 36000
forwardable = true
krb4_convert = false
}
For your setup you’ll need to tweak this file to fill in details of your AD default_realm, servers, domain, etc (ie replace anything that looks like lunet.lboro.ac.uk!).
We can check this is working from the command line by using the kinit command:
kinit username
This will ask for our AD password and then, assuming the authentication is successful we can check the tickets granted using the klist command. If everything is working you should see a krbtgt (Kerberos Ticket Granting Ticket) listed.
Once we know Kerberos on the server is working OK, we can then let the LUMP code make use of it. The password checking functions are in the LUMP.pm Perl library module in the CheckPassword() method. You need to make sure that the server priniciple variable in that routine ($serverp) has the right name – for our AD it looks like:
my $serverp = Authen::Krb5::parse_name('krbtgt/LUNET.LBORO.AC.UK@LUNET.LBORO.AC.UK');
Obviously you’ll need to change the LUNET.LBORO.AC.UK@LUNET.LBORO.AC.UK to whatever realm you use at your site – the installer should do this for you if you tell it the details when you do the installation. Once you’ve got this set, folk should be able to authenticate in LORLS 6/7 using their AD credentials. Woohoo!
But what if you don’t have an AD or the ability to use Kerberos? Well, in the demo system we don’t use our AD or Kerberos but instead include some hardcoded test accounts. If you look in the CheckPassword() method in LUMP.pm you’ll see there’s a few (potentially commented out) lines that say:
# # Demo system hack!
# if(!$ok) {
# $ok = 1 if($username eq "aker" && $password eq "demic");
# $ok = 1 if($username eq "libby" && $password eq "rarian");
# }
If you remove the hashes from the start of these lines, you can turn these hardcoded accounts back on. You can could also add additional accounts along the same lines. This will work, but obviously isn’t as scalable as integrating with an AD – handy for initial system testing though (remember to either change the usernames/passwords or turn them off afterwards though!).
However it should be noted that this isn’t an ‘either-or’ situation – you can both attempt authentication against Kerberos and then, if it fails, fall back to some hard coded usernames and passwords. You could also slip in some code to interact with other, non-Kerberized authentication mechanisms in here (eg talking to an LDAP directory). Its just limited by your coding ability and availabililty of Perl tools for talking to whatever your infrastructure is using.
OK, so the users can now be authenticated but we don’t really know much about them. LORLS 6/7 can make use of additional data about the user, such as their full name and email address. The LUMP API code will call the ExternalUserLookup() method in LUMP.pm to do this, but this routine is sneaky: it doesn’t really do anything other than attempt to call a method called User_Lookup() in an external Perl module called LOCAL_User_Lookup.pm. You don’t need to edit LUMP.pm to use this – its LOCAL_User_Lookup.pm that you need to provide/tweak.
Now LOCAL_User_Lookup.pm is, as the name suggests, something that needs to take account of the local sitatuion. If you don’t provide it, LORLS will just move on (it traps the error of the file not being there or not working). However if you do provide it, you can take the user’s username in the User_Lookup() method and return three things:
- Boolean indicating if they were found in whatever directory service was queried
- The full, human readable name of the user (eg: “Dr R.U.Listening”)
- The user’s email address (eg r.u.listening@example.com)
Now as has been stressed, this is a LOCAL directory lookup – we don’t know what hoops you might need to go through to get this information in your local environment. However for those of you playing along at home with an AD, here’s some cut-out-and-keep Perl taken from our particular LOCAL_User_Lookup.pm file:
#!/usr/bin/perl
package LOCAL_User_Lookup;
use Net::LDAP;
sub User_Lookup {
my $username = shift;
my $found = 0;
my $fullname = '';
my $email = '';
my @ldap_servers =
(
{
host => 'lunet.lboro.ac.uk',
binduser => 'service_account@lunet.lboro.ac.uk',
password => 'Yeah, This is really our phrase. NOT!',
base => 'dc=lunet,dc=lboro,dc=ac,dc=uk',
},
);
my $searchpath = '';
my $ldap;
foreach my $this_ldap_server (@ldap_servers) {
my $host = $this_ldap_server->{host};
my $binduser = $this_ldap_server->{binduser};
my $password = $this_ldap_server->{password};
my $base = $this_ldap_server->{base};
my $ldap = Net::LDAP->new($host);
next if(!defined($ldap));
my $mesg = $ldap->bind($binduser,
password => $password);
next if($mesg->is_error);
$mesg = $ldap->search(base => $base,
filter => "sAMAccountName=$username");
my @entries = $mesg->entries;
foreach my $entry (@entries) {
foreach my $attr ( $entry->attributes ) {
if($attr eq 'mail') {
$email = $entry->get_value( $attr );
$found++;
} elsif($attr eq 'displayName') {
$fullname = $entry->get_value( $attr );
$found++;
}
}
}
}
return($found, $fullname, $email);
}
1;
You might find that’ll work on your AD as well if you use a pretty vanilla schema, though you’ll obviously need to tweak the LDAP server credentials and the organizational unit (OU) that you’re looking at. If you don’t know what these are, you’ll need to talk to whoever runs your AD. If they don’t know what these are then, er, they should! 🙂 You can also include “fudges” for accounts not in the AD (the aker and libby hard coded accounts for example) or talk to more than one AD (we had to do that for a while during development as the University was moving from one AD tree to another – that’s why we have the @ldap_servers array in that example code as it once had more than one LDAP server to query: one in each AD).
Adding a user to the Sysadmin usergroup
When you install LUMP, you can elect to set up an admin account, to allow you to perform some of the admin tasks. This account is linked to a special usergroup in the database called “Sysadmins”. Now having more than one admin account might be handy for some organisations. For example you might want to allow folk who work in the Library systems team to all log in as admins so that they can see all the reading lists in the system in order to easily help other library staff and academics with issues. Admins can also do sexy things like create new structural unit types, data type groups and data types – effectively making the structure of the data held in the system open ended and configurable to your local needs.
To add a user to the Sysadmin groups we’ll nip into the database using the mysql command line tool to runs some SQL queries against whatever database LUMP is using (by default the handily named ‘LUMP’). The first thing to do is find the ID of the Sysadmin group in the usergroup table:
select id from usergroup where group_name = "Sysadmins";
This will return a number (for example 3 in our dev installation). Next we need to know the ID of the user we want to “admin up”. Lets assume their username is “lbres” for this example. So we’ll need the SQL:
select id from user where username = "lbres";
This gives us the user’s ID (lets say 59). We now need to link the user to the Sysadmin group by popping both of these numbers into the user_usergroup_link table:
insert into user_usergroup_link (user_id, usergroup_id, created_time, created_by, modified_time, modified_by) values (59, 3, now(), 'me', now(), 'me');
Replace ‘me’ with whatever your username is and Bob’s your aunt’s husband.
You shouldn’t need to do this very often (which is why we’ve not bothered making a web based tool to do this yet!).
If you want to add and remove people from groups programmatically there are some XML APIs you can make use of: Editing/AddUser2Usergroup and Editing/RemoveUserFromUsergroup. There’s also Members4Usergroup, UserGroupMembership, Editing/AddUsergroup and Editing/RemoveUsergroup XML APIs for querying and editing usergroup information. All these XML CGI APIs have embedded POD (plain old documentation!) so you can use perldoc to extract a handy manual page (eg perldoc UserGroupMembership) to find out how to call them (you can use more or less any programming language that can make HTTP transactions – Perl, Python, Javascript, C, etc, etc. If you’re really sick, even Java. 🙂 ).
Looking up item availability from your Library Management System
LUMP provides the backend database and API for the LORLS 6/7 reading list system, but its not going to be the only database that libraries run. The most important database in most libraries is the Library Management System (LMS) that provides the OPAC, stock management, issuing, etc services. LUMP can integrate with many LMS products using a couple of mechanisms.
The first, and most standardised, the use of Z39.50. In LUMP.pm you’ll see a number of methods that are named Z3950Hostname(), Z3950Port(), Z3950DBName(), Z3950Username(), Z3950Password(), MaxZ3950Matches() and Z3950ConnectionLifespan(). If you’ve used a Z39.50 server on your LMS before the first five of those should be pretty self-explanatory – they’re the hostname of the server that is running the Z39.50 server, the port that it is running on (usually 210), the name of the Z39.50 database to query and the username/password required to connect (if any).
MaxZ3950Matches() and Z3950ConnectionLifespan() need a little more explanation, although to start with you’re probably best leaving them at their default values. MaxZ3950Matches() tells LUMP how many Z39.50 matched records to pull at once. For some Z39.50 servers this is a bit pointless but in other LMS system, especially ones where there might be an element of charging by the vendor for Z39.50 queries or results, we might want to limit it. By default we opt for 10, as that seems to work OK for us (both on our current Ex Libris Aleph system and the previous Talis system).
Z3950ConnectionLifespan() tells LUMP how many Z39.50 queries it can make on a Z39.50 connection before it should tear it down and build a new one. In an ideal world this wouldn’t be needed, but in the past we’ve found some (cough commercial cough) Z39.50 servers could get into a bit of tizzy if you pumped lots of different queries down one connection. The default is 50 which has worked for us at Loughborough. If you’ve got a particularly tizzy prone Z39.50 server you might need to reduce it!
As well as Z39.50, we can also make use of Ex Libris’s X Server system in order to acquire holdings information. This is used to help populate the “Held by Library” data elements and also in the CLUMP front end to display a table of current holding statuses for books, etc. Now obviously not everyone has an X Server – if you don’t you’ll find the Holdings() method in StructuralUnit.pm doesn’t return anything. I’m afraid you’ll either need to:
- Put up with this!
- Buy Ex Libris’s X Server based products
- Work out how to acquire holdings from your LMS and replace the guts of Holdings() with new code
If you do code up the last of these options we’d love to hear about it and, if possible, include your code as an option in future distributions.
However assuming you do have an X server, how do you use it? First off you’ll either have had to tell the installer script about it or (if you forgot/didn’t have the details/whatever) configure a little snippet of Perl that can be read in to provide details of your X server and its login. This lives in the LUMP module directory (probably /usr/local/LUMP/holding_logins unless you’ve opted to put it somewhere else) and looks something like this:
$host="http://aleph.mysite.ac.uk/X";
$bib_base = "mys01";
$bib_library = "mys01";
$user_name="lorls6user";
$user_password="top sekret password";
This is eval()’ed a line at a time into the StructuralUnit.pm module when the Holdings() method is used. As long as you actually set those variables with valid values you should be OK. Those values above are, naturally enough, just made up!
The Holdings() method in StructuralUnit.pm then compares loan-status fields returned in the X Server XML stream for queries for a work with a number of states we use at Loughborough – ‘Long loan’, ‘Week loan’, ‘Short loan’, ‘Reference’, ‘Software’, ‘Bound items loanable to staff’ and ‘On Order’. We also look at the due-date field and if it is ‘On Shelf’ increment the number of available copies. Now if your system has different loan-statuses you might well need to tweak the code in the Holdings() method to reflect that – the due-date of ‘On Shelf’ should be the default though.