Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Yale CAS Customizations

Generally speaking, one should try not to make modifications to off the shelf software. However, the advantage of Open Source software is that you have access to the source code if you need it. Sometime a single line change to the source accomplishes quickly something that could otherwise only be done with weeks or months of trial and error through the poorly documented Spring Configuration. Sometimes Yale has special needs for which the JASIG code has no solution.

The good news is that CAS has a logical modular design based on well defined, if sometimes poorly documented, interfaces. If you understand the architecture of Authentication Managers, Authentication Handlers, Credential to Principal Resolvers, and Ticket Caches, then you can create your own implementation of any specific interface and generate custom code that will easily migrate to new releases.

There are several problems that trigger a need for customization:

Yale has a small number of customizations to CAS. They are installed using standard CAS conventions for adding code and changing configuration.

To understand the way the customizations integrate to CAS you need to understand the CAS architecture, interfaces, and Spring configuration. This is not, however, a place to review the vanilla CAS design.

JBoss (not Tomcat)

Yale Production Services runs Java applications under JBoss, while JASIG releases CAS for the simpler Tomcat environment.

The J2EE standard requires each application server to provide a set of standard services. Implicitly, this means that there are certain JAR files that define the standard APIs (Servlet, JPA, JSF, ...) that the application server has to provide for the running WAR. So generally there are some JAR files that the server uses for itself, some JAR files it shares with the WAR, and then the WAR itself contains private JAR files in its WEB-INF/lib directory. Tomcat and JBoss 4 or 5 solves this problem by creating separate directories of JAR files that are internal for the server or shared with the application. Unfortunately, JBoss has a lot more shared JAR files and they conflict with the JAR files that JASIG packages in the WAR it builds to run under the less extensive Tomcat environment. This created a lot of work reconciling the Maven Dependencies in CAS to match the JBoss 5 shared libraries.

JBoss EAP 6 has a modular system of libraries similar to OSGi. Each JAR file used by the system and potentially shared with applications is stored in a separate "module" directory. An application can access any of these libraries, but unlike the previous versions of JBoss it does not get a whole mass of JAR files automatically. The WAR has to include a JBoss specific configuration file naming the modules that it wants to access. By default, a WAR file will mostly use the JAR files in its WEB-INF/lib configuration and if JBoss internally uses a different version of the same JAR file, there is no conflict.

However, the J2EE standard still requires JBoss or any other application server to automatically provide the Servlet API, JPA, and other API defining JAR files automatically to every WAR. This potentially poses a problem if there is a JAR file that JBoss has decided is part of the standard API but CAS needs to use its own local copy. We discover that the logging libraries (SLF4J and log4j) fall in this category. JBoss EAP 6 will share its logging libraries and will screw up CAS logging unless you include a WEB-INF/jboss-deployment-structure.xml file that tells JBoss to exclude the org.apache.log4j, org.slf4j, and org.slf4j.impl modules from the default shared module list. If you don't do this, then the WEB-INF/classes/log4j.xml file is read and it creates the log files, but you don't get any log messages in them (or anywhere else for that matter).

Yale has a Netid System (not just AD)

CAS added a password expiration mechanism that works well with Active Directory. While Yale uses AD to authenticate passwords, the Netid system that creates, activates, and changes passwords for Netids is driven by databases external to AD. In the past Yale has had its own custom version of Password Expiration logic, although in the future we may attempt to change AD enough to allow us to use the standard JASIG CAS moduleTomcat only supports the basic Servlet functions while JBoss supports the entire suite of J2EE standard services. When an application decides to use a standard API that is part of J2EE but is not installed in Tomcat, they have to include their own copy of the support library. For example, CAS has JPA support for the Ticket Cache. JPA is built into JBoss and every other J2EE server, but to get that service in Tomcat you have to include a copy of Hibernate (or another JPA implementation) in your WAR.

JBoss complains when CAS attempts to include in WEB-INF/lib:

  • Libraries that should not be explicitly included because they are now part of Java (XML libraries like xalan and xerces).
  • Libraries that duplicate function that is built into JBoss and any other full J2EE server (hibernate, JAXB).

So any library that causes trouble is excluded during the Yale Maven project that builds the final WAR. In addition, the JASIG code add a persistence.xml file in WEB-INF/classes that causes trouble for JBoss, so it is removed.

JBoss provides a number of optional libraries that are not part of the J2EE standard but are commonly used by applications. The biggest problem is JAR files associated with any of the dozen or so "logging" frameworks (log4j, slf4j, commons logging) which all come in JBoss along with its own JBoss logging. JBoss by default tends to replace your logging jar file with its own, but that doesn't generally work. The only way to reliably get all the logs you want is to include a WEB-INF/jboss-deployment-structure.xml with exclude statements for all the log related JAR files. Then logging for CAS is controlled by the CAS log4j.xml file and not the JBoss logging configuration.

Maven Dependency MisManagement

After all the processing, we occasionally find two different versions of the same library included in WEB-INF/lib (example: commons.io Version 2.0 and 2.4). Probably one version was added by the vanilla CAS build process, and then a second version number was unintentionally added during the Yale customizations. You might spend a week of trial and error trying to figure out what you don't understand about Maven Dependency Management syntax, but there is a brute force solution to delete the libraries you don't want (and while you are at it the libraries that conflict with the JBoss enviroment) by specifying the undesirable JAR files in the "packagingExcludes" tag of the maven-war-plugin configuration in the Yale Webapp POM. Basically, this is a place to put in a last minute override and delete of files that you do not want copied to the final WAR artifact.

Project Structure

CAS is distributed as a top level "parent" Maven project containing subdirectories each of which is a Maven subproject that builds a component (a JAR or WAR file). The order in which the subprojects are processed is determined by the order of <module> statements in the parent. You build first files that other projects use, and later the projects that use them. Generally you build server-core first, then the optional JAR files. You have to create the WAR after you have built all the JAR files that go into it.

The parent project is supposed to provide global dependency management. That is, it should ensure that every project that uses commons.io uses Version 2.4 and that in the end it is Version 2.4 of the JAR file that ends up in the WAR and therefore is used at run time.

Unfortunately, the JASIG parent project also includes some "open source project legal boilerplat" steps that insure that copyleft statements are in order and that all the Gnu and Apache license statements for included libraries are properly disclosed. That is important for JASIG, but Yale does not need it for its internal modifications and releases and we don't have the expertise to conform to build NOTICE and other boilerplate files. We do, however, need the dependency management because we include code that uses commons.io and we also have to compile against Version 2.4 just like everyone else.

So the first step in adapting any new CAS release is to merge the previous Yale top level project POM with the new one from JASIG.

First, you have to remove the reference to a undistributed JASIG parent (that is a parent to your parent) that is referenced only by a URL and stored on a JASIG server. That drags in the legal boilerplate processing.

Then you have to make sure that you update the version numbers at the end to be the new version numbers distributed by JASIG. You should also make sure that any new dependency management entries have been added. If there are changes to the Maven plugin configurations, you have to read the manual and figure out if they are helpful or a problem.

Then you copy over the Yale custom projects (expired-password-change, preferences, CushyTicketRegistry, and the Yale-webapp or "cas-server-war" project). They become additional subdirectories.

Now for the big step. Yale does not want to make changes to JASIG project source, but sometimes there are problems that only show up when you run in JBoss, or bugs in code that supports features that Yale does not need or use, and we may make some custom changes to a JASIG project if it is important.

So the starting point would ideally be that we do not build local custom copies of any JASIG subproject but only build the new Yale custom projects. If that is going to be the case, then you would comment out all the JASIG <module> statements in the parent project (because we would then use vanilla JASIG JAR and template-WAR files from the JASIG repository) and you would add <module> files for each Yale project subdirectory you just copied over.

If you make a single change to any JASIG project, you have to give it a new Yale Version ID to distinguish it from the vanilla artifact, then change the version number of dependencies in other projects.

Note that the vanilla cas-server-webapp project creates a Template WAR file that is then modified by the Yale-webapp project which uses the recommended technique of WAR Overlay process. WAR Overlay can add new files and replace old files in a WAR, but it is not automatically able to change the version number of dependencies.

Therefore, suppose you start with a vanilla cas-server-core for release "3.5.2.1" (and all the JASIG modules are commented out). However, there is a bug and you have to change one line of code. You now have to create a Yale modified artifact with a new release ID. At this point Yale takes advantage of the fact that JASIG starts out at 3.*.* releases, so Yale can number its release 1.*.* without conflict. The corresponding Yale releases to 3.5 are Yale's 1.1, so you uncomment the <module> for server-core in the parent project, and in the server-core POM you change the release from 3.5.2.1 to 1.1.x-SNAPSHOT (whatever the current Yale version is for all the other Yale projects).

Bugfixes typically don't change method signatures, so it probably doesn't matter if a project compiling source to build a JAR uses 3.5.2.1 or 1.1.x-SNAPSHOT during the compile. However, the cas-server-webapp project Version 3.5.2.1 if it is not changed will have included the cas-server-core JAR Version 3.5.2.1 and that JAR will be in the WEB-INF/lib. So either you have to also modify the cas-server-webapp project to be Version 1.1.x-SNAPSHOT and to depend on the 1.1.x-SNAPSHOT Version of server-core, or else you are going to have to make the Yale-webapp WAR Overlay project both delete the 3.5.2.1 server-core JAR file (by adding it to the exclude list in the maven-war-plugin configuration in the POM) and then add the 1.1.x-SNAPSHOT JAR file (by changing the version number in the Dependency list).

Yale developed a package of optional CAS code under the brand name CUSHY. All the CUSHY code is in the cas-server-support-CusyTicketRegistry project, but while the entire project is built, currently CAS at Yale only uses one bean based on one class (CushyClusterConfiguration) and ignores the rest of the code. The CushyClusterConfiguration bean is added to the TicketRegistry XML Spring configuration file to configure Ehcache after discovering the current environment (sandbox, dev, test, prod). Echache needs a configuration file that lists all the other nodes in the cluster. Without this bean, we would need to keep a separate configuration file for each server and the Jenkins install job would have to install the correct file on each machine. Automatically configuring the file based on discovering the hostname of the current machine is much simpler.

Occasional Loss of "Flow" Variables during Login

...

Unfortunately, this fails sometimes at Yale. It is not clear if anyone else experiences this problem with CAS, and it may be due to some local network problem or the way we configure the network. However, frequently enough the Flow seems to get broken when the user hits Enter on the page where he submits the userid and password. The JASIG CAS code stores one important piece of data in a Webflow variable (the service= string) that has to be available after this form is submitted, and it has no logic that can recover if the Flow is broken by some network problem. It is relatively trivial to program around this, because the service string could just as easily be stored in a hidden field of the form as in a Flow variable. However, this is a modification of the JASIG code that makes CAS more robust against failure of the Webflow mechanism for whatever reason.

...

Statistics

There is a standard CAS has a large number of optional features, but they are all configured by the central administrator. There is no provision for the User to select optional CAS behavior. It seemed that this feature would be required in the future, so a "proof of concept" module has been created called CAS preferences. When the user goes to the /cas/preferences URL he is presented with a Form where he can select various check boxes and radio buttons. These choices are then written to a Cookie scoped to the CAS server and are available at the beginning of the login Webflow to influence the path through subsequent login sequences. This could be used to enable additional forms of authentication, special processing, or even additional logging to debug a problem someone else is having.

Statistics

There is a standard CAS page with basic server information, but Yale Production Services has a standard format expected for Opsview. So we used the requirement to add Yale specific monitoring datapage with basic server information, but Yale Production Services has a standard format expected for Opsview. So we used the requirement to add Yale specific monitoring data.

WAR Overlay

The standard CAS Build sequence first complies Core and all the optional JAR files and stores them in the Maven Repository. Then a vanilla cas-server-webapp project build an uncustomized CAS WAR file that contains all the standard HTML and XML files and includes all the previous modules and dependencies in the lib directory.

The last step uses a Maven technique called a WAR Overlay. This is a Maven project that contains only the Yale customization files. Replacement HTML and CSS files, replacement Spring XML configuration files, and the source of a small number of Java classes. This project depends on the vanilla WAR file built in the previous step. That WAR file provides a template for the final CAS WAR, but any files in the WAR Overlay project replace files with the same name in the vanilla template.

While large blocks of Yale Java code are in the the previously mentioned Yale JAR projects, the WAR Overlay contains all the Yale customizations. Thanks to the overlay mechanism, it does not have to duplicate any of the standard CAS files that require no changes. Every file in this project is Yale specific. Any CAS file that is not in the project is supplied by the vanilla CAS release and has not been modified by Yale.