Get my own properties in cas client and Liferay

158 Views Asked by At

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 ?

2

There are 2 best solutions below

0
user2178964 On BEST ANSWER

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.

2
Olaf Kock On

Where do I start?

It's 2023, and not only is your CAS client old, but your whole Liferay installation is old. Likely you'll only find people with ancient knowledge about this >10y old platform, that has seen significant architectural changes since then.

With regards to property-values: They're never automatically replaced, just because they're part of some properties file. Replacement is explicit, for some (few) values in few places.

There used to be documentation for how to introduce another filter into the system, but I'm not sure the 5.x documentation is still around. And indeed, I'd recommend to use the existing filter as basis. It might not be elegant, but so is running an outdated version, with known security issues, on a server. You should be able to follow roughly the 6.x documentation that you can still find online (might need minor tweaks, you'll run into them)

The actual solution that I'd recommend is to upgrade your system - though I've not heard about CAS for a long time. Validate if that's still supported.