Configurar notificaciones de cambio que incluyan datos del recurso

Microsoft Graph permite que las aplicaciones se suscriban y reciban notificaciones de cambios para los recursos a través de diferentes canales de entrega. Puede configurar suscripciones para incluir los datos de recursos cambiados (como el contenido de un mensaje de chat de Microsoft Teams o información de presencia de Microsoft Teams) en las notificaciones de cambios. Las notificaciones de cambio que incluyen los datos de cambio de recursos se denominan notificaciones enriquecidas. La aplicación puede usar notificaciones enriquecidas para ejecutar la lógica de negocios sin tener que realizar una llamada API independiente para capturar el recurso modificado.

Este artículo le guía por el proceso de configuración de notificaciones enriquecidas en la aplicación.

Recursos admitidos

Las notificaciones enriquecidas están disponibles para los siguientes recursos.

Nota:

Las notificaciones enriquecidas de las suscripciones a puntos de conexión marcadas con un asterisco (*) solo están disponibles en el punto de /beta conexión.

Recurso Rutas de acceso de recursos admitidas Limitaciones
evento de Outlook Cambios en todos los eventos del buzón de un usuario: /users/{id}/events Requiere $select devolver solo un subconjunto de propiedades en la notificación enriquecida. Para obtener más información, vea Cambiar notificaciones para recursos de Outlook.
mensaje de Outlook Cambios en todos los mensajes del buzón de un usuario: /users/{id}/messages

Cambios en los mensajes de la bandeja de entrada de un usuario: /users/{id}/mailFolders/{id}/messages
Requiere $select devolver solo un subconjunto de propiedades en la notificación enriquecida. Para obtener más información, vea Cambiar notificaciones para recursos de Outlook.
contacto personal de Outlook Cambios en todos los contactos personales del buzón de un usuario: /users/{id}/contacts

Cambios en todos los contactos personales de contactFolder de un usuario: /users/{id}/contactFolders/{id}/contacts
Requiere $select devolver solo un subconjunto de propiedades en la notificación enriquecida. Para obtener más información, vea Cambiar notificaciones para recursos de Outlook.
Llamada de TeamsRegistro Todas las grabaciones de una organización: communications/onlineMeetings/getAllRecordings

Todas las grabaciones de una reunión específica: communications/onlineMeetings/{onlineMeetingId}/recordings

Una grabación de llamadas que está disponible en una reunión organizada por un usuario específico: users/{id}/onlineMeetings/getAllRecordings

Grabación de llamadas que está disponible en una reunión en la que se instala una aplicación de Teams determinada: appCatalogs/teamsApps/{id}/installedToOnlineMeetings/getAllRecordings *
Cuotas de suscripción permitidas:
  • Por aplicación y combinación de reuniones en línea: 1
  • Por aplicación y combinación de usuario: 1
  • Por usuario (para las suscripciones que realizan el seguimiento de las grabaciones en todas las aplicaciones en línea organizadas por el usuario): 10 suscripciones.
  • Por organización: 10 000 suscripciones totales.
  • Llamada de TeamsTranscript Todas las transcripciones de una organización: communications/onlineMeetings/getAllTranscripts

    Todas las transcripciones de una reunión específica: communications/onlineMeetings/{onlineMeetingId}/transcripts

    Transcripción de llamadas que está disponible en una reunión organizada por un usuario específico: users/{id}/onlineMeetings/getAllTranscripts

    Transcripción de llamadas que está disponible en una reunión en la que se instala una aplicación de Teams determinada: appCatalogs/teamsApps/{id}/installedToOnlineMeetings/getAllTrancripts *
    Cuotas de suscripción permitidas:
  • Por aplicación y combinación de reuniones en línea: 1
  • Por aplicación y combinación de usuario: 1
  • Por usuario (para las transcripciones de seguimiento de suscripciones en todas las webMeetings organizadas por el usuario): 10 suscripciones.
  • Por organización: 10 000 suscripciones totales.
  • Canal de Teams Cambios en los canales de todos los equipos: /teams/getAllChannels

    Cambios en el canal en un equipo específico: /teams/{id}/channels
    -
    Chat de Teams Cambios en cualquier chat del inquilino: /chats

    Cambios en un chat específico: /chats/{id}
    -
    chatmessage de Teams Cambios en los mensajes de chat en todos los canales de todos los equipos: /teams/getAllMessages

    Cambios en los mensajes de chat en un canal específico: /teams/{id}/channels/{id}/messages

    Cambios en los mensajes de chat en todos los chats: /chats/getAllMessages

    Cambios en los mensajes de chat en un chat específico: /chats/{id}/messages

    Los cambios en los mensajes de chat de todos los chats de los que forma parte un usuario determinado son parte de: /users/{id}/chats/getAllMessages
    No admite el uso $select de para devolver solo las propiedades seleccionadas. La notificación enriquecida consta de todas las propiedades de la instancia modificada.
    conversationMember de Teams Cambios en la pertenencia a un equipo específico: /teams/{id}/members



    Cambios en la pertenencia a un chat específico: /chats/{id}/members
    -
    Microsoft Teams onlineMeeting * Cambios en una reunión en línea: /communications/onlineMeetings/?$filter=JoinWebUrl eq '{joinWebUrl} * No admite el uso $select de para devolver solo las propiedades seleccionadas. La notificación enriquecida consta de todas las propiedades de la instancia modificada.
    Presencede Teams Cambios en la presencia de un solo usuario: /communications/presences/{id} No admite el uso $select de para devolver solo las propiedades seleccionadas. La notificación enriquecida consta de todas las propiedades de la instancia modificada.
    Equipo de Teams Cambios en cualquier equipo del inquilino: /teams

    Cambios en un equipo específico: /teams/{id}
    -

    Datos de recurso en la carga útil de notificación

    En general, este tipo de notificaciones de cambio incluye los siguientes datos de recursos en la carga:

    • ID y tipo de la instancia de recurso modificada, devuelta en la propiedad resourceData.
    • Todos los valores de propiedad de esa instancia de recurso, cifrados como se especifica en la suscripción, se devuelven en la propiedadencryptedContent.
    • O, dependiendo del recurso, propiedades específicas devueltas en la propiedad resourceData. Para obtener sólo propiedades específicas, especifíquelas como parte de la URL del recurso en la suscripción, utilizando un $select parámetro.

    Crear una suscripción

    Las notificaciones enriquecidas se configuran de la misma manera que las notificaciones de cambio básicas.

    Por motivos de seguridad, Microsoft Graph cifra los datos de recursos devueltos en una notificación enriquecida. Debe proporcionar una clave de cifrado pública como parte de la creación de la suscripción. Para obtener más información sobre cómo crear y administrar claves de cifrado, consulte Descifrado de datos de recursos a partir de notificaciones de cambios.

    Para crear una suscripción que incluya notificaciones enriquecidas, debe especificar las siguientes propiedades:

    • includeResourceData que debería establecerse en true para solicitar explícitamente datos de recursos.
    • encryptionCertificate , que contiene solo la clave pública que Usa Microsoft Graph para cifrar los datos de recursos que devuelve a la aplicación.
    • encryptionCertificateId el cual es su propio identificador para el certificado. Utilice este ID. para que coincida en cada notificación de cambios, qué certificado utilizar para el descifrado.

    Tenga en cuenta lo siguiente:

    Ejemplo de solicitud de suscripción

    El siguiente ejemplo se suscribe a los mensajes de canal que se crean o actualizan en Microsoft Teams.

    POST https://graph.microsoft.com/v1.0/subscriptions
    Content-Type: application/json
    {
      "changeType": "created,updated",
      "notificationUrl": "https://webhook.azurewebsites.net/api/resourceNotifications",
      "resource": "/teams/{id}/channels/{id}/messages",
      "includeResourceData": true,
      "encryptionCertificate": "{base64encodedCertificate}",
      "encryptionCertificateId": "{customId}",
      "expirationDateTime": "2019-09-19T11:00:00.0000000Z",
      "clientState": "{secretClientState}"
    }
    

    Respuesta a la suscripción

    HTTP/1.1 201 Created
    Content-Type: application/json
    
    {
      "changeType": "created,updated",
      "notificationUrl": "https://webhook.azurewebsites.net/api/resourceNotifications",
      "resource": "/teams/{id}/channels/{id}/messages",
      "includeResourceData": true,
      "encryptionCertificateId": "{custom ID}",
      "expirationDateTime": "2019-09-19T11:00:00.0000000Z",
      "clientState": "{secret client state}"
    }
    

    Notificaciones del ciclo de vida de la suscripción

    Algunos eventos pueden interferir con el flujo de notificaciones de cambios en una suscripción existente. La suscripción a las notificaciones del ciclo de vida le informan de las acciones que debe realizar para mantener un flujo ininterrumpido. A diferencia de una notificación de cambio de recursos que informa de un cambio en una instancia de recurso, una notificación de ciclo de vida es sobre la propia suscripción y su estado actual en el ciclo de vida.

    Para obtener más información sobre cómo recibir y responder a las notificaciones de ciclo de vida, consulte Reducción de suscripciones que faltan y notificaciones de cambio.

    Validación de la autenticidad de las notificaciones

    Las aplicaciones a menudo ejecutan la lógica de negocios basada en los datos de los recursos que se incluyen en las notificaciones de cambios. Es importante haber verificado primero la autenticidad de cada notificación de cambios. De lo contrario, un tercero podría suplantar electrónicamente su aplicación con notificaciones de cambios falsas y hacer que se ejecute incorrectamente la lógica de negocio, lo cual puede provocar un incidente de seguridad.

    Para las notificaciones de cambio básicas que no contienen datos de recursos, solo tiene que validarlas en función del valor clientState , tal como se describe en Procesamiento de la notificación de cambio. Esto es aceptable, ya que puede realizar llamadas de confianza posteriores a Microsoft Graph para obtener acceso a los datos de los recursos y, por lo tanto, el impacto de cualquier intento de falsificación es limitado.

    Para las notificaciones de cambios que suministran datos de recursos, realice una validación más completa antes de procesar los datos.

    En esta sección:

    Token de validación en la notificación de cambios

    Una notificación de cambio con datos de recursos contiene una propiedad adicional, validationTokens, que contiene una matriz de tokens web JSON (JWT) generados por Microsoft Graph. Microsoft Graph genera un solo token para cada par de aplicaciones e inquilinos distintos para los que hay un elemento en la matriz de valores . Tenga en cuenta que las notificaciones de cambio pueden contener una combinación de elementos para varias aplicaciones e inquilinos que se suscribieron con la misma notificationUrl.

    Nota: si está configurando notificaciones de cambio entregadas a través de Azure Event Hubs, Microsoft Graph no enviará los tokens de validación. Microsoft Graph no necesita validar la notificationUrl.

    En el siguiente ejemplo, la notificación de cambios contiene dos elementos para la misma aplicación y para dos espacios empresariales diferentes, por lo que la matriz validationTokens contiene dos tokens que necesitan ser validados.

    {
        "value": [
            {
                "subscriptionId": "76619225-ff6b-4489-96ca-4ef547e78b22",
                "tenantId": "84bd8158-6d4d-4958-8b9f-9d6445542f95",
                "changeType": "created",
                ...
            }
        ],
        "validationTokens": [
            "eyJ0eXAiOiJKV1QiLCJhb...",
            "cGlkYWNyIjoiMiIsImlkc..."
        ]
    }
    

    Nota: para obtener una descripción completa de los datos que se envían cuando se entregan las notificaciones de cambios, vea changeNotificationCollection.

    Cómo validar

    Use MSAL para ayudarle a controlar la validación de tokens o una biblioteca de terceros para una plataforma diferente.

    Sea consciente de lo siguiente:

    • Asegúrese de enviar siempre un código de estado HTTP 202 Accepted como parte de la respuesta a la notificación de cambios.
    • Responda antes de validar la notificación de cambios (por ejemplo, si almacena notificaciones de cambios en colas para su procesamiento posterior) o después (si los procesa sobre la marcha), incluso si la validación falló.
    • Aceptar una notificación de cambios evita reintentos de entrega innecesarios y también evita que cualquier posible agente deshonesto descubra si han superado o no la validación. Siempre puede optar por ignorar una notificación de cambios no válida después de haberla aceptado.

    En particular, realice la validación en todos los tokens JWT de la colección validationTokens. Si algún token falla, considere la notificación de cambios sospechosa e investigue más a fondo.

    Utilice los siguientes pasos para validar los tokens y las aplicaciones que generan tokens:

    1. Valide que el token no ha expirado.

    2. Valide que el token no se ha alterado y que la autoridad esperada haya emitido, Plataforma de identidad de Microsoft:

      • Obtenga las claves de firma desde el punto final de la configuración común:https://login.microsoftonline.com/common/.well-known/openid-configuration. La aplicación almacena en caché esta configuración durante algún tiempo. Tenga en cuenta que la configuración se actualiza con frecuencia ya que las claves de firma se giran diariamente.
      • Verifique la firma del token JWT usando esas teclas.

      No acepte tokens emitidos por ninguna otra autoridad.

    3. Valide que el token fue emitido para su aplicación que se está suscribiendo a notificaciones de cambio.

      Los siguientes pasos forman parte de la lógica de validación estándar en las bibliotecas de tokens de JWT y normalmente se pueden ejecutar como una llamada de función única.

      • Valida que el "público" del token coincida con el ID de tu aplicación.
      • Si tiene más de una aplicación recibiendo notificaciones de cambios, asegúrese de comprobar si hay varios ID.
    4. Crítico: Validar que la aplicación que generó el token representa al editor de notificación de cambios de Microsoft Graph.

      • Compruebe que la propiedad appid en el token coincide con el valor esperado de0bf30f3b-4a52-48df-9a82-234910c4a086.
      • Esto garantiza que una aplicación diferente que no sea Microsoft Graph no envíe notificaciones de cambio.

    Ejemplo de token JWT

    El siguiente es un ejemplo de las propiedades incluidas en el token JWT que se necesitan para la validación.

    {
      // aud is your app's id 
      "aud": "8e460676-ae3f-4b1e-8790-ee0fb5d6148f",                           
      "iss": "https://sts.windows.net/84bd8158-6d4d-4958-8b9f-9d6445542f95/",
      "iat": 1565046813,
      "nbf": 1565046813,
      // Expiration date 
      "exp": 1565075913,                                                        
      "aio": "42FgYKhZ+uOZrHa7p+7tfruauq1HAA==",
      // appid represents the notification publisher and must always be the same value of 0bf30f3b-4a52-48df-9a82-234910c4a086 
      "appid": "0bf30f3b-4a52-48df-9a82-234910c4a086",                          
      "appidacr": "2",
      "idp": "https://sts.windows.net/84bd8158-6d4d-4958-8b9f-9d6445542f95/",
      "tid": "84bd8158-6d4d-4958-8b9f-9d6445542f95",
      "uti": "-KoJHevhgEGnN4kwuixpAA",
      "ver": "1.0"
    }
    

    Ejemplo: comprobar los tokens de validación

    // add Microsoft.IdentityModel.Protocols.OpenIdConnect and System.IdentityModel.Tokens.Jwt nuget packages to your project
    public async Task<bool> ValidateToken(string token, string tenantId, IEnumerable<string> appIds)
    {
        var configurationManager = new ConfigurationManager<OpenIdConnectConfiguration>("https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration", new OpenIdConnectConfigurationRetriever());
        var openIdConfig = await configurationManager.GetConfigurationAsync();
        var handler = new JwtSecurityTokenHandler();
        try
        {
        handler.ValidateToken(token, new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateIssuerSigningKey = true,
            ValidateLifetime = true,
            ValidIssuer = $"https://sts.windows.net/{tenantId}/",
            ValidAudiences = appIds,
            IssuerSigningKeys = openIdConfig.SigningKeys
        }, out _);
        return true;
        }
        catch (Exception ex)
        {
        Trace.TraceError($"{ex.Message}:{ex.StackTrace}");
        return false;
        }
    }
    

    Descifrar datos de recursos de notificaciones de cambios

    La propiedad resourceData de una notificación de cambios incluye sólo el ID. básico y la información de tipo de una instancia de recurso. La propiedad encryptedData contiene todos los datos de recursos, cifrados por Microsoft Graph utilizando la clave pública proporcionada en la suscripción. La propiedad también contiene los valores necesarios para la verificación y descifrado. Esto se hace para aumentar la seguridad de los datos de los clientes a los que se accede mediante notificaciones de cambios. Es su responsabilidad proteger la clave privada para asegurarse de que un tercero no puede descifrar los datos del cliente, incluso si logran interceptar las notificaciones de cambio originales.

    En esta sección:

    Administración de claves de cifrado

    1. Obtener un certificado con un par de claves asimétricas.

      • Puede firmar el certificado de forma automática, ya que Microsoft Graph no comprueba el emisor del certificado y usa la clave pública solo para el cifrado.

      • Utilice Azure Key Vault como la solución para crear, rotar y gestionar certificados de forma segura. Asegúrese de que las teclas cumplen los siguientes criterios:

        • La clave debe ser de tipo RSA
        • El tamaño de la clave debe estar comprendido entre 2.048 bits y 4.096 bits.
    2. Exportar el certificado en formato X.509 con codificación base64, y incluir sólo la clave pública.

    3. Al crear una suscripción:

      • Proporcione el certificado en la propiedad encryptionCertificate, utilizando el contenido con codificación base64 en el que se exportó el certificado.

      • Proporcione su propio identificador en la propiedad encryptionCertificateId.

        Este identificador le permite hacer coincidir sus certificados con las notificaciones de cambios que recibe y recuperar los certificados de su almacén de certificados. El identificador puede tener hasta 128 caracteres.

    4. Administre la clave privada de forma segura, de modo que el código de procesamiento de notificaciones de cambios pueda acceder a la clave privada para descifrar los datos de los recursos.

    Claves rotativas

    Para minimizar el riesgo de que una clave privada se vea comprometida, cambie periódicamente sus claves asimétricas. Siga estos pasos para introducir un nuevo par de claves:

    1. Obtener un nuevo certificado con un nuevo par de claves asimétricas. Utilícelo para todas las suscripciones nuevas que se estén creando.

    2. Actualice las suscripciones existentes con la nueva clave de certificado.

      • Haga esto como parte de la renovación regular de la suscripción.
      • O bien, enumere todas las suscripciones y proporcione la clave. Utilice la operación revisión en la suscripción y actualice las propiedades encryptionCertificate y encryptionCertificateId.
    3. Tenga en cuenta lo siguiente:

      • Durante algún tiempo, el certificado antiguo puede seguir utilizándose para el cifrado. Su aplicación debe tener acceso tanto a los certificados antiguos como a los nuevos para poder descifrar el contenido.
      • Utilice la propiedad encryptionCertificateId en cada notificación de cambios para identificar la clave correcta a utilizar.
      • Descarte el certificado antiguo sólo cuando no haya visto ninguna notificación de cambios reciente que haga referencia al mismo.

    Descifrar los datos de recursos

    Para optimizar el rendimiento, Microsoft Graph utiliza un proceso de cifrado de dos pasos:

    • Genera una clave simétrica de un solo uso y la utiliza para encriptar datos de recursos.
    • Utiliza la clave pública asimétrica (que usted proporcionó al suscribirse) para cifrar la clave simétrica y la incluye en cada notificación de cambios de esa suscripción.

    Suponga siempre que la clave simétrica es diferente para cada elemento de la notificación de cambios.

    Para descifrar los datos de recursos, su aplicación debería realizar los pasos invertidos, utilizando las propiedades bajo encryptedContent en cada notificación de cambios:

    1. Utilice la propiedad encryptionCertificateId para identificar el certificado a utilizar.

    2. Inicialice un componente criptográfico RSA (como el .NET RSACryptoServiceProvider) con la clave privada.

    3. Descifre la clave simétrica entregada en la propiedad dataKey de cada elemento de la notificación de cambios.

      Utilice Optimal Asymmetric Encryption Padding (OAEP) para el algoritmo de descifrado.

    4. Utilizar la clave simétrica para calcular la firma HMAC-SHA256 del valor en los datos.

      Compárelo con el valor en dataSignature. Si no coinciden, suponga que la carga se ha manipulado y no la descifra.

    5. Utilice la clave simétrica con un Advanced Encryption Standard (AES) (como el .NET AesCryptoServiceProvider) para descifrar el contenido de los datos.

      • Utilice los siguientes parámetros de descifrado para el algoritmo AES:

        • Relleno: PKCS7
        • Modo de cifrado: CSC
      • Ajuste el "vector de inicialización" copiando los primeros 16 bytes de la clave simétrica utilizada para el descifrado.

    6. El valor descifrado es una cadena JSON que representa la instancia del recurso en la notificación de cambios.

    Ejemplo: descifrar una notificación con datos de recursos cifrados

    El siguiente es un ejemplo de notificación de cambios que incluye valores de propiedad cifrados de una instancia de chatMessage en un mensaje de canal. La instancia se especifica por el valor @odata.id.

    {
        "value": [
            {
                "subscriptionId": "76222963-cc7b-42d2-882d-8aaa69cb2ba3",
                "changeType": "created",
                // Other properties typical in a resource change notification
                "resource": "teams('d29828b8-c04d-4e2a-b2f6-07da6982f0f0')/channels('19:f127a8c55ad949d1a238464d22f0f99e@thread.skype')/messages('1565045424600')/replies('1565047490246')",
                "resourceData": {
                    "id": "1565293727947",
                    "@odata.type": "#Microsoft.Graph.ChatMessage",
                    "@odata.id": "teams('88cbc8fc-164b-44f0-b6a6-b59b4a1559d3')/channels('19:8d9da062ec7647d4bb1976126e788b47@thread.tacv2')/messages('1565293727947')/replies('1565293727947')"
                },
                "encryptedContent": {
                    "data": "{encrypted data that produces a full resource}",
            "dataSignature": "<HMAC-SHA256 hash>",
                    "dataKey": "{encrypted symmetric key from Microsoft Graph}",
                    "encryptionCertificateId": "MySelfSignedCert/DDC9651A-D7BC-4D74-86BC-A8923584B0AB",
                    "encryptionCertificateThumbprint": "07293748CC064953A3052FB978C735FB89E61C3D"
                }
            }
        ],
        "validationTokens": [
            "eyJ0eXAiOiJKV1QiLCJhbGciOiJSU..."
        ]
    }
    

    Nota: para obtener una descripción completa de los datos que se envían cuando se entregan las notificaciones de cambios, vea changeNotificationCollection.

    Esta sección contiene algunos fragmentos de código útiles que utilizan C# y .NET para cada etapa de desencriptación.

    Descifrar la clave simétrica

    // Initialize with the private key that matches the encryptionCertificateId.
    RSACryptoServiceProvider rsaProvider = ...;        
    byte[] encryptedSymmetricKey = Convert.FromBase64String(<value from dataKey property>);
    
    // Decrypt using OAEP padding.
    byte[] decryptedSymmetricKey = rsaProvider.Decrypt(encryptedSymmetricKey, fOAEP: true);
    
    // Can now use decryptedSymmetricKey with the AES algorithm.
    

    Comparar la firma de datos utilizando HMAC-SHA256

    byte[] decryptedSymmetricKey = <the aes key decrypted in the previous step>;
    byte[] encryptedPayload = <the value from the data property, still encrypted>;
    byte[] expectedSignature = <the value from the dataSignature property>;
    byte[] actualSignature;
    
    using (HMACSHA256 hmac = new HMACSHA256(decryptedSymmetricKey))
    {
        actualSignature = hmac.ComputeHash(encryptedPayload);
    }
    if (actualSignature.SequenceEqual(expectedSignature))
    {
        // Continue with decryption of the encryptedPayload.
    }
    else
    {
        // Do not attempt to decrypt encryptedPayload. Assume notification payload has been tampered with and investigate.
    }
    

    Descifrar el contenido de los datos del recurso

    AesCryptoServiceProvider aesProvider = new AesCryptoServiceProvider();
    aesProvider.Key = decryptedSymmetricKey;
    aesProvider.Padding = PaddingMode.PKCS7;
    aesProvider.Mode = CipherMode.CBC;
    
    // Obtain the intialization vector from the symmetric key itself.
    int vectorSize = 16;
    byte[] iv = new byte[vectorSize];
    Array.Copy(decryptedSymmetricKey, iv, vectorSize);
    aesProvider.IV = iv;
    
    byte[] encryptedPayload = Convert.FromBase64String(<value from data property>);
    
    string decryptedResourceData;
    // Decrypt the resource data content.
    using (var decryptor = aesProvider.CreateDecryptor())
    {
      using (MemoryStream msDecrypt = new MemoryStream(encryptedPayload))
      {
          using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
          {
              using (StreamReader srDecrypt = new StreamReader(csDecrypt))
              {
                  decryptedResourceData = srDecrypt.ReadToEnd();
              }
          }
      }
    }
    
    // decryptedResourceData now contains a JSON string that represents the resource.