/* * See LICENSE for licensing and NOTICE for copyright. */ package net.shibboleth.idp.cas.flow; import javax.annotation.Nonnull; import net.shibboleth.idp.cas.protocol.ProtocolError; import net.shibboleth.idp.cas.protocol.TicketValidationResponse; import net.shibboleth.idp.cas.protocol.TicketValidationRequest; import net.shibboleth.idp.cas.ticket.ServiceTicket; import net.shibboleth.idp.cas.ticket.Ticket; import net.shibboleth.idp.cas.ticket.TicketContext; import net.shibboleth.idp.cas.ticket.TicketService; import net.shibboleth.idp.profile.AbstractProfileAction; import net.shibboleth.idp.profile.ActionSupport; import net.shibboleth.utilities.java.support.logic.Constraint; import org.opensaml.profile.context.ProfileRequestContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.webflow.execution.Event; import org.springframework.webflow.execution.RequestContext; /** * CAS protocol service ticket validation action emits one of the following events based on validation result: * * <ul> * <li>{@link Events#ServiceTicketValidated serviceTicketValidated}</li> * <li>{@link Events#ProxyTicketValidated proxyTicketValidated}</li> * <li>{@link ProtocolError#InvalidTicketFormat invalidTicketFormat}</li> * <li>{@link ProtocolError#ServiceMismatch serviceMismatch}</li> * <li>{@link ProtocolError#TicketExpired ticketExpired}</li> * <li>{@link ProtocolError#TicketRetrievalError ticketRetrievalError}</li> * </ul> * * <p> * In the success case a {@link net.shibboleth.idp.cas.protocol.TicketValidationResponse} message is created and stored * as request scope parameter under the key {@value FlowStateSupport#TICKET_VALIDATION_RESPONSE_KEY}. * * @author Marvin S. Addison */ public class ValidateTicketAction extends AbstractProfileAction<TicketValidationRequest, TicketValidationResponse> { /** Class logger. */ private final Logger log = LoggerFactory.getLogger(ValidateTicketAction.class); /** Manages CAS tickets. */ @Nonnull private final TicketService ticketService; /** * Creates a new instance. * * @param ticketService Ticket service component. */ public ValidateTicketAction(@Nonnull TicketService ticketService) { this.ticketService = Constraint.isNotNull(ticketService, "TicketService cannot be null"); } @Nonnull @Override protected Event doExecute( final @Nonnull RequestContext springRequestContext, final @Nonnull ProfileRequestContext profileRequestContext) { final TicketValidationRequest request = FlowStateSupport.getTicketValidationRequest(springRequestContext); if (request == null) { log.info("TicketValidationRequest not found in flow state."); return ProtocolError.IllegalState.event(this); } final Ticket ticket; try { final String ticketId = request.getTicket(); log.debug("Attempting to validate {}", ticketId); if (ticketId.startsWith("ST-")) { ticket = ticketService.removeServiceTicket(request.getTicket()); } else if (ticketId.startsWith("PT-")) { ticket = ticketService.removeProxyTicket(ticketId); } else { return ProtocolError.InvalidTicketFormat.event(this); } if (ticket != null) { log.debug("Found and removed {}/{} from ticket store", ticket, ticket.getSessionId()); } } catch (RuntimeException e) { log.debug("CAS ticket retrieval failed with error: {}", e); return ProtocolError.TicketRetrievalError.event(this); } if (ticket == null || ticket.getExpirationInstant().isBeforeNow()) { return ProtocolError.TicketExpired.event(this); } if (!ticket.getService().equalsIgnoreCase(request.getService())) { log.debug("Service issued for {} does not match {}", ticket.getService(), request.getService()); return ProtocolError.ServiceMismatch.event(this); } profileRequestContext.addSubcontext(new TicketContext(ticket)); FlowStateSupport.setTicketValidationResponse(springRequestContext, new TicketValidationResponse()); log.info("Successfully validated {} for {}", request.getTicket(), request.getService()); if (ticket instanceof ServiceTicket) { return Events.ServiceTicketValidated.event(this); } return Events.ProxyTicketValidated.event(this); } }