Skip to content

Commit

Permalink
Add example for storing custom user attributes for ldap federated use…
Browse files Browse the repository at this point in the history
…rs locally, despite a read-only ldap connection.

Signed-off-by: Thomas Darimont <[email protected]>
  • Loading branch information
thomasdarimont committed Aug 29, 2024
1 parent 425759e commit b86a03f
Show file tree
Hide file tree
Showing 2 changed files with 121 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package com.github.thomasdarimont.keycloak.custom.userstorage.ldap;

import com.google.auto.service.AutoService;
import lombok.extern.jbosslog.JBossLog;
import org.keycloak.Config;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.storage.UserStorageProviderFactory;
import org.keycloak.storage.ldap.LDAPIdentityStoreRegistry;
import org.keycloak.storage.ldap.LDAPStorageProvider;
import org.keycloak.storage.ldap.LDAPStorageProviderFactory;
import org.keycloak.storage.ldap.idm.model.LDAPObject;
import org.keycloak.storage.ldap.idm.store.ldap.LDAPIdentityStore;
import org.keycloak.storage.ldap.mappers.LDAPConfigDecorator;

import java.util.Map;
import java.util.regex.Pattern;

/**
* Example for a custom {@link LDAPStorageProvider} which supports storing user attributes locally despite a read-only ldap connection.
*/
public class AcmeLDAPStorageProvider extends LDAPStorageProvider {

private final Pattern localCustomAttributePattern;

public AcmeLDAPStorageProvider(LDAPStorageProviderFactory factory, KeycloakSession session, ComponentModel model, LDAPIdentityStore ldapIdentityStore, Pattern localCustomAttributePattern) {
super(factory, session, model, ldapIdentityStore);
this.localCustomAttributePattern = localCustomAttributePattern;
}

@Override
protected UserModel proxy(RealmModel realm, UserModel local, LDAPObject ldapObject, boolean newUser) {
UserModel proxy = super.proxy(realm, local, ldapObject, newUser);
return new AcmeReadonlyLDAPUserModelDelegate(proxy, localCustomAttributePattern);
}

@JBossLog
@AutoService(UserStorageProviderFactory.class)
public static class Factory extends LDAPStorageProviderFactory {

private LDAPIdentityStoreRegistry ldapStoreRegistry;

private Pattern localCustomAttributePattern;

@Override
public void init(Config.Scope config) {
this.ldapStoreRegistry = new LDAPIdentityStoreRegistry();
String localCustomAttributePatternString = config.get("localCustomAttributePattern", "(custom-.*|foo)");
log.infof("Using local custom attribute pattern: %s", localCustomAttributePatternString);
this.localCustomAttributePattern = Pattern.compile(localCustomAttributePatternString);
}

@Override
public LDAPStorageProvider create(KeycloakSession session, ComponentModel model) {
Map<ComponentModel, LDAPConfigDecorator> configDecorators = getLDAPConfigDecorators(session, model);

LDAPIdentityStore ldapIdentityStore = this.ldapStoreRegistry.getLdapStore(session, model, configDecorators);
return new AcmeLDAPStorageProvider(this, session, model, ldapIdentityStore, localCustomAttributePattern);
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package com.github.thomasdarimont.keycloak.custom.userstorage.ldap;

import org.keycloak.models.UserModel;
import org.keycloak.models.utils.UserModelDelegate;
import org.keycloak.storage.ldap.ReadonlyLDAPUserModelDelegate;

import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;

public class AcmeReadonlyLDAPUserModelDelegate extends ReadonlyLDAPUserModelDelegate {

private final Pattern localCustomAttributePattern;

public AcmeReadonlyLDAPUserModelDelegate(UserModel delegate, Pattern localCustomAttributePattern) {
super(delegate);
this.localCustomAttributePattern = localCustomAttributePattern;
}

@Override
public void setAttribute(String name, List<String> values) {

if (localCustomAttributePattern.matcher(name).matches()) {
UserModel rootDelegate = getRootDelegate(delegate);
rootDelegate.setSingleAttribute(name, values.get(0));
return;
}

super.setAttribute(name, values);
}

@Override
public void removeAttribute(String name) {

if (localCustomAttributePattern.matcher(name).matches()) {
UserModel rootDelegate = getRootDelegate(delegate);
rootDelegate.removeAttribute(name);
return;
}

super.removeAttribute(name);
}

/**
* Unwrap deeply nested {@link UserModelDelegate UserModelDelegate's}
*
* @param delegate
* @return
*/
private UserModel getRootDelegate(UserModel delegate) {
UserModel current = delegate;
while (current instanceof UserModelDelegate del) {
current = del.getDelegate();
}
return current;
}
}

0 comments on commit b86a03f

Please sign in to comment.