/* * See LICENSE for licensing and NOTICE for copyright. */ package net.shibboleth.idp.cas.ticket; import java.io.IOException; import java.util.HashMap; import java.util.Map; import javax.annotation.Nonnull; import javax.annotation.Nullable; import net.shibboleth.idp.cas.config.ProxyGrantingTicketConfiguration; import net.shibboleth.idp.cas.config.ProxyTicketConfiguration; import net.shibboleth.idp.cas.config.ServiceTicketConfiguration; import net.shibboleth.idp.cas.ticket.serialization.ProxyGrantingTicketSerializer; import net.shibboleth.idp.cas.ticket.serialization.ProxyTicketSerializer; import net.shibboleth.idp.cas.ticket.serialization.ServiceTicketSerializer; import net.shibboleth.utilities.java.support.annotation.Duration; import net.shibboleth.utilities.java.support.annotation.constraint.Positive; import net.shibboleth.utilities.java.support.logic.Constraint; import org.joda.time.DateTime; import org.opensaml.storage.StorageRecord; import org.opensaml.storage.StorageSerializer; import org.opensaml.storage.StorageService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Simple ticket management service that generates tickets using a {@link TicketIdGenerator} component and stores * tickets in a {@link org.opensaml.storage.StorageService}. * * @author Marvin S. Addison */ public class SimpleTicketService implements TicketService { /** Map of ticket classes to context names. */ private static final Map<Class<? extends Ticket>, String> CONTEXT_CLASS_MAP = new HashMap<>(); /** Map of ticket classes to serializers. */ private static final Map<Class<? extends Ticket>, StorageSerializer<? extends Ticket>> SERIALIZER_MAP = new HashMap<>(); private static final ServiceTicketSerializer ST_SERIALIZER = new ServiceTicketSerializer(); private static final ProxyTicketSerializer PT_SERIALIZER = new ProxyTicketSerializer(); private static final ProxyGrantingTicketSerializer PGT_SERIALIZER = new ProxyGrantingTicketSerializer(); /** Class logger. */ private final Logger log = LoggerFactory.getLogger(SimpleTicketService.class); /** Storage service to which ticket persistence operations are delegated. */ @Nonnull private final StorageService storageService; @Nonnull private final ServiceTicketConfiguration serviceTicketConfiguration; @Nonnull private final ProxyGrantingTicketConfiguration proxyGrantingTicketConfiguration; @Nonnull private final ProxyTicketConfiguration proxyTicketConfiguration; static { CONTEXT_CLASS_MAP.put(ServiceTicket.class, ServiceTicketConfiguration.PROFILE_ID); CONTEXT_CLASS_MAP.put(ProxyTicket.class, ProxyTicketConfiguration.PROFILE_ID); CONTEXT_CLASS_MAP.put(ProxyGrantingTicket.class, ProxyGrantingTicketConfiguration.PROFILE_ID); SERIALIZER_MAP.put(ServiceTicket.class, ST_SERIALIZER); SERIALIZER_MAP.put(ProxyTicket.class, PT_SERIALIZER); SERIALIZER_MAP.put(ProxyGrantingTicket.class, PGT_SERIALIZER); } public SimpleTicketService( @Nonnull final StorageService storageService, @Nonnull final ServiceTicketConfiguration serviceTicketConfiguration, @Nonnull final ProxyGrantingTicketConfiguration proxyGrantingTicketConfiguration, @Nonnull final ProxyTicketConfiguration proxyTicketConfiguration) { this.storageService = Constraint.isNotNull(storageService, "StorageService cannot be null."); this.serviceTicketConfiguration = Constraint.isNotNull( serviceTicketConfiguration, "ServiceTicketConfiguration cannot be null."); this.proxyGrantingTicketConfiguration = Constraint.isNotNull( proxyGrantingTicketConfiguration, "ProxyGrantingTicketConfiguration cannot be null."); this.proxyTicketConfiguration = Constraint.isNotNull( proxyTicketConfiguration, "ProxyTicketConfiguration cannot be null."); } @Override @Nonnull public ServiceTicket createServiceTicket( @Nonnull final String sessionId, @Nonnull final String service, final boolean renew) { Constraint.isNotNull(sessionId, "Session ID cannot be null"); Constraint.isNotNull(service, "Service cannot be null"); final ServiceTicket st = new ServiceTicket( serviceTicketConfiguration.getSecurityConfiguration().getIdGenerator().generateIdentifier(), sessionId, service, DateTime.now().plus(serviceTicketConfiguration.getTicketValidityPeriod()).toInstant(), renew); log.debug("Generated ticket {}", st); store(st); return st; } @Override @Nullable public ServiceTicket removeServiceTicket(@Nonnull final String id) { Constraint.isNotNull(id, "Id cannot be null"); return delete(id, ServiceTicket.class); } @Override @Nonnull public ProxyGrantingTicket createProxyGrantingTicket( @Nonnull final ServiceTicket serviceTicket, @Nonnull final String pgtId) { Constraint.isNotNull(serviceTicket, "ServiceTicket cannot be null"); Constraint.isNotNull(pgtId, "PGT ID cannot be null"); final ProxyGrantingTicket pgt = new ProxyGrantingTicket( pgtId, serviceTicket.getSessionId(), serviceTicket.getService(), DateTime.now().plus(proxyGrantingTicketConfiguration.getTicketValidityPeriod()).toInstant(), null); log.debug("Generated ticket {}", pgt); store(pgt); return pgt; } @Nonnull @Override public ProxyGrantingTicket createProxyGrantingTicket( @Nonnull final ProxyTicket proxyTicket, @Nonnull final String pgtId) { Constraint.isNotNull(proxyTicket, "ProxyTicket cannot be null"); Constraint.isNotNull(pgtId, "PGT ID cannot be null"); final ProxyGrantingTicket pgt = new ProxyGrantingTicket( pgtId, proxyTicket.getSessionId(), proxyTicket.getService(), DateTime.now().plus(proxyGrantingTicketConfiguration.getTicketValidityPeriod()).toInstant(), proxyTicket.getPgtId()); log.debug("Generated ticket {}", pgt); store(pgt); return pgt; } @Override @Nullable public ProxyGrantingTicket fetchProxyGrantingTicket(@Nonnull final String id) { Constraint.isNotNull(id, "Id cannot be null"); return read(id, ProxyGrantingTicket.class); } @Override @Nullable public ProxyGrantingTicket removeProxyGrantingTicket(@Nonnull final String id) { Constraint.isNotNull(id, "Id cannot be null"); final ProxyGrantingTicket pgt = delete(id, ProxyGrantingTicket.class); return pgt; } @Nonnull @Override public ProxyTicket createProxyTicket( @Nonnull final ProxyGrantingTicket pgt, @Nonnull final String service) { Constraint.isNotNull(pgt, "ProxyGrantingTicket cannot be null"); Constraint.isNotNull(service, "Service cannot be null"); final ProxyTicket pt = new ProxyTicket( proxyTicketConfiguration.getSecurityConfiguration().getIdGenerator().generateIdentifier(), pgt.getSessionId(), service, DateTime.now().plus(proxyTicketConfiguration.getTicketValidityPeriod()).toInstant(), pgt.getId()); store(pt); return pt; } @Nullable @Override public ProxyTicket removeProxyTicket(final @Nonnull String id) { return delete(id, ProxyTicket.class); } private <T extends Ticket> void store(final T ticket) { log.debug("Storing {}", ticket); try { if (!storageService.create( context(ticket.getClass()), ticket.getId(), ticket, serializer(ticket.getClass()), ticket.getExpirationInstant().getMillis())) { throw new RuntimeException("Failed to store ticket " + ticket); } } catch (IOException e) { throw new RuntimeException("Failed to store ticket " + ticket, e); } } private <T extends Ticket> T delete(final String id, final Class<T> clazz) { log.debug("Deleting {}", id); final T ticket = read(id, clazz); if (ticket == null) { return null; } try { log.debug("Attempting to delete " + ticket); if (this.storageService.delete(context(clazz), id)) { log.debug("Deleted ticket {}", id); } else { log.info("Failed deleting {}. Ticket probably expired from storage facility.", id); } } catch (IOException e) { throw new RuntimeException("Error deleting ticket " + id, e); } return ticket; } private <T extends Ticket> T read(final String id, final Class<T> clazz) { log.debug("Reading {}", id); final T ticket; try { final String context = context(clazz); final StorageRecord<T> record = storageService.read(context, id); if (record == null) { log.debug("{} not found", id); return null; } ticket = record.getValue(serializer(clazz), context, id); } catch (IOException e) { throw new RuntimeException("Error reading ticket."); } return ticket; } private static String context(final Class<? extends Ticket> clazz) { return CONTEXT_CLASS_MAP.get(clazz); } private static <T extends Ticket> StorageSerializer<T> serializer(final Class<T> clazz) { return (StorageSerializer<T>) SERIALIZER_MAP.get(clazz); } }