package org.apereo.cas.adaptors.x509.config;
import org.apache.commons.lang3.StringUtils;
import org.apereo.cas.adaptors.x509.authentication.CRLFetcher;
import org.apereo.cas.adaptors.x509.authentication.ResourceCRLFetcher;
import org.apereo.cas.adaptors.x509.authentication.handler.support.X509CredentialsAuthenticationHandler;
import org.apereo.cas.adaptors.x509.authentication.ldap.LdaptiveResourceCRLFetcher;
import org.apereo.cas.adaptors.x509.authentication.principal.X509SerialNumberPrincipalResolver;
import org.apereo.cas.adaptors.x509.authentication.principal.X509SubjectAlternativeNameUPNPrincipalResolver;
import org.apereo.cas.adaptors.x509.authentication.principal.X509SubjectDNPrincipalResolver;
import org.apereo.cas.adaptors.x509.authentication.principal.X509SubjectPrincipalResolver;
import org.apereo.cas.adaptors.x509.authentication.principal.X509SerialNumberAndIssuerDNPrincipalResolver;
import org.apereo.cas.adaptors.x509.authentication.revocation.checker.CRLDistributionPointRevocationChecker;
import org.apereo.cas.adaptors.x509.authentication.revocation.checker.NoOpRevocationChecker;
import org.apereo.cas.adaptors.x509.authentication.revocation.checker.ResourceCRLRevocationChecker;
import org.apereo.cas.adaptors.x509.authentication.revocation.checker.RevocationChecker;
import org.apereo.cas.adaptors.x509.authentication.revocation.policy.AllowRevocationPolicy;
import org.apereo.cas.adaptors.x509.authentication.revocation.policy.DenyRevocationPolicy;
import org.apereo.cas.adaptors.x509.authentication.revocation.policy.RevocationPolicy;
import org.apereo.cas.adaptors.x509.authentication.revocation.policy.ThresholdExpiredCRLRevocationPolicy;
import org.apereo.cas.authentication.AuthenticationEventExecutionPlan;
import org.apereo.cas.authentication.AuthenticationHandler;
import org.apereo.cas.authentication.principal.DefaultPrincipalFactory;
import org.apereo.cas.authentication.principal.PrincipalFactory;
import org.apereo.cas.authentication.principal.PrincipalResolver;
import org.apereo.cas.authentication.AuthenticationEventExecutionPlanConfigurer;
import org.apereo.cas.configuration.CasConfigurationProperties;
import org.apereo.cas.configuration.model.support.x509.X509Properties;
import org.apereo.cas.configuration.support.Beans;
import org.apereo.cas.services.ServicesManager;
import org.apereo.cas.util.RegexUtils;
import org.apereo.services.persondir.IPersonAttributeDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import net.sf.ehcache.Cache;
/**
* This is {@link X509AuthenticationConfiguration}.
*
* @author Misagh Moayyed
* @since 5.0.0
*/
@Configuration("x509AuthenticationConfiguration")
@EnableConfigurationProperties(CasConfigurationProperties.class)
public class X509AuthenticationConfiguration {
private static final int HEX = 16;
@Autowired
private ResourceLoader resourceLoader;
@Autowired
@Qualifier("attributeRepository")
private IPersonAttributeDao attributeRepository;
@Autowired
@Qualifier("servicesManager")
private ServicesManager servicesManager;
@Autowired
private CasConfigurationProperties casProperties;
@Bean
public RevocationPolicy allowRevocationPolicy() {
return new AllowRevocationPolicy();
}
@Bean
@RefreshScope
public RevocationPolicy thresholdExpiredCRLRevocationPolicy() {
return new ThresholdExpiredCRLRevocationPolicy(casProperties.getAuthn().getX509().getRevocationPolicyThreshold());
}
@Bean
public RevocationPolicy denyRevocationPolicy() {
return new DenyRevocationPolicy();
}
@Bean
public RevocationChecker crlDistributionPointRevocationChecker() {
final X509Properties x509 = casProperties.getAuthn().getX509();
final Cache cache = new Cache("CRL".concat(UUID.randomUUID().toString()),
x509.getCacheMaxElementsInMemory(),
x509.isCacheDiskOverflow(),
x509.isCacheEternal(),
x509.getCacheTimeToLiveSeconds(),
x509.getCacheTimeToIdleSeconds());
return new CRLDistributionPointRevocationChecker(
x509.isCheckAll(),
getRevocationPolicy(x509.getCrlUnavailablePolicy()),
getRevocationPolicy(x509.getCrlExpiredPolicy()),
cache,
crlFetcher(),
x509.isThrowOnFetchFailure());
}
@Bean
public RevocationChecker noOpRevocationChecker() {
return new NoOpRevocationChecker();
}
@Bean
public CRLFetcher resourceCrlFetcher() {
return new ResourceCRLFetcher();
}
@Bean
public RevocationChecker resourceCrlRevocationChecker() {
final X509Properties x509 = casProperties.getAuthn().getX509();
final Set<Resource> x509CrlResources = x509.getCrlResources()
.stream()
.map(s -> this.resourceLoader.getResource(s))
.collect(Collectors.toSet());
return new ResourceCRLRevocationChecker(
x509.isCheckAll(),
getRevocationPolicy(x509.getCrlResourceUnavailablePolicy()),
getRevocationPolicy(x509.getCrlResourceExpiredPolicy()),
x509.getRefreshIntervalSeconds(),
crlFetcher(),
x509CrlResources);
}
private RevocationPolicy getRevocationPolicy(final String policy) {
switch (policy.trim().toLowerCase()) {
case "allow":
return new AllowRevocationPolicy();
case "threshold":
return thresholdExpiredCRLRevocationPolicy();
case "deny":
default:
return new DenyRevocationPolicy();
}
}
@Bean
public CRLFetcher crlFetcher() {
final X509Properties x509 = casProperties.getAuthn().getX509();
switch (x509.getCrlFetcher().toLowerCase()) {
case "ldap":
return ldaptiveResourceCRLFetcher();
case "resource":
default:
return resourceCrlFetcher();
}
}
@Bean
@RefreshScope
public AuthenticationHandler x509CredentialsAuthenticationHandler() {
final X509Properties x509 = casProperties.getAuthn().getX509();
final RevocationChecker revChecker;
switch (x509.getRevocationChecker().trim().toLowerCase()) {
case "resource":
revChecker = resourceCrlRevocationChecker();
break;
case "crl":
revChecker = crlDistributionPointRevocationChecker();
break;
case "none":
default:
revChecker = noOpRevocationChecker();
break;
}
return new X509CredentialsAuthenticationHandler(
x509.getName(),
servicesManager,
x509PrincipalFactory(),
StringUtils.isNotBlank(x509.getRegExTrustedIssuerDnPattern())
? RegexUtils.createPattern(x509.getRegExTrustedIssuerDnPattern()) : null,
x509.getMaxPathLength(),
x509.isMaxPathLengthAllowUnspecified(),
x509.isCheckKeyUsage(),
x509.isRequireKeyUsage(),
StringUtils.isNotBlank(x509.getRegExSubjectDnPattern())
? RegexUtils.createPattern(x509.getRegExSubjectDnPattern()) : null,
revChecker);
}
@Bean
public CRLFetcher ldaptiveResourceCRLFetcher() {
final X509Properties x509 = casProperties.getAuthn().getX509();
return new LdaptiveResourceCRLFetcher(Beans.newLdaptiveConnectionConfig(x509.getLdap()),
Beans.newLdaptiveSearchExecutor(x509.getLdap().getBaseDn(), x509.getLdap().getSearchFilter()),
x509.getCertificateAttribute());
}
@Bean
@RefreshScope
public PrincipalResolver x509SubjectPrincipalResolver() {
final X509Properties x509 = casProperties.getAuthn().getX509();
final X509SubjectPrincipalResolver r = new X509SubjectPrincipalResolver(x509.getPrincipalDescriptor());
r.setAttributeRepository(attributeRepository);
r.setPrincipalAttributeName(x509.getPrincipal().getPrincipalAttribute());
r.setReturnNullIfNoAttributes(x509.getPrincipal().isReturnNull());
r.setPrincipalFactory(x509PrincipalFactory());
return r;
}
@Bean
@RefreshScope
public PrincipalResolver x509SubjectDNPrincipalResolver() {
final X509Properties x509 = casProperties.getAuthn().getX509();
final X509SubjectDNPrincipalResolver r = new X509SubjectDNPrincipalResolver();
r.setAttributeRepository(attributeRepository);
r.setPrincipalAttributeName(x509.getPrincipal().getPrincipalAttribute());
r.setReturnNullIfNoAttributes(x509.getPrincipal().isReturnNull());
r.setPrincipalFactory(x509PrincipalFactory());
return r;
}
@Bean
@RefreshScope
public PrincipalResolver x509SubjectAlternativeNameUPNPrincipalResolver() {
final X509Properties x509 = casProperties.getAuthn().getX509();
final X509SubjectAlternativeNameUPNPrincipalResolver r = new X509SubjectAlternativeNameUPNPrincipalResolver();
r.setAttributeRepository(attributeRepository);
r.setPrincipalAttributeName(x509.getPrincipal().getPrincipalAttribute());
r.setReturnNullIfNoAttributes(x509.getPrincipal().isReturnNull());
r.setPrincipalFactory(x509PrincipalFactory());
return r;
}
@Bean
@RefreshScope
public PrincipalResolver x509SerialNumberPrincipalResolver() {
final X509Properties x509 = casProperties.getAuthn().getX509();
final X509SerialNumberPrincipalResolver r;
final int radix = x509.getPrincipalSNRadix();
if (Character.MIN_RADIX <= radix && radix <= Character.MAX_RADIX) {
if (radix == HEX) {
r = new X509SerialNumberPrincipalResolver(radix, x509.isPrincipalHexSNZeroPadding());
} else {
r = new X509SerialNumberPrincipalResolver(radix, false);
}
} else {
r = new X509SerialNumberPrincipalResolver();
}
r.setAttributeRepository(attributeRepository);
r.setPrincipalAttributeName(x509.getPrincipal().getPrincipalAttribute());
r.setReturnNullIfNoAttributes(x509.getPrincipal().isReturnNull());
r.setPrincipalFactory(x509PrincipalFactory());
return r;
}
@ConditionalOnMissingBean(name = "x509PrincipalFactory")
@Bean
public PrincipalFactory x509PrincipalFactory() {
return new DefaultPrincipalFactory();
}
@Bean
@RefreshScope
public PrincipalResolver x509SerialNumberAndIssuerDNPrincipalResolver() {
final X509Properties x509 = casProperties.getAuthn().getX509();
return new X509SerialNumberAndIssuerDNPrincipalResolver(x509.getSerialNumberPrefix(), x509.getValueDelimiter());
}
/**
* The type X 509 authentication event execution plan configuration.
*/
@Configuration("x509AuthenticationEventExecutionPlanConfiguration")
public class X509AuthenticationEventExecutionPlanConfiguration implements AuthenticationEventExecutionPlanConfigurer {
@Autowired
@Qualifier("personDirectoryPrincipalResolver")
private PrincipalResolver personDirectoryPrincipalResolver;
@Override
public void configureAuthenticationExecutionPlan(final AuthenticationEventExecutionPlan plan) {
PrincipalResolver resolver = personDirectoryPrincipalResolver;
if (casProperties.getAuthn().getX509().getPrincipalType() != null) {
switch (casProperties.getAuthn().getX509().getPrincipalType()) {
case SERIAL_NO:
resolver = x509SerialNumberPrincipalResolver();
break;
case SERIAL_NO_DN:
resolver = x509SerialNumberAndIssuerDNPrincipalResolver();
break;
case SUBJECT:
resolver = x509SubjectPrincipalResolver();
break;
case SUBJECT_ALT_NAME:
resolver = x509SubjectAlternativeNameUPNPrincipalResolver();
break;
default:
resolver = x509SubjectDNPrincipalResolver();
break;
}
}
plan.registerAuthenticationHandlerWithPrincipalResolver(x509CredentialsAuthenticationHandler(), resolver);
}
}
}