package org.apereo.cas.web.flow; import org.apereo.cas.CasProtocolConstants; import org.apereo.cas.CentralAuthenticationService; import org.apereo.cas.authentication.Authentication; import org.apereo.cas.authentication.AuthenticationException; import org.apereo.cas.authentication.AuthenticationResult; import org.apereo.cas.authentication.AuthenticationResultBuilder; import org.apereo.cas.authentication.AuthenticationSystemSupport; import org.apereo.cas.authentication.Credential; import org.apereo.cas.authentication.principal.Service; import org.apereo.cas.services.RegisteredService; import org.apereo.cas.services.ServicesManager; import org.apereo.cas.ticket.AbstractTicketException; import org.apereo.cas.ticket.InvalidTicketException; import org.apereo.cas.ticket.ServiceTicket; import org.apereo.cas.ticket.registry.TicketRegistrySupport; import org.apereo.cas.web.support.WebUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.util.StringUtils; import org.springframework.webflow.action.AbstractAction; import org.springframework.webflow.action.EventFactorySupport; import org.springframework.webflow.core.collection.LocalAttributeMap; import org.springframework.webflow.execution.Event; import org.springframework.webflow.execution.RequestContext; /** * Action to generate a service ticket for a given Ticket Granting Ticket and * Service. * * @author Scott Battaglia * @since 3.0.0 */ public class GenerateServiceTicketAction extends AbstractAction { private static final Logger LOGGER = LoggerFactory.getLogger(GenerateServiceTicketAction.class); private final CentralAuthenticationService centralAuthenticationService; private final AuthenticationSystemSupport authenticationSystemSupport; private final TicketRegistrySupport ticketRegistrySupport; private final ServicesManager servicesManager; public GenerateServiceTicketAction(final AuthenticationSystemSupport authenticationSystemSupport, final CentralAuthenticationService authenticationService, final TicketRegistrySupport ticketRegistrySupport, final ServicesManager servicesManager) { this.authenticationSystemSupport = authenticationSystemSupport; this.centralAuthenticationService = authenticationService; this.ticketRegistrySupport = ticketRegistrySupport; this.servicesManager = servicesManager; } /** * {@inheritDoc} * <p> * In the initial primary authentication flow, credentials are cached and available. * Since they are authenticated as part of submission first, there is no need to doubly * authenticate and verify credentials. * <p> * In subsequent authentication flows where a TGT is available and only an ST needs to be * created, there are no cached copies of the credential, since we do have a TGT available. * So we will simply grab the available authentication and produce the final result based on that. */ @Override protected Event doExecute(final RequestContext context) { final Service service = WebUtils.getService(context); LOGGER.debug("Service asking for service ticket is [{}]", service); final String ticketGrantingTicket = WebUtils.getTicketGrantingTicketId(context); LOGGER.debug("Ticket-granting ticket found in the context is [{}]", ticketGrantingTicket); try { final Authentication authentication = this.ticketRegistrySupport.getAuthenticationFrom(ticketGrantingTicket); if (authentication == null) { throw new InvalidTicketException(new AuthenticationException("No authentication found for ticket " + ticketGrantingTicket), ticketGrantingTicket); } final RegisteredService registeredService = servicesManager.findServiceBy(service); LOGGER.debug("Registered service asking for service ticket is [{}]", registeredService); WebUtils.putRegisteredService(context, registeredService); WebUtils.putService(context, service); if (registeredService != null) { if (!StringUtils.isEmpty(registeredService.getAccessStrategy().getUnauthorizedRedirectUrl())) { LOGGER.debug("Registered service may redirect to [{}] for unauthorized access requests", registeredService.getAccessStrategy().getUnauthorizedRedirectUrl()); } WebUtils.putUnauthorizedRedirectUrlIntoFlowScope(context, registeredService.getAccessStrategy().getUnauthorizedRedirectUrl()); } if (WebUtils.getWarningCookie(context)) { LOGGER.debug("Warning cookie is present in the request context. Routing result to [{}] state", CasWebflowConstants.STATE_ID_WARN); return result(CasWebflowConstants.STATE_ID_WARN); } final Credential credential = WebUtils.getCredential(context); final AuthenticationResultBuilder builder = this.authenticationSystemSupport.establishAuthenticationContextFromInitial(authentication, credential); final AuthenticationResult authenticationResult = builder.build(service); LOGGER.debug("Built the final authentication result [{}] to grant service ticket to [{}]", authenticationResult, service); final ServiceTicket serviceTicketId = this.centralAuthenticationService.grantServiceTicket(ticketGrantingTicket, service, authenticationResult); WebUtils.putServiceTicketInRequestScope(context, serviceTicketId); LOGGER.debug("Granted service ticket [{}] and added it to the request scope", serviceTicketId); return success(); } catch (final AbstractTicketException e) { if (e instanceof InvalidTicketException) { LOGGER.debug("CAS has determined ticket-granting ticket [{}] is invalid and must be destroyed", ticketGrantingTicket); this.centralAuthenticationService.destroyTicketGrantingTicket(ticketGrantingTicket); } if (isGatewayPresent(context)) { LOGGER.debug("Request indicates that it is gateway. Routing result to [{}] state", CasWebflowConstants.STATE_ID_GATEWAY); return result(CasWebflowConstants.STATE_ID_GATEWAY); } LOGGER.warn("Could not grant service ticket [{}]. Routing to [{}]", e.getMessage(), CasWebflowConstants.TRANSITION_ID_AUTHENTICATION_FAILURE); return newEvent(CasWebflowConstants.TRANSITION_ID_AUTHENTICATION_FAILURE, e); } } /** * Checks if {@code gateway} is present in the request params. * * @param context the context * @return true, if gateway present */ protected boolean isGatewayPresent(final RequestContext context) { return StringUtils.hasText(context.getExternalContext() .getRequestParameterMap().get(CasProtocolConstants.PARAMETER_GATEWAY)); } /** * New event based on the id, which contains an error attribute referring to the exception occurred. * * @param id the id * @param error the error * @return the event */ private Event newEvent(final String id, final Exception error) { return new EventFactorySupport().event(this, id, new LocalAttributeMap<>("error", error)); } }