Modelo de apps en detalle (III): High-Trust

En los primeros dos posts de esta serie hemos visto la evolución del código personalizado en SharePoint y la arquitectura básica del modelo de apps. También hemos visto a grandes rasgos como la app se autentica con SharePoint utilizando Azure Access Control Service (ACS) como intermediario.

Las apps en SharePoint Online funcionan con este método de autorización de las apps, llamado low-trust.

Modelo Low-Trust: SharePoint + app + ACS

En este modelo, SharePoint es el responsable de pasar un token de contexto a la app, cuando hace la redirección a ella. La app valida el token de contexto y lo intercambia por un token de acceso, haciendo una llamada a ACS. Fijaos que el modelo low-trust deja a la app como un mero transmisor de tokens entre SharePoint y ACS. Por eso mismo se llama de "baja confianza". La app no puede añadir ni quitar nada de la información de autenticación y autorización. Todo esto viene dado en el token inicial que se le pasa a la app desde SharePoint.

Este modelo es muy útil para apps públicas sobre las que no tenemos ningún control. Por ello es el modelo usado para la SharePoint Store y las apps disponibles en SharePoint Online. En los entornos on-premise corporativos, este modelo requiere dar conectividad a Internet a los servidores de SharePoint y los servidores de las apps así como establecer una relación de confianza entre SharePoint on-premise y el servicio ACS de Azure.

Modelo High-Trust: SharePoint + app

En los entornos corporativos on-premise de SharePoint, podemos usar otro modelo de autorización y autenticación de las apps, en el que SharePoint establece una relación de confianza con la app y delega en ella la autenticación del usuario. Este modelo es el llamado high-trust o de alta confianza. También se le llama S2S (Server-to-Server).

No hay que confundir el modelo de high-trust con full-trust. Full-trust es el código personalizado de servidor de SharePoint que desplegamos en la GAC, y por lo que tiene todos los permisos que tiene SharePoint. Una app high-trust tendrá como mucho los permisos que se le han otorgado durante su instalación, ni más ni menos, idéntico al caso de las low-trust apps.

Entonces, ¿por qué se llama "de alta confianza"? Es así debido a que no hay un intermediario (como ACS) y que SharePoint se fía de la app para que le construya el token de acceso adecuado.

Para que el modelo high-trust funcione, tenemos que habilitar una relación de confianza entre la app y SharePoint. Esto se consigue utilizando un certificado digital. La parte pública de este certificado se registra en SharePoint como "trusted token issuer" o emisor de tokens de confianza. En el caso de las low-trust apps, el único "emisor de tokens de confianza" es el ACS. La parte privada del certificado se usará en la app para firmar los tokens emitidios por ella.

Al ser la app un emisor de tokens de confianza, SharePoint aceptará los tokens que ella misma construya. Previamente los validará con la clave pública del certificado, lo que comprueba que no han estados modificados durante el tránsito. Luego, aplicará los permisos que tiene la app en SharePoint (que se le han dado al instalarla) e incluirá la información del usuario actual (que está contenida en el token de acceso que construye la app).

Fijaos que la app puede construir el token de acceso para cualquier usuario. Esta es la parte en la que nos tenemos que "fiar" de la app. Sin embargo, el ámbito de actuación de la app está limitado a los permisos que se le han dado al instalar, así que aunque la app puede "falsear" la información del usuario (como decir que viene de parte de la cuenta del sistema), como mucho puede hacer lo que se le permite a la app, sin poder saltárselo.

Vayamos a ver el "baile" de conversación entre SharePoint y una app high-trust al abrir la app desde el navegador.

image

  1. El usuario abre una página de SharePoint y clica en el enlace de la app.
  2. SharePoint hace la redirección a la URL de la app (respuesta 302 de HTTP). No incluye ningún token de contexto (como en el caso de las low-trust apps).
  3. El navegador realiza la petición a la URL de MiApp.com como respuesta a la redirección.
  4. La aplicación recibe la petición HTTP. Para acceder a SharePoint mediante CSOM o REST, la aplicación necesita un token de acceso. La aplicación construye el token de acceso y lo firma con la clave privada de su certificado. Incluye este token en la petición a SharePoint.
  5. SharePoint comprueba la validez del token de acceso con la clave pública del certificado de la aplicación y devuelve los datos que la aplicación le ha pedido.
  6. La aplicación renderiza el HTML que se va a visualizar y lo devuelve al navegador.

Construyendo los tokens de acceso

La misma clase auxiliar TokenHelper.cs que usamos en las apps de ASP.NET contiene los métodos para construir los tokens de acceso de high-trust apps. Si miramos el código fuente del mismomiramos el código fuente del mismo, veremos dos métodos:

  • GetS2SAccessTokenWithWindowsIdentity
  • GetS2SClientContextWithWindowsIdentity

El primer método devuelve un token de acceso para un usuario concreto, firmado con la clave privada del certificado de la app. El segundo devuelve un contexto de SharePoint CSOM (ClientContext) inicializado para un usuario concreto.

El usuario se le pasa como parámetro al método, y es una instancia de WindowsIdentity. Internamente, la clase TokenHelper convierte este usuario en un conjunto de claims y construye el token con estos claims. Este detalle de implementación obliga a que la app esté alojada con la autenticación Windows habilitada, ya que sin ella la app no podría obtener una instancia de WindowsIdentity. Sin embargo, es posible cambiar la clase TokenHelper para que expida tokens con un ClaimsIdentity (identidad procedente de claims) si usamos ADFS o algún otro proveedor de identidad (STS) compatible con credenciales SAML. Lo veremos en uno de los próximos posts.

Pero, ¿cómo sabe TokenHelper donde está el certificado para firmar? Bueno, se lo tenemos que decir nosotros usando el fichero web.config. Hay que añadir unas entradas adicionales, a parte de las obligatorias para la app (como el ID de la app y su URI), especificando una ruta de certificado y una contraseña para firmar con la clave privada. Además, el application pool de la app tiene que tener permisos suficientes sobre la carpeta donde se halla el certificado.

Resumen

Hemos visto como las apps high-trust simplifican la autorización y autenticación en un entorno on-premise. En el próximo post veremos como configurar un entorno de SharePoint on-premise y haremos una app high-trust desde cero.

Modelo de apps en detalle (II): Las piezas

En el primer post de esta serie sobre el modelo de apps de SharePoint 2013, expliqué la problemática de tener código a medida en SharePoint y como SharePoint ha evolucionado para acomodarla, hasta llegar al modelo de apps en SharePoint 2013.

En este segundo post voy a explicar las piezas clave del modelo de apps, para asentar los conocimientos básicos antes de meternos en detalle con la autorización y la autenticación de la app.

Los "webs" del modelo de apps

A grandes rasgos, el modelo de apps involucra tres piezas básicas en forma de sitio web:

  • "host web" (SharePoint)
  • "app web" (SharePoint)
  • "remote web"

Voy a poner un diagrama completo de las piezas de las apps y lo comentaremos a lo largo de este post. Entender bien estas piezas y su relación es tener la mitad del trabajo hecho. Vayamos por partes.

image 

Host Web

Las apps se instalan en un sitio de SharePoint y se lanzan desde allí. Ese sitio web, en el que la app está instalada y tiene ciertos permisos, se llama host web.

App Web

Una app al instalarse puede crear un sitio de SharePoint para su propio uso. Este sitio, si existe, se llama app web y tiene la URL con un dominio diferente al de host web. Esto se hace para impedir llamadas cross-site de JavaScript.

¿Para qué nos sirve un app web? La idea es guardar aquí los datos propios de la app, como los resultados parciales, configuraciones, perfiles de usuarios etc. La app web es invisible para el usuario normal de SharePoint, y no se puede acceder a ella mediante la interfaz de usuario.

Remote Web

Para las aplicaciones provider-hosted (es decir, las que tienen código y no sólo JavaScript), la invocación de la app tiene forma de redirect hacia una URL en la que está alojada la aplicación. Este sitio donde está alojada la app se llama remote web. Lo más probable es que la app esté alojada en un IIS (on-premise o en Azure) pero podría estar alojada en cualquier tecnología web (Apache, Linux, Node.js…). Sin embargo, si usamos ASP.NET como la plataforma para la app, se nos simplifica la comunicación con SharePoint ya que disponemos de librerías y helpers.

Los permisos

Pongamos como ejemplo que acabamos de clicar el enlace de la app en SharePoint, concretamente en http://contoso/site1. . Como ya sabéis, esto implica que la app está instalada en http://contoso/site1, que es la host web de la app.

La app también tiene ciertos permisos sobre la host web, que se le otorgan al instalarla. Los permisos pueden ser de leer algunas listas de la host web hasta tener control total sobre la tenancy o la site collection en la que está la host web. Los permisos de declaran en el paquete de la app (que veremos más adelante) y la persona que instala la app tiene que tener la potestad de otorgarlos.

Resumiendo, la app tiene los permisos sobre la host web que le hayamos dado al instalarla. Ni uno más ni uno menos.

Si la app dispone de una app web (que es otro de los parámetros que podemos poner en el paquete de la app), la app tiene el control total sobre la app web. Puede hacer y deshacer lo que le dé la gana. Como la app web no se muestra al usuario y sirve como un mero repositorio de datos para la app, no hay peligro en darle control total sobre esa parte de SharePoint.

Para nuestro ejemplo, imaginemos que la app web tiene la url http://app123.contosoapps.

La comunicación entre SharePoint y la app

Volvamos al ejemplo. Hemos clicado la app en http://contoso/site1 y nos ha llevado a https://wingtip/app1/start.aspx. Esta URL es parte del paquete de la app, y es la que lleva a la remote web de la app. En este caso, imaginemos que https://wingtip/app1 es un sitio IIS en una máquina dentro del datacenter de Contoso. Una máquina que no tiene ni idea de SharePoint y que contiene una app hecha en ASP.NET WebForms.

ASP.NET carga la página start.aspx. En este momento la app tiene que establecer la conexión con SharePoint para cargar los datos que necesita de allí. Veamos como lo hace.

Lo primero que necesita la app en remote web es saber a qué URLs tiene que llamar para obtener los datos de la host web y la app web.

¿Cómo lo sabe? Podríamos guardar estas URLs en la configuración de la app (en web.config o en la base de datos) pero lo más habitual es que al llamar la app desde SharePoint se incluyan estas URLs en la URL con la que se llama a la app. Así, nuestra app no se llama con un redirect a https://wingtip/app1/start.aspx sino a https://wingtip/app1/start.aspx?SPHostUrl=http://contoso/site1&SPAppWebUrl=http://app123.contosoapps. Como podéis ver, en los parámetros SPHostUrl y SPAppWebUrl tenemos las URLs de la host web y de la app web, respectivamente.

image

Usando el modelo de objetos de cliente de SharePoint podríamos instanciar un contexto a través de esas URLs, pero nos faltaría la autenticación. Es decir, ¿con qué credenciales de usuario abrimos el contexto de SharePoint? Vamos a ver como lo resuelve el modelo de apps.

Autenticación de la app

En el primer post de esta seria dije que las apps de SharePoint 2013 tienen identidad propia y se les pueden asignar permisos. Pues este es el mecanismo con el que se resuelve el problema de las credenciales: el contexto de SharePoint se abrirá con las credenciales de la app.

¿De dónde vamos a sacar las credenciales de la app? Esto depende de si estamos usando las apps en SharePoint Online o una granja on-premise federada con Azure Access Control Service (ACS) o bien estamos usando las apps en un entorno on-premise con certificados.

El primer caso es el más frecuente y se llama "low-trust" (baja confianza). En este caso, SharePoint usa un servicio de Azure llamado ACS (Access Control Service) para confimar la identidad de la app. ¿Cómo? Pues sencillamente antes de hacer el redirect a la app, inyecta en la cabecera de la petición HTTP un pequeño fragmento de texto, un token de contexto, que le servirá a la app luego para sacar las credenciales.

image

Este token de contexto contiene la información sobre la identidad de la app. La app (ASP.NET) tiene que extraer este token de la petición inicial HTTP, hacer una petición a ACS e intercambiarlo por otro token, llamado token de acceso. El token de acceso nos sirve para instanciar un contexto de SharePoint, ya que es un token OAuth que SharePoint 2013 acepta. Este token se incorpora en la cabecera de la petición a la interfaz REST de SharePoint 2013 con el nombre de "Bearer".

image

Podéis ver el contenido exacto de los tokens de contexto y acceso en el siguiente enlace: http://msdn.microsoft.com/library/office/fp179932.aspx#Tokens.

Todo este proceso de extraer los tokens, validarlos, intercambiarlos y crear el contexto de SharePoint está encapsulado en una clase autogenerada por Visual Studio al hacer una app de SharePoint, que se llama TokenHelper.cs. Podéis examinarlo y ver como hace la petición a ACS y que luego incorpora el token de acceso para llamar a SharePoint.

Si estamos en una plataforma que no es .NET (como PHP por ejemplo), podemos hacer el mismo proceso pero no tendremos un TokenHelper mágico. Tendremos que hacer la extracción y construcción de tokens y de llamadas a ACS de manera manual, pero no hay nada intrínsecamente difícil en ello.

Resumiendo

Como podéis ver, el "baile" entre las piezas de las apps es delicado y hay muchos conceptos involucrados. Os recomiendo releer este post hasta que os quede claro la interacción entre host web, app web y remote web, así como el papel de los tokens. Si queréis más detalles sobre los tokens y la autenticación low-trust, os recomiendo el artículo siguiente de Kirk Evans del equipo de SharePoint (en inglés): http://blogs.msdn.com/b/kaevans/archive/2013/04/05/inside-sharepoint-2013-oauth-context-tokens.aspx.

En el siguiente post veremos como las aplicaciones "high-trust" resuelven el problema de la autenticación sin delegarlo a un servicio de terceros como ACS.

Modelo de apps en detalle (I): Introducción

Hola a todos,

En un reciente proyecto me he visto involucrado en mucho detalle en el modelo de apps de SharePoint 2013 en entornos corporativos, con aplicaciones de alta confianza (high-trust) y autenticación federada con Claims. Mi idea en esta serie de artículos en el blog es compartir estos conocimientos ya que creo que existe poca información de primera mano sobre la utilización real del modelo de apps, fuera de las apps de demostración.

En este primer post voy a recordar la evolución del modelo de programación de SharePoint.

Antes de SharePoint 2010

Para un programador "veterano" en SharePoint, la programación en SharePoint siempre implica poner el código NET en el servidor de SharePoint. Desde que SharePoint abrazó el modelo de programación NET en la versión 2003, siempre ha sido así. El código de nuestras soluciones SharePoint se pone en la carpeta BIN de la aplicación web de SharePoint o bien en la GAC del servidor.

image

Los beneficios de este enfoque son el acceso a toda la potencia del modelo de objetos de SharePoint de servidor (Server Object Model) y de NET. Sin embargo, nos exponemos a que nuestro código ralentize el servidor (ya que se ejecuta en el mismo proceso que el código propio de SharePoint) y a que tengamos que preparar bien las actualizaciones de SharePoint. Solo hay que recordar las migraciones de una versión de SharePoint a otra y la caza y captura de código a medida para actualizarlo al nuevo modelo.

La raíz del problema es que SharePoint "puro" sin código a medida tiene un rendimiento muy bueno y que SharePoint con código a medida que no esté bien optimizado (y la verdad es que la mayoría del código a medida en SharePoint que circula por ahí no lo está) puede castigar el rendimiento de tal manera que no sea usable. En esencia: no hay nada que nos impida "frenar" a SharePoint con nuestro código lento. (como ejercicio podéis poner una webpart en SharePoint que haga un Thread.Sleep(5000) y SharePoint se volverá 5 segundos más lento).

SharePoint 2010: CSOM y Sandboxed

Para solventar de alguna manera este acoplamiento fuerte entre SharePoint y nuestro código, en SharePoint 2010 aparecen dos componentes arquitectónicos nuevos.

En primer lugar, tenemos un modelo de objetos de cliente (Client-side Object Model, CSOM) disponible en NET, Silverlight y JavaScript. De esta manera podemos crear código que se ejecute fuera de SharePoint. El modelo de objetos de cliente no es tan potente como el modelo de objetos de servidor, pero es usable en muchos escenarios comunes.

En segundo lugar, aparece el concepto de código sandboxed. Por primera vez, el código a medida se puede ejecutar en un proceso separado de SharePoint y por tanto sometido a restricciones de tiempo de CPU y memoria. De esta manera podemos mantener estable el entorno propio de SharePoint y protegerlo de cierta manera de nuestro código potencialmente acaparador de recursos.

modelo_sandbox

Las aplicaciones sandboxed prometían lo mejor de los dos mundos: acceso al modelo de objetos de servidor y a la vez un control sobre el uso de recursos del código que lo utiliza.

SharePoint 2013: Cloud-based Apps (CBA)

¡Y llegó SharePoint 2013! De buenas a primeras, se cargó de un plumazo "deprecador" el modelo sandbox que tanto prometía. Para sustituirlo, optó por desterrar el código a medida fuera del servidor de SharePoint en el nuevo modelo de apps (cloud-based applications, CBA).

Por tanto, el modelo de desarrollo preferente en SharePoint 2013 es tener todo el código fuera de SharePoint (en apps) y puesto en otro proceso que potencialmente esté en otra máquina así como utilizar el modelo de objetos de cliente (ya introducido en SharePoint 2010) para interactuar con los datos en SharePoint. De esta manera se cumple el objetivo importante de no que el código a medida lento no haga lento a SharePoint, pero a costa de tener que utilizar una API de cliente incompleta y que no está a la par del modelo de objetos de servidor.

modelo_apps

Para que el código a medida de las apps pueda llamar a SharePoint sin tener en cuenta el usuario concreto, se adaptó el estándar OAuth para crear las credenciales de las apps. En SharePoint 2013 una app tiene una identidad propia y permisos propios. En tiempo de ejecución, se combinan los permisos de la app y los permisos del usuario que utiliza la app para determinar los permisos efectivos.

Cuando apareció el modelo de las apps, había 3 "sabores" de las mismas: SharePoint-hosted, auto-hosted y provider-hosted. En junio de 2014 desapareció la opción auto-hosted. Las apps SharePoint-hosted sólo pueden contener código JavaScript así que para las aplicaciones corporativas son más bien inútiles. Esto nos deja con el modelo provider-hosted como el único realmente utilizado para hacer una app de cierta relevancia en SharePoint 2013.

Resumen

En este post hemos revisado la evolución de los modelos de desarrollo a medida de SharePoint, hasta el modelo de apps de SharePoint 2013. En el post siguiente abordaré el modelo de las apps en detalle para explicar las piezas que lo componen e ir introduciendo el concepto de low-trust y high-trust apps. ¡Hasta pronto!

Checking for User Permissions and Getting UnauthorizedAccessException

In a recent project I have been writing code to check if an arbitrary user can create new documents in certain document libraries. In order to do the check, I used the good old DoesUserHavePermissions method, which is present in SPWeb, SPList and SPListItem objects (securable objects).

SYMPTOMS

When using DoesUserHavePermissions() method on a securable object, you get UnauthorizedAccessException.

CAUSES

There are multiple causes for this behavior.

FIrst, the current user context is such that the current user has no rights to enumerate permissions on the SPWeb/SPList/SPListItem object. If so, the exception will be raised.

So, your first inclination is to use RunWithElevatedPrivileges to check the permissions. However, it also throws the same exception. The cause is a token check that the DoesUserHavePermissions method includes in its code (as explained by Phil Harding). The user token is compared against the current user. Somehow, the user token for elevated object is not the same as the current user in the context and the exception is being thrown.

SOLUTION

I managed to solve this issue by explicitly opening the securable object with a System Account token, instead of using RunWithElevatedPrivileges.