keycloak-scim/docs/documentation/server_development/topics/user-storage/augmenting.adoc
2023-05-16 11:57:26 +02:00

105 lines
4.6 KiB
Text

=== Augmenting external storage
The `PropertyFileUserStorageProvider` example is really limited. While we will be able to log in with users stored
in a property file, we won't be able to do much else. If users loaded by this provider need special role or group
mappings to fully access particular applications there is no way for us to add additional role mappings to these users.
You also can't modify or add additional important attributes like email, first and last name.
For these types of situations, {project_name} allows you to augment your external store by storing extra information
in {project_name}'s database. This is called federated user storage and is encapsulated within the
`org.keycloak.storage.federated.UserFederatedStorageProvider` class.
.UserFederatedStorageProvider
[source,java]
----
package org.keycloak.storage.federated;
public interface UserFederatedStorageProvider extends Provider,
UserAttributeFederatedStorage,
UserBrokerLinkFederatedStorage,
UserConsentFederatedStorage,
UserNotBeforeFederatedStorage,
UserGroupMembershipFederatedStorage,
UserRequiredActionsFederatedStorage,
UserRoleMappingsFederatedStorage,
UserFederatedUserCredentialStore {
...
Stream<GroupModel> getGroupsStream(RealmModel realm, String userId)
void joinGroup(RealmModel realm, String userId, GroupModel group);
void leaveGroup(RealmModel realm, String userId, GroupModel group);
Stream<String> getMembershipStream(RealmModel realm, GroupModel group, Integer firstResult, Integer max);
...
interface Streams extends UserFederatedStorageProvider,
UserAttributeFederatedStorage.Streams,
UserBrokerLinkFederatedStorage.Streams,
UserConsentFederatedStorage.Streams,
UserFederatedUserCredentialStore.Streams,
UserGroupMembershipFederatedStorage.Streams,
UserRequiredActionsFederatedStorage.Streams,
UserRoleMappingsFederatedStorage.Streams {
...
@Override
default List<String> getStoredUsers(RealmModel realm, int first, int max) {
return this.getStoredUsersStream(realm, first, max).collect(Collectors.toList());
}
@Override
Stream<String> getStoredUsersStream(RealmModel realm, Integer first, Integer max);
...
}
}
----
The `UserFederatedStorageProvider` instance is available on the `KeycloakSession.userFederatedStorage()` method.
It has all different kinds of methods for storing attributes, group and role mappings, different credential types,
and required actions. If your external store's datamodel cannot support the full {project_name} feature
set, then this service can fill in the gaps.
Also, the `UserFederatedStorageProvider.Streams` interface exists. It makes all collection-based methods in `UserFederatedStorageProvider`
default by providing implementations that delegate to the stream-based variants instead of the other way around.
It allows for implementations to focus on the stream-based approach for processing sets of data and benefit
from the potential memory and performance optimizations of that approach. See <<_stream_based_interfaces,Stream-based interfaces>> for more information.
{project_name} comes with a helper class `org.keycloak.storage.adapter.AbstractUserAdapterFederatedStorage`
that will delegate every single `UserModel` method except get/set of username to user federated storage. Override
the methods you need to override to delegate to your external storage representations. It is strongly
suggested you read the javadoc of this class as it has smaller protected methods you may want to override. Specifically
surrounding group membership and role mappings.
==== Augmentation example
In our `PropertyFileUserStorageProvider` example, we just need a simple change to our provider to use the
`AbstractUserAdapterFederatedStorage`.
.PropertyFileUserStorageProvider
[source,java]
----
protected UserModel createAdapter(RealmModel realm, String username) {
return new AbstractUserAdapterFederatedStorage(session, realm, model) {
@Override
public String getUsername() {
return username;
}
@Override
public void setUsername(String username) {
String pw = (String)properties.remove(username);
if (pw != null) {
properties.put(username, pw);
save();
}
}
};
}
----
We instead define an anonymous class implementation of `AbstractUserAdapterFederatedStorage`. The `setUsername()`
method makes changes to the properties file and saves it.