package net.unicon.cas.mfa; import com.codahale.metrics.annotation.Counted; import com.codahale.metrics.annotation.Metered; import com.codahale.metrics.annotation.Timed; import com.google.common.base.Predicate; import net.unicon.cas.mfa.authentication.principal.MultiFactorCredentials; import org.jasig.cas.CentralAuthenticationService; import org.jasig.cas.authentication.Authentication; import org.jasig.cas.authentication.AuthenticationManager; import org.jasig.cas.authentication.Credential; import org.jasig.cas.authentication.principal.Service; import org.jasig.cas.logout.LogoutRequest; import org.jasig.cas.ticket.ExpirationPolicy; import org.jasig.cas.ticket.InvalidTicketException; import org.jasig.cas.ticket.ServiceTicket; import org.jasig.cas.ticket.Ticket; import org.jasig.cas.ticket.TicketCreationException; import org.jasig.cas.ticket.TicketException; import org.jasig.cas.ticket.TicketGrantingTicket; import org.jasig.cas.ticket.TicketGrantingTicketImpl; import org.jasig.cas.ticket.registry.TicketRegistry; import org.jasig.cas.util.UniqueTicketIdGenerator; import org.jasig.cas.validation.Assertion; import org.jasig.inspektr.audit.annotation.Audit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.validation.constraints.NotNull; import java.util.Collection; import java.util.List; /** * An extension of {@link CentralAuthenticationService} that routes CAS requests to a delegate, * thereby allowing the ability to partially override behavior that is MFA specific. * The current implementation is specific on the operation of creating ticket creating tickets * based on the assumptions that the credentials received are of type {@link MultiFactorCredentials} * and that the authentication context is verified and readily available, based on which the TGT will * be added to the configured {@link TicketRegistry}. * * This implementation here is merely responsible for creating the ticket granting ticket * and again, assumes that the authentication context has been established by all other * authentication managers in the flow. The authentication context is carried within the * {@link MultiFactorCredentials} instance. * * Assumptions: the TicketRegistry wired into this CentralAuthenticationService instance is the same as that wired * into the wrapped delegate. * (That way when this implementation adds tickets directly to the registry in createTGT and delegateTGT * those tickets will be available to the delegate in its fulfilling such methods as grantServiceTicket.) * * * @author Misagh Moayyed */ public final class MultiFactorAwareCentralAuthenticationService implements CentralAuthenticationService { /** Log instance for logging events, info, warnings, errors, etc. */ private final Logger logger = LoggerFactory.getLogger(this.getClass()); /** The wrapped CentralAuthenticationService. */ private CentralAuthenticationService delegate; private UniqueTicketIdGenerator ticketGrantingTicketUniqueTicketIdGenerator; private TicketRegistry ticketRegistry; private ExpirationPolicy ticketGrantingTicketExpirationPolicy; private AuthenticationManager authenticationManager; /** New Ticket Registry for storing and retrieving services tickets. Can point to the same one as the ticketRegistry variable. */ @NotNull private TicketRegistry serviceTicketRegistry; @Override @Audit( action="TICKET_GRANTING_TICKET", actionResolverName="CREATE_TICKET_GRANTING_TICKET_RESOLVER", resourceResolverName="CREATE_TICKET_GRANTING_TICKET_RESOURCE_RESOLVER") @Timed(name = "CREATE_TICKET_GRANTING_TICKET_TIMER") @Metered(name = "CREATE_TICKET_GRANTING_TICKET_METER") @Counted(name="CREATE_TICKET_GRANTING_TICKET_COUNTER", monotonic=true) public TicketGrantingTicket createTicketGrantingTicket(final Credential... credentials) throws TicketException { final MultiFactorCredentials mfaCredentials = (MultiFactorCredentials) credentials[0]; final Authentication authentication = mfaCredentials.getAuthentication(); if (authentication == null) { throw new TicketCreationException(new RuntimeException("Authentication cannot be null")); } final TicketGrantingTicket ticketGrantingTicket = new TicketGrantingTicketImpl( this.ticketGrantingTicketUniqueTicketIdGenerator.getNewTicketId(TicketGrantingTicket.PREFIX), authentication, this.ticketGrantingTicketExpirationPolicy); this.ticketRegistry.addTicket(ticketGrantingTicket); return ticketGrantingTicket; } @Timed(name = "GET_TICKET_TIMER") @Metered(name = "GET_TICKET_METER") @Counted(name="GET_TICKET_COUNTER", monotonic=true) @Override public <T extends Ticket> T getTicket(final String ticketId, final Class<? extends Ticket> clazz) throws InvalidTicketException { return delegate.getTicket(ticketId, clazz); } @Timed(name = "GET_TICKETS_TIMER") @Metered(name = "GET_TICKETS_METER") @Counted(name="GET_TICKETS_COUNTER", monotonic=true) @Override public Collection<Ticket> getTickets(final Predicate predicate) { return this.delegate.getTickets(predicate); } @Audit( action="SERVICE_TICKET", actionResolverName="GRANT_SERVICE_TICKET_RESOLVER", resourceResolverName="GRANT_SERVICE_TICKET_RESOURCE_RESOLVER") @Timed(name = "GRANT_SERVICE_TICKET_TIMER") @Metered(name="GRANT_SERVICE_TICKET_METER") @Counted(name="GRANT_SERVICE_TICKET_COUNTER", monotonic=true) @Override public ServiceTicket grantServiceTicket(final String ticketGrantingTicketId, final Service service) throws TicketException { return this.delegate.grantServiceTicket(ticketGrantingTicketId, service); } @Audit( action="SERVICE_TICKET", actionResolverName="GRANT_SERVICE_TICKET_RESOLVER", resourceResolverName="GRANT_SERVICE_TICKET_RESOURCE_RESOLVER") @Timed(name="GRANT_SERVICE_TICKET_TIMER") @Metered(name="GRANT_SERVICE_TICKET_METER") @Counted(name="GRANT_SERVICE_TICKET_COUNTER", monotonic=true) @Override public ServiceTicket grantServiceTicket( final String ticketGrantingTicketId, final Service service, final Credential... credentials) throws org.jasig.cas.authentication.AuthenticationException, TicketException { return this.delegate.grantServiceTicket(ticketGrantingTicketId, service, credentials); } @Audit( action="SERVICE_TICKET_VALIDATE", actionResolverName="VALIDATE_SERVICE_TICKET_RESOLVER", resourceResolverName="VALIDATE_SERVICE_TICKET_RESOURCE_RESOLVER") @Timed(name="VALIDATE_SERVICE_TICKET_TIMER") @Metered(name="VALIDATE_SERVICE_TICKET_METER") @Counted(name="VALIDATE_SERVICE_TICKET_COUNTER", monotonic=true) @Override public Assertion validateServiceTicket(final String serviceTicketId, final Service service) throws TicketException { return this.delegate.validateServiceTicket(serviceTicketId, service); } @Audit( action="TICKET_GRANTING_TICKET_DESTROYED", actionResolverName="DESTROY_TICKET_GRANTING_TICKET_RESOLVER", resourceResolverName="DESTROY_TICKET_GRANTING_TICKET_RESOURCE_RESOLVER") @Timed(name = "DESTROY_TICKET_GRANTING_TICKET_TIMER") @Metered(name="DESTROY_TICKET_GRANTING_TICKET_METER") @Counted(name="DESTROY_TICKET_GRANTING_TICKET_COUNTER", monotonic=true) @Override public List<LogoutRequest> destroyTicketGrantingTicket(@NotNull final String ticketGrantingTicketId) { return this.delegate.destroyTicketGrantingTicket(ticketGrantingTicketId); } @Audit( action="PROXY_GRANTING_TICKET", actionResolverName="GRANT_PROXY_GRANTING_TICKET_RESOLVER", resourceResolverName="GRANT_PROXY_GRANTING_TICKET_RESOURCE_RESOLVER") @Timed(name="GRANT_PROXY_GRANTING_TICKET_TIMER") @Metered(name="GRANT_PROXY_GRANTING_TICKET_METER") @Counted(name="GRANT_PROXY_GRANTING_TICKET_COUNTER", monotonic=true) @Override public TicketGrantingTicket delegateTicketGrantingTicket(final String serviceTicketId, final Credential... credentials) throws org.jasig.cas.authentication.AuthenticationException, TicketException { return this.delegate.delegateTicketGrantingTicket(serviceTicketId, credentials); } public void setAuthenticationManager(final AuthenticationManager manager) { this.authenticationManager = manager; } /** * The set TicketRegistry should be the same registry used by the CentralAuthenticationService instance * provided to setCentralAuthenticationServiceDelegate. * @param ticketRegistry non-null TicketRegistry shared with the delegate CAS */ /** * Method to set the TicketRegistry. * * @param ticketRegistry the TicketRegistry to set. */ public void setTicketRegistry(final TicketRegistry ticketRegistry) { this.ticketRegistry = ticketRegistry; if (this.serviceTicketRegistry == null) { this.serviceTicketRegistry = ticketRegistry; } } /** * Inject a ticket granting ticket expiration policy. * @param ticketGrantingTicketExpirationPolicy the non-null policy on TGT expiration. */ public void setTicketGrantingTicketExpirationPolicy(final ExpirationPolicy ticketGrantingTicketExpirationPolicy) { this.ticketGrantingTicketExpirationPolicy = ticketGrantingTicketExpirationPolicy; } /** * Inject a TGT unique ID generator. * @param uniqueTicketIdGenerator the non-null TGT unique ID generator. */ public void setTicketGrantingTicketUniqueTicketIdGenerator(final UniqueTicketIdGenerator uniqueTicketIdGenerator) { this.ticketGrantingTicketUniqueTicketIdGenerator = uniqueTicketIdGenerator; } /** * Inject a delegate CAS implementation to fulfill the non-TGT-creating CAS API methods. * The delegate CAS instance should share a TicketRegistry with this CAS instance otherwise this CAS will be * granting TGTs that will not be honored by the delegate. * @param cas the non-null delegate CAS */ public void setCentralAuthenticationServiceDelegate(final CentralAuthenticationService cas) { this.delegate = cas; } }