Friday, December 23, 2011

CAS Domain Solution

It's been a while now that we where stuck on this Problem. Basically it is like this, that we can not be sure in our situation that all our applications are able to distinguish between to identical usernames in different domains.

We know that it is against the principle of CAS to move some parts of the authorization from the application to the SSO, however it is needed in our case, and at the End it was not that difficult. Basically we have added a new database table which holds the allowed domains for each service. So we are able to check during the validation step if the service requested accepts the entered credentials.

The support for domains is similar to the one for attributes, so it was enough to just extend the RegisteredService interface adding methods for getting the list of allowed domains and a boolean that defines if the domain selection should be considered.

public interface DomainRegisteredService extends RegisteredService {
/**
    * Sets whether we should bother to read the domain list or not.
    *
    * @return true if we should read it, false otherwise.
    */
   boolean isIgnoreDomains();
   /**
    * Returns the list of allowed domains.
    *
    * @return the list of domains
    */
   List getAllowedDomains();
}

Unfortunately you cannot just extend the original implementation, so we had to copy it and extend it with the needed fields and methods.

@ElementCollection(targetClass = String.class, fetch = FetchType.EAGER)
@JoinTable(name = "drs_domains")
@Column(name = "d_name", nullable = false)
@IndexColumn(name = "d_id")
private List allowedDomains = new ArrayList();
private boolean ignoreDomains = true;

This change introduces the need of extending or exchanging some classes like the ServiceRegistryDao and the RegisteredServiceValidator. However this changes are quite straight forward, so I'll not going to post them right now. The more important change is in the CentralAuthenticationServiceImpl where we need to check the domains.

@Override
@Audit(action = "SERVICE_TICKET_VALIDATE", actionResolverName = "VALIDATE_SERVICE_TICKET_RESOLVER", resourceResolverName = "VALIDATE_SERVICE_TICKET_RESOURCE_RESOLVER")
@Profiled(tag = "VALIDATE_SERVICE_TICKET", logFailuresSeparately = false)
@Transactional(readOnly = false)
public Assertion validateServiceTicket(final String serviceTicketId, final Service service) throws TicketException {
[..]
if (!registeredService.isIgnoreDomains()) {
boolean domainValidated = false;
if (principal == null || principal.getAttributes() == null) {
log.error("Domain of ServiceTicket [" + serviceTicketId + "] with service [" + serviceTicket.getService().getId() + " can not be determined");
throw new TicketValidationException(serviceTicket.getService());
}
List<Pair<String, String>> attributes = getAttributesOfPrincipal(principal.getAttributes().get("distinguishedName"));
List<String> domains = registeredService.getAllowedDomains();
if (domains != null && attributes != null) {
for (Pair<String, String> attribute : attributes) {
if (attribute.getLeft().equals("DC") && domains.contains(attribute.getRight())) {
domainValidated = true;
}
}
}
if (!domainValidated) {
log.error("Domain of ServiceTicket [" + serviceTicketId + "] with service [" + serviceTicket.getService().getId() + " not allowed to use CAS with this domain");
throw new TicketValidationException(serviceTicket.getService());
}
}

What's still missing is how to populate the list of domains, however that's also done like the one for attributes in the deployerConfigContext.xml.

No comments: