package org.apereo.cas;
|
|
import com.codahale.metrics.annotation.Counted;
|
import com.codahale.metrics.annotation.Metered;
|
import com.codahale.metrics.annotation.Timed;
|
import java.util.Collections;
|
import java.util.HashMap;
|
import java.util.List;
|
import java.util.Map;
|
import org.apereo.cas.authentication.Authentication;
|
import org.apereo.cas.authentication.AuthenticationBuilder;
|
import org.apereo.cas.authentication.AuthenticationException;
|
import org.apereo.cas.authentication.AuthenticationResult;
|
import org.apereo.cas.authentication.CurrentCredentialsAndAuthentication;
|
import org.apereo.cas.authentication.DefaultAuthenticationBuilder;
|
import org.apereo.cas.authentication.MixedPrincipalException;
|
import org.apereo.cas.authentication.PrincipalException;
|
import org.apereo.cas.authentication.principal.Principal;
|
import org.apereo.cas.authentication.principal.Service;
|
import org.apereo.cas.logout.LogoutManager;
|
import org.apereo.cas.logout.LogoutRequest;
|
import org.apereo.cas.services.RegisteredService;
|
import org.apereo.cas.services.RegisteredServiceAccessStrategyUtils;
|
import org.apereo.cas.services.RegisteredServiceAttributeReleasePolicy;
|
import org.apereo.cas.services.ServiceContext;
|
import org.apereo.cas.services.ServicesManager;
|
import org.apereo.cas.services.UnauthorizedProxyingException;
|
import org.apereo.cas.services.UnauthorizedSsoServiceException;
|
import org.apereo.cas.support.events.CasProxyGrantingTicketCreatedEvent;
|
import org.apereo.cas.support.events.CasProxyTicketGrantedEvent;
|
import org.apereo.cas.support.events.CasServiceTicketGrantedEvent;
|
import org.apereo.cas.support.events.CasServiceTicketValidatedEvent;
|
import org.apereo.cas.support.events.CasTicketGrantingTicketCreatedEvent;
|
import org.apereo.cas.support.events.CasTicketGrantingTicketDestroyedEvent;
|
import org.apereo.cas.ticket.AbstractTicketException;
|
import org.apereo.cas.ticket.InvalidTicketException;
|
import org.apereo.cas.ticket.ServiceTicket;
|
import org.apereo.cas.ticket.ServiceTicketFactory;
|
import org.apereo.cas.ticket.TicketFactory;
|
import org.apereo.cas.ticket.TicketGrantingTicket;
|
import org.apereo.cas.ticket.TicketGrantingTicketFactory;
|
import org.apereo.cas.ticket.UnrecognizableServiceForServiceTicketValidationException;
|
import org.apereo.cas.ticket.proxy.ProxyGrantingTicket;
|
import org.apereo.cas.ticket.proxy.ProxyGrantingTicketFactory;
|
import org.apereo.cas.ticket.proxy.ProxyTicket;
|
import org.apereo.cas.ticket.proxy.ProxyTicketFactory;
|
import org.apereo.cas.ticket.registry.TicketRegistry;
|
import org.apereo.cas.validation.Assertion;
|
import org.apereo.cas.validation.ImmutableAssertion;
|
import org.apereo.cas.validation.ValidationServiceSelectionStrategy;
|
import org.apereo.inspektr.audit.annotation.Audit;
|
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.util.StringUtils;
|
|
/**
|
* @author Tanbin
|
* @date 2018-12-12
|
*/
|
@Transactional(readOnly = false, transactionManager = "ticketTransactionManager")
|
public class CentralAuthenticationServiceImpl extends AbstractCentralAuthenticationService {
|
private static final long serialVersionUID = -8943828074939533986L;
|
|
public CentralAuthenticationServiceImpl() {}
|
|
public CentralAuthenticationServiceImpl(TicketRegistry ticketRegistry, TicketFactory ticketFactory, ServicesManager servicesManager, LogoutManager logoutManager) { super(ticketRegistry, ticketFactory, servicesManager, logoutManager); }
|
|
@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(String ticketGrantingTicketId) {
|
try {
|
this.logger.debug("Removing ticket [{}] from registry...", ticketGrantingTicketId);
|
TicketGrantingTicket ticket = (TicketGrantingTicket)getTicket(ticketGrantingTicketId, TicketGrantingTicket.class);
|
this.logger.debug("Ticket found. Processing logout requests and then deleting the ticket...");
|
CurrentCredentialsAndAuthentication.bindCurrent(ticket.getAuthentication());
|
List<LogoutRequest> logoutRequests = this.logoutManager.performLogout(ticket);
|
this.ticketRegistry.deleteTicket(ticketGrantingTicketId);
|
doPublishEvent(new CasTicketGrantingTicketDestroyedEvent(this, ticket));
|
return logoutRequests;
|
} catch (InvalidTicketException e) {
|
this.logger.debug("TicketGrantingTicket [{}] cannot be found in the ticket registry.", ticketGrantingTicketId);
|
return Collections.emptyList();
|
}
|
}
|
|
@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(String ticketGrantingTicketId, Service service, AuthenticationResult authenticationResult) throws AuthenticationException, AbstractTicketException {
|
TicketGrantingTicket ticketGrantingTicket = (TicketGrantingTicket)getTicket(ticketGrantingTicketId, TicketGrantingTicket.class);
|
RegisteredService registeredService = this.servicesManager.findServiceBy(service);
|
RegisteredServiceAccessStrategyUtils.ensurePrincipalAccessIsAllowedForService(service, registeredService, ticketGrantingTicket);
|
Authentication currentAuthentication = evaluatePossibilityOfMixedPrincipals(authenticationResult, ticketGrantingTicket);
|
RegisteredServiceAccessStrategyUtils.ensureServiceSsoAccessIsAllowed(registeredService, service, ticketGrantingTicket);
|
evaluateProxiedServiceIfNeeded(service, ticketGrantingTicket, registeredService);
|
getAuthenticationSatisfiedByPolicy(currentAuthentication, new ServiceContext(service, registeredService));
|
List<Authentication> authentications = ticketGrantingTicket.getChainedAuthentications();
|
Authentication latestAuthentication = (Authentication)authentications.get(authentications.size() - 1);
|
CurrentCredentialsAndAuthentication.bindCurrent(latestAuthentication);
|
Principal principal = latestAuthentication.getPrincipal();
|
ServiceTicketFactory factory = (ServiceTicketFactory)this.ticketFactory.get(ServiceTicket.class);
|
ServiceTicket serviceTicket = (ServiceTicket)factory.create(ticketGrantingTicket, service, (authenticationResult != null && authenticationResult
|
.isCredentialProvided()));
|
this.ticketRegistry.updateTicket(ticketGrantingTicket);
|
this.ticketRegistry.addTicket(serviceTicket);
|
this.logger.info("Granted ticket [{}] for service [{}] and principal [{}]", new Object[] { serviceTicket
|
.getId(), service.getId(), principal.getId() });
|
doPublishEvent(new CasServiceTicketGrantedEvent(this, ticketGrantingTicket, serviceTicket));
|
return serviceTicket;
|
}
|
|
private static Authentication evaluatePossibilityOfMixedPrincipals(AuthenticationResult context, TicketGrantingTicket ticketGrantingTicket) throws MixedPrincipalException {
|
Authentication currentAuthentication = null;
|
if (context != null) {
|
currentAuthentication = context.getAuthentication();
|
if (currentAuthentication != null) {
|
Authentication original = ticketGrantingTicket.getAuthentication();
|
if (!currentAuthentication.getPrincipal().equals(original.getPrincipal())) {
|
throw new MixedPrincipalException(currentAuthentication, currentAuthentication.getPrincipal(), original.getPrincipal());
|
}
|
}
|
}
|
return currentAuthentication;
|
}
|
|
@Audit(action = "PROXY_TICKET", actionResolverName = "GRANT_PROXY_TICKET_RESOLVER", resourceResolverName = "GRANT_PROXY_TICKET_RESOURCE_RESOLVER")
|
@Timed(name = "GRANT_PROXY_TICKET_TIMER")
|
@Metered(name = "GRANT_PROXY_TICKET_METER")
|
@Counted(name = "GRANT_PROXY_TICKET_COUNTER", monotonic = true)
|
@Override
|
public ProxyTicket grantProxyTicket(String proxyGrantingTicket, Service service) throws AbstractTicketException {
|
ProxyGrantingTicket proxyGrantingTicketObject = (ProxyGrantingTicket)getTicket(proxyGrantingTicket, ProxyGrantingTicket.class);
|
RegisteredService registeredService = this.servicesManager.findServiceBy(service);
|
try {
|
RegisteredServiceAccessStrategyUtils.ensurePrincipalAccessIsAllowedForService(service, registeredService, proxyGrantingTicketObject);
|
RegisteredServiceAccessStrategyUtils.ensureServiceSsoAccessIsAllowed(registeredService, service, proxyGrantingTicketObject);
|
} catch (PrincipalException e) {
|
throw new UnauthorizedSsoServiceException();
|
}
|
evaluateProxiedServiceIfNeeded(service, proxyGrantingTicketObject, registeredService);
|
getAuthenticationSatisfiedByPolicy(proxyGrantingTicketObject.getRoot().getAuthentication(), new ServiceContext(service, registeredService));
|
List<Authentication> authentications = proxyGrantingTicketObject.getChainedAuthentications();
|
Authentication authentication = (Authentication)authentications.get(authentications.size() - 1);
|
CurrentCredentialsAndAuthentication.bindCurrent(authentication);
|
Principal principal = authentication.getPrincipal();
|
ProxyTicketFactory factory = (ProxyTicketFactory)this.ticketFactory.get(ProxyTicket.class);
|
ProxyTicket proxyTicket = (ProxyTicket)factory.create(proxyGrantingTicketObject, service);
|
this.ticketRegistry.updateTicket(proxyGrantingTicketObject);
|
this.ticketRegistry.addTicket(proxyTicket);
|
this.logger.info("Granted ticket [{}] for service [{}] for user [{}]", new Object[] { proxyTicket
|
.getId(), service.getId(), principal.getId() });
|
doPublishEvent(new CasProxyTicketGrantedEvent(this, proxyGrantingTicketObject, proxyTicket));
|
return proxyTicket;
|
}
|
|
@Audit(action = "PROXY_GRANTING_TICKET", actionResolverName = "CREATE_PROXY_GRANTING_TICKET_RESOLVER", resourceResolverName = "CREATE_PROXY_GRANTING_TICKET_RESOURCE_RESOLVER")
|
@Timed(name = "CREATE_PROXY_GRANTING_TICKET_TIMER")
|
@Metered(name = "CREATE_PROXY_GRANTING_TICKET_METER")
|
@Counted(name = "CREATE_PROXY_GRANTING_TICKET_COUNTER", monotonic = true)
|
@Override
|
public ProxyGrantingTicket createProxyGrantingTicket(String serviceTicketId, AuthenticationResult authenticationResult) throws AuthenticationException, AbstractTicketException {
|
CurrentCredentialsAndAuthentication.bindCurrent(authenticationResult.getAuthentication());
|
ServiceTicket serviceTicket = (ServiceTicket)this.ticketRegistry.getTicket(serviceTicketId, ServiceTicket.class);
|
if (serviceTicket == null || serviceTicket.isExpired()) {
|
this.logger.debug("ServiceTicket [{}] has expired or cannot be found in the ticket registry", serviceTicketId);
|
throw new InvalidTicketException(serviceTicketId);
|
}
|
RegisteredService registeredService = this.servicesManager.findServiceBy(serviceTicket.getService());
|
RegisteredServiceAccessStrategyUtils.ensurePrincipalAccessIsAllowedForService(serviceTicket, authenticationResult, registeredService);
|
if (!registeredService.getProxyPolicy().isAllowedToProxy()) {
|
this.logger.warn("ServiceManagement: Service [{}] attempted to proxy, but is not allowed.", serviceTicket.getService().getId());
|
throw new UnauthorizedProxyingException();
|
}
|
Authentication authentication = authenticationResult.getAuthentication();
|
ProxyGrantingTicketFactory factory = (ProxyGrantingTicketFactory)this.ticketFactory.get(ProxyGrantingTicket.class);
|
ProxyGrantingTicket proxyGrantingTicket = factory.create(serviceTicket, authentication);
|
this.logger.debug("Generated proxy granting ticket [{}] based off of [{}]", proxyGrantingTicket, serviceTicketId);
|
this.ticketRegistry.addTicket(proxyGrantingTicket);
|
doPublishEvent(new CasProxyGrantingTicketCreatedEvent(this, proxyGrantingTicket));
|
return proxyGrantingTicket;
|
}
|
|
@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(String serviceTicketId, Service service) throws AbstractTicketException {
|
if (!ticketAuthenticityIsVerified(serviceTicketId)) {
|
this.logger.info("Service ticket [{}] is not a valid ticket issued by CAS.", serviceTicketId);
|
throw new InvalidTicketException(serviceTicketId);
|
}
|
ServiceTicket serviceTicket = (ServiceTicket)this.ticketRegistry.getTicket(serviceTicketId, ServiceTicket.class);
|
if (serviceTicket == null) {
|
this.logger.info("Service ticket [{}] does not exist.", serviceTicketId);
|
throw new InvalidTicketException(serviceTicketId);
|
}
|
try {
|
synchronized (serviceTicket) {
|
if (serviceTicket.isExpired()) {
|
this.logger.info("ServiceTicket [{}] has expired.", serviceTicketId);
|
throw new InvalidTicketException(serviceTicketId);
|
}
|
if (!serviceTicket.isValidFor(service)) {
|
this.logger.error("Service ticket [{}] with service [{}] does not match supplied service [{}]", new Object[] { serviceTicketId, serviceTicket
|
.getService().getId(), service });
|
throw new UnrecognizableServiceForServiceTicketValidationException(serviceTicket.getService());
|
}
|
}
|
Service selectedService = ((ValidationServiceSelectionStrategy)this.validationServiceSelectionStrategies.stream().sorted().filter(s -> s.supports(service)).findFirst().get()).resolveServiceFrom(service);
|
RegisteredService registeredService = this.servicesManager.findServiceBy(selectedService);
|
this.logger.debug("Located registered service definition {} from {} to handle validation request", registeredService, selectedService);
|
RegisteredServiceAccessStrategyUtils.ensureServiceAccessIsAllowed(selectedService, registeredService);
|
TicketGrantingTicket root = serviceTicket.getGrantingTicket().getRoot();
|
Authentication authentication = getAuthenticationSatisfiedByPolicy(root
|
.getAuthentication(), new ServiceContext(selectedService, registeredService));
|
Principal principal = authentication.getPrincipal();
|
RegisteredServiceAttributeReleasePolicy attributePolicy = registeredService.getAttributeReleasePolicy();
|
this.logger.debug("Attribute policy [{}] is associated with service [{}]", attributePolicy, registeredService);
|
Map<String, Object> attributesToRelease = (attributePolicy != null) ? attributePolicy.getAttributes(principal) : new HashMap(5);
|
String principalId = registeredService.getUsernameAttributeProvider().resolveUsername(principal, selectedService);
|
Principal modifiedPrincipal = this.principalFactory.createPrincipal(principalId, attributesToRelease);
|
AuthenticationBuilder builder = DefaultAuthenticationBuilder.newInstance(authentication);
|
builder.setPrincipal(modifiedPrincipal);
|
Authentication finalAuthentication = builder.build();
|
CurrentCredentialsAndAuthentication.bindCurrent(finalAuthentication);
|
ImmutableAssertion immutableAssertion = new ImmutableAssertion(finalAuthentication, serviceTicket.getGrantingTicket().getChainedAuthentications(), selectedService, serviceTicket.isFromNewLogin());
|
doPublishEvent(new CasServiceTicketValidatedEvent(this, serviceTicket, immutableAssertion));
|
return immutableAssertion;
|
} finally {
|
if (serviceTicket.isExpired()) {
|
this.ticketRegistry.deleteTicket(serviceTicketId);
|
} else {
|
this.ticketRegistry.updateTicket(serviceTicket);
|
}
|
}
|
}
|
|
@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)
|
@Override
|
public TicketGrantingTicket createTicketGrantingTicket(AuthenticationResult authenticationResult) throws AuthenticationException, AbstractTicketException {
|
Authentication authentication = authenticationResult.getAuthentication();
|
Service service = authenticationResult.getService();
|
CurrentCredentialsAndAuthentication.bindCurrent(authentication);
|
if (service != null) {
|
RegisteredService registeredService = this.servicesManager.findServiceBy(service);
|
RegisteredServiceAccessStrategyUtils.ensurePrincipalAccessIsAllowedForService(service, registeredService, authentication);
|
}
|
TicketGrantingTicketFactory factory = (TicketGrantingTicketFactory)this.ticketFactory.get(TicketGrantingTicket.class);
|
TicketGrantingTicket ticketGrantingTicket = factory.create(authentication);
|
this.ticketRegistry.addTicket(ticketGrantingTicket);
|
doPublishEvent(new CasTicketGrantingTicketCreatedEvent(this, ticketGrantingTicket));
|
return ticketGrantingTicket;
|
}
|
|
private boolean ticketAuthenticityIsVerified(String ticketId) {
|
if (this.cipherExecutor != null) {
|
this.logger.debug("Attempting to decode service ticket {} to verify authenticity", ticketId);
|
return !StringUtils.isEmpty(this.cipherExecutor.decode(ticketId));
|
}
|
return !StringUtils.isEmpty(ticketId);
|
}
|
}
|