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: 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ási végpontot állítanak be, ahol a Partnerközpont elküldheti 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.
Csalási esemény észlelve az Azure-ban ("azure-fraud-event-detected")
Ez az esemény az Azure-ból való csalás észlelésekor jön elő.
Delegált rendszergazdai kapcsolat 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á.
Viszonteladói kapcsolat ügyfél általi elfogadása esemény ("reseller-relationship-accepted-by-customer")
Ez az esemény akkor következik be, amikor a bérlő ügyfél jóváhagyja a viszonteladói kapcsolatot.
Ügyfél által elfogadott közvetett viszonteladói kapcsolati esemény ("indirect-reseller-relationship-accepted-by-customer")
Ez az esemény akkor keletkezik, amikor az ügyfélszolgáltató jóváhagyja a közvetett viszonteladói kapcsolatot.
Delegált rendszergazdai kapcsolat megszűnt 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.
Granuláris rendszergazdai hozzáférés-hozzárendelés aktivált esemény ("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.
Esemény: Részletes rendszergazdai hozzáférés-hozzárendelés létrehozva ("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.
Finomhangolt rendszergazdai hozzáférés-hozzárendelés törlési eseménye ("granular-admin-access-assignment-deleted")
Ez az esemény akkor fordul elő, amikor a partner törli a granulált 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 fordul elő, amikor a partner frissíti a granuláris delegált adminisztrátori jogosultságok hozzáférés hozzárendelését.
Granuláris rendszergazdai kapcsolat aktiválási esemény ("granular-admin-relationship-activated")
Ez az esemény akkor következik be, amikor a Részletes Delegált Rendszergazdai Jogosultságok létrejönnek és aktívvá válnak az ügyfél általi jóváhagyásra.
Granulált rendszergazdai kapcsolat jóváhagyott esemény ("granular-admin-relationship-approved")
Ez az esemény akkor kerül sor, amikor az ügyfélbérlő jóváhagyja a részletes delegált rendszergazdai jogosultságokat.
Granuláris admin kapcsolati lejárati esemény ("granular-admin-relationship-expired")
Ez az esemény akkor jön elő, ha a részletes delegált rendszergazdai jogosultságok lejártak.
Granuláris rendszergazdai kapcsolat által létrehozott esemény ("granular-admin-relationship-created")
Ez az esemény akkor jelenik meg, amikor a részletes delegált rendszergazdai jogosultságok létrehozásra kerülnek.
Granuláris adminisztrátori kapcsolat frissítési eseménye ("granular-admin-relationship-updated")
Ez az esemény akkor indul el, ha az ügyfél vagy a partner frissíti a részletesen meghatározott delegált rendszergazdai jogosultságokat.
Granuláris adminisztrátori 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.
Granuláris rendszergazda kapcsolat megszakítás eseménye ("granular-admin-relationship-terminated")
Ez az esemény akkor következik be, ha a partner vagy az ügyfél bérlője megszünteti a részletes delegált adminisztrátori jogosultságokat.
Új kereskedelmi migrálás befejezve ("new-commerce-migration-completed")
Ez az esemény akkor generálódik, amikor az új kereskedelmi migráció befejeződött.
Új kereskedelmi migráció létrehozva ("new-commerce-migration-created")
Ez az esemény az új kereskedelmi migráció létrehozásakor jön létre.
Az új kereskedelmi migrálás sikertelen ("new-commerce-migration-failed")
Ez az esemény akkor keletkezik, ha az új kereskedelmi migráció sikertelen.
Átvitel létrehozása ("create-transfer")
Ez az esemény akkor keletkezik, amikor az átvitel létrejön.
Frissítésátvitel ("update-transfer")
Ez az esemény az átvitel frissítésekor jön létre.
Teljes átvitel ("teljes átvitel")
Ez az esemény az átvitel befejezésekor jön elő.
Lejáró átruházás ("expire-transfer")
Ez az esemény akkor jön elő, amikor az átvitel lejárt.
Sikertelen átvitel ("sikertelen á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 akkor keletkezik, amikor a hivatkozás frissül.
Kapcsolódó javaslattal létrehozott esemény ("related-referral-created")
Ez az esemény akkor aktiválódik, amikor a kapcsolódó ajánlás létrejön.
Kapcsolódó ajánlás frissítésesemény ("related-referral-updated")
Ez az esemény akkor keletkezik, amikor a kapcsolódó ajánlás frissül.
Előfizetés aktív eseménye ("subscription-active")
Ez az esemény az előfizetés aktiválásakor jön elő.
Megjegyzés
Az előfizetés aktív webhookja és a hozzá tartozó tevékenységnapló-esemény jelenleg csak Sandbox 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.
Megjegyzés
Az "Subscription Pending" webhook és a kapcsolódó tevékenységnapló-esemény jelenleg csak sandbox bérlők számára érhető el.
Előfizetés megújítása esemény ("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.
Megjegyzés
Az előfizetés megújításáról szóló webhook és a hozzá tartozó tevékenységnapló-esemény jelenleg csak Sandbox bérlők számára érhető el.
Előfizetés frissítés esemény ("előfizetés frissítés")
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.
Megjegyzé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 ("test-created")
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épés eseménye ("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 webhook esemény digitálisan alá van írva egy tanúsítvánnyal, amely a Microsoft Roothoz kapcsolódik. 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"
}
Megjegyzé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.
Megjegyzé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. Az auditálási rekord URI-ja. |
ErőforrásVáltozásUtcDátum | 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 a Hitelesítési fejlécben levő Bearer tokennel történik. Hozzáférési jogkivonat beszerzése a hozzáféréshez https://api.partnercenter.microsoft.com
. A jogkivonat megegyezik azzal, amelyet a Partnerközpont többi API-jának eléréséhez használnak.
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
Visszaküldi a Webhooks esemény regisztrációját egy bérlő számára.
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.
Megjegyzé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 StreamReaders;
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}."));
}
}
}
}