Configurar notificações de alteração que incluam dados de recurso

O Microsoft Graph permite que os aplicativos assinem e recebam notificações de alteração para recursos por meio de diferentes canais de entrega. É possíve configurar as assinaturas para incluir os dados alterados dos recursos (como o conteúdo de uma mensagem de bate-papo ou informações de presença do Microsoft Teams) nas notificações de alteração. As notificações de alteração que incluem os dados de alteração de recurso são chamadas de notificações avançadas. Seu aplicativo pode usar notificações avançadas para executar sua lógica de negócios sem precisar fazer uma chamada de API separada para buscar o recurso alterado.

Este artigo orienta você no processo de configuração de notificações avançadas em seu aplicativo.

Recursos com suporte

Notificações avançadas estão disponíveis para os recursos a seguir.

Observação

Notificações avançadas para assinaturas em pontos de extremidade marcados com um asterisco (*) só estão disponíveis no /beta ponto de extremidade.

Recurso Caminhos de recursos com suporte Limitações
Evento do Outlook Alterações em todos os eventos na caixa de correio de um usuário: /users/{id}/events $select Requer retornar apenas um subconjunto de propriedades na notificação avançada. Para obter mais informações, consulte Alterar notificações para recursos do Outlook.
Mensagem do Outlook Alterações em todas as mensagens na caixa de correio de um usuário: /users/{id}/messages

Alterações nas mensagens na caixa de entrada de um usuário: /users/{id}/mailFolders/{id}/messages
$select Requer retornar apenas um subconjunto de propriedades na notificação avançada. Para obter mais informações, consulte Alterar notificações para recursos do Outlook.
Contato pessoal do Outlook Alterações em todos os contatos pessoais na caixa de correio de um usuário: /users/{id}/contacts

Alterações em todos os contatos pessoais no contactFolder de um usuário: /users/{id}/contactFolders/{id}/contacts
$select Requer retornar apenas um subconjunto de propriedades na notificação avançada. Para obter mais informações, consulte Alterar notificações para recursos do Outlook.
Chamada do TeamsRecording Todas as gravações em uma 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 em uma reunião organizada por um usuário específico: users/{id}/onlineMeetings/getAllRecordings

Uma gravação de chamada que se torna disponível em uma reunião em que um aplicativo específico do Teams está instalado: appCatalogs/teamsApps/{id}/installedToOnlineMeetings/getAllRecordings *
Cotas máximas de assinaturas:
  • Por aplicativo e combinação de reunião online: 1
  • Por aplicativo e combinação de usuário: 1
  • Por usuário (para assinaturas que acompanham gravações em todos os onlineMeetings organizados pelo usuário): 10 assinaturas.
  • Por organização: 10.000 assinaturas totais.
  • Teams callTranscript Todas as transcrições em uma 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 em uma reunião organizada por um usuário específico: users/{id}/onlineMeetings/getAllTranscripts

    Uma transcrição de chamada que se torna disponível em uma reunião em que um determinado aplicativo do Teams está instalado: appCatalogs/teamsApps/{id}/installedToOnlineMeetings/getAllTrancripts *
    Cotas máximas de assinaturas:
  • Por aplicativo e combinação de reunião online: 1
  • Por aplicativo e combinação de usuário: 1
  • Por usuário (para assinaturas que acompanham transcrições em todos os onlineMeetings organizados pelo usuário): 10 assinaturas.
  • Por organização: 10.000 assinaturas totais.
  • Canal do Teams Alterações nos canais em todas as equipes: /teams/getAllChannels

    Alterações no canal em uma equipe específica: /teams/{id}/channels
    -
    Chat do Teams Alterações em qualquer chat no locatário: /chats

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

    Alterações nas mensagens de chat em um canal específico: /teams/{id}/channels/{id}/messages

    Alterações nas mensagens de chat em todos os chats: /chats/getAllMessages

    Alterações nas mensagens de chat em um chat específico: /chats/{id}/messages

    Alterações nas mensagens de chat em todos os chats que um determinado usuário faz parte: /users/{id}/chats/getAllMessages
    Não dá suporte ao uso $select para retornar apenas propriedades selecionadas. A notificação avançada consiste em todas as propriedades da instância alterada.
    conversationMember do Teams Alterações na associação em uma equipe específica: /teams/{id}/members



    Alterações na associação em um chat específico: /chats/{id}/members
    -
    Equipes onlineMeeting * Alterações em uma reunião online: /communications/onlineMeetings/?$filter=JoinWebUrl eq '{joinWebUrl} * Não dá suporte ao uso $select para retornar apenas propriedades selecionadas. A notificação avançada consiste em todas as propriedades da instância alterada.
    Teams presença Alterações na presença de um único usuário: /communications/presences/{id} Não dá suporte ao uso $select para retornar apenas propriedades selecionadas. A notificação avançada consiste em todas as propriedades da instância alterada.
    Equipe do Teams Alterações em qualquer equipe no locatário: /teams

    Alterações em uma equipe específica: /teams/{id}
    -

    Dados de recursos na carga de notificação

    Em geral, esse tipo de notificações de alteração inclui os seguintes dados de recurso no conteúdo:

    • 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

    Notificações avançadas são configuradas da mesma forma que as notificações básicas de alteração.

    Para segurança, o Microsoft Graph criptografa os dados de recurso retornados em uma notificação avançada. Você deve fornecer uma chave de criptografia pública como parte da criação da assinatura. Para obter mais informações sobre como criar e gerenciar chaves de criptografia, consulte Descriptografando dados de recursos de notificações de alteração.

    Para criar uma assinatura que inclua notificações avançadas, você deve especificar as seguintes propriedades:

    • 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 usa para criptografar os dados de recurso que retorna ao seu aplicativo.
    • 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.

    Lembre-se do seguinte:

    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 recurso que informa uma alteração em uma instância de recurso, uma notificação de ciclo de vida é sobre a própria assinatura e seu estado atual no ciclo de vida.

    Para obter mais informações sobre como receber e responder às notificações do ciclo de vida, confira Reduzir assinaturas ausentes e alterar notificações.

    Validando a autenticidade das notificações

    Os aplicativos geralmente executam a lógica comercial com base nos dados de recursos incluídos nas notificações de alterações. Verificar a autenticidade de cada notificação de alteração primeiro é importante. Caso contrário, um terceiro poderá imitar seu aplicativo com notificações de alteração falsa e fazê-lo executar a lógica de negócios incorretamente, e isso pode levar a um incidente de segurança.

    Para notificações básicas de alteração que não contêm dados de recurso, basta validá-los com base no valor clientState , conforme descrito no Processamento da notificação de alteração. Isso é aceitável, uma vez que você pode fazer chamadas confiáveis subsequentes do Microsoft Graph para obter acesso ao dados do recurso, portanto, o impacto das tentativas de falsificação é limitado.

    Para notificações de alteração que entregam dados de recursos, execute uma validação mais completa antes de processar os dados.

    Nesta seção:

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

    Uma notificação de alteração com dados de recurso contém uma propriedade adicional, validationTokens, que contém uma matriz de JSON Web Tokens (JWT) gerada pelo Microsoft Graph. O Microsoft Graph gera um único token para cada par de aplicativos e locatários distintos para os quais há um item na matriz de valor . Lembre-se de que as notificações de alteração podem conter uma combinação de itens para vários aplicativos e locatários que se inscreveram usando a mesma notificaçãoUrl.

    Nota: Se você estiver configurando notificações de alteração entregues através dos Hubs de Eventos do Azure, o Microsoft Graph não enviará os tokens de validação. Microsoft Graph não precisa validar o notificationUrl.

    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": "84bd8158-6d4d-4958-8b9f-9d6445542f95",
                "changeType": "created",
                ...
            }
        ],
        "validationTokens": [
            "eyJ0eXAiOiJKV1QiLCJhb...",
            "cGlkYWNyIjoiMiIsImlkc..."
        ]
    }
    

    Nota: para obter uma descrição completa dos dados enviados quando as notificações de alterações são entregues, consulte changeNotificationCollection .

    Como validar

    Use o MSAL para ajudá-lo a lidar com a validação de token ou uma biblioteca de terceiros para uma plataforma diferente.

    Fique atento ao seguinte:

    • 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 (por exemplo, se você armazenar as notificações de alteração em filas para processamento posterior) ou depois (se você as processar instantaneamente), mesmo se a validação falhar.
    • 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. Você sempre pode optar por ignorar uma notificação de alteração inválida após aceitá-la.

    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. Valide se o token não expirou.

    2. A validação do token não foi adulterada e foi emitida 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. Essa configuração é armazenada em cache pelo aplicativo por algum tempo. Lembre-se de que a configuração é atualizada frequentemente, uma vez que as chaves de assinatura são giradas 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.
      • Isso garante que as notificações de alteração não sejam enviadas por um aplicativo diferente que não seja o Microsoft Graph.

    Exemplo de Token JWT

    A seguir, é apresentado um exemplo das 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. É sua responsabilidade proteger a chave privada para garantir que os dados do cliente não possam ser descriptografados por terceiros, mesmo que eles consigam interceptar as notificações de alteração originais.

    Nesta seção:

    Gerenciar as chaves de criptografia

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

      • Você pode autoassinar o certificado, já que o Microsoft Graph não verifica o emissor de certificado e usa a chave pública apenas para criptografia.

      • Use o Cofre da Chave do Azure como a solução para criar, girar e gerenciar certificados com segurança. Cerifique-se de que as chaves atendem aos seguintes critérios:

        • A chave deve ser do tipo RSA
        • O tamanho da chave deve estar entre 2.048 bits e 4.096 bits
    2. Exportar o certificar no formato base64-encoded X.509 e incluir apenas a chave pública.

    3. Ao criar uma assinatura:

      • Forneça o certificado na propriedade encryptionCertificate usando o conteúdo base64-encoded 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 isso como parte da renovação da assinatura regular.
      • 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. Lembre-se do seguinte:

      • Por algum tempo, o certificado antigo ainda pode ser usado para criptografia. 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.
      • Descarte o certificado antigo somente quando não houver notificações de alterações recentes fazendo referência a ele.

    Descriptografar dados de recursos

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

    • Ele gera uma chave simétrica de uso único e a usa para criptografar os dados do recurso.
    • 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 eles não corresponderem, suponha que a carga tenha sido adulterada e não a descriptografe.

    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

    A seguir, é apresentado um exemplo de notificação de alteração que inclui valores de propriedade criptografados de uma chatMessage instância em uma 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..."
        ]
    }
    

    Nota: para obter uma descrição completa dos dados enviados quando as notificações de alterações são entregues, consulte changeNotificationCollection .

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

    Descriptografar a chave simétrica

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

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