Custom InfoPath Email Activity para Sharepoint Designer 2010.

por Mauricio Rodriguez  14. marzo 2012

Recientemente he finalizado, junto con el equipo de Sharepoint, un proyecto interesante en cuanto al nivel de personalización que el cliente exigía.

El requerimiento era crear un workflow de aprobación reusable para formularios de solicitud de empleo. Lo significativo de este workflow era que debía trabajar sin lista de tareas ( esencial en los workflows de aprobación de Sharepoint ) y con emails personalizados, los cuales debían llevar adjunto el formulario (hecho en InfoPath), de manera que los usuarios involucrados en el proceso de solicitud, puedan abrirlos desde Outlook y completarlos.

Desde el inicio pensamos en una Custom Activity que pueda cumplir con el requerimiento de envío de mails .

En esta oportunidad vamos a ver como crear una Custom Activity para enviar mails con formularios InfoPath adjuntos para poder usarla desde el Sharepoint Designer en el diseño de una solución.

1 - Creación de la estructura de la solución.

Para comenzar vamos a crear un Empty SharePoint Project en Visual Studio 2010.

Le damos nombre al proyecto y seleccionamos “Ok”. Se abrirá una ventana en la cual deberemos especificar el Servidor de Sharepoint donde se hará el deploy de la solución, y si lo queremos hacer como Sandboxed Solution o FarmSolution. En nuestro ejemplo vamos a usar una Farm Solution.

image

Finalizamos, y Visual Studio nos creará un proyecto en base al template Seleccionado que se verá como la siguiente imagen.

Vamos a modificar el nombre del archivo key.snk por el nombre de nuestro proyecto, en este caso VEMN.CustomActivity. Esto es mas que nada para mantener un orden y una correspondencia.

 

Una vez hecho esto, agregamos una Feature, le damos un nombre, siempre es preferible usar el mismo nombre del proyecto para mantener una coherencia.

image

Luego vamos a agregar una Sharepoint Mapped Folder. Nos paramos sobre el proyecto, Clik Derecho –> Add –> Sharepoint Mapped Folder. Es de vital importancia el siguiente paso ya que este cambia de acuerdo al lenguaje en que esté nuestro Sitio. Como el nuestro está inglés seleccionaremos la ruta …/TEMPLATE/1033/Workflow. En caso de que nuestro sitio este en Español debemos seleccionar el directorio 3082 o el código que le corresponda.

image

Esto mapea esta carpeta a nuestro proyecto en donde vamos a agregar un archivo xml que va a contener la metadata y las definiciones de parámetros de entrada y salida de nuestra Custom Activity. Este lo llamaremos con mismo nombre que el proyecto y la extensión se la cambiaremos de .xml a .actions, como se puede ver en la imagen siguiente.

image

 

2 - Definición del archivo ACTIONS

Definiremos a continuación, el archivo .actions que acabamos de agregar. Es importante tener en cuenta que este archivo es leído por el SPD para levantar nuestra custom activity y habilitarla en el menú Action. Por lo cual deberemos prestar mucha atención al momento de crearlo, para no tener problemas en la implementación.

Nuestro xml debe quedar con las siguiente estructura. Luego discutiremos sobre que es cada cosa:

   1: <WorkflowInfo Language="en-US">
   2:    <Actions Sequential="then" Parallel="and">
   3:       <Action Name="Send mail with attachment infopath form"
   4:            ClassName="VEMN.CustomActivity.SendMailActivity"
   5:            Assembly="VEMN.CustomActivity,Version=1.0.0.0,Culture=neutral,PublicKeyToken=7819e4834cee4b0e"
   6:            AppliesTo="all"
   7:            Category="Custom Actions">
   8:           <RuleDesigner Sentence="Send mail with this %1 attachment to %2. Use %3 like sender. >
   9:              <FieldBind Field="AttachmentFileName" Text="form (url)" DesignerType="TextArea" Id="1"/>
  10:              <FieldBind Field="To,CC,Subject,Body" Text="these user(s)" DesignerType="Email" Id="2"/>
  11:              <FieldBind Field="From" Text="this user" DesignerType="TextArea" Id="3"/>  
  12:           </RuleDesigner>
  13:           <Parameters>
  14:             <Parameter Name="__Context" Type="Microsoft.SharePoint.WorkflowActions.WorkflowContext" Direction="In" />
  15:             <Parameter Name="__ListId" Type="System.String, mscorlib" Direction="In" />
  16:             <Parameter Name="__ListItem" Type="System.Int32, mscorlib" Direction="In" />
  17:             <Parameter Name="__ActivationProperties" Type="Microsoft.SharePoint.Workflow.SPWorkflowActivationProperties, Microsoft.SharePoint" Direction="Out" />
  18:             <Parameter Name="AttachmentFileName" Type="System.String, mscorlib" Direction="In" />
  19:             <Parameter Name="To" Type="System.Collections.ArrayList, mscorlib" Direction="In" />
  20:             <Parameter Name="CC" Type="System.Collections.ArrayList, mscorlib" Direction="Optional" />
  21:             <Parameter Name="Subject" Type="System.String, mscorlib" Direction="In" />
  22:             <Parameter Name="Body" Type="System.String, mscorlib" Direction="Optional" />
  23:             <Parameter Name="From" Type="System.String, mscorlib" Direction="In" />
  24:          </Parameters>
  25:       </Action>
  26:    </Actions>
  27: </WorkflowInfo>

El nodo WorkflowInfo solo definimos el lenguaje del sitio en el cual vamos a utilizar nuestra funcionalidad. El nodo Actions define la forma en el texto se construye luego de la descripción de la acción en el diseñador del flujo de trabajo. Por ejemplo, en una acción secuencial seria algo así: “<actiondescription> then”.

Lo realmente interesante está en el nodo Action, en el, los atributos Name, ClassName, Assembly, ApplyTo and Category definen: el nombre de la acción en el Menú de Acciones (o Actions Menu), la Clase y el Assembly para el código, si se refiere a List Items , sólo documentos, o todos, y la categoría bajo la cual aparecerá listada nuestra Acción.

El nodo RuleDesigner específica el texto a mostrar y los parámetros de entrada. El atributo Sentence determina la sentencia que se mostrará en el Diseñador de workflows. Podemos especificar cada parámetro con un signo % seguido de un numero, por ejemplo 1, que apuntará al primer parámetro y así sucesivamente. Estos parámetros son referenciados por los FieldBind. El atributo text del mismo reemplazara a los % del Sentence. El atributo ID debe coincidir con el orden del parámetro.

Y por ultimo el nodo Parameters y sus hijos definen los parámetros que serán pasados a nuestra Custom Activity. Los nombres de los attibutos Field de los Nodos FieldBind deben coincidir exactamente con los atributos Name de los Nodos Parameters para que sean transferidos a nuestro código.

3 - Definición de nuestra clase principal.

Crearemos una clase que llamada SendMailActivity.cs o como quieran llamarla. La misma debe heredar de System.Workflow.ComponentModel.Activity (no olvidemos agregar la referencia a esta dll) la cual contiene un método llamado Execute que es el punto clave para nuestra Custom Activity que luego vamos a sobrescribir.

Ahora, definamos las propiedades en nuestra clase de modo que coincidan con lo que acabamos de establecer en el archivo .actions para que quede todo mapeado. Este mapeo se logra gracias al objeto DependencyProperty como veremos luego.

Al inicio nos ocuparemos de definir las propiedades del WorkflowContext y luego las personalizadas.

   1: #region [ Workflow Context Properties ]
   2:  
   3: public static DependencyProperty __ContextProperty = DependencyProperty.Register("__Context", typeof(WorkflowContext), typeof(SendMailActivity));
   4:  
   5: [ValidationOption(ValidationOption.Required)]
   6: public WorkflowContext __Context
   7: {
   8:     get
   9:     {
  10:         return ((WorkflowContext)(base.GetValue(__ContextProperty)));
  11:     }
  12:     set
  13:     {
  14:         base.SetValue(__ContextProperty, value);
  15:     }
  16: }
  17:  
  18: public static DependencyProperty __ListIdProperty = DependencyProperty.Register("__ListId", typeof(string), typeof(SendMailActivity));
  19:  
  20: [ValidationOption(ValidationOption.Required)]
  21: public string __ListId
  22: {
  23:     get
  24:     {
  25:         return ((string)(base.GetValue(__ListIdProperty)));
  26:     }
  27:     set
  28:     {
  29:         base.SetValue(__ListIdProperty, value);
  30:     }
  31: }
  32:  
  33: public static DependencyProperty __ListItemProperty = DependencyProperty.Register("__ListItem", typeof(int), typeof(SendMailActivity));
  34:  
  35: [ValidationOption(ValidationOption.Required)]
  36: public int __ListItem
  37: {
  38:     get
  39:     {
  40:         return ((int)(base.GetValue(__ListItemProperty)));
  41:     }
  42:     set
  43:     {
  44:         base.SetValue(__ListItemProperty, value);
  45:     }
  46: }
  47:  
  48: public static DependencyProperty __ActivationPropertiesProperty = DependencyProperty.Register("__ActivationProperties", typeof(SPWorkflowActivationProperties), typeof(SendMailActivity));
  49:  
  50: [ValidationOption(ValidationOption.Required)]
  51: public SPWorkflowActivationProperties __ActivationProperties
  52: {
  53:     get
  54:     {
  55:         return (SPWorkflowActivationProperties)base.GetValue(__ActivationPropertiesProperty);
  56:     }
  57:     set
  58:     {
  59:         base.SetValue(__ActivationPropertiesProperty, value);
  60:     }
  61: }
  62:  
  63: #endregion [ Workflow Context Properties ]

Pasemos ahora, a crear las propiedades personalizadas.

   1: #region [ Custom Workflow Properties ]
   2:  
   3: public static DependencyProperty ToProperty = DependencyProperty.Register("To", typeof(ArrayList), typeof(SendMailActivity));
   4:  
   5: [ValidationOption(ValidationOption.Required)]
   6: public ArrayList To
   7: {
   8:     get
   9:     {
  10:         return ((ArrayList)(base.GetValue(SendMailActivity.ToProperty)));
  11:     }
  12:     set
  13:     {
  14:         base.SetValue(SendMailActivity.ToProperty, value);
  15:     }
  16: }
  17:  
  18: public static DependencyProperty CCProperty = DependencyProperty.Register("CC", typeof(ArrayList), typeof(SendMailActivity));
  19:  
  20: [ValidationOption(ValidationOption.Optional)]
  21: public ArrayList CC
  22: {
  23:     get
  24:     {
  25:         return ((ArrayList)(base.GetValue(SendMailActivity.CCProperty)));
  26:     }
  27:     set
  28:     {
  29:         base.SetValue(SendMailActivity.CCProperty, value);
  30:     }
  31: }
  32:  
  33: public static DependencyProperty SubjectProperty = DependencyProperty.Register("Subject", typeof(string), typeof(SendMailActivity));
  34:  
  35: [ValidationOption(ValidationOption.Required)]
  36: public string Subject
  37: {
  38:     get
  39:     {
  40:         return ((string)(base.GetValue(SendMailActivity.SubjectProperty)));
  41:     }
  42:     set
  43:     {
  44:         base.SetValue(SendMailActivity.SubjectProperty, value);
  45:     }
  46: }
  47:  
  48: public static DependencyProperty BodyProperty = DependencyProperty.Register("Body", typeof(string), typeof(SendMailActivity));
  49:  
  50: [ValidationOption(ValidationOption.Optional)]
  51: public string Body
  52: {
  53:     get
  54:     {
  55:         return ((string)(base.GetValue(SendMailActivity.BodyProperty)));
  56:     }
  57:     set
  58:     {
  59:         base.SetValue(SendMailActivity.BodyProperty, value);
  60:     }
  61: }
  62:  
  63: public static DependencyProperty AttachmentFileNameProperty = DependencyProperty.Register("AttachmentFileName", typeof(string), typeof(SendMailActivity));
  64:  
  65: [ValidationOption(ValidationOption.Required)]
  66: public string AttachmentFileName
  67: {
  68:     get
  69:     {
  70:         return ((string)(base.GetValue(AttachmentFileNameProperty)));
  71:     }
  72:     set
  73:     {
  74:         base.SetValue(AttachmentFileNameProperty, value);
  75:     }
  76: }
  77:  
  78: public static DependencyProperty FromProperty = DependencyProperty.Register("From", typeof(string), typeof(SendMailActivity));
  79:  
  80: [ValidationOption(ValidationOption.Required)]
  81: public string From
  82: {
  83:     get
  84:     {
  85:         return ((string)(base.GetValue(SendMailActivity.FromProperty)));
  86:     }
  87:     set
  88:     {
  89:         base.SetValue(SendMailActivity.FromProperty, value);
  90:     }
  91: }
  92:  
  93:  
  94: #endregion [ Custom Workflow Properties ]

Ahora bien,  podemos avanzar en la creación de nuestro método. Aquí explicaré con más detalle la solución.

Lo que necesitabamos hacer para poder adjuntar el formulario infopath a un email era acceder físicamente al mismo. El problema de esto era que no tenia forma de obtener la referencia hacia ninguna ruta “física” del item (formulario), pero sí, por medio del contexto del Workflow, podía saber cual era su ubicación lógica (Url) desde la propiedad CurrentItemUrl. Aquí llegamos al tema más interesante de este post.

Primer paso importante:

Como podrán ver luego, se usó la url del ítem actual para crear un WebRequest a partir del cual podía obtener un ResponseStream. Algo bastante sugestivo, y algo que no podemos olvidar de hacer al crear el Request para un formulario InfoPath, es enviar un parámetro en el header del mensaje de modo que cuando se ejecute, este no sea redireccionado a FormServer.aspx (redirección por default cuando accedemos a un archivo infopath). El modo de enviar este parámetro es el siguiente:  myRequest.Headers.Add("Translate:f");.

Les dejo un link referente a este ultimo tema. Parámetros con los que puede trabajar InfoPath Forms.

http://msdn.microsoft.com/en-us/library/ie/ms772417.aspx

Segundo paso importante:

Luego de obtener el Stream para el adjunto, se crea el Attachment del email, estableciendo para su ContentType correspondiente. Ejemplo:

   1: Attachment form = new Attachment(msForm, new ContentType("application/x-microsoft-InfoPathForm")); 

Tercer paso importante:

Agregar los siguientes headers al Mensaje:

   1: message.Headers.Add("Content-Class", "InfoPathForm.InfoPath");
   2: message.Headers.Add("Message-Class", "IPM.InfoPathForm.InfoPath");

 

Como expliqué en los párrafos anteriores, nuestra clase hereda de Activity. Esto implica que estamos obligados a implementar el método Execute sobrescribiéndolo con nuestra propia lógica. En el siguiente ejemplo podemos ver como quedaría nuestro código siguiendo las indicaciones anteriores:

   1: protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
   2: {
   3:     using (var web = __Context.Web)
   4:     {
   5:         try
   6:         {
   7:             // Obtiene la informacion necesaria para el mensaje que se envía, 
   8:             //relacionada al item actual sobre el cual esta corriendo el workflow. 
   9:             var message = BuildMailMessage(web);
  10:             
  11:             try
  12:             {
  13:                 using (SPSite site = new SPSite(AttachmentFileName))
  14:                 {
  15:                     using (SPWeb fileWeb = site.OpenWeb())
  16:                     {
  17:                         string nameForm;
  18:  
  19:                         //Stream del Archivo InfoPath xml
  20:                         Stream msForm = CreateResponseStream(fileWeb, AttachmentFileName, out nameForm);
  21:                         
  22:                         Attachment form = new Attachment(msForm, new ContentType("application/x-microsoft-InfoPathForm"));
  23:  
  24:                         message.Headers.Add("Content-Class", "InfoPathForm.InfoPath");
  25:                         message.Headers.Add("Message-Class", "IPM.InfoPathForm.InfoPath");
  26:  
  27:                         message.Attachments.Add(form);
  28:                     
  29:                     }
  30:                 }
  31:  
  32:             }
  33:             catch (Exception ex)
  34:             {
  35:                 // NO se pudo encontrar e archivo.
  36:                 Common.WriteFailToHistoryLog(web, WorkflowInstanceId, string.Format(CultureInfo.InvariantCulture, "No se pudo adjuntar el archivo: '{0}' - Mensaje: {1}.", AttachmentFileName, ex.Message));
  37:             }
  38:  
  39:             if (!string.IsNullOrEmpty(From))
  40:             {
  41:                 //Se obtiene la cuenta del usuario enviador del mail
  42:                 message.From = GetMailAddress(__Context.Web, From);
  43:                 Common.WriteFailToHistoryLog(web, WorkflowInstanceId, string.Format(CultureInfo.InvariantCulture, "From: {0}", message.From));
  44:             }
  45:  
  46:             if (message.To.Count > 0)
  47:             {
  48:                 // Se establece el objeto SMTP en base a la configuracion del smtp de nuestro Sharepoint
  49:                 SmtpClient smtpClient = LoadSmtpInformation();
  50:  
  51:                 if (message != null)
  52:                     Common.WriteFailToHistoryLog(web, WorkflowInstanceId, string.Format(CultureInfo.InvariantCulture, "Direccion de mail del From: {0} - To: {1} -  ", message.From.Address, message.To[0].Address));
  53:                 smtpClient.Send(message);
  54:  
  55:                 // Log en el historial del Workflow
  56:                 Common.WriteSuccessToHistoryLog(web, WorkflowInstanceId, string.Format(CultureInfo.InvariantCulture, "Email correctamente enviado a los usuarios: {0}", message.To));
  57:             }
  58:             else
  59:             {
  60:                 // Log en el historial del Workflow
  61:                 StringBuilder emailAddressesTo = new StringBuilder();
  62:                 for (int i = 0; i < To.Count; i++)
  63:                 {
  64:                     emailAddressesTo.AppendFormat(CultureInfo.InvariantCulture, "{0}, ", To[i]);
  65:                 }
  66:                 // Trim de la utlima coma
  67:                 emailAddressesTo = emailAddressesTo.Remove(emailAddressesTo.Length - 1, 2);
  68:  
  69:  
  70:                 Common.WriteFailToHistoryLog(web, WorkflowInstanceId, string.Format(CultureInfo.InvariantCulture, "Unable to send email out. No valid user email addresses for the following users ({0}) were found.", emailAddressesTo));
  71:             }
  72:         }
  73:         catch (Exception ex)
  74:         {
  75:             // Log en el historial del Workflow
  76:             Common.WriteFailToHistoryLog(web, WorkflowInstanceId, string.Format(CultureInfo.InvariantCulture, "Falló el envío de mail. Mensaje de Error: {0}", ex.Message));
  77:         }
  78:         finally
  79:         {
  80:             // Cleanup - Dispose de las propiedades privadas antes de salir
  81:             if (__ActivationProperties != null)
  82:             {
  83:                 __ActivationProperties.Dispose();
  84:             }
  85:  
  86:             if (__Context != null)
  87:             {
  88:                 __Context.Dispose();
  89:             }
  90:         }
  91:     }
  92:  
  93:     return ActivityExecutionStatus.Closed;
  94: }
  95:  
  96: private Stream CreateResponseStream(SPWeb fileWeb, string nameFile, out string name)
  97: {
  98:     SPFile attachment = null;
  99:     attachment = fileWeb.GetFile(nameFile);
 100:     name = attachment.Name;
 101:     WebRequest req = WebRequest.Create(nameFile);
 102:     req.Headers.Add("Translate:f");
 103:     req.Credentials = CredentialCache.DefaultNetworkCredentials;
 104:     WebResponse response = req.GetResponse();
 105:     Stream msForm = response.GetResponseStream();
 106:  
 107:     return msForm;
 108: }
 109:  
 110: private static SmtpClient LoadSmtpInformation()
 111: {
 112:   string smtpServer = SPAdministrationWebApplication.Local.OutboundMailServiceInstance.Server.Address;
 113: return new SmtpClient(smtpServer);
 114: }

Veamos un poco el código. Primero obtenemos el Contexto Web actual. Luego usando  algunos métodos helpers construimos el mensaje (BuildMailMessage)

Después de crear el mensaje tratamos de cargar el adjunto especificado por la propiedad. Para esto usamos la propiedad AttachmentFileName para abrir el SPSite donde el archivo reside. Luego abrimos un SPWeb para obtener el archivo en cuestión. (dentro del método CreateResponseStream).

Desarrollamos los tres pasos definidos en los párrafos anteriores. Y finalmente establecemos el SMTP de nuestro Sharepoint (LoadSmtpInformation ) y con el método GetMailAddress obtenemos las direcciones de correo en caso de que en los parámetros nos llegue el Account Name.

4 - Registración de Custom Action.

Antes de usar nuestra Custom Action debemos, primero, registrarla en el Web.config de nuestro Sharepoint. Para hacer esto usaremos un Feature Reciver que ejecute esta acción durante la activación.

Como agregar un EventReciver:

 

image

 

Esto nos generará una clase Vemn.EventReceiver.cs en la cual vamos a implementar los siguientes métodos:

   1: public override void FeatureActivated(SPFeatureReceiverProperties properties)
   2: {
   3:     SPWebApplication webapp = (SPWebApplication)properties.Feature.Parent;
   4:     UpdateWebConfig(webapp, true);
   5:  
   6: }
   7:  
   8: public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
   9: {
  10:     SPWebApplication webapp = (SPWebApplication)properties.Feature.Parent;
  11:     UpdateWebConfig(webapp, false);
  12:  
  13: }
  14:  
  15: private void UpdateWebConfig(SPWebApplication webApp, bool featureActivated)
  16: {
  17:     SPWebConfigModification modification =
  18:        new SPWebConfigModification("authorizedType[@Assembly=\"Vemn.CustomActivity, Version=1.0.0.0, Culture=neutral, PublicKeyToken=7819e4834cee4b0e\"][@Namespace=\"Vemn.CustomActivity\"][@TypeName=\"*\"][@Authorized=\"True\"]", "configuration/System.Workflow.ComponentModel.WorkflowCompiler/authorizedTypes");
  19:  
  20:     modification.Owner = "Vemn.CustomActivity";
  21:     modification.Sequence = 0;
  22:     modification.Type = SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode;
  23:     modification.Value =
  24:          string.Format(CultureInfo.InstalledUICulture,
  25:          "<authorizedType Assembly=\"{0}\" Namespace=\"{1}\" TypeName=\"{2}\" Authorized=\"{3}\"/>",
  26:          new object[] { "Vemn.CustomActivity, Version=1.0.0.0, Culture=neutral, PublicKeyToken=7819e4834cee4b0e", "Vemn.CustomActivity", "*", "True" });
  27:  
  28:     if (featureActivated)
  29:         webApp.WebConfigModifications.Add(modification);
  30:     else
  31:         webApp.WebConfigModifications.Remove(modification);
  32:  
  33:  
  34:     SPFarm.Local.Services.GetValue<SPWebService>().ApplyWebConfigModifications();
  35: }

Esto agregará o removerá un nodo authorizedType en el Web.config de nuestro Sharepoint, para habilitar el uso de nuestra Action en el Workflow Designer.

Agregar una entrada Safecontrol en el manifiesto de nuestro Package.

Para asegurarnos de que Sharepoint Cargue de manera segura nuestra CustomAction es que añadimos la entrada SafeControl sobrescribiendo el archivo Package.Template.xml de la siguiente manera:

   1: <Assemblies>
   2:     <Assembly Location="Vemn.CustomActivity.dll" DeploymentTarget="GlobalAssemblyCache">
   3:         <SafeControls>
   4:             <SafeControl Assembly="$SharePoint.Project.AssemblyFullName$" Namespace="$SharePoint.Project.FileNameWithoutExtension$" TypeName="*" />
   5:         </SafeControls>
   6:     </Assembly>
   7: </Assemblies>

Ahora si ya estamos en condiciones de compilar, hacer el deploy y testear.

Creamos un Workflow Reusable y revisamos si nuestra Custom Acrivity está disponible para usar.

image

Deberíamos poder seleccionarla e insertarla como sentencia.

image

Conclusión

Como pudimos ver, Sharepoint 2010 nos permite extender las capacidades de la versión OOB. Esto sumado a los Workflows Reusables nos dan una gran flexibilidad para el diseño y desarrollo de soluciones que exigen considerados niveles de personalización.

Tags: , , , ,

.NET | Sharepoint | Visual Studio

Extensible Connectivity Management Agents (ECMA) para Forefront Identity Manager (FIM) - I

por Mauricio Rodriguez  23. diciembre 2011

Forefront_IdentityManager_2010_rgb_180x51_nEGRO

Creo que es apropiado encarar este post desde una de las grandes problemáticas que se esta empezando a dar en la actualidad:

En la medida en que los sistemas de las organizaciones se van multiplicando con el paso del tiempo, la administración de identidades (información de usuarios) entre dichos sistemas se va complejizando volviéndose un trabajo arduo, tedioso y en muchos casos incontrolable. Es importante tener en cuenta los riesgos de seguridad que pueden tener lugar en escenarios de este tipo. Para ilustrar un poco esto, les daré un ejemplo real en unas de las empresas en la cual trabajé:

”La organización contaba con una gran variedad de sistemas y tecnologías como Microsoft Sharepoint, SAP, Oracle, Biztalk, etc. Nos informan que un directivo de alto rango había sido despedido, por lo que tenía que empezar un proceso de baja de usuario en todos los sistemas en los que estaba registrado, con todo lo que esto significa, ya que había varias instancias de este usuario en cada  una de las aplicaciones en las que había tenido actividad. Lo interesante de esta anécdota es que el informe lo recibimos a los últimos minutos de finalizar la jornada laboral un día Viernes, por lo que tuvimos que poner leña al fuego y tratar de ubicar a todos los responsables de cada departamento para pedir la baja de este usuario, si es que aún no se habían retirado. Recuerdo que fue un día largo y nos trajo muchos dolores de cabeza.”

Forefront Identity Manager (FIM) es una plataforma de administración de identidades, credenciales y políticas de acceso de Microsoft, destinada a cubrir este tipo de dificultades.

Voy a desarrollar brevemente y lo más claro posible, qué es Forefront Identity Manager para los que no lo conocen. Y como final y puntapié inicial del próximo post, introducirnos a los Management Agents de FIM.

Introducción

Para comenzar, vamos a ver dos imágenes interesantes. La primera es bastante intuitiva y nos va a servir para armar un mapa mental sobre el concepto de FIM:

 

image

Como pueden ver Forefront Identity Manager 2010, posee la capacidad de integrar identidades de diferentes aplicaciones y datasources, centralizando y sincronizando información. A partir de este sistema centralizado, los administradores pueden definir políticas, manejar credenciales, usuarios y grupos de modo que estos cambios puedan impactar en las aplicaciones o data sources conectados a FIM. Una de las cosas más importantes que nos provee FIM es la posibilidad de personalizar los conectores, pudiendo estos, ser desarrollados en Visual Studio, lo que implica que podamos hacer extensible el manejo y sincronización de los datos hacia cualquier sistema que disponga de una interfaz de comunicación.

En FIM 2010 Disponemos de un Portal basado en Sharepoint y la consola de Sincronización, desde donde es posible visualizar y administrar todas estas operaciones, como las Reglas de Sincronización, Workflows, Mapeos, etc.

Una vez conceptualizado esto, podemos pasar a la siguiente imagen para observar un poco mas en detalle los componentes de la arquitectura de FIM:

image

 

Para el tema en desarrollo nos vamos a concentrar en los siguientes componentes principales: el Portal (2), los Adapters (10), estos serán nuestros Management Agents (MA), el FIMSync (11), el cual es el corazón de esta plataforma que como pueden ver es el encargado de integrar a los Identity Stores y por último el FIM Service que involucra los componentes 5,6,7 y 8.

Para mas información sobre los demás componentes, les dejo el link de msdn http://msdn.microsoft.com/en-us/library/windows/desktop/ff182370.aspx

Como trabajan estos?

Como mencioné antes, FIM dispone de la capacidad de conectar múltiples Identity Stores o DataSources por medio de los MAs. Cada MA que se crea para alguna conexión, dispone de una base de datos propia llamada ConnectorSpace (esta base es creada por FIM cada vez que se crea un MA).

Supongamos que una de las aplicaciones conectadas a FIM realiza alguna modificación en los datos de un usuario, como podría ser la dirección de mail, el teléfono o el lugar de trabajo. El MA de este sistema importará los datos modificados al ConnectorSpace. Luego se ejecuta una sincronización que creará, o actualizara en caso de que existan, estos datos en una base única llamada Metaverse, luego, mediante, una Regla de Sincronización (Synchronization Rule), se detectan que DataSources utilizan estos datos de modo de importarlos a los ConnectorSpaces correspondientes, para finalmente ser exportados a dichos DataSources.  De este modo se mantiene la consistencia de datos referente a usuarios o grupos en todos los sistemas que estén conectados y vinculados mediante las Reglas de Sincronización.

 

 

MVAndCS

Les dejo la documentación en donde puede ver con mayor detalle todos los aspectos de la sincronización:

http://msdn.microsoft.com/en-us/library/windows/desktop/ms698364.aspx

Para nuestro objetivo nos vamos a enfocar sobre los MAs (Management Agents). Los MA son componentes (dlls), desarrollados en Visual Studio .Net, que residen en el servidor de Sincronización, encargados de realizar las operaciones de importación y exportación de los datos.

image

 

Es claro en la imagen, la interfaz que brindan estos MA para la integración de aplicaciones.

En FIM disponemos de una basta lista de MA para crear y configurar, como por ejemplo para integrar SQL, AD, SAP, etc. Pero que pasa si ninguno de estos cumple con los requerimientos del cliente?,  o si nuestro MA tiene que realizar algún proceso en particular?, y aquí ya nos vamos perfilando al tema troncal de este post, tenemos la posibilidad de desarrollar nuestros propios Extensible Connectivity Management Agent (ECMA) a partir una API (Microsoft.MetadirectoryServices.dll) que nos provee FIMSynchronizationService.

En el próximo post estaremos viendo como desarrollar e implementar un Management Agent personalizado para integrar un sistema de  facturación a través de sus WebServices.

Acerca de los Autores

Este es el blog del equipo de VEMN SA 
Presentaremos temas que nos parezcan de interés sobre tecnología .NET, Procesos y Metodologías y todo aquello relacionado con el proceso de desarrollo de Software

Month List

BlogRoll

Download OPML file OPML