Partilhar via


Configurar notificações de alteração que incluem dados de recursos (notificações avançadas)

O Microsoft Graph permite que as aplicações subscrevam e recebam notificações de alterações aos recursos em que estão interessadas. Embora possa subscrever o tipo básico de notificações de alteração, alguns recursos, como mensagens de chat e recursos de presença do Microsoft Teams, suportam notificações avançadas.

As notificações avançadas incluem os dados de recursos que foram alterados, permitindo que a sua aplicação execute lógica de negócio sem ter de fazer uma chamada à API separada para obter o recurso alterado. Este artigo orienta-o ao longo do processo de configuração de notificações avançadas na sua aplicação.

Recursos com suporte

Estão disponíveis notificações avançadas para os seguintes recursos.

Observação

As notificações avançadas para subscrições para pontos finais marcadas com um asterisco (*) só estão disponíveis no /beta ponto final.

Recurso Caminhos de recursos suportados Limitações
Evento do Outlook Alterações a todos os eventos na caixa de correio de um utilizador: /users/{id}/events $select É necessário devolver apenas um subconjunto de propriedades na notificação avançada. Para obter mais informações, veja Alterar notificações para recursos do Outlook.
Mensagem do Outlook Alterações a todas as mensagens na caixa de correio de um utilizador: /users/{id}/messages

Alterações às mensagens na caixa de entrada de um utilizador: /users/{id}/mailFolders/{id}/messages
$select É necessário devolver apenas um subconjunto de propriedades na notificação avançada. Para obter mais informações, veja Alterar notificações para recursos do Outlook.
Contato pessoal do Outlook Alterações a todos os contactos pessoais na caixa de correio de um utilizador: /users/{id}/contacts

Alterações a todos os contactos pessoais no contacto de um utilizadorPasta: /users/{id}/contactFolders/{id}/contacts
$select É necessário devolver apenas um subconjunto de propriedades na notificação avançada. Para obter mais informações, veja Alterar notificações para recursos do Outlook.
Chamada do TeamsRegisto Todas as gravações numa organização: communications/onlineMeetings/getAllRecordings

Todas as gravações para uma reunião específica: communications/onlineMeetings/{onlineMeetingId}/recordings

Uma gravação de chamada que fica disponível numa reunião organizada por um utilizador específico: users/{id}/onlineMeetings/getAllRecordings

Uma gravação de chamada que fica disponível numa reunião onde está instalada uma determinada aplicação do Teams: appCatalogs/teamsApps/{id}/installedToOnlineMeetings/getAllRecordings *
Cotas máximas de assinaturas:
  • Combinação por aplicação e reunião online: 1
  • Combinação por aplicação e utilizador: 1
  • Por utilizador (para subscrições que monitorizam as gravações em todos os OnlineMeetings organizados pelo utilizador): 10 subscrições.
  • Por organização: 10 000 subscrições totais.
  • Chamada do TeamsTranscript Todas as transcrições numa organização: communications/onlineMeetings/getAllTranscripts

    Todas as transcrições de uma reunião específica: communications/onlineMeetings/{onlineMeetingId}/transcripts

    Uma transcrição de chamada que fica disponível numa reunião organizada por um utilizador específico: users/{id}/onlineMeetings/getAllTranscripts

    Uma transcrição de chamadas que fica disponível numa reunião onde está instalada uma determinada aplicação do Teams: appCatalogs/teamsApps/{id}/installedToOnlineMeetings/getAllTrancripts *
    Cotas máximas de assinaturas:
  • Combinação por aplicação e reunião online: 1
  • Combinação por aplicação e utilizador: 1
  • Por utilizador (para subscrições que monitorizam transcrições em todos os OnlineMeetings organizados pelo utilizador): 10 subscrições.
  • Por organização: 10 000 subscrições totais.
  • Canal do Teams Alterações aos canais em todas as equipas: /teams/getAllChannels

    Alterações ao canal numa equipa específica: /teams/{id}/channels
    -
    Chat do Teams Alterações a qualquer conversa no inquilino: /chats

    Alterações a um chat específico: /chats/{id}
    -
    Teams chatMessage Alterações às mensagens de chat em todos os canais em todas as equipas: /teams/getAllMessages

    Alterações às mensagens de chat num canal específico: /teams/{id}/channels/{id}/messages

    Alterações às mensagens de chat em todas as conversas: /chats/getAllMessages

    Alterações às mensagens de chat num chat específico: /chats/{id}/messages

    As alterações às mensagens de chat em todas as conversas dos utilizadores específicos fazem parte: /users/{id}/chats/getAllMessages
    Não suporta a utilização $select para devolver apenas as propriedades selecionadas. A notificação avançada consiste em todas as propriedades da instância alterada.
    conversationMember do Teams Alterações à associação numa equipa específica: /teams/{id}/members



    Alterações à associação num chat específico: /chats/{id}/members
    -
    Teams onlineMeeting * Alterações a uma reunião online: /communications/onlineMeetings/?$filter=JoinWebUrl eq '{joinWebUrl} * Não suporta a utilização $select para devolver apenas as propriedades selecionadas. A notificação avançada consiste em todas as propriedades da instância alterada.
    Teams presença Alterações à presença de um único utilizador: /communications/presences/{id} Não suporta a utilização $select para devolver apenas as propriedades selecionadas. A notificação avançada consiste em todas as propriedades da instância alterada.
    Equipe do Teams Alterações a qualquer equipa no inquilino: /teams

    Alterações a uma equipa específica: /teams/{id}
    -

    Dados de recursos na carga de notificação

    As notificações avançadas incluem os seguintes dados de recursos no payload:

    • ID e tipo de instância de recurso alterado, retornados na propriedade resourceData.
    • Todos os valores de propriedade da instância de recurso, criptografados conforme especificado na assinatura, retornados na propriedade encryptedContent.
    • Or, dependendo do recurso, propriedades específicas retornadas na propriedade resourceData. Para obter somente propriedades específicas, especifique-as como parte da URL do recurso na assinatura, usando um parâmetro $select.

    Criar uma assinatura

    As notificações avançadas são configuradas da mesma forma que as notificações de alteração básicas, exceto se tiver de especificar as seguintes propriedades adicionais:

    • includeResourceData que deve ser definido como true para solicitar explicitamente os dados de recursos.
    • encryptionCertificate , que contém apenas a chave pública que o Microsoft Graph utiliza para encriptar os dados de recursos devolvidos à sua aplicação. Por motivos de segurança, o Microsoft Graph encripta os dados de recursos devolvidos numa notificação avançada. Tem de fornecer uma chave de encriptação pública como parte da criação da subscrição. Para obter mais informações sobre como criar e gerir chaves de encriptação, veja Desencriptar dados de recursos a partir de notificações de alteração.
    • encryptionCertificateId é o seu próprio identificador para o certificado. Use esse ID para corresponder a cada notificação de alteração cujo certificado foi utilizado para descriptografia.

    Também tem de validar ambos os pontos finais, conforme descrito em Validação do ponto final de notificação. Se optar por utilizar o mesmo URL para ambos os pontos finais, receberá e deverá responder a dois pedidos de validação.

    Exemplo de solicitação de assinatura

    O exemplo a seguir assina as mensagens de canal que estão sendo criadas ou atualizadas no 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}"
    }
    

    Resposta de assinatura

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

    Notificações do ciclo de vida da assinatura

    Certos eventos podem interferir no fluxo de notificação de alterações em uma assinatura existente. As notificações sobre o ciclo de vida da assinatura informam as ações a serem tomadas para manter um fluxo ininterrupto. Ao contrário de uma notificação de alteração de recursos que informa uma alteração a uma instância de recurso, uma notificação de ciclo de vida é sobre a própria subscrição e o estado atual no ciclo de vida.

    Para obter mais informações sobre como receber e responder a notificações de ciclo de vida, veja Reduzir subscrições em falta e alterar notificações.

    Validando a autenticidade das notificações

    Antes de executar a lógica de negócio com base nos dados de recursos incluídos nas notificações de alteração, primeiro tem de verificar a autenticidade de cada notificação de alteração. Caso contrário, um terceiro pode falsificar a sua aplicação com notificações de alteração falsas e fazê-la executar a lógica de negócio incorretamente, o que pode levar a um incidente de segurança.

    Para notificações de alteração básicas que não contêm dados de recursos, basta validá-las com base no valor clientState , conforme descrito em Processar a notificação de alteração. Esta validação é aceitável, uma vez que pode fazer chamadas fidedignas subsequentes do Microsoft Graph para obter acesso aos dados de recursos e, por conseguinte, o impacto de quaisquer tentativas de spoofing é limitado.

    Para notificações avançadas, execute uma validação mais completa antes de processar os dados.

    Nesta secção, vai explorar os seguintes conceitos de validação:

    Tokens de validação na notificação de alteração

    Uma notificação de alteração com dados de recursos contém uma propriedade adicional, validationTokens, que contém uma matriz de Tokens Web JSON (JWT) gerados pelo Microsoft Graph. O Microsoft Graph gera um único token para cada par de inquilinos e aplicações diferentes para os quais existe um item na matriz de valores . Tenha em atenção que as notificações de alteração podem conter uma combinação de itens para várias aplicações e inquilinos que subscreveram com o mesmo notificationUrl.

    Observação

    O Microsoft Graph não envia tokens de validação para notificações de alteração fornecidas através dos Hubs de Eventos do Azure porque o serviço de subscrição não precisa de validar o notificationUrl para os Hubs de Eventos.

    No exemplo a seguir, a notificação de alteração contém dois itens para o mesmo aplicativo e para dois locatários diferentes, portanto, o conjunto validationTokens contém dois tokens que precisam 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..."
        ]
    }
    

    O objeto de notificação de alteração está na estrutura do tipo de recurso changeNotificationCollection.

    Como validar

    Utilize a Biblioteca de Autenticação da Microsoft (MSAL) para o ajudar a processar a validação de tokens ou uma biblioteca de terceiros para uma plataforma diferente.

    Tenha em atenção os seguintes princípios:

    • Certifique-se de sempre enviar um HTTP 202 Accepted código de status como parte da resposta à notificação de alteração.
    • Responda antes de validar a notificação de alteração, mesmo que a validação falhe mais tarde. Ou seja, responda imediatamente ao receber a notificação de alteração, quer armazene notificações em filas para processamento posterior ou processe-as de imediato.
    • A aceitação de uma notificação de alteração evita novas tentativas desnecessárias de entrega e também impede que possíveis atores invasores descubram se foram ou não aprovados na validação. Pode sempre optar por ignorar uma notificação de alteração inválida depois de a receber.

    Especificamente, realize a validação em todos os tokens JWT na coleção validationTokens. Se algum token falhar, considere a notificação de alteração suspeita e investigue mais.

    Use as etapas a seguir para validar tokens e aplicativos que geram tokens:

    1. Confirme que o token não expirou.

    2. Valide se o token não foi adulterado e foi emitido pela autoridade esperada, plataforma de identidade da Microsoft:

      • Obtenha as chaves de assinatura do ponto de extremidade de configuração comum: https://login.microsoftonline.com/common/.well-known/openid-configuration. Esta configuração é colocada em cache pela sua aplicação durante algum tempo. A configuração é atualizada frequentemente à medida que as chaves de assinatura são rodadas diariamente.
      • Verifique a assinatura do token JWT usando essas chaves.

      Não aceite tokens emitidos por nenhuma outra autoridade.

    3. Valide se o token foi emitido para o aplicativo que está inscrito para alterar as notificações.

      As etapas a seguir fazem parte da lógica de validação padrão nas bibliotecas de token JWT e podem ser tipicamente executadas como uma única chamada de função.

      • Valide a “audiência” no token que corresponde à ID do seu aplicativo.
      • Se você tiver mais de um aplicativo recebendo notificações de alterações, verifique a existencia de vários IDs.
    4. Crítico: valide se o aplicativo que gerou o token representa o distribuidor de notificação de alteração do Microsoft Graph.

      • Verifique se a propriedade appid no token corresponde ao valor esperado de 0bf30f3b-4a52-48df-9a82-234910c4a086.
      • Isto garante que as notificações de alteração não são enviadas por uma aplicação diferente que não seja o Microsoft Graph.

    Exemplo de Token JWT

    O exemplo seguinte mostra as propriedades incluídas no token JWT que são necessárias para validação.

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

    Exemplo: verificação de tokens de validação

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

    Descriptografar os dados de recursos de notificações de alteração

    A propriedade resourceData de uma notificação de alteração inclui apenas o ID básico e as informações do tipo de uma instância de recurso. A propriedade encryptedData contém os dados de recursos completos, criptografados pelo Microsoft Graph usando a chave pública fornecida na assinatura. A propriedade também contém valores necessários para verificação e descriptografia. Isso é feito para aumentar a segurança dos dados do cliente acessados por meio de notificações de alterações. É da sua responsabilidade proteger a chave privada para garantir que terceiros não conseguem desencriptar os dados do cliente, mesmo que consigam intercetar as notificações de alteração originais.

    Nesta secção, irá aprender os seguintes conceitos:

    Gerenciar as chaves de criptografia

    1. Obtenha um certificado com um par de chaves assimétricas.

      • Pode utilizar um certificado autoassinado, uma vez que o Microsoft Graph não verifica o emissor do certificado e utiliza a chave pública apenas para encriptação.

      • Utilize o Azure Key Vault para criar, rodar e gerir certificados de forma segura. Cerifique-se de que as chaves atendem aos seguintes critérios:

        • A chave tem de ser do tipo RSA.
        • O tamanho da chave tem de estar entre 2048 bits e 4096 bits.
    2. Exporte o certificado no formato X.509 codificado com Base64 e inclua apenas a chave pública.

    3. Ao criar uma assinatura:

      • Forneça o certificado na propriedade encryptionCertificate , utilizando o conteúdo codificado em Base64 no qual o certificado foi exportado.

      • Forneça seu próprio identificador na propriedade encryptionCertificateId.

        Esse identificador permite corresponder seus certificados às notificações de alterações recebidas e recuperar certificados do seu repositório de certificados. O identificador pode ter no máximo 128 caracteres.

    4. Gerencie com segurança a chave privada com para que seu código de processamento de notificação de alteração acesse a chave privada para decriptar os dados do recurso.

    Chaves de rotação

    Para minimizar o risco de uma chave privada ser comprometida, altere periodicamente suas chaves assimétricas. Siga estas etapas para introduzir um novo par de chaves:

    1. Obtenha um novo certificado com um novo par de chaves assimétricas. Use-o para todas as novas assinaturas sendo criadas.

    2. Atualize as assinaturas existentes com a nova chave de certificado.

      • Faça desta atualização parte da renovação regular da subscrição.
      • Ou enumere todas as assinaturas e forneça a chave. Use a operação PATCH na assinatura e atualize as propriedades encryptionCertificate e encryptionCertificateId.
    3. Tenha em atenção os seguintes princípios:

      • Durante algum tempo, o certificado antigo ainda pode ser utilizado para encriptação. Seu aplicativo deve ter acesso a certificados novos e antigos para poder descriptografar o conteúdo.
      • Use a propriedade encryptionCertificateId em cada notificação de alteração para identificar a chave correta a ser utilizada.
      • Elimine o certificado antigo apenas quando não vir notificações de alteração recentes a referenciá-lo.

    Descriptografar dados de recursos

    Para otimizar o desempenho, o Microsoft Graph usa um processo de criptografia de duas etapas:

    • Gera uma chave simétrica de utilização única e utiliza-a para encriptar dados de recursos.
    • Ele usa a chave pública assimétrica (que você forneceu ao fazer a assinatura) para criptografar a chave simétrica e a incluir em cada notificação de alteração desta assinatura.

    Sempre assuma que a chave simétrica é diferente para cada item na notificação de alteração.

    Para decriptar os dados de recursos, o seu aplicativo deve executar as etapas inversas, usando as propriedades encryptedContent em cada notificação de alteração:

    1. Use a propriedade encryptionCertificateId para identificar o certificado a ser usado.

    2. Inicialize um componente criptográfico da RSA (como o .NET RSACryptoServiceProvider) com a chave privada.

    3. Decripte a chave simétrica entregue na propriedade dataKey de cada item na notificação de alteração.

      Use o Preenchimento de Criptografia Assimétrica Ideal (OAEP) para o algoritmo de descriptografia.

    4. Use a chave simétrica para calcular a assinatura HMAC-SHA256 do valor em dados.

      Compare-o com o valor em dataSignature. Se não corresponderem, suponha que o payload foi adulterado e não o desencripte.

    5. Use a chave simétrica com a criptografia AES (como o .NET AesCryptoServiceProvider) para descriptografar o conteúdo em dados.

      • Use os seguintes parâmetros de descriptografia para o algoritmo AES:

        • Preenchimento: PKCS7
        • Modo de criptografia: CBC
      • Defina o “vetor de inicialização” copiando os primeiros 16 bytes da chave simétrica usada para descriptografia.

    6. O valor decriptado é uma sequência JSON que representa a instância do recurso na notificação de alteração.

    Exemplo: decriptando uma notificação com dados de recurso criptografados

    O exemplo JSON seguinte mostra uma notificação de alteração que inclui valores de propriedade encriptadas de uma instância chatMessage numa mensagem de canal. A instância é especificada pelo @odata.id valor.

    {
        "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 obter uma descrição completa dos dados enviados quando as notificações de alteração são entregues, veja changeNotificationCollection resource type (Tipo de recurso changeNotificationCollection).

    Descriptografar a chave simétrica

    A seção contém alguns trechos de código úteis que usam C# e .NET em cada etapa da descriptografia.

    // 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 assinatura de dados usando 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.
    }
    

    Descriptografar o conteúdo dos dados de recursos

    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.