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.


Tuesday, November 29, 2011

CAS Domain Problem

In our company we have the problem, that we have 2 different domains where the authentication can happen. Unfortunately we can not assure that the credentials are unique over both Domains. e.g. there could be a user "Tom" in Domain1 and another "Tom" in Domain2 which belong to two different physical users (hopefully with two different passwords)

Now where's the problem? Well in the various applications we need to authorize the user depending on their position in the company. So it could be that the "Tom" in Domain1 can access an application but the "Tom" from Domain2 not. So how we can distinguish between the two "Toms"?

Despite that some of the applications, basically the ones that are developed in-house, can be extended to consider the domain passed as argument(see CAS & Attributes), there are some third party apps that cannot. So how those applications could have worked before? Basically for those we have one running version per domain, which solved the problem.


For those now we had to find another way. As we cannot check it on the application side the domain check needs to be implemented on CAS meaning that on "validateServiceTicket" another check to the authenticating Domain needs to be placed.

Important in this case is not to change to much of the original structure of CAS, in order not to loose the possibility to update when CAS updates. The solution has not yet been implemented nor tested, so stay tuned for the solution post.

UPDATE: Finally there is the solution

Friday, November 25, 2011

CAS Themes and Views

Recently we faced a big problem in our organisation regarding the look and structure of the login pages which could not be solved by CAS standard theme concept. By default the theme definition of CAS just changes the default CSS which for sure offers a wide variety within the pages, but made it difficult for us to fit our needs.

--> We had to change it in a way that also the views change

1) In the cas properties instead of telling the real name of the views I want to see just direct in any case to some defualt view like this cas.viewResolver.basename=views-default

2) Under src/main/webapp/WEB-INF/classes add one property file for each theme you want to use afterwards
src/main/webapp/WEB-INF/classes
|
+- cas-theme-theme1.properties
|
+- cas-theme-theme2.properties
|
+- ...

3) In this files just add one more property (beside the ones that are already there by default)
theme.view=/WEB-INF/view/jsp/theme1/ui/
When the structure of your views is still like the one from default it should point perfektly to the views of theme1 in this example


4) Now change the default views e.g. src/main/webapp/WEB-INF/view/jsp/default/ui/casLoginView.jsp to the following

<%@ page contentType="text/html; charset=UTF-8" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<spring:theme code="theme.view" var="theme"></spring:theme>
<jsp:include page="${theme}casLoginView.jsp"/>

With this definition now the casLoginView.uses the view defined by the theme.view property in cas-theme-themeX.properties. file.


And now how to change the property file? Just use the Services GUI (e.g. https://login.cas.net/cas/services/manage.html) and enter there the name of the theme for the various services. And that's actually the power of the solution. Now you can define one default theme for your server and a lot of "real" themes for the various services that use you're CAS.


Of course it is a change in the basic logic of CAS, however it is reversable in a few steps if one day changing the basic CSS would be enough


UPDATE:
By default the theme names are cas-theme-xxx which is at least 10 chars long. The default implementation of  the CAS Serive Managemant GUI allows theme names with a maximum length of 10, so change the add.jsp to allow the longer names

Linux boots into memtest86

Today I had the problem, that my ubuntu instance was booting automatically into the memtest86 grub entry. This was mainly because that was the only grub entry that remained after a linux image update.

The simplest way to fix this problem was using a live CD and the following commands:
1) boot into live CD

2) create a directory to mount the old linux on your hdd
sudo mkdir /mnt/temp
3) mount it
sudo mount /dev/sda1 /mnt/temp for i in /dev /dev/pts /proc /sys; do sudo mount -B $i /mnt/temp$i; done
4) copy the resolve.conf into the original ubuntu so that the apt-get will work in future commands
sudo cp /etc/resolv.conf /mnt/temp/etc/resolv.conf
5) change the root directory to the newly created mount point
sudo chroot /mnt/temp
6) now check if the kernel is already on your system. If not just install it
apt-get install linux
6.1) do some cleaning in your system
apt-get autoclean
apt-get clean
apt-get update
apt-get upgrade
apt-get install -f
dpkg --configure -a
7) if this still not works try to reinstall grub
apt-get purge grub grub-pc grub-common
apt-get install grub-common grub-pc
update-grub
8) umount the whole stuff
for i in /dev/pts /dev /proc /sys / ; do sudo umount /mnt/temp$i ; done
9) restart the system

Now it should boot into the actual linux kernel and your system is back!

Friday, October 28, 2011

Sending Attributes with ja-sig CAS response


Basically there is one still unsolved problem which has to be handled in some way when you like to use attributes in CAS' response
Look for the "casServiceValidationSuccess.jsp" page (there could be more if there are different themes in your CAS) and change it to something like this
<%@ page session="false"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions"%>

<cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>
 <cas:authenticationSuccess>
  <cas:user>${fn:escapeXml(assertion.chainedAuthentications[fn:length(assertion.chainedAuthentications)-1].principal.id)}</cas:user>
  <c:if test="${not empty pgtIou}">
   <cas:proxyGrantingTicket>${pgtIou}</cas:proxyGrantingTicket>
  </c:if>
  <c:if test="${fn:length(assertion.chainedAuthentications) > 1}">
   <cas:proxies>
    <c:forEach var="proxy" items="${assertion.chainedAuthentications}"
     varStatus="loopStatus" begin="0"
     end="${fn:length(assertion.chainedAuthentications)-2}" step="1">
     <cas:proxy>${fn:escapeXml(proxy.principal.id)}</cas:proxy>
    </c:forEach>
   </cas:proxies>
  </c:if>
  <%-- CAS attributes -- BEGIN -- --%>
  <cas:attributes>
   <c:if
    test="${fn:length(assertion.chainedAuthentications[fn:length(assertion.chainedAuthentications)-1].principal.attributes) >= 1}">
    <c:forEach var="attr"
     items="${assertion.chainedAuthentications[fn:length(assertion.chainedAuthentications)-1].principal.attributes}"
     varStatus="loopStatus" begin="0"
     end="${fn:length(assertion.chainedAuthentications[fn:length(assertion.chainedAuthentications)-1].principal.attributes)-1}"
     step="1">
     <cas:${fn:escapeXml(attr.key)}>${fn:escapeXml(attr.value)}</cas:${fn:escapeXml(attr.key)}>
    </c:forEach>
   </c:if>
  </cas:attributes>
  <%-- CAS attributes -- END -- --%>
 </cas:authenticationSuccess>
</cas:serviceResponse>

What's its purpose? Well, it just reads the attributes if there are some and adds them in an appropriate way to the response
<cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>
  <cas:authenticationSuccess>
    <cas:user>justme</cas:user>
  
    <cas:proxyGrantingTicket>PGTIOU-5-fvra6OPAutt4fbEBAh2T</cas:proxyGrantingTicket>
  
    <cas:attributes>
      <cas:userPrincipalName>justme</cas:userPrincipalName>
      <cas:email>justme@mail.org</cas:email>
      <cas:FirstName>Just</cas:FirstName>
      <cas:LastName>Me</cas:LastName>
      <cas:displayName>Just Me</cas:displayName>
    </cas:attributes>

  </cas:authenticationSuccess>
</cas:serviceResponse>

Wednesday, September 07, 2011

Jasig CAS: migrate from 3.4.8 to 3.4.10 LDAP error


Today I tried to migrate from CAS version 3.4.8 to 3.4.10 which resulted in the following error:
[/WEB-INF/deployerConfigContext.xml]: Initialization of bean failed;
nested exception is org.springframework.beans.factory.BeanInitializationException: Bean state is invalid: ldapTemplate - may not be null
    at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:328)
    at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:106)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1325)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1086)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:517)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:456)
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:291)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:288)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:190)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:580)
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:895)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:425)
at org.springframework.web.context.ContextLoader.createWebApplicationContext(ContextLoader.java:276)
This error was produced by the following LDAP configuration:
<bean id="authenticationHandler"
class="org.jasig.cas.adaptors.ldap.BindLdapAuthenticationHandler">
<property name="filter" value="sAMAccountName=%u" />
<property name="searchBase" value="ou=xxx,dc=xxx,dc=xxx" />
<property name="contextSource" ref="contextSource" />
<property name="ignorePartialResultException" value="yes" />
</bean>
Despite that the error message suggests something different, there is no need to add a ldapTemplate property but duplicate the contextSource and rename it to searchContextSource
<bean id="authenticationHandler"
class="org.jasig.cas.adaptors.ldap.BindLdapAuthenticationHandler">
<property name="filter" value="sAMAccountName=%u" />
<property name="searchBase" value="ou=xxx,dc=xxx,dc=xxx" />
<property name="contextSource" ref="contextSource" />
<property name="searchContextSource" ref="contextSource" />
<property name="ignorePartialResultException" value="yes" />
</bean>
And now its working again!