package org.apereo.cas.config; import org.apache.commons.lang3.StringUtils; import org.apereo.cas.CipherExecutor; import org.apereo.cas.authentication.PseudoPlatformTransactionManager; import org.apereo.cas.configuration.CasConfigurationProperties; import org.apereo.cas.configuration.model.core.ticket.TicketGrantingTicketProperties; import org.apereo.cas.configuration.model.core.ticket.registry.TicketRegistryProperties; import org.apereo.cas.configuration.support.Beans; import org.apereo.cas.logout.LogoutManager; import org.apereo.cas.ticket.DefaultTicketCatalog; import org.apereo.cas.ticket.ExpirationPolicy; import org.apereo.cas.ticket.ServiceTicketFactory; import org.apereo.cas.ticket.TicketCatalog; import org.apereo.cas.ticket.TicketCatalogConfigurer; import org.apereo.cas.ticket.TicketFactory; import org.apereo.cas.ticket.TicketGrantingTicketFactory; import org.apereo.cas.ticket.UniqueTicketIdGenerator; import org.apereo.cas.ticket.factory.DefaultProxyGrantingTicketFactory; import org.apereo.cas.ticket.factory.DefaultProxyTicketFactory; import org.apereo.cas.ticket.factory.DefaultServiceTicketFactory; import org.apereo.cas.ticket.factory.DefaultTicketFactory; import org.apereo.cas.ticket.factory.DefaultTicketGrantingTicketFactory; import org.apereo.cas.ticket.proxy.ProxyGrantingTicketFactory; import org.apereo.cas.ticket.proxy.ProxyHandler; import org.apereo.cas.ticket.proxy.ProxyTicketFactory; import org.apereo.cas.ticket.proxy.support.Cas10ProxyHandler; import org.apereo.cas.ticket.proxy.support.Cas20ProxyHandler; import org.apereo.cas.ticket.registry.DefaultTicketRegistry; import org.apereo.cas.ticket.registry.DefaultTicketRegistrySupport; import org.apereo.cas.ticket.registry.NoOpLockingStrategy; import org.apereo.cas.ticket.registry.TicketRegistry; import org.apereo.cas.ticket.registry.TicketRegistrySupport; import org.apereo.cas.ticket.registry.support.LockingStrategy; import org.apereo.cas.ticket.support.AlwaysExpiresExpirationPolicy; import org.apereo.cas.ticket.support.HardTimeoutExpirationPolicy; import org.apereo.cas.ticket.support.MultiTimeUseOrTimeoutExpirationPolicy; import org.apereo.cas.ticket.support.NeverExpiresExpirationPolicy; import org.apereo.cas.ticket.support.RememberMeDelegatingExpirationPolicy; import org.apereo.cas.ticket.support.ThrottledUseAndTimeoutExpirationPolicy; import org.apereo.cas.ticket.support.TicketGrantingTicketExpirationPolicy; import org.apereo.cas.ticket.support.TimeoutExpirationPolicy; import org.apereo.cas.util.HostNameBasedUniqueTicketIdGenerator; import org.apereo.cas.util.cipher.NoOpCipherExecutor; import org.apereo.cas.util.cipher.ProtocolTicketCipherExecutor; import org.apereo.cas.util.http.HttpClient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.AutoConfigureAfter; 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.context.annotation.Lazy; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.annotation.EnableTransactionManagement; import org.springframework.transaction.annotation.TransactionManagementConfigurer; import java.util.List; import java.util.Map; /** * This is {@link CasCoreTicketsConfiguration}. * * @author Misagh Moayyed * @since 5.0.0 */ @Configuration("casCoreTicketsConfiguration") @EnableConfigurationProperties(CasConfigurationProperties.class) @EnableScheduling @EnableAsync @EnableTransactionManagement(proxyTargetClass = true) @AutoConfigureAfter(value = {CasCoreUtilConfiguration.class, CasCoreTicketIdGeneratorsConfiguration.class}) public class CasCoreTicketsConfiguration implements TransactionManagementConfigurer { private static final Logger LOGGER = LoggerFactory.getLogger(CasCoreTicketsConfiguration.class); @Autowired private CasConfigurationProperties casProperties; @Lazy @Autowired @Qualifier("uniqueIdGeneratorsMap") private Map<String, UniqueTicketIdGenerator> uniqueIdGeneratorsMap; @Autowired @Qualifier("logoutManager") private LogoutManager logoutManager; @Autowired @Qualifier("ticketRegistry") private TicketRegistry ticketRegistry; @Autowired @Qualifier("supportsTrustStoreSslSocketFactoryHttpClient") private HttpClient httpClient; @ConditionalOnMissingBean(name = "defaultProxyGrantingTicketFactory") @Bean public ProxyGrantingTicketFactory defaultProxyGrantingTicketFactory() { return new DefaultProxyGrantingTicketFactory( ticketGrantingTicketUniqueIdGenerator(), grantingTicketExpirationPolicy(), protocolTicketCipherExecutor()); } @ConditionalOnMissingBean(name = "defaultProxyTicketFactory") @RefreshScope @Bean @Lazy public ProxyTicketFactory defaultProxyTicketFactory() { final boolean onlyTrackMostRecentSession = casProperties.getTicket().getTgt().isOnlyTrackMostRecentSession(); return new DefaultProxyTicketFactory(proxyTicketExpirationPolicy(), uniqueIdGeneratorsMap, protocolTicketCipherExecutor(), onlyTrackMostRecentSession); } @ConditionalOnMissingBean(name = "ticketGrantingTicketUniqueIdGenerator") @Bean @RefreshScope public UniqueTicketIdGenerator ticketGrantingTicketUniqueIdGenerator() { return new HostNameBasedUniqueTicketIdGenerator.TicketGrantingTicketIdGenerator( casProperties.getTicket().getTgt().getMaxLength(), casProperties.getHost().getName()); } @ConditionalOnMissingBean(name = "proxy20TicketUniqueIdGenerator") @Bean public UniqueTicketIdGenerator proxy20TicketUniqueIdGenerator() { return new HostNameBasedUniqueTicketIdGenerator.ProxyTicketIdGenerator( casProperties.getTicket().getPgt().getMaxLength(), casProperties.getHost().getName()); } @ConditionalOnMissingBean(name = "defaultServiceTicketFactory") @Bean @Lazy public ServiceTicketFactory defaultServiceTicketFactory() { final boolean onlyTrackMostRecentSession = casProperties.getTicket().getTgt().isOnlyTrackMostRecentSession(); return new DefaultServiceTicketFactory(serviceTicketExpirationPolicy(), uniqueIdGeneratorsMap, onlyTrackMostRecentSession, protocolTicketCipherExecutor()); } @ConditionalOnMissingBean(name = "defaultTicketGrantingTicketFactory") @Bean public TicketGrantingTicketFactory defaultTicketGrantingTicketFactory() { return new DefaultTicketGrantingTicketFactory(ticketGrantingTicketUniqueIdGenerator(), grantingTicketExpirationPolicy(), protocolTicketCipherExecutor()); } @ConditionalOnMissingBean(name = "defaultTicketFactory") @Bean public TicketFactory defaultTicketFactory() { return new DefaultTicketFactory(defaultProxyGrantingTicketFactory(), defaultTicketGrantingTicketFactory(), defaultServiceTicketFactory(), defaultProxyTicketFactory()); } @ConditionalOnMissingBean(name = "proxy10Handler") @Bean public ProxyHandler proxy10Handler() { return new Cas10ProxyHandler(); } @ConditionalOnMissingBean(name = "proxy20Handler") @Bean public ProxyHandler proxy20Handler() { return new Cas20ProxyHandler(httpClient, proxy20TicketUniqueIdGenerator()); } @ConditionalOnMissingBean(name = "ticketRegistry") @Bean public TicketRegistry ticketRegistry() { LOGGER.warn("Runtime memory is used as the persistence storage for retrieving and managing tickets. " + "Tickets that are issued during runtime will be LOST upon container restarts. This MAY impact SSO functionality."); final TicketRegistryProperties.InMemory mem = casProperties.getTicket().getRegistry().getInMemory(); return new DefaultTicketRegistry( mem.getInitialCapacity(), mem.getLoadFactor(), mem.getConcurrency(), Beans.newTicketRegistryCipherExecutor(mem.getCrypto())); } @ConditionalOnMissingBean(name = "defaultTicketRegistrySupport") @Bean public TicketRegistrySupport defaultTicketRegistrySupport() { return new DefaultTicketRegistrySupport(ticketRegistry); } @ConditionalOnMissingBean(name = "grantingTicketExpirationPolicy") @Bean public ExpirationPolicy grantingTicketExpirationPolicy() { final TicketGrantingTicketProperties tgt = casProperties.getTicket().getTgt(); if (tgt.getRememberMe().isEnabled()) { final RememberMeDelegatingExpirationPolicy p = new RememberMeDelegatingExpirationPolicy(); p.setRememberMeExpirationPolicy(new HardTimeoutExpirationPolicy(tgt.getRememberMe().getTimeToKillInSeconds())); p.setSessionExpirationPolicy(buildTicketGrantingTicketExpirationPolicy()); return p; } return buildTicketGrantingTicketExpirationPolicy(); } @ConditionalOnMissingBean(name = "serviceTicketExpirationPolicy") @Bean @RefreshScope public ExpirationPolicy serviceTicketExpirationPolicy() { return new MultiTimeUseOrTimeoutExpirationPolicy.ServiceTicketExpirationPolicy( casProperties.getTicket().getSt().getNumberOfUses(), casProperties.getTicket().getSt().getTimeToKillInSeconds()); } @ConditionalOnMissingBean(name = "proxyTicketExpirationPolicy") @Bean public ExpirationPolicy proxyTicketExpirationPolicy() { return new MultiTimeUseOrTimeoutExpirationPolicy.ProxyTicketExpirationPolicy( casProperties.getTicket().getPt().getNumberOfUses(), casProperties.getTicket().getPt().getTimeToKillInSeconds()); } @ConditionalOnMissingBean(name = "lockingStrategy") @Bean public LockingStrategy lockingStrategy() { return new NoOpLockingStrategy(); } @ConditionalOnMissingBean(name = "ticketTransactionManager") @Bean public PlatformTransactionManager ticketTransactionManager() { return new PseudoPlatformTransactionManager(); } @RefreshScope @Bean @ConditionalOnMissingBean(name = "protocolTicketCipherExecutor") public CipherExecutor protocolTicketCipherExecutor() { if (casProperties.getTicket().getSecurity().isCipherEnabled()) { return new ProtocolTicketCipherExecutor( casProperties.getTicket().getSecurity().getEncryptionKey(), casProperties.getTicket().getSecurity().getSigningKey()); } LOGGER.debug("Protocol tickets generated by CAS are not signed/encrypted."); return NoOpCipherExecutor.getInstance(); } private ExpirationPolicy buildTicketGrantingTicketExpirationPolicy() { final TicketGrantingTicketProperties tgt = casProperties.getTicket().getTgt(); if (tgt.getMaxTimeToLiveInSeconds() <= 0 && tgt.getTimeToKillInSeconds() <= 0) { LOGGER.warn("Ticket-granting ticket expiration policy is set to NEVER expire tickets."); return new NeverExpiresExpirationPolicy(); } if (tgt.getTimeout().getMaxTimeToLiveInSeconds() > 0) { LOGGER.debug("Ticket-granting ticket expiration policy is based on a timeout of [{}] seconds", tgt.getTimeout().getMaxTimeToLiveInSeconds()); return new TimeoutExpirationPolicy(tgt.getTimeout().getMaxTimeToLiveInSeconds()); } if (tgt.getMaxTimeToLiveInSeconds() > 0 && tgt.getTimeToKillInSeconds() > 0) { LOGGER.debug("Ticket-granting ticket expiration policy is based on hard/idle timeouts of [{}]/[{}] seconds", tgt.getMaxTimeToLiveInSeconds(), tgt.getTimeToKillInSeconds()); return new TicketGrantingTicketExpirationPolicy(tgt.getMaxTimeToLiveInSeconds(), tgt.getTimeToKillInSeconds()); } if (tgt.getThrottledTimeout().getTimeInBetweenUsesInSeconds() > 0 && tgt.getThrottledTimeout().getTimeToKillInSeconds() > 0) { final ThrottledUseAndTimeoutExpirationPolicy p = new ThrottledUseAndTimeoutExpirationPolicy(); p.setTimeToKillInSeconds(tgt.getThrottledTimeout().getTimeToKillInSeconds()); p.setTimeInBetweenUsesInSeconds(tgt.getThrottledTimeout().getTimeInBetweenUsesInSeconds()); LOGGER.debug("Ticket-granting ticket expiration policy is based on throttled timeouts"); return p; } if (tgt.getHardTimeout().getTimeToKillInSeconds() > 0) { LOGGER.debug("Ticket-granting ticket expiration policy is based on a hard timeout of [{}] seconds", tgt.getHardTimeout().getTimeToKillInSeconds()); return new HardTimeoutExpirationPolicy(tgt.getHardTimeout().getTimeToKillInSeconds()); } LOGGER.warn("Ticket-granting ticket expiration policy is set to ALWAYS expire tickets."); return new AlwaysExpiresExpirationPolicy(); } @Override public PlatformTransactionManager annotationDrivenTransactionManager() { return ticketTransactionManager(); } @ConditionalOnMissingBean(name = "ticketCatalog") @Autowired @Bean public TicketCatalog ticketCatalog(final List<TicketCatalogConfigurer> configurers) { final DefaultTicketCatalog plan = new DefaultTicketCatalog(); configurers.forEach(c -> { final String name = StringUtils.removePattern(c.getClass().getSimpleName(), "\\$.+"); LOGGER.debug("Configuring ticket metadata registration plan [{}]", name); c.configureTicketCatalog(plan); }); return plan; } }