Partnerközpont – webhookok
A következőre vonatkozik: Partnerközpont | A 21Vianet által üzemeltetett Partnerközpont | Partnerközpont a Microsoft Cloud for US Government számára
Megfelelő szerepkörök: globális rendszergazda | Számlázási rendszergazda | Rendszergazdai ügynök | Értékesítési ügynök | Segélyszolgálati ügynök
A Partnerközpont Webhook API-kkal a partnerek regisztrálhatnak az erőforrás-változási eseményekre. Ezek az események HTTP POST-k formájában érkeznek a partner regisztrált URL-címére. Ha eseményt szeretne fogadni a Partnerközpontból, a partnerek visszahívást üzemeltetnek, ahol a Partnerközpont közzéteheti az erőforrás-módosítási eseményt. Az esemény digitálisan alá van írva, így a partner ellenőrizheti, hogy a Partnerközpontból küldte-e. A webhookértesítések csak azokra a környezetekre aktiválódnak, amelyek a co-sell legújabb konfigurációját használják.
A Partnerközpont a következő webhookeseményeket támogatja.
Észlelt Azure-csalási esemény ("azure-fraud-event-detected")
Ez az esemény az Azure-ból való csalás észlelésekor jön elő.
Delegált rendszergazdai kapcsolat által jóváhagyott esemény ("dap-admin-relationship-approved")
Ez az esemény akkor jön létre, ha a delegált rendszergazdai jogosultságokat az ügyfél bérlője hagyja jóvá.
Az ügyfélesemény által elfogadott viszonteladói kapcsolat ("reseller-relationship-accepted-by-customer")
Ez az esemény akkor jön elő, amikor az ügyfélbérlemény jóváhagyja a viszonteladói kapcsolatot.
Ügyfélesemény által elfogadott közvetett viszonteladói kapcsolat ("indirect-reseller-relationship-accepted-by-customer")
Ez az esemény akkor jön elő, amikor az ügyfélbérlemény jóváhagyja a közvetett viszonteladói kapcsolatot.
Delegált rendszergazdai kapcsolat megszakított eseménye ("dap-admin-relationship-terminated")
Ez az esemény akkor jön elő, amikor az ügyfél megszünteti a delegált rendszergazdai jogosultságokat.
A Microsoft-esemény által megszakított Dap-rendszergazdai kapcsolat ("dap-admin-relationship-terminated-by-microsoft")
Ez az esemény akkor jön létre, amikor a Microsoft leállítja a DAP-t a partner és az ügyfél bérlője között, ha a DAP több mint 90 napig inaktív.
Részletes rendszergazdai hozzáférés-hozzárendelés aktivált eseménye ("granular-admin-access-assignment-activated")
Ez az esemény akkor jön létre, amikor a partner aktiválja a részletes delegált rendszergazdai jogosultságok hozzáférési hozzárendelését, miután a Microsoft Entra-szerepkörök adott biztonsági csoportokhoz lettek rendelve.
Részletes rendszergazdai hozzáférés-hozzárendelés létrehozott eseménye ("granular-admin-access-assignment-created")
Ez az esemény akkor jön létre, amikor a partner létrehozza a részletes delegált rendszergazdai jogosultságok hozzáférési hozzárendelését. A partnerek adott biztonsági csoportokhoz rendelhetnek ügyfél által jóváhagyott Microsoft Entra-szerepköröket.
Részletes rendszergazdai hozzáférés-hozzárendelés törölt eseménye ("granular-admin-access-assignment-deleted")
Ez az esemény akkor jön elő, amikor a partner törli a részletes delegált rendszergazdai jogosultságok hozzáférési hozzárendelését.
Részletes rendszergazdai hozzáférés-hozzárendelés frissített eseménye ("granular-admin-access-assignment-updated")
Ez az esemény akkor jön elő, amikor a partner frissíti a részletes delegált rendszergazdai jogosultságok hozzáférési hozzárendelését.
Részletes rendszergazdai kapcsolat aktivált eseménye ("granular-admin-relationship-activated")
Ez az esemény akkor jön létre, amikor a részletes delegált rendszergazdai jogosultságok létrejönnek, és aktívak az ügyfél jóváhagyásához.
Részletes rendszergazdai kapcsolat által jóváhagyott esemény ("granular-admin-relationship-approved")
Ez az esemény akkor jön elő, ha az ügyfélbérlemény jóváhagyja a részletes delegált rendszergazdai jogosultságokat.
Részletes rendszergazdai kapcsolat lejárt eseménye ("granular-admin-relationship-expired")
Ez az esemény akkor jön elő, ha a részletes delegált rendszergazdai jogosultságok lejártak.
Részletes rendszergazdai kapcsolat által létrehozott esemény ("granular-admin-relationship-created")
Ez az esemény a részletes delegált rendszergazdai jogosultságok létrehozásakor jön létre.
Részletes rendszergazdai kapcsolat frissített eseménye ("részletes-admin-relationship-updated")
Ez az esemény akkor jelentkezik, ha az ügyfél vagy a partner frissíti a részletes delegált rendszergazdai jogosultságokat.
Részletes rendszergazdai kapcsolat automatikus kiterjesztett eseménye ("granular-admin-relationship-auto-extended")
Ez az esemény akkor jön elő, amikor a rendszer automatikusan kibővíti a részletes delegált rendszergazdai jogosultságokat.
Részletes rendszergazdai kapcsolat megszakadt eseménye ("részletes-admin-relationship-terminated")
Ez az esemény akkor jön elő, ha a partner vagy az ügyfél bérlője leállítja a részletes delegált rendszergazdai jogosultságokat.
Számlára kész esemény ("számla-kész")
Ez az esemény akkor jön létre, amikor az új számla készen áll.
Új kereskedelmi migrálás befejezve ("new-commerce-migration-completed")
Ez az esemény akkor jön létre, amikor az új kereskedelmi migrálás befejeződött.
Új kereskedelmi migrálás létrehozva ("new-commerce-migration-created")
Ez az esemény az új kereskedelmi migrálás létrehozásakor jön létre.
Az új kereskedelmi migrálás sikertelen ("new-commerce-migration-failed")
Ez az esemény akkor jön létre, ha az új kereskedelmi migrálás sikertelen.
Átvitel létrehozása ("create-transfer")
Ez az esemény az átvitel létrehozásakor jön létre.
Frissítésátvitel ("update-transfer")
Ez az esemény az átvitel frissítésekor jelenik meg.
Teljes átvitel ("teljes átvitel")
Ez az esemény az átvitel befejezésekor jön elő.
Feladatátvitel ("feladatátvitel")
Ez az esemény akkor jön elő, ha az átvitel meghiúsul.
Az új kereskedelmi áttelepítési ütemezés meghiúsult ("new-commerce-migration-schedule-failed")
Ez az esemény akkor jön létre, ha az új kereskedelmi migrálási ütemezés meghiúsul.
Javaslat által létrehozott esemény ("javaslat létrehozva")
Ez az esemény az átirányítás létrehozásakor jön létre.
Javaslat frissítve esemény ("javaslat frissítve")
Ez az esemény a javaslat frissítésekor jön létre.
Kapcsolódó javaslattal létrehozott esemény ("related-referral-created")
Ez az esemény a kapcsolódó javaslat létrehozásakor jön létre.
Kapcsolódó javaslat frissített eseménye ("related-referral-updated")
Ez az esemény a kapcsolódó javaslat frissítésekor jön létre.
Előfizetés aktív eseménye ("subscription-active")
Ez az esemény az előfizetés aktiválásakor jön elő.
Feljegyzés
Az előfizetés aktív webhookja és a hozzá tartozó tevékenységnapló-esemény jelenleg csak tesztkörnyezet-bérlők számára érhető el.
Előfizetés függőben lévő esemény ("előfizetés függőben")
Ez az esemény akkor jön létre, amikor a megfelelő rendelés sikeresen megérkezett, és az előfizetés létrehozása függőben van.
Feljegyzés
Az előfizetés függőben lévő webhook és a kapcsolódó tevékenységnapló-esemény jelenleg csak a tesztkörnyezet-bérlők számára érhető el.
Előfizetés megújított eseménye ("előfizetés megújítva")
Ez az esemény akkor jön elő, amikor az előfizetés befejezi a megújítást.
Feljegyzés
Az előfizetés megújított webhookja és a hozzá tartozó tevékenységnapló-esemény jelenleg csak tesztkörnyezet-bérlők számára érhető el.
Előfizetés frissítve esemény ("előfizetés frissítve")
Ez az esemény akkor jön elő, amikor az előfizetés megváltozik. Ezek az események akkor jönnek létre, ha belső változás történik a Partnerközpont API-val végzett módosítások mellett.
Feljegyzés
Az előfizetés módosítása és az Előfizetés frissítése esemény aktiválása között akár 48 óra is lehet.
Tesztesemény ("teszt-létrehozva")
Ez az esemény lehetővé teszi a regisztráció önálló előkészítését és tesztelését egy tesztesemény kérésével, majd a folyamat nyomon követésével. Az esemény kézbesítése közben a Microsofttól kapott hibaüzenetek láthatók. Ez a korlátozás csak a "teszt által létrehozott" eseményekre vonatkozik. A hét napnál régebbi adatok törlődnek.
Küszöbérték túllépte az eseményt ("usagerecords-thresholdExceeded")
Ez az esemény akkor keletkezik, ha a Microsoft Azure-használat mennyisége bármely ügyfél esetében meghaladja a használati költségkeretet (a küszöbértéket). További információ: (Azure-költségkeret beállítása az ügyfelek számára/partnerközpont/set-an-azure-spending-budget-for-your-customers).
A jövőbeni webhookesemények olyan erőforrásokhoz lesznek hozzáadva, amelyek a partner által nem irányítható rendszerben változnak, és további frissítéseket végeznek, hogy az események a lehető legközelebb legyenek a "valós idejű" állapothoz. A partnerek visszajelzése arról, hogy mely események adnak értéket a vállalkozásuknak, hasznos annak meghatározásához, hogy milyen új eseményeket kell hozzáadniuk.
A Partnerközpont által támogatott webhookesemények teljes listájáért tekintse meg a Partnerközpont webhookeseményeit.
Előfeltételek
- Hitelesítő adatok a Partnerközpont hitelesítésében leírtak szerint. Ez a forgatókönyv támogatja az önálló alkalmazással és az App+Felhasználói hitelesítő adatokkal való hitelesítést is.
Események fogadása a Partnerközpontból
Ahhoz, hogy eseményeket fogadjon a Partnerközpontból, közzé kell tennie egy nyilvánosan elérhető végpontot. Mivel ez a végpont elérhető, ellenőriznie kell, hogy a kommunikáció a Partnerközpontból származik-e. Minden fogadott webhookesemény digitálisan alá van írva egy, a Microsoft Roothoz láncolást használó tanúsítvánnyal. Az esemény aláírásához használt tanúsítványra mutató hivatkozást is meg kell adni. Ez lehetővé teszi a tanúsítvány megújítását anélkül, hogy újra kellene üzembe helyeznie vagy újrakonfigurálnia a szolgáltatást. A Partnerközpont 10 kísérletet tesz az esemény kézbesítésére. Ha az esemény még mindig nem érkezik meg 10 kísérlet után, akkor az offline üzenetsorba kerül, és a kézbesítéskor nem történik további kísérlet.
Az alábbi minta egy partnerközpontból közzétett eseményt mutat be.
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"
}
Feljegyzés
Az Engedélyezési fejléc "Signature" (Aláírás) sémával rendelkezik. Ez a tartalom base64 kódolású aláírása.
A visszahívás hitelesítése
A Partnerközponttól kapott visszahívási esemény hitelesítéséhez kövesse az alábbi lépéseket:
- Ellenőrizze, hogy vannak-e a szükséges fejlécek (engedélyezés, x-ms-certificate-url, x-ms-signature-algoritmus).
- Töltse le a tartalom aláírásához használt tanúsítványt (x-ms-certificate-url).
- Ellenőrizze a tanúsítványláncot.
- Ellenőrizze a tanúsítvány "szervezetét".
- Olvassa el a tartalmat UTF8 kódolással egy pufferbe.
- Hozzon létre egy RSA titkosítási szolgáltatót.
- Ellenőrizze, hogy az adatok megegyeznek-e a megadott kivonatoló algoritmussal (például SHA256) aláírt adatokkal.
- Ha az ellenőrzés sikeres, dolgozza fel az üzenetet.
Feljegyzés
Alapértelmezés szerint az aláírási jogkivonat egy engedélyezési fejlécben lesz elküldve. Ha a SignatureTokenToMsSignatureHeadert igaz értékre állítja be a regisztrációban, az aláírási jogkivonatot a rendszer az x-ms-signature fejlécben küldi el.
Eseménymodell
Az alábbi táblázat egy Partnerközpont-esemény tulajdonságait ismerteti.
Tulajdonságok
Név | Leírás |
---|---|
EventName | Az esemény neve. A(z) {resource}-{action} formában. Például: "test-created". |
ResourceUri | A módosított erőforrás URI-ja. |
ResourceName | A módosított erőforrás neve. |
AuditUrl | Opcionális. A naplózási rekord URI-ja. |
ResourceChangeUtcDate | Az erőforrás változásának dátuma és időpontja UTC formátumban. |
Minta
Az alábbi minta egy Partnerközpont-esemény struktúráját mutatja be.
{
"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 API-k
Hitelesítés
A Webhook API-khoz intézett összes hívás hitelesítése az Engedélyezési fejléc Tulajdonos jogkivonatával történik. Hozzáférési jogkivonat beszerzése a hozzáféréshez https://api.partnercenter.microsoft.com
. Ez a jogkivonat ugyanaz a jogkivonat, amely a Partnerközpont többi API-jának eléréséhez használatos.
Események listájának lekérése
A Webhook API-k által jelenleg támogatott események listáját adja vissza.
Erőforrás URL-címe
https://api.partnercenter.microsoft.com/webhooks/v1/registration/events
Példa kérése
GET /webhooks/v1/registration/events
content-type: application/json
authorization: Bearer eyJ0e.......
accept: */*
host: api.partnercenter.microsoft.com
Példa válaszra
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" ]
Regisztráció események fogadásához
Regisztrál egy bérlőt a megadott események fogadásához.
Erőforrás URL-címe
https://api.partnercenter.microsoft.com/webhooks/v1/registration
Példa kérése
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"]
}
Példa válaszra
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" ]
}
Regisztráció megtekintése
Egy bérlő Webhooks-eseményregisztrációját adja vissza.
Erőforrás URL-címe
https://api.partnercenter.microsoft.com/webhooks/v1/registration
Példa kérése
GET /webhooks/v1/registration
Content-Type: application/json
Authorization: Bearer ...
Accept: */*
Host: api.partnercenter.microsoft.com
Accept-Encoding: gzip, deflate
Példa válaszra
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"]
}
Eseményregisztráció frissítése
Frissíti a meglévő eseményregisztrációt.
Erőforrás URL-címe
https://api.partnercenter.microsoft.com/webhooks/v1/registration
Példa kérése
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"]
}
Példa válaszra
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" ]
}
Tesztesemény küldése a regisztráció ellenőrzéséhez
Létrehoz egy teszteseményt a Webhooks-regisztráció ellenőrzéséhez. Ez a teszt annak ellenőrzésére szolgál, hogy fogadhat-e eseményeket a Partnerközpontból. Az események adatai hét nappal a kezdeti esemény létrehozása után törlődnek. Az érvényesítési esemény elküldése előtt regisztrálnia kell magát a "teszt által létrehozott" eseményre a regisztrációs API használatával.
Feljegyzés
Az érvényesítési esemény közzétételekor percenként 2 kérelemre vonatkozó korlátozás van érvényben.
Erőforrás URL-címe
https://api.partnercenter.microsoft.com/webhooks/v1/registration/validationEvents
Példa kérése
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:
Példa válaszra
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" }
Az esemény kézbesítésének ellenőrzése
Az érvényesítési esemény aktuális állapotát adja vissza. Ez az ellenőrzés hasznos lehet az eseménykézbesítési problémák elhárításához. A válasz minden egyes, az esemény kézbesítésére tett kísérlet eredményét tartalmazza.
Erőforrás URL-címe
https://api.partnercenter.microsoft.com/webhooks/v1/registration/validationEvents/{correlationId}
Példa kérése
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
Példa válaszra
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"
}]
}
Példa aláírás-ellenőrzésre
Minta visszahívásvezérlő aláírása (ASP.NET)
[AuthorizeSignature]
[Route("webhooks/callback")]
public IHttpActionResult Post(PartnerResourceChangeCallBack callback)
Aláírás érvényesítése
Az alábbi példa bemutatja, hogyan adhat hozzá engedélyezési attribútumot ahhoz a vezérlőhöz, amely visszahívásokat fogad a Webhook-eseményekről.
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}."));
}
}
}
}