Alfresco – Desarrollo de una página de inicio previa en Share

/ / Blog, Digital Development, Document Management
alfresco-agreement-filter-1
Alfresco Global Virtual Hack-a-thon 2015
Liberada la versión de Alfresco 5.0.d

A continuación describimos el proceso de desarrollo de una página inicial para Alfresco Share 5.

Objetivos

  • Mostrar una página inicial de condiciones de uso para todos los usuarios de Alfresco Share
  • Cada usuario debe aceptar las condiciones antes de usar Alfresco Share

alfresco-agreement-filter-diagram

 

Filtro web para Alfresco Share

Cada petición web debe ser interceptada para comprobar que el usuario haya aceptado las Condiciones de Uso, por lo que debemos desarrollar un filtro web adicional a los existentes en la aplicación web de Share. Para evitar modificaciones en el fichero original de Alfresco web.xml, puede utilizarse la tecnología Servlet 3.0 @WebFilter basada en anotaciones y disponible a partir de la especificación 3.0 de Servlet. Para poder utilizar esta librería, debe incluirse como dependencia provista en el pom.xml original generado por Alfresco Maven SDK 2.0.

<?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>

En la implementación del filtro debemos tener en cuenta algunos requisitos:

  • Cada petición web pasará por este código, por lo que deberá ser lo más eficiente posible
  • Pueden llegar peticiones web no autenticadas
  • La información relativa al estado de aceptación de las condiciones del uso por parte del usuario será almacenada en el repositorio de Alfresco

 

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() {}
}

Una vez que hemos establecido el mecanismo de intercepción para las peticiones web, podemos desarrollar la página de condiciones de uso.

Página de condiciones de uso

Dado que Aikau no dispone de un componente nativo para desarrollar páginas de tipo “standalone”, tomamos como punto de partida la página Quick Shared del propio Alfresco Share. El uso de tecnología FTL en Alfresco 5 debería estar desaconsejado, pero en este caso la alternativa pasaría por el desarrollo de un nuevo componente Aikau con la complejidad y el esfuerzo que ello conlleva.

Para la construcción de la funcionalidad, se desarrollan los “templates”, “pages” y “components” necesarios para cada página

  • Página agreement
    • Incluye una cabecera, una cabecera de nodo y un componente de previsualización web del documento
    • Recoge el documento de Condiciones de Uso mediante un webscript de Alfresco repo (lo que permite parametrizar la ruta en el archivo de configuración alfresco-global.properties)
    • Envía la pulsación Aceptar a la página agreement-redirect
  • Página agreement-redirect
    • Incluye una invocación a un webscript de Alfresco repo para almacenar la aceptación del usuario y una redicción HTML a la página de inicio de Alfresco Share para dar paso al usuario a la aplicación

alfresco-agreement-filter-1

Webscrips de Alfresco repo

Para dar servicio a estas páginas, son necesarios unos cuantos webscripts en Alfresco repo:

  • agreement.get
    • Devuelve la propiedad residual agreementChecked del noderef del usuario
  • agreement.post
    • Establece el valor de la propiedad residual agreementChecked en el noderef del usuario

alfresco-agreement-filter-2

  • agreement-page.get
    • Devuelve el noderef de la página de contenido buscándola a partir de la ruta indicada en el fichero de configuración alfresco-global.properties

alfresco-agreement-filter-3

Código fuente

Tenéis disponible todo el código fuente del proyecto en nuestro GitHub: https://github.com/keensoft/alfresco-agreement-filter

Unidad de negocio, keensoft