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.

Thursday, December 01, 2011

Hibernate Statistics & Criteria Queries

Recently we had to check the performance of our systems and a main part of that is beside the UI the database performance. For that purpose we came over Hibernate Statistics (of course because we access our DB over Hibernate)

As we are using Spring to configure our back end we had to define the statistics bean using JMX because it fits better in our project. It can be done using the following definition
<bean id="jmxExporter"
    class="org.springframework.jmx.export.MBeanExporter">
    <property name="beans">
        <map>
            <entry key="Hibernate:name=statistics" >
                <ref local="databaseStatisticsService" />
            </entry>
        </map>
    </property>
</bean>
<bean id="databaseStatisticsService"
    class="it.mydomain.core.service.impl.DatabaseStatisticsServiceImpl">
    <property name="statisticsEnabled" value="${hibernate.generate_statistics}" />
    <property name="sessionFactory" ref="sessionFactory"/>
</bean>
The property "${hibernate.generate_statistics}" contains just "true" and needs to be added also to the hibernate properties in the format "hibernate.generate_statistics=${hibernate.generate_statistics}"

The "databaseStatisticsService" is just a simple extension of the "org.hibernate.jmx.StatisticsService" providing some simple methods to retrieve the statistics in a convenient way. It's interface its like the following

import org.hibernate.jmx.StatisticsServiceMBean;
import org.hibernate.stat.CollectionStatistics;
import org.hibernate.stat.EntityStatistics;
import org.hibernate.stat.QueryStatistics;
import org.hibernate.stat.SecondLevelCacheStatistics;
import org.hibernate.stat.Statistics;
public interface DatabaseStatisticsService extends StatisticsServiceMBean {
boolean isDbStatisticsEnabled();
Statistics getDbStatistics();
List getDbStatisticSummary();
List getDbStatisticCollections();
List getDbStatisticEntities();
List getDbStatisticQueries();
List getDbStatisticSecondLevelCache();
}

In this interface you can already see, that there is a method "List getDbStatisticQueries();" which should contain the data for each performed query; at least we thought that it should be like that :-)

However by default it contains just the data for the HQL queries but not the ones for the Criteria ones. After a short Google excursion we found that there is an issue that is still open in the hibernate bug tracker. Usually in my experience efficient database queries are a main factor of the applications performance, so we had to find a way to add this data, which turned out to be much simpler as expected.

The main idea is just to add a simple method to the "org.hibernate.loader.criteria.CriteriaLoader" class that allows the Statistic collector to index also that data. The simplest way to do so is just to add the method using an aspect like the following
package it.unibz.ict.core.util.aspect;

import org.hibernate.loader.criteria.CriteriaLoader;
import org.hibernate.stat.Statistics;

/**
 * Aspect adding a method to {@link CriteriaLoader} to allow Hibernate
 * {@link Statistics} pick up criteria queries, too.
 */
public privileged aspect CriteriaStatisticsAspect {
public static final String PREFIX = "[CRITERIA] ";
/**
* @see http://opensource.atlassian.com/projects/hibernate/browse/HHH-3452
* @return
*/
public String CriteriaLoader.getQueryIdentifier() {
return PREFIX + getSQLString();
}
/**
* Dummy method; without this Eclipse would see an error in the getSQLString
* call above
*
* @return dummy value
*/
private String getSQLString() {
return null;
}
}
Problem solved! At least until this method is added by default by the hibernate developer team.