Alfresco – Developing a must read “Terms of use” page for Share

Description

  • Showing a “terms of use” initial page for every user in Alfresco Share
  • User must accept it before starting to use Alfresco Share

alfresco-agreement-filter-diagram

 

Web filter in Alfresco Share

Every request has to be examined in order to decide if user has accepted Terms of Use, so a web filter must be added to Alfresco Share web app. In order to avoid Alfresco web.xml modification, Servlet 3.0 @WebFilter annotation feature can be used. Share project pom.xml default must be modified to include this library.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

    <parent>
        <artifactId>agreement-filter</artifactId>
        <groupId>es.keensoft.alfresco</groupId>
        <version>1.0.0</version>
    </parent>

    <modelVersion>4.0.0</modelVersion>
    <artifactId>agreement-filter-share</artifactId>
    <packaging>amp</packaging>
    <name>Share components :: Agreement initial page for every new user</name>

    <dependencies>
        <!-- @WebFilter available since servlet 3.0 (Tomcat 7) -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.0.1</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.extensions.surf</groupId>
            <artifactId>spring-surf-api</artifactId>
            <scope>provided</scope>
        </dependency>
    </dependencies>

</project>

Filter implementation needs to deal with some conditions:

  • Every request is crossing it, so performance is a must
  • Requests can be unauthenticated
  • Alfresco repo must return user acceptance information

 

package es.keensoft.share.filter;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.springframework.context.ApplicationContext;
import org.springframework.extensions.surf.RequestContextUtil;
import org.springframework.extensions.surf.site.AuthenticationUtil;
import org.springframework.extensions.surf.support.AlfrescoUserFactory;
import org.springframework.extensions.surf.util.URLEncoder;
import org.springframework.extensions.webscripts.Status;
import org.springframework.extensions.webscripts.connector.Connector;
import org.springframework.extensions.webscripts.connector.ConnectorContext;
import org.springframework.extensions.webscripts.connector.ConnectorService;
import org.springframework.extensions.webscripts.connector.HttpMethod;
import org.springframework.extensions.webscripts.connector.Response;
import org.springframework.web.context.support.WebApplicationContextUtils;

// Unordered filter: chain until first user logged request arrives
@WebFilter(urlPatterns={"/page/*"})
public class AgreementFilter implements Filter {

    private static final String AGREEMENT_PAGE_PATH = "/agreement";
    private static final String AGREEMENT_REDIRECT_PAGE_PATH = "/agreement-redirect";
    public static String SESSION_ATTRIBUTE_KEY_AGREEMENT_CHECKED = "_alf_AGREEMENT_CHECKED";

    private ConnectorService connectorService;

    @Override
    public void init(FilterConfig config) throws ServletException {
        ApplicationContext context = WebApplicationContextUtils.getRequiredWebApplicationContext(config.getServletContext());
        this.connectorService = (ConnectorService)context.getBean("connector.service");
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {

        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) resp;
        HttpSession session = request.getSession();

        String userId = AuthenticationUtil.getUserId(request);

        if (checkAgreement(request, userId)) {

            try {

                RequestContextUtil.initRequestContext(WebApplicationContextUtils.getRequiredWebApplicationContext(request.getServletContext()), request, true);
                Connector conn = connectorService.getConnector(AlfrescoUserFactory.ALFRESCO_ENDPOINT_ID, userId, session);
                Response res = conn.call("/keensoft/agreement/" + URLEncoder.encode(userId), new ConnectorContext(HttpMethod.GET));

                if (Status.STATUS_OK == res.getStatus().getCode()) {

                    JSONObject userData = (JSONObject) new JSONParser().parse(res.getResponse());
                    if (userData.get("agreementChecked") != null) {
                        session.setAttribute(SESSION_ATTRIBUTE_KEY_AGREEMENT_CHECKED, Boolean.TRUE);
                        chain.doFilter(req, resp);
                    } else {
                        response.sendRedirect(request.getContextPath() + "/page/agreement");
                    }

                }

            } catch (Exception e) {
                throw new ServletException(e);
            }

        } else {

            chain.doFilter(req, resp);

        }

    }

    // Limit Alfresco repo webscript invocations
    private boolean checkAgreement(HttpServletRequest request, String userId) {

        HttpSession session = request.getSession();

        boolean userLoggedIn = AuthenticationUtil.getUserId(request) != null;

        boolean agreementPreviouslyChecked =
                session.getAttribute(SESSION_ATTRIBUTE_KEY_AGREEMENT_CHECKED) != null &&
                (Boolean) session.getAttribute(SESSION_ATTRIBUTE_KEY_AGREEMENT_CHECKED);

        boolean agreementPageRequested =
                request.getPathInfo().endsWith(AGREEMENT_PAGE_PATH) ||
                request.getPathInfo().endsWith(AGREEMENT_REDIRECT_PAGE_PATH);

        return (userLoggedIn && !agreementPreviouslyChecked && !agreementPageRequested);

    }

    @Override
    public void destroy() {}
}

Once all requests are under control, terms of use page can be developed.

Terms of use page

As Aikau have no native components to build a standalone page, Alfresco quickshare page can be taken as starting point. Using FTL in Alfresco 5 should be discouraged, but in this case there is no easy alternative.

Some development is required (templates, pages and components):

  • agreement page
    • Including a header, a node-header and a web-preview component
    • Recovering terms of use document from Alfresco repo webscript (in order to allow alfresco-global.properties parametrization)
    • Submitting Accept click to agreement-redirect page
  • agreement-redirect page
    • Including a webscript invocation to store user acceptance and an HTML redirection to home page

alfresco-agreement-filter-1

Alfresco repo webscripts

Custom Alfresco repo webscripts must be available in order to support this functionality:

  • agreement.get
    • Return residual agreementChecked property from user noderef
  • agreement.post
    • Set residual agreementChecked property to user noderef

alfresco-agreement-filter-2

  • agreement-page.get
    • Return terms of use content page noderef by searching the path configured in alfresco-global.properties

alfresco-agreement-filter-3

Source code

Available at https://github.com/keensoft/alfresco-agreement-filter

2 comentarios en “Alfresco – Developing a must read “Terms of use” page for Share

  1. Gracias Angel, cuando se actualiza (cambia, modifica) los Términos de Uso (Alfresco_Terms_Of_Use.pdf) , es necesario que el usuario nuevamente los acepte y esto no ocurre actualmente, ya que solo aparece la primera vez que ingresa a Alfresco. Hay alguna manera de resolverlo.

    • Gracias por el comentario. Es muy pertinente, lo apunto como mejora para el addon.

      La modificación es relativamente sencilla, basta con incluir la versión del documento que el usuario ha aceptado en las propiedades residuales que se guardan en el propio nodo del usuario. Al hacer la comprobación en el filtro web, se verifica la versión aceptada y se redirecciona en caso de que sea inferior.

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s