135 lines
5.9 KiB
Java
135 lines
5.9 KiB
Java
package org.gcube.keycloak.protocol.oidc.mapper;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
|
|
import org.jboss.logging.Logger;
|
|
import org.keycloak.models.ClientSessionContext;
|
|
import org.keycloak.models.KeycloakSession;
|
|
import org.keycloak.models.ProtocolMapperModel;
|
|
import org.keycloak.models.UserSessionModel;
|
|
import org.keycloak.protocol.oidc.mappers.AbstractOIDCProtocolMapper;
|
|
import org.keycloak.protocol.oidc.mappers.OIDCAccessTokenMapper;
|
|
import org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper;
|
|
import org.keycloak.provider.ProviderConfigProperty;
|
|
import org.keycloak.representations.AccessToken;
|
|
import org.keycloak.representations.AccessToken.Access;
|
|
import org.keycloak.representations.IDToken;
|
|
|
|
public class D4ScienceContextMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper {
|
|
|
|
private static final Logger logger = Logger.getLogger(D4ScienceContextMapper.class);
|
|
|
|
public static final String HTTP_REQUEST_HEADER_NAME = "d4scm.header-name";
|
|
public static final String NARROW_RESOURCE_ACCESS = "d4scm.narrow-ra";
|
|
|
|
private static final List<ProviderConfigProperty> CONFIG_PROPERTIES = new ArrayList<>();
|
|
|
|
// Assuring that the mapper is executed as last
|
|
private static final int PRIORITY = Integer.MAX_VALUE;
|
|
private static final String DISPLAY_TYPE = "OIDC D4Science Context Mapper";
|
|
private static final String PROVIDER_ID = "oidc-d4scince-context-mapper";
|
|
|
|
public static final String DEFAULT_HEADER_NAME = "X-D4Science-Context";
|
|
public static final String DEFAULT_TOKEN_CLAIM = "aud";
|
|
|
|
static {
|
|
OIDCAttributeMapperHelper.addTokenClaimNameConfig(CONFIG_PROPERTIES);
|
|
CONFIG_PROPERTIES.forEach(conf -> {
|
|
if (OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME.equals(conf.getName()))
|
|
conf.setDefaultValue(DEFAULT_TOKEN_CLAIM);
|
|
conf.setReadOnly(true);
|
|
});
|
|
|
|
OIDCAttributeMapperHelper.addIncludeInTokensConfig(CONFIG_PROPERTIES, D4ScienceContextMapper.class);
|
|
|
|
ProviderConfigProperty headerProperty = new ProviderConfigProperty();
|
|
headerProperty.setName(HTTP_REQUEST_HEADER_NAME);
|
|
headerProperty.setLabel("HTTP request header name with the requested context");
|
|
headerProperty.setType(ProviderConfigProperty.STRING_TYPE);
|
|
headerProperty.setHelpText("The HTTP header that contains the requested context to be mapped as the requested in the configured claim");
|
|
headerProperty.setDefaultValue(DEFAULT_HEADER_NAME);
|
|
headerProperty.setReadOnly(true);
|
|
CONFIG_PROPERTIES.add(headerProperty);
|
|
|
|
ProviderConfigProperty narrowProperty = new ProviderConfigProperty();
|
|
narrowProperty.setName(NARROW_RESOURCE_ACCESS);
|
|
narrowProperty.setLabel("Narrow down resource access array?");
|
|
narrowProperty.setType(ProviderConfigProperty.BOOLEAN_TYPE);
|
|
narrowProperty.setHelpText("Narrow down resource access claim to contain only the requested context entry");
|
|
CONFIG_PROPERTIES.add(narrowProperty);
|
|
}
|
|
|
|
@Override
|
|
public String getDisplayCategory() {
|
|
return TOKEN_MAPPER_CATEGORY;
|
|
}
|
|
|
|
@Override
|
|
public int getPriority() {
|
|
return PRIORITY;
|
|
}
|
|
|
|
@Override
|
|
public String getDisplayType() {
|
|
return DISPLAY_TYPE;
|
|
}
|
|
|
|
@Override
|
|
public String getHelpText() {
|
|
return "Maps the D4Science context audience by reading the configured header's value and sets it as the configured token claim, if it is in scope";
|
|
}
|
|
|
|
@Override
|
|
public List<ProviderConfigProperty> getConfigProperties() {
|
|
return CONFIG_PROPERTIES;
|
|
}
|
|
|
|
@Override
|
|
public String getId() {
|
|
return PROVIDER_ID;
|
|
}
|
|
|
|
@Override
|
|
protected void setClaim(final IDToken token,
|
|
final ProtocolMapperModel mappingModel,
|
|
final UserSessionModel userSession,
|
|
final KeycloakSession keycloakSession,
|
|
final ClientSessionContext clientSessionCtx) {
|
|
|
|
// Since only the OIDCAccessTokenMapper interface is implemented, we are almost sure that
|
|
// the token object is an AccessToken but adding a specific check anyway
|
|
if (token instanceof AccessToken) {
|
|
AccessToken accessToken = (AccessToken) token;
|
|
String headerName = mappingModel.getConfig().get(HTTP_REQUEST_HEADER_NAME);
|
|
if (headerName == null || "".equals(headerName)) {
|
|
headerName = DEFAULT_HEADER_NAME;
|
|
}
|
|
logger.tracef("Looking for the '%s' header", headerName);
|
|
String requestedD4SContext = keycloakSession.getContext().getRequestHeaders().getHeaderString(headerName);
|
|
|
|
if (requestedD4SContext != null && !"".equals(requestedD4SContext)) {
|
|
logger.debugf("Checking resource access for the requested context: %s", requestedD4SContext);
|
|
Access contextAccessInResourceAccess = accessToken.getResourceAccess(requestedD4SContext);
|
|
if (contextAccessInResourceAccess != null) {
|
|
logger.debugf("Mapping it as the configured claim: %s",
|
|
mappingModel.getConfig().get(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME));
|
|
|
|
OIDCAttributeMapperHelper.mapClaim(token, mappingModel, requestedD4SContext);
|
|
|
|
if (Boolean.parseBoolean(mappingModel.getConfig().get(NARROW_RESOURCE_ACCESS))) {
|
|
logger.debugf("Removing all access details but the requested context");
|
|
accessToken.getResourceAccess().clear();
|
|
accessToken.getResourceAccess().put(requestedD4SContext, contextAccessInResourceAccess);
|
|
}
|
|
} else {
|
|
logger.warnf("Requested context '%s' is not accessible to the client: %s", requestedD4SContext,
|
|
clientSessionCtx.getClientSession().getClient().getName());
|
|
}
|
|
} else {
|
|
logger.tracef("Header not found in request");
|
|
}
|
|
}
|
|
}
|
|
|
|
} |