[keycloak-dev] User Federation Provider Cache

Bill Burke bburke at redhat.com
Wed Jun 15 11:33:01 EDT 2016


This redesign is quite complex as you have to take a lot of variables 
into account.  I also am trying to make things backward compatible and 
have the new SPI co-exist with the old.  Just remember though, the 
biggest reason for this rewrite is to AVOID IMPORTING!

Here's what I'm currently doing:

Federated Data
--------------
There will be a built-in federated user data service.  It exists to 
store additional data about a user, for example:

public interface UserAttributeFederatedStorage {
     void setSingleAttribute(RealmModel realm, UserModel user, String 
name, String value);
     void setAttribute(RealmModel realm, UserModel user, String name, 
List<String> values);
     void removeAttribute(RealmModel realm, UserModel user, String name);
     MultivaluedHashMap<String, String> getAttributes(RealmModel realm, 
UserModel user);
}

There will be federated storage options for all data.

User Storage Provider SPI
-----------------
Current UserProvider is going to be split up into smaller interface. 
SPI will remain the same though.  There is going to be a 
UserStorageProvider SPI.  Instances can implement one or more of the 
smaller UserProvider interfaces.

User Storage Provider instances will be returning UserModel instances. 
This means providers will be responsible for implementing some sort of 
UserAdapter implementation.  These custom UserAdapters will be 
responsible for interacting with the Federated Data Store and their 
custom storage:  Merging data between i.e. LDAP and the Federated 
Storage, managing which store stores what data, managing what data lives 
where.  Going to provide a helper abstract UserAdapter class that helps 
managing this data.

There will be a UserStorageManager class that implements all 
UserProvider instances.  Our caching layer will delegate to that instead 
of the actual storage provider.  The UserStorageManager will work 
similarly to UserFederationManager.  So, it would work like this:

1. session.users().getUserByUsername(realm, username)
2. User cache looks in cache, doesn't find it,
3. UserStorageManager.getUserByUsername()
4. UserStorageManager iterates all UserStorageProviders looking to see 
which one offers session.users().getUserByUsername() and invoke it.
5. LDAP.getUserByUsername()
6. LDAP finds the user, creates a UserAdapter, returns it.
7. User cache gets the UserModel returned by LDAP, it caches exactly 
like it is implemented now.  The LDAP UserAdapter, is responsible for 
pulling data and merging it from LDAP store and Federation Store.

User ids are an URN

"f:" + providerId + ":" + provider-user-metadata

UserStorageManager.getUserById() will parse the ID and call directly on 
the UserStorageProvider that initially loaded the user.

Updates are invoked directly on UserModel class.  LDAP UserAdapter will 
be responsible for figuring out what data is stored/updated in LDAP and 
what should be stored in Federated Storage.

More comments follow:


On 6/15/16 4:47 AM, Marek Posolda wrote:
> Hi Bill,
>
> few notes from me. I am mostly adding the scenarios, which will be good
> to support/improve or which we should still keep supporting after
> refactoring IMO. Sorry for bit longer email :)
>
> Note: When I talk "LDAP", I usually mean "LDAP or any other 3rd party
> federation storage"
>
>
> Persistent vs. Transient modes
> ----------------------------------------
>
> I think we should still keep some support for "persistent" mode (the
> case when user is imported to Keycloak local DB).
>

I want to get rid of import and only have it as an option for Brokering 
or for transient users (users that can't be looked up from a store out 
of band).  The LDAP adapter will be responsible for knowing what data is 
stored in LDAP and what data is stored in the Federated Store, so there 
is no need to "import"


> Assuming the scenario:
> - john is imported from LDAP with "unsynced" mode
> - john changes his password in account management. LDAP is unsynced
> (read-only), so the password needs to be changed in Keycloak storage
> - After server restart, john should have set the new password, not the
> old one from LDAP. Hence the only possibility seems to keep support for
> "import" this user into Keycloak DB and have user persistent.
>
> The similar situation applies for any update of user, which can't be
> saved back to LDAP (either because LDAP is read-only or because it
> doesn't support store the keycloak metadata like social links, consents,
> required actions etc)
>
> Regarding this, I am thinking about possible import modes like:
> 1) Persistent : User attributes from LDAP are imported into Keycloak DB
> (same behaviour like now)
>

No more importing.

> 2) Transient : User attributes from LDAP are not imported into Keycloak
> DB, but just cached locally. Any updates of the user are either dropped
> after server restart or disallowed. For example: Maybe consents can be
> just dropped, but some other things like requiredRoles should be rather
> disallowed to change? For example if admin adds some requiredAction to
> user "john", the requiredAction shouldn't disappear after server
> restart. This would be a security hole IMO. So in that case, if admin
> tries to manually add requiredAction to such user, the exception will be
> thrown so admin is aware that this is not supported. Anyway, for support
> any writing to cache, the cache will need to be replicated though (for
> example: user consent saved into cache on node1 should be visible on
> node2 as well).
>


> 3) Hybrid : LDAP user is not imported into Keycloak DB immediatelly.
> They are imported "on-demand" after user is updated and the update can't
> be saved to LDAP
>

No more on-demand loading of data.  IIRC, we discussed this in Brno. 
LDAP users will be cached in the same way JPA Users currently are.



>
> Caching
> -----------
>
> I hope new SPI will allow us more flexibility to support various
> scenarios. It will be good to support at least those IMO:
>

See above.  LDAP users will be cached in same way JPA Users currently are.

> 3) I agree that will be cool to support different expiration time based
> if it's LDAP user or just Keycloak-local user. Infinispan allows it
> though with something like : cache.put("ldapuser", ldapUser, 60,
> TimeUnit.SECONDS);
>

I think this will be an option that we will want to support.

>
>
> Transactions and 3rd party updates
> -----------------------------------------------
>
> - Will be good to improve registration of user to LDAP. Ideally during
> registration new user to LDAP, we should allow to send all data at once.
> (currently UserFederationProvider.register supports sending just
> username). Also we should allow to specify if register to 3rd party
> provider should be done *before* or *after* the registration to Keycloak
> local DB. For details, see https://issues.jboss.org/browse/KEYCLOAK-1075
> and all the comments from users...
>
> - Also updating user should be ideally at once. For example if you call:
> user.setFirstName("john");
> user.setLastName("Doe");
> user.setEmail("john at email.cz");
>
> we shouldn't have 3 update calls to LDAP, but just one. Maybe we can
> address this with transaction abstraction? I've already did something
> for LDAP provider (see TxAwareLDAPUserModelDelegate ), however will be
> good to provide something more generic for user storage SPI. Then when
> KeycloakTransactionManager.commit is called, the data are send to
> federation storage
>

This would be an implementation detail.  Similar to JPA Users, the LDAP 
UserAdapter will be remembered per session.  The LDAP adapter could 
queue up updates then at commit time flush them all.

Maybe you should consider writing an LDAP version of JPA?  ;-)  Probably 
a lot of code could be borrowed from PL IDM API.



>
> Sync users
> --------------
> We should still keep the option to sync users into Keycloak DB as we
> have now. Note some persistent storages like LDAP are limited with
> pagination. So the easiest possibility for some admins is just to sync
> users, so they can easily search them in admin console.
>
Doing a full import just to support pagination is overkill.  I'm 
guessing that a lot of deployments will not manage users through the 
Keycload admin console.  We can offer a manual a Sync SPI that providers 
can implement.




>
> Proxy yes/no?
> ------------------
>

Proxy yes, but it won't work the same.  See above.

Bill


More information about the keycloak-dev mailing list