package org.keycloak.protocol.oidc.mappers;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ProtocolMapperContainerModel;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.protocol.ProtocolMapperConfigException;
import org.keycloak.protocol.oidc.utils.PairwiseSubMapperUtils;
import org.keycloak.protocol.oidc.utils.PairwiseSubMapperValidator;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.IDToken;
import java.util.LinkedList;
import java.util.List;
/**
* Set the 'sub' claim to pairwise .
*
* @author <a href="mailto:martin.hardselius@gmail.com">Martin Hardselius</a>
*/
public abstract class AbstractPairwiseSubMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper, UserInfoTokenMapper {
public static final String PROVIDER_ID_SUFFIX = "-pairwise-sub-mapper";
public abstract String getIdPrefix();
/**
* Generates a pairwise subject identifier.
*
* @param mappingModel
* @param sectorIdentifier client sector identifier
* @param localSub local subject identifier (user id)
* @return A pairwise subject identifier
*/
public abstract String generateSub(ProtocolMapperModel mappingModel, String sectorIdentifier, String localSub);
/**
* Override to add additional provider configuration properties. By default, a pairwise sub mapper will only contain configuration for a sector identifier URI.
*
* @return A list of provider configuration properties.
*/
public List<ProviderConfigProperty> getAdditionalConfigProperties() {
return new LinkedList<>();
}
/**
* Override to add additional configuration validation. Called when instance of mapperModel is created/updated for this protocolMapper through admin endpoint.
*
* @param session
* @param realm
* @param mapperContainer client or clientTemplate
* @param mapperModel
* @throws ProtocolMapperConfigException if configuration provided in mapperModel is not valid
*/
public void validateAdditionalConfig(KeycloakSession session, RealmModel realm, ProtocolMapperContainerModel mapperContainer, ProtocolMapperModel mapperModel) throws ProtocolMapperConfigException {
}
@Override
public final String getDisplayCategory() {
return AbstractOIDCProtocolMapper.TOKEN_MAPPER_CATEGORY;
}
@Override
public final IDToken transformIDToken(IDToken token, ProtocolMapperModel mappingModel, KeycloakSession session, UserSessionModel userSession, AuthenticatedClientSessionModel clientSession) {
setSubject(token, generateSub(mappingModel, getSectorIdentifier(clientSession.getClient(), mappingModel), userSession.getUser().getId()));
return token;
}
@Override
public final AccessToken transformAccessToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session, UserSessionModel userSession, AuthenticatedClientSessionModel clientSession) {
setSubject(token, generateSub(mappingModel, getSectorIdentifier(clientSession.getClient(), mappingModel), userSession.getUser().getId()));
return token;
}
@Override
public final AccessToken transformUserInfoToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session, UserSessionModel userSession, AuthenticatedClientSessionModel clientSession) {
setSubject(token, generateSub(mappingModel, getSectorIdentifier(clientSession.getClient(), mappingModel), userSession.getUser().getId()));
return token;
}
private void setSubject(IDToken token, String pairwiseSub) {
token.getOtherClaims().put("sub", pairwiseSub);
}
@Override
public final List<ProviderConfigProperty> getConfigProperties() {
List<ProviderConfigProperty> configProperties = new LinkedList<>();
configProperties.add(PairwiseSubMapperHelper.createSectorIdentifierConfig());
configProperties.addAll(getAdditionalConfigProperties());
return configProperties;
}
private String getSectorIdentifier(ClientModel client, ProtocolMapperModel mappingModel) {
String sectorIdentifierUri = PairwiseSubMapperHelper.getSectorIdentifierUri(mappingModel);
if (sectorIdentifierUri != null && !sectorIdentifierUri.isEmpty()) {
return PairwiseSubMapperUtils.resolveValidSectorIdentifier(sectorIdentifierUri);
}
return PairwiseSubMapperUtils.resolveValidSectorIdentifier(client.getRootUrl(), client.getRedirectUris());
}
@Override
public final void validateConfig(KeycloakSession session, RealmModel realm, ProtocolMapperContainerModel mapperContainer, ProtocolMapperModel mapperModel) throws ProtocolMapperConfigException {
ClientModel client = null;
if (mapperContainer instanceof ClientModel) {
client = (ClientModel) mapperContainer;
PairwiseSubMapperValidator.validate(session, client, mapperModel);
}
validateAdditionalConfig(session, realm, mapperContainer, mapperModel);
}
@Override
public final String getId() {
return "oidc-" + getIdPrefix() + PROVIDER_ID_SUFFIX;
}
}