package org.apereo.cas.oidc.profile;
import org.apereo.cas.authentication.principal.Principal;
import org.apereo.cas.authentication.principal.PrincipalFactory;
import org.apereo.cas.authentication.principal.Service;
import org.apereo.cas.oidc.OidcConstants;
import org.apereo.cas.oidc.claims.BaseOidcScopeAttributeReleasePolicy;
import org.apereo.cas.oidc.claims.OidcAddressScopeAttributeReleasePolicy;
import org.apereo.cas.oidc.claims.OidcCustomScopeAttributeReleasePolicy;
import org.apereo.cas.oidc.claims.OidcEmailScopeAttributeReleasePolicy;
import org.apereo.cas.oidc.claims.OidcPhoneScopeAttributeReleasePolicy;
import org.apereo.cas.oidc.claims.OidcProfileScopeAttributeReleasePolicy;
import org.apereo.cas.oidc.claims.mapping.OidcAttributeToScopeClaimMapper;
import org.apereo.cas.services.ChainingAttributeReleasePolicy;
import org.apereo.cas.services.DenyAllAttributeReleasePolicy;
import org.apereo.cas.services.OidcRegisteredService;
import org.apereo.cas.services.RegisteredService;
import org.apereo.cas.services.ServicesManager;
import org.apereo.cas.support.oauth.profile.DefaultOAuth20ProfileScopeToAttributesFilter;
import org.apereo.cas.support.oauth.util.OAuth20Utils;
import org.jooq.lambda.Unchecked;
import org.pac4j.core.context.J2EContext;
import org.reflections.Reflections;
import org.reflections.scanners.SubTypesScanner;
import org.reflections.util.ClasspathHelper;
import org.reflections.util.ConfigurationBuilder;
import org.reflections.util.FilterBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* This is {@link OidcProfileScopeToAttributesFilter}.
*
* @author Misagh Moayyed
* @since 5.1.0
*/
public class OidcProfileScopeToAttributesFilter extends DefaultOAuth20ProfileScopeToAttributesFilter {
private static final Logger LOGGER = LoggerFactory.getLogger(OidcProfileScopeToAttributesFilter.class);
private Map<String, BaseOidcScopeAttributeReleasePolicy> filters;
private Collection<BaseOidcScopeAttributeReleasePolicy> userScopes;
private final OidcAttributeToScopeClaimMapper attributeToScopeClaimMapper;
private final PrincipalFactory principalFactory;
private final ServicesManager servicesManager;
public OidcProfileScopeToAttributesFilter(final PrincipalFactory principalFactory,
final ServicesManager servicesManager,
final Collection<BaseOidcScopeAttributeReleasePolicy> userScopes,
final OidcAttributeToScopeClaimMapper attributeToScopeClaimMapper) {
this.attributeToScopeClaimMapper = attributeToScopeClaimMapper;
this.filters = new HashMap<>();
final String packageName = BaseOidcScopeAttributeReleasePolicy.class.getPackage().getName();
final Reflections reflections =
new Reflections(new ConfigurationBuilder()
.filterInputsBy(new FilterBuilder().includePackage(packageName))
.setUrls(ClasspathHelper.forPackage(packageName))
.setScanners(new SubTypesScanner(true)));
final Set<Class<? extends BaseOidcScopeAttributeReleasePolicy>> subTypes =
reflections.getSubTypesOf(BaseOidcScopeAttributeReleasePolicy.class);
subTypes.forEach(Unchecked.consumer(t -> {
final BaseOidcScopeAttributeReleasePolicy ex = t.newInstance();
filters.put(ex.getScopeName(), ex);
}));
userScopes.forEach(t -> filters.put(t.getScopeName(), t));
this.principalFactory = principalFactory;
this.servicesManager = servicesManager;
this.userScopes = userScopes;
}
@Override
public Principal filter(final Service service, final Principal profile,
final RegisteredService registeredService, final J2EContext context) {
final Principal principal = super.filter(service, profile, registeredService, context);
if (registeredService instanceof OidcRegisteredService) {
final OidcRegisteredService oidcService = (OidcRegisteredService) registeredService;
final Collection<String> scopes = new ArrayList<>(OAuth20Utils.getRequestedScopes(context));
scopes.addAll(oidcService.getScopes());
if (!scopes.contains(OidcConstants.OPENID)) {
LOGGER.debug("Request does not indicate a scope [{}] that can identify OpenID Connect", scopes);
return principal;
}
final Map<String, Object> attributes = new HashMap<>();
filterAttributesByScope(scopes, attributes, principal, service, oidcService);
return this.principalFactory.createPrincipal(profile.getId(), attributes);
}
return principal;
}
private void filterAttributesByScope(final Collection<String> stream,
final Map<String, Object> attributes,
final Principal principal,
final Service service,
final RegisteredService registeredService) {
stream.stream()
.distinct()
.filter(s -> this.filters.containsKey(s))
.forEach(s -> {
final BaseOidcScopeAttributeReleasePolicy policy = filters.get(s);
attributes.putAll(policy.getAttributes(principal, service, registeredService));
});
}
@Override
public void reconcile(final RegisteredService service) {
if (!(service instanceof OidcRegisteredService)) {
super.reconcile(service);
return;
}
final List<String> otherScopes = new ArrayList<>();
final ChainingAttributeReleasePolicy policy = new ChainingAttributeReleasePolicy();
final OidcRegisteredService oidc = OidcRegisteredService.class.cast(service);
oidc.getScopes().forEach(s -> {
switch (s.trim().toLowerCase()) {
case OidcConstants.EMAIL:
policy.getPolicies().add(new OidcEmailScopeAttributeReleasePolicy());
break;
case OidcConstants.ADDRESS:
policy.getPolicies().add(new OidcAddressScopeAttributeReleasePolicy());
break;
case OidcConstants.PROFILE:
policy.getPolicies().add(new OidcProfileScopeAttributeReleasePolicy());
break;
case OidcConstants.PHONE:
policy.getPolicies().add(new OidcPhoneScopeAttributeReleasePolicy());
break;
case OidcConstants.OFFLINE_ACCESS:
oidc.setGenerateRefreshToken(true);
break;
case OidcCustomScopeAttributeReleasePolicy.SCOPE_CUSTOM:
otherScopes.add(s.trim());
break;
default:
final BaseOidcScopeAttributeReleasePolicy userPolicy = userScopes.stream()
.filter(t -> t.getScopeName().equals(s.trim()))
.findFirst()
.orElse(null);
if (userPolicy != null) {
policy.getPolicies().add(userPolicy);
}
}
});
otherScopes.remove(OidcConstants.OPENID);
if (!otherScopes.isEmpty()) {
policy.getPolicies().add(new OidcCustomScopeAttributeReleasePolicy(otherScopes));
}
if (policy.getPolicies().isEmpty()) {
oidc.setAttributeReleasePolicy(new DenyAllAttributeReleasePolicy());
} else {
oidc.setAttributeReleasePolicy(policy);
}
this.servicesManager.save(oidc);
}
}