MOSS: Leyendo los user profiles

Uno de los retos que se nos ha planteado en el CIIN es como visualizar el directorio de empleados de una organización (que en este caso se encuentra en una estructura de directorio activo, DA) en un team site de MOSS utilizando las capacidades que este nos aporta.  El primer punto a tener en cuenta es que MOSS a través de los Shared Services Providers (SSP), y en concreto el SSP referente a User Profiles, nos permite cargar la información de todos los usuarios de una organización de manera manual o automática definiendo un origen de importación que puede ser un DA, un recurso de DA, un directorio LDAP o bien un Business Data Catalog (BDC).



(Como ocurría con la configuración de la búsqueda, la importación automática de perfiles se puede programar con una cierta periodicidad. Mi compañero Pablo me ha prometido que contará en detalle cómo se realiza la importación de profiles de un DA en su próximo post, ya veréis que es algo realmente interesante y con algún truquito que otro.).


Una vez importados los perfiles de usuario, el listado de los mismos es visible desde la administración central de MOSS (en el SSP Perfiles de usuarios y propiedades-> Ver perfiles de usuario).



Cada user profile tiene un serie de propiedades, muchas de las cuáles coinciden con las que presenta un DA para los distintos usuarios que almacena. Pero, ¿Cómo podemos ver este listado de usuarios fuera de la administración central? ¿Se podría además ver para cada usuario la ficha de detalle del mismo?  La pregunta difícil es la primera, puesto que una vez sepamos como listar los user profiles fuera de la administración central, visualizar el detalle de un profile en concreto no tiene porque ser complejo.



Alternativas para visualizar los user profiles fuera de la administración central


Para visualizar todos los user profiles creados, hay varias alternativas:


·         A través de crear un sitio de búsqueda específico pare personas, de manera que una vez realizada la correspondiente indexación podremos buscar usuarios concretos en el listado importado.


·         Atacando el servicio web UserProfile.asmx de nuestra máquina MOSS y mostrando el listado de usuarios en una web part o en una lista de MOSS.


·         Atacando el modelo de objetos de MOSS y mostrando el listado de usuarios en una web part o en una lista.


En nuestro caso, hemos optado por la tercera opción y como no queremos duplicar la información de los profiles en una lista, construiremos una web part que permita mostrar el listado de profiles y que al seleccionar un profile concreto permita ver el detalle del mismo de forma similar al estilo preview panel de una lista de WSS 3.0 / MOSS:



Resolviendo el problema


Tras una serie de pruebas de concepto sobre como leer los user profiles y ver que objetos hay que manejar para cumplir con los requerimientos comentados, pasamos a crear la web part correspondiente. Para agilizar el proceso de creación, y despliegue posterior de la web part utilizamos las extensiones de WSS 3.0 para Visual Studio (aprovecho para comentar que es una pena que no estén oficialmente soportadas por Microsoft). Ya comentamos en un post previo, que al utilizar estas extensiones se añaden las extensiones e infrasestructura necesaria para desplegar la web part sin más que hacer el deploy de la solución.



Para poder trabajar con los user profiles, y construir una web part que dinámicamente muestra un listado de los mismos y permite visualizar el detalle del profile seleccionado necesitamos añadir e importa al proyecto los siguientes espacios de nombres:







using System.Web.UI.WebControls;


using System.Data;


using Microsoft.Office.Server;


using Microsoft.Office.Server.UserProfiles;


using System.Web;


using System.Drawing;


En negrita he marcado los espacios de nombres necesarios para atacar los user profiles. El resto de espacios de nombres se utilizan para la visualización de los profiles. Antes de seguir, os voy a mostrar (esto es lo que se dice empezar por el final) que pinta tiene la web part que finalmente se despliega para enlazarlo con los pasos de creación:



Los pasos para llegar a construir la web part anterior son los siguientes:


Sobreescribir el método CreateChildControls()


Lo primero que haremos en el código de la web part es sobreescribir el método CreateChildControls(), que nos permitirá crear todos los controles que va a incluir nuestra web part (y que se construyen de manera dinámica en base a la información de los user profiles). Antes de crear los controles hijos necesarios, es en este método donde obtendremos el listado de profiles que tenemos importados / creados en el SSP de MOSS. Para el caso expuesto, el código necesario para la recuperación de los user profiles es el siguiente:







                SPSite spsSitio = SPControl.GetContextSite(this.Context);


                ServerContext scContexto = ServerContext.GetContext(spsSitio);


                UserProfileManager upManager = new UserProfileManager(scContexto);


 


                //Construimos la tabla con las columnas que nos interesan


                dtData.Columns.Add(ID_PROFILE);


                dtData.Columns.Add(CAMPO1_PROFILE);


                dtData.Columns.Add(CAMPO2_PROFILE);


                dtData.Columns.Add(CAMPO3_PROFILE);


                dtData.Columns.Add(CAMPO4_PROFILE);


                dtData.Columns.Add(CAMPO5_PROFILE);


                dtData.Columns.Add(CAMPO6_PROFILE);


                dtData.Columns.Add(CAMPO7_PROFILE);


                dtData.Columns.Add(CAMPO8_PROFILE);


                dtData.Columns.Add(CAMPO9_PROFILE);


 


                //Llenamos la tabla


                foreach (UserProfile cUser in upManager)


                {


                    UserProfileValueCollection upValue;


                    dtRow = dtData.NewRow();


                    foreach (Property pPropiedad in cUser.ProfileManager.Properties)


                    {


                        //ID


                        if (pPropiedad.Name == ID_PROFILE)


                        {


                            upValue = cUser[pPropiedad.Name];


                            dtRow[upValue.Property.Name] = upValue.Value;


                        }


                        //Campo1


                        if (pPropiedad.Name == CAMPO1_PROFILE)


                        {


                            upValue = cUser[pPropiedad.Name];


                            dtRow[upValue.Property.Name] = upValue.Value;


                        }


                        //Campo2


                        if (pPropiedad.Name == CAMPO2_PROFILE)


                        {


                            upValue = cUser[pPropiedad.Name];


                            dtRow[upValue.Property.Name] = upValue.Value;


                        }


                        //Campo3


                        if (pPropiedad.Name == CAMPO3_PROFILE)


                        {


                            upValue = cUser[pPropiedad.Name];


                            dtRow[upValue.Property.Name] = upValue.Value;


                        }


                        //Campo4


                        if (pPropiedad.Name == CAMPO4_PROFILE)


                        {


                            upValue = cUser[pPropiedad.Name];


                            dtRow[upValue.Property.Name] = upValue.Value;


                        }


                        //Campo5


                        if (pPropiedad.Name == CAMPO5_PROFILE)


                        {


                            upValue = cUser[pPropiedad.Name];


                            dtRow[upValue.Property.Name] = upValue.Value;


                        }


                        //Campo6


                        if (pPropiedad.Name == CAMPO6_PROFILE)


                        {


                            upValue = cUser[pPropiedad.Name];


                            dtRow[upValue.Property.Name] = upValue.Value;


                        }


                        //Campo7


                        if (pPropiedad.Name == CAMPO7_PROFILE)


                        {


                            upValue = cUser[pPropiedad.Name];


                            dtRow[upValue.Property.Name] = upValue.Value;


                        }


                        //campo8


                        if (pPropiedad.Name == CAMPO8_PROFILE)


                        {


                            upValue = cUser[pPropiedad.Name];


                            dtRow[upValue.Property.Name] = upValue.Value;


                        }


                        //Campo9


                        if (dtRow[CAMPO3_PROFILE].ToString() != “” || dtRow[CAMPO4_PROFILE].ToString() != “”)


                        {


                            if (dtRow[CAMPO4_PROFILE].ToString() == “”)


                            {


                                dtRow[CAMPO9_PROFILE] = dtRow[CAMPO3_PROFILE];


                            }


                            else


                            {


                                dtRow[CAMPO9_PROFILE] = dtRow[CAMPO3_PROFILE] + “- ” + dtRow[CAMPO4_PROFILE];


                            }


 


                        }


 


                    }


                    dtData.Rows.Add(dtRow);


                }


Como vemos, una vez obtenido el sitio (a través de spsSitio que es un objeto de tipo SPSite y utilizando el método GetContext para obtener el sitio actual) en el que se va a ejecutar la web part y el contexto del servidor (objeto scContexto, de tipo ServerContext y utilizando el método GetContext para obtener el contexto actual), los perfiles de usuario almacenados en nuestro servidor de MOSS se obtienen a partir de un objeto de tipo UserProfileManager (clase utilizada para acceder a los datos de perfiles de usuario) que a partir del contexto del servidor nos devuelve una colección de objetos de tipo UserProfile. Una vez que tenemos la colección de user profiles, lo siguiente que hacemos es recorrerla y nos quedamos con las propiedades que nos interesan de cada user profile. Para ello:


·         En el primer nivel de anidamiento, vamos recorriendo cada objeto de tipo UserProfile almacenado en la colección de user profiles.







  foreach (UserProfile cUser in upManager)


 


·         En el segundo nivel de anidamiento, seleccionamos las propiedades que nos interesen de cada user profile.







                foreach (UserProfile cUser in upManager)


                {


                    UserProfileValueCollection upValue;


                    dtRow = dtData.NewRow();


                    foreach (Property pPropiedad in cUser.ProfileManager.Properties)


                    {


En este segundo nivel, para realizar el recorrido por la colección de propiedades (cUser.ProfileManager.Properties) de cada user profile estamos utilizando un objeto de UserProfiles.Property, que es la clase que representa la definición para una propiedad de un user profile. Finalmente, cada propiedad que necesitemos la estamos almacenando en el objeto upValue que es de tipo UserProfileValueCollection, que nos permite construir una colección con las propiedades que nos interese visualizar para cada user profile. A continuación, cada propiedad la asignamos a un objeto de tipo DataRow que luego añadiremos al objeto DataTable que aparece al principio del listado. Este último objeto es el que finalmente utilizaremos para construir la web part, tanto para visualizar la lista de user profiles como para luego mostrar el detalle del user profile seleccionado.


Una vez que tenemos los user proflies, ya podemos empezar a construir los controles hijos que constituirán nuestra web part.







                ////Tabla Principal


                tblContenedor = new Table();


 


                //Añadimos una fila       


                tblRow = new TableRow();


 


                //Lista Empleados       


                tblCell = new TableCell();


                tblListaEmpleados = new Table();


                tblCell.Controls.Add(this.tblListaEmpleados);


                tblRow.Cells.Add(tblCell);


 


                //Detalle Empleado       


                tblCell = new TableCell();


                this.tblDetalleEmpleado = new Table();


                this.tblDetalleEmpleado.CssClass = ESTILOS_FICHA_EMPLEADO;


                tblCell.Controls.Add(this.tblDetalleEmpleado);


                tblRow.Cells.Add(tblCell);


 


                //Lo añadimos todo a la tabla principal…


                this.tblContenedor.Rows.Add(tblRow);


 


                //Radiobutton


                rdbListaEmpleados = new RadioButtonList();


                rdbListaEmpleados.EnableViewState = true;


                rdbListaEmpleados.AutoPostBack = true;


                this.rdbListaEmpleados.SelectedIndex = 0;


                this.rdbListaEmpleados.CssClass = ESTILOS_LISTA_EMPLEADOS;


 


                this.rdbListaEmpleados.DataSource = dtData;


                this.rdbListaEmpleados.DataValueField = ID_PROFILE;


                this.rdbListaEmpleados.DataTextField = CAMPO9_PROFILE;


                this.rdbListaEmpleados.DataBind();


                //Evento para el radiobutton


                this.rdbListaEmpleados.SelectedIndexChanged +=


                    new System.EventHandler(this.rdbListaEmpleados_SelectedIndexChanged);


 


                //Añadimos el radiobutton list a la tabla


                tblRow = new TableRow();


                tblCell = new TableCell();


                tblCell.Controls.Add(this.rdbListaEmpleados);


                tblRow.Cells.Add(tblCell);


                tblContenedor.EnableViewState = true;


                //Añadiendo las celdas a la tabla


                this.tblListaEmpleados.Rows.Add(tblRow);


 


                if (dtData.Rows.Count > 0)


                {


                    this.Controls.Add(this.tblContenedor);


 


                }


Lo más interesante del código anterior (la presentación de la información utilizando una tabla principal que contiene otras dos tablas que contendrán el listado de usuarios por un lado, y el detalle del empleado seleccionado es lo de menos) es lo siguiente:


·         La fuente de datos utilizada para rdbListaEmpleados es justo el objeto DataTable construido a partir de los datos de los perfiles de usuario. En concreto, estamos utilizando los campos ID_PROFILE que contiene el ID único para cada user profile (propiedad UserProfile_GUID de cada user profile) y CAMPO9_PROFILE (que contiene la concatenación de los campos PreferredName y WorkPhone), el primero lo vinculamos a la propiedad DataValueField de rdbListaEmpleados y el segundo a la propiedad DataTextField.







                this.rdbListaEmpleados.DataSource = dtData;


                this.rdbListaEmpleados.DataValueField = ID_PROFILE;


                this.rdbListaEmpleados.DataTextField = CAMPO9_PROFILE;


                this.rdbListaEmpleados.DataBind();


 


·         La forma en que se añade el manejador para el evento SelectedIndexChanged del control rdbListaEmpleados que es de tipo RadioButtonList():







                this.rdbListaEmpleados.SelectedIndexChanged +=


                    new System.EventHandler(this.rdbListaEmpleados_SelectedIndexChanged);


Como vemos, el manejador se añade a rdbListaEmpleados utilizando la forma ya conocida de añadir manejadores en C# para controles web o de Windows forms. Algo importante es que tenemos que configurar la propiedad AutoPostBack a True para que se pueda disparar el evento SelectedIndexChanged de rdbListaEmpleados y se ejecute el manejador.


·         Como se añaden los controles hijos a la web part, y que como ya habéis visto y conoceréis, implica hacer una llamada del método Add() de la colección de controles de la web part (en este caso, lo mismo se utiliza cuando creamos user controls, páginas ASP.NET, formularios Windows Forms,…). Como vemos, para añadir todos los controles hijos nos basta con añadir tblContenedor que es la tabla maestra que contiene las tablas auxiliares utilizadas que a su vez contienen los controles necesarios para visualizar los datos de un user profile.







this.Controls.Add(this.tblContenedor);


 


Sobreescribir el método Render()


Una vez que ya tenemos creados los controles hijos y añadidos a la web part, necesitamos que estos se visualicen. Se consigue sobreescribiendo el método Render() de la web part que nos permite justamente eso: renderizar los controles hijos de la web part de manera que al final los visualicemos de manera transparente como controles web típicos (y que nos devuelve el servidor web).







        protected override void Render(HtmlTextWriter writer)


        {


 


            if (dtData.Rows.Count > 0)


            {


 


                if (this.rdbListaEmpleados.SelectedIndex == 0)


                {


                    this.tblDetalleEmpleado.Rows.Clear();


                    this.rdbListaEmpleados_SelectedIndexChanged(this.rdbListaEmpleados, null);


                }


                this.tblContenedor.RenderControl(writer);


            }


            else


            {


                if (this.lblErrorProducido.Text == “”)


                {


                    writer.Write(“<b> El directorio de empleados está vacio o no cargado </>”);


                }


                else


                {


                    this.lblErrorProducido.RenderControl(writer);


                }


 


            }


        }


Como vemos, el renderizado efectivo de los controles de la web part se realiza en la línea this.tblContenedor.RenderControl(writer). Es decir, el método RenderControl de nuestro control principal es el que se encarga de hacer dicho renderizado a través de un objeto de tipo HtmlTextWriter.


Manejador del objeto RadioButtonList()


Finalmente, sólo nos queda incluir el código del manejador para el evento SeletedIndexChangeg() del objeto rdbListaEmpleados. En este código es dónde se construirá, para cada user profile seleccionado, la ficha de detalle de un usuario. Una muestra de cómo se construye dicha ficha de detalle es el siguiente listado:







foreach (DataRow dr in dtData.Rows)


            {


                //Verificamos la opción elegida por el usuario


                if (this.rdbListaEmpleados.SelectedItem.Value == dr[ID_PROFILE].ToString())


                {


                    //Nombre


                    tblRow = new TableRow();


                    tblCell = new TableCell();


                    tblCell.Text = ETIQUETA1_PROFILE;


                    tblRow.Cells.Add(tblCell);


                    tblCell = new TableCell();


                    tblCell.Text = dr[CAMPO1_PROFILE].ToString();


                    //tblCell.BackColor = System.Drawing.Color.LightSkyBlue;


                    tblRow.Cells.Add(tblCell);


                    this.tblDetalleEmpleado.Rows.Add(tblRow);


 


                  


 


                                        //Fotografía


                    if (dr[CAMPO8_PROFILE].ToString() != “”)


                    {


                        tblRow = new TableRow();


                        tblCell = new TableCell();


 


                        tblRow.Cells.Add(tblCell);


                        tblCell = new TableCell();


 


                        imgFotografia = new System.Web.UI.WebControls.Image();


                        imgFotografia.ImageUrl = dr[CAMPO8_PROFILE].ToString();


                        tblCell.Controls.Add(imgFotografia);


 


                        tblCell.Controls.Add(imgFotografia);


                        tblRow.Cells.Add(tblCell);


                    }


 


                    //Lo añadimos todo a la tabla!


 


                    this.tblDetalleEmpleado.Rows.Add(tblRow);


                }


            }


        }


Como vemos, construir la ficha de detalle del usuario seleccionado es una tarea sencilla. No tenemos más que comprobar cuál es el elemento seleccionado por el usuario y a partir de eta comprobación construir la ficha de detalle del empleado que estará contenida en la tabla tblDetalleempleado.


Despliegue y uso de la web part


Una vez que hemos codificado las distintas partes de la web part y tras comprobar que todo compila bien, procedemos a desplegar la web part. En este caso, y gracias a las extensiones de WSS 3.0 para Visual Studio, el despliegue es realmente sencillo puesto que se realiza de manera automática a través de la opción Implementar Solución que tenemos disponible en Generar -> Implementar Solución. El detalle de lo que se hace en este proceso de implementación ya lo comentamos en un post previo, por lo que os remito a ese post.


Ya sólo nos falta asegurar que la web part está desplegada en todos los sitios que nos interesa a través del Administrador de Soluciones de la administración central de MOSS:



Tras comprobar que la feature correspondiente a la web part está activada, ya podemos utilizar la web part en los sitios de MOSS dónde la hayamos implementado (ya sabéis, Configuración del sitio -> Características del sitio -> Características de la colección de sitios).



Sin más, esto es lo que os quería contar sobre los user profiles de MOSS. La verdad es que el tema es chulo, aunque al principio nos dio algún que otro quebradero de cabeza tanto listar los user profiles como visualizarlos del modo requerido. Por supuesto, esta web part es muy sencilla y se puede mejorar mucho ( a ver si alguien se anima). Os dejo el código de la web part aquí.

Publicado por

Juan Carlos González

Juan Carlos es Ingeniero de Telecomunicaciones por la Universidad de Valladolid y Diplomado en Ciencias Empresariales por la Universidad Oberta de Catalunya (UOC). Cuenta con más de 12 años de experiencia en tecnologías y plataformas de Microsoft diversas (SQL Server, Visual Studio, .NET Framework, etc.), aunque su trabajo diario gira en torno a SharePoint & Office 365. Juan Carlos es MVP de Office Servers & Services desde 2015 (anteriormente fue reconocido por Microsoft como MVP de Office 365 y MVP de SharePoint Server desde 2008 hasta 2015), coordinador del grupo de usuarios .NET de Cantabria (Nuberos.Net, www.nuberos.es), co-fundador y coordinador del Grupo de Usuarios de SharePoint de España (SUGES, www.suges.es), así como co-director de la revista gratuita en castellano sobre SharePoint CompartiMOSS (www.compartimoss.com). Hasta la fecha, ha publicado 8 libros sobre SharePoint & Office 365 y varios artículos en castellano y en inglés sobre ambas plataformas.

24 comentarios sobre “MOSS: Leyendo los user profiles”

  1. Un excelente artículo, Juan Carlos.
    Sin embargo tengo una duda: Quiero rescatar los user profiles ya no a nivel de web parts, o búsquedas, sino para tratar esos parámetros de usuario dentro de workflows generados con Designer. En concreto necesito expresar que si un usuario pertenece a un departamento concreto, tenga un validador diferente a si es de otro departamento (cada departamento tiene un validador). Pero designer, en su condicional “Creado Por”, aunque deja seleccionar grupos, no mira si el usuario que ha generado el elemento de la lista pertenece al grupo seleccionado, solo compara los literales y ve que “Administradores != intranetjuan”.

    Por ello intenté seguir el segundo método que comentas y atacar el UserProfile.asmx desde InfoPath, el problema es que no deja publicar el formulario si no tiene un certificado digital de confianza…

    Mi pregunta es: ¿Cómo puedo ver el “Departamento” al que pertenece el usuario que genera un formulario para tratar ese campo en el flujo de trabajo? He pensado en generar un acceso de datos en Designer, pero no sé en que tabla del SQLServer se guardan los user profiles, o en hacer un BDC que ataque a esa tabla de la BD… ¿Alguna sugerencia? No quisiera entrar en Visual Studio, ya que entonces todo se complica en exceso.

    Gracias, un saludo!

    Ignasi Tebé.

  2. Hola Ignasi,
    Para poder utilizar el departamento tienes que tener incluida esta columna en la librería de formularios o en la lista dónde estés guardando el formulario. Si utilizas la opción de Infopath (Aqui te hago una pregunta, ¿has conseguido que se recupere el departamento en el preview del formulario antes de publicarlo? Si lo has conseguido con la llamada al servicio web, veo más factible que consigas publicar el formulario en tu sitio de MOSS, que por lo que comentas te falla ¿estás utilizando https en el sitio MOSS dónde quieres publicar? Si no lo estás utilizando, en principio debería funcionarte la publicación, y si lo usas también porque Infopath no imponen ninguna restricción en este sentido (por lo que yo se).

    En definitiva, la mejor opción es que consigas tener la información del departamento en el formulario bien porque la lees del user profile o utilizas una lista auxiliar en la que guardes esa información. Esta es una alternativa para tu segunda pregunta, utilizando la publicación.

    No te recomiendo que intentes hacer una acceso a datos en la BD de contenidos de MOSS, piensa que la idea de Sharepoint es que trabajemos con listas y elementos de listas, en ningún caso a nivel de la BD. Por el mismo motivo, no aplica el que te crees un BDC para esto, el BDC está pensado para atacar a sistemas backend fuera de MOSS, y además implica que tengas las herramientas (BDC Meta Man) o conocimientos (XML) necesarios para construirlos.

    Si tienes más dudas, aqui estamos para echarte un cable

    Un saludo

    JC

  3. Buenos dias Juan Carlos

    Quizás el problema venga porque mi formulario se publica via web (con form services)y eso limita las opciones del mismo. Pero en este proyecto no hay licencias de infopath para todas las máquinas cliente, así que ha de ser via web…

    Mi servidor no es https, es un acceso http normal.

    Yo también pienso que la mejor manera es con Infopath, pero mi desesperación viene cuando consigo verlo todo correctamente en la vista previa, pero no consigo publicar el formulario en la biblioteca. Anteriormente esa biblioteca tenía el mismo formulario sin el acceso a datos del userprofiles y no daba problema ninguno, pero en el momento que le configuro el campo Departamento y muestro los datos con GetUserProfileByName (todo ok hasta aquí), necesito habilitar en Opciones de formulario–>Seguridad y confianza la casilla de “plena confianza” y para ello me pide firmar digitalmente el formulario.

    Si la firma digital no es reconocida, te obliga, en el momento de la publicación a guardarlo en un directorio (no deja publicarlo directamente como plantilla de la lista).

    He intentado cargar la plantilla en la lista directamente sin infopath, pero entonces no la detecta como form services e intenta abrir el formulario con infopath en un cliente que no tiene dicho programa.

    Lo de la lista auxiliar que comentas, ¿te refieres a crear una lista usuario-departamento de forma manual? eso implicaría un mantenimiento muy engorroso, no? En una empresa donde hay bajas y altas contínuas, volvería loco al admninistrador del sistema. Si hubiera un proceso para automatizar eso… Lo ideal es leer del userprofile del AD, pero no se como llegar a esos datos directamente en las listas de SharePoint…

    No acabo de entender la filosofía de infopath, debería dejarme publicar el formulario que yo quiera, bajo mi responsabilidad, sin obligarme a firmalo digitalmente con una firma reconocida!

    Gracias por tu tiempo, Juan Carlos.

  4. Hola Ignasi,
    Íbamos a hacer justo esa prueba para contestarte, pero ya las hecho tú, asique perfecto. En principio, la llamada a un sevicio web de MOSS desde Infopath no implica que tengas que marcar el formulario como confianza plena, sino que como muy bien has hecho basta con que marques Dominio. En principio, salvo que alguien me corrija, esta opción no te dará problemas de seguridad.

    Enhorabuena!

    JC

  5. Muchas gracias Juan Carlos, para mí ha sido una revolución encontrar vuestra web. Los artículos de “recopilación de enlaces interesantes” son geniales y me han abierto un mundo de blogs sobre MOSS 2007 im-presionantes.

    Hasta ahora solo miraba en un par de webs y en muchos desafíos estaba más solo que la una, pero la comunidad MOSS 2007 crece de forma apabullante, y a dia de hoy existen grandes referencias como la vuestra.

    Sin mas os animo a seguir en vuestra línea, inspiradora para muchas hormiguitas como yo.

  6. Gracias a tí, los comentarios que ponéis la gente que lee habitualmente Geeks y las cuestiones que nos planteáis nos animan a seguir contando cosas interesantes.
    Un saludo
    JC

  7. Hola,

    Estoy intentando crear un webpart que muester información de usuario de forma aleatoria siguiente el ejemplo mostrado en este post. La cuestión es el desarrollo me funciona perfectamente con el usuario administrador. Sin embargo, salta una excepción al acceder con otros usuarios.

    La línea de código que da problemas es la siguiente:

    For Each upro As UserProfile In upManager
    //…
    Next

    La excepción:

    System.UnauthorizedAccessException: Access Denied: Only an administrator may enumerate through all user profiles

    Hay alguna maner de solucionar este problema?

    Un Saludo,
    Gracias,

  8. Hola Aitor,
    Lo que te está pasando es que estás realizando una acción que requiere permisos de usuario con ciertos privilegios. Para evitarte este problema, lo que tienes que hacer es una elevación de privilegios en el código en el que lees los profiles (con tu comentario me acabo de dar cuenta que en el ejemplo subido no lo hice). Simplemente sería algo tal que así:

    SPSecurity.RunWithElevatedPrivileges(delegate()
    {
    // Obtener un contexto de seguridad
    using (SPControl.GetContextSite(this.Context))
    {

    ‘Código lectura de los user profiles

    }
    };

    Con esto se te debería solucionar el problema que comentas.

    Un saludo
    JC

  9. Hola,

    He probado la solución que me has propuesto, de la siguiente forma:

    Public Sub getUsuarioElevado()
    Dim i As Integer
    Dim us2 As New Usuario
    Using spsitio = SPControl.GetContextSite(Me.Context)
    Dim scContexto As ServerContext = ServerContext.GetContext(spsitio)
    Dim upManager As UserProfileManager = New UserProfileManager(scContexto)
    i = Int(Rnd() * (upManager.Count – 1))
    Dim aGuid As New ArrayList
    For Each upro As UserProfile In upManager
    aGuid.Add(upro.ID)
    Next
    Dim up As UserProfile = upManager.GetUserProfile(aGuid(i))
    us2.nombre = up(“AccountName”).Value.ToString()
    Try
    us2.urlImagen = up(“PictureURL”).Value.ToString
    Catch ex As Exception
    us2.urlImagen = String.Empty
    End Try
    Try
    us2.email = up(“workEmail”).Value.ToString
    Catch ex As Exception
    us2.email = String.Empty
    End Try
    Me.us = us2
    End Using
    End Sub

    Llamo a este procedimiento de la siguiente forma:

    Dim MiCodigoElevado As SPSecurity.CodeToRunElevated = New SPSecurity.CodeToRunElevated(AddressOf Me.getUsuarioElevado)

    Ahora bien, sigue dándome el mismo error. Solamente permite acceder al atributo “count” de la clase “UserProfileManager” si eres administrador. De la misma forma solamente permite recorrer la lista “UserProfileManager” si eres administrador.

    No sé si se me esacapa algo.

    Un Saludo,
    Gracias,

  10. Hola!
    El problema es que así no estás haciendo la elevación de privilegios de forma corecta. Tienes que utilizar SPSecurity.RunWithElevatedPrivileges(delegate()…fijate en como te lo he puesto para C#. En VB.NET es similar, te paso la referencia y el ejemplo que ponen en MSDN:
    http://msdn2.microsoft.com/en-us/library/microsoft.sharepoint.spsecurity.runwithelevatedprivileges.aspx

    Declaración:
    Public Shared Sub RunWithElevatedPrivileges ( _
    secureCode As CodeToRunElevated _
    )

    Uso:

    Dim secureCode As CodeToRunElevated

    SPSecurity.RunWithElevatedPrivileges(secureCode)

    COmo yo lo he hecho en C# es justo con un método anónimo que también tienes en VB.NET.

    Espero que con esta info. puedas resolver el problema.

    JC

  11. Hola,

    Me he dado cuenta que en el comentario anterior no he puesto como llamaba al método RunWithElevatedPrivileges:

    Dim MiCodigoElevado As CodeToRunElevated = New CodeToRunElevated(AddressOf getUsuarioElevado)
    RunWithElevatedPrivileges(MiCodigoElevado)

    El método donde quiero establecer permisos de administrador es el mismo que expliqué en el anterior comentario. He seguido las pautas establecidas por MSDN y por siguiente artículo:

    http://msdn2.microsoft.com/en-us/library/bb466220.aspx

    Aún así, no logro realizar correctamente la impersonalización en VB .NET. La única solución que se me ocurría ha sido programar el webpart en C#, pero también falla.

    Un Saludo,
    Gracias,

  12. Hola Aitor,
    El viernes estuve revisando y probando la web part con un usuario pertenciente al grupo de lectores de un sitio de MOSS y puede comprobar que efectivamente tenías razón y que ni con elevación de privilegios se consiguen leer los user profiles almacenados en el profile store. He estado buecenado por el SDK de MOSS y no contiene ninguna mención a esta situación, lo que me parece sorprendente pues puede ser un requerimiento usual. He encontrado varios enlaces en los que explican como se podría resolver el problema, pero de momento no he conseguido que se listen los user profiles aún consiguiendo que la cuenta que accede a los mismos es NETWORK SERVICE que si tiene permisos para acceder a los profiles. Si te parece, pásame tu e-mail personal y a ver si conseguimos sacar algo en claro de esto. El mio es jcgonzalez@ciin.es. Aqui te paso los links que he estado mirando y en los que tratan el problema:

    • Lo que he intentado el viernes es http://sharingpoint.blogspot.com/2006/03/who-am-i-demystifying-sharepoint.html, y con esto conseguí que la entidad que accede y realiza todas las operaciones sea NETWORK SERVICE (Pablo lo vio conmigo el viernes)…pero sigue dando el mismo fallo…lo que me mosquea de este link es lo que pone al final: Of all the methods that are available for running code under an administrative context in SharePoint, this combination of impersonation and application domains is the only one that I’ve found works in every situation I’ve tried. Es decir, no me queda claro si de la forma que se expone en este artículo se está ejecutando el código como administrador en el contexto de sharepoint que parece que es la única forma de leer los profiles del objeto que mencionas.
    • Linkados a este enlace hay otros en la que se hace lo mismo de otras formas más o menos complicadas pero que creo (salvo que Ángel cuando pueda echarle un ojo me diga lo contrario) llevan a lo mismo:
    o http://www.lcbridge.nl/vision/impersonation.htm, el problema de la solución de este link es que utilizan objetos que si dice Visual Stuido 2005 están obsoletos.
    o http://www.bluedoglimited.com/SharePointThoughts/ViewPost.aspx?ID=7, esa es la forma que parece más compleja y que parece que lleva a lo mismo.
    o Otro más: http://mindsharpblogs.com/todd/archive/2005/05/03/467.aspx

    Un saludo y gracias por tu comentarios

    JC

    p.d: a ver si conseguimos que funcione.

  13. Aquí lo que debeis revisar es los permisos que tiene el usuario con el que se elevan los permisos (AppPool), para configurarlo en el SSP.
    – Manage user profiles
    – Use Personal Features
    – Read profiles

    La consideración a incluir es que la aplicación que corra con dichas credenciales, sea voluntaria o ‘involuntariamente’ tendrá esos privilegios.
    De ahí por ejemplo el comentario del Best Practice de emplear cuentas distintas para el CentralAdmin y cualquier sitio.

    un saludo

  14. Hola a los dos!
    Gracias por los comentarios, la verdad es que el asunto de leer los profiles nos ha dado muchos quebraderos de cabeza…al final opte por replicarlos en una lista de contactos de WSS 3.0 y ahí lo había dejado, pero a la vuelta de mis vacaciones probaremos lo que nos comentas para ver si conseguimos leer los profiles sin tener que duplicarlos. Muchas gracias Edim y gracias también Carlos.

    JC

  15. Hola, muy buen post, gracias por la información, pero quisiera hacer una pequeña pergunta: si yo quisiera que MOSS leyera datos desde un BDC para colocarlos como datos de los perfiles de usuarios, ¿cómo se realiza esta acción? ya tengo escrito el BDC, el cual tiene un solo filtro el que de alguna forma pretendo que haga referencia a la propiedad account name de MOSS. Por ejemplo, tengo varios usuarios que no tienen valores en la propiedad WorkEmail debido a que no estan puestas en el AD, y mediante el BDC pretendo validar que la cuenta de usuario o Account Name, donde ésta sea igual a la que existe en la conexión con el BDC me coloque el correo que se encuentre en ese registro.

    De antemano gracias por sus respuestas.

    Atte
    Alberto

  16. Hola Alberto,
    Para hacer lo que me comentas, no entiendo muy bien que es lo que quieres hacer…explicame de forma más precisa que necesitas porque tu pregunta es un poco liosa.

    Un saludo

    JC’s

  17. Ya… a ver si ahora me explico bien… en mis usuarios de MOSS no están puestos los correos electrónicos, sólo los nombres de usuarios y de dominio, entonces yo desde una base de datos que tengo en SQL 2005 quiero extraer los correos de los usuarios mediante la conexión con BDC. La idea es que en la base de datos hay un campo que guarda los nombres de usuario de dominio, y en base a este campo saber a qué usuario le corresponde cada correo… quisiera saber paso a paso cómo se hace eso por favor… no sé si me expliqué bien…

    De antemano Gracias
    Alberto

  18. Buenas Alberto,
    Me imaginaba que ibas por ahí. Yo lo que haría (intentaré hacer una prueba de concepto,pero no te prometo nada en cuanto a cuando la tendré acabada) es crear un servicio que te lea por una parte el contenedor de los profiles (la cuenta de usuario), busque la cuenta de e-mail correspondiente en la BD SQL Server y a continuación actualice la propieda de e-mail…vamos, que necesitas programación. Desde mi punto de vista, el BDC no lo necesitas para lo que quieres hacer.

    Un saludo

    JC’s

  19. Gracias por la respuesta, Carlos, entiendo lo que me quieres decir, el problema es que soy algo novato en esto de MOSS, ¿me podrías orientar un poco en cuanto a la solución de crear un servicio que haga lo que yo deseo?
    De antemano muchas gracias por todo, y disculpe que lo moleste.

    Atte.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos necesarios están marcados *