Discussion:
Refactoring Authenticated/Identified
Steven Grimm
2006-08-01 18:17:18 UTC
Permalink
Your suggestion of making authentication always 'fall through' might
be workable, but I'm not entirely sure that it's really what is needed.
Now that we're on the same page about what I'm trying to achieve, I
suspect you'll be able to come up with something cleaner than that. It
still feels to me like I want to be somewhere between Identified and
Authenticated, but given that the user data needs to be stored somewhere
on the back end, you kind of need the hierarchy of backend storage types
that the Authenticated subclasses give you (memory vs. db, purged vs.
not, etc.)

However, maybe that's a sign that the stuff in the Authenticated
subclasses isn't actually in the right place to begin with. Consider it
this way: Authenticated provides the logic to do different things
depending on whether the user has a valid authenticated session, and the
logic to manage that session (cookie management, etc.) That is its core
purpose. The user and session data stores are really *inputs* to the
authentication logic (or services it uses, if you prefer). Subclasses of
Authenticated should be about changing the authentication logic, not
changing which persistence service holds the user and session data. The
persistence service is only loosely coupled to the authentication logic.

The loose coupling I'm talking about is almost there already, in the
form of the *Deployer classes, but the problem is that the selection of
a Deployer is compiled into the Authentication class hierarchy rather
than determined as an independent configuration setting. That means you
can't subclass the authentication logic without making a parallel
hierarchy of subclasses for the different combinations of storage types.
So what I'm driving at is that the selection of the Deployer class
should be purely a runtime configuration thing, and Authenticated should
have none of its existing subclasses.

I would add one aspect to the Deployer setup, though: the various stores
should be configured separately rather than tied together. That is,
there should be a SessionDeployer, CredentialsDeployer, and
RememberDeployer interfaces, or whatever names make sense. The "mixed"
case would no longer be its own concrete class, but just a configuration
with a memory-based SessionDeployer and DB-based CredentialsDeployer and
RememberDeployers. Then it'd be trivial for someone to substitute their
own backend for one of those without touching the other one. For
example, I will probably want a DB-based credentials store and my own
session store that runs on a clustered memory cache.

Vanilla RIFE sites won't know the difference: they will keep extending
rife/authenticated/database.xml or whatever, and they never need to know
that that file now has additional config parameters that cause stuff to
be dynamically created on the backend.

Once that refactoring is complete, it then becomes an almost trivial
matter to do a subclass of Identified that knows how to pull the data
for a user whose identity is known but who isn't currently
authenticated. The subclass just uses the same Deployers as the
authentication element and the rest happens for free.

Before I said I might do it as a class in between Identified and
Authenticated, but now I think I might do it as a superclass of
Identified. I realize this is not your (Geert's) favorite way of
thinking about the problem, but bear with me and maybe it'll make sense:

RememberedSession (restores sessions from remember-me)
Identified (looks up user ID from session ID)
Authenticated (matches username/password)
RoleUserAuthenticated (or should this logic be injected into
Authenticated too?)

The "remember me" logic (both creating and reading the cookie) moves
into RememberedSession. If a session is restored from a cookie, the
remember-me attribute is set just as today. The "prohibit remember"
logic gets moved there too, so sites that want today's Identified
semantics can still have them; if that flag is set, RememberedSession
simply refrains from marking the request as part of a session.
Identified and Authenticated will just see it as a request that doesn't
have a valid session, no need for any special logic.

That feels architecturally clean to me: it separates the three distinct
questions that get asked on each page hit (is this a previously known
user who wants to be remembered, is this a user we know about, is this
user really who he claims to be) into separate classes. I think this
will make the authentication system not only more flexible, but also
easier to understand. YMMV on that though.

-Steve
Steven Grimm
2006-08-03 22:48:48 UTC
Permalink
Anyone have any comments on this idea? Is it a sensible approach or
crazy talk? If it's crazy, anyone have a better way to get the result I
want?

-Steve
Post by Steven Grimm
Your suggestion of making authentication always 'fall through' might
be workable, but I'm not entirely sure that it's really what is needed.
Now that we're on the same page about what I'm trying to achieve, I
suspect you'll be able to come up with something cleaner than that. It
still feels to me like I want to be somewhere between Identified and
Authenticated, but given that the user data needs to be stored
somewhere on the back end, you kind of need the hierarchy of backend
storage types that the Authenticated subclasses give you (memory vs.
db, purged vs. not, etc.)
However, maybe that's a sign that the stuff in the Authenticated
subclasses isn't actually in the right place to begin with. Consider
it this way: Authenticated provides the logic to do different things
depending on whether the user has a valid authenticated session, and
the logic to manage that session (cookie management, etc.) That is its
core purpose. The user and session data stores are really *inputs* to
the authentication logic (or services it uses, if you prefer).
Subclasses of Authenticated should be about changing the
authentication logic, not changing which persistence service holds the
user and session data. The persistence service is only loosely coupled
to the authentication logic.
The loose coupling I'm talking about is almost there already, in the
form of the *Deployer classes, but the problem is that the selection
of a Deployer is compiled into the Authentication class hierarchy
rather than determined as an independent configuration setting. That
means you can't subclass the authentication logic without making a
parallel hierarchy of subclasses for the different combinations of
storage types. So what I'm driving at is that the selection of the
Deployer class should be purely a runtime configuration thing, and
Authenticated should have none of its existing subclasses.
I would add one aspect to the Deployer setup, though: the various
stores should be configured separately rather than tied together. That
is, there should be a SessionDeployer, CredentialsDeployer, and
RememberDeployer interfaces, or whatever names make sense. The "mixed"
case would no longer be its own concrete class, but just a
configuration with a memory-based SessionDeployer and DB-based
CredentialsDeployer and RememberDeployers. Then it'd be trivial for
someone to substitute their own backend for one of those without
touching the other one. For example, I will probably want a DB-based
credentials store and my own session store that runs on a clustered
memory cache.
Vanilla RIFE sites won't know the difference: they will keep extending
rife/authenticated/database.xml or whatever, and they never need to
know that that file now has additional config parameters that cause
stuff to be dynamically created on the backend.
Once that refactoring is complete, it then becomes an almost trivial
matter to do a subclass of Identified that knows how to pull the data
for a user whose identity is known but who isn't currently
authenticated. The subclass just uses the same Deployers as the
authentication element and the rest happens for free.
Before I said I might do it as a class in between Identified and
Authenticated, but now I think I might do it as a superclass of
Identified. I realize this is not your (Geert's) favorite way of
RememberedSession (restores sessions from remember-me)
Identified (looks up user ID from session ID)
Authenticated (matches username/password)
RoleUserAuthenticated (or should this logic be injected
into Authenticated too?)
The "remember me" logic (both creating and reading the cookie) moves
into RememberedSession. If a session is restored from a cookie, the
remember-me attribute is set just as today. The "prohibit remember"
logic gets moved there too, so sites that want today's Identified
semantics can still have them; if that flag is set, RememberedSession
simply refrains from marking the request as part of a session.
Identified and Authenticated will just see it as a request that
doesn't have a valid session, no need for any special logic.
That feels architecturally clean to me: it separates the three
distinct questions that get asked on each page hit (is this a
previously known user who wants to be remembered, is this a user we
know about, is this user really who he claims to be) into separate
classes. I think this will make the authentication system not only
more flexible, but also easier to understand. YMMV on that though.
-Steve
_______________________________________________
Rife-devel mailing list
http://lists.uwyn.com/mailman/listinfo/rife-devel
Geert Bevin
2006-08-04 14:24:12 UTC
Permalink
Post by Steven Grimm
Anyone have any comments on this idea? Is it a sensible approach or
crazy talk? If it's crazy, anyone have a better way to get the
result I want?
Sorry Steve, haven't had the time yet to read it with the attention
it deserves. I'll get to it this weekend.

Take care,

Geert

--
Geert Bevin
Uwyn "Use what you need" - http://uwyn.com
RIFE Java application framework - http://rifers.org
Music and words - http://gbevin.com
Geert Bevin
2006-08-06 07:31:31 UTC
Permalink
Hi Steven,
Post by Steven Grimm
The loose coupling I'm talking about is almost there already, in
the form of the *Deployer classes, but the problem is that the
selection of a Deployer is compiled into the Authentication class
hierarchy rather than determined as an independent configuration
setting. That means you can't subclass the authentication logic
without making a parallel hierarchy of subclasses for the different
combinations of storage types. So what I'm driving at is that the
selection of the Deployer class should be purely a runtime
configuration thing, and Authenticated should have none of its
existing subclasses.
I would add one aspect to the Deployer setup, though: the various
stores should be configured separately rather than tied together.
That is, there should be a SessionDeployer, CredentialsDeployer,
and RememberDeployer interfaces, or whatever names make sense. The
"mixed" case would no longer be its own concrete class, but just a
configuration with a memory-based SessionDeployer and DB-based
CredentialsDeployer and RememberDeployers. Then it'd be trivial for
someone to substitute their own backend for one of those without
touching the other one. For example, I will probably want a DB-
based credentials store and my own session store that runs on a
clustered memory cache.
Vanilla RIFE sites won't know the difference: they will keep
extending rife/authenticated/database.xml or whatever, and they
never need to know that that file now has additional config
parameters that cause stuff to be dynamically created on the backend.
I think that this would be a very useful refactoring. Honestly, the
tight bound between the deployer classes and the actual
authentication elements has always somewhat bothered me. Actually, I
can't believe that I never thought of doing this way. Then again, it
has been awhile since I created custom authentication logic, so I can
cut myself some slack. ;-)
Post by Steven Grimm
Once that refactoring is complete, it then becomes an almost
trivial matter to do a subclass of Identified that knows how to
pull the data for a user whose identity is known but who isn't
currently authenticated. The subclass just uses the same Deployers
as the authentication element and the rest happens for free.
Before I said I might do it as a class in between Identified and
Authenticated, but now I think I might do it as a superclass of
Identified. I realize this is not your (Geert's) favorite way of
RememberedSession (restores sessions from remember-me)
Identified (looks up user ID from session ID)
Authenticated (matches username/password)
RoleUserAuthenticated (or should this logic be injected
into Authenticated too?)
The "remember me" logic (both creating and reading the cookie)
moves into RememberedSession. If a session is restored from a
cookie, the remember-me attribute is set just as today. The
"prohibit remember" logic gets moved there too, so sites that want
today's Identified semantics can still have them; if that flag is
set, RememberedSession simply refrains from marking the request as
part of a session. Identified and Authenticated will just see it as
a request that doesn't have a valid session, no need for any
special logic.
That feels architecturally clean to me: it separates the three
distinct questions that get asked on each page hit (is this a
previously known user who wants to be remembered, is this a user we
know about, is this user really who he claims to be) into separate
classes. I think this will make the authentication system not only
more flexible, but also easier to understand. YMMV on that though.
Sorry, but I've still don't feel comfortable with this. As discussed
before, you still seem to want to use the remember functionality for
something that it hasn't been designed for. Its purpose really is to
automate the provision of credentials to the authentication layer. It
can't really be separated from it because the authentication checks
still need to be performed every time credentials are retrieved and
filled in automatically.

Of all the previous suggestions that you've made, I think that the
fall-through authentication solution seems the best to me (the one
that doesn't require a form when no authentication session is present
and no remember cookie can be found). It doesn't solve the
theoretical use case of people that want to retrieve user credentials
of accounts that haven't been registered as users to the
authentication back-end yet, but it does solve your "eternal
authentication session" problem without introducing an artificial
embedded element.

Take care,

Geert

--
Geert Bevin
Uwyn "Use what you need" - http://uwyn.com
RIFE Java application framework - http://rifers.org
Music and words - http://gbevin.com
Steven Grimm
2006-08-06 18:24:11 UTC
Permalink
Post by Geert Bevin
I think that this would be a very useful refactoring. Honestly, the
tight bound between the deployer classes and the actual authentication
elements has always somewhat bothered me. Actually, I can't believe
that I never thought of doing this way. Then again, it has been awhile
since I created custom authentication logic, so I can cut myself some
slack. ;-)
Super, glad I'm not totally off the wall here. I have been doing some
custom authentication code on both of my current RIFE projects (totally
different on each, one is just the "remember me" stuff and the other is
a custom Authenticated subclass that sends the user to a third-party
authentication service to log in) so any movement in this direction will
make my life much easier.

What I'd love -- and I'm sure you would too -- would be to get to the
point where a reasonably skilled developer could write their own, say,
user database module without having to ever look at the RIFE source
tree. Not that there's anything bad about having the sources to refer
to, of course, but if my experience is typical, right now things are
intertwined tightly enough that someone writing custom auth code has to
be highly aware of the implementation details of the authentication system.

This refactoring, assuming it turns out to be as feasible as it sounds,
will definitely help in that area. I was not looking forward to figuring
out how to explain the current Deployer system on the auth internals
Wiki page. (Which, BTW, I am about to go work on a little more.)
Post by Geert Bevin
Of all the previous suggestions that you've made, I think that the
fall-through authentication solution seems the best to me (the one
that doesn't require a form when no authentication session is present
and no remember cookie can be found). It doesn't solve the theoretical
use case of people that want to retrieve user credentials of accounts
that haven't been registered as users to the authentication back-end
yet, but it does solve your "eternal authentication session" problem
without introducing an artificial embedded element.
I'm not sure I understand that use case. Can you give me an example?

I've just filed RIFE-301 with an initial stab at an implementation of
the fall-through mechanism. Seems to work just fine.

-Steve
Geert Bevin
2006-08-06 23:06:33 UTC
Permalink
Hi Steven,
Post by Steven Grimm
What I'd love -- and I'm sure you would too -- would be to get to
the point where a reasonably skilled developer could write their
own, say, user database module without having to ever look at the
RIFE source tree. Not that there's anything bad about having the
sources to refer to, of course, but if my experience is typical,
right now things are intertwined tightly enough that someone
writing custom auth code has to be highly aware of the
implementation details of the authentication system.
Indeed that would be awesome. I'm wondering, would you be up for
making this refactoring happen? It will be quite a while before I can
look at it. It's really time that I spend as much free time as
possible on the book(s) and the advancement of continuations.
Post by Steven Grimm
This refactoring, assuming it turns out to be as feasible as it
sounds, will definitely help in that area. I was not looking
forward to figuring out how to explain the current Deployer system
on the auth internals Wiki page. (Which, BTW, I am about to go work
on a little more.)
Post by Geert Bevin
Of all the previous suggestions that you've made, I think that the
fall-through authentication solution seems the best to me (the one
that doesn't require a form when no authentication session is
present and no remember cookie can be found). It doesn't solve the
theoretical use case of people that want to retrieve user
credentials of accounts that haven't been registered as users to
the authentication back-end yet, but it does solve your "eternal
authentication session" problem without introducing an artificial
embedded element.
I'm not sure I understand that use case. Can you give me an example?
I was referring to the one I described in an earlier email:
http://www.nabble.com/Re%3A-%22Remember-me%22-for-identified-%28not-
authenticated%29-pages--p5592145.html
Post by Steven Grimm
I've just filed RIFE-301 with an initial stab at an implementation
of the fall-through mechanism. Seems to work just fine.
That looks like a good implementation of it. I would rename
"allow_anonymous" to "enforce_authenticated", feels more logical and
general-purpose to me.

To implement the tests for this, I can point you to the related
files. First, add the required elements to the authentication sites
that are here: http://rifers.org:8088/browse/rifers/rife/trunk/
programs/unittests/config/site

If you want, you can add the individual element XML files here, but I
don't do that anymore: http://rifers.org:8088/browse/rifers/rife/
trunk/programs/unittests/config/element/authentication

Then you add the actual tests to the JUnit suites that are here:
http://rifers.org:8088/browse/rifers/rife/trunk/src/unittests/com/
uwyn/rife/authentication/elements

To run only these, I simple comment out the non authentication test
suites from:
http://rifers.org:8088/browse/rifers/rife/trunk/src/unittests/com/
uwyn/rife/TestRifeTests.java?r=trunk

and only leave the last bunch of server-side test suites in here:
http://rifers.org:8088/browse/rifers/rife/trunk/src/unittests/com/
uwyn/rife/authentication/TestSuiteAuthentication.java?r=trunk

To only use one database for testing, modify this file and prefix the
"unittests*" names with another character (I use 'd' of disabled):
http://rifers.org:8088/browse/rifers/rife/trunk/programs/unittests/
config/rep/unittests_datasources.xml?r=trunk

Hope this is a bit clear, otherwise don't hesitate to ask.

Take care,

Geert

PS.: I know that this would all be much easier if the tests were
converted to TestNG ... but I'm not really up for that test ;-)
--
Geert Bevin
Uwyn "Use what you need" - http://uwyn.com
RIFE Java application framework - http://rifers.org
Music and words - http://gbevin.com
Steven Grimm
2006-09-07 19:18:17 UTC
Permalink
First a quick update for everyone reading this: I've done a first round
of the Authenticated refactoring, making the selection of the
SessionManager and SessionValidator completely dynamic based on element
configuration. There are no more Mixed element classes since they are
just the database elements with the memory-based session classes
configured in. Application code that uses rife/authenticated/mixed.xml
won't notice the difference.

But now for the real reason I'm writing this: While I was thinking about
what I want to use this newfound configuration for, I had an idea about
how to factor out the Purging stuff too.

What I want to do is put a caching layer on top of the user and session
data so I don't have to hit the database to fetch the data for a user
who's currently logged in. But I *do* want a database sitting under the
cache; whenever there's a cache miss the database should be consulted
and the cache should be populated.

So basically I want to wrap a caching facade object around the
underlying database-capable objects. And it would be great to allow a
stack of these facades, much in the same way we can stack child-trigger
elements if we want to.

Once that general mechanism is in place, purging is just another thing
that can be layered on top of a SessionManager and/or RememberManager.
In fact, the current implementations of PurgingSessionManager and
PurgingRememberManager barely need to change at all; the only difference
is how the system knows to instantiate them and what to wrap them
around. And the purging behavior could be layered on top of, say, my
caching wrapper.

I think the general principle is probably not that controversial. The
interesting question is, what does the configuration look like? And
that's where I'm less certain.

Spring actually has a reasonable syntax for configuring which beans are
wrapped in which others -- you define the underlying bean, then define
the wrapper bean and configure the underlying bean as a property of the
wrapping bean. But since RIFE doesn't have an equivalent configuration
mechanism for low-level objects, I think that might be a bit too
heavyweight of a change to introduce. On the other hand, if we're
willing to bite that bullet, it probably *is* the most flexible way to
go about this.

Instead, though, maybe something simpler is in order. Something like
this (using some of the new properties I've introduced in my
refactoring; hopefully it's easy enough to follow.) This is a definition
for an element that uses a purging wrapper around a caching wrapper
around a database session manager.

First, database.xml (minus the stuff not related to session managers,
for the sake of example):

<element extends="rife/authenticated/authenticated.xml">
<property
name="sessionmanagerfactory_class">DatabaseSessionsFactory</property>
<property name="datasource"></property>
<property name="sessionmanager_wrap_prefix">db_</property>
</element>

This is just like a normal (post-refactoring) session manager
declaration: a factory class and the parameters it needs to instantiate
its objects. The new thing is the last property; the easiest way to
explain it is by proceeding to the next element, databasecaching.xml:

<element extends="rife/authenticated/database.xml">
<property
name="db_sessionmanagerfactory_class">SessionManagerWrapperFactory</property>
<property
name="db_sessionmanager_class">CachingSessionManagerWrapper</property>
<property name="db_cache_size">1M</property>
<property name="db_cache_expiration">LRU</property>
<property name="db_sessionmanager_wrap_prefix">cached_</property>
</element>

Here all the properties are prefixed with the wrap prefix defined by the
element we're wrapping. This prevents namespace collisions. All the
classes involved will be able to take prefix arguments and will know to
strip off their prefixes when looking up their properties. With that in
mind, this looks similar to the initial element: we define a factory
class (in this case, one that knows how to produce wrapper classes; it
will pass the underlying database session manager to the wrapper's
constructor) and tell the factory what underlying class to make. That
underlying class has some properties. Then we define the next prefix in
case there's another element in the chain, which in this case is in
databasecachingpurging.xml:

<element extends="rife/authenticated/databasecaching.xml">
<property
name="cached_sessionmanagerfactory_class">SessionManagerWrapperFactory</property>
<property
name="cached_sessionmanager_class">PurgingSessionManagerWrapper</property>
<property name="cached_purge_frequency">...</property>
<property name="cached_sessionmanager_wrap_prefix">purged_</property>
</element>

Another wrapper as before, which also defines a prefix for the next link
in the chain. But this time there isn't one. The session manager setup
code will look for a "purged_sessionmanagerfactory_class" property on
the current element, not see one, and will return the
PurgingSessionManagerWrapper object to the caller.

I admit the notion of using prefixes is a little wacky; you really
*should* be able to use the same properties at each level so you don't
have to change your element's properties just to wrap a different
parent. But (as I just confirmed with Geert on IRC) the namespace of
element properties is flat, so you pretty much have to do something like
this to represent an arbitrary chain of parameterized objects.

As with my current round of refactoring, this new flexibility is
completely hidden from applications that just want to use the pre-canned
"databaseunpurged.xml" or whatever. They continue to extend those XML
files as before, blissfully unaware that stuff has gotten more dynamic
under the covers.

What do you guys think?

-Steve
Geert Bevin
2006-09-18 06:46:53 UTC
Permalink
Hi Steven,

I think this should maybe better be solved by adding a single
"sessionmanager_filter" property that can be injected through IoC.
The objects should implement the SessionManagerFilter interface,
which would then in turn extend the SessionManager interface.

Instances of the filter classes are then simply setup for IoC in
participants. One of the standard implementations can take a
collection of filters that are processed sequentially.

I'm thinking of maybe adding a setDelegate(SessionManager) method to
SessionManagerFilter which will always be called to set the instance
of the manager that is being wrapped. It would then be up to the
filter to call the corresponding method on the wrapped manager if
that's needed or to not do so if that's appropriate.

I think that setting things up like this will be a lot easier that
adding a whole bunch of properties with prefixes.

What do you think?

Geert
Post by Steven Grimm
First a quick update for everyone reading this: I've done a first
round of the Authenticated refactoring, making the selection of the
SessionManager and SessionValidator completely dynamic based on
element configuration. There are no more Mixed element classes
since they are just the database elements with the memory-based
session classes configured in. Application code that uses rife/
authenticated/mixed.xml won't notice the difference.
But now for the real reason I'm writing this: While I was thinking
about what I want to use this newfound configuration for, I had an
idea about how to factor out the Purging stuff too.
What I want to do is put a caching layer on top of the user and
session data so I don't have to hit the database to fetch the data
for a user who's currently logged in. But I *do* want a database
sitting under the cache; whenever there's a cache miss the database
should be consulted and the cache should be populated.
So basically I want to wrap a caching facade object around the
underlying database-capable objects. And it would be great to allow
a stack of these facades, much in the same way we can stack child-
trigger elements if we want to.
Once that general mechanism is in place, purging is just another
thing that can be layered on top of a SessionManager and/or
RememberManager. In fact, the current implementations of
PurgingSessionManager and PurgingRememberManager barely need to
change at all; the only difference is how the system knows to
instantiate them and what to wrap them around. And the purging
behavior could be layered on top of, say, my caching wrapper.
I think the general principle is probably not that controversial.
The interesting question is, what does the configuration look like?
And that's where I'm less certain.
Spring actually has a reasonable syntax for configuring which beans
are wrapped in which others -- you define the underlying bean, then
define the wrapper bean and configure the underlying bean as a
property of the wrapping bean. But since RIFE doesn't have an
equivalent configuration mechanism for low-level objects, I think
that might be a bit too heavyweight of a change to introduce. On
the other hand, if we're willing to bite that bullet, it probably
*is* the most flexible way to go about this.
Instead, though, maybe something simpler is in order. Something
like this (using some of the new properties I've introduced in my
refactoring; hopefully it's easy enough to follow.) This is a
definition for an element that uses a purging wrapper around a
caching wrapper around a database session manager.
First, database.xml (minus the stuff not related to session
<element extends="rife/authenticated/authenticated.xml">
<property
name="sessionmanagerfactory_class">DatabaseSessionsFactory</property>
<property name="datasource"></property>
<property name="sessionmanager_wrap_prefix">db_</property>
</element>
This is just like a normal (post-refactoring) session manager
declaration: a factory class and the parameters it needs to
instantiate its objects. The new thing is the last property; the
easiest way to explain it is by proceeding to the next element,
<element extends="rife/authenticated/database.xml">
<property
name="db_sessionmanagerfactory_class">SessionManagerWrapperFactory</
property>
<property
name="db_sessionmanager_class">CachingSessionManagerWrapper</property>
<property name="db_cache_size">1M</property>
<property name="db_cache_expiration">LRU</property>
<property name="db_sessionmanager_wrap_prefix">cached_</property>
</element>
Here all the properties are prefixed with the wrap prefix defined
by the element we're wrapping. This prevents namespace collisions.
All the classes involved will be able to take prefix arguments and
will know to strip off their prefixes when looking up their
properties. With that in mind, this looks similar to the initial
element: we define a factory class (in this case, one that knows
how to produce wrapper classes; it will pass the underlying
database session manager to the wrapper's constructor) and tell the
factory what underlying class to make. That underlying class has
some properties. Then we define the next prefix in case there's
another element in the chain, which in this case is in
<element extends="rife/authenticated/databasecaching.xml">
<property
name="cached_sessionmanagerfactory_class">SessionManagerWrapperFactory
</property>
<property
name="cached_sessionmanager_class">PurgingSessionManagerWrapper</
property>
<property name="cached_purge_frequency">...</property>
<property name="cached_sessionmanager_wrap_prefix">purged_</
property>
</element>
Another wrapper as before, which also defines a prefix for the next
link in the chain. But this time there isn't one. The session
manager setup code will look for a
"purged_sessionmanagerfactory_class" property on the current
element, not see one, and will return the
PurgingSessionManagerWrapper object to the caller.
I admit the notion of using prefixes is a little wacky; you really
*should* be able to use the same properties at each level so you
don't have to change your element's properties just to wrap a
different parent. But (as I just confirmed with Geert on IRC) the
namespace of element properties is flat, so you pretty much have to
do something like this to represent an arbitrary chain of
parameterized objects.
As with my current round of refactoring, this new flexibility is
completely hidden from applications that just want to use the pre-
canned "databaseunpurged.xml" or whatever. They continue to extend
those XML files as before, blissfully unaware that stuff has gotten
more dynamic under the covers.
What do you guys think?
-Steve
_______________________________________________
Rife-devel mailing list
http://lists.uwyn.com/mailman/listinfo/rife-devel
--
Geert Bevin
Uwyn "Use what you need" - http://uwyn.com
RIFE Java application framework - http://rifers.org
Music and words - http://gbevin.com
Steven Grimm
2006-09-21 20:18:14 UTC
Permalink
The refactoring I described in the original message of this thread is
now done and checked into the "authrefactor" branch in svn. Feel free to
check it out! Unless your application is directly messing with the
authentication elements (e.g. you have your own subclass of
DatabaseAuthenticated) this should be a completely invisible refactoring.

You can now substitute your own classes for any of RIFE's
authentication-related ones without having to write your own
Authenticated element. If you crack open the element XML files such as
authenticated/database.xml, you'll see a bunch of new config options
that control which classes are used. Namely:

* sessionmanagerfactory_class - this class should implement
SessionManagerFactory. Built-in implementations are
DatabaseSessionsFactory and SimpleSessionManagerFactory (more on
the latter below).
* sessionvalidatorfactory_class - this class should implement
SessionValidatorFactory. Built-in implementations are
DatabaseSessionValidatorFactory and SimpleSessionValidatorFactory.
* remembermanagerfactory_class - this class should implement
RememberManagerFactory. One built-in implementation:
DatabaseRememberFactory ("remember me" doesn't make sense for
simple in-memory implementations).
* credentialsmanagerfactory_class - this class should implement
CredentialsManagerFactory. Built-in implementations are
DatabaseUsersFactory and MemoryUsersFactory.

The SimpleSessionManagerFactory and SimpleSessionValidatorFactory
classes implement a simple cache of singletons based on ID/class-name
pairs. If you have an implementation that doesn't need to read any
configuration from the authentication element, these are appropriate to
use; that will very often be the case with session validators even if
you are using your own custom session manager. These factory classes
expect element properties to control which classes to instantiate:

* sessionmanager_class - implements SessionManager. Built-in
implementation is MemorySessions (there is also still
DatabaseSessions but you can't instantiate that with the simple
factory.)
* sessionmanager_id - a string identifier. This means the same thing
it used to in the MemorySessions configuration, but now it can
apply to other session manager implementations too.
* sessionvalidator_class - implements SessionValidator. Built-in
implementation is BasicSessionValidator, which just calls the
associated session and remember managers without any optimization.
This will be appropriate in many cases. (There is also
DatabaseSessionValidator but you can't instantiate it with the
simple factory.)

This refactoring completely gets rid of the storage-medium-based
subclasses of Authenticated. There is no more DatabaseAuthenticated or
MixedAuthenticated -- these are all just configurations of a single
class that looks at the properties. That should make it significantly
easier for people who want to alter the behavior of the authentication
system; previously if you wanted to override something in Authenticated,
you had to subclass all the different leaf nodes of Authenticated's
class hierarchy. And more importantly, if all you want to do is
substitute your own user database or whatever, you no longer have to
subclass Authenticated at all. Just override the credentials manager
factory class in your element's configuration and you're good to go.

Comments and problem reports welcome.

There will be one more round of refactoring before I consider this done:
the purging vs. non-purging distinction is still hardwired into the
class hierarchy. See the "Refactoring Purging" thread on this list for
some idea of what I'd like to do with that.

-Steve
Post by Steven Grimm
Your suggestion of making authentication always 'fall through' might
be workable, but I'm not entirely sure that it's really what is needed.
Now that we're on the same page about what I'm trying to achieve, I
suspect you'll be able to come up with something cleaner than that. It
still feels to me like I want to be somewhere between Identified and
Authenticated, but given that the user data needs to be stored
somewhere on the back end, you kind of need the hierarchy of backend
storage types that the Authenticated subclasses give you (memory vs.
db, purged vs. not, etc.)
However, maybe that's a sign that the stuff in the Authenticated
subclasses isn't actually in the right place to begin with. Consider
it this way: Authenticated provides the logic to do different things
depending on whether the user has a valid authenticated session, and
the logic to manage that session (cookie management, etc.) That is its
core purpose. The user and session data stores are really *inputs* to
the authentication logic (or services it uses, if you prefer).
Subclasses of Authenticated should be about changing the
authentication logic, not changing which persistence service holds the
user and session data. The persistence service is only loosely coupled
to the authentication logic.
The loose coupling I'm talking about is almost there already, in the
form of the *Deployer classes, but the problem is that the selection
of a Deployer is compiled into the Authentication class hierarchy
rather than determined as an independent configuration setting. That
means you can't subclass the authentication logic without making a
parallel hierarchy of subclasses for the different combinations of
storage types. So what I'm driving at is that the selection of the
Deployer class should be purely a runtime configuration thing, and
Authenticated should have none of its existing subclasses.
I would add one aspect to the Deployer setup, though: the various
stores should be configured separately rather than tied together. That
is, there should be a SessionDeployer, CredentialsDeployer, and
RememberDeployer interfaces, or whatever names make sense. The "mixed"
case would no longer be its own concrete class, but just a
configuration with a memory-based SessionDeployer and DB-based
CredentialsDeployer and RememberDeployers. Then it'd be trivial for
someone to substitute their own backend for one of those without
touching the other one. For example, I will probably want a DB-based
credentials store and my own session store that runs on a clustered
memory cache.
Vanilla RIFE sites won't know the difference: they will keep extending
rife/authenticated/database.xml or whatever, and they never need to
know that that file now has additional config parameters that cause
stuff to be dynamically created on the backend.
Once that refactoring is complete, it then becomes an almost trivial
matter to do a subclass of Identified that knows how to pull the data
for a user whose identity is known but who isn't currently
authenticated. The subclass just uses the same Deployers as the
authentication element and the rest happens for free.
Before I said I might do it as a class in between Identified and
Authenticated, but now I think I might do it as a superclass of
Identified. I realize this is not your (Geert's) favorite way of
RememberedSession (restores sessions from remember-me)
Identified (looks up user ID from session ID)
Authenticated (matches username/password)
RoleUserAuthenticated (or should this logic be injected
into Authenticated too?)
The "remember me" logic (both creating and reading the cookie) moves
into RememberedSession. If a session is restored from a cookie, the
remember-me attribute is set just as today. The "prohibit remember"
logic gets moved there too, so sites that want today's Identified
semantics can still have them; if that flag is set, RememberedSession
simply refrains from marking the request as part of a session.
Identified and Authenticated will just see it as a request that
doesn't have a valid session, no need for any special logic.
That feels architecturally clean to me: it separates the three
distinct questions that get asked on each page hit (is this a
previously known user who wants to be remembered, is this a user we
know about, is this user really who he claims to be) into separate
classes. I think this will make the authentication system not only
more flexible, but also easier to understand. YMMV on that though.
-Steve
_______________________________________________
Rife-devel mailing list
http://lists.uwyn.com/mailman/listinfo/rife-devel
Loading...