Improving LifeRay 6 CAS integration

Lately, I had the dubious pleasure of integrating CAS with LifeRay (the results of which can be seen in my previous posts). Unfortunately, LifeRay assumes that both CAS and LifeRay are connected to the same user store (LDAP server or any similar security store), and thus no user import is necessary. But, as CAS has a much wider range of supported user stores – this is not always the case.
I needed to address this issue, meaning – allow users to login through CAS, even if they are not LifeRay users.

Concept

I replaced LifeRay CAS filter, and made sure that the AttributePrincipal object arriving from CAS client is stored at the HTTPSession.
Then, I replaced LifeRay auto-login class, and used LifeRay API to create a user if a user has logged in but did not exist in the internal LifeRay user database.

July-17, 2013 – Since I got many comments on this topic, I decided to open source the code mentioned here. Please see https://github.com/liranzel/liferay-cas-no-ldap/ for details.

The How

Here’s what I did:

  1. Configure LifeRay for CAS (see my previous post – http://tonaconsulting.com/configuring-liferay-and-cas-to-work-with-ldap/, but DON’T configure the LifeRay for LDAP
  2. Create a new Java project.
  3. As I use Maven, I used the following pom.xml file:
    <?xml version="1.0"?>
    <project
    	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
    	xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    	<modelVersion>4.0.0</modelVersion>
    	<groupId>com.tona.liferay</groupId>
    	<artifactId>Authenticator</artifactId>
    	<version>1.0-SNAPSHOT</version>
    	<packaging>jar</packaging>
    	<name>Authenticator</name>
    	<dependencies>
    
    		<dependency>
    			<groupId>org.slf4j</groupId>
    			<artifactId>slf4j-api</artifactId>
    			<version>1.6.6</version>
    		</dependency>
    
    		<dependency>
    			<groupId>javax.portlet</groupId>
    			<artifactId>portlet-api</artifactId>
    			<version>2.0</version>
    		</dependency>
    
    		<dependency>
    			<groupId>junit</groupId>
    			<artifactId>junit</artifactId>
    			<version>3.8.1</version>
    			<scope>test</scope>
    		</dependency>
    
    		<dependency>
    			<groupId>org.jasig.cas.client</groupId>
    			<artifactId>cas-client-core</artifactId>
    			<version>3.2.1</version>
    		</dependency>
    
    		<dependency>
    			<groupId>log4j</groupId>
    			<artifactId>log4j</artifactId>
    			<version>1.2.14</version>
    		</dependency>
    
    		<dependency>
    			<groupId>com.liferay.portal</groupId>
    			<artifactId>portal-client</artifactId>
    			<version>6.0.4</version>
    		</dependency>
    		<dependency>
    			<groupId>com.liferay.portal</groupId>
    			<artifactId>portal-impl</artifactId>
    			<version>6.0.4</version>
    			<scope>provided</scope>
    		</dependency>
    		<dependency>
    			<groupId>com.liferay.portal</groupId>
    			<artifactId>portal-service</artifactId>
    			<version>6.0.4</version>
    			<scope>provided</scope>
    		</dependency>
    		<dependency>
    			<groupId>com.liferay.portal</groupId>
    			<artifactId>util-java</artifactId>
    			<version>6.0.4</version>
    		</dependency>
    
    		<dependency>
    			<groupId>com.liferay.portal</groupId>
    			<artifactId>util-bridges</artifactId>
    			<version>6.0.4</version>
    			<scope>provided</scope>
    		</dependency>
    
    	</dependencies>
    </project>
    
    
  4. I create a new class, called TonaCASFilter, that derives from CASFilter. Note that I had to copy some code from the parent class, as it was not easily extensible ūüôĀ
    public class TonaCasFilter extends CASFilter {
    
    	public static String LOGIN = CASFilter.class.getName() + "LOGIN";
    
    	public static void reload(long companyId) {
    		_ticketValidators.remove(companyId);
    	}
    
    	protected Log getLog() {
    		return _log;
    	}
    
    	protected TicketValidator getTicketValidator(long companyId)
    		throws Exception {
    
    		TicketValidator ticketValidator = _ticketValidators.get(companyId);
    
    		if (ticketValidator != null) {
    			return ticketValidator;
    		}
    
    		String serverName = PrefsPropsUtil.getString(
    			companyId, PropsKeys.CAS_SERVER_NAME, PropsValues.CAS_SERVER_NAME);
    		String serverUrl = PrefsPropsUtil.getString(
    			companyId, PropsKeys.CAS_SERVER_URL, PropsValues.CAS_SERVER_URL);
    		String loginUrl = PrefsPropsUtil.getString(
    			companyId, PropsKeys.CAS_LOGIN_URL, PropsValues.CAS_LOGIN_URL);
    
    		Saml11TicketValidator cas20ProxyTicketValidator = new Saml11TicketValidator(serverUrl);
    		
    		Map<String, String> parameters = new HashMap<String, String>();
    
    		parameters.put("serverName", serverName);
    		parameters.put("casServerUrlPrefix", serverUrl);
    		parameters.put("casServerLoginUrl", loginUrl);
    		parameters.put("redirectAfterValidation", "false");
    
    		cas20ProxyTicketValidator.setCustomParameters(parameters);
    
    		_ticketValidators.put(companyId, cas20ProxyTicketValidator);
    
    		return cas20ProxyTicketValidator;
    	}
    
    	protected void processFilter(
    			HttpServletRequest request, HttpServletResponse response,
    			FilterChain filterChain)
    		throws Exception {
    
    		long companyId = PortalUtil.getCompanyId(request);
    
    		if (PrefsPropsUtil.getBoolean(
    				companyId, PropsKeys.CAS_AUTH_ENABLED,
    				PropsValues.CAS_AUTH_ENABLED)) {
    
    			HttpSession session = request.getSession();
    
    			String pathInfo = request.getPathInfo();
    
    			if (pathInfo.indexOf("/portal/logout") != -1) {
    				session.invalidate();
    
    				String logoutUrl = PrefsPropsUtil.getString(
    					companyId, PropsKeys.CAS_LOGOUT_URL,
    					PropsValues.CAS_LOGOUT_URL);
    
    				response.sendRedirect(logoutUrl);
    
    				return;
    			}
    			else {
    				String login = (String)session.getAttribute(LOGIN);
    
    				String serverName = PrefsPropsUtil.getString(
    					companyId, PropsKeys.CAS_SERVER_NAME,
    					PropsValues.CAS_SERVER_NAME);
    
    				String serviceUrl = PrefsPropsUtil.getString(
    					companyId, PropsKeys.CAS_SERVICE_URL,
    					PropsValues.CAS_SERVICE_URL);
    
    				if (Validator.isNull(serviceUrl)) {
    					serviceUrl = CommonUtils.constructServiceUrl(
    						request, response, serviceUrl, serverName, "ticket",
    						false);
    				}
    
    				String ticket = ParamUtil.getString(request, "ticket");
    
    				if (Validator.isNull(ticket)) {
    					if (Validator.isNotNull(login)) {
    						processFilter(
    								TonaCasFilter.class, request, response, filterChain);
    					}
    					else {
    						String loginUrl = PrefsPropsUtil.getString(
    							companyId, PropsKeys.CAS_LOGIN_URL,
    							PropsValues.CAS_LOGIN_URL);
    
    						loginUrl = HttpUtil.addParameter(
    							loginUrl, "service", serviceUrl);
    
    						response.sendRedirect(loginUrl);
    					}
    
    					return;
    				}
    
    				TicketValidator ticketValidator = getTicketValidator(
    					companyId);
    
    				Assertion assertion = ticketValidator.validate(
    					ticket, serviceUrl);
    
    				if (assertion != null) {
    					AttributePrincipal attributePrincipal =
    						assertion.getPrincipal();
    
    					login = attributePrincipal.getName();
    
    					session.setAttribute(LOGIN, login);
    					session.setAttribute("principal", attributePrincipal);
    				}
    			}
    		}
    
    		processFilter(TonaCasFilter.class, request, response, filterChain);
    	}
    
    	private static Log _log = LogFactoryUtil.getLog(TonaCasFilter.class);
    
    	private static Map<Long, TicketValidator> _ticketValidators =
    		new ConcurrentHashMap<Long, TicketValidator>();
    
    }
    
  5. I then create the new auto-login class. Again – as it was not very extendible, I had to copy-paste allot of code from the parent class…
    public class TonaCASAutoLogin extends CASAutoLogin {
    	private Logger logger = LoggerFactory.getLogger(TonaCASAutoLogin.class.getName());
    
    	@Override
    	public String[] login(HttpServletRequest request, HttpServletResponse response) {
    		String[] credentials = null;
    
    		try {
    			long companyId = PortalUtil.getCompanyId(request);
    
    			if (!PrefsPropsUtil.getBoolean(companyId, PropsKeys.CAS_AUTH_ENABLED, PropsValues.CAS_AUTH_ENABLED)) {
    
    				return credentials;
    			}
    
    			HttpSession session = request.getSession();
    
    			String login = (String) session.getAttribute(CASFilter.LOGIN);
    
    			if (Validator.isNull(login)) {
    				return credentials;
    			}
    
    			AttributePrincipal principal = (AttributePrincipal) session.getAttribute("principal");
    			if (principal != null) {
    
    				Map<String, Object> attrs = principal.getAttributes();
    
    				Configuration.getInstance().load();
    				
    				Object groupMembership = attrs.get(Configuration.getInstance().getMemberOfProperty());
    
    				if (groupMembership != null) {
    					com.liferay.portal.service.ServiceContext context = new com.liferay.portal.service.ServiceContext();
    
    					User user = null;
    					
    					String email = attrs.get("email").toString();
    					String lastName = attrs.get("lastName").toString();
    					String firstName = attrs.get("firstName").toString();
    
    					try {
    						user = UserLocalServiceUtil.getUserByScreenName(companyId, login);
    					} catch (NoSuchUserException nsue) {
    						// User not found.
    					}
    
    					// The groups the user needs to belong to
    					long[] mapToGroupsArray = getUserGroups(companyId, groupMembership.toString());
    					
    					// The community we want to map the user to
    					long groupId = 10131;
    
    
    					// User not found - create it.
    					if (user == null) {
    						try {
    							UserLocalServiceUtil.addUser(0, companyId, false, "not-used", "not-used", false,
    									fixScreenName(login), email, 0, "", Locale.getDefault(), firstName, "", lastName,
    									0, 0, true, 1, 1, 1970, null, new long[] {groupId}, null, null, mapToGroupsArray, false, context);
    
    						} catch (Exception e) {
    							logger.error("Can't add user", e);
    						}
    					} else {
    						// User exists - remap groups
    						UserGroupLocalServiceUtil.setUserUserGroups(user.getUserId(), mapToGroupsArray);
    						
    						// Ensure user has the right community
    						
    						UserLocalServiceUtil.addGroupUsers(groupId, new long[] { user.getUserId()});
    					}
    				} 
    			} 
    
    			return super.login(request, response);
    
    		} catch (Throwable e) {
    			logger.error("Can't auto-login, reverting to default behavior", e);
    		}
    
    		return super.login(request, response);
    	}
    
    	private String fixScreenName(String loginName) {
    		
    		String name = loginName;
    		
    		if (name.contains("@")) {
    			name = name.substring(0,name.indexOf("@"));
    		}
    
    		return name;
    	}
    
    	private long[] getUserGroups(long companyId, String groupMembership) throws Exception {
    		String[] groups = groupMembership.toString().split(";");
    
    		List<Long> mapToGroups = new ArrayList<Long>();
    
    		for (String group : groups) {
    			if (group.contains("[")) {
    				group = group.replace('[', ' ');
    				group = group.replace(']', ' ');
    				group = group.trim();
    			}
    			String groupName = group;
    
    			if (groupName != null) {
    				UserGroup liferayGroup = UserGroupLocalServiceUtil.getUserGroup(companyId, groupName);
    				if (liferayGroup != null) {
    					logger.debug("Found user group " + liferayGroup.getUserGroupId());
    				mapToGroups.add(liferayGroup.getUserGroupId());
    				} else {
    					logger.debug("Liferay group " + groupName + " not found");
    				}
    			}
    		}
    
    		long[] mapToGroupsArray = new long[mapToGroups.size()];
    		int i = 0;
    		for (long l : mapToGroups) {
    			mapToGroupsArray[i] = l;
    			++i;
    		}
    		
    		return mapToGroupsArray;
    	}
    }
    

    Note that you must make sure CAS sends all the relevant properties in the return SAML response, and that the groups sent exist in LifeRay.

  6. Now, create a JAR file (mvn clean install), and copy the JAR file to TOMCAT_HOME/webapps/ROOT/WEB-INF/lib
  7. Edit the LifeRay web.xml file. It can be found in TOMCAT_HOME/webapps/ROOT/WEB-INF. Replace the line
    <filter-class>com.liferay.portal.servlet.filters.sso.cas.CASFilter</filter-class>
    

    with the following line:

    <filter-class>com.tona.security.TonaCasFilter</filter-class>
    
  8. Edit the LifeRay portal-ext.properties file. It can be found in TOMCAT_HOME/webapps/ROOT/WEB-INF/classes. Add the following line:
    auto.login.hooks=com.tona.security.TonaCASAutoLogin
    
  9. Restart LifeRay. All should work…

Configuring LifeRay and CAS to work with LDAP

I saw many tutorials on CAS, Liferay and LDAP – but unfortunetly, none of them worked for me. So I decided to document what does work (at least for me).
Note that my environment is based on LifeRay 6.0.5 and CAS 3.5.1.

  1. Configure Tomcat for SSL. I have used port 443. You can read all about it here
    1. After creating the certificates, I just ended up with adding the following tag in TOMCAT_HOME/conf/server.xml
    2. <Connector
                 port="443" maxThreads="200"
                 scheme="https" secure="true" SSLEnabled="true"
                 keystoreFile="/root/.keystore" keystorePass="password"
                 clientAuth="false" sslProtocol="TLS"/>  
      
    3. IMPORTANT I did not manage to make CAS work with a self signed certificate, so I’ve used a temporary free one.
  2. Configure LifeRay for LDAP
    1. Login to LifeRay
    2. Go to the Control Panel–>Settings–>Authentication–>LDAP
    3. Ensure the “Enabled” check box is selected
    4. I strongly suggest enabling the “Import” checkbox and ensure Import is enabled for server startup.
    5. Add a server
    6. Fill in the LDAP server details (it’s easy to check them with an LDAP browser like jxplorer)
    7. Save your configuration
    8. I usually restart Tomcat after that change, and view the log to see all users were successfully imported
  3. Build CAS
    1. Download CAS (I downloaded it from here)
    2. Unzip the file
    3. Edit the CAS_HOME/cas-server-webapp/pom.xml file and add the following:
    4. <dependency>
           <groupId>org.jasig.cas</groupId>
           <artifactId>cas-server-support-ldap</artifactId>
           <version>3.5.1</version>
      </dependency>
      
    5. Build CAS using maven. The command to run is mvn clean install
  4. Deploy CAS
    1. Copy the newly created WAR file from CAS_HOME/cas-server-webapp/target/cas.war to TOMCAT_HOME/webapps
  5. Configure CAS for LDAP
    1. Edit the TOMCAT_HOME/webapps/cas/WEB-INF/deployerConfigContext.xml
    2. Add the following at the end of the file (just before the /beans tag)
    3. <bean id="contextSource" class="org.springframework.ldap.core.support.LdapContextSource">
        <!-- DO NOT enable JNDI pooling for context sources that perform LDAP bind operations. -->
        <property name="pooled" value="false"/>
      
        <!--
          Although multiple URLs may defined, it's strongly recommended to avoid this configuration
          since the implementation attempts hosts in sequence and requires a connection timeout
          prior to attempting the next host, which incurs unacceptable latency on node failure.
          A proper HA setup for LDAP directories should use a single virtual host that maps to multiple
          real hosts using a hardware load balancer.
        -->
        <property name="url" value="ldap://LDAP_SERVER:389" />
      
        <!--
          Manager credentials are only required if your directory does not support anonymous searches.
          Never provide these credentials for FastBindLdapAuthenticationHandler since the user's
          credentials are used for the bind operation.
        -->
        <property name="userDn" value="cn=Manager"/>
        <property name="password" value="test"/>
      
        <!-- Place JNDI environment properties here. -->
        <property name="baseEnvironmentProperties">
          <map>
            <!-- Three seconds is an eternity to users. -->
            <entry key="com.sun.jndi.ldap.connect.timeout" value="3000" />
            <entry key="com.sun.jndi.ldap.read.timeout" value="3000" />
      
            <!-- Explained at http://download.oracle.com/javase/1.3/docs/api/javax/naming/Context.html#SECURITY_AUTHENTICATION -->
            <entry key="java.naming.security.authentication" value="simple" />
          </map>
        </property>
      </bean>
      
    4. Add the following under the list tag of the authenticationHandlers tag
    5.         <bean class="org.jasig.cas.adaptors.ldap.BindLdapAuthenticationHandler"
        p:filter="mail=%u"
        p:searchBase="ou=people,dc=test,dc=com"
        p:contextSource-ref="contextSource" />
                              </list>
                      </property>
              </bean>
      
      
  6. Configure LifeRay for CAS
    1. Login to LifeRay
    2. Go to the Control Panel–>Settings–>Authentication–>CAS
    3. Ensure the “Enabled” check box is selected
    4. Ensure the “LDAP Import” check box is selected
    5. Enter the URLs of the CAS server
    6. Save
    7. Add the following line to TOMCAT_HOME/webapps/ROOT/WEB-INF/classes/system-ext.properties
    8. com.liferay.filters.sso.cas.CASFilter=true
      
    9. Add the following line to TOMCAT_HOME/webapps/ROOT/WEB-INF/classes/portal-ext.properties
    10. auto.login.hooks=com.liferay.portal.security.auth.CASAutoLogin
      
    11. Restart Tomcat

You can now access your LifeRay instance, and get the CAS login instead…

Using Liferay SSO with Oracle Access Manager 11g

Well, I was given a challenging task – using Oracle Access Manager 11g as an SSO provider for Liferay 6.0 (the community edition…).
Now, as you might know – there is no built-in OAM support for Liferay – so I was stuck with configuring one myself. Since I didn’t even have the OAM installed – I’ll detail all the steps I did. To simplify matters – I installed OAM on Microsoft Windows Server, but the same should hold for Linux.

All Oracle downloads were downloaded from edelivery.oracle.com. Version is 11.1.0.6.
 

OAM Installation

  1. Install Oracle database. I didn’t install Oracle XE, but rather the Enterprise edition.
  2. Alter the Oracle database.
    1. Open sqlplus as sys and run the following commands
    2. alter system set open_cursors=1000 scope=both;
      alter system set processes=1000 scope=SPFILE;
      
  3. Restart Oracle DB.
  4. Run RCU (V33643-01), and check the Identity Managent checkbox. Proceed with the installation.
  5. Install WebLogic Server (wls1036_generic)
  6. Install SOA Suite (ofm_soa_generic_11.1.1.6.0_disk1_1of2 and ofm_soa_generic_11.1.1.6.0_disk1_2of2).
  7. Install IdM (V33644-01_1of2 and V33644-01_2of2)
  8. From your ORACLE_HOME/IDM_HOME/common/bin run the config.cmd file.
  9. Install all the required components (especially all the Oracle Access Manager relevant components).
  10. DO NOT START THE ADMIN SERVER.
  11. Run the following WLST scripts (thank you Warren
  12. $MW_HOME/oracle_common/common/bin/wlst.cmd $ORACLE_HOME/common/tools/configureSecurityStore.py -d $IAM_DOMAIN_LOCATION -m create     -c IAM -p $ORA_PASS
    $MW_HOME/oracle_common/common/bin/wlst.cmd $ORACLE_HOME/common/tools/configureSecurityStore.py -d $IAM_DOMAIN_LOCATION -m validate
    
  13. Where
    1. $MW_HOME is where you put the Middleware home (e.g. ~/Oracle/Middleware)
      $ORACLE_HOME
    2. is the Oracle IAM home (e.g. ~/Oracle/Middleware/Oracle_IAM1)
      $IAM_DOMAIN_
    3. LOCATION is the domain home (e.g. ~/Oracle/Middleware/user_projects/domains/OAMDomain)
    4. $ORA_PASS is the password needed to talk to the database
  14. Now you can safely run the admin server. Connect to it using IP_ADDR:7001/em, and start the OAM managed server too.

Apache installation/configuration

On a separate machine (I used RedHat Linux 5.5):

  1. Install Apache2.2
  2. Configure WebGate (I used ZIP file oam_int_linux_v10_cd1.zip)
  3. Configure Apache to act as a proxy for your Liferay server by using ProxyPass and ProxyPassReverse. For instance:
  4. ProxyRequests Off
    ProxyPass /web http://LIFERAY_SERVER:8080/web
    ProxyPassReverse /web http://LIFERAY_SERVER:8080/web
    
  5. Configure WebGate in the Apache. On my machine the configuration looked like this:
  6. LoadModule obWebgateModule "/usr/local/webgate/product/access/oblix/apps/webgate/bin/webgate.so"
    
    LoadFile "/usr/local/webgate/libgcc_s.so.1"
    LoadFile "/usr/local/webgate/libstdc++.so.6"
    
            WebGateInstalldir "/usr/local/webgate/product/access"
            WebGateMode PEER
            #webgateload obWebgateModule "/usr/local/webgate/product/access/oblix/apps/webgate/bin/webgate.so"
    
    <Location /access/oblix/apps/webgate/bin/webgate.cgi>
            SetHandler obwebgateerr
    </Location>
    <Location "/oberr.cgi">
            SetHandler obwebgateerr
    </Location>
    <LocationMatch "/*">
            AuthType Oblix
            require valid-user
    </LocationMatch>
    

OAM Configuration

  1. Open the Access Manager console, and click on the “New OAM10g WebGate”
  2. Fill in the details, exactly as you did during the WebGate installation.
  3. Go to “Application Domains”, and select the newly created Application Policy
  4. Change any required value, and select “Authorization Policies”
  5. Select the “Protected Resource Policy”
  6. Select “Responses”
  7. Add a new response – HTTP Header with the name of LIFERAY_SCREEN_NAME and value of uid

Liferay Configuration

  1. Edit the portal-ext.properties file and add the following line:
  2. auto.login.hooks=com.liferay.portal.security.auth.RequestHeaderAutoLogin
    

Restart Apache, and browse to it. You should get the OAM login page, and after login – you should see you have automatically logged-in into Liferay…

Import users into Liferay

If you’re using Liferay, and not using LDAP, you will probably face into the same problem I did – how to import a large amount of users without manually adding them to the system.

So, loving automation, I’ve decided to create a simple Portlet that does just that.

  1. Create a new Dynamic Web App in Eclipse.
  2. Configure all necessary deployment files (liferay-portlet.xml, portlet.xml, web.xml etc)
  3. Create a new User class:
    package com.tona.liferay.web;public class User {
    private String firstName;
    private String lastName;
    private String email;
    private String phoneNo;
    private String screenName;
    private String password;
    
    public String getEmail() {
    return email;
    }
    
    public void setEmail(String email) {
    this.email = email;
    }
    
    public String getPhoneNo() {
    return phoneNo;
    }
    
    public void setPhoneNo(String phoneNo) {
    this.phoneNo = phoneNo;
    }
    
    public String getFirstName() {
    return firstName;
    }
    
    public void setFirstName(String firstName) {
    this.firstName = firstName;
    }
    
    public String getLastName() {
    return lastName;
    }
    
    public void setLastName(String lastName) {
    this.lastName = lastName;
    }
    
    public String getScreenName() {
    return screenName;
    }
    
    public void setScreenName(String screenName) {
    this.screenName = screenName;
    }
    
    public String getPassword() {
    return password;
    }
    
    public void setPassword(String password) {
    this.password = password;
    }
    
    public User(String line) {
    String[] tokens = line.split(",");
    setFirstName(tokens[1]);
    setLastName(tokens[2]);
    setEmail(tokens[3]);
    setPhoneNo(tokens[4]);
    String screenName = getFirstName() + getLastName().substring(0, 3);
    setScreenName(screenName.toLowerCase());
    setPassword(getScreenName() + "123");
    }
    
    public User() {
    
    }
    
    }
    
  4. Create the portlet itself:
    package com.tona.liferay.web;import java.io.BufferedReader;
    import java.io.FileReader;
    import java.io.IOException;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Locale;
    import java.util.logging.Level;
    import java.util.logging.Logger;
    
    import javax.portlet.ActionRequest;
    import javax.portlet.ActionResponse;
    import javax.portlet.PortletException;
    
    import com.liferay.portal.model.Company;
    import com.liferay.portal.service.CompanyLocalServiceUtil;
    import com.liferay.portal.service.UserLocalServiceUtil;
    import com.liferay.util.bridges.mvc.MVCPortlet;
    
    public class ImportUsersPortlet extends MVCPortlet {
    public void importUsers(ActionRequest actionRequest,
    ActionResponse actionResponse) throws IOException, PortletException {
    
    String fileName = actionRequest.getParameter("fileName");
    
    BufferedReader fr = new BufferedReader(new FileReader(fileName));
    
    Listusers = new ArrayList();
    
    String line;
    
    while ((line = fr.readLine()) != null) {
    users.add(new User(line));
    }
    
    // We now have the user list
    com.liferay.portal.service.ServiceContext context = new com.liferay.portal.service.ServiceContext();
    long companyId = 0;
    
    try {
    Company company = CompanyLocalServiceUtil.getCompanies().get(0);
    companyId = company.getCompanyId();
    for (User user : users) {
    try {
    UserLocalServiceUtil.addUser(0, companyId, false,
    user.getPassword(), user.getPassword(), false, user.getScreenName(),
    user.getEmail(), 0, "", Locale.getDefault(),
    user.getFirstName(), "", user.getLastName(), 0, 0,
    true, 1, 1, 1970, null, null, null, null, null, false,
    context);
    } catch (Exception e) {
    e.printStackTrace();
    }
    }
    } catch (Exception e) {
    e.printStackTrace();
    }
    
    }
    }
    
  5. Create a WAR file and deploy it in Liferay
  6. Note that the portlet does not upload the CSV file – and expects it to exist on the Liferay server itself
  7. You can of course change the algorithm of the screen-name and password creation, by changing the User constructor method.

Improving authorization performance in Liferay

I have recently run a benchmark on a Liferay 6.1 portal. Most of the bottlenecks were the result of the portlets themselves, but a major point was the authorization mechanism of Liferay.
The way Liferay authorization works is that for every component on the screen an authorization check is made. Now, this makes sense. But the problem is that the default implementation is a bit naive – it goes to the database to check if the user has the correct authorization. And since rights can be inherited – each check goes multiple times to the database.
So, I set out to solve this irritating issue by using a cache. Now – this cache is naive too – if a user was granted a new role – a server restart is needed. But this implementation is easy to extend.

  1. Create a new class, that extends com.liferay.portal.security.permission.AdvancedPermissionChecker
  2. Open the portal-ext.properties file.
  3. Add the property permissions.checker={your class name} to it
  4. The class code should look like this:
    package com.tona.liferay.permission;
    
    import java.util.HashMap;
    import java.util.Map;
    
    public class TonaPermissionChecker extends
    		com.liferay.portal.security.permission.AdvancedPermissionChecker {
    
    	private static Map<PermissionKey, Boolean> permissionCache = new HashMap<PermissionKey, Boolean>();
    
    	public boolean hasPermission(long groupId, String name, String primKey,
    			String actionId) {
    
    		PermissionKey key = new PermissionKey(groupId, name, primKey, actionId);
    		if (permissionCache.containsKey(key)) {
    			return permissionCache.get(key);
    		}
    
    		boolean result = super.hasPermission(groupId, name, primKey, actionId);
    		permissionCache.put(key, result);
    
    		return result;
    
    	}
    
    	private class PermissionKey {
    		private long groupId;
    		private String name;
    		private String primKey;
    		private String actionId;
    
    		public PermissionKey(long groupId, String name, String primKey,
    				String actionId) {
    			super();
    			this.groupId = groupId;
    			this.name = name;
    			this.primKey = primKey;
    			this.actionId = actionId;
    		}
    
    		public long getGroupId() {
    			return groupId;
    		}
    
    		public void setGroupId(long groupId) {
    			this.groupId = groupId;
    		}
    
    		public String getName() {
    			return name;
    		}
    
    		public void setName(String name) {
    			this.name = name;
    		}
    
    		public String getPrimKey() {
    			return primKey;
    		}
    
    		public void setPrimKey(String primKey) {
    			this.primKey = primKey;
    		}
    
    		public String getActionId() {
    			return actionId;
    		}
    
    		public void setActionId(String actionId) {
    			this.actionId = actionId;
    		}
    
    		@Override
    		public int hashCode() {
    			final int prime = 31;
    			int result = 1;
    			result = prime * result
    					+ ((actionId == null) ? 0 : actionId.hashCode());
    			result = prime * result + (int) (groupId ^ (groupId >>> 32));
    			result = prime * result + ((name == null) ? 0 : name.hashCode());
    			result = prime * result
    					+ ((primKey == null) ? 0 : primKey.hashCode());
    			return result;
    		}
    
    		@Override
    		public boolean equals(Object obj) {
    			if (this == obj)
    				return true;
    			if (obj == null)
    				return false;
    			if (getClass() != obj.getClass())
    				return false;
    			PermissionKey other = (PermissionKey) obj;
    			if (actionId == null) {
    				if (other.actionId != null)
    					return false;
    			} else if (!actionId.equals(other.actionId))
    				return false;
    			if (groupId != other.groupId)
    				return false;
    			if (name == null) {
    				if (other.name != null)
    					return false;
    			} else if (!name.equals(other.name))
    				return false;
    			if (primKey == null) {
    				if (other.primKey != null)
    					return false;
    			} else if (!primKey.equals(other.primKey))
    				return false;
    			return true;
    		}
    	}
    }
    
  5. Package the class in a JAR file, and put in TOMCAT_HOME/webapps/ROOT/WEB-INF/lib