Well here is my solution:
Class: CentralAuthenticationServiceImpl
private final Map<TicketGrantingTicket, Map<String, ServiceUse>>
servicesMonitoring = new WeakHashMap<TicketGrantingTicket, Map<String,
ServiceUse>>();
<code>
@Auditable(action="SERVICE_TICKET",successSuffix="_CREATED",failureSuffix="_NOT_CREATED",actionResolverClass=org.inspektr.audit.spi.support.ObjectCreationAuditableActionResolver.class,resourceResolverClass=org.jasig.cas.audit.spi.ServiceResourceResolver.class)
@Statistic(name="GRANT_SERVICE_TICKET",requiredPrecision={Precision.DAY,Precision.MINUTE,Precision.HOUR})
@Transactional(readOnly = false)
public String grantServiceTicket(final String ticketGrantingTicketId,
final Service service, final Credentials credentials)
throws TicketException {
Assert.notNull(ticketGrantingTicketId,
"ticketGrantingticketId cannot be null");
Assert.notNull(service, "service cannot be null");
final TicketGrantingTicket ticketGrantingTicket;
ticketGrantingTicket = (TicketGrantingTicket) this.ticketRegistry
.getTicket(ticketGrantingTicketId, TicketGrantingTicket.class);
// if a user is not logged out, and did not sign out after 8 hours, the
cookie won't expire, but the ticket WILL
if (ticketGrantingTicket == null){
throw new InvalidTicketException();
}
synchronized (ticketGrantingTicket) {
if (ticketGrantingTicket.isExpired()) {
this.ticketRegistry.deleteTicket(ticketGrantingTicketId);
throw new InvalidTicketException();
}
}
final RegisteredService registeredService = this.servicesManager
.findServiceBy(service);
if (registeredService == null || !registeredService.isEnabled()) {
if (log.isDebugEnabled()) {
log.debug("Service [" + service.getId()
+ "] not found in ServiceRegistry.");
}
throw new UnauthorizedServiceException();
}
if (!registeredService.isSsoEnabled() && credentials == null
&& ticketGrantingTicket.getCountOfUses() > 0) {
throw new UnauthorizedSsoServiceException();
}
checkForAbuse(service, ticketGrantingTicket, registeredService);
if (credentials != null) {
try {
final Authentication authentication =
this.authenticationManager
.authenticate(credentials);
final Authentication originalAuthentication =
ticketGrantingTicket.getAuthentication();
if
(!(authentication.getPrincipal().equals(originalAuthentication.getPrincipal())
&&
authentication.getAttributes().equals(originalAuthentication.getAttributes())))
{
throw new TicketCreationException();
}
} catch (final AuthenticationException e) {
throw new TicketCreationException(e);
}
}
// XXX fix this
final UniqueTicketIdGenerator serviceTicketUniqueTicketIdGenerator =
this.uniqueTicketIdGeneratorsForService
.get(service.getClass().getName());
final ServiceTicket serviceTicket = ticketGrantingTicket
.grantServiceTicket(serviceTicketUniqueTicketIdGenerator
.getNewTicketId(ServiceTicket.PREFIX), service,
this.serviceTicketExpirationPolicy, credentials != null);
// FIXME: when a Data Exception happens, log it to the logfile
try {
this.serviceTicketRegistry.addTicket(serviceTicket);
} catch (Exception de) {
log.error("Data exception while saving the ticket " +
serviceTicket.getId() + " to the database");
log.error("The TGT for this service ticket is been used for
following services:");
StringBuilder strb = new StringBuilder();
if (serviceTicket.getGrantingTicket() != null) {
for (String serviceId :
serviceTicket.getGrantingTicket().getServices()) {
strb.append(serviceId + ", ");
}
log.error(strb.toString());
}
}
if (log.isInfoEnabled()) {
log.info("Granted service ticket ["
+ serviceTicket.getId()
+ "] for service ["
+ service.getId()
+ "] for user ["
+ serviceTicket.getGrantingTicket().getAuthentication()
.getPrincipal().getId() + "]");
}
return serviceTicket.getId();
}
/**
* Check for abuse.
*
* @param service
* the service
* @param ticketGrantingTicket
* the ticket granting ticket
* @param registeredService
* the registered service
*
* @throws InvalidTicketException
* the invalid ticket exception
*/
private void checkForAbuse(final Service service, final
TicketGrantingTicket ticketGrantingTicket, final RegisteredService
registeredService) throws InvalidTicketException {
ServiceUse serviceUse = null;
// returns null if not exists(any more)
Map<String, ServiceUse> serviceUseMap = null;
serviceUseMap = servicesMonitoring.get(ticketGrantingTicket);
if(serviceUseMap==null){
serviceUseMap = new HashMap<String, ServiceUse>();
}
serviceUse = serviceUseMap.get(registeredService.getServiceId());
if(serviceUse == null){
serviceUse =
serviceUseFactory.createServiceUse(registeredService, service,
ticketGrantingTicket);
}
if(serviceUse.testForAbuse()){
throw new UnauthorizedServiceException();
}
serviceUseMap.put(registeredService.getServiceId(), serviceUse);
this.servicesMonitoring.put(ticketGrantingTicket,
serviceUseMap);
if (ticketGrantingTicket == null) {
throw new InvalidTicketException();
}
}
</code>
/**
* The Class ServiceUse.
*/
<code>
public class ServiceUse implements Serializable {
/** The Commons Logging instance. */
private final Log log = LogFactory.getLog(getClass());
/** The Constant serialVersionUID. */
private static final long serialVersionUID = 1L;
/** The TicketGrantingTicket this is associated with. */
private TicketGrantingTicket ticketGrantingTicket;
/** The service this ticket is valid for. */
private Service service;
/** The time set. */
private Long timeStartedMonitoring;
/** The Number redirects. */
private Integer numberRedirects = 0;
/** The max time. in miliseconds */
private Long maxTime;
/** The maxium redirects. */
private Integer maximumRedirects;
/** The abuse service. */
private Boolean abuseServiceDetected = false;
private RegisteredService registeredService;
public final RegisteredService getRegisteredService() {
return registeredService;
}
public final void setRegisteredService(RegisteredService
registeredService)
{
this.registeredService = registeredService;
}
/**
* Gets the max used ratio.
*
* @return the max used ratio
*/
private final Long getMaxUsedRatio(){
return maxTime/maximumRedirects;
}
/**
* Gets the current used ratio, this is calculated from the max time.
*
*
* @return the current used ratio
*/
private final Long getCurrentUsedRatio(){
return maxTime/numberRedirects;
}
/**
* Gets the maxium redirects.
*
* @return the maxium redirects
*/
public final Integer getMaximumRedirects() {
return maximumRedirects;
}
/**
* Sets the maxium redirects.
*
* @param maximumRedirects
* the new maxium redirects
*/
public final void setMaximumRedirects(final Integer maxiumRedirects) {
this.maximumRedirects = maxiumRedirects;
}
/**
* Gets the max time.
*
* @return the max time
*/
public final Long getMaxTime() {
return maxTime;
}
/**
* Sets the max time.
*
* @param maxTime
* the new max time
*/
public final void setMaxTime(final Long maxTime) {
this.maxTime = maxTime;
}
/**
* Gets the number redirects.
*
* @return the number redirects
*/
public final Integer getNumberRedirects() {
return numberRedirects;
}
/**
* Sets the number redirects.
*
* @param numberRedirects
* the new number redirects
*/
public final void setNumberRedirects(final Integer numberRedirects) {
this.numberRedirects = numberRedirects;
}
/**
* Gets the service.
*
* @return the service
*/
public final Service getService() {
return service;
}
/**
* Sets the service.
*
* @param service
* the new service
*/
public final void setService(final Service service) {
this.service = service;
}
/**
* Gets the ticket granting ticket.
*
* @return the ticket granting ticket
*/
public final TicketGrantingTicket getTicketGrantingTicket() {
return ticketGrantingTicket;
}
/**
* Sets the ticket granting ticket.
*
* @param ticketGrantingTicket
* the new ticket granting ticket
*/
public final void setTicketGrantingTicket(
TicketGrantingTicket ticketGrantingTicket) {
this.ticketGrantingTicket = ticketGrantingTicket;
}
/**
* Gets the time set.
*
* @return the time set
*/
public final Long getTimeStartedMonitoring() {
return timeStartedMonitoring;
}
/**
* Sets the time set.
*
* @param timeStartedMonitoring
* the new time set
*/
public final void setTimeStartedMonitoring(final Long timeSet) {
this.timeStartedMonitoring = timeSet;
}
/**
* Gets the abuse service.
*
* @return the abuse service
*/
public final Boolean getAbuseServiceDetected() {
return abuseServiceDetected;
}
/**
* Test for abuse.
*
* @return true, if abused is detected
*/
public boolean testForAbuse() {
Boolean abused = true;
if (!getAbuseServiceDetected()) {
if (checkMonitoringTime()) {
setReStartMonitoring();
}
numberRedirects+=1;
if(getMaxUsedRatio() < getCurrentUsedRatio()){
abused = false;
}else{
abuseServiceDetected = true;
log.error("BLOCKED<" +
registeredService.getServiceId()
+ "> " +
"url+params_last_used:" + service.getId() + numberRedirects +
" req in " + (System.currentTimeMillis() - timeStartedMonitoring) + "ms "
+ ticketGrantingTicket.getId()
+ " TGT_#used: "
+
ticketGrantingTicket.getCountOfUses());
}
}
return abused;
}
/**
* Check monitoring time.
*
* @return the boolean
*/
private Boolean checkMonitoringTime() {
return ((System.currentTimeMillis() - timeStartedMonitoring) >
maxTime);
}
/**
* Sets the start monitoring.
*/
private void setReStartMonitoring() {
timeStartedMonitoring = System.currentTimeMillis();
numberRedirects = 0;
}
/**
* Instantiates a new service use.
*/
public ServiceUse() {
super();
timeStartedMonitoring = System.currentTimeMillis();
}
/* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
final int PRIME = 31;
int result = 1;
result = PRIME * result + ((service == null) ? 0 :
service.hashCode());
result = PRIME
* result
+ ((ticketGrantingTicket == null) ? 0 :
ticketGrantingTicket
.hashCode());
return result;
}
/* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
final ServiceUse other = (ServiceUse) obj;
if (service == null) {
if (other.service != null)
return false;
} else if (!service.equals(other.service))
return false;
if (ticketGrantingTicket == null) {
if (other.ticketGrantingTicket != null)
return false;
} else if
(!ticketGrantingTicket.equals(other.ticketGrantingTicket))
return false;
return true;
}
}
</code>
<code>
public interface ServiceUseFactory {
public abstract ServiceUse createServiceUse(RegisteredService
registeredService, Service service,
TicketGrantingTicket ticketGrantingTicket);
}
</code>
<code>
/**
* The Class ServiceUseFactoryImpl.
*/
public class ServiceUseFactoryImpl implements ServiceUseFactory {
/** The max time. in miliseconds */
private Long maxTime;
/** The maxium redirects. */
private Integer maximumRedirects;
/**
* Gets the maxium redirects.
*
* @return the maxium redirects
*/
public final Integer getMaximumRedirects() {
return maximumRedirects;
}
/**
* Sets the maxium redirects.
*
* @param maximumRedirects
* the new maxium redirects
*/
public final void setMaximumRedirects(final Integer maximumRedirects) {
this.maximumRedirects = maximumRedirects;
}
/**
* Gets the max time.
*
* @return the max time
*/
public final Long getMaxTime() {
return maxTime;
}
/**
* Sets the max time.
*
* @param maxTime
* the new max time
*/
public final void setMaxTime(final Long maxTime) {
this.maxTime = maxTime;
}
/**
* Instantiates a new service use factory impl.
*/
public ServiceUseFactoryImpl(){
}
/* (non-Javadoc)
* @see
org.jasig.cas.ticket.ServiceUseFactory#createServiceUse(org.jasig.cas.authentication.principal.Service,
org.jasig.cas.ticket.TicketGrantingTicket)
*/
public ServiceUse createServiceUse(RegisteredService registeredService,
Service service, TicketGrantingTicket ticketGrantingTicket){
ServiceUse serviceUse = new ServiceUse();
serviceUse.setRegisteredService(registeredService);
serviceUse.setMaximumRedirects(maximumRedirects);
serviceUse.setMaxTime(maxTime);
serviceUse.setService(service);
serviceUse.setTicketGrantingTicket(ticketGrantingTicket);
return serviceUse;
}
}
</code>
Advantage:
If an application has a badly written cas client, we will now see it on our
logs.
I hope this code can be a bit helpful.
Danny B.
--
View this message in context:
http://www.nabble.com/possible-solution-preventing-abuse-cas-infinite-loop-redirect-service-tp23389145p23518436.html
Sent from the CAS Users mailing list archive at Nabble.com.
--
You are currently subscribed to [email protected] as:
[email protected]
To unsubscribe, change settings or access archives, see
http://www.ja-sig.org/wiki/display/JSG/cas-user