Why it's bad to do simpler things?
:-)
AFAIK filter pattern (or interceptor/chain whatever you call it)
is proven to work in many places. The provider at level X can
always decide if it delegates call to method "getUserByXXX" to
next provider (and then proxy/cache or do whatever according to
his logic) or return something by itself.
If I understand correctly your proposal, it requires
UserFederationProvider to decide, if it wants to import or just
return InMemoryUserModel . So if we want to support in-memory for
LDAP, we will need to have 2 versions of LDAPFederationProvider
(current which imports into userStorage() and another, which will
return InMemoryUserModel instances ). That's not ideal IMO.
As I mentioned before, there are also 2 additional usecases, which
are important to support IMO:
1) Case when admin changes some user attributes directly in LDAP
and he wants the LDAP data to be immediately visible in Keycloak.
This is what we currently support (see
FederationProvidersIntegrationTest.testDirectLDAPUpdate() ). Maybe
I am missing something with your proposal, but if we hardcode
CacheProvider to be always first, we lost this.
2) Case when admin doesn't change user attributes in LDAP
directly, but rather prefer to save performance and read data from
cache. In this case, admin configures the chain like you proposed:
CacheProvider => UserFederationManager => UserProvider
IMO it will be cool to have single implementation of
LDAPFederationProvider (and others), which works for both those
cases and also for in-memory at the same time. Just let admin to
decide how he want to configure chain of UserProviders, but not
require UserFederationProvider itself to care about it.
For my proposal, I assume that UserProvider will have just 2 new
methods:
UserProvider getNext();
void setNext(UserProvider next);
Only change in current UserFederationManager and
DefaultCacheUserProvider is, that they will call "getNext()" when
they need delegate. They don't care about what the delegate
actually is, that's not their responsibility.
For the "in-memory" provider, it might be the per-request
in-memory provider (users stored just per-request). So if you have
chain like:
userFederationManager => inMemory
Then assume the session.users().getUserByUsername is called:
1) First delegate is UserFederationManager, so calling
UserFederationManager.getUserByUsername
2) This line
https://github.com/keycloak/keycloak/blob/master/model/api/src/main/java/org/keycloak/models/UserFederationManager.java#L180
will call getNext().getUserByUsername() and returns null as the
user was not yet looked for this request.
3) Going to federationProviders and call
LDAPFederationProvider.getUserByUsername
4) LDAPFederationProvider query user in LDAP and calls
importUserFromLDAP . This
calls session.userStorage().
addUser, which will put user
into in-memory provider (I assume that session.userStorage() will
be kept and will always point to the next delegate after
UserFederationManager ). The LDAPFederationProvider will then
return LDAP proxy of UserModel.
The in-memory provider will also work with searching ( searchUser
) as federationLoad will first pre-load users into in-memory and
then calls "query" and proxy users.
The only limitation I can see now is calling of
session.users().getUsers() as this doesn't preload users from
federation. But if people add cache and use chain like:
cache => federationManager => inMemory
it will work fine and find all users retrieved from LDAP in any
previous requests.
In summary: UserProvider chaining is:
1) Very flexible
2) Supports in-memory, but also other use-cases too. It's all up
to admin preference how to configure chain
3) No dependencies of providers on each other
4) Minimal changes to UserFederationManager and
DefaultCacheUserProvider . Just need to call "getNext()" to
retrieve next provider
5) Current UserFederationProvider will work fine for all cases and
automatically gains "in-memory" support without need to change
anything in their code. Assuming that for backwards compatibility,
we will keep "session.userStorage()" to always point to next
delegate of UserFederationManager . If it's JPA, then imports user
like now. If it will be "in-memory" it will just return cache user
for this request and return per-request inMemory user.
Marek
On 03/12/15 18:58, Bill Burke wrote: