I have created an oidc mapper extension in order to create a uid attribute on the brokered user based on a hash of the username .
This works well when the user is federated for the first time. But it does not update the user attribute if the user is already there
@AutoService(IdentityProviderMapper.class)
public class username2uidmapper extends AbstractClaimMapper {
public static final String COMPATIBLE_PROVIDERS = {KeycloakOIDCIdentityProviderFactory.PROVIDER_ID, OIDCIdentityProviderFactory.PROVIDER_ID};
private static final List configProperties = new ArrayList<>();
public static final String USER_ATTRIBUTE = “user.attribute”;
private static final Set IDENTITY_PROVIDER_SYNC_MODES = new HashSet<>(Arrays.asList(IdentityProviderSyncMode.values()));public static final String MINVALUE = "10000"; public static final String MAXVALUE = "60000"; public static final String updateExisting = "false"; private final Logger logger = Logger.getLogger(this.toString()); static { ProviderConfigProperty property; ProviderConfigProperty property1; ProviderConfigProperty property2; ProviderConfigProperty property3; property3 = new ProviderConfigProperty(); property3.setName(MAXVALUE); property3.setLabel("Max value"); property3.setHelpText("The max value of the uid range"); property3.setType(ProviderConfigProperty.STRING_TYPE); configProperties.add(property3); property2 = new ProviderConfigProperty(); property2.setName(MINVALUE); property2.setLabel("Min value"); property2.setHelpText("The min value of the uid range"); property2.setType(ProviderConfigProperty.STRING_TYPE); configProperties.add(property2); property1 = new ProviderConfigProperty(); property1.setName(CLAIM); property1.setLabel("Claim"); property1.setHelpText("Name of claim to search for in the token. Usually it is preferred_username. You can reference nested claims using a '.', i.e. 'address.locality'. To use dot (.) literally, escape it with backslash (\\.)"); property1.setType(ProviderConfigProperty.STRING_TYPE); configProperties.add(property1); property = new ProviderConfigProperty(); property.setName(USER_ATTRIBUTE); property.setLabel("User Attribute Name"); property.setHelpText("User attribute name which will be stored in the attribute. Normally and for linux use this will be uid."); property.setType(ProviderConfigProperty.STRING_TYPE); configProperties.add(property); } public static final String PROVIDER_ID = "username2uid-user-attribute-idp-mapper"; @Override public boolean supportsSyncMode(IdentityProviderSyncMode syncMode) { return IDENTITY_PROVIDER_SYNC_MODES.contains(syncMode); } @Override public List<ProviderConfigProperty> getConfigProperties() { return configProperties; } static final List<ProviderConfigProperty> CONFIG_PROPERTIES; @Override public String getDisplayCategory() { return "Attribute Importer"; } static { CONFIG_PROPERTIES = new ArrayList<>(); } @Override public String[] getCompatibleProviders() { return COMPATIBLE_PROVIDERS; } @Override public String getId() { return PROVIDER_ID; } @Override public String getDisplayType() { return "Create uid from username"; } @Override public void preprocessFederatedIdentity(KeycloakSession session, RealmModel realm, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) { String attribute = mapperModel.getConfig().get(USER_ATTRIBUTE); if (Objects.equals(attribute, "") || attribute == null) { context.removeUserAttribute(attribute); return; } Object value = getClaimValue(mapperModel, context); if (value == null || value == "") { context.removeUserAttribute(attribute); return; } logger.error("INSIDE FUNCTION preprocessFederatedIdentity value is "+ value); String minvalue = mapperModel.getConfig().get(MINVALUE); String maxvalue = mapperModel.getConfig().get(MAXVALUE); try { int result = hashToIntRange(value.toString(), Integer.parseInt(minvalue), Integer.parseInt(maxvalue)); logger.error("INSIDE FUNCTION preprocessFederatedIdentity Result: " + result); List<String> values = toList(result); List<String> valuesToString = values.stream() .filter(Objects::nonNull) .map(Object::toString) .collect(Collectors.toList()); logger.error("INSIDE FUNCTION preprocessFederatedIdentity will set attribute to "+ attribute); logger.error("INSIDE FUNCTION preprocessFederatedIdentity will set attribute to "+ valuesToString); context.setUserAttribute(attribute, valuesToString); } catch (Exception e) { // Handle script evaluation exception logger.error("Exception : " + e); } } public int hashToIntRange(String input, int min, int max) { // Get the hash code of the input string int hashCode = input.hashCode(); // Map the hash code to the desired range int range = max - min + 1; return Math.abs(hashCode % range) + min; } private List<String> toList(Object value) { List<Object> values = (value instanceof List) ? (List) value : Collections.singletonList(value); return values.stream() .filter(Objects::nonNull) .map(Object::toString) .collect(Collectors.toList()); }
the same functionality is also in
@Override public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
which is called when synch mode is Force.
Function is called I can see this in the logs there is no exception but attribute is not updated.
Even if i use a hardcoded value it is applied only the first time the user is imported from the idp.