Files
Facilitor/UTILS/Exchange/process_webhook.js
2025-04-04 08:57:44 +00:00

172 lines
7.6 KiB
JavaScript

/* Globals */
var DEZE; // context voor alle globale functies vanuit ASP
var __Log;
var __DoLog;
var _AiAi;
var safe;
var Oracle;
var custabspath;
var config;
var token;
function initialize(params)
{
DEZE = params.DEZE;
__Log = DEZE.__Log;
__DoLog = DEZE.__DoLog;
_AiAi = DEZE._AiAi;
safe = DEZE.safe;
customerId = DEZE.customerId;
custabspath = params.custabspath;
Oracle = DEZE.Oracle;
}
// notification zoals de webhook hem van Graph binnenkreeg
function process_webhook(res_ruimte_key, zaalemail, notification)
{
__Log("Now in process_webhook.wsc for " + zaalemail);
/* global */ config = loadconfig(custabspath + "\\Exchange\\exchange.config");
/* global */ token = requestToken(config);
if ("encryptedContent" in notification) {
var success = process_encrypted_notification(res_ruimte_key, zaalemail, notification);
if (success === null) {
// Either;
// Signature invalid (= malafide request)
// Empty notification
// Event not yet ready for processing
return false;
} else if (success) {
return true; // Gelukt; klaar
} /* else if (success === false) {
Niet gelukt; ga hieronder door in de unencrypted flow die zelf alle data ophaalt
} */
}
// Onderstaande route is voor Outlook series, daarvan krijgen we niet direct alle benodigde informatie door
// Tevens voor backwards compatibility, vlak na de upgrade naar 2024.1 zijn de subscriptions nog niet vernieuwd en ontvangen we nog normale notifications
/* Zijn alle event-id's al in Facilitor bekend? Zo niet, vul die dan eerst aan */
if (getMSGraphSyncLevel() & 4) // Anders kunnen we het event bij de organizer toch niet opvragen
{ /*
Als wij vanuit Facilitor een reservering aanmaken doen wij dat op naam van de organisator (res_rsv_ruimte_externnr2)
Dan weten we het id van het event van de ruimte (res_rsv_ruimte_externnr) nog niet.
*/
var sql = "SELECT 'dummy'"
+ " FROM res_rsv_ruimte"
+ " WHERE res_rsv_ruimte_externnr IS NOT NULL"
+ " AND INSTR(res_rsv_ruimte_externnr, " + safe.quoted_sql(notification.resourceData.id) + ") = 1" // =1 want ook bij occurrences is dit het id van de seriesMaster
+ " AND res_rsv_ruimte_verwijder IS NULL";
var oRs = Oracle.Execute(sql);
if (oRs.EoF) { // We moeten het res_rsv_ruimte_externnr (ruimte-event-id) nog invullen, is alleen leeg reserveringen die in Facilitor zijn aangemaakt
__Log("Reservering niet teruggevonden in Facilitor op basis van externnr, eerst event-gegevens opvragen bij Outlook."
+ "\nRuimte-resource id: " + notification.resourceData.id);
if (updateExternnr(zaalemail, notification.resourceData.id) == -1) {
return null; // Errorcode -1; exit
}
}
oRs.Close();
}
return doImport(res_ruimte_key, zaalemail, "EXCHANGE");
}
function process_encrypted_notification(res_ruimte_key, zaalemail, notification)
{
var success = 0;
var base64encodedPayload = notification.encryptedContent.data;
var base64encodedKey = notification.encryptedContent.dataKey;
var base64encodedSignature = notification.encryptedContent.dataSignature;
var encryptionCertificateId = notification.encryptedContent.encryptionCertificateId;
if (!unlockChilkat()) {
__DoLog("Error Chilkat", "#FF0000");
return false;
}
// 1. Use the encryptionCertificateId property to identify the certificate to use.
if (encryptionCertificateId !== CERT_ID_PREFIX + customerId.toUpperCase()) {
__DoLog("Error invalid certificate ID; " + encryptionCertificateId + " (should be: " + CERT_ID_PREFIX + customerId.toUpperCase() + ")", "#FF0000");
return false;
} else {
var pfx = new ActiveXObject("Chilkat_9_5_0.Pfx");
var success = pfx.LoadPfxFile(custabspath + "\\Exchange\\" + config.certificate, config.certificate_password);
if (success != 1) {
__DoLog("LoadPfxFile geen succes", "#FF0000");
__DoLog(pfx.LastErrorText);
return false;
}
}
// 2. Initialize an RSA cryptographic component (such as the .NET RSACryptoServiceProvider) with the private key.
var rsa = new ActiveXObject("Chilkat_9_5_0.Rsa");
rsa.OaepPadding = 1;
rsa.EncodingMode = "base64";
rsa.Charset = "utf-8";
success = rsa.ImportPrivateKeyObj(pfx.GetPrivateKey(0));
if (success != 1) {
__DoLog("Importing private key failed", "#FF0000");
__DoLog(rsa.LastErrorText);
return false;
}
// 3. Decrypt the symmetric key delivered in the dataKey property of each item in the change notification.
try {
var decryptedKeyBytes = rsa.DecryptBytesENC(base64encodedKey, 1);
} catch (e) {
__DoLog(e.message);
return false;
}
// 4. Use the symmetric key to calculate the HMAC-SHA256 signature of the value in data (=base64encodedPayload).
var crypt = new ActiveXObject("Chilkat_9_5_0.Crypt2");
crypt.MacAlgorithm = "hmac";
crypt.HashAlgorithm = "SHA-256";
crypt.SetMacKeyBytes(decryptedKeyBytes);
var decodedPayloadBytes = crypt.Decode(base64encodedPayload, "base64");
var base64encodedHMAC = crypt.MacBytesENC(decodedPayloadBytes);
if (base64encodedHMAC !== base64encodedSignature) {
__DoLog("Unable to verify signature", "#FF0000");
__DoLog(notification);
return null;
}
// 5. Use the symmetric key with an Advanced Encryption Standard (AES) (such as the .NET AesCryptoServiceProvider) to decrypt the content in data.
crypt.EncodingMode = "base64";
// crypt.SetEncodedKey(base64EncodedDecryptedSymetricKey, "base64");
crypt.SecretKey = decryptedKeyBytes;
// Set the "initialization vector" by copying the first 16 bytes of the symmetric key used for decryption.
// We hebben de eerste 16 bytes (1 byte = 8 bits) nodig, hex is base16, elk karakter is dus 4 bits (2 karakters = 8 bits = 1 byte), dus pak de eerste 32 hex-karakters
crypt.IV = crypt.Decode(crypt.Encode(decryptedKeyBytes, "hex").slice(0, 32), "hex");
// 6. The decrypted value is a JSON string that represents the resource instance in the change notification.
try {
var decryptedResourceDataStr = crypt.BytesToString(crypt.DecryptBytes(crypt.Decode(base64encodedPayload, "base64")), "utf-8");
if (decryptedResourceDataStr === "{}") { // Leeg = deleted (en deleted != isCancelled, deleten gebeurd in de agenda van de deelnemende ruimte, isCancelled gebeurd door de organisator)
var isDeleted = true;
}
var decryptedResourceData = JSON.parse(decryptedResourceDataStr);
if (isDeleted) {
decryptedResourceData["@removed"] = {
"reason": "deleted"
}
}
decryptedResourceData.id = notification.resourceData.id; // Die staat niet in de rich notification, maar bij de resourceData
} catch (e) {
__DoLog(e.message);
return false;
}
if (decryptedResourceData.type === "singleInstance" || decryptedResourceData["@removed"]) { // Voor series(seriesMaster/occurrence/exception) moeten we toch meer gegevens ophalen, dus via de reguliere flow
if (!eventReadyForProcessing(zaalemail, decryptedResourceData)) {
return null;
}
return doImportSingle(res_ruimte_key, zaalemail, decryptedResourceData);
}
}