CASifying OpenCms

Requirements

1. OpenCms is authenticated by CAS + LDAP.

2. OpenCms is authorized by CAS + LDAP.

3. Support OpenCms OU.

4. CAS will search  LDAP for groups and roles when validating, not authenticating.

5. Validation URI can be customised, not /serviceValidate only.

6. Easy to extend the module to support CAS + DATABASE.

Environments

Tested in Fedora 10, OpenJDK 1.6.0, Tomcat 5.5.27, OpenCms7.0.5, CAS3.3.1, OpenLDAP 2.4.12.

Login Procedure


Module Parameters

 Module parameters for authentication handler:

AuthenticationHandler: cn.langhua.opencms.ldap.cas.CmsCasAuthenticationHandler

AutoUserRoleName: not required. If you want the user can login OpenCms workplace by default, this parameter should be RoleWorkplaceUsers.

CasUrl: not required, default is https://localhost:8443/cas.

CasLoginUri: not required, the uri to CAS login, default is /login.

CasValidateUri: not required, the uri to CAS validate, default is /serviceValidate.

CasLenientURL: not required, if set, this url will be used to validate CAS ticket, default is null.

CasLogoutUri: not required, default is /logout.

 Module parameters for authorization handler:

AuthenticationHandler: cn.langhua.opencms.ldap.cas.CmsCasAuthorizationHandler

GroupSearchDN: required, the group dn to resolve OpenCms OU. If not set, will use BaseDN.

RoleSearchDN: required, the role dn to resolve OpenCms role. If not set, will use BaseDN.

BaseDN: not required.

AutoUserRoleName: not required. If you want the user can login OpenCms workplace by default, this parameter should be RoleWorkplaceUsers.

CasUrl: not required, default is https://localhost:8443/cas.

CasLoginUri: not required, the uri to CAS login, default is /login.

CasValidateUri: not required, the uri to CAS validate, default is /serviceValidate.

CasLenientURL: not required, if set, this url will be used to validate CAS ticket, default is null.

CasLogoutUri: not required, default is /logout.

Add a new validate servlet to CAS

Here I use /authzValidate as the new validate servlet uri for an example.

1. Add /authzValidate in $(cas_server)/WEB-INF/web.xml

<!-- start service validate extensions -->
	<servlet-mapping>
		<servlet-name>cas</servlet-name>
		<url-pattern>/authzValidate</url-pattern>
	</servlet-mapping>
	<!-- end service validate extensions -->

2. Modify $(cas_server)/WEB-INF/cas-servlet.xml

...
				<!-- start service validate extensions -->
				<prop
					key="/authzValidate">
					authzValidateController
				</prop>
				<!-- end service validate extensions -->
...
	<!-- start service validate extensions -->
	<bean id="authzValidateController" class="org.jasig.cas.web.ServiceValidateController"
		p:validationSpecificationClass="org.jasig.cas.validation.Cas20WithoutProxyingValidationSpecification"
		p:centralAuthenticationService-ref="centralAuthorizationService"
		p:proxyHandler-ref="proxy20Handler"
		p:argumentExtractor-ref="casArgumentExtractor" />
	<!-- end service validate extensions -->

3. Modify $(cas_server)/WEB-INF/spring-configuration/applicationContext.xml


<!-- CentralAuthorizationService, please note, this is authz -->
	<bean id="centralAuthorizationService" class="cn.langhua.cas.LDAPAuthzCASImpl"
		p:ticketGrantingTicketExpirationPolicy-ref="grantingTicketExpirationPolicy"
		p:serviceTicketExpirationPolicy-ref="serviceTicketExpirationPolicy"
		p:authenticationManager-ref="authenticationManager"
		p:ticketGrantingTicketUniqueTicketIdGenerator-ref="ticketGrantingTicketUniqueIdGenerator"
		p:ticketRegistry-ref="ticketRegistry"
            p:servicesManager-ref="servicesManager"
            p:persistentIdGenerator-ref="persistentIdGenerator"
		p:uniqueTicketIdGeneratorsForService-ref="uniqueIdGeneratorsMap"
		p:attributeRepository-ref="attributeRepository" />





4.Modify $(cas_server)/WEB-INF/deployerConfigContext.xml

<bean id="attributeRepository"
		class="cn.langhua.cas.services.persondir.support.ldap.LdapPersonAttributeDaoExtension">
		<property name="baseDN"
			value="o=langhua,c=cn" />
		<!-- This query is used to find the entry for populating attributes. {0} will be replaced by the new Principal ID extracted from the ldap-->
		<property name="query" value="(uid:caseExactmatch:={0})" />

		<property name="contextSource" ref="contextSource" />
		<property name="groupSearchDN" value="ou=users,ou=opencms,ou=applications,o=langhua,c=cn" />
		<property name="roleSearchDN" value="ou=roles,ou=opencms,ou=applications,o=langhua,c=cn" />
		<property name="authorType" value="member" />

		<property name="ldapAttributesToPortalAttributes">
			<map>
				<!-- Mapping beetween LDAP entry's attributes (key) and Principal"s (value) -->
				<entry key="groups" value="groups" />
				<entry key="roles" value="roles" />
				<entry key="uid" value="uid" />
				<entry value="title" key="title"/>
				<entry key="cn" value="Name"/>
				<entry key="sn" value="sn"/>
			</map>
		</property>
	</bean>

5. Modify $(cas_server)/WEB-INF/view/jsp/protocol/2.0/casServiceValidationSuccess.jsp

<%@ page session="false" pageEncoding="UTF-8"
%><%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"
%><%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn"
%><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="${fn:length(assertion.chainedAuthentications[fn:length(assertion.chainedAuthentications)-1].principal.attributes) > 0}">			<cas:attributes>
<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)}" step="1">
				<cas:attribute>
					<cas:name>${fn:escapeXml(attr.key)}</cas:name>
					<cas:value>${fn:escapeXml(attr.value)}</cas:value>
				</cas:attribute>
</c:forEach>				</cas:attributes>
</c:if>
<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:authenticationSuccess>
</cas:serviceResponse>

6. Sample of /authzValidate response

<cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>
	<cas:authenticationSuccess>
		<cas:user>shijh</cas:user>
			<cas:attributes>

				<cas:attribute>
					<cas:name>uid</cas:name>
					<cas:value>shijh</cas:value>
				</cas:attribute>

				<cas:attribute>
					<cas:name>Name</cas:name>
					<cas:value>石京海</cas:value>
				</cas:attribute>

				<cas:attribute>
					<cas:name>sn</cas:name>
					<cas:value>石京海</cas:value>
				</cas:attribute>

				<cas:attribute>
					<cas:name>title</cas:name>
					<cas:value>manager</cas:value>
				</cas:attribute>

				<cas:attribute>
					<cas:name>roles</cas:name>
					<cas:value>cn=RoleRootAdmins,ou=opencms,ou=roles,ou=applications,o=langhua,c=cn</cas:value>
				</cas:attribute>

				<cas:attribute>
					<cas:name>distinguishedName</cas:name>
					<cas:value>uid=shijh,ou=beijing,o=langhua,c=cn</cas:value>
				</cas:attribute>

				<cas:attribute>
					<cas:name>groups</cas:name>
					<cas:value>cn=Administrators,ou=opencms,ou=groups,ou=applications,o=langhua,c=cn</cas:value>
				</cas:attribute>
				</cas:attributes>

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

How to get the module and the source code

The source code of cn.langhua.cas is here.

Source code of OpenCms-LDAP module:

SVN:

http://www.langhua.cn/langhua/modules/ldap/

Username:anon

Password:anon

ViewVC:

http://www.langhua.cn/viewvc/svn/modules/ldap/

Shi Yusen/Beijing Langhua Ltd.

http://langhua.org/
http://langhua.biz/
http://www.langhua.cn/