Notes for a User/Auth architecture
UmlautAuth Module: Developer notes and documentation.
One question, do you guys need to handle IP-address recognition too? I have some cases where I need to make something available to a logged in user _or_ a user from a recognized IP, etc. But I haven't quite worked through how to support that architecturally. So if you guys dont' need it yet, we'll just ignore it for now. :)
1. USER MODEL
So there will be a User model in the database. (There's one there now, but it's not being used for anything. Take it over to make it what we want). It'll have a unique account name (with unique index), support a few standard attributes (firstname, lastname, email, maybe even cell number for your txt messaging stuff), and also support an arbitrary hash of key/values, the UserAttributes you were talking about. it might make sense to have some abstract notion of 'group membership' built in standard, not just in the arbitrary hash, since this is such a common pattern, but not sure how to do that.
(That arbitrary hash of UserValues could be implemented in a normal rdbms normalized way -- or could just be a hash serialized to a single column. Rails supports the latter easily. Not sure what is best -- normalized would allow you to query on it, the serialized hash would actually be more efficient in the app, and easier to work with. Serialized hash also easily allows the values to be arrays or other complex data types. I think I lean toward serialized hash.)
Everyone will use this model, no need for a custom sub-class. This makes inter-operability with existing plugins easier. Shareable code can assume more or less what a User object will look like.
A logged in user is represented simply by the user pk ID in the session. (This is the Rails best way to do it).
Umlaut will provide some methods for interacting with logged in user and with users in general:
a) hasLoggedInUser ( is there a pk in the session? ) b) loggedInUser (lazy load user from pk in session, return nil if none exists) c) setLoggedInUser( user ) d) logoutUser()
Those are probably all methods in the Application controller. hasLoggedInUser and loggedInUser at least obviously also need to be exposed as helper methods.
One trick is that actual Service code,since it's running in a seperate thread, doesn't have straightforward access to the session. However, I anticipated this, and a couple months ago added in a way for Service threads to access the session. So there probably needs to be hasLoggedInUser and loggedInUser methods in the Service super-class that use the special method of session access.
2. AUTH PROCESS
This is based on what David Walker just did in Xerxes, after thinking it all through. The idea is that there's kind of a 'plugin' architecture for Auth modules. Of course 'plugin architecture' in Rails just means a class with certain expected methods.
The methods that an Auth plugin can implement, that will be called by Umlaut at appropriate times are:
- onEveryRequest() => a method that will be called in a before_filter on _every_ Umlaut request, to give an auth module the opportunity to check an SSO system for an already logged in user or logged out user, etc.
- beforeLogin() => a call back
- loginScreen() => a call back that returns a hash that Umlaut will pass to render() to render a login page in the Umlaut app, if neccesary. Or it can be a hash that redirects to some external login app.
- afterLogin() => If a local login screen is rendered, it submits to this action. This action can also be used as a 'callback' from an external login screen you redirect to.
- logout() => callback that Umalut calls on logout
All of these methods could be implemented or not by the Auth module.
So you only need to implement actual custom auth stuff, Umlaut takes care of the control flow and bookkeeping. I can provide more details about how this would work, but we'll leave it out for now.
You'd configure your auth module in an initializer -- actually auth module(s) cause it should be possible to have more than one available. Something like:
config.login_modules = [
{ :id => "shibboleth", :class => :ShibbolethAuth :default => true }, { :id => "horizon", :class => :HorizonAuth }
]
So that's a start. You with me so far? Heh.
Actually, i'll stick all of this on the wiki, and mention it on the listserv.
Jonathan