Search code examples
javaliferayplaceholdercasjasig

Get my own properties in cas client and Liferay


I have a project with Liferay 5. The basic cas client used is old, and I needed to update it to cas client v3.2.1

So I update the jar on the server, and modify the web.xml of Liferay :

<filter>
        <filter-name>SSO CAS Filter</filter-name>
        <filter-class>org.jasig.cas.client.authentication.AuthenticationFilter</filter-class>
        <init-param>
            <param-name>casServerLoginUrl</param-name>
            <param-value>test</param-value>
        </init-param>
        <init-param>
            <param-name>serverName</param-name>
            <param-value>test</param-value>
        </init-param>
    </filter>

    <filter>
        <filter-name>CASValidationFilter</filter-name>
        <filter-class>org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter</filter-class>
        <init-param>
            <param-name>casServerUrlPrefix</param-name>
            <param-value>test</param-value>
        </init-param>
        <init-param>
            <param-name>serverName</param-name>
            <param-value>test</param-value>
        </init-param>
    </filter>

It works great.

But, I now want to have the param-value of this serverName and casServerLoginUrl be dynamically filled with some properties file or system property.

I tried to use :

${cas.login.url}

And put it in the portal-ext.properties file.

But the value is not replaced.

If I check in org.jasig.cas.client.authentication.AuthenticationFilter class, it seems the loading process is like this :

protected final String getPropertyFromInitParams(final FilterConfig filterConfig, final String propertyName, final String defaultValue)  {
        final String value = filterConfig.getInitParameter(propertyName);

        if (CommonUtils.isNotBlank(value)) {
            log.info("Property [" + propertyName + "] loaded from FilterConfig.getInitParameter with value [" + value + "]");
            return value;
        }

        final String value2 = filterConfig.getServletContext().getInitParameter(propertyName);

        if (CommonUtils.isNotBlank(value2)) {
            log.info("Property [" + propertyName + "] loaded from ServletContext.getInitParameter with value [" + value2 + "]");
            return value2;
        }
        InitialContext context;
        try {
         context = new InitialContext();
        } catch (final NamingException e) {
            log.warn(e,e);
            return defaultValue;
        }
        
        
        final String shortName = this.getClass().getName().substring(this.getClass().getName().lastIndexOf(".")+1);
        final String value3 = loadFromContext(context, "java:comp/env/cas/" + shortName + "/" + propertyName);
        
        if (CommonUtils.isNotBlank(value3)) {
            log.info("Property [" + propertyName + "] loaded from JNDI Filter Specific Property with value [" + value3 + "]");
            return value3;
        }
        
        final String value4 = loadFromContext(context, "java:comp/env/cas/" + propertyName); 
        
        if (CommonUtils.isNotBlank(value4)) {
            log.info("Property [" + propertyName + "] loaded from JNDI with value [" + value4 + "]");
            return value4;
        }

        log.info("Property [" + propertyName + "] not found.  Using default value [" + defaultValue + "]");
        return defaultValue;
    }

So, it should get ${cas.login.url} and Liferay, based on his portal-ext.properties mechanism, should replace the value dynamically.

But it doesn't work.

The only solution appear to duplicate the AuthentificationFilter class into my Project, extends the one from cas-client original, and customize the get parameters value process, but it's not really clean no ?


Solution

  • I successfully resolved my issue. Here is how I solve it properly.

    The cas client library from jasig (now apereo) propose to use a properties file to handle all params value linked to CAS configuration, like : casServerUrlPrefix, casServerUrlLogin, renew...etc

    I just take the most recent version compatible with java 1.6 :

    <dependency>
        <groupId>org.jasig.cas.client</groupId>
        <artifactId>cas-client-core</artifactId>
        <version>3.4.1</version>
    </dependency>
    

    Now, In the web.xml file of Liferay, I add two blocks for "configurationStrategy" and "configFileLocation" at the beginning, like explain in the documentation here :

    https://github.com/apereo/java-cas-client/blob/cas-client-3.4.1/README.md

    <context-param>
         <param-name>contextClass</param-name>
         <param-value>com.liferay.portal.spring.context.PortalApplicationContext</param-value>
    </context-param>
    <context-param>
         <param-name>contextConfigLocation</param-name>
         <param-value/>
    </context-param>
    <context-param>
         <param-name>configurationStrategy</param-name>
         <param-value>PROPERTY_FILE</param-value>
    </context-param>
    <context-param>
         <param-name>configFileLocation</param-name>
         <param-value>/opt/liferay/mycas.properties</param-value>
    </context-param>
    

    And just at reminder, I continue to keep my new filters also in the web.xml :

    <filter>
        <filter-name>SSO CAS Filter</filter-name>
        <filter-class>org.jasig.cas.client.authentication.AuthenticationFilter</filter-class>
        <init-param>
            <param-name>casServerLoginUrl</param-name>
            <param-value>${java.system.property.casServerLoginUrl}</param-value>
        </init-param>
        <init-param>
            <param-name>serverName</param-name>
            <param-value>${java.system.property.serverName}</param-value>
        </init-param>
    </filter>
    <filter>
        <filter-name>CASValidationFilter</filter-name>
        <filter-class>org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter</filter-class>
        <init-param>
            <param-name>casServerUrlPrefix</param-name>
            <param-value>${java.system.property.casServerUrlPrefix}</param-value>
        </init-param>
        <init-param>
            <param-name>serverName</param-name>
            <param-value>${java.system.property.serverName}</param-value>
        </init-param>
    </filter>
                ...
                
    <filter-mapping>
        <filter-name>SSO CAS Filter</filter-name>
        <url-pattern>/c/portal/login</url-pattern>
    </filter-mapping>
    <filter-mapping>
        <filter-name>SSO CAS Filter</filter-name>
        <url-pattern>/c/portal/logout</url-pattern>
    </filter-mapping>
    <filter-mapping>
        <filter-name>CASValidationFilter</filter-name>
        <url-pattern>/c/portal/login</url-pattern>
    </filter-mapping>
    <filter-mapping>
        <filter-name>CASValidationFilter</filter-name>
        <url-pattern>/c/portal/logout</url-pattern>
    </filter-mapping>
    

    ...

    Verify that you well have the cas-client jar v3.4.1 on your liferay server, in apache-tomcat-6.0.20/lib/ext/

    For the web.xml file, I just have to put my version in my project, and when the ant script generate the liferay deployment zip archive, it merged the web.xml generated by liferay mechanism, and my own web.xml.

    Don't forget also to add this to your portal-ext.properties file :

    auto.login.hooks=com.yourownpackage.auth.security.CASAutoLogin
    

    With this configuration, You just have to define the code into this class, it will be your entry point after login, and the attributes of the user could be retrieved without any problem :)

    package com.yourownpackage.auth.security;
    
    import java.util.*;
    
    import javax.naming.Binding;
    import javax.naming.NamingEnumeration;
    import javax.naming.NamingException;
    import javax.naming.directory.SearchControls;
    import javax.naming.directory.SearchResult;
    import javax.naming.ldap.LdapContext;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import javax.servlet.http.HttpSession;
    import com.liferay.portal.NoSuchUserException;
    import com.liferay.portal.PortalException;
    import com.liferay.portal.SystemException;
    import com.liferay.portal.kernel.log.Log;
    import com.liferay.portal.kernel.log.LogFactoryUtil;
    import com.liferay.portal.kernel.util.StringPool;
    import com.liferay.portal.kernel.util.StringUtil;
    import com.liferay.portal.kernel.util.Validator;
    import com.liferay.portal.model.User;
    import com.liferay.portal.security.auth.AutoLogin;
    import com.liferay.portal.security.ldap.PortalLDAPUtil;
    import com.liferay.portal.service.UserLocalServiceUtil;
    import com.liferay.portal.util.PortalUtil;
    import com.liferay.portal.util.PrefsPropsUtil;
    import com.liferay.portal.util.PropsKeys;
    import com.liferay.portal.util.PropsValues;
    import java.util.Enumeration;
    
    import org.jasig.cas.client.validation.AssertionImpl;
    
    /**
     * CASAutoLogin bas� sur celle fournie par d�faut de Liferay.
     * 
     * @see com.liferay.portal.security.auth.CASAutoLogin
     */
    public class CASAutoLogin implements AutoLogin {
    
        public Map<String, String> convertToStringMap(Map<String, Object> attributes) {
            Map<String, String> stringAttributes = new HashMap<String, String>();
    
            for (Map.Entry<String, Object> entry : attributes.entrySet()) {
                String attributeName = entry.getKey();
                Object attributeValue = entry.getValue();
    
                if (attributeValue instanceof String) {
                    String attributeStringValue = (String) attributeValue;
                    stringAttributes.put(attributeName, attributeStringValue);
                }
            }
    
            return stringAttributes;
        }
    
        public List<String> extractRoles(Map<String, Object> attributes) {
            List<String> roles = new LinkedList<String>();
    
            Object memberOfAttribute = attributes.get("memberof");
            if (memberOfAttribute instanceof List) {
                List<String> memberOfList = (List<String>) memberOfAttribute;
                for (String memberOf : memberOfList) {
                    if (memberOf.startsWith("cn=")) {
                        String role = memberOf.substring(3); // Exclude "cn="
                        roles.add(role);
                    }
                }
            }
    
            return roles;
        }
    
        /**
         * M�thode principale de Login
         */
        public String[] login(HttpServletRequest request, HttpServletResponse response) {
    
            try {
                long companyId = PortalUtil.getCompanyId(request);
    
                if (!isCASAuthenficationEnabled(companyId)) {
    
                    return null;
                }
    
                // On lit la session CAS
                HttpSession session = request.getSession();
                AssertionImpl casAssertion = (AssertionImpl) session.getAttribute("_const_cas_assertion_");
                String userName = null;
                Map<String, Object> attributes = null;
    
                // Check if the assertion object is available and of the correct type
                if (casAssertion != null) {
                    // Get the attributes from the attribute principal
                    attributes = casAssertion.getPrincipal().getAttributes();
                    userName = casAssertion.getPrincipal().getName();
                } else {
                    // Handle the case when the assertion object is not available or of the correct type
                    throw new Exception("Non authentifié");
                }
    
                if (userName != null) {
                    Map<String, String> userAttributes = convertToStringMap(attributes);
                    System.out.println("User attributes:");
                    for (Map.Entry<String, String> entry : userAttributes.entrySet()) {
                        String attributeName = entry.getKey();
                        String attributeValue = entry.getValue();
                        System.out.println(attributeName + ": " + attributeValue);
                    }
    
                    // Extract roles
                    List<String> roles = extractRoles(attributes);
                    System.out.println("\nRoles:");
                    for (String role : roles) {
                        System.out.println(role);
                    }
    
                     ... your own logic
    
                } else {
                    throw new Exception("Non authentifié");
                }
    
                ... your own logic
            } catch (Exception e) {
                _log.error(e, e);
            }
    
            return null;
        }
    
    
        private static Log _log = LogFactoryUtil.getLog(CASAutoLogin.class);
    
    }
    

    Now it works perfectly.