martes, 18 de octubre de 2016

Webpart para auditar documentos en SharePoint Online

En varias ocasiones nos han preguntado si SharePoint cuenta con alguna capacidad de auditar quién ha visto, descargado, modificado o borrado algún documento dentro de la plataforma; e incluso, hay personas mucho más pro-activas que se dieron a la tarea de leer los post que ofrece Microsoft y dan por sentado que la plataforma lo permite. 

Ahora, esto llega a generar cierta confusión en ellos cuando se adentran al detalle de la nota en la que pone las diferencias entre la plataforma en la nube (es decir SharePoint Online) y la plataforma On-Premise (dentro de nuestra infraestructura de servidores); y es completamente normal; como normal es que yo no conozca a profundidad de leyes, contabilidad, marketing, microbiología, etc. Para esos casos en los que necesitas entender o resolver esos pequeños-grandes detalles, siempre lo recomendable es acudir con un experto.

Pues bien, en esta ocasión les comparto mi experiencia con este requerimiento que a mi parecer es muy normal, sobre todo, saber quién ha leído un documento dentro de SharePoint Online (que al momento de escribir este post, no permite auditar las descargas o las lecturas de documentos en la plataforma de manera natural). Para exponer el caso tomemos en cuenta lo siguiente:

  1. SharePoint es una plataforma flexible que permite crear tantos sitios web como bibliotecas de documentos tengamos en mente. El orden o estructura se lo podemos dejar a la imaginación, por lo que la solución que demos, debe ser tan flexible como bibliotecas tengamos.
  2. Los usuarios que gestionan el contenido en los sitios quieren saber qué usuarios acceden al contenido que ellos publican en los diversos portales.
  3. Se espera que los documentos sean compartidos en diferentes formas como:
    1. Vínculos mediante correo electrónico.
    2. Colocando los vínculos en los menús o portales como accesos directos.
Con estos puntos en mente, pensemos en un sistema mailing, de estadísticos de twitter e incluso, de las búsquedas en Google. Si observamos un poco sobre el comportamiento del navegador al momento de presionar los vínculos, antes de enviarnos a la página que esperamos ver nos envía a una página en blanco y posteriormente nos redirecciona a la liga que seleccionamos, es justa en esa página en blanco en la que estamos entregando información sobre nuestro comportamiento sin darnos cuenta de ello.


Trasladando esta observación a nuestro escenario, ¿podríamos nosotros proponer una pequeña App que modifique los vínculos de los documentos y que, éstos vínculos nos permitan enviar a los usuarios que los presionen a una página intermedia que registre el comportamiento? 
Claro que sí y para ello, habría que crear un proyecto en Visual Studio con la plantilla de SharePoint Add-In. 
Para ello:


1. Ya con nuestro proyecto creado, agregué una página web llamada URLAuditable.aspx
2. Agregué una página web llamada Analytics.aspx

3. Agregué un nuevo ítem del estilo Menu Item Custom Action al que llamé GetURLAudit y seleccioné la página creada en el punto 1.
Agregando el Control para el menú

4. Este ítem te permite asociarlo a tipos de contenido Biblioteca de documentos e indicar que se presentará en las bibliotecas de documentos del sitio Host. 
Asignando en dónde se mostrará el nuevo control
Indicando la página que se mostrará al presionar el botón.
Si observamos un poco el XML creado por el ítem, éste nos indica que proporcionará como parámetro en la URL el ID de la lista y el ID del ítem con lo que podremos recuperar (usando un poco de programación con JavaScript) toda la información del documento.

<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <CustomAction Id="8c10803a-8293-427b-84b1-250036b46c18.GetURLAudit"
                RegistrationType="List"
                RegistrationId="101"
                Location="EditControlBlock"
                Sequence="10001"
                Title="Obtener URL Auditable">
    
    <UrlAction Url="~appWebUrl/Pages/URLAuditable.aspx?{StandardTokens}&amp;SPListItemId={ItemId}&amp;SPListId={ListId}" />
  </CustomAction>
</Elements>

5. En la página a la que nos referencía construiremos la URL que nos ayudará a capturar el comportamiento de los visitantes. Para ello, agregué en la página un campo de texto.

Campo de texto para construir una URL
6. Cree una lista de SharePoint llamada Registro en la que capturaré todos los eventos de carga de la página Analytics.aspx.
Agregando la lista para registrar las visitas.
7. En la página ULAuditable.aspx coloqué el siguiente código jQuery para generar la nueva liga:

var hostweburl;
var appweburl;
var idLista;

$(document).ready(function () {
    hostweburl = decodeURIComponent(getQueryStringParameter("SPHostUrl"));
    appweburl = decodeURIComponent(getQueryStringParameter("SPAppWebUrl"));
    
    var scriptbase = hostweburl + "/_layouts/15/";

    $.getScript(scriptbase + "SP.RequestExecutor.js", getListName);
});

function getListName() {
    var executor = new SP.RequestExecutor(appweburl);
    idLista = decodeURIComponent(getQueryStringParameter("SPListId")).replace("{", "").replace("}", "");
    
    executor.executeAsync(
        {
            url: appweburl + "/_api/SP.AppContextSite(@target)/web/lists(guid'" + idLista + "')?@target='" + hostweburl + "'",
            method: "GET",
            headers: { "Accept": "application/json; odata=verbose" },
            success: function (data) {
                var jsonObject = JSON.parse(data.body);
                var tituloBiblioteca = jsonObject.d.Title;

                getDocumentName(tituloBiblioteca);
            },
            error: onQueryFailed
        }
    );
}

function getDocumentName(tituloBiblioteca) {
    var executor = new SP.RequestExecutor(appweburl);
    var iditem = decodeURIComponent(getQueryStringParameter("SPListItemId")).replace("{", "").replace("}", "");
    
    executor.executeAsync(
        {
            url: appweburl + "/_api/SP.AppContextSite(@target)/web/lists/GetByTitle('" + tituloBiblioteca + "')/items(" + iditem + ")/File?@target='" + hostweburl + "'",
            method: "GET",
            headers: { "Accept": "application/json; odata=verbose" },
            success: function (data) {
                var jsonObject = JSON.parse(data.body);

                $("#txtURLEditable").val(appweburl + "/Pages/Analytics.aspx?SPHostUrl=" + encodeURIComponent(hostweburl) + "&SPAppWebUrl=" + encodeURIComponent(appweburl) + "&SPListId=" + encodeURIComponent("{" + idLista + "}") + "&SPListItemId=" + iditem);
                
            },
            error: onQueryFailed
        }
    );
}

8. En la página Analytics.aspx coloqué el siguiente código para capturar el evento de acceso:

var hostweburl;
var appweburl;
var idLista;
var context, web;

$(document).ready(function () {
    context = SP.ClientContext.get_current();
    web = context.get_web();

    context.load(web);
    context.executeQueryAsync((this, function (sender, args) {
        getListName();
    }),
    Function.createDelegate(this, onQueryFailed));
});

function getListName() {
    var executor = new SP.RequestExecutor(appweburl);
    idLista = decodeURIComponent(getQueryStringParameter("SPListId")).replace("{", "").replace("}", "");

    executor.executeAsync(
        {
            url: appweburl + "/_api/SP.AppContextSite(@target)/web/lists(guid'" + idLista + "')?@target='" + hostweburl + "'",
            method: "GET",
            headers: { "Accept": "application/json; odata=verbose" },
            success: function (data) {
                var jsonObject = JSON.parse(data.body);
                var tituloBiblioteca = jsonObject.d.Title;

                getDocumentName(tituloBiblioteca);
            },
            error: onQueryFailed
        }
    );
}

function getDocumentName(tituloBiblioteca) {
    var executor = new SP.RequestExecutor(appweburl);
    var iditem = decodeURIComponent(getQueryStringParameter("SPListItemId")).replace("{", "").replace("}", "");

    executor.executeAsync(
        {
            url: appweburl + "/_api/SP.AppContextSite(@target)/web/lists/GetByTitle('" + tituloBiblioteca + "')/items(" + iditem + ")/File?@target='" + hostweburl + "'",
            method: "GET",
            headers: { "Accept": "application/json; odata=verbose" },
            success: function (data) {
                var jsonObject = JSON.parse(data.body);

                registrarEvento(jsonObject.d.Name, tituloBiblioteca, jsonObject.d.LinkingUrl);
            },
            error: onQueryFailed
        }
    );
}

function registrarEvento(nombreArchivo, tituloBiblioteca, url) {
    var oList = context.get_web().get_lists().getByTitle('Registro');
    var itemCreateInfo = new SP.ListItemCreationInformation();
    var oListItem = oList.addItem(itemCreateInfo);

    oListItem.set_item('Title', nombreArchivo);
    oListItem.set_item('Biblioteca', tituloBiblioteca);
    oListItem.update();

    context.load(oListItem);

    context.executeQueryAsync(
        Function.createDelegate(this, function (sender, args) {
            console.log("Evento registrado");
            window.location = url;
        }),
        Function.createDelegate(this, onQueryFailed));
}

Si observan el código, es muy similar al del punto 7 y puede haber algunas cosas que no sean necesarias debido a que es un ejemplo recortado del requerimiento original, en el que nos pidieron leer propiedades adicionales del usuarios como su número de Empleado y su Puesto. Sin embargo, creo que funciona bien para los propósitos del post.

9. Le di permisos de lectura al App desde el archivo Manifest.xml a la colección de sitios y los perfiles de usuarios (este último por si haces lectura desde los perfiles de usuario para traer información adicional).


10. ¡Finalmente, a probar!

Una vez corriendo la aplicación, todas las bibliotecas de documentos en el sitio Host tendrán un nuevo control que nos permitirá crear la URL, compartirla y al abrirla, registrar el evento de lectura.

Control en biblioteca de SharePoint Online
Registro de auditoría en la lista personalizada dentro del App.

¡Espero que este post les sea de utilidad y de ser así, por favor compartan y comenten que estamos para escucharlos!

También están invitados a conocer Chimalli Apps en donde colocamos soluciones empresariales para ustedes.