miércoles, 4 de febrero de 2009

Cambiar las credenciales de usuario en Sharepoint

En ocasiones puede plantearse el caso en el que necesitemos manejar los objetos de Sharepoint pero con una identidad distinta a la del usuario logado o el del administrador.

Dentro del contexto de Sharepoint disponemos de varios métodos para cambiar las credenciales:

RunWithElevatedPrivileges

Requiere de Impersonate=true

SPSecurity.RunWithElevatedPrivileges( delegate() {

….

} );

 

WindowsImpersonationContext

Nos permite ejecutar nuestro código con las credenciales de otro usuario. En este ejemplo con el usuario configurado en el pool de aplicaciones:

using (WindowsImpersonationContext wic = WindowsIdentity.Impersonate(IntPtr.Zero))

{

………….

}

 

SHAREPOINT\\system

Consiste en capturar el Token del usuario “system” para conectar de nuevo con ese token.

El usuario “SHAREPOINT\\system” es un usuario especial, utilizaremos este caso cuando nos encontremos en un EventReceiver ya que no podremos utilizar RunWithElevatedPrivileges.

using (SPWeb webOrigUser = properties.OpenWeb())
{
    SPUserToken token = webOrigUser.AllUsers["SHAREPOINT\\system"].UserToken;
    using (SPSite site = new SPSite(properties.SiteId, token))
    {
        using (SPWeb currentWeb = site.OpenWeb(properties.RelativeWebUrl))
        {
            try
            {
                DisableEventFiring();

                SPList sourceList = currentWeb.Lists[properties.ListId];
                SPListItem itemAdded = sourceList.GetItemById(properties.ListItem.ID);
                ……

            }
            catch (Exception ex1)
            {
                properties.ErrorMessage = ex1.Message;
                properties.Status = SPEventReceiverStatus.CancelWithError;
            }
            finally
            {
                EnableEventFiring();
            }

        }
    }
}

 

Fuera del contexto de Sharepoint

Para poder ejecutar el modelo de objetos de Sharepoint fuera del contexto de sharepoint, es decir en un proceso distinto, ya sea un servicio web en otro workerprocess o una aplicación de consola.

En este caso nuestro código se ejecutará con las credenciales del usuario que maneje el proceso.

Este caso lo utilizaremos cuando tengamos que hacer integraciones con sistemas que no pueden migrarse o aplicaciones para migrar contenido.

Para utilizar este método el usuario con el que hagamos logon además de tener acceso al site debe tener el privilegio “Actuar como parte del sistema operativo”, en mi caso le he dado permisos de administrador de la máquina directamente. Si no lo hacemos nos permitirá conectar pero nos dará una excepción al acceder a los elementos de las listas.

Podéis consultar más ejemplos: http://support.microsoft.com/kb/306158/es

En mi caso he creado una aplicación de consola que muestra el contenidos de una lista:

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Security.Principal;
using Microsoft.SharePoint;

namespace ConsoleApplication1
{
    class Program
    {

        const int LOGON32_PROVIDER_DEFAULT = 0;
        const int LOGON32_LOGON_NETWORK = 3;

        [DllImport("advapi32.dll", SetLastError = true)]
        private static extern bool LogonUser(String lpszUsername, String
        lpszDomain, String lpszPassword,
        int dwLogonType, int dwLogonProvider, ref IntPtr phToken);

        [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
        private extern static bool CloseHandle(IntPtr handle);

        [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern int DuplicateToken(IntPtr hToken,
            int impersonationLevel,
            ref IntPtr hNewToken);

        [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern bool RevertToSelf();

 

        static void Main(string[] args)
        {
            WindowsImpersonationContext context = impersonateValidUser("miusuario", "midominio", "password");
            try
            {
                using (SPSite site = new SPSite("http://localhost/))
                {
                    using (SPWeb web = site.OpenWeb())
                    {
                        Console.WriteLine(web.Lists["milista"].Title);
                        foreach (SPListItem item in web.Lists["milista"].Items)
                            Console.WriteLine(item.Title);
                    }
                }
            }
            finally
            {
                context.Undo();
            }

            Console.ReadLine();
        }

        private static WindowsImpersonationContext impersonateValidUser(String userName, String domain, String password)
        {
            WindowsIdentity tempWindowsIdentity;
            WindowsImpersonationContext impersonationContext;
            IntPtr token = IntPtr.Zero;
            IntPtr tokenDuplicate = IntPtr.Zero;

            if (RevertToSelf())
            {
                if (LogonUser(userName, domain, password, LOGON32_LOGON_NETWORK, LOGON32_PROVIDER_DEFAULT, ref token))
                {
                    if (DuplicateToken(token, 2, ref tokenDuplicate) != 0)
                    {
                        tempWindowsIdentity = new WindowsIdentity(tokenDuplicate);
                        impersonationContext = tempWindowsIdentity.Impersonate();
                        if (impersonationContext != null)
                        {
                            CloseHandle(token);
                            CloseHandle(tokenDuplicate);
                            return impersonationContext;;
                        }
                    }
                }
            }
            if (token != IntPtr.Zero)
                CloseHandle(token);
            if (tokenDuplicate != IntPtr.Zero)
                CloseHandle(tokenDuplicate);
            return null;
        }

    }
}

5 comentarios:

Mario Cortés Flores dijo...

El código que está dentro del RunWithElevatedPrivileges se ejecuta con la identidad SHAREPOINT\System para los objetos de Sharepoint y con la identidad del usuario del proceso para los accesos al sistema.
Aquí existe un pequeño matiz y es que si usamos un objeto SPSite su identidad dependerá de cuando se haya instanciado, si antes o después del RunWithElevatedPrivileges. Si utilizamos dentro de un bloque RunWithElevatedPrivileges el SPContext.Current.Site estaremos utilizando la identidad del usuario logado ya que el contexto se creó antes de utilizar RunWithElevatedPrivileges.
Por lo que para elevar correctamente los privilegios tendremos que crear una nueva instancia de SPsite dentro del bloque RunWithElevatedPrivileges.

Por ejemplo:

SPSecurity.RunWithElevatedPrivileges(delegate() {
using (SPSite elevatedSiteCollection = new SPSite(this.Site.ID)) {
using (SPWeb elevatedSite = elevatedSiteCollection.OpenWeb(this.Web.ID)) {
// access elevatedSiteCollection and
//elevatedSite as SHAREPOINT\System
}
}
});

Puedes ver un ejemplo muy completo en http://msdn.microsoft.com/es-es/magazine/cc163287.aspx

Anónimo dijo...

Hola Mario. Ante todo, darte las gracias por tus buenos consejos en el mundo Sharepoint.

Acerca de tu post, me surge la duda de si podemos manejar objetos de Sharepoint desde otro servidor, es decir, imaginate que tenemos un servidor con sharepoint con el que desarrolla y en que se quieren ejecutar aplicaciones de consola para acceder y manejar el resto de servidores o granjas de sharepoint de entornos de integracion.

No he visto con rotundidad que esto sea posible o no lo sea. ¿que opinas?

Mario Cortés Flores dijo...

Hola, la verdad es que no he probado nunca a ejecutar tareas de configuración y mantenimiento de la granja desde un servidor que no pertenezca a la granja.

La mayoría de las operaciones puedes indicar una url como ServerContext.GetContext, SearchContext.GetContext, SPFarm.Open.

Pero eso sí, para poder ejecutar una aplicación que utiliza el modelo de objetos de sharepoint debe al menos tener instalado sharepoint en el servidor sobre el que se ejecute.

Anónimo dijo...

Hola Mario,

Tras innumerables intentos, no he logrado acceder por completo al modelo de objetos de SharePoint desde un servidor que no esté dentro de la granja. Mas concretamente falla el constructor del objeto SPSite.

He desistido en el intento, realizando esas tareas a través de web services implementados en el servidor.

Mi pregunta ahora es: ¿Por que se puede acceder a través del navegador web (desde cualquier máquina) a la administración de SharePoint y no se puede desde esa misma máquina por código?

En fin, cosas de la vida.

Gracias de nuevo Mario, por compartir tus conocimientos.

Mario Cortés Flores dijo...

La consola de administración de Sharepoint al fin y al cabo es una aplicación web cuyo proceso se está ejecutando en una máquina que pertenece a la granja de sharepoint.