package org.apereo.cas.ticket.registry; import com.google.common.base.Throwables; import org.apereo.cas.authentication.CoreAuthenticationTestUtils; import org.apereo.cas.authentication.principal.DefaultPrincipalFactory; import org.apereo.cas.authentication.principal.Principal; import org.apereo.cas.config.CasCoreAuthenticationConfiguration; import org.apereo.cas.config.CasCoreAuthenticationHandlersConfiguration; import org.apereo.cas.config.CasCoreAuthenticationMetadataConfiguration; import org.apereo.cas.config.CasCoreAuthenticationPolicyConfiguration; import org.apereo.cas.config.CasCoreAuthenticationPrincipalConfiguration; import org.apereo.cas.config.CasCoreAuthenticationServiceSelectionStrategyConfiguration; import org.apereo.cas.config.CasCoreAuthenticationSupportConfiguration; import org.apereo.cas.config.CasCoreConfiguration; import org.apereo.cas.config.CasCoreHttpConfiguration; import org.apereo.cas.config.CasCoreServicesConfiguration; import org.apereo.cas.config.CasCoreTicketCatalogConfiguration; import org.apereo.cas.config.CasCoreTicketsConfiguration; import org.apereo.cas.config.CasPersonDirectoryConfiguration; import org.apereo.cas.config.JpaTicketRegistryConfiguration; import org.apereo.cas.config.JpaTicketRegistryTicketCatalogConfiguration; import org.apereo.cas.config.support.EnvironmentConversionServiceInitializer; import org.apereo.cas.logout.config.CasCoreLogoutConfiguration; import org.apereo.cas.mock.MockService; import org.apereo.cas.ticket.AbstractTicketException; import org.apereo.cas.ticket.ExpirationPolicy; import org.apereo.cas.ticket.ServiceTicket; import org.apereo.cas.ticket.Ticket; import org.apereo.cas.ticket.TicketGrantingTicket; import org.apereo.cas.ticket.TicketGrantingTicketImpl; import org.apereo.cas.ticket.UniqueTicketIdGenerator; import org.apereo.cas.ticket.proxy.ProxyGrantingTicket; import org.apereo.cas.ticket.proxy.ProxyTicket; import org.apereo.cas.ticket.support.HardTimeoutExpirationPolicy; import org.apereo.cas.ticket.support.MultiTimeUseOrTimeoutExpirationPolicy; import org.apereo.cas.util.DefaultUniqueTicketIdGenerator; import org.apereo.cas.util.SchedulingUtils; import org.junit.Test; import org.junit.runner.RunWith; 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.test.context.SpringBootTest; import org.springframework.boot.test.context.TestConfiguration; import org.springframework.cloud.autoconfigure.RefreshAutoConfiguration; import org.springframework.context.ApplicationContext; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.support.TransactionCallback; import org.springframework.transaction.support.TransactionTemplate; import javax.annotation.PostConstruct; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import static org.junit.Assert.*; /** * Unit test for {@link JpaTicketRegistry} class. * * @author Marvin S. Addison * @since 3.0.0 */ @RunWith(SpringRunner.class) @SpringBootTest(classes = { JpaTicketRegistryTests.JpaTestConfiguration.class, RefreshAutoConfiguration.class, CasCoreAuthenticationConfiguration.class, CasCoreAuthenticationPrincipalConfiguration.class, CasCoreAuthenticationPolicyConfiguration.class, CasCoreAuthenticationMetadataConfiguration.class, CasCoreAuthenticationSupportConfiguration.class, CasCoreAuthenticationHandlersConfiguration.class, CasCoreHttpConfiguration.class, CasCoreServicesConfiguration.class, CasPersonDirectoryConfiguration.class, CasCoreLogoutConfiguration.class, CasCoreConfiguration.class, CasCoreAuthenticationServiceSelectionStrategyConfiguration.class, CasCoreTicketsConfiguration.class, CasCoreTicketCatalogConfiguration.class, JpaTicketRegistryTicketCatalogConfiguration.class, JpaTicketRegistryConfiguration.class}) @ContextConfiguration(initializers = EnvironmentConversionServiceInitializer.class) public class JpaTicketRegistryTests { /** * Number of clients contending for operations in concurrent test. */ private static final int CONCURRENT_SIZE = 20; private static final UniqueTicketIdGenerator ID_GENERATOR = new DefaultUniqueTicketIdGenerator(64); private static final ExpirationPolicy EXP_POLICY_TGT = new HardTimeoutExpirationPolicy(1000); private static final ExpirationPolicy EXP_POLICY_ST = new MultiTimeUseOrTimeoutExpirationPolicy(1, 1000); private static final ExpirationPolicy EXP_POLICY_PGT = new HardTimeoutExpirationPolicy(2000); private static final ExpirationPolicy EXP_POLICY_PT = new MultiTimeUseOrTimeoutExpirationPolicy(1, 2000); private static final Logger LOGGER = LoggerFactory.getLogger(JpaTicketRegistryTests.class); @Autowired @Qualifier("ticketTransactionManager") private PlatformTransactionManager txManager; @Autowired @Qualifier("ticketRegistry") private TicketRegistry ticketRegistry; @TestConfiguration public static class JpaTestConfiguration { @Autowired protected ApplicationContext applicationContext; @PostConstruct public void init() { SchedulingUtils.prepScheduledAnnotationBeanPostProcessor(applicationContext); } } @Test public void verifyTicketDeletionInBulk() { final TicketGrantingTicket newTgt = newTGT(); addTicketInTransaction(newTgt); final TicketGrantingTicket tgtFromDb = (TicketGrantingTicket) getTicketInTransaction(newTgt.getId()); final ServiceTicket newSt = grantServiceTicketInTransaction(tgtFromDb); final ServiceTicket stFromDb = (ServiceTicket) getTicketInTransaction(newSt.getId()); final ProxyGrantingTicket newPgt = grantProxyGrantingTicketInTransaction(stFromDb); final ProxyGrantingTicket pgtFromDb = (ProxyGrantingTicket) getTicketInTransaction(newPgt.getId()); final ProxyTicket newPt = grantProxyTicketInTransaction(pgtFromDb); getTicketInTransaction(newPt.getId()); deleteTicketsInTransaction(); } @Test public void verifyTicketCreationAndDeletion() throws Exception { // TGT final TicketGrantingTicket newTgt = newTGT(); addTicketInTransaction(newTgt); TicketGrantingTicket tgtFromDb = (TicketGrantingTicket) getTicketInTransaction(newTgt.getId()); assertNotNull(tgtFromDb); assertEquals(newTgt.getId(), tgtFromDb.getId()); // ST final ServiceTicket newSt = grantServiceTicketInTransaction(tgtFromDb); final ServiceTicket stFromDb = (ServiceTicket) getTicketInTransaction(newSt.getId()); assertNotNull(stFromDb); assertEquals(newSt.getId(), stFromDb.getId()); // PGT final ProxyGrantingTicket newPgt = grantProxyGrantingTicketInTransaction(stFromDb); final ProxyGrantingTicket pgtFromDb = (ProxyGrantingTicket) getTicketInTransaction(newPgt.getId()); assertNotNull(pgtFromDb); assertEquals(newPgt.getId(), pgtFromDb.getId()); tgtFromDb = (TicketGrantingTicket) getTicketInTransaction(newTgt.getId()); assertNotNull(tgtFromDb); assertEquals(1, tgtFromDb.getProxyGrantingTickets().size()); // PT final ProxyTicket newPt = grantProxyTicketInTransaction(pgtFromDb); final ProxyTicket ptFromDb = (ProxyTicket) getTicketInTransaction(newPt.getId()); assertNotNull(ptFromDb); assertEquals(newPt.getId(), ptFromDb.getId()); // ST 2 final ServiceTicket newSt2 = grantServiceTicketInTransaction(tgtFromDb); final ServiceTicket st2FromDb = (ServiceTicket) getTicketInTransaction(newSt2.getId()); assertNotNull(st2FromDb); assertEquals(newSt2.getId(), st2FromDb.getId()); // PGT 2 final ProxyGrantingTicket newPgt2 = grantProxyGrantingTicketInTransaction(st2FromDb); final ProxyGrantingTicket pgt2FromDb = (ProxyGrantingTicket) getTicketInTransaction(newPgt2.getId()); assertNotNull(pgt2FromDb); assertEquals(newPgt2.getId(), pgt2FromDb.getId()); tgtFromDb = (TicketGrantingTicket) getTicketInTransaction(newTgt.getId()); assertNotNull(tgtFromDb); assertEquals(2, tgtFromDb.getProxyGrantingTickets().size()); // delete PGT 2 deleteTicketInTransaction(pgt2FromDb.getId()); assertNull(getTicketInTransaction(newPgt2.getId())); tgtFromDb = (TicketGrantingTicket) getTicketInTransaction(newTgt.getId()); assertNotNull(tgtFromDb); assertEquals(1, tgtFromDb.getProxyGrantingTickets().size()); // delete ticket hierarchy tgtFromDb = (TicketGrantingTicket) getTicketInTransaction(newTgt.getId()); assertNotNull(tgtFromDb); deleteTicketInTransaction(tgtFromDb.getId()); assertNull(getTicketInTransaction(newTgt.getId())); assertNull(getTicketInTransaction(newSt.getId())); assertNull(getTicketInTransaction(newPgt.getId())); assertNull(getTicketInTransaction(newPt.getId())); } @Test public void verifyConcurrentServiceTicketGeneration() throws Exception { final TicketGrantingTicket newTgt = newTGT(); addTicketInTransaction(newTgt); final ExecutorService executor = Executors.newFixedThreadPool(CONCURRENT_SIZE); try { final List<ServiceTicketGenerator> generators = new ArrayList<>(CONCURRENT_SIZE); for (int i = 0; i < CONCURRENT_SIZE; i++) { generators.add(new ServiceTicketGenerator(newTgt.getId(), this.ticketRegistry, this.txManager)); } final List<Future<String>> results = executor.invokeAll(generators); for (final Future<String> result : results) { assertNotNull(result.get()); } } catch (final Exception e) { LOGGER.error("testConcurrentServiceTicketGeneration produced an error", e); fail("testConcurrentServiceTicketGeneration failed."); } finally { executor.shutdownNow(); } assertEquals(CONCURRENT_SIZE, this.ticketRegistry.getTickets().size() - 1); } static TicketGrantingTicket newTGT() { final Principal principal = new DefaultPrincipalFactory().createPrincipal( "bob", Collections.singletonMap("displayName", "Bob")); return new TicketGrantingTicketImpl( ID_GENERATOR.getNewTicketId(TicketGrantingTicket.PREFIX), CoreAuthenticationTestUtils.getAuthentication(principal), EXP_POLICY_TGT); } static ServiceTicket newST(final TicketGrantingTicket parent) { return parent.grantServiceTicket( ID_GENERATOR.getNewTicketId(ServiceTicket.PREFIX), new MockService("https://service.example.com"), EXP_POLICY_ST, false, true); } static ProxyGrantingTicket newPGT(final ServiceTicket parent) { try { return parent.grantProxyGrantingTicket( ID_GENERATOR.getNewTicketId(ProxyGrantingTicket.PROXY_GRANTING_TICKET_PREFIX), CoreAuthenticationTestUtils.getAuthentication(), EXP_POLICY_PGT); } catch (final AbstractTicketException e) { throw Throwables.propagate(e); } } static ProxyTicket newPT(final ProxyGrantingTicket parent) { return parent.grantProxyTicket( ID_GENERATOR.getNewTicketId(ProxyTicket.PROXY_TICKET_PREFIX), new MockService("https://proxy-service.example.com"), EXP_POLICY_PT, false); } private void addTicketInTransaction(final Ticket ticket) { new TransactionTemplate(txManager).execute(status -> { ticketRegistry.addTicket(ticket); return null; }); } private void deleteTicketsInTransaction() { new TransactionTemplate(txManager).execute((TransactionCallback<Void>) status -> { ticketRegistry.deleteAll(); return null; }); } private void deleteTicketInTransaction(final String ticketId) { new TransactionTemplate(txManager).execute((TransactionCallback<Void>) status -> { ticketRegistry.deleteTicket(ticketId); return null; }); } private Ticket getTicketInTransaction(final String ticketId) { return new TransactionTemplate(txManager).execute(status -> ticketRegistry.getTicket(ticketId)); } private ServiceTicket grantServiceTicketInTransaction(final TicketGrantingTicket parent) { return new TransactionTemplate(txManager).execute(status -> { final ServiceTicket st = newST(parent); ticketRegistry.addTicket(st); return st; }); } private ProxyGrantingTicket grantProxyGrantingTicketInTransaction(final ServiceTicket parent) { return new TransactionTemplate(txManager).execute(status -> { final ProxyGrantingTicket pgt = newPGT(parent); ticketRegistry.addTicket(pgt); return pgt; }); } private ProxyTicket grantProxyTicketInTransaction(final ProxyGrantingTicket parent) { return new TransactionTemplate(txManager).execute(status -> { final ProxyTicket st = newPT(parent); ticketRegistry.addTicket(st); return st; }); } private static class ServiceTicketGenerator implements Callable<String> { private final PlatformTransactionManager txManager; private final String parentTgtId; private final TicketRegistry jpaTicketRegistry; ServiceTicketGenerator(final String tgtId, final TicketRegistry jpaTicketRegistry, final PlatformTransactionManager txManager) { parentTgtId = tgtId; this.jpaTicketRegistry = jpaTicketRegistry; this.txManager = txManager; } @Override public String call() throws Exception { return new TransactionTemplate(txManager).execute(status -> { final ServiceTicket st = newST((TicketGrantingTicket) jpaTicketRegistry.getTicket(parentTgtId)); jpaTicketRegistry.addTicket(st); return st.getId(); }); } } }