« Back to Single Sign-on

CAS and LDAP in Liferay 5.2

Introduction #

This page details how to use CAS/LDAP integration in Liferay 5.2.2. In this document, changes were made to the Liferay code to make it work. This document does not apply for Liferay 6.0+.

Example Environment #

  • Liferay 5.2.2
  • Tomcat 6
  • Apache DS 1.5.4
  • CAS 3.3.1

This article assumes that you've got CAS and LDAP configured correctly. If you need some help with this there are some good Wiki articles, as well as the Portal Administrator's Guide.

Authentication Settings #

General

  • How do users authenticate: By Email Address

LDAP

  • Enabled: true
  • Required: true
  • Authentication Search Filter: (mail=@email_address@)

LDAP Mappings:

  • Sscreen Name: cn
  • Password: userPassword
  • Email Address: mail
  • First Nam: givenName
  • Last Name: sn
  • Job Title: title
  • Group: groupMembership

CAS settings:

  • Enabled: true
  • Import from LDAP: true

Problems to Overcome #

The problems encountered with getting LDAP and CAS up and running are:

Exporting to LDAP during LDAP import fails #

If a user exists in LDAP but not Liferay, when they first login Liferay tries to add them to the DB. During the user add, the LDAP export functionality kicks in. As the users Contact object hasn't been created yet, the export fails and things go pear shaped. See LEP-7360

Exporting to LDAP fails if the user doesn't have a Job Title #

When adding a new user that doesn't have a Job Title, the LDAP export fails on ApacheDS 1.5.4 as it doesn't like attributes with an empty string. Something about being syntactically incorrect.

Passwords exported to LDAP are plain text #

CAS authentication doesn't work #

CAS authentication doesn't work when authentication type is email address.

The final part of the CAS authentication protocol is for the Liferay server to validate the CAS 'ticket'. (Check out the CAS documentation or for a good overview see http://static.springsource.org/spring-security/site/reference/html/cas.html#cas-how-it-works). The response from the CAS server is the username used in the authentication process. Liferay assumes that this is the users screen name.

Logging out when using CAS does not log out of Liferay #

See LPS-2551

Fix #

To fix the problems, we make changes to the following files:

  • /com/liferay/portal/model/impl/UserImpl.java
  • /com/liferay/portal/security/ldap/LDAPUser.java
  • /com/liferay/portal/security/ldap/PortalLDAPUtil.java
  • /com/liferay/portal/security/auth/CASAutoLogin.java
  • /com/liferay/portal/servlet/filters/sso/cas/CASFilter.java

The patch for these files is attached

Problem 1#

When a user is being added the User model object is saved before its Contact model. If the user already exists in LDAP, the PortalLDAPUtil.exportToLDAP(User) function, called as part of the saving of the User object, calls user.getContact(). Because there is no contact a blank instance is returned. Therefore the LDAP attributes exported are all blank which causes LDAP exception.

The fix for this problem is to instead of creating the new Contact object via

contact = new ContactImpl();
call the Contact service via
contact =  ContactLocalServiceUtil.createContact(getContactId());
This will ensure that the isNew() function of the Contact object will return true

Then in PortalLDAPUtil.exportToLDAP(User) don't export the user if the contact is new

if (binding == null || user.getContact().isNew() ) {

Problem 2#

Problem 2 is caused by setting the job title attribute even if the User value is null.

This was fixed by checking for null before setting the LDAP attribute.

Also see LPS-3786

Problem 3#

Exporting of plain text passwords is fixed by the following bit of code

if (user.isPasswordModified() &&
	Validator.isNotNull(user.getPassword())) {

	String ldapPassword = "{" + PwdEncryptor.PASSWORDS_ENCRYPTION_ALGORITHM + "}" + user.getPassword(); 
	mods.addItem(
		userMappings.getProperty("password"),
		ldapPassword);
}

This will set the password in LDAP to be the same as the encrypted password stored in the Liferay DB.

Problem 4#

This fix is in the CASAutoLogin.java class. The fix was two fold. 1. Use the COMPANY_SECURITY_AUTH_TYPE property to determine how to find an existing user

	String authType = PrefsPropsUtil.getString(
	                           				companyId, PropsKeys.COMPANY_SECURITY_AUTH_TYPE,
	                           				PropsValues.COMPANY_SECURITY_AUTH_TYPE);
				
	if (authType.equals(CompanyConstants.AUTH_TYPE_SN)) {
		
		user = UserLocalServiceUtil.getUserByScreenName(
			companyId, screenName);
	}
	else {
		user = UserLocalServiceUtil.getUserByEmailAddress(companyId, screenName);
	}

2. When adding a user from LDAP the search filter now works for both screen name and email address

String filter = PortalLDAPUtil.getAuthSearchFilter(companyId, screenName, screenName, "");

This one line of code replaces about 15 lines of code.

Problem 5#

When CAS is enabled, logging out redirects to the CAS logout URL. The problem is that logging out of Liferay is not performed.

CAS has some functionality that, by default, is turned off. When turned on it allows the CAS logout URL to have a service parameter, just like the login URL. If the service parameter exists, CAS will redirect to the service once it has completed logging out.

To turn ON this feature you need to edit the WEB-INF/cas-servlet.xml file of your CAS deployment. Look for the logoutController definition and add the line

p:followServiceRedirects="true"

To get use this feature from Liferay we need to do two things

  • Add the service parameter to the CAS logout redirect
  • Process that CAS redirect to do the actual logout of Liferay.

Point one is handled by the changes to CASFilter Point two needed a new Struts action mapping that is different from the normal logout, i.e. /portal/logout, but runs the same struts action. To do this I added the following to struts-config.xml

<action path="/portal/caslogout" type="com.liferay.portal.action.LogoutAction" />

We also add /portal/caslogout to the portal property auth.public.paths

auth.public.paths=\
       ...
        /portal/extend_session_confirm,\
        /portal/json_service,\
        /portal/logout,\
+       /portal/caslogout,\
        /portal/open_id_request,\
        /portal/open_id_response,\
1 Attachment
56959 Views
Average (0 Votes)
The average rating is 0.0 stars out of 5.
Comments
Threaded Replies Author Date
When a user changes his/her password, there is... Stef Heyenrath May 18, 2009 9:15 AM
I'm facing with another problem, liferay... Nowhere Rosa September 24, 2009 12:05 AM
It seems that LifeRay SSO does not support CAS... John J November 25, 2009 4:02 AM
Tested with CAS3.2, same issue:-( Anyone can... John J November 25, 2009 4:24 AM
mine works with CAS 3.4 Van Hoai Pham December 8, 2009 2:52 AM
what is the solutino for this prolem, ... Auditya manikanta Vadrevu January 7, 2010 10:05 PM
hello, I am working with 5.2.3 and i have used... Mahmudur Rahman Manna August 2, 2010 12:13 PM
When you have liferay 5.2.2 and CAS configured... Eduardo Lopez Seijo August 25, 2010 4:10 AM

When a user changes his/her password, there is the following exception in the logging:

17:07:07,421 INFO 2009-05-18 17:07:07,411 [http-127.0.0.1-8080-2] ERROR portal-web.docroot.html.portal.render_portlet.jsp - com.liferay.portal.ModelListenerException: javax.naming.NameAlreadyBoundException: [LDAP: error code 68 - ENTRY_ALREADY_EXISTS: failed for Add Request :
ClientEntry
dn: 2.5.4.3=2000a,2.5.4.11=users,0.9.2342.19200300.100.1.25=example,0.9.2342.1920030­0.100.1.25=com
objectclass: top
objectclass: person
objectclass: inetOrgPerson
objectclass: organizationalPerson
mail: 2000a@x.nl
sn: 2000a
userpassword: '0x32 0x30 0x30 0x30 0x61 '
cn: 2000a
givenname: 2000a
: cn=2000a,ou=users,dc=example,dc=com already exists!]; remaining name 'cn=2000a,ou=users,dc=example,dc=com'
at com.liferay.portal.model.ContactListener.onAfterUpdate(ContactListener.java:56)
­at com.liferay.portal.service.persistence.ContactPersistenceImpl.update(ContactPers­istenceImpl.java:191)
at com.liferay.portal.service.impl.UserLocalServiceImpl.updateUser(UserLocalService­Impl.java:2331)
at com.liferay.portal.service.impl.UserServiceImpl.updateUser(UserServiceImpl.java:­602)
at com.liferay.portal.service.impl.UserServiceImpl.updateUser(UserServiceImpl.java:­633)
at com.liferay.portal.service.UserServiceUtil.updateUser(UserServiceUtil.java:387)
­at com.liferay.portlet.enterpriseadmin.action.EditUserAction.updateUser(EditUserAct­ion.java:449)
at com.liferay.portlet.enterpriseadmin.action.EditUserAction.processAction(EditUser­Action.java:118)
at com.liferay.portlet.myaccount.action.EditUserAction.processAction(EditUserAction­.java:58)
at com.liferay.portal.struts.PortletRequestProcessor.process(PortletRequestProcesso­r.java:180)
at com.liferay.portlet.StrutsPortlet.processAction(StrutsPortlet.java:197)
at com.sun.portal.portletcontainer.appengine.filter.FilterChainImpl.doFilter(Filter­ChainImpl.java:98)
at com.liferay.portal.kernel.portlet.PortletFilterUtil.doFilter(PortletFilterUtil.j­ava:57)
at com.liferay.portlet.InvokerPortletImpl.invoke(InvokerPortletImpl.java:630)
at com.liferay.portlet.InvokerPortletImpl.invokeAction(InvokerPortletImpl.java:662)­
at com.liferay.portlet.InvokerPortletImpl.processAction(InvokerPortletImpl.java:357­)
at com.liferay.portal.action.LayoutAction.processPortletRequest(LayoutAction.java:5­95)
at com.liferay.portal.action.LayoutAction.processLayout(LayoutAction.java:423)
at com.liferay.portal.action.LayoutAction.execute(LayoutAction.java:195)
at org.apache.struts.action.RequestProcessor.processActionPerform(RequestProcessor.­java:431)
Caused by: javax.naming.NameAlreadyBoundException: [LDAP: error code 68 - ENTRY_ALREADY_EXISTS: failed for Add Request :


What's wrong?
Posted on 5/18/09 9:15 AM.
I'm facing with another problem, liferay imports user from LDAP, but it does'n update password in DB when user log in successfully the first time. Without CAS it works fine. Is it a problem or expected behaviour? You can read more here: http://www.liferay.com/web/guest/community/forums/-/message_boards/message/40602­43

Any suggestion would be appreciated
Posted on 9/24/09 12:05 AM.
It seems that LifeRay SSO does not support CAS 3.4 so you have to try with another CAS version...
Posted on 11/25/09 4:02 AM in reply to Nowhere Rosa.
Tested with CAS3.2, same issue:-(

Anyone can help?


type Exception report

message

description The server encountered an internal error () that prevented it from fulfilling this request.

exception

javax.servlet.ServletException: edu.yale.its.tp.cas.client.CASAuthenticationException: Unable to validate ProxyTicketValidator [[edu.yale.its.tp.cas.client.ProxyTicketValidator proxyList= [edu.yale.its.tp.cas.client.ServiceTicketValidator casValidateUrl=[https://localhost:18009/cas-web/proxyValidate] ticket=[ST-1-bpKAQ4HRqzcvBbegNQI9-cas] service=[http%3A%2F%2Flocalhost%3A8080%2Fc%2Fportal%2Flogin] renew=false]]]
edu.yale.its.tp.cas.client.filter.CASFilter.doFilter(CASFilter.ja­va:381)
com.liferay.portal.servlet.filters.sso.cas.CASFilter.processFilter(CASFi­lter.java:141)
com.liferay.portal.kernel.servlet.BaseFilter.doFilter(BaseFilter.­java:92)
com.liferay.portal.kernel.servlet.BaseFilter.processFilter(BaseFilter.j­ava:165)
com.liferay.portal.sharepoint.SharepointFilter.processFilter(Sharepoint­Filter.java:192)
com.liferay.portal.kernel.servlet.BaseFilter.doFilter(BaseFilte­r.java:92)
com.liferay.portal.kernel.servlet.BaseFilter.processFilter(BaseFilter­.java:165)
com.liferay.portal.servlet.filters.virtualhost.VirtualHostFilter.proc­essFilter(VirtualHostFilter.java:189)
com.liferay.portal.kernel.servlet.BaseFilt­er.doFilter(BaseFilter.java:92)
com.liferay.portal.kernel.servlet.BaseFilter.pro­cessFilter(BaseFilter.java:165)
com.liferay.portal.servlet.filters.threadlocalca­che.ThreadLocalCacheFilter.processFilter(ThreadLocalCacheFilter.java:52)
com.lif­eray.portal.kernel.servlet.BaseFilter.doFilter(BaseFilter.java:92)
com.liferay.p­ortal.kernel.servlet.BaseFilter.processFilter(BaseFilter.java:165)
com.liferay.p­ortal.kernel.servlet.BaseFilter.doFilter(BaseFilter.java:95)
org.tuckey.web.filt­ers.urlrewrite.UrlRewriteFilter.doFilter(UrlRewriteFilter.java:738)


root cause

edu.yale.its.tp.cas.client.CASAuthenticationException: Unable to validate ProxyTicketValidator [[edu.yale.its.tp.cas.client.ProxyTicketValidator proxyList= [edu.yale.its.tp.cas.client.ServiceTicketValidator casValidateUrl=[https://localhost:18009/cas-web/proxyValidate] ticket=[ST-1-bpKAQ4HRqzcvBbegNQI9-cas] service=[http%3A%2F%2Flocalhost%3A8080%2Fc%2Fportal%2Flogin] renew=false]]]
edu.yale.its.tp.cas.client.CASReceipt.getReceipt(CASReceipt.java:­58)
edu.yale.its.tp.cas.client.filter.CASFilter.getAuthenticatedUser(CASFilter.j­ava:455)
edu.yale.its.tp.cas.client.filter.CASFilter.doFilter(CASFilter.java:378­)
com.liferay.portal.servlet.filters.sso.cas.CASFilter.processFilter(CASFilter.j­ava:141)
com.liferay.portal.kernel.servlet.BaseFilter.doFilter(BaseFilter.java:9­2)
com.liferay.portal.kernel.servlet.BaseFilter.processFilter(BaseFilter.java:16­5)
com.liferay.portal.sharepoint.SharepointFilter.processFilter(SharepointFilter­.java:192)
com.liferay.portal.kernel.servlet.BaseFilter.doFilter(BaseFilter.java­:92)
com.liferay.portal.kernel.servlet.BaseFilter.processFilter(BaseFilter.java:­165)
com.liferay.portal.servlet.filters.virtualhost.VirtualHostFilter.processFil­ter(VirtualHostFilter.java:189)
com.liferay.portal.kernel.servlet.BaseFilter.doF­ilter(BaseFilter.java:92)
com.liferay.portal.kernel.servlet.BaseFilter.processFi­lter(BaseFilter.java:165)
com.liferay.portal.servlet.filters.threadlocalcache.Th­readLocalCacheFilter.processFilter(ThreadLocalCacheFilter.java:52)
com.liferay.p­ortal.kernel.servlet.BaseFilter.doFilter(BaseFilter.java:92)
com.liferay.portal.­kernel.servlet.BaseFilter.processFilter(BaseFilter.java:165)
com.liferay.portal.­kernel.servlet.BaseFilter.doFilter(BaseFilter.java:95)
org.tuckey.web.filters.ur­lrewrite.UrlRewriteFilter.doFilter(UrlRewriteFilter.java:738)


root cause

java.net.SocketException: Unexpected end of file from server
sun.net.www.http.HttpClient.parseHTTPHeader(HttpClient.java:769)
sun.net.­www.http.HttpClient.parseHTTP(HttpClient.java:632)
sun.net.www.http.HttpClient.p­arseHTTPHeader(HttpClient.java:766)
sun.net.www.http.HttpClient.parseHTTP(HttpCl­ient.java:632)
sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpUR­LConnection.java:1049)
edu.yale.its.tp.cas.util.SecureURL.retrieve(SecureURL.jav­a:86)
edu.yale.its.tp.cas.client.ServiceTicketValidator.validate(ServiceTicketVa­lidator.java:212)
edu.yale.its.tp.cas.client.CASReceipt.getReceipt(CASReceipt.ja­va:56)
edu.yale.its.tp.cas.client.filter.CASFilter.getAuthenticatedUser(CASFilte­r.java:455)
edu.yale.its.tp.cas.client.filter.CASFilter.doFilter(CASFilter.java:­378)
com.liferay.portal.servlet.filters.sso.cas.CASFilter.processFilter(CASFilte­r.java:141)
com.liferay.portal.kernel.servlet.BaseFilter.doFilter(BaseFilter.jav­a:92)
com.liferay.portal.kernel.servlet.BaseFilter.processFilter(BaseFilter.java­:165)
com.liferay.portal.sharepoint.SharepointFilter.processFilter(SharepointFil­ter.java:192)
com.liferay.portal.kernel.servlet.BaseFilter.doFilter(BaseFilter.j­ava:92)
com.liferay.portal.kernel.servlet.BaseFilter.processFilter(BaseFilter.ja­va:165)
com.liferay.portal.servlet.filters.virtualhost.VirtualHostFilter.process­Filter(VirtualHostFilter.java:189)
com.liferay.portal.kernel.servlet.BaseFilter.­doFilter(BaseFilter.java:92)
com.liferay.portal.kernel.servlet.BaseFilter.proces­sFilter(BaseFilter.java:165)
com.liferay.portal.servlet.filters.threadlocalcache­.ThreadLocalCacheFilter.processFilter(ThreadLocalCacheFilter.java:52)
com.lifera­y.portal.kernel.servlet.BaseFilter.doFilter(BaseFilter.java:92)
com.liferay.port­al.kernel.servlet.BaseFilter.processFilter(BaseFilter.java:165)
com.liferay.port­al.kernel.servlet.BaseFilter.doFilter(BaseFilter.java:95)
org.tuckey.web.filters­.urlrewrite.UrlRewriteFilter.doFilter(UrlRewriteFilter.java:738)


note The full stack trace of the root cause is available in the Apache Tomcat/6.0.20 logs.
Posted on 11/25/09 4:24 AM in reply to John J.
mine works with CAS 3.4
Posted on 12/8/09 2:52 AM in reply to John J.
what is the solutino for this prolem,

15:53:11,784 ERROR [CASAutoLogin:172] Problem accessing LDAP server
2com.liferay.portal.NoSuchUserException: User thill99 was not found in the LDAP server
3 at com.liferay.portal.security.auth.CASAutoLogin.addUser(CASAutoLogin.java:167)
4............................................

more here,
http://www.liferay.com/web/guest/community/forums/-/message_boards/message/13806­76
Posted on 1/7/10 10:05 PM.
hello,
I am working with 5.2.3 and i have used your patch in ext-impl but classloader is always loading the old CASAutoLogin before but not the ext-impl one .

( http://www.liferay.com/web/guest/community/forums/-/message_boards/message/14114­12 ) in this thread Mika suggested not to use the same name, I have tried it with new Name e.g. CASAutoLoginExt and also changed the auto.login.hooks property accordingly but it gives exception ClassNotFound.

Please advise me if you dont have any such trouble or if you had then how you solved that.

Thanks in advance
Posted on 8/2/10 12:13 PM.
When you have liferay 5.2.2 and CAS configured without LDAP, does "Default password policy" works correctly??. I realized that Password expiration and Lockout rules don't work!
Posted on 8/25/10 4:10 AM.