Partner Center-Webhooks
Gilt für: Partner Center | Partner Center-Betrieb durch 21Vianet | Partner Center für Microsoft Cloud for US Government
Geeignete Rollen: Globaler Administrator | Abrechnungsadministrator | Administrator-Agent | Vertriebsmitarbeiter | Helpdesk-Agent
Mit den Partner Center-Webhook-APIs können Partner sich für Ressourcenänderungsereignisse registrieren. Diese Ereignisse werden in Form von HTTP-POSTs an die registrierte URL des Partners übermittelt. Um ein Ereignis vom Partner Center zu erhalten, hosten Partner einen Rückruf, bei dem Partner Center das Ereignis zum Ändern der Ressource POST bereitstellen kann. Das Ereignis ist digital signiert, damit der Partner überprüfen kann, ob es vom Partner Center gesendet wurde. Webhook-Benachrichtigungen werden nur für die Umgebung ausgelöst, die über die neueste Konfiguration für co-sell verfügt.
Partner Center unterstützt die folgenden Webhook-Ereignisse.
Azure Fraud Event Detected ("azure-fraud-event-detected")
Dieses Ereignis wird ausgelöst, wenn das Azure-Betrugsereignis erkannt wird.
Delegierte Administratorbeziehung genehmigtes Ereignis ("dap-admin-relationship-approved")
Dieses Ereignis wird ausgelöst, wenn die delegierten Administratorrechte vom Kundenmandanten genehmigt werden.
Reseller Relationship Accepted by Customer Event ("Reseller-relationship-accepted-by-customer")
Dieses Ereignis wird ausgelöst, wenn der Kunde die Reseller-Beziehung genehmigt.
Indirekte Händlerbeziehung, die vom Kundenereignis akzeptiert wird ("indirect-reseller-relationship-accepted-by-customer")
Dieses Ereignis wird ausgelöst, wenn der Kundenmandant die indirekte Händlerbeziehung genehmigt.
Ereignis "Delegierte Administratorbeziehung beendet" ("dap-admin-relationship-terminated")
Dieses Ereignis wird ausgelöst, wenn der Kunde die delegierten Administratorrechte beendet.
Dap Admin Relationship Terminated By Microsoft Event ("dap-admin-relationship-terminated-by-microsoft")
Dieses Ereignis wird ausgelöst, wenn Microsoft DAP zwischen Partner und Kundenmandanten beendet, wenn DAP länger als 90 Tage inaktiv ist.
Granular Admin Access Assignment Activated Event ("granular-admin-access-assignment-activated")
Dieses Ereignis wird ausgelöst, wenn der Partner die Zugriffszuweisung für granulare delegierte Administratorrechte aktiviert, sobald die Microsoft Entra-Rollen bestimmten Sicherheitsgruppen zugewiesen wurden.
Granular Admin Access Assignment Created Event ("granular-admin-access-assignment-created")
Dieses Ereignis wird ausgelöst, wenn der Partner die Zugriffszuweisung für granulare delegierte Administratorrechte erstellt. Partner können Kunden genehmigte Microsoft Entra-Rollen bestimmten Sicherheitsgruppen zuweisen.
Granular Admin Access Assignment Deleted Event ("granular-admin-access-assignment-deleted")
Dieses Ereignis wird ausgelöst, wenn der Partner die Zugriffszuweisung für granulare delegierte Administratorrechte löscht.
Granular Admin Access Assignment Updated Event ("granular-admin-access-assignment-updated")
Dieses Ereignis wird ausgelöst, wenn der Partner die Zugriffszuweisung für granulare delegierte Administratorrechte aktualisiert.
Granular Admin Relationship Activated Event ("granular-admin-relationship-activated")
Dieses Ereignis wird ausgelöst, wenn die granularen delegierten Administratorrechte erstellt und aktiv sind, damit der Kunde dies genehmigen kann.
Granular Admin Relationship Approved Event ("granular-admin-relationship-approved")
Dieses Ereignis wird ausgelöst, wenn der Kundenmandant die granularen delegierten Administratorrechte genehmigt.
Granular Admin Relationship Expired Event ("granular-admin-relationship-expired")
Dieses Ereignis wird ausgelöst, wenn die granularen delegierten Administratorrechte abgelaufen sind.
Granular Admin Relationship Created Event ("granular-admin-relationship-created")
Dieses Ereignis wird ausgelöst, wenn die granularen delegierten Administratorrechte erstellt werden.
Granular Admin Relationship Updated Event ("granular-admin-relationship-updated")
Dieses Ereignis wird ausgelöst, wenn der Kunde oder Partner die granularen delegierten Administratorrechte aktualisiert.
Granular Admin Relationship Auto Extended Event ("granular-admin-relationship-auto-extended")
Dieses Ereignis wird ausgelöst, wenn das System automatisch die granularen delegierten Administratorrechte erweitert.
Granular Admin Relationship Terminated Event ("granular-admin-relationship-terminated")
Dieses Ereignis wird ausgelöst, wenn der Partner- oder Kundenmandant die granularen delegierten Administratorrechte beendet.
Invoice Ready Event ("invoice-ready")
Dieses Ereignis wird ausgelöst, wenn die neue Rechnung bereit ist.
Neue Commerce-Migration abgeschlossen ("new-commerce-migration-completed")
Dieses Ereignis wird ausgelöst, wenn die neue E-Commerce-Migration abgeschlossen ist.
Neue Commerce-Migration erstellt ("new-commerce-migration-created")
Dieses Ereignis wird ausgelöst, wenn die neue Commerce-Migration erstellt wird.
New Commerce Migration Failed ("new-commerce-migration-failed")
Dieses Ereignis wird ausgelöst, wenn die neue E-Commerce-Migration fehlgeschlagen ist.
Transfer erstellen ("Create-transfer")
Dieses Ereignis wird ausgelöst, wenn die Übertragung erstellt wird.
Updateübertragung ("Updateübertragung")
Dieses Ereignis wird ausgelöst, wenn die Übertragung aktualisiert wird.
Vollständige Übertragung ("vollständiger Transfer")
Dieses Ereignis wird ausgelöst, wenn die Übertragung abgeschlossen ist.
Fehlerübertragung ("Fail-Transfer")
Dieses Ereignis wird ausgelöst, wenn die Übertragung fehlschlägt.
Neuer Handelsmigrationszeitplan fehlgeschlagen ("New-commerce-migration-schedule-failed")
Dieses Ereignis wird ausgelöst, wenn der neue Zeitplan für die E-Commerce-Migration fehlgeschlagen ist.
Empfehlung erstellt Ereignis ("Empfehlung erstellt")
Dieses Ereignis wird ausgelöst, wenn die Empfehlung erstellt wird.
Empfehlungs-Aktualisiertes Ereignis ("Empfehlung aktualisiert")
Dieses Ereignis wird ausgelöst, wenn die Empfehlung aktualisiert wird.
Related Referral Created Event ("related-referral-created")
Dieses Ereignis wird ausgelöst, wenn die zugehörige Empfehlung erstellt wird.
Related Referral Updated Event ("related-referral-updated")
Dieses Ereignis wird ausgelöst, wenn die zugehörige Empfehlung aktualisiert wird.
Subscription Active Event ("subscription-active")
Dieses Ereignis wird ausgelöst, wenn das Abonnement aktiviert wird.
Hinweis
Der Aktive Abonnementwebhook und das entsprechende Aktivitätsprotokollereignis sind derzeit nur für Sandkastenmandanten verfügbar.
Abonnement ausstehendes Ereignis ("Abonnement ausstehend")
Dieses Ereignis wird ausgelöst, wenn die entsprechende Bestellung erfolgreich empfangen wurde und die Abonnementerstellung aussteht.
Hinweis
Der Abonnement-Webhook und das entsprechende Aktivitätsprotokollereignis sind derzeit nur für Sandkastenmandanten verfügbar.
Abonnementneuerungsereignis ("Abonnement erneuert")
Dieses Ereignis wird ausgelöst, wenn das Abonnement die Verlängerung abgeschlossen hat.
Hinweis
Der abonnementneuerte Webhook und das entsprechende Aktivitätsprotokollereignis sind derzeit nur für Sandkastenmandanten verfügbar.
Subscription Updated Event ("subscription-updated")
Dieses Ereignis wird ausgelöst, wenn sich das Abonnement ändert. Diese Ereignisse werden generiert, wenn zusätzlich zu änderungen über die Partner Center-API eine interne Änderung vorhanden ist.
Hinweis
Es gibt eine Verzögerung von bis zu 48 Stunden zwischen dem Zeitpunkt, zu dem sich ein Abonnement ändert und das Ereignis "Abonnement aktualisiert" ausgelöst wird.
Testereignis ("test-created")
Mit diesem Ereignis können Sie Ihre Registrierung selbst onboarden und testen, indem Sie ein Testereignis anfordern und dann den Fortschritt nachverfolgen. Sie können die Fehlermeldungen sehen, die von Microsoft empfangen werden, während Sie versuchen, das Ereignis zu übermitteln. Diese Einschränkung gilt nur für "test-created"-Ereignisse. Daten, die älter als sieben Tage sind, werden gelöscht.
Threshold Exceeded-Ereignis ("usagerecords-thresholdExceeded")
Dieses Ereignis wird ausgelöst, wenn der Betrag der Microsoft Azure-Nutzung für jeden Kunden das Budget für Nutzungsausgaben (deren Schwellenwert) überschreitet. Weitere Informationen finden Sie unter (Festlegen eines Azure-Ausgabenbudgets für Ihre Kunden/partner-center/set-an-azure-spending-budget-for-your-customers).
Zukünftige Webhook-Ereignisse werden für Ressourcen hinzugefügt, die sich im System ändern, auf das der Partner nicht kontrolliert wird, und weitere Updates werden vorgenommen, um diese Ereignisse so nah wie möglich in Echtzeit zu erhalten. Feedback von Partnern, welche Ereignisse ihrem Unternehmen Einen Mehrwert verleihen, ist nützlich, um zu bestimmen, welche neuen Ereignisse hinzugefügt werden sollen.
Eine vollständige Liste der webhook-Ereignisse, die vom Partner Center unterstützt werden, finden Sie unter Partner Center-Webhook-Ereignisse.
Voraussetzungen
- Anmeldeinformationen, wie unter Partner Center-Authentifizierung beschrieben. Dieses Szenario unterstützt die Authentifizierung mit eigenständigen App- und App+Benutzeranmeldeinformationen.
Empfangen von Ereignissen vom Partner Center
Um Ereignisse vom Partner Center zu empfangen, müssen Sie einen öffentlich zugänglichen Endpunkt verfügbar machen. Da dieser Endpunkt verfügbar gemacht wird, müssen Sie überprüfen, ob die Kommunikation vom Partner Center stammt. Alle webhook-Ereignisse, die Sie empfangen, werden digital mit einem Zertifikat signiert, das mit dem Microsoft-Stamm verkettet ist. Außerdem wird ein Link zum Zertifikat bereitgestellt, das zum Signieren des Ereignisses verwendet wird. Dadurch kann das Zertifikat erneuert werden, ohne dass Sie Ihren Dienst erneut bereitstellen oder neu konfigurieren müssen. Partner Center versucht 10, das Ereignis zu übermitteln. Wenn das Ereignis nach 10 Versuchen immer noch nicht übermittelt wird, wird es in eine Offlinewarteschlange verschoben, und es werden keine weiteren Versuche zur Zustellung unternommen.
Das folgende Beispiel zeigt ein ereignis, das aus Partner Center veröffentlicht wurde.
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"
}
Hinweis
Der Autorisierungsheader weist ein Schema von "Signatur" auf. Dies ist eine base64-codierte Signatur des Inhalts.
So authentifizieren Sie den Rückruf
Führen Sie die folgenden Schritte aus, um das vom Partner Center empfangene Rückrufereignis zu authentifizieren:
- Überprüfen Sie, ob die erforderlichen Header vorhanden sind (Autorisierung, x-ms-certificate-url, x-ms-signature-algorithm).
- Lade das Zertifikat herunter, das zum Signieren des Inhalts verwendet wurde (x-ms-certificate-url).
- Überprüfen Sie die Zertifikatkette.
- Überprüfen Sie die "Organisation" des Zertifikats.
- Lesen Sie den Inhalt mit UTF8-Codierung in einen Puffer.
- Erstelle einen RSA-Kryptografieanbieter.
- Überprüfen Sie, welche Daten mit dem angegebenen Hashalgorithmus signiert wurden (z. B. SHA256).
- Wenn die Überprüfung erfolgreich ist, verarbeiten Sie die Nachricht.
Hinweis
Standardmäßig wird das Signaturtoken in einem Autorisierungsheader gesendet. Wenn Sie SignatureTokenToMsSignatureHeader in Ihrer Registrierung auf "true" festlegen, wird das Signaturtoken stattdessen im x-ms-signature-Header gesendet.
Ereignismodell
In der folgenden Tabelle werden die Eigenschaften eines Partner Center-Ereignisses beschrieben.
Eigenschaften
Name des Dataflows | Beschreibung |
---|---|
EventName | Der Name des Ereignisses. Im Formular {resource}-{action}. Beispiel: "test-created". |
ResourceUri | Der URI der Ressource, die geändert wurde. |
ResourceName | Der Name der Ressource, die geändert wurde. |
AuditUrl | Optional. Der URI des Überwachungsdatensatzes. |
ResourceChangeUtcDate | Datum und Uhrzeit im UTC-Format, wenn die Ressourcenänderung aufgetreten ist. |
Beispiel
Das folgende Beispiel zeigt die Struktur eines Partner Center-Ereignisses.
{
"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"
}
Webhook-APIs
Authentifizierung
Alle Aufrufe der Webhook-APIs werden mithilfe des Bearer-Tokens im Autorisierungsheader authentifiziert. Abrufen eines Zugriffstokens für den Zugriff https://api.partnercenter.microsoft.com
. Dieses Token ist dasselbe Token, das für den Zugriff auf die restlichen Partner Center-APIs verwendet wird.
Abrufen einer Liste von Ereignissen
Gibt eine Liste der Ereignisse zurück, die derzeit von den Webhook-APIs unterstützt werden.
Ressourcen-URL
https://api.partnercenter.microsoft.com/webhooks/v1/registration/events
Anforderungsbeispiel
GET /webhooks/v1/registration/events
content-type: application/json
authorization: Bearer eyJ0e.......
accept: */*
host: api.partnercenter.microsoft.com
Beispielantwort
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" ]
Registrieren für den Empfang von Ereignissen
Registriert einen Mandanten, um die angegebenen Ereignisse zu empfangen.
Ressourcen-URL
https://api.partnercenter.microsoft.com/webhooks/v1/registration
Anforderungsbeispiel
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"]
}
Beispielantwort
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" ]
}
Anzeigen einer Registrierung
Gibt die Webhooks-Ereignisregistrierung für einen Mandanten zurück.
Ressourcen-URL
https://api.partnercenter.microsoft.com/webhooks/v1/registration
Anforderungsbeispiel
GET /webhooks/v1/registration
Content-Type: application/json
Authorization: Bearer ...
Accept: */*
Host: api.partnercenter.microsoft.com
Accept-Encoding: gzip, deflate
Beispielantwort
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"]
}
Aktualisieren einer Ereignisregistrierung
Aktualisiert eine vorhandene Ereignisregistrierung.
Ressourcen-URL
https://api.partnercenter.microsoft.com/webhooks/v1/registration
Anforderungsbeispiel
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"]
}
Beispielantwort
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" ]
}
Senden eines Testereignisses zum Überprüfen Ihrer Registrierung
Generiert ein Testereignis, um die Webhooks-Registrierung zu überprüfen. Dieser Test soll überprüfen, ob Sie Ereignisse vom Partner Center empfangen können. Daten für diese Ereignisse werden sieben Tage nach der Erstellung des ersten Ereignisses gelöscht. Sie müssen für das Ereignis "test-created" mit der Registrierungs-API registriert sein, bevor Sie ein Überprüfungsereignis senden.
Hinweis
Beim Bereitstellen eines Überprüfungsereignisses gibt es eine Drosselungsgrenze von 2 Anforderungen pro Minute.
Ressourcen-URL
https://api.partnercenter.microsoft.com/webhooks/v1/registration/validationEvents
Anforderungsbeispiel
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:
Beispielantwort
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" }
Überprüfen, ob das Ereignis übermittelt wurde
Gibt den aktuellen Status des Überprüfungsereignisses zurück. Diese Überprüfung kann hilfreich sein, um Probleme bei der Ereignisübermittlung zu beheben. Die Antwort enthält ein Ergebnis für jeden Versuch, das Ereignis zu übermitteln.
Ressourcen-URL
https://api.partnercenter.microsoft.com/webhooks/v1/registration/validationEvents/{correlationId}
Anforderungsbeispiel
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
Beispielantwort
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"
}]
}
Beispiel für signaturüberprüfung
Beispielsignatur des Rückrufcontrollers (ASP.NET)
[AuthorizeSignature]
[Route("webhooks/callback")]
public IHttpActionResult Post(PartnerResourceChangeCallBack callback)
Signaturüberprüfung
Das folgende Beispiel zeigt, wie Sie dem Controller, der Rückrufe von Webhook-Ereignissen empfängt, ein Autorisierungsattribut hinzufügen.
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}."));
}
}
}
}