Elementy webhook Centrum partnerskiego
Dotyczy: Centrum partnerskie | Centrum partnerskie obsługiwane przez firmę 21Vianet | Centrum partnerskie dla chmury firmy Microsoft dla instytucji rządowych USA
Odpowiednie role: administrator globalny | Administrator rozliczeń | Agent administracyjny | Agent sprzedaży | Agent pomocy technicznej
Interfejsy API elementu webhook Centrum partnerskiego umożliwiają partnerom rejestrowanie się na potrzeby zdarzeń zmiany zasobów. Te zdarzenia są dostarczane w postaci poST HTTP do zarejestrowanego adresu URL partnera. Aby odebrać zdarzenie z Centrum partnerskiego, partnerzy hostują wywołanie zwrotne, w którym Centrum partnerskie może opublikować zdarzenie zmiany zasobu. Zdarzenie jest podpisane cyfrowo, aby partner mógł sprawdzić, czy został wysłany z Centrum partnerskiego. Powiadomienia elementu webhook są wyzwalane tylko w środowisku, które ma najnowszą konfigurację wspólnej sprzedaży.
Centrum partnerskie obsługuje następujące zdarzenia elementu webhook.
Wykryto zdarzenie oszustwa platformy Azure ("azure-fraud-event-detected")
To zdarzenie jest zgłaszane po wykryciu zdarzenia oszustwa platformy Azure.
Zdarzenie zatwierdzonej relacji delegowanej administratora ("dap-admin-relationship-approved")
To zdarzenie jest zgłaszane, gdy delegowane uprawnienia administratora są zatwierdzane przez dzierżawę klienta.
Relacja odsprzedawcy zaakceptowana przez zdarzenie klienta ("reseller-relationship-accepted-by-customer")
To zdarzenie jest zgłaszane, gdy dzierżawa klienta zatwierdzi relację odsprzedawcy.
Relacja odsprzedawcy pośredniego zaakceptowana przez zdarzenie klienta ("indirect-reseller-relationship-accepted-by-customer")
To zdarzenie jest zgłaszane, gdy dzierżawa klienta zatwierdzi relację odsprzedawcy pośredniego.
Zdarzenie zakończonej relacji administratora delegowanego ("dap-admin-relationship-terminated")
To zdarzenie jest zgłaszane, gdy klient zakończy uprawnienia administratora delegowanego.
Relacja administratora dap zakończona przez zdarzenie firmy Microsoft ("dap-admin-relationship-terminated-by-microsoft")
To zdarzenie jest zgłaszane, gdy firma Microsoft kończy obsługę języka DAP między dzierżawą Partner i Klient, gdy usługa DAP jest nieaktywna przez ponad 90 dni.
Zdarzenie Aktywowane szczegółowego przypisania dostępu administratora ("szczegółowe aktywowane przypisanie dostępu administratora)
To zdarzenie jest zgłaszane, gdy partner aktywuje przypisanie dostępu Uprawnień administratora delegowanego szczegółowego po przypisaniu ról firmy Microsoft Entra do określonych grup zabezpieczeń.
Zdarzenie utworzonego szczegółowego przypisania dostępu administratora ("granular-admin-access-assignment-created")
To zdarzenie jest zgłaszane, gdy partner tworzy przypisanie dostępu uprawnień administratora delegowanego szczegółowego. Partnerzy mogą przypisywać zatwierdzone przez klienta role firmy Microsoft do określonych grup zabezpieczeń.
Szczegółowe zdarzenie usunięcia przypisania dostępu administratora ("szczegółowe-administrator-access-assignment-deleted")
To zdarzenie jest zgłaszane, gdy partner usunie przypisanie dostępu do uprawnień administratora delegowanego szczegółowego.
Zaktualizowane zdarzenie szczegółowego przypisania dostępu administratora ("szczegółowe informacje o administracyjnym-dostępie-przypisaniu-zaktualizowano")
To zdarzenie jest zgłaszane, gdy partner aktualizuje przypisanie dostępu uprawnień administratora delegowanego szczegółowego.
Zdarzenie aktywowane szczegółowej relacji administratora ("szczegółowe aktywowane relacje administratora")
To zdarzenie jest zgłaszane, gdy zostanie utworzone szczegółowe uprawnienia administratora delegowanego i aktywne dla klienta do zatwierdzenia.
Szczegółowe zdarzenie zatwierdzonej relacji administratora ("szczegółowe zatwierdzenie relacji administratora")
To zdarzenie jest zgłaszane, gdy dzierżawa klienta zatwierdza szczegółowe uprawnienia administratora delegowanego.
Szczegółowe zdarzenie wygasłej relacji administratora ("szczegółowe relacje administratora wygasłe")
To zdarzenie jest zgłaszane po wygaśnięciu uprawnień administratora delegowanego szczegółowego.
Szczegółowe zdarzenie utworzonej relacji administratora ("szczegółowe tworzenie relacji administratora")
To zdarzenie jest zgłaszane podczas tworzenia szczegółowych uprawnień administratora delegowanego.
Zaktualizowane zdarzenie szczegółowej relacji administratora ("szczegółowe informacje dotyczące relacji administratora")
To zdarzenie jest zgłaszane, gdy klient lub partner aktualizuje szczegółowe uprawnienia administratora delegowanego.
Szczegółowe zdarzenie rozszerzonej relacji administratora ("szczegółowe relacje administratora-auto-extended")
To zdarzenie jest zgłaszane, gdy system automatycznie rozszerza uprawnienia administratora delegowanego szczegółowego.
Szczegółowe zdarzenie zakończonej relacji administratora ("szczegółowe zakończenie relacji administratora")
To zdarzenie jest zgłaszane, gdy partner lub dzierżawa klienta kończy szczegółowe uprawnienia administratora delegowanego.
Zdarzenie gotowe do faktury ("gotowe na fakturę")
To zdarzenie jest zgłaszane, gdy nowa faktura jest gotowa.
Zakończono migrację nowego handlu ("new-commerce-migration-completed")
To zdarzenie jest zgłaszane po zakończeniu migracji do nowego handlu.
Utworzono nową migrację handlową ("new-commerce-migration-created")
To zdarzenie jest zgłaszane podczas tworzenia nowej migracji handlowej.
Migracja nowego handlu nie powiodła się ("new-commerce-migration-failed")
To zdarzenie jest zgłaszane, gdy migracja nowego handlu nie powiedzie się.
Tworzenie transferu ("create-transfer")
To zdarzenie jest zgłaszane podczas tworzenia transferu.
Transfer aktualizacji ("update-transfer")
To zdarzenie jest zgłaszane po zaktualizowaniu transferu.
Complete Transfer ("complete-transfer")
To zdarzenie jest zgłaszane po zakończeniu transferu.
Transfer po awarii ("transfer w trybie fail-transfer")
To zdarzenie jest zgłaszane, gdy transfer zakończy się niepowodzeniem.
Nowy harmonogram migracji do handlu nie powiódł się ("new-commerce-migration-schedule-failed")
To zdarzenie jest zgłaszane, gdy nowy harmonogram migracji handlu nie powiedzie się.
Zdarzenie utworzone w odwołaniu ("utworzone odwołanie")
To zdarzenie jest zgłaszane podczas tworzenia odwołania.
Zdarzenie zaktualizowane odwołania ("zaktualizowano odwołania")
To zdarzenie jest zgłaszane po zaktualizowaniu odwołania.
Powiązane zdarzenie utworzone polecenia ("related-referral-created")
To zdarzenie jest zgłaszane podczas tworzenia powiązanego odwołania.
Powiązane zdarzenie aktualizacji odwołań ("related-referral-updated")
To zdarzenie jest zgłaszane po zaktualizowaniu powiązanego odwołania.
Aktywne zdarzenie subskrypcji ("subscription-active")
To zdarzenie jest zgłaszane po aktywowaniu subskrypcji.
Uwaga
Aktywny element webhook subskrypcji i odpowiadające mu zdarzenie dziennika aktywności są obecnie dostępne tylko dla dzierżaw piaskownicy.
Zdarzenie oczekujące na subskrypcję ("oczekiwanie na subskrypcję")
To zdarzenie jest zgłaszane, gdy odpowiednie zamówienie zostało pomyślnie odebrane, a tworzenie subskrypcji jest oczekujące.
Uwaga
Oczekujący element webhook subskrypcji i odpowiadające mu zdarzenie dziennika aktywności są obecnie dostępne tylko dla dzierżaw piaskownicy.
Zdarzenie odnawiane subskrypcji ("odnawiane subskrypcje")
To zdarzenie jest zgłaszane po zakończeniu odnawiania subskrypcji.
Uwaga
Odnawiany element webhook subskrypcji i odpowiadające mu zdarzenie dziennika aktywności są obecnie dostępne tylko dla dzierżaw piaskownicy.
Zdarzenie zaktualizowane subskrypcji ("subskrypcja zaktualizowana")
To zdarzenie jest zgłaszane, gdy subskrypcja ulegnie zmianie. Te zdarzenia są generowane, gdy oprócz zmian wprowadzonych za pośrednictwem interfejsu API Centrum partnerskiego następuje zmiana wewnętrzna.
Uwaga
Między upływem czasu zmiany subskrypcji a wyzwoleniem zdarzenia Aktualizacja subskrypcji trwa do 48 godzin.
Zdarzenie testowe ("utworzone test")
To zdarzenie umożliwia samodzielne dołączanie i testowanie rejestracji przez żądanie zdarzenia testowego, a następnie śledzenie postępu. Podczas próby dostarczenia zdarzenia można zobaczyć komunikaty o błędach odbierane od firmy Microsoft. To ograniczenie dotyczy tylko zdarzeń "utworzonych przez test". Dane starsze niż siedem dni są czyszczone.
Przekroczono zdarzenie progowe ("usagerecords-thresholdExceeded")
To zdarzenie jest zgłaszane, gdy kwota użycia platformy Microsoft Azure dla każdego klienta przekracza budżet wydatków na użycie (ich próg). Aby uzyskać więcej informacji, zobacz (Ustawianie budżetu wydatków na platformę Azure dla klientów/centrum partnerskiego/set-an-azure-spending-budget-for-your-customers).
Przyszłe zdarzenia elementu webhook zostaną dodane dla zasobów, które zmieniają się w systemie, którego partner nie kontroluje, a dalsze aktualizacje zostaną wprowadzone w celu uzyskania tych zdarzeń tak blisko "czasu rzeczywistego", jak to możliwe. Opinie partnerów na temat tego, które zdarzenia dodają wartość do swojej firmy, są przydatne podczas określania nowych zdarzeń do dodania.
Aby uzyskać pełną listę zdarzeń elementu webhook obsługiwanych przez Centrum partnerskie, zobacz Zdarzenia elementu webhook w Centrum partnerskim.
Wymagania wstępne
- Poświadczenia zgodnie z opisem w temacie Uwierzytelnianie w Centrum partnerskim. Ten scenariusz obsługuje uwierzytelnianie zarówno przy użyciu autonomicznych poświadczeń aplikacji, jak i aplikacji i użytkownika.
Odbieranie zdarzeń z Centrum partnerskiego
Aby odbierać zdarzenia z Centrum partnerskiego, musisz udostępnić publicznie dostępny punkt końcowy. Ponieważ ten punkt końcowy jest uwidoczniony, należy sprawdzić, czy komunikacja pochodzi z Centrum partnerskiego. Wszystkie otrzymane zdarzenia elementu webhook są podpisane cyfrowo przy użyciu certyfikatu, który jest łańcuchem do katalogu głównego firmy Microsoft. Zostanie również udostępniony link do certyfikatu użytego do podpisania zdarzenia. Dzięki temu certyfikat może zostać odnowiony bez konieczności ponownego wdrażania lub ponownego konfigurowania usługi. Centrum partnerskie wykonuje 10 prób dostarczenia zdarzenia. Jeśli zdarzenie nie zostanie jeszcze dostarczone po 10 próbach, zostanie przeniesione do kolejki offline i nie zostaną wykonane dalsze próby dostarczenia.
Poniższy przykład przedstawia zdarzenie opublikowane w Centrum partnerskim.
POST /webhooks/callback
Content-Type: application/json
Authorization: Signature VOhcjRqA4f7u/4R29ohEzwRZibZdzfgG5/w4fHUnu8FHauBEVch8m2+5OgjLZRL33CIQpmqr2t0FsGF0UdmCR2OdY7rrAh/6QUW+u+jRUCV1s62M76jbVpTTGShmrANxnl8gz4LsbY260LAsDHufd6ab4oejerx1Ey9sFC+xwVTa+J4qGgeyIepeu4YCM0oB2RFS9rRB2F1s1OeAAPEhG7olp8B00Jss3PQrpLGOoAr5+fnQp8GOK8IdKF1/abUIyyvHxEjL76l7DVQN58pIJg4YC+pLs8pi6sTKvOdSVyCnjf+uYQWwmmWujSHfyU37j2Fzz16PJyWH41K8ZXJJkw==
X-MS-Certificate-Url: https://3psostorageacct.blob.core.windows.net/cert/pcnotifications-dispatch.microsoft.com.cer
X-MS-Signature-Algorithm: rsa-sha256
Host: api.partnercenter.microsoft.com
Accept-Encoding: gzip, deflate
Content-Length: 195
{
"EventName": "test-created",
"ResourceUri": "http://localhost:16722/v1/webhooks/registration/test",
"ResourceName": "test",
"AuditUri": null,
"ResourceChangeUtcDate": "2017-11-16T16:19:06.3520276+00:00"
}
Uwaga
Nagłówek Autoryzacja ma schemat "Signature". Jest to zakodowany w formacie base64 podpis zawartości.
Jak uwierzytelnić wywołanie zwrotne
Aby uwierzytelnić zdarzenie wywołania zwrotnego odebrane z Centrum partnerskiego, wykonaj następujące kroki:
- Sprawdź, czy istnieją wymagane nagłówki (Authorization, x-ms-certificate-url, x-ms-signature-algorithm).
- Pobierz certyfikat użyty do podpisania zawartości (x-ms-certificate-url).
- Sprawdź łańcuch certyfikatów.
- Sprawdź wartość "Organizacja" certyfikatu.
- Odczytywanie zawartości z kodowaniem UTF8 w buforze.
- Utwórz dostawcę kryptograficznego RSA.
- Sprawdź, czy dane pasują do tego, co zostało podpisane przy użyciu określonego algorytmu wyznaczania wartości skrótu (na przykład SHA256).
- Jeśli weryfikacja zakończy się pomyślnie, przetwórz komunikat.
Uwaga
Domyślnie token podpisu jest wysyłany w nagłówku autoryzacji. Jeśli ustawisz parametr SignatureTokenToMsSignatureHeader na wartość true w rejestracji, token podpisu zostanie wysłany w nagłówku x-ms-signature.
Model zdarzeń
W poniższej tabeli opisano właściwości zdarzenia Centrum partnerskiego.
Właściwości
Nazwa/nazwisko | opis |
---|---|
EventName | Nazwa zdarzenia. W formularzu {resource}-{action}. Na przykład "test-created". |
Identyfikator ResourceUri | Identyfikator URI zmienionego zasobu. |
ResourceName | Nazwa zmienionego zasobu. |
AuditUrl | Opcjonalny. Identyfikator URI rekordu inspekcji. |
ResourceChangeUtcDate | Data i godzina w formacie UTC, kiedy nastąpiła zmiana zasobu. |
Przykład
Poniższy przykład przedstawia strukturę zdarzenia Centrum partnerskiego.
{
"EventName": "test-created",
"ResourceUri": "http://api.partnercenter.microsoft.com/webhooks/v1/registration/validationEvents/c0bfd694-3075-4ec5-9a3c-733d3a890a1f",
"ResourceName": "test",
"AuditUri": null,
"ResourceChangeUtcDate": "2017-11-16T16:19:06.3520276+00:00"
}
Interfejsy API elementów webhook
Uwierzytelnianie
Wszystkie wywołania interfejsów API elementu webhook są uwierzytelniane przy użyciu tokenu elementu nośnego w nagłówku autoryzacji. Uzyskiwanie tokenu dostępu w celu uzyskania dostępu do https://api.partnercenter.microsoft.com
elementu . Ten token jest tym samym tokenem, który jest używany do uzyskiwania dostępu do pozostałych interfejsów API Centrum partnerskiego.
Pobieranie listy zdarzeń
Zwraca listę zdarzeń obecnie obsługiwanych przez interfejsy API elementu webhook.
Adres URL zasobu
https://api.partnercenter.microsoft.com/webhooks/v1/registration/events
Przykład żądania
GET /webhooks/v1/registration/events
content-type: application/json
authorization: Bearer eyJ0e.......
accept: */*
host: api.partnercenter.microsoft.com
Przykład odpowiedzi
HTTP/1.1 200
Status: 200
Content-Length: 183
Content-Type: application/json; charset=utf-8
Content-Encoding: gzip
Vary: Accept-Encoding
MS-CorrelationId: aaaa0000-bb11-2222-33cc-444444dddddd
MS-RequestId: 79419bbb-06ee-48da-8221-e09480537dfc
X-Locale: en-US
[ "subscription-updated", "test-created", "usagerecords-thresholdExceeded" ]
Rejestrowanie w celu odbierania zdarzeń
Rejestruje dzierżawę w celu odbierania określonych zdarzeń.
Adres URL zasobu
https://api.partnercenter.microsoft.com/webhooks/v1/registration
Przykład żądania
POST /webhooks/v1/registration
Content-Type: application/json
Authorization: Bearer eyJ0e.....
Accept: */*
Host: api.partnercenter.microsoft.com
Accept-Encoding: gzip, deflate
Content-Length: 219
{
"WebhookUrl": "{{YourCallbackUrl}}",
"WebhookEvents": ["subscription-updated", "test-created"]
}
Przykład odpowiedzi
HTTP/1.1 200
Status: 200
Content-Length: 346
Content-Type: application/json; charset=utf-8
content-encoding: gzip
Vary: Accept-Encoding
MS-CorrelationId: bbbb1111-cc22-3333-44dd-555555eeeeee
MS-RequestId: f04b1b5e-87b4-4d95-b087-d65fffec0bd2
{
"SubscriberId": "e82cac64-dc67-4cd3-849b-78b6127dd57d",
"WebhookUrl": "{{YourCallbackUrl}}",
"WebhookEvents": [ "subscription-updated", "test-created" ]
}
Wyświetlanie rejestracji
Zwraca rejestrację zdarzeń elementów webhook dla dzierżawy.
Adres URL zasobu
https://api.partnercenter.microsoft.com/webhooks/v1/registration
Przykład żądania
GET /webhooks/v1/registration
Content-Type: application/json
Authorization: Bearer ...
Accept: */*
Host: api.partnercenter.microsoft.com
Accept-Encoding: gzip, deflate
Przykład odpowiedzi
HTTP/1.1 200
Status: 200
Content-Length: 341
Content-Type: application/json; charset=utf-8
Content-Encoding: gzip
Vary: Accept-Encoding
MS-CorrelationId: cccc2222-dd33-4444-55ee-666666ffffff
MS-RequestId: ca30367d-4b24-4516-af08-74bba6dc6657
X-Locale: en-US
{
"WebhookUrl": "{{YourCallbackUrl}}",
"WebhookEvents": ["subscription-updated", "test-created"]
}
Aktualizowanie rejestracji zdarzeń
Aktualizuje istniejącą rejestrację zdarzeń.
Adres URL zasobu
https://api.partnercenter.microsoft.com/webhooks/v1/registration
Przykład żądania
PUT /webhooks/v1/registration
Content-Type: application/json
Authorization: Bearer eyJ0eXAiOR...
Accept: */*
Host: api.partnercenter.microsoft.com
Accept-Encoding: gzip, deflate
Content-Length: 258
{
"WebhookUrl": "{{YourCallbackUrl}}",
"WebhookEvents": ["subscription-updated", "test-created"]
}
Przykład odpowiedzi
HTTP/1.1 200
Status: 200
Content-Length: 346
Content-Type: application/json; charset=utf-8
content-encoding: gzip
Vary: Accept-Encoding
MS-CorrelationId: bbbb1111-cc22-3333-44dd-555555eeeeee
MS-RequestId: f04b1b5e-87b4-4d95-b087-d65fffec0bd2
{
"SubscriberId": "e82cac64-dc67-4cd3-849b-78b6127dd57d",
"WebhookUrl": "{{YourCallbackUrl}}",
"WebhookEvents": [ "subscription-updated", "test-created" ]
}
Wysyłanie zdarzenia testowego w celu zweryfikowania rejestracji
Generuje zdarzenie testowe w celu zweryfikowania rejestracji elementów webhook. Ten test ma na celu sprawdzenie, czy zdarzenia można odbierać z Centrum partnerskiego. Dane dotyczące tych zdarzeń są usuwane siedem dni po utworzeniu zdarzenia początkowego. Przed wysłaniem zdarzenia weryfikacji należy zarejestrować zdarzenie "utworzone przez test" przy użyciu interfejsu API rejestracji.
Uwaga
Podczas publikowania zdarzenia weryfikacji występuje limit ograniczenia 2 żądań na minutę.
Adres URL zasobu
https://api.partnercenter.microsoft.com/webhooks/v1/registration/validationEvents
Przykład żądania
POST /webhooks/v1/registration/validationEvents
MS-CorrelationId: dddd3333-ee44-5555-66ff-777777aaaaaa
Authorization: Bearer ...
Accept: */*
Host: api.partnercenter.microsoft.com
Accept-Encoding: gzip, deflate
Content-Length:
Przykład odpowiedzi
HTTP/1.1 200
Status: 200
Content-Length: 181
Content-Type: application/json; charset=utf-8
Content-Encoding: gzip
Vary: Accept-Encoding
MS-CorrelationId: eeee4444-ff55-6666-77aa-888888bbbbbb
MS-RequestId: 2f498d5a-a6ab-468f-98d8-93c96da09051
X-Locale: en-US
{ "correlationId": "eeee4444-ff55-6666-77aa-888888bbbbbb" }
Sprawdź, czy zdarzenie zostało dostarczone
Zwraca bieżący stan zdarzenia weryfikacji. Ta weryfikacja może być przydatna do rozwiązywania problemów z dostarczaniem zdarzeń. Odpowiedź zawiera wynik dla każdej próby dostarczenia zdarzenia.
Adres URL zasobu
https://api.partnercenter.microsoft.com/webhooks/v1/registration/validationEvents/{correlationId}
Przykład żądania
GET /webhooks/v1/registration/validationEvents/eeee4444-ff55-6666-77aa-888888bbbbbb
MS-CorrelationId: dddd3333-ee44-5555-66ff-777777aaaaaa
Authorization: Bearer ...
Accept: */*
Host: api.partnercenter.microsoft.com
Accept-Encoding: gzip, deflate
Przykład odpowiedzi
HTTP/1.1 200
Status: 200
Content-Length: 469
Content-Type: application/json; charset=utf-8
Content-Encoding: gzip
Vary: Accept-Encoding
MS-CorrelationId: ffff5555-aa66-7777-88bb-999999cccccc
MS-RequestId: 0843bdb2-113a-4926-a51c-284aa01d722e
X-Locale: en-US
{
"correlationId": "eeee4444-ff55-6666-77aa-888888bbbbbb",
"partnerId": "00234d9d-8c2d-4ff5-8c18-39f8afc6f7f3",
"status": "completed",
"callbackUrl": "{{YourCallbackUrl}}",
"results": [{
"responseCode": "OK",
"responseMessage": "",
"systemError": false,
"dateTimeUtc": "2017-12-08T21:39:48.2386997"
}]
}
Przykład weryfikacji podpisu
Przykładowy podpis kontrolera wywołania zwrotnego (ASP.NET)
[AuthorizeSignature]
[Route("webhooks/callback")]
public IHttpActionResult Post(PartnerResourceChangeCallBack callback)
Walidacja podpisu
W poniższym przykładzie pokazano, jak dodać atrybut autoryzacji do kontrolera, który odbiera wywołania zwrotne z zdarzeń elementu webhook.
namespace Webhooks.Security
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http;
using System.Web.Http.Controllers;
using Microsoft.Partner.Logging;
/// <summary>
/// Signature based Authorization
/// </summary>
public class AuthorizeSignatureAttribute : AuthorizeAttribute
{
private const string MsSignatureHeader = "x-ms-signature";
private const string CertificateUrlHeader = "x-ms-certificate-url";
private const string SignatureAlgorithmHeader = "x-ms-signature-algorithm";
private const string MicrosoftCorporationIssuer = "O=Microsoft Corporation";
private const string SignatureScheme = "Signature";
/// <inheritdoc/>
public override async Task OnAuthorizationAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
{
ValidateAuthorizationHeaders(actionContext.Request);
await VerifySignature(actionContext.Request);
}
private static async Task<string> GetContentAsync(HttpRequestMessage request)
{
// By default the stream can only be read once and we need to read it here so that we can hash the body to validate the signature from microsoft.
// Load into a buffer, so that the stream can be accessed here and in the api when it binds the content to the expected model type.
await request.Content.LoadIntoBufferAsync();
var s = await request.Content.ReadAsStreamAsync();
var reader = new StreamReader(s);
var body = await reader.ReadToEndAsync();
// set the stream position back to the beginning
if (s.CanSeek)
{
s.Seek(0, SeekOrigin.Begin);
}
return body;
}
private static void ValidateAuthorizationHeaders(HttpRequestMessage request)
{
var authHeader = request.Headers.Authorization;
if (string.IsNullOrWhiteSpace(authHeader?.Parameter) && string.IsNullOrWhiteSpace(GetHeaderValue(request.Headers, MsSignatureHeader)))
{
throw new HttpResponseException(request.CreateErrorResponse(HttpStatusCode.Unauthorized, "Authorization header missing."));
}
var signatureHeaderValue = GetHeaderValue(request.Headers, MsSignatureHeader);
if (authHeader != null
&& !string.Equals(authHeader.Scheme, SignatureScheme, StringComparison.OrdinalIgnoreCase)
&& !string.IsNullOrWhiteSpace(signatureHeaderValue)
&& !signatureHeaderValue.StartsWith(SignatureScheme, StringComparison.OrdinalIgnoreCase))
{
throw new HttpResponseException(request.CreateErrorResponse(HttpStatusCode.Unauthorized, $"Authorization scheme needs to be '{SignatureScheme}'."));
}
if (string.IsNullOrWhiteSpace(GetHeaderValue(request.Headers, CertificateUrlHeader)))
{
throw new HttpResponseException(request.CreateErrorResponse(HttpStatusCode.BadRequest, $"Request header {CertificateUrlHeader} missing."));
}
if (string.IsNullOrWhiteSpace(GetHeaderValue(request.Headers, SignatureAlgorithmHeader)))
{
throw new HttpResponseException(request.CreateErrorResponse(HttpStatusCode.BadRequest, $"Request header {SignatureAlgorithmHeader} missing."));
}
}
private static string GetHeaderValue(HttpHeaders headers, string key)
{
headers.TryGetValues(key, out var headerValues);
return headerValues?.FirstOrDefault();
}
private static async Task VerifySignature(HttpRequestMessage request)
{
// Get signature value from either authorization header or x-ms-signature header.
var base64Signature = request.Headers.Authorization?.Parameter ?? GetHeaderValue(request.Headers, MsSignatureHeader).Split(' ')[1];
var signatureAlgorithm = GetHeaderValue(request.Headers, SignatureAlgorithmHeader);
var certificateUrl = GetHeaderValue(request.Headers, CertificateUrlHeader);
var certificate = await GetCertificate(certificateUrl);
var content = await GetContentAsync(request);
var alg = signatureAlgorithm.Split('-'); // for example RSA-SHA1
var isValid = false;
var logger = GetLoggerIfAvailable(request);
// Validate the certificate
VerifyCertificate(certificate, request, logger);
if (alg.Length == 2 && alg[0].Equals("RSA", StringComparison.OrdinalIgnoreCase))
{
var signature = Convert.FromBase64String(base64Signature);
var csp = (RSACryptoServiceProvider)certificate.PublicKey.Key;
var encoding = new UTF8Encoding();
var data = encoding.GetBytes(content);
var hashAlgorithm = alg[1].ToUpper();
isValid = csp.VerifyData(data, CryptoConfig.MapNameToOID(hashAlgorithm), signature);
}
if (!isValid)
{
// log that we were not able to validate the signature
logger?.TrackTrace(
"Failed to validate signature for webhook callback",
new Dictionary<string, string> { { "base64Signature", base64Signature }, { "certificateUrl", certificateUrl }, { "signatureAlgorithm", signatureAlgorithm }, { "content", content } });
throw new HttpResponseException(request.CreateErrorResponse(HttpStatusCode.Unauthorized, "Signature verification failed"));
}
}
private static ILogger GetLoggerIfAvailable(HttpRequestMessage request)
{
return request.GetDependencyScope().GetService(typeof(ILogger)) as ILogger;
}
private static async Task<X509Certificate2> GetCertificate(string certificateUrl)
{
byte[] certBytes;
using (var webClient = new WebClient())
{
certBytes = await webClient.DownloadDataTaskAsync(certificateUrl);
}
return new X509Certificate2(certBytes);
}
private static void VerifyCertificate(X509Certificate2 certificate, HttpRequestMessage request, ILogger logger)
{
if (!certificate.Verify())
{
logger?.TrackTrace("Failed to verify certificate for webhook callback.", new Dictionary<string, string> { { "Subject", certificate.Subject }, { "Issuer", certificate.Issuer } });
throw new HttpResponseException(request.CreateErrorResponse(HttpStatusCode.Unauthorized, "Certificate verification failed."));
}
if (!certificate.Issuer.Contains(MicrosoftCorporationIssuer))
{
logger?.TrackTrace($"Certificate not issued by {MicrosoftCorporationIssuer}.", new Dictionary<string, string> { { "Issuer", certificate.Issuer } });
throw new HttpResponseException(request.CreateErrorResponse(HttpStatusCode.Unauthorized, $"Certificate not issued by {MicrosoftCorporationIssuer}."));
}
}
}
}