Configurer les notifications de modifications qui incluent des données de ressource

Microsoft Graph permet aux applications de s’abonner et de recevoir des notifications de modification pour les ressources via différents canaux de distribution. Vous pouvez configurer les abonnements de façon à inclure les données de ressources modifiées (par exemple, le contenu d’un message de conversation ou des informations de présence Microsoft Teams) dans les notifications de modifications. Les notifications de modification qui incluent les données de modification de ressource sont appelées notifications enrichies. Votre application peut utiliser des notifications enrichies pour exécuter votre logique métier sans avoir à effectuer un appel d’API distinct pour extraire la ressource modifiée.

Cet article vous guide tout au long du processus de configuration des notifications enrichies dans votre application.

Ressources prises en charge

Des notifications enrichies sont disponibles pour les ressources suivantes.

Remarque

Les notifications enrichies pour les abonnements aux points de terminaison marqués d’un astérisque (*) sont uniquement disponibles sur le point de /beta terminaison.

Resource Chemins d’accès aux ressources pris en charge Limitations
événement Outlook Modifications apportées à tous les événements dans la boîte aux lettres d’un utilisateur : /users/{id}/events Nécessite $select de retourner uniquement un sous-ensemble de propriétés dans la notification enrichie. Pour plus d’informations, voir Notifications de modification pour les ressources Outlook.
message Outlook Modifications apportées à tous les messages dans la boîte aux lettres d’un utilisateur : /users/{id}/messages

Modifications apportées aux messages dans la boîte de réception d’un utilisateur : /users/{id}/mailFolders/{id}/messages
Nécessite $select de retourner uniquement un sous-ensemble de propriétés dans la notification enrichie. Pour plus d’informations, voir Notifications de modification pour les ressources Outlook.
contact personnel Outlook Modifications apportées à tous les contacts personnels dans la boîte aux lettres d’un utilisateur : /users/{id}/contacts

Modifications apportées à tous les contacts personnels dans le dossier contactFolder d’un utilisateur : /users/{id}/contactFolders/{id}/contacts
Nécessite $select de retourner uniquement un sous-ensemble de propriétés dans la notification enrichie. Pour plus d’informations, voir Notifications de modification pour les ressources Outlook.
CallRecording Teams Tous les enregistrements d’un organization :communications/onlineMeetings/getAllRecordings

Tous les enregistrements d’une réunion spécifique : communications/onlineMeetings/{onlineMeetingId}/recordings

Enregistrement d’appel qui devient disponible dans une réunion organisée par un utilisateur spécifique : users/{id}/onlineMeetings/getAllRecordings

Enregistrement d’appel qui devient disponible dans une réunion où une application Teams particulière est installée : appCatalogs/teamsApps/{id}/installedToOnlineMeetings/getAllRecordings *
Quotas maximaux d'abonnement :
  • Par application et combinaison de réunion en ligne : 1
  • Par combinaison d’application et d’utilisateur : 1
  • Par utilisateur (pour les enregistrements de suivi des abonnements dans tous les onlineMeetings organisés par l’utilisateur) : 10 abonnements.
  • Par organization : 10 000 abonnements au total.
  • Teams callTranscript Toutes les transcriptions d’un organization :communications/onlineMeetings/getAllTranscripts

    Toutes les transcriptions d’une réunion spécifique : communications/onlineMeetings/{onlineMeetingId}/transcripts

    Transcription d’appel qui devient disponible dans une réunion organisée par un utilisateur spécifique : users/{id}/onlineMeetings/getAllTranscripts

    Transcription d’appel qui devient disponible dans une réunion où une application Teams particulière est installée : appCatalogs/teamsApps/{id}/installedToOnlineMeetings/getAllTrancripts *
    Quotas maximaux d'abonnement :
  • Par application et combinaison de réunion en ligne : 1
  • Par combinaison d’application et d’utilisateur : 1
  • Par utilisateur (pour les transcriptions de suivi des abonnements dans tous les onlineMeetings organisés par l’utilisateur) : 10 abonnements.
  • Par organization : 10 000 abonnements au total.
  • Canal Teams Modifications apportées aux canaux dans toutes les équipes : /teams/getAllChannels

    Modifications apportées au canal dans une équipe spécifique : /teams/{id}/channels
    -
    Conversation Teams Modifications apportées à n’importe quelle conversation dans le locataire : /chats

    Modifications apportées à une conversation spécifique : /chats/{id}
    -
    chatmessage Teams Modifications apportées aux messages de conversation dans tous les canaux de toutes les équipes : /teams/getAllMessages

    Modifications apportées aux messages de conversation dans un canal spécifique : /teams/{id}/channels/{id}/messages

    Modifications apportées aux messages de conversation dans toutes les conversations : /chats/getAllMessages

    Modifications apportées aux messages de conversation dans une conversation spécifique : /chats/{id}/messages

    Modifications apportées aux messages de conversation dans toutes les conversations dont un utilisateur particulier fait partie : /users/{id}/chats/getAllMessages
    Ne prend pas en charge l’utilisation $select de pour retourner uniquement les propriétés sélectionnées. La notification enrichie se compose de toutes les propriétés du instance modifié.
    conversationMember Teams Modifications apportées à l’appartenance à une équipe spécifique : /teams/{id}/members



    Modifications apportées à l’appartenance à une conversation spécifique : /chats/{id}/members
    -
    Teams onlineMeeting * Modifications apportées à une réunion en ligne : /communications/onlineMeetings/?$filter=JoinWebUrl eq '{joinWebUrl} * Ne prend pas en charge l’utilisation $select de pour retourner uniquement les propriétés sélectionnées. La notification enrichie se compose de toutes les propriétés du instance modifié.
    présenceTeams Modifications apportées à la présence d’un seul utilisateur : /communications/presences/{id} Ne prend pas en charge l’utilisation $select de pour retourner uniquement les propriétés sélectionnées. La notification enrichie se compose de toutes les propriétés du instance modifié.
    Équipe Teams Modifications apportées à n’importe quelle équipe dans le locataire : /teams

    Modifications apportées à une équipe spécifique : /teams/{id}
    -

    Données de ressources dans la charge utile de notification

    En général, ce type de notifications de modification inclut les données de ressource suivantes dans la charge utile :

    • ID et type de l’instance de ressource modifiée, renvoyée dans la propriété resourceData.
    • Toutes les valeurs de propriété de cette instance de ressource, cryptées comme spécifié dans l’abonnement, sont renvoyées dans la propriété encryptedContent.
    • Ou, selon la ressource, les propriétés spécifiques renvoyées par la propriété resourceData. Pour obtenir uniquement des propriétés spécifiques, spécifiez-les dans le cadre de l’URL deressource dans l’abonnement, à l’aide d’un paramètre de $select.

    Création d’un abonnement

    Les notifications enrichies sont configurées de la même façon que les notifications de modification de base.

    Pour des questions de sécurité, Microsoft Graph chiffre les données de ressources retournées dans une notification enrichie. Vous devez fournir une clé de chiffrement publique dans le cadre de la création de l’abonnement. Pour plus d’informations sur la création et la gestion des clés de chiffrement, consultez Déchiffrement des données de ressources à partir de notifications de modification.

    Pour créer un abonnement qui inclut des notifications enrichies, vous devez spécifier les propriétés suivantes :

    • includeResourceData qui doit être paramétré sur true pour demander explicitement des données de ressource.
    • encryptionCertificate qui contient uniquement la clé publique utilisée par Microsoft Graph pour chiffrer les données de ressources qu’il retourne à votre application.
    • encryptionCertificateId qui est votre propre identificateur pour le certificat. Utilisez cet ID pour faire correspondre dans chaque notification de modification, quel certificat utiliser pour le déchiffrement.

    Gardez les éléments suivants à l’esprit :

    • Validez les deux points de terminaison comme décrit dans validation de point de terminaison de notification. Si vous choisissez d’utiliser la même URL pour les deux points de terminaison, vous recevez et devez répondre à deux demandes de validation.

    Exemple de demande d’abonnement

    L’exemple suivant s’abonne aux messages de canal en cours de création ou de mise à jour dans 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}"
    }
    

    Réponse de l’abonnement

    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}"
    }
    

    Notifications de cycle de vie d’abonnement

    Certains événements peuvent interférer avec un flux normal de notification de modification dans un abonnement existant. Les notifications de cycle de vie d’abonnement vous informent des actions à entreprendre afin de maintenir un flux ininterrompu. Contrairement à une notification de modification de ressource qui informe une modification d’une ressource instance, une notification de cycle de vie concerne l’abonnement lui-même et son état actuel dans le cycle de vie.

    Pour plus d’informations sur la façon de recevoir des notifications de cycle de vie et d’y répondre, consultez Réduire les abonnements manquants et les notifications de modification.

    Validation de l’authenticité des notifications

    Les applications exécutent souvent une logique métier basée sur des données de ressources incluses dans les notifications de modifications. Il est important de vérifier l’authenticité de chaque notification de modification. Dans le cas contraire, un tiers pourrait usurper votre application avec de fausses notifications de modifications, faire en sorte qu’il exécute sa logique métier de façon incorrecte, ce qui peut entraîner un incident de sécurité.

    Pour les notifications de modification de base qui ne contiennent pas de données de ressources, validez-les simplement en fonction de la valeur clientState , comme décrit dans Traitement de la notification de modification. Cette opération est acceptable, car vous pouvez effectuer des appels Microsoft Graph approuvés par la suite pour accéder aux données de ressources. par conséquent, l’impact des tentatives d’usurpation est limité.

    Pour les notifications de modifications qui dépassent les données de ressources, effectuez une validation plus approfondie avant de traiter les données.

    Dans cette section :

    Jetons de validation dans la notification de modification

    Une notification de modification avec des données de ressource contient une propriété supplémentaire, validationTokens, qui contient un tableau de jetons web JSON (JWT) générés par Microsoft Graph. Microsoft Graph génère un jeton unique pour chaque paire d’applications distinctes et de locataire pour laquelle il existe un élément dans le tableau de valeurs . Gardez à l’esprit que les notifications de modification peuvent contenir une combinaison d’éléments pour différentes applications et locataires qui se sont abonnés à l’aide de la même notificationUrl.

    Remarque : Si vous avez mis en place des notifications de modification remis par le biais d’Azure Event Hubs, Microsoft Graph n’enverra pas les jetons de validation. Microsoft Graph n’a pas besoin de valider la notificationUrl.

    Dans l’exemple suivant, la notification de modification contient deux éléments pour la même application, et pour deux clients différents ; par conséquent, la matrice validationTokens contient deux jetons qui doivent être validés.

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

    Remarque : pour une description complète des données envoyées lorsque les notifications de modifications sont transmises, voir changeNotificationCollection.

    Comment valider

    Utilisez MSAL pour vous aider à gérer la validation des jetons ou une bibliothèque tierce pour une autre plateforme.

    Tenez compte des points suivants :

    • Veillez à toujours envoyer un code d’état HTTP 202 Accepted dans le cadre de la réponse à la notification de modification.
    • Répondez avant de valider la notification de modification (par exemple, si vous stockez les notifications de modifications dans des files d’attente pour traitement ultérieur) ou après (si vous les traitez à la volée), même si la validation a échoué.
    • L’acceptation d’une notification de modification évite toute tentative inutile de remise des messages, empêchant également les acteurs potentiellement malveillants de savoir s’ils ont échoué ou réussi la validation. Vous pouvez toujours choisir d’ignorer une notification de modification non valide une fois que vous l’avez acceptée.

    En particulier, effectuez une validation sur chaque jeton JWT dans la collection validationTokens. En cas d’échec d’un jeton, considérez la notification de modification comme suspecte et examinez-la de manière plus approfondie.

    Procédez comme suit pour valider les jetons et les applications qui génèrent des jetons :

    1. Vérifiez que le jeton n’a pas expiré.

    2. Vérifiez que le jeton n’a pas été falsifié et qu’il a été émis par l’autorité attendue, Plateforme d'identités Microsoft :

      • Obtenez les clés de signature à partir du point de terminaison de configuration commun : https://login.microsoftonline.com/common/.well-known/openid-configuration. Cette configuration est mise en cache par votre application pendant un certain temps. Sachez que la configuration est régulièrement mise à jour lorsque les clés de signature sont pivotées quotidiennement.
      • Vérifiez la signature du jeton JWT à l’aide de ces clés.

      N’acceptez pas les jetons émis par une autre autorité.

    3. Vérifiez que le jeton a été émis pour votre application qui s’abonne aux notifications de modifications.

      Les étapes suivantes font partie de la logique de validation standard dans les bibliothèques de jetons JWT et peuvent être exécutées en tant qu’appel de fonction unique.

      • Validez que l’« audience » dans le jeton corresponde à votre ID d’application.
      • Si vous avez plusieurs applications recevant des notifications de modifications, vérifiez s’il existe plusieurs ID.
    4. Critique: Vérifiez que l’application ayant généré le jeton représente l’éditeur de notification de modifications Microsoft Graph.

      • Vérifiez que la propriété AppID dans le jeton correspond à la valeur attendue de 0bf30f3b-4a52-48df-9a82-234910c4a086.
      • Cela garantit que les notifications de modification ne sont pas envoyées par une autre application qui n’est pas Microsoft Graph.

    Exemple de jeton JWT

    Voici un exemple des propriétés incluses dans le jeton JWT qui sont nécessaires à la validation.

    {
      // 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"
    }
    

    Exemple : vérification des jetons de validation

    // 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;
        }
    }
    

    Déchiffrement des données de ressource à partir de notifications de modifications

    La propriété resourceData d’une notification de modification comprend uniquement l’ID de base et les informations de type d’une instance de ressource. La propriété encryptedData contient les données de ressources complètes, cryptées par Microsoft Graph à l’aide de la clé publique incluse dans l’abonnement. La propriété contient également des valeurs requises pour la vérification et le déchiffrement. Cette opération a pour effet de renforcer la sécurité des données client consultées via des notifications de modifications. Il vous incombe de sécuriser la clé privée pour vous assurer que les données client ne peuvent pas être déchiffrées par un tiers, même s’il parvient à intercepter les notifications de modification d’origine.

    Dans cette section :

    Gestion des clés de chiffrement

    1. Obtenir un certificat à l’aide d’une paire de clés asymétriques.

      • Vous pouvez signer automatiquement le certificat, car Microsoft Graph ne vérifie pas l’émetteur du certificat et utilise la clé publique uniquement pour le chiffrement.

      • Utilisez Azure Key Vault comme solution pour créer, faire pivoter et gérer les certificats de façon sécurisée. Assurez-vous que les clés répondent aux critères suivants :

        • La clé doit être de type RSA
        • La taille de clé doit être comprise entre 2 048 bits et 4 096 bits
    2. Exportez le certificat au format X.509 encodé au format Base64 et incluez uniquement la clé publique.

    3. Lors de la création d’un abonnement :

      • Fournissez le certificat dans la propriété encryptionCertificate, en utilisant le contenu encodé au format Base64 dans lequel le certificat a été exporté.

      • Fournissez votre propre identificateur dans la propriété encryptionCertificateId.

        Cet identifiant vous permet de faire correspondre vos certificats aux notifications de modifications que vous recevez et de récupérer des certificats de votre magasin de certificats. Cet identifiant peut contenir jusqu’à 128 caractères.

    4. Gérez la clé privée de façon sécurisée afin que votre code de traitement de notification de modification puisse accéder à la clé privée de façon à déchiffrer les données de ressource.

    Clés de rotation

    Pour réduire le risque de compromission d’une clé privée, modifiez régulièrement vos clés asymétriques. Pour introduire une nouvelle paire de clés, procédez comme suit :

    1. Obtenir un nouveau certificat à l’aide d’une nouvelle paire de clés asymétriques. Utilisez-la pour tous les nouveaux abonnements créés.

    2. Mettez à jour les abonnements existants à l’aide de la nouvelle clé de certificat.

      • Effectuez cette opération dans le cadre du renouvellement régulier de l’abonnement.
      • Vous pouvez également énumérer tous les abonnements et fournir la clé. Utilisez l’opération de correctif sur l’abonnement et mettez à jour les propriétés encryptionCertificate et encryptionCertificateId.
    3. Gardez à l’esprit les éléments suivants :

      • Pendant un certain temps, l’ancien certificat peut toujours être utilisé pour le chiffrement. Votre application doit avoir accès à la fois aux certificats anciens et nouveaux afin de pouvoir déchiffrer le contenu.
      • Utilisez la propriété encryptionCertificateId dans chaque notification de modification pour identifier la clé correcte à utiliser.
      • Ignorez l’ancien certificat uniquement lorsque vous n’avez vu aucune notification de modification récente lui faisant référence.

    Déchiffrement des données de ressource

    Pour optimiser les performances, Microsoft Graph utilise un processus de chiffrement en deux étapes :

    • Il génère une clé symétrique à usage unique et l’utilise pour chiffrer des données de ressource.
    • Il utilise la clé asymétrique publique (que vous avez fournie lors de l’abonnement) pour chiffrer la clé symétrique et l’inclut dans chaque notification de modification de cet abonnement.

    Partez toujours du principe que la clé symétrique est différente pour chaque élément de la notification de modification.

    Pour déchiffrer des données de ressources, votre application doit effectuer les étapes inverses, à l’aide des propriétés de encryptedContent dans chaque notification de modification :

    1. Utilisez la propriété encryptionCertificateId pour identifier le certificat correct à utiliser.

    2. Initialisez un composant cryptographique RSA (par exemple, .NET RSACryptoServiceProvider) avec la clé privée.

    3. Déchiffrez la clé symétrique livrée dans la propriété dataKey de chaque élément de la notification de modification.

      Utilisez OAEP (Optimal Asymmetric Encryption Padding) pour l’algorithme de déchiffrement.

    4. Utilisez la clé symétrique pour calculer la signature HMAC-SHA256 de la valeur des données.

      Comparez-le à la valeur de dataSignature. Si elles ne correspondent pas, supposons que la charge utile a été falsifiée et ne la déchiffrez pas.

    5. Utilisez la clé symétrique avec une norme de chiffrement avancée (AES) (par exemple, .NET AesCryptoServiceProvider) pour déchiffrer le contenu de données.

      • Utilisez les paramètres de déchiffrement suivants pour l’algorithme AES :

        • Remplissage : PKCS7
        • Mode de chiffrement : CBC
      • Configurez le « vecteur d’initialisation » en copiant les 16 premiers octets de la clé symétrique utilisée pour le déchiffrement.

    6. La valeur déchiffrée est une chaîne JSON qui représente l’instance de ressource dans la notification de modification.

    Exemple : déchiffrement d’une notification avec des données de ressource chiffrées

    Voici un exemple de notification de modification qui inclut les valeurs de propriété chiffrées d’une instance chatMessage dans un message de canal. L’instance est spécifiée par la valeur @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..."
        ]
    }
    

    Remarque : pour une description complète des données envoyées lorsque les notifications de modifications sont transmises, voir changeNotificationCollection.

    Cette section contient des extraits de code utiles qui utilisent C# et .net pour chaque étape du déchiffrement.

    Déchiffrer la clé symétrique

    // 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.
    

    Comparer les signatures de données à l’aide de 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.
    }
    

    Déchiffrer le contenu des données de la ressource

    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.