Compartir a través de


Configuración de notificaciones de cambios de Microsoft Graph con datos de recursos

Microsoft Graph permite que las aplicaciones se suscriban y reciban notificaciones sobre los cambios en los recursos. En este artículo se explica cómo configurar notificaciones enriquecidas, que incluyen datos de recursos directamente en la carga de notificación.

Las notificaciones enriquecidas eliminan la necesidad de llamadas API adicionales para obtener recursos actualizados, lo que hace que sea más rápido y fácil ejecutar la lógica de negocios.

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
Copilot aiInteraction Interacciones de IA de Copilot de las que forma parte un usuario determinado: copilot/users/{userId}/interactionHistory/getAllEnterpriseInteractions

Interacciones de IA de Copilot en una organización: copilot/interactionHistory/getAllEnterpriseInteractions
Cuotas de suscripción permitidas:
  • Por combinación de aplicaciones e inquilinos (para suscripciones que realizan el seguimiento de las interacciones de IA en un inquilino): 1
  • Por combinación de aplicaciones y usuarios (para suscripciones que realizan un seguimiento de las interacciones de IA de la que forma parte un usuario determinado): 1
  • Por usuario (para suscripciones que realizan un seguimiento de las interacciones de IA de las que forma parte un usuario determinado): 10 suscripciones.
  • Por organización: 10 000 suscripciones totales.
  • 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 todos los equipos del inquilino: /teams/getAllMembers

    Cambios en la pertenencia a todos los canales de un equipo específico: /teams/{id}/channels/getAllMembers

    Cambios en la pertenencia a todos los canales de todo el inquilino: /teams/getAllChannels/getAllMembers

    Cambios en la pertenencia a un chat específico: /chats/{id}/members

    Cambios en la pertenencia a todos los chats de Teams: /chats/getAllMembers
    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.
    Microsoft Teams onlineMeeting * Cambios en una reunión en línea: /communications/onlineMeetings(joinWebUrl='{encodedJoinWebUrl}')/meetingCallEvents * 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. Una suscripción permitida por aplicación por reunión en línea. Para obtener más información, consulte Obtención de notificaciones de cambio para las actualizaciones de eventos de llamadas a reuniones de Microsoft Teams.
    Presencede Teams Cambios en la presencia de un solo usuario: /communications/presences/{id}

    Cambios en la presencia de varios usuarios: /communications/presences?$filter=id in ({id},{id}...)
    La suscripción para la presencia de varios usuarios está limitada a 650 usuarios distintos. 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. Una suscripción permitida por aplicación por usuario delegado. Para obtener más información, consulte Obtención de notificaciones de cambios para las actualizaciones de presencia en Microsoft Teams.
    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

    Las notificaciones enriquecidas incluyen datos de recursos con estos detalles:

    • Identificador y tipo de la instancia de recurso modificada, que se encuentra en la propiedad resourceData .
    • Todos los valores de propiedad de la instancia de recurso, cifrados como se especifica en la suscripción, se encuentran en la propiedad encryptedContent .
    • Propiedades específicas del recurso, en función del recurso, o si se solicitan mediante un $select parámetro en la dirección URL del recurso de la suscripción.

    Crear una suscripción

    Para configurar notificaciones enriquecidas, siga los mismos pasos que las notificaciones de cambio básicas, pero incluya estas propiedades necesarias:

    • includeResourceData: establézcalo en true para solicitar datos de recursos.
    • encryptionCertificate: proporcione la clave pública que usa Microsoft Graph para cifrar los datos del recurso. Obtenga más información en Descifrado de datos de recursos a partir de notificaciones de cambios.
    • encryptionCertificateId: proporcione un identificador para que el certificado coincida con las notificaciones con la clave de descifrado correcta.

    Valide ambos puntos de conexión, según se describe en Validación del punto de conexión de la notificación. Si usa la misma dirección URL para ambos puntos de conexión, recibirá y debe responder a dos solicitudes de validación.

    Ejemplo: Solicitud de suscripción

    En este ejemplo se crea una suscripción para los mensajes de canal 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": "{customId}",
      "expirationDateTime": "2019-09-19T11:00:00.0000000Z",
      "clientState": "{secretClientState}"
    }
    

    Notificaciones del ciclo de vida de la suscripción

    Los eventos pueden interrumpir el flujo de notificaciones de cambio en una suscripción. Las notificaciones de ciclo de vida le indican qué acciones realizar para mantener el flujo ininterrumpido. A diferencia de las notificaciones de cambio de recursos, las notificaciones de ciclo de vida se centran en el estado de la suscripción.

    Para obtener más información, consulte Reducción de las suscripciones que faltan y notificaciones de cambio.

    Validar la autenticidad de las notificaciones

    Compruebe siempre la autenticidad de las notificaciones de cambios antes de procesarlas. Esto impide que la aplicación desencadene una lógica de negocios incorrecta mediante notificaciones falsas de terceros.

    Para las notificaciones básicas, validelas con el valor clientState , como se explica en Procesamiento de la notificación de cambios. Para las notificaciones enriquecidas, realice pasos de validación adicionales.

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

    Las notificaciones enriquecidas incluyen una propiedad validationTokens , que contiene una matriz de tokens web JSON (JWT). Cada token es único para el par de aplicaciones e inquilinos. Una notificación de cambio puede contener una combinación de elementos para varias aplicaciones e inquilinos que se suscribieron con la misma notificationUrl.

    Nota:

    Microsoft Graph no envía tokens de validación para las notificaciones de cambio que se entregan a través de Azure Event Hubs porque el servicio de suscripción no necesita validar notificationUrl para Event Hubs.

    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": "aaaabbbb-0000-cccc-1111-dddd2222eeee",
                "changeType": "created",
                ...
            },
            {
                "subscriptionId": "5cfe2387-163c-4006-81bb-1b5e1e060afe",
                "tenantId": "bbbbcccc-1111-dddd-2222-eeee3333ffff",
                "changeType": "created",
                ...
            }
        ],
        "validationTokens": [
            "eyJ0eXAiOiJKV1QiLCJhb...",
            "cGlkYWNyIjoiMiIsImlkc..."
        ]
    }
    

    El objeto de notificación de cambios se encuentra en la estructura del tipo de recurso changeNotificationCollection.

    Cómo validar

    Use la Biblioteca de autenticación de Microsoft (MSAL) o una biblioteca de terceros para validar los tokens. Siga estos pasos:

    Tenga en cuenta los siguientes principios:

    • Responda a la notificación con un código de HTTP 202 Accepted estado inmediatamente.
    • Responda antes de validar la notificación de cambio, incluso si se produce un error en la validación más adelante. Responda inmediatamente al recibir la notificación de cambio, ya sea que almacene notificaciones en colas para su procesamiento posterior o las procese sobre la marcha.
    • Aceptar y responder a una notificación de cambio evita reintentos de entrega innecesarios y oculta los resultados de validación de los posibles atacantes. Siempre puede omitir una notificación de cambio no válida después de recibirla.

    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.

    Siga estos pasos para validar los tokens y las aplicaciones que los generan:

    1. Valide que el token no ha expirado.

    2. Valide que el Plataforma de identidad de Microsoft emitió el token y que no se ha alterado.

      • 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 puede almacenar en caché esta configuración durante algún tiempo. 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. Confirme que el token se emitió para la aplicación.

      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. Valide que la propiedad del azp token coincide con el valor esperado de , que representa el publicador de 0bf30f3b-4a52-48df-9a82-234910c4a086notificaciones de cambios de Microsoft Graph.

    Ejemplo de token JWT

    En el ejemplo siguiente se muestran las propiedades del token JWT necesarios para la validación.

    {
      // aud is your app's id
      "aud": "925bff9f-f6e2-4a69-b858-f71ea2b9b6d0",
      "iss": "https://login.microsoftonline.com/9f4ebab6-520d-49c0-85cc-7b25c78d4a93/v2.0",
      "iat": 1624649764,
      "nbf": 1624649764,
      "exp": 1624736464,
      "aio": "E2ZgYGjnuFglnX7mtjJzwR5lYaWvAA==",
      // azp represents the notification publisher and must always be the same value of 0bf30f3b-4a52-48df-9a82-234910c4a086
      "azp": "0bf30f3b-4a52-48df-9a82-234910c4a086",
      "azpacr": "2",
      "oid": "1e7d79fa-7893-4d50-bdde-164260d9c5ba",
      "rh": "0.AX0AtrpOnw1SwEmFzHslx41KkzsP8wtSSt9ImoIjSRDEoIZ9AAA.",
      "sub": "1e7d79fa-7893-4d50-bdde-164260d9c5ba",
      "tid": "9f4ebab6-520d-49c0-85cc-7b25c78d4a93",
      "uti": "mIB4QKCeZE6hK71XUHJ3AA",
      "ver": "2.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 cambio incluye el identificador básico y la información de tipo de una instancia de recurso. La propiedad encryptedData tiene los datos de recursos completos, cifrados por Microsoft Graph con la clave pública proporcionada en la suscripción. La propiedad también contiene los valores necesarios para la verificación y descifrado. Este cifrado se realiza para aumentar la seguridad de los datos del cliente a los que se accede a través de notificaciones de cambios. Proteja la clave privada para asegurarse de que un tercero no puede descifrar los datos del cliente, incluso si interceptan las notificaciones de cambio originales.

    En esta sección, aprenderá los siguientes conceptos:

    Administración de claves de cifrado

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

      • Puede usar un certificado autofirmado, ya que Microsoft Graph no comprueba el emisor del certificado y usa la clave pública solo para el cifrado.

      • Use Azure Key Vault para crear, rotar y administrar certificados de forma segura. Asegúrese de que las teclas cumplen los siguientes criterios:

        • La clave debe ser del tipo RSA.
        • El tamaño de la clave debe estar comprendido entre 2.048 bits y 4.096 bits.
    2. Exporte el certificado en formato X.509 codificado en Base64 e incluya solo la clave pública.

    3. Al crear una suscripción:

      • Proporcione el certificado en la propiedad encryptionCertificate mediante el contenido codificado en 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

    Cambie las claves asimétricas periódicamente para minimizar el riesgo de que una clave privada esté en peligro. 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 que esta actualización forme parte de la renovación normal 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 los siguientes principios:

      • Es posible que el certificado antiguo todavía se use para el cifrado durante algún tiempo. 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 del certificado antiguo solo cuando no vea notificaciones de cambio recientes que hacen referencia a él.

    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 usa para cifrar los 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.

    Supongamos que la clave simétrica es diferente para cada elemento de la notificación de cambio.

    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. Identifique el certificado correcto mediante la propiedad encryptionCertificateId .

    2. Inicialice un componente criptográfico RSA con la clave privada. Una manera sencilla de inicializar un componente RSA es usar el método RSACertificateExtensions.GetRSAPrivateKey(X509Certificate2) con una instancia X509Certificate2 , que contiene la clave privada descrita en Administración de claves de cifrado.

    3. Descifra la clave simétrica en la propiedad dataKey de cada elemento de la notificación de cambio mediante la clave privada. Use el relleno óptimo de cifrado asimétrico (OAEP) como algoritmo de descifrado.

    4. Use la clave simétrica para calcular la firma HMAC-SHA256 para el valor de los datos. Compárelo con el valor en dataSignature. Si no coinciden, suponga que la carga está alterada y no la descifra.

    5. Descifre la proeprty de datos mediante la clave simétrica con advanced encryption Standard (AES), como los Aes de .NET.

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

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

    Los datos descifrados serán una cadena JSON que representa el recurso.

    Ejemplo: Descifrado de datos de recursos

    En el ejemplo JSON siguiente se muestra una notificación de cambio que incluye valores de propiedad cifrados de una instancia de chatMessage en un mensaje de canal. El @odata.id valor especifica la instancia.

    {
      "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..."
      ]
    }
    

    Para obtener una descripción completa de los datos enviados cuando se entregan las notificaciones de cambio, vea tipo de recurso changeNotificationCollection.

    Descifrar la clave simétrica

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

    // Initialize with the private key that matches the encryptionCertificateId.
    X509Certificate2 certificate = <instance of X509Certificate2 matching the encryptionCertificateId property>;
    RSA rsa = certificate.GetRSAPrivateKey();
    byte[] encryptedSymmetricKey = Convert.FromBase64String(<value from dataKey property>);
    
    // Decrypt using OAEP padding.
    byte[] decryptedSymmetricKey = rsa.Decrypt(encryptedSymmetricKey, RSAEncryptionPadding.OaepSHA1);
    
    // 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

    Aes aesProvider = Aes.Create();
    aesProvider.Key = decryptedSymmetricKey;
    aesProvider.Padding = PaddingMode.PKCS7;
    aesProvider.Mode = CipherMode.CBC;
    
    // Obtain the initialization 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.