Partilhar via


Configurar notificações de alteração do Microsoft Graph com dados de recursos

O Microsoft Graph permite que as aplicações subscrevam e recebam notificações sobre alterações aos recursos. Este artigo explica como configurar notificações avançadas, que incluem dados de recursos diretamente no payload de notificação.

As notificações avançadas removem a necessidade de chamadas adicionais à API para obter recursos atualizados, tornando-a mais rápida e fácil de executar a lógica de negócio.

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
Copilot aiInteraction Interações de IA copilot das quais um determinado utilizador faz parte: copilot/users/{userId}/interactionHistory/getAllEnterpriseInteractions

Interações de IA do Copilot numa organização: copilot/interactionHistory/getAllEnterpriseInteractions
Cotas máximas de assinaturas:
  • Combinação por aplicação e inquilino (para subscrições que monitorizam as interações de IA num inquilino): 1
  • Combinação por aplicação e utilizador (para subscrições que monitorizam as interações de IA de que um determinado utilizador faz parte): 1
  • Por utilizador (para subscrições que monitorizam as interações de IA de que um determinado utilizador faz parte): 10 subscrições.
  • Por organização: 10 000 subscrições totais.
  • 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 em todas as equipas no inquilino: /teams/getAllMembers

    Alterações à associação em todos os canais numa equipa específica: /teams/{id}/channels/getAllMembers

    Alterações à associação para todos os canais em todo o inquilino: /teams/getAllChannels/getAllMembers

    Alterações à associação num chat específico: /chats/{id}/members

    Alterações à associação para todas as conversas do Teams: /chats/getAllMembers
    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 onlineMeeting * Alterações a uma reunião online: /communications/onlineMeetings(joinWebUrl='{encodedJoinWebUrl}')/meetingCallEvents * 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. Uma subscrição permitida por aplicação por reunião online. Para obter mais informações, consulte Obter notificações de alteração para atualizações de eventos de chamada de reunião do Microsoft Teams.
    Teams presença Alterações à presença de um único utilizador: /communications/presences/{id}

    Alterações à presença de vários utilizadores: /communications/presences?$filter=id in ({id},{id}...)
    A subscrição para a presença de vários utilizadores está limitada a 650 utilizadores distintos. 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. Uma subscrição permitida por aplicação por utilizador delegado. Para obter mais informações, consulte Obter notificações de alteração para atualizações de presença no Microsoft Teams.
    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 dados de recursos com estes detalhes:

    • O ID e o tipo da instância de recurso alterada, encontrados na propriedade resourceData .
    • Todos os valores de propriedade da instância de recurso, encriptados conforme especificado na subscrição, encontrados na propriedade encryptedContent .
    • Propriedades específicas do recurso, consoante o recurso, ou se solicitado através de um $select parâmetro no URL do recurso da subscrição.

    Criar uma assinatura

    Para configurar notificações avançadas, siga os mesmos passos das notificações de alteração básicas, mas inclua estas propriedades necessárias:

    • includeResourceData: defina esta opção para true pedir dados de recursos.
    • encryptionCertificate: forneça a chave pública que o Microsoft Graph utiliza para encriptar os dados de recursos. Saiba mais em Desencriptar dados de recursos a partir de notificações de alteração.
    • encryptionCertificateId: forneça um identificador para que o certificado corresponda às notificações com a chave de desencriptação correta.

    Valide ambos os pontos de extremidade conforme descrito em Validação de ponto de extremidade de notificação. Se utilizar o mesmo URL para ambos os pontos finais, receberá e deverá responder a dois pedidos de validação.

    Exemplo: Pedido de subscrição

    Este exemplo cria uma subscrição para mensagens de canal 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": "{customId}",
      "expirationDateTime": "2019-09-19T11:00:00.0000000Z",
      "clientState": "{secretClientState}"
    }
    

    Notificações do ciclo de vida da assinatura

    Os eventos podem interromper o fluxo de notificações de alteração numa subscrição. As notificações de ciclo de vida indicam que ações tomar para manter o fluxo ininterruptamente. Ao contrário das notificações de alteração de recursos, as notificações de ciclo de vida focam-se no estado da subscrição.

    Para saber mais, veja Reduzir subscrições em falta e alterar notificações.

    Validar a autenticidade das notificações

    Verifique sempre a autenticidade das notificações de alteração antes de as processar. Isto impede que a sua aplicação acione lógica de negócio incorreta através de notificações falsas de terceiros.

    Para notificações básicas, valide-as com o valor clientState , conforme explicado em Processar a notificação de alteração. Para notificações avançadas, execute passos de validação adicionais.

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

    As notificações avançadas incluem uma propriedade validationTokens , que contém uma matriz de JSON Web Tokens (JWT). Cada token é exclusivo para a aplicação e o par de inquilinos. Uma notificação de alteração pode 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 de 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) ou uma biblioteca de terceiros para validar tokens. Siga estas etapas:

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

    • Responda à notificação com um HTTP 202 Accepted código de status imediatamente.
    • Responda antes de validar a notificação de alteração, mesmo que a validação falhe mais tarde. Responda imediatamente após receber a notificação de alteração, quer armazene notificações em filas para processamento posterior ou processe-as de imediato.
    • Aceitar e responder a uma notificação de alteração impede repetições de entrega desnecessárias e oculta os resultados de validação de potenciais atacantes. Pode sempre 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.

    Siga estes passos para validar os tokens e as aplicações que os geram:

    1. Confirme que o token não expirou.

    2. Confirme que o plataforma de identidade da Microsoft emitiu o token e que não foi adulterado.

      • Obtenha as chaves de assinatura do ponto de extremidade de configuração comum: https://login.microsoftonline.com/common/.well-known/openid-configuration. A sua aplicação pode colocar esta configuração em cache 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. Confirme que o token foi emitido para a sua aplicação.

      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. Confirme que a propriedade do azp token corresponde ao valor esperado de , que representa o editor de 0bf30f3b-4a52-48df-9a82-234910c4a086notificação de alteração do Microsoft Graph.

    Exemplo de Token JWT

    O exemplo seguinte mostra as propriedades no token JWT necessário para validação.

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

    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 numa notificação de alteração inclui o ID básico e as informações de tipo de uma instância de recurso. A propriedade encryptedData tem os dados de recursos completos, encriptados pelo Microsoft Graph com a chave pública fornecida na subscrição. A propriedade também contém valores necessários para verificação e descriptografia. Esta encriptação é feita para aumentar a segurança dos dados dos clientes acedidos através de notificações de alteração. Proteja a chave privada para garantir que terceiros não conseguem desencriptar os dados dos clientes, mesmo que intercetem 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

    Altere periodicamente as chaves assimétricas para minimizar o risco de uma chave privada ficar comprometida. 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:

      • O certificado antigo ainda pode ser utilizado para encriptação durante algum tempo. 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.

    Suponha 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. Identifique o certificado correto com a propriedade encryptionCertificateId .

    2. Inicialize um componente criptográfico RSA com a chave privada. Uma forma fácil de inicializar um componente RSA é utilizar o Método RSACertificateExtensions.GetRSAPrivateKey(X509Certificate2) com uma instância X509Certificate2 , que contém a chave privada descrita em Gerir chaves de encriptação.

    3. Desencripte a chave simétrica na propriedade dataKey de cada item na notificação de alteração com a sua chave privada. Utilize o Preenchimento de Encriptação Assimétrica Ideal (OAEP) como algoritmo de desencriptação.

    4. Utilize a chave simétrica para calcular a assinatura HMAC-SHA256 para o valor nos dados. Compare-o com o valor em dataSignature. Se não corresponderem, suponha que o payload foi adulterado e não o desencripte.

    5. Desencripte a propriedade de dados com a chave simétrica com a Standard de Encriptação Avançada (AES), como os .NET Aes.

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

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

    Os dados desencriptados serão uma cadeia JSON que representa o recurso.

    Exemplo: Desencriptar dados de recursos

    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. O @odata.id valor especifica a instância.

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

    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.