Impersonación en ensamblados .Net de Workflow en Microsoft CRM

El otro día surgía en los foros la pregunta sobre como impersonar al usuario en las llamadas a los servicios web del CRM dentro del código .Net de un ensamblado de workflow. La documentación del SDK tiene un pequeño error sobre cómo conseguir esto, así que vamos a intentar aclarar cómo podemos conseguir está impersonación y porque es necesaria.


El Escenario


Como bien sabemos, en Microsoft Dynamics CRM disponemos de un motor de workflow que nos permite crear procesos de negocio que automaticen la realización de algunas tareas, y además, este motor de workflow puede ser extendido incluyendo nuevas acciones mediante código .Net 1.1.


El motor de Workflow se ejecuta como un servicio de Windows, “Microsoft CRM Workflow Service”, y como cualquier servicio lo hace bajo unas determinadas credenciales de usuario. Por defecto, estas credenciales suelen ser las del Servicio de Red, aunque podemos cambiarlas en cualquier momento a través de la configuración del servicio. Este motor de workflow es el que se encarga de ejecutar las acciones programadas en los procesos de workflow, y por lo tanto será él el que ejecute el código de los ensamblados que hayamos añadido.


Es típico que desde este código personalizado queramos llamar a los servicios web del CRM para completar alguna tarea. Y en la mayor parte de las ocasiones queremos hacerlo en nombre del usuario que ha disparado la regla (impersonation), es decir, ejecutando la llamada a los servicios web con las credenciales del usuario. Sin embargo, si no hacemos nada el código se ejecutará bajo las credenciales que utilice el Servicio de Workflow (Servicio de Red por defecto) con lo que no conseguiremos obtener el resultado deseado.


Las credenciales del servicio de Workflow pueden ser modificadas para utilizar las de un usuario del CRM, de esta manera todas las llamadas a los servicios web desde un ensamblado de workflow utilizarán por defecto esas credenciales. Sin embargo, hacer esto no es una buena práctica, y puede introducir riesgos de seguridad.


Impersonación


Para hacer que las llamadas a los servicios web utilicen otras credenciales disponemos de varias opciones. La primera sería establecer unas nuevas credenciales “a mano” en el proxy del servicio web. Es decir fijar las credenciales de un determinado usuario mediante su nombre de login y contraseña.







1
2
3
4
//Create crm service proxy
CrmService service = new CrmService();
service.Url = “http://localhost:5555/mscrmservices/2006/crmservice.asmx”;

service.Credentials = new System.Net.NetworkCredential(“user”, “password”, “domain”);


De esta forma las llamadas al servicio web se harán en nombre de este usuario. Pero esto no es lo que buscamos, ya que de esta forma no estamos impersonando al usuario que dispara el workflow sino a un usuario específico del CRM, aunque en muchos casos esta es una solución válida.


En el SDK de Microsoft Dynamics CRM se comenta (ver código a continuación) un método para conseguir esto. Que se basa en fijar las credenciales por defecto, y establecer la propiedad CallerId (una cabecera de los mensajes SOAP del Servicio Web del CRM) con el valor del guid del usuario al que queremos impersonar.







1
2
3
4
5
6
CrmService service = new CrmService();
service.Credentials = System.Net.CredentialCache.DefaultCredentials;

// Get the current user ID.
WhoAmIRequest userRequest = new WhoAmIRequest();
WhoAmIResponse userResp = (WhoAmIResponse) service.Execute(userRequest);

service.CallerIdValue.CallerGuid = userResp.UserId;


Sin embargo, este método no consigue el resultado deseado ya que la utilización del mensaje WhoAmI devuelve, entre otras cosas, el guid del usuario a partir de las credenciales con las que se realiza la llamada (útil para aplicaciones integradas en el CRM), con lo que si lo hacemos desde un ensamblado de workflow nos devolverá el guid de la cuenta sobre la que se esté ejecutando el Servicio de Workflow. Cómo normalmente este se ejecuta con las credenciales del Servicio de Red, nos devolverá un guid que no pertenece a ningún usuario, si no a una cuenta de usuario especial llamada SYSTEM que no tiene permisos para realizar la mayoría de las tareas del CRM.


Entonces, ¿Cómo lo conseguimos? La respuesta está en usar el CallerID (también se menciona en el SDK). Se trata de un XML que contiene el guid del usuario que dispara la regla de workflow, y que puede ser pasado como parámetro por el motor de workflow al método del ensamblado .net. Para ello, a la hora de registrar nuestro ensamblado en el workflow.config, tendremos que especificar un parámetro del tipo caller. Y en nuestro código tendremos que recuperar del XML que viene en este parámetro el guid del usuario, podemos hacerlo mediante una función como la del ejemplo.







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
        private static Guid GetCaller(string callerXml)
{
XmlDocument xmldoc = new XmlDocument();
xmldoc.LoadXml(callerXml);

Guid caller = new Guid(xmldoc.DocumentElement.SelectSingleNode(“//caller/userid”).InnerText);
return caller;
}

public bool sendFax(String callerId)
{

//Create crm service proxy
CrmService service = new CrmService();
service.Url = “http://localhost:5555/mscrmservices/2006/crmservice.asmx”;


// Get the user who triggered the workflow rule
Guid user = GetCaller(callerId);

// Impersonate the user
service.Credentials = System.Net.CredentialCache.DefaultCredentials;
service.CallerIdValue = new CallerId();
service.CallerIdValue.CallerGuid = user;
…..


De esta forma conseguimos realmente impersonar al usuario que dispara la ejecución de la regla de workflow, y realizar operaciones en el CRM mediante los servicios web utilizando sus identidad (y sus permisos).


Espero que este rollo haya servido para aclarar un poquito el concepto de impersonación en el CRM y cómo podemos utilizarlo para llamar a los servicios web desde ensamblados personalizados de workflow. A continuación os dejo un ejemplo completo de un ensamblado que crea una actividad de fax utilizando impersonando al usuario. Proximamente, más ejemplos. Espero vuestro comentarios.


Un Saludo,


Marco Amoedo


Ejemplo Completo


– Workflow.config –







1
2
3
4
5
6
7
8
9
10
11
12
13
14
<workflow.config xmlns=”http://microsoft.com/mscrm/workflow/” allowunsignedassemblies=”true”>
  <methods>

….

    <method name=”Send Fax”
      assembly=”NotificationWorkFlow.dll”
      typename=”NotificationWorkFlow.Fax”
      methodname=”sendFax”
      group=”Plain Concepts”
      timeout=”7200″>
      <parameter name=”Caller” datatype=”caller”/>
      <result datatype=”boolean”/>
    </method>
  </methods>
</workflow.config>


– Código .Net –







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
using System;
using System.Text;
using CrmSdk2.crmsdk; //Referencia a libreria con Proxy de los servicios Web, hay que añadirla la dll
using System.Xml;

namespace NotificationWorkFlow
{
public class Fax
{
private static Guid GetCaller(string callerXml)
{
XmlDocument xmldoc = new XmlDocument();
xmldoc.LoadXml(callerXml);

Guid caller = new Guid(xmldoc.DocumentElement.SelectSingleNode(“//caller/userid”).InnerText);
return caller;
}

public bool sendFax(String callerId)
{

//Create crm service proxy
CrmService service = new CrmService();
service.Url = “http://localhost:5555/mscrmservices/2006/crmservice.asmx”;

// Get the user who triggered the workflow rule
Guid user = GetCaller(callerId);

// Impersonate the user
service.Credentials = System.Net.CredentialCache.DefaultCredentials;
service.CallerIdValue = new CallerId();
service.CallerIdValue.CallerGuid = user;

//Build the owner property
Owner propietario = new Owner();
propietario.Value = user;
//Build the fax object
fax fax = new fax();

//Fill fax’s properties and set the owning user
fax.subject = “Test Fax from Admin”;
fax.description = “New Fax”;
fax.ownerid = propietario;

// Create the party sending and receiving the fax.
activityparty party = new activityparty();

//Set the properties of Activityparty.
party.partyid = new Lookup();
party.partyid.type = EntityName.systemuser.ToString();
party.partyid.Value = user;

// The party sends and receives the fax.
fax.from = new activityparty[] { party };
fax.to = new activityparty[] { party };

// Create the fax.
Guid createdFaxId = service.Create(fax);

return true;

}


}

}


Más información en el SDK de Microsoft Dynamics CRM.

2 comentarios en “Impersonación en ensamblados .Net de Workflow en Microsoft CRM”

  1. Wuenas.

    Tengo una consulta:
    Puedo desde el javascript de un formulario o de un Callout disparar un WorkFlow?? En mas detalle, lo que necesito es correr automaticamente un WorkFlow de la entidad Cuenta cuando se actualizen sus campos o se le de de alta. Para el caso del alta se soluciona facilmente creando un Workflow para el evento Create, pero para Update no se pueden crear Workflows.
    Estuve investigando mucho en la web y en el SDK del CRM sin resultados favorables.

    Por favor si conoceis algun truquillo para eso e informarmelo, os estare muy agradecido.
    Saludos Cordiales

Deja un comentario

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