[Fusionforge-general] [RFC] Revamping the pluggable authentication mechanisms

Roland Mas lolando at debian.org
Thu Feb 3 21:30:24 CET 2011

  Hi all,

  There was a Coclico project meeting in Paris on Tuesday, and some of
us stayed there yesterday too for a sort-of-hacklab.  Not much actual
code was produced, but we collected some requirements and tried to draft
a plan for how authentication (and related) plugins could be improved.

  Some context:

- Madhumita Dhar is working on an OAuth plugin that should allow a
forge user to grant part of their permissions to an external service
(such as a Hudson server) without sharing their actual password.  The
use case here is that I would like to allow Hudson to, for instance,
submit bugs on my behalf, but I don't want to grant Hudson all of my
forge admin privileges.

- Olivier Berger would like to allow logging in with different
authentication mechanisms (such as CAS and Shibboleth), but some only
do authentication and do not provide extra information about the user
(such as their real name and/or email address).

- Christian Bayle keeps pestering us about how Kerberos is the way to
go, but again this only provides authentication, and the extra
information may have to be fetched from an LDAP directory.

- Folks at INRIA would like to grant some extra privileges to users
whose email address matches a given pattern.

- I'm increasingly getting asked by clients about the authentication
system, and whether it can be plugged into CAS, or synchronised to an
external database, or what's the status of the LDAP plugin, and so on,
with feature requirements beyond the current status.

  The current implementation of external authentication plugins (well,
mostly of the LDAPextauth plugin) mixes up a few of these aspects.  I
accept the blame for that, since I think I was the one who designed and
implemented it.  Anyway, here's how it works, for reference (and to help
understand the proposed changes):

- Session setup is currently based on a login page asking for a login
name and a password.  These credentials are (potentially) passed to
plugins via a hook, then they are checked against what's in the
database.  If the database says they're fine, then the session is
established and a cookie is set.

- The pre.php script included in each page checks the cookie if
present, and sets up the FusionForge session object accordingly if the
cookie is present and correct.

- An external authentication plugin such as LDAPextauth only works
during the hook mentioned earlier.  Given a login name and a password,
it checks them against an LDAP tree.  If they match, the plugin
ensures that the data in the database is present and correct: this can
involve creating the user in the DB, or updating some properties such
as their real name or email address, or even updating the password to
reflect a change that occurred in LDAP.

  This current setup isn't completely worthless (it's been in use at
several places), but it lacks flexibility.  This flexibility could be
added by logically disconnecting the various functions of the whole
"authentication stuff".  Our discussion led to the following proposal
for a refactoring of that code.

  Basically, we're looking at five different parts:

1. The session checking mechanism, whose purpose is to initiate, in
FusionForge, one GFUser object considered to be logged in, or NULL if
nobody's logged in.

2. The log in process, whose purpose is to have *something* check for
credentials, according to whatever local policy is in force, and set
up the session in the way that step 1 expects (could be a cookie, in
the simplest case).

3. The process of injecting or updating the user account information
into the database, so that it stays in sync with an external data
source (LDAP tree for instance).

4. The authorization process, which determines, based on the current
context, the list of available roles, from which a set of permissions
will be derived.  The "context" here includes, obviously, the active
user when a session has previously been found active, but it can also
include other things such as the remote IP address for instance, so
that users connecting from the local network have some extra roles, or
the time and date, so that certain operations are not available
outside of business hours, and so on.  This could also be used to
*restrict* the set of available roles, for the Hudson use case
mentioned earlier.

5. The log out process, whereby one closes a session and returns to
anonymous browsing.  For some reason we had forgotten it yesterday :-)

  Each of these steps can make use of plugins to delegate some of the
functionality.  We have identified a few new hooks that will have to
be defined, and implemented by auth plugins.

Step 1: this will probably be two hooks, with names such as
check_auth_info and fetch_auth_info.  The first hook will return a
status like "yes, there's an active session", "I don't know, but maybe
other plugins have a better view", and "no" (which would mean "there's a
session cookie or whatever this plugin recognizes, but it has expired,
so the session in itself should be considered invalid").  The second
hook should return a GFUser object corresponding to the user that's
currently identified by the session, after mapping the LDAP or OpenID
account into a username that the forge understands.  Some thought will
have to be put into how these hooks can be combined or cascaded.

Step 2: two hooks will probably be needed.  One called
display_auth_form, whose the result should be to display whatever HTML
is required to start the authentication process for the chosen method.
In case several mechanisms are available (for instance, internal
authentication for local users plus Shibboleth/CAS/OpenID for external
users), this could end up with a page containing several forms, the user
being expected to type their credentials in the appropriate one.  The
other hook would be display_create_user_form, and it could be used on
the register page if one wanted to create a user where the username
can't easily be derived from the authentication credentials.  If for
instance I want to create a user with WebID, something will have to map
the WebID URL to a username.  The user registration form will then have
only two fields (chosen username, and WebID URL); the rest of the
information associated with a user could be obtained automatically from
the WebID provider (please forgive any inaccurateness there -- I really
have no clue how WebID actually works, this is mostly what I deduced
from context during meetings :-)

Step 3: fetch_account_info.  Plugins implementing that will have the
opportunity to update stuff in the database based on their own data
sources.  This could involve updating an email address, of course, but
this hook could also be used to synchronize project memberships from an
external source.

Step 4: Two hooks should probably be needed here, unless we find a
manageable way to do with one.  The first would be get_extra_roles,
and could be used to enable a role with some given permissions to
users based on information not available in the database.  For
instance, the ability to approve projects could be granted to users
connecting from the local network.  The second hook would be
filter_roles, and this one would work in the opposite direction:
basically, it would apply a mask on the set of the available roles,
and restrict it to a given subset.  The use case for the second hook
is the delegation of permissions, via OAuth, to an external tool: to
take the same example as above, I want Hudson to connect to the forge
in my name (so the tracker items it creates appear as having been
submitted by me), but I don't want to grant it the full set of my
permissions in case the Hudson gets compromised.

Step 5: maybe a single hook ("invalidate_session" maybe?), but this was
overlooked during the discussion (I just remembered it while writing
this email), so not much time has been spent into thinking about this.

  This seemed to cover all the use cases we could think of; but this
is still mostly an idea, and no implementation has been started yet.
We're therefore looking for comments and suggestions, or things we've
forgotten to handle.

  Some thoughts about implementation: the current system could be
migrated from global functions to plugin-like hook implementations, so
it could theoretically be disabled (if you want to make sure your
forge *only* authenticates users against your LDAP).  This would mean
a new "plugin".  Since some functionality of that plugin could be
re-used for others (if the session is stored in a cookie, for
instance), there'll probably be a new AuthPlugin class that
authentication plugins could inherit from, in much the same way as
there's an SCMPlugin class with common implementations of methods for
the various SCM plugins.  Also, while we're at it, we may replace our
custom cookie handling with calls to the appropriate modules in the
Zend framework if it makes life easier (and code shorter).

  The good things are that the authentication subsystem is relatively
self-contained (session.php mostly, plus login.php and logout.php), so
the impact of a rewrite in terms of code won't be huge (probably a few
hundred lines of code in total); also, the new RBAC system already
provides much of the functionality needed for step 4.

  With a bit of luck, I'll make time to work on that in the coming
weeks because it may be needed by a client.  I won't object if anyone
beats me to it though :-)

Roland Mas

The cherry blossom / Tumbles from the highest tree / One needs more petrol
  -- in Good Omens (Terry Pratchett and Neil Gaiman)

More information about the Fusionforge-general mailing list