Hi Ryan,
I'm not sure whether I'd be able to share our code on this one - I'd have to check with my superiors but I'll let you know.
In order to get it to work, we use the existing Apache2 cosign filter, and protect a single directory within our installation. In our case, we've opted to:
- protect /login
- unprotect but use SetHandler cosign on /cosign/valid
For the most part, the basic plugin is just standard plugin boilerplate. We define a list of userfields (things like firstname, lastname, email, department, etc), a constructor to grab config and set the authtype value, and a few other bits.
We've defined a function 'get_cosign_username' which retrieves the username from the $_SERVER variable and passes it through strtolower. This checks that that AUTH_TYPE is 'Cosign' and that a remote user value is set before return that REMOTE_USER. We also have a function to check whether the account is a friend account (we allow these but they exist in a different LDAP tree to our core users).
The interesting bits come when we look at the loginpage_hook() and user_login() functions.
In loginpage_hook(), we check that we have a valid username, and then add that username to the existing global $frm. $frm is used on the login page for the username and password (and a few other bits). As I recall, loginpage_hook() is called as a user is being logged in. By setting the username based on get_cosign_username(), and a fixed password (we obviously don't have access to the real passwords but have to provide something so we make something up), this means that when the Moodle login functions are processed, we have a username and password already filled in. If they correspond to a user in Moodle, then login should be successful.
Again, the user_login() function is interesting. It's called as part of the login process and must return true for a valid user. We confirm again that we were supplied with a username by cosign (get_cosign_username() again). We then check whether the user has been created in Moodle yet - if not, we create the user and redirect them back to the /login page where they get automatically logged in. Otherwise, we return truthfully.
We also have a logoutpage_hook() to ensure that when a user logs out, we log them out of Cosign, and redirect them to the cosign logout page (set via a config option on our plugin).
The only remaining bit really is pretty much copied from the ldap plugin - without looking into that part of the plugin, I couldn't tell you exactly how it works. Will try and do so when I get a chance. We call the ldap functions from the user_login function, but I forget the details of how we do so. We've also substantially rewritten how we do this recently to cater for our specific requirements.
This is a 2.X plugin only - we're running 2.5 having started with Moodle 2.2 on our Moodle pilot 2 years ago.
The multiple identities thing is an interesting one and came about as a result of some other changes we made recently.
To start with, we sometimes have a requirement to allow cosign friends into Moodle so, as I mentioned before, we have a function to check whether a user is a cosign friend, and if so we access a different LDAP tree for user profile retrieval.
We also have a separate tree for our applicants, and for short-term user accounts which are provided to one or two day courses that some departments run. Again, we have corresponding functions in our plugin which detect these based on the cosign realm (again, provided in $_SERVER).
All in all, this means that we just query different parts of the tree based on the type of Cosign user.
The magic bit came about because some of these users (e.g. Applicants) migrate from being in one tree to another (our standard user accounts). We needed to be able to detect this, and update their user profile accordingly. Thankfully, our identity system (I wouldn't go so far as to call it an IDM and it's entirely custom-written and hooks into all sorts of areas of other software) ties all of these accounts together inside a single user record. It has the facility to present these to LDAP with all relevant details as the relevant user accounts in the relevant trees. Thusly, a single person may have as many accounts as they like. Helpfully, it adds a custom LDAP attribute to help us to tie them all together. The ownerCID field will be the same for each of these users. We store that CID field against a Moodle user id in a separate table (auth_cosign_cid_mapping). That means that when a user logs in, we retrieve their ownerCID, look for it in that table, retrieve the user account details associated with that particular mapping, and in the loginpage_hook(), we set the username to the username against this record. We do this with a function called get_canonical_user() (or something like that - this is from memory). Wherever we get a username from cosign, we now call get_canonical_user() and use the username field that it returns instead of the account that the user was authenticated against. This means that whichever account they authenticate against, as long as the CID matches, they can log in with.
This does have some limitations - you cannot merge accounts so you need to make sure that they're all valid; and it has the effect of preventing login if the username and CID do not match for a username in use in Moodle. We actually see this as a feature funnily enough - we re-use our usernames a year after a user has been removed (which is 9 months after they leave the institution). As a result, we need to make sure that we soft delete old users so that their usernames can be reused without risk of data breach. This provides us with an additional backstop to make sure we don't hit that kind of issue.
The CID matching also means that we run a CLI sync script (similar in nature to the LDAP script found in auth/ldap/cli). This fills Moodle with all of the users in our main LDAP tree, as well as all of those in one of our other trees but not those cosign friend accounts. This isn't strictly necessary, but it does mean that all valid user accounts are available to be used by staff when they want to enrol a user. This is also where we handle account deletion (soft delete), account restoration (if a user comes back several years later and is given a user account, all of their previous data is restored to them), account renames (usernames can change), account migrations (a user changes tree for some reason), and the like.
We only do a soft delete because the idea of removing data scares the bejesus out of me. A full user_delete() also removes a lot of other data. IIRC it removes things like grades and enrolments. Quite frankly, I'd really rather this didn't happen from a cron job. So we set the user deleted field, rename the user as user_delete() would, and can the e-mail address in the same way too. The user account is 'deleted', but the data associated with it is not.
So yeah, in a nut-shell, that's what we do. I'll see whether I'm allowed to release our plugin. It may be that we can release an older version without the user identity tomfoolery. As I say, some of the changes we've made have made it very specific to our setup.
Andrew