1175 lines
51 KiB
JavaScript
1175 lines
51 KiB
JavaScript
/*
|
|
$Revision$
|
|
$Id$
|
|
|
|
File: ms_Graph.js
|
|
|
|
Description: MS Graph koppeling hulpfuncties
|
|
*/
|
|
|
|
/* GLOBALS */
|
|
var fso = new ActiveXObject("Scripting.FileSystemObject");
|
|
var oCrypto = new ActiveXObject("SLNKDWF.Crypto");
|
|
var S_msgraph_sync_level;
|
|
var FCLT_KEY_PROP_ID_NAME = "String {00020329-0000-0000-c000-000000000046} Name fclt.res_rsv_ruimte.key";
|
|
var REANIMATE_THRESHOLD_MS = 30000; // Niet reanimeren als langer dan 30s geleden verwijderd
|
|
|
|
/*////////////////////////////////
|
|
// //
|
|
// Bearer token acquisition //
|
|
// //
|
|
////////////////////////////////*/
|
|
function requestToken(config)
|
|
{
|
|
setMSGraphSyncLevel();
|
|
return requestTokenCC(config);
|
|
}
|
|
|
|
// Request bearer token using [C]lient [C]redentials
|
|
function requestTokenCC(config)
|
|
{
|
|
if (config.certificate)
|
|
return requestTokenCertificate(config);
|
|
else
|
|
{
|
|
var data = 'grant_type=client_credentials'
|
|
+ '&client_secret=' + config.client_secret
|
|
+ '&client_id=' + config.client_id
|
|
+ '&scope=' + 'https://graph.microsoft.com/.default';
|
|
var request = {
|
|
"method": "POST",
|
|
"url": "https://login.microsoftonline.com/" + config.tenant + "/oauth2/v2.0/token",
|
|
"body": data,
|
|
"headers": { "Content-Type": "application/x-www-form-urlencoded" }
|
|
}
|
|
var xhr = doHTTP(request);
|
|
return xhr ? xhr.access_token : null;
|
|
}
|
|
}
|
|
|
|
// Request bearer token using a Certificate
|
|
function requestTokenCertificate(config)
|
|
{
|
|
if (!unlockChilkat()) {
|
|
return false;
|
|
}
|
|
var pfx = new ActiveXObject("Chilkat_9_5_0.Pfx");
|
|
var success = pfx.LoadPfxFile(custabspath + "\\Exchange\\" + config.certificate, config.certificate_password);
|
|
if (success != 1) {
|
|
__DoLog("LoadEncryptedPemFile geen succes", "#FF0000");
|
|
__DoLog(pfx.LastErrorText);
|
|
return null;
|
|
}
|
|
var privKey = pfx.GetPrivateKey(0);
|
|
|
|
var jwt = new ActiveXObject("Chilkat_9_5_0.Jwt");
|
|
var jose = new ActiveXObject("Chilkat_9_5_0.JsonObject");
|
|
var thumbprint = "thumbprint" in config ? config.thumbprint : config.client_assertion.header.x5t;
|
|
jose.AppendString("alg", "RS256");
|
|
jose.AppendString("typ", "JWT");
|
|
|
|
// x5t to base64
|
|
jose.AppendString("x5t", oCrypto.hex2base64(thumbprint, false, true));
|
|
|
|
var claims = new ActiveXObject("Chilkat_9_5_0.JsonObject");
|
|
var jwtDateTime = new Date();
|
|
|
|
claims.AppendString("iss", config.client_id);
|
|
claims.AppendString("jti", getGUID()); // (Unique) JWT token identifier
|
|
claims.AppendString("sub", config.client_id);
|
|
claims.AppendString("aud", "https://login.microsoftonline.com/" + config.tenant + "/oauth2/v2.0/token");
|
|
|
|
claims.AddIntAt(-1, "iat", Math.floor(jwtDateTime.getTime()/1000)); // Issued at time
|
|
claims.AddIntAt(-1, "nbf", Math.floor(jwtDateTime.getTime()/1000)); // Not before
|
|
jwtDateTime.setMinutes(jwtDateTime.getMinutes() + 10);
|
|
claims.AddIntAt(-1, "exp", Math.floor(jwtDateTime.getTime()/1000)); // Expiration time
|
|
|
|
var parms = 'grant_type=client_credentials'
|
|
+ '&client_id=' + config.client_id
|
|
+ '&scope=' + 'https://graph.microsoft.com/.default'
|
|
+ '&client_assertion=' + jwt.CreateJwtPk(jose.Emit(), claims.Emit(), privKey)
|
|
+ '&client_assertion_type=' + 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer';
|
|
var request = {
|
|
"method": "POST",
|
|
"url": "https://login.microsoftonline.com/" + config.tenant + "/oauth2/v2.0/token",
|
|
"body": parms,
|
|
"headers": { "Content-Type": "application/x-www-form-urlencoded" }
|
|
}
|
|
var xhr = doHTTP(request);
|
|
return xhr ? xhr.access_token : null;
|
|
}
|
|
|
|
/*////////////////////////////////
|
|
// //
|
|
// Helper functions //
|
|
// //
|
|
////////////////////////////////*/
|
|
function loadconfig(configpath)
|
|
{
|
|
if (!fso.FileExists(configpath))
|
|
return null;
|
|
var f = fso.OpenTextFile(configpath, 1); // ForReading
|
|
var config = eval("(" + f.ReadAll() + ")");
|
|
config.loglevel = config.loglevel || 0;
|
|
f.Close();
|
|
return config;
|
|
}
|
|
|
|
function setMSGraphSyncLevel()
|
|
{
|
|
if (S_msgraph_sync_level !== null)
|
|
{
|
|
var sql = "SELECT COALESCE(fac_setting_pvalue, fac_setting_default) fac_setting_value"
|
|
+ " FROM fac_setting"
|
|
+ " WHERE fac_setting_name = 'msgraph_sync_level'";
|
|
var oRs = Oracle.Execute(sql);
|
|
/* global */ S_msgraph_sync_level = oRs("fac_setting_value").Value;
|
|
oRs.Close();
|
|
}
|
|
}
|
|
|
|
function getMSGraphSyncLevel()
|
|
{
|
|
if (S_msgraph_sync_level === null)
|
|
setMSGraphSyncLevel();
|
|
return S_msgraph_sync_level;
|
|
}
|
|
|
|
/* Retuns the dates in between Facilitor synchronises appointments for this room */
|
|
function getCalendarViewDates(res_ruimte_extern_id) {
|
|
var dateFrom = new Date();
|
|
dateFrom.setDate(dateFrom.getDate() - config.fullpast);
|
|
dateFrom.setHours(0, 0, 0, 0);
|
|
|
|
var dateTo = new Date();
|
|
dateTo.setDate(dateTo.getDate() + config.fullfuture);
|
|
dateTo.setHours(23, 59, 59, 999);
|
|
|
|
var startdatum = null;
|
|
var vervaldatum = null;
|
|
|
|
var sql = "SELECT res_ruimte_startdatum, res_ruimte_vervaldatum"
|
|
+ " FROM res_ruimte"
|
|
+ " WHERE res_ruimte_extern_id = " + safe.quoted_sql(res_ruimte_extern_id)
|
|
+ " AND res_ruimte_verwijder IS NULL";
|
|
var oRs = Oracle.Execute(sql);
|
|
if (!oRs.EoF) {
|
|
startdatum = oRs("res_ruimte_startdatum").Value;
|
|
vervaldatum = oRs("res_ruimte_vervaldatum").Value;
|
|
}
|
|
oRs.Close();
|
|
|
|
if (startdatum !== null) {
|
|
startdatum = new Date(startdatum);
|
|
}
|
|
if (vervaldatum !== null) {
|
|
vervaldatum = new Date(vervaldatum);
|
|
}
|
|
if (startdatum !== null && startdatum.getTime() >= dateTo.getTime() || // Ruimte begint pas na de einddatum van de calenderView
|
|
vervaldatum !== null && vervaldatum.getTime() <= dateFrom.getTime()) { // Ruimte is vervallen voor de begindatum van de calendarView
|
|
return false; // Nothing to do
|
|
}
|
|
if (startdatum !== null && startdatum.getTime() > dateFrom.getTime()) {
|
|
dateFrom = startdatum;
|
|
}
|
|
|
|
dateTo.setHours(0, 0, 0, 0); // Vergelijk op dag
|
|
if (vervaldatum !== null && vervaldatum.getTime() <= dateTo.getTime()) {
|
|
dateTo = new Date(vervaldatum.setDate(vervaldatum.getDate() - 1));
|
|
}
|
|
dateTo.setHours(23, 59, 59, 999);
|
|
|
|
if (dateTo.getTime() <= dateFrom.getTime()) {
|
|
return false; // Nothing to do
|
|
}
|
|
|
|
return { "from": dateFrom, "to": dateTo };
|
|
}
|
|
|
|
/* Alleen als ze bij locations staan en geaccepteerd hebben */
|
|
function getLocationsFromEvent(event) {
|
|
var results = [];
|
|
if (event && ("locations" in event) && ("attendees" in event)) {
|
|
for (var l in event.locations) {
|
|
// Vind de locaties bij de attendees en kijk of ze geaccepteerd hebben
|
|
for (var a in event.attendees) {
|
|
if (event.locations[l].uniqueId === event.attendees[a].emailAddress.address &&
|
|
event.attendees[a].status.response === "accepted" &&
|
|
!inArray(event.attendees[a].emailAddress.address.toUpperCase(), results))
|
|
{
|
|
results.push(event.attendees[a].emailAddress.address.toUpperCase());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return results;
|
|
}
|
|
|
|
/* Returns the event data */
|
|
function getCalendarEventByID(userPrincipalName, id)
|
|
{
|
|
var request = {
|
|
"method": "GET",
|
|
"url": "https://graph.microsoft.com/v1.0/users/" + encodeURIComponent(userPrincipalName) + "/events/" + id
|
|
+ "?$expand=singleValueExtendedProperties($filter=id eq '" + FCLT_KEY_PROP_ID_NAME + "')",
|
|
"headers": { "Authorization": "Bearer " + token }
|
|
}
|
|
return doHTTP(request);
|
|
}
|
|
|
|
/* Returns the event data */
|
|
function getCalendarEventByICalUId(userPrincipalName, iCalUId, isOccurrence)
|
|
{ // occurrences (en exceptions) zijn alleen via de calenderView beschikbaar, seriesMaster alleen via events (singleInstance via beiden)
|
|
var endpoint = isOccurrence ? "calendarView" : "events";
|
|
var url = "https://graph.microsoft.com/v1.0/users/" + encodeURIComponent(userPrincipalName) + "/" + endpoint + "?$filter=iCalUId eq '" + iCalUId + "'"
|
|
+ "&$expand=singleValueExtendedProperties($filter=id eq '" + FCLT_KEY_PROP_ID_NAME + "')";
|
|
if (isOccurrence) { // Dit endpoint heeft verplichte datum-parameters
|
|
var calendarDates = getCalendarViewDates(userPrincipalName);
|
|
if (!calendarDates) {
|
|
return null;
|
|
}
|
|
url += "&startDateTime=" + calendarDates.from.toISOString() + "&endDateTime=" + calendarDates.to.toISOString();
|
|
}
|
|
var request = {
|
|
"method": "GET",
|
|
"url": url,
|
|
"headers": { "Authorization": "Bearer " + token }
|
|
}
|
|
var xhr = doHTTP(request);
|
|
return xhr ? xhr.value[0] : null;
|
|
}
|
|
|
|
/* Returns the event data */
|
|
function getCalendarEventByRsv(userPrincipalName, res_rsv_ruimte_key)
|
|
{
|
|
var request = {
|
|
"method": "GET",
|
|
"url": "https://graph.microsoft.com/v1.0/users/" + encodeURIComponent(userPrincipalName) + "/events?"
|
|
+ "$filter=singleValueExtendedProperties/Any(ep: ep/id eq '" + FCLT_KEY_PROP_ID_NAME + "' and ep/value eq '" + res_rsv_ruimte_key + "')",
|
|
"headers": { "Authorization": "Bearer " + token }
|
|
}
|
|
var xhr = doHTTP(request);
|
|
if (xhr && xhr.value && xhr.value.length === 1) { /* Eenduidig gevonden? */
|
|
return xhr.value[0];
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/* Returns all changed events within the configurated timeframe */
|
|
function getCalendarItems(userPrincipalName, skiptoken, deltatoken)
|
|
{
|
|
var parms;
|
|
if (skiptoken) { // Set parameters to read the next batch of events
|
|
parms = "$skiptoken=" + skiptoken;
|
|
} else if (deltatoken) { // Set parameters to read first batch of changed events
|
|
parms = "$deltatoken=" + deltatoken;
|
|
} else { // Set timeframe parameters to read all changed events
|
|
var calendarDates = getCalendarViewDates(userPrincipalName);
|
|
if (!calendarDates) {
|
|
return false;
|
|
}
|
|
parms = 'startDateTime=' + calendarDates.from.toISOString() + '&endDateTime=' + calendarDates.to.toISOString();
|
|
__Log("Full syncing from " + calendarDates.from.toISOString() + " to " + calendarDates.to.toISOString() + " for user " + userPrincipalName
|
|
+ " (" + Math.ceil((calendarDates.to.getTime() - calendarDates.from.getTime() - (1000 * 3600)) / (1000 * 3600 * 24)) + " days)"); // 1u extra aftrekken om de wintertijd niet als dag te laten tellen
|
|
}
|
|
|
|
var request = {
|
|
"method": "GET",
|
|
"url": "https://graph.microsoft.com/v1.0/users/" + encodeURIComponent(userPrincipalName) + "/calendarView/delta?" + parms,
|
|
"headers": { "Accept": "application/json", "Authorization": "Bearer " + token }
|
|
}
|
|
return doHTTP(request);
|
|
}
|
|
|
|
/* Returns all known subscriptions */
|
|
function getSubscriptions(skiptoken)
|
|
{
|
|
var parms = "";
|
|
if (typeof skiptoken !== "undefined") { // Set parameters to read the next batch of events
|
|
parms = "?$skiptoken=" + skiptoken;
|
|
}
|
|
var request = {
|
|
"method": "GET",
|
|
"url": "https://graph.microsoft.com/v1.0/subscriptions" + parms,
|
|
"headers": { "Accept": "application/json", "Authorization": "Bearer " + token }
|
|
}
|
|
return doHTTP(request);
|
|
}
|
|
|
|
/* Returns the notificationUrl used by MS Graph to send Facilitor notifications about changes in resources on which we are subscribed */
|
|
function getNotificationUrl(customerId)
|
|
{
|
|
if (!customerId) {
|
|
return false;
|
|
}
|
|
var hookurl;
|
|
if (config.hookurl) {
|
|
hookurl = config.hookurl;
|
|
} else { // Autodetect ngrok started with: ngrok http sggr.facws001.sg.nl:80
|
|
var fac_version = custabspath.split("\\")[custabspath.split("\\").length - 3]; // 'Branch#####' of 'Trunk', 'APPL' op productie, maar die komt hier niet
|
|
var request = {
|
|
"method": "GET",
|
|
"url": "http://127.0.0.1:4040/api/tunnels/command_line",
|
|
"headers": { "Accept": "application/json" },
|
|
"params": {
|
|
"callback": function (request, response) {
|
|
__Log("Ngrok detected: " + response.public_url);
|
|
__Log(" tip: view traffic at http://127.0.0.1:4040");
|
|
hookurl = response.public_url + "/" + fac_version + "/";
|
|
}
|
|
}
|
|
}
|
|
doHTTP(request);
|
|
}
|
|
if (!hookurl) {
|
|
_AiAi("INTERNAL_ERROR attempting to subscribe without a valid hookurl");
|
|
}
|
|
|
|
var apikey;
|
|
var apiname = "MSGRAPHNOTIFICATION";
|
|
var sql = "SELECT prs_perslid_apikey FROM prs_perslid WHERE prs_perslid_oslogin = '_MSGRAPHNOTIFICATION'";
|
|
var oRs = Oracle.Execute(sql);
|
|
if (!oRs.EoF) {
|
|
apikey = oRs("prs_perslid_apikey").Value;
|
|
} else {
|
|
__DoLog("Error: There exists no user with login '_MSGRAPHNOTIFICATION'", "#FF0000");
|
|
}
|
|
oRs.Close();
|
|
if (apikey == null) {
|
|
__DoLog("Error: The user with login '_MSGRAPHNOTIFICATION' does not have an API-key configured", "#FF0000");
|
|
return false;
|
|
}
|
|
return hookurl + "?API={0}&APIKEY={1}&fac_id={2}".format(apiname, apikey, customerId); // fac_id is vooral voor ngrok nodig
|
|
}
|
|
|
|
function validNotificationUrl(url) {
|
|
// Test zelf ook even de hookurl direct op dezelfde manier als MS-Graph dat doen.
|
|
// Zelf kunnen we betere foutmeldingen geven
|
|
var validationToken = "Validation: testing MS Graph <-> notificationUrl connection (" + new Date().toISOString() + ")";
|
|
__Log("\nPre-testing webhook url (like MS Graph will do soon): ");
|
|
__Log("get "+ url + "&validationToken=" + encodeURIComponent(validationToken));
|
|
var request = {
|
|
"method": "POST",
|
|
"url": url + "&validationToken=" + encodeURIComponent(validationToken),
|
|
"headers": {"Accept": "application/json" },
|
|
"params": {
|
|
"callback": function (request, response) {
|
|
return response.responseText == validationToken;
|
|
}
|
|
}
|
|
}
|
|
return doHTTP(request);
|
|
}
|
|
|
|
function getSyncToken(zaalemail) {
|
|
var token = null;
|
|
|
|
// Zoek token op
|
|
var sql = "SELECT s.res_ruimte_syncstate" // syncstate = deltatoken
|
|
+ " FROM res_ruimte r"
|
|
+ " , res_ruimte_sync s"
|
|
+ " WHERE r.res_ruimte_key = s.res_ruimte_key(+)"
|
|
+ " AND r.res_ruimte_verwijder IS NULL"
|
|
+ " AND res_ruimte_extern_id = " + safe.quoted_sql(zaalemail);
|
|
var oRs = Oracle.Execute(sql);
|
|
if (!oRs.EoF) {
|
|
token = oRs("res_ruimte_syncstate").Value;
|
|
}
|
|
oRs.Close();
|
|
return token;
|
|
}
|
|
|
|
function upsertSyncToken(zaalemail, token) {
|
|
var res_ruimte_key;
|
|
if (!isNaN(zaalemail) && typeof zaalemail === "number") { // De res_ruimte_key is al meegegeven (ipv de zaalemail)
|
|
res_ruimte_key = zaalemail;
|
|
} else if (typeof zaalemail === "string") { // Bepaal res_ruimte_key
|
|
var sql = "SELECT res_ruimte_key"
|
|
+ " FROM res_ruimte"
|
|
+ " WHERE res_ruimte_verwijder IS NULL"
|
|
+ " AND res_ruimte_extern_id = " + safe.quoted_sql(zaalemail);
|
|
var oRs = Oracle.Execute(sql);
|
|
if (oRs.EoF) {
|
|
__DoLog("Error: Ruimte met extern id '" + zaalemail + "' niet gevonden");
|
|
oRs.Close();
|
|
return false;
|
|
}
|
|
res_ruimte_key = oRs("res_ruimte_key").Value;
|
|
oRs.Close();
|
|
} else {
|
|
__DoLog("Error: Invalid type zaalemail '" + zaalemail + "' [" + (typeof zaalemail) + "]");
|
|
return false;
|
|
}
|
|
|
|
// Bestaat er voor deze ruimte al een syncstate?
|
|
sql = "SELECT 'dummy'"
|
|
+ " FROM res_ruimte_sync"
|
|
+ " WHERE res_ruimte_key = " + res_ruimte_key;
|
|
oRs = Oracle.Execute(sql);
|
|
var isNew = oRs.EoF;
|
|
oRs.Close();
|
|
|
|
// Upsert de syncstate
|
|
if (isNew) { // Deze ruimte wordt voor het eerst gekoppeld, maak een res_ruimte_sync-record aan
|
|
sql = "INSERT INTO res_ruimte_sync (res_ruimte_key,"
|
|
+ " res_ruimte_syncstate,"
|
|
+ " res_ruimte_syncdate)"
|
|
+ " VALUES (" + res_ruimte_key + ", " + safe.quoted_sql(token) + ", SYSDATE)";
|
|
} else { // De res_ruimte_sync-record bestaan al voor deze ruimte, update de syncstate/deltatoken
|
|
sql = "UPDATE res_ruimte_sync"
|
|
+ " SET res_ruimte_syncstate = " + safe.quoted_sql(token)
|
|
+ " , res_ruimte_syncdate = SYSDATE"
|
|
+ " WHERE res_ruimte_key = " + res_ruimte_key;
|
|
}
|
|
Oracle.Execute(sql);
|
|
return true;
|
|
}
|
|
|
|
/* Mogelijk is deze notificatie een @removed/isCancelled-notificatie
|
|
In deze functie valideren we dat deze notificaties ook zouden moeten leiden tot een Facilitor [D]elete
|
|
Mogelijk is er nl. alleen van ruimte gewisseld, en moeten we de reservering niet weggooien
|
|
*Deze functie werkt momenteel alleen voor Facilitor ruimte wijzigingen
|
|
*/
|
|
function validateNotification(userPrincipalName, roomCalendarEvent) {
|
|
if ((getMSGraphSyncLevel() & 4) && roomCalendarEvent.isCancelled) {
|
|
// Mogelijk slechts van ruimte gewisseld, kunnen we dit event nog terugvinden bij de organisator en zit er een Facilitor-ruimte bij deze afspraak?
|
|
var organizerCalendarEvent = getCalendarEventByICalUId(roomCalendarEvent.organizer.emailAddress.address, roomCalendarEvent.iCalUId, roomCalendarEvent.seriesMasterId);
|
|
if (organizerCalendarEvent && organizerCalendarEvent.isCancelled === false && organizerCalendarEvent.attendees && organizerCalendarEvent.attendees.length) {
|
|
for (var i = 0; i < organizerCalendarEvent.attendees.length; i++) {
|
|
var attendee = organizerCalendarEvent.attendees[i];
|
|
if (attendee.type === "resource" && attendee.emailAddress.address != userPrincipalName && attendee.status.response !== "declined") { // Status none/notResponded/accepted krijgen we via een andere notificatie binnen en verwerken we daar/later
|
|
var sql = "SELECT '' FROM res_ruimte WHERE res_ruimte_extern_id = " + safe.quoted_sql(attendee.emailAddress.address);
|
|
var oRs = Oracle.Execute(sql);
|
|
var roomChanged = !oRs.EoF; // Ruimte is gewisseld naar een bestaande Facilitor ruimte; negeer deze delete
|
|
oRs.Close();
|
|
if (roomChanged) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/* Zo wordt het externnr in Facilitor opgeslagen */
|
|
function getExternnrFromEvent(thisEvent, params) {
|
|
params = params || {};
|
|
|
|
var externnr;
|
|
// Bepaal externnr
|
|
switch (thisEvent.type) {
|
|
// case "seriesMaster": // Komt hier niet voor
|
|
// case "occurrence": // Komt hier niet voor
|
|
case "exception": // We doen hier niets met iCalUId want dat gebruiken we alleen voor room-changes, en dat ondersteunen we niet in Outlook-series
|
|
externnr = thisEvent.seriesMasterId + "|" + thisEvent.id;
|
|
break;
|
|
case "singleInstance":
|
|
externnr = (params.iCalUId ? "##iCalUId##" + thisEvent.iCalUId : thisEvent.id) + "|";
|
|
break;
|
|
}
|
|
return externnr;
|
|
}
|
|
|
|
/*
|
|
Update het Facilitor res_rsv_ruimte-record met link-data/id's
|
|
Returns the new res_rsv_ruimte_externnr
|
|
*/
|
|
function updateFcltRecord(res_rsv_ruimte_key, thisEvent, externnr2, params) {
|
|
var externnr;
|
|
var sql = "UPDATE res_rsv_ruimte"
|
|
+ " SET res_rsv_ruimte_externsyncdate = SYSDATE";
|
|
if (thisEvent) {
|
|
externnr = getExternnrFromEvent(thisEvent, params);
|
|
sql += " , res_rsv_ruimte_externnr = " + safe.quoted_sql(externnr)
|
|
} else if (thisEvent === null) { // Expliciet null maakt het veld leeg
|
|
sql += " , res_rsv_ruimte_externnr = NULL"
|
|
}
|
|
if (getMSGraphSyncLevel() & 4) {
|
|
if (externnr2) {
|
|
sql += ", res_rsv_ruimte_externnr2 = " + safe.quoted_sql(externnr2);
|
|
} else {
|
|
sql += ", res_rsv_ruimte_externnr2 = "
|
|
+ " (CASE"
|
|
+ " WHEN res_rsv_ruimte_externnr2 = '##PENDING##'"
|
|
+ " THEN NULL" // Herstel de koppeling van deze reservering
|
|
+ " ELSE res_rsv_ruimte_externnr2"
|
|
+ " END)";
|
|
}
|
|
}
|
|
sql += " WHERE res_rsv_ruimte_key = " + res_rsv_ruimte_key;
|
|
Oracle.Execute(sql);
|
|
return typeof externnr === undefined || externnr;
|
|
}
|
|
|
|
/*
|
|
Heeft dit roomCalendarEvent een Facilitor key aan boord die we kunnen matchen?
|
|
*/
|
|
function getFcltKeyFromEvent(roomCalendarEvent) {
|
|
if ("singleValueExtendedProperties" in roomCalendarEvent) {
|
|
for (var i = 0; i < roomCalendarEvent.singleValueExtendedProperties.length; i++) {
|
|
if (roomCalendarEvent.singleValueExtendedProperties[i].id === FCLT_KEY_PROP_ID_NAME) { // Key gevonden
|
|
return parseInt(roomCalendarEvent.singleValueExtendedProperties[i].value, 10); // => res_rsv_ruimte_key
|
|
}
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/* Pas de FCLT ruimte aan indien nodig */
|
|
function checkUpdateRoomchange(roomCalendarEvent, recordset) {
|
|
var zalen = getLocationsFromEvent(roomCalendarEvent);
|
|
if (zalen.length === 1 && zalen[0] !== recordset("res_ruimte_extern_id").Value) { // De ruimte in Exchange != de gematchte FCLT ruimte; pas aan indien bekend
|
|
var zaalChangeSql = "UPDATE res_rsv_ruimte"
|
|
+ " SET res_ruimte_opstel_key = "
|
|
+ " (CASE"
|
|
+ " WHEN (SELECT exc.getOpstelling (" + safe.quoted_sql(zalen[0]) + ") FROM DUAL) IS NOT NULL"
|
|
+ " THEN (SELECT exc.getOpstelling (" + safe.quoted_sql(zalen[0]) + ") FROM DUAL)"
|
|
+ " ELSE res_ruimte_opstel_key"
|
|
+ " END)"
|
|
+ " WHERE res_rsv_ruimte_key = " + recordset("res_rsv_ruimte_key").Value;
|
|
Oracle.Execute(zaalChangeSql);
|
|
// De rest (dirty-state, objecten & artikelen) wordt met de import wel rechtgetrokken, bezoekers niet, deleted = deleted
|
|
}
|
|
}
|
|
|
|
/* Undelete een reservering incl. bijbehorenden */
|
|
function undeleteFclt(res_rsv_ruimte_key) {
|
|
|
|
/* Reanimeer de Delen */
|
|
sql = "UPDATE res_rsv_deel"
|
|
+ " SET res_rsv_deel_verwijder = NULL"
|
|
+ " WHERE res_rsv_ruimte_key = " + res_rsv_ruimte_key
|
|
+ " AND res_rsv_deel_verwijder = (SELECT res_rsv_ruimte_verwijder"
|
|
+ " FROM res_rsv_ruimte"
|
|
+ " WHERE res_rsv_ruimte_key = " + res_rsv_ruimte_key + ")";
|
|
Oracle.Execute(sql);
|
|
|
|
/* Reanimeer de Artikelen */
|
|
sql = "UPDATE res_rsv_artikel"
|
|
+ " SET res_rsv_artikel_verwijder = NULL"
|
|
+ " WHERE res_rsv_ruimte_key = " + res_rsv_ruimte_key
|
|
+ " AND res_rsv_artikel_verwijder = (SELECT res_rsv_ruimte_verwijder"
|
|
+ " FROM res_rsv_ruimte"
|
|
+ " WHERE res_rsv_ruimte_key = " + res_rsv_ruimte_key + ")";
|
|
Oracle.Execute(sql);
|
|
|
|
/* Reanimeer de Reservering */
|
|
var sql = "UPDATE res_rsv_ruimte"
|
|
+ " SET res_rsv_ruimte_externsyncdate = SYSDATE"
|
|
+ " , res_rsv_ruimte_verwijder = NULL"
|
|
+ " , res_status_fo_key = 2"
|
|
+ " WHERE res_rsv_ruimte_key = " + res_rsv_ruimte_key;
|
|
Oracle.Execute(sql);
|
|
|
|
// Afspraken zijn hard verwijderd, dat kan dus niet meer hersteld worden
|
|
}
|
|
|
|
/* Test of de ruimte uberhaupt al geaccepteerd heeft */
|
|
function eventReadyForProcessing(userPrincipalName, thisEvent) {
|
|
if (!thisEvent.isOrganizer && ("attendees" in thisEvent)) { // Een notificatie van een uitgenodigde ruimte
|
|
for (var i in thisEvent.attendees) {
|
|
var attendee = thisEvent.attendees[i];
|
|
if (attendee.emailAddress.address === userPrincipalName && attendee.status.response != "accepted") { // Er is nog helemaal niet geaccepteerd
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/* Alleen bij S(msgraph_sync_level) & 4
|
|
Deze functie wordt aangeroepen na het ontvangen van een notificatie van een event die in Facilitor nog niet bekend is
|
|
We proberen de notificatie te matchen met een Facilitor reservering (als die bestaat) en te complementeren met het externnr
|
|
Reserveringen vanuit Outlook gebruiken deze functie niet, die hebben al direct bij het aanmaken een externnr
|
|
*/
|
|
function updateExternnr(userPrincipalName, id)
|
|
{
|
|
var roomCalendarEvent = getCalendarEventByID(userPrincipalName, id);
|
|
if (!roomCalendarEvent || !validateNotification(userPrincipalName, roomCalendarEvent)) {
|
|
return false;
|
|
}
|
|
|
|
if (roomCalendarEvent.type === "seriesMaster") { // Niets te updaten, we registreren geen seriesMaster's in Facilitor, de occurrences volgen hierna
|
|
return null;
|
|
}
|
|
|
|
if (!eventReadyForProcessing(userPrincipalName, roomCalendarEvent)) {
|
|
return -1; // 'Errorcode'; -> Stop this import
|
|
}
|
|
|
|
/* Kunnen we de notificatie matchen met een Facilitor reservering die zojuist is gemaakt op basis van de res_rsv_ruimte_key? */
|
|
var res_rsv_ruimte_key = getFcltKeyFromEvent(roomCalendarEvent);
|
|
var externnr = null;
|
|
var sql = "SELECT rr.res_rsv_ruimte_key"
|
|
+ " , rr.res_rsv_ruimte_verwijder"
|
|
+ " , r.res_ruimte_extern_id"
|
|
+ " FROM res_rsv_ruimte rr"
|
|
+ " , res_ruimte_opstelling ro"
|
|
+ " , res_ruimte r"
|
|
+ " WHERE ro.res_ruimte_opstel_key = rr.res_ruimte_opstel_key"
|
|
+ " AND r.res_ruimte_key = ro.res_ruimte_key";
|
|
if (res_rsv_ruimte_key > -1) { // Het event bevat een Facilitor res_rsv_ruimte_key, zoek daarop
|
|
sql += " AND rr.res_rsv_ruimte_key = " + res_rsv_ruimte_key;
|
|
} else if (roomCalendarEvent.type === "singleInstance") { // Probeer dan te matchen op iCalUId
|
|
sql += " AND rr.res_rsv_ruimte_externnr = " + safe.quoted_sql("##iCalUId##" + roomCalendarEvent.iCalUId + "|");
|
|
} else {
|
|
sql += " AND 1 = 0";
|
|
}
|
|
var oRs = Oracle.Execute(sql);
|
|
if (!oRs.EoF) { // Gevonden
|
|
var isDeleted = oRs("res_rsv_ruimte_verwijder").Value !== null;
|
|
var reanimate = !(roomCalendarEvent["@removed"] || roomCalendarEvent.isCancelled) &&
|
|
isDeleted &&
|
|
new Date().getTime() - new Date(oRs("res_rsv_ruimte_verwijder").Value).getTime() < REANIMATE_THRESHOLD_MS;
|
|
if (reanimate) {
|
|
undeleteFclt(oRs("res_rsv_ruimte_key").Value);
|
|
} else if (isDeleted) { // 'Grace period' van een soft delete overschreden; verwijder link onder water en ga verder met nieuwe afspraak
|
|
if (res_rsv_ruimte_key > -1) { // Haal de rsv-key weg bij het event; we gaan deze nooit meer gebruiken.
|
|
patchEventWithFcltKey(roomCalendarEvent);
|
|
}
|
|
updateFcltRecord(oRs("res_rsv_ruimte_key").Value, null); // Verwijder de opgeslagen linked id's permanent uit Facilitor
|
|
oRs.Close();
|
|
return null; // Niets (relevants) gevonden, dus niets te updaten
|
|
} else if (res_rsv_ruimte_key === -1 && !(roomCalendarEvent["@removed"] || roomCalendarEvent.isCancelled)) { // Gevonden, het actuele event bevat nog geen Facilitor res_rsv_ruimte_key, patch dat direct maar
|
|
patchEventWithFcltKey(roomCalendarEvent, oRs("res_rsv_ruimte_key").Value);
|
|
}
|
|
checkUpdateRoomchange(roomCalendarEvent, oRs);
|
|
externnr = updateFcltRecord(oRs("res_rsv_ruimte_key").Value, roomCalendarEvent);
|
|
}
|
|
oRs.Close();
|
|
return externnr;
|
|
}
|
|
|
|
/*
|
|
Attempts to update the Facilitor-rsv-record with the organizer-event-id
|
|
Only called when S(msgraph_sync_level) & 4
|
|
*/
|
|
function updateExternnr2(userPrincipalName, id, hostPrincipalName)
|
|
{
|
|
var roomCalendarEvent = getCalendarEventByID(userPrincipalName, id);
|
|
if (!roomCalendarEvent || roomCalendarEvent.type === "seriesMaster" /* Hier doet Facilitor niets mee */)
|
|
return "";
|
|
var organizerCalendarEvent = getCalendarEventByICalUId(hostPrincipalName, roomCalendarEvent.iCalUId, roomCalendarEvent.seriesMasterId);
|
|
if (!organizerCalendarEvent)
|
|
return "";
|
|
var matchByExternnr;
|
|
if (roomCalendarEvent.type == "singleInstance") {
|
|
matchByExternnr = "res_rsv_ruimte_externnr = " + safe.quoted_sql(id + "|"); // Exact
|
|
} else { // occurrence of exception
|
|
var occurrenceId = id.replace(/_/g, '\\_');
|
|
matchByExternnr = "res_rsv_ruimte_externnr LIKE " + safe.quoted_sql("%|" + occurrenceId) + " ESCAPE '\\'"; // LIKE
|
|
}
|
|
id = id.replace(/_/g, '\\_'); // Escape de underscore
|
|
var sql = "UPDATE res_rsv_ruimte"
|
|
+ " SET res_rsv_ruimte_externnr2 = " + safe.quoted_sql(organizerCalendarEvent.id)
|
|
+ " WHERE " + matchByExternnr;
|
|
Oracle.Execute(sql);
|
|
return organizerCalendarEvent.id;
|
|
}
|
|
|
|
/* Patch het Exchange event met de Facilitor res_rsv_ruimte_key */
|
|
function patchEventWithFcltKey(roomCalendarEvent, res_rsv_ruimte_key) {
|
|
var userPrincipalName = roomCalendarEvent.organizer.emailAddress.address;
|
|
var organisatorEvent = getCalendarEventByICalUId(userPrincipalName, roomCalendarEvent.iCalUId, roomCalendarEvent.seriesMasterId);
|
|
var data = {
|
|
"singleValueExtendedProperties": [{
|
|
"id": FCLT_KEY_PROP_ID_NAME,
|
|
"value": "-1"
|
|
}]
|
|
}
|
|
if (res_rsv_ruimte_key) {
|
|
data.singleValueExtendedProperties[0].value = String(res_rsv_ruimte_key);
|
|
}
|
|
var request = {
|
|
"method": "PATCH",
|
|
"url": "https://graph.microsoft.com/v1.0/users/" + encodeURIComponent(userPrincipalName) + "/events/" + organisatorEvent.id,
|
|
"body": JSON.stringify(data),
|
|
"headers": { "Content-Type": "application/json", "Authorization": "Bearer " + token }
|
|
}
|
|
var result = doHTTP(request);
|
|
return result;
|
|
}
|
|
|
|
/*////////////////////////////////
|
|
// //
|
|
// Main functions //
|
|
// //
|
|
////////////////////////////////*/
|
|
function doCheckExpiringAuth()
|
|
{
|
|
if (!unlockChilkat()) {
|
|
return false;
|
|
}
|
|
var certificates = new ActiveXObject("Chilkat_9_5_0.CertStore");
|
|
var success = certificates.LoadPfxFile(custabspath + "\\Exchange\\" + config.certificate, config.certificate_password);
|
|
if (success) {
|
|
var cert = certificates.GetCertificate(0);
|
|
var expirationDate = new Date(cert.ValidToStr);
|
|
var expDays = Math.floor((expirationDate.getTime() - new Date().getTime()) / 1000 / 60 / 60 / 24); // Days untill expiration
|
|
if (expDays < 30) {
|
|
var txt = "Uw Outlook koppeling certificaat verloopt over " + expDays + " dagen. Neem contact op met Facilitor voor een nieuw certificaat";
|
|
var sql = "BEGIN fac.putsystemnotification({0}, 3); END;".format(safe.quoted_sql(txt)); // 3=portal+email
|
|
Oracle.Execute(sql);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function syncPending(res_ruimte_key) {
|
|
if (!(getMSGraphSyncLevel() & 4)) { // Alleen voor de volledige koppeling
|
|
return false;
|
|
}
|
|
// Zoek de ##PENDING## reserveringen op
|
|
var sql = _getSql("C")
|
|
+ " AND rrr.res_rsv_ruimte_externnr2 = '##PENDING##'";
|
|
if (res_ruimte_key > 0) {
|
|
sql += " AND rr.res_ruimte_key = " + res_ruimte_key
|
|
}
|
|
var oRs = Oracle.Execute(sql);
|
|
while (!oRs.EoF) {
|
|
var rsv_ruimte_key = oRs("res_rsv_ruimte_key").Value;
|
|
var thisEvent = getCalendarEventByRsv(oRs("host_mail").Value, rsv_ruimte_key); // Is deze afspraak al wel bekend in Exchange?
|
|
if (thisEvent === null) { /* Niet gevonden */
|
|
_resToGraph(oRs, "C"); // Dan maken we hem bij deze alsnog aan
|
|
} else { /* We hebben hem toch gevonden! Link ze aan elkaar; */
|
|
updateFcltRecord(rsv_ruimte_key, thisEvent, thisEvent.id, { "iCalUId": true });
|
|
}
|
|
oRs.moveNext();
|
|
}
|
|
oRs.Close();
|
|
return true;
|
|
}
|
|
|
|
// Let op, deze functie doet eerst een full-sync
|
|
function createSubscription(userPrincipalName, res_ruimte_key, notificationUrl, urlValidated) {
|
|
if (!urlValidated && !validNotificationUrl(notificationUrl)) {
|
|
return false;
|
|
}
|
|
|
|
// Ververs syncstate (=maak leeg) zodat we niet de incremental changes binnenhalen, maar de hele calendarView
|
|
upsertSyncToken(+res_ruimte_key, null);
|
|
|
|
// Nu full-'sync'en, dan hebben we zsm een deltaToken waarmee we alleen mutaties kunnen ophalen
|
|
doImport(res_ruimte_key, userPrincipalName);
|
|
|
|
// En dan nu de subscription aanmaken
|
|
var expirationDate = new Date();
|
|
expirationDate.setDate(expirationDate.getDate() + 2); // 2 dagen (max is 3)
|
|
|
|
__Log("Creating hook for " + userPrincipalName);
|
|
|
|
var changeType = "updated";
|
|
var clientState = oCrypto.hex_random(20);
|
|
|
|
// Alle afspraken komen binnen met de "updated"-notificatie (de update is nl. dat de ruimte de afspraak heeft geaccepteerd),
|
|
// behalve afspraken die direct in een ruimte-agenda worden geboekt want die hoeven niet eerst geaccepteerd te worden.
|
|
// Die afspraken willen we alleen in Facilitor importeren als we een fallback-user hebben geconfigureerd, anders niet.
|
|
var sql = "SELECT prs_perslid_email FROM prs_v_aanwezigperslid WHERE prs_perslid_oslogin = " + safe.quoted_sql_upper("_MSGRAPH_FALLBACK_ROOMS");
|
|
var oRs = Oracle.Execute(sql);
|
|
if (!oRs.EoF && oRs("prs_perslid_email").Value) {
|
|
changeType = "created,updated";
|
|
}
|
|
oRs.Close();
|
|
|
|
var data = {
|
|
"changeType" : changeType,
|
|
"notificationUrl" : notificationUrl + "&res_ruimte=" + res_ruimte_key + "&hookcreated=" + new Date().toISOString(),
|
|
"resource" : "/users/" + encodeURIComponent(userPrincipalName) + "/events",
|
|
"clientState" : clientState,
|
|
"expirationDateTime": expirationDate
|
|
}
|
|
var newSubscriptionCallback = function _newSubscriptionCallback(request, response) {
|
|
__Log("Gelukt");
|
|
__Log(JSON.stringify(response, null, 2));
|
|
|
|
// Update de data aangaande de (ver)nieuw(d)e subscription
|
|
sql = "UPDATE res_ruimte"
|
|
+ " SET res_ruimte_graphhooksecret = " + safe.quoted_sql(clientState) // Met dit secret verifieren we dat requests van MS Graph ook echt van MS Graph zijn
|
|
+ " , res_ruimte_externsyncdate = SYSDATE" // Dit is de datum waarop de subscription is aangemaakt
|
|
+ " WHERE res_ruimte_key = " + res_ruimte_key;
|
|
Oracle.Execute(sql);
|
|
|
|
// Kijk nu of we nog pending (facilitor)reserveringen hebben en corrigeer die
|
|
syncPending(res_ruimte_key);
|
|
|
|
return true;
|
|
}
|
|
var request = {
|
|
"method": "POST",
|
|
"url": "https://graph.microsoft.com/v1.0/subscriptions",
|
|
"body": JSON.stringify(data),
|
|
"headers": { "Content-Type": "application/json", "Authorization": "Bearer " + token },
|
|
"params": { "callback": newSubscriptionCallback }
|
|
}
|
|
return doHTTP(request);
|
|
}
|
|
|
|
function deleteSubscription(userPrincipalName) {
|
|
var response = getSubscriptions();
|
|
var subscriptionList = [];
|
|
if (response && response.value) {
|
|
var resource = "/users/" + encodeURIComponent(userPrincipalName) + "/events";
|
|
for (var i = 0; i < response.value.length; i++) {
|
|
if (response.value[i].resource == resource) { // Filter op Extern ID
|
|
subscriptionList.push(response.value[i].id);
|
|
}
|
|
}
|
|
while (response.skiptoken) {
|
|
response = getSubscriptions(response.skiptoken);
|
|
for (var i = 0; i < response.value.length; i++) {
|
|
if (response.value[i].resource == resource) { // Filter op Extern ID
|
|
subscriptionList.push(response.value[i].id);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
var success = true;
|
|
for (var i = 0; i < subscriptionList.length; i++) {
|
|
var request = {
|
|
"method": "DELETE",
|
|
"url": "https://graph.microsoft.com/v1.0/subscriptions/" + subscriptionList[i],
|
|
"headers": { "Accept": "application/json", "Authorization": "Bearer " + token }
|
|
}
|
|
success = success && doHTTP(request);
|
|
}
|
|
return success;
|
|
}
|
|
|
|
/*////////////////////////////////
|
|
// //
|
|
// Utils //
|
|
// //
|
|
////////////////////////////////*/
|
|
function doHTTP(method, url, body, headers, params)
|
|
{
|
|
if (arguments.length === 1 && typeof arguments[0] === "object") {
|
|
request = arguments[0];
|
|
var method = request.method;
|
|
var url = request.url;
|
|
var body = request.body || null;
|
|
var headers = request.headers;
|
|
headers["client-request-id"] = getGUID();
|
|
request.params = request.params || {};
|
|
var params = request.params;
|
|
} else { // Deprecated; het request is nu 1 object als argument
|
|
var params = params || {};
|
|
}
|
|
|
|
// var SXH_PROXY_SET_PROXY = 2;
|
|
var SXH_OPTION_IGNORE_SERVER_SSL_CERT_ERROR_FLAGS = 2;
|
|
var SXH_SERVER_CERT_IGNORE_ALL_SERVER_ERRORS = 0x3300;
|
|
|
|
var objXMLHTTP = new ActiveXObject("MSXML2.ServerXMLHTTP.6.0");
|
|
|
|
objXMLHTTP.setTimeouts(1200000, 270000, 255000, 240000);
|
|
// Defaults: Unlimited, 60000, 30000, 30000
|
|
|
|
objXMLHTTP.open(method, url, !!params.async);
|
|
if (headers) {
|
|
if ("Authorization" in headers && headers.Authorization === "Bearer null") {
|
|
headers.Authorization = "Bearer " + requestToken(config);
|
|
}
|
|
var header;
|
|
for (header in headers) {
|
|
objXMLHTTP.setRequestHeader(header, headers[header]);
|
|
}
|
|
}
|
|
var user_lang = "nl";
|
|
var server_tz = "Europe/Amsterdam";
|
|
if (typeof DEZE !== "undefined") { // Alleen vanuit de browser
|
|
user_lang = (DEZE.Session("user_lang") || "nl").toLowerCase();
|
|
server_tz = DEZE.S("fac_server_timezone");
|
|
}
|
|
objXMLHTTP.setRequestHeader("Accept-Language", user_lang);
|
|
objXMLHTTP.setRequestHeader("Prefer", "outlook.timezone=\"" + server_tz + "\""); // Dan hoeven wij niets meer om te zetten
|
|
|
|
objXMLHTTP.setOption(SXH_OPTION_IGNORE_SERVER_SSL_CERT_ERROR_FLAGS, SXH_SERVER_CERT_IGNORE_ALL_SERVER_ERRORS);
|
|
|
|
if (config.loglevel > 0 && body) {
|
|
__Log2File("request.xml", body);
|
|
}
|
|
|
|
var start = new Date().getTime();
|
|
try {
|
|
objXMLHTTP.send(body);
|
|
} catch (e) {
|
|
var duration = new Date().getTime() - start;
|
|
__DoLog("doHTTP ERROR: " + e.description + " (" + duration + "ms)", "#FF0000"); // Bijvoorbeeld; 'A connection with the server could not be established' als ngrok lokaal niet draait
|
|
if (request) {
|
|
__DoLog(request);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
return _handleMsGraphResponse(request, objXMLHTTP);
|
|
}
|
|
|
|
/* Evalueert een MS Graph response */
|
|
function _handleMsGraphResponse(request, httpResponse)
|
|
{
|
|
if (request.params.async) {
|
|
if (httpResponse.readyState && httpResponse.readyState != 4) {
|
|
httpResponse.waitForResponse(180); // Wacht max. 180s op antwoord
|
|
}
|
|
}
|
|
|
|
if (!request.batch && // Als request.batch = true, dan is httpResponse een geparsed object, en geen echt httpResponse
|
|
!(httpResponse.readyState && httpResponse.readyState == 4) ||
|
|
!(httpResponse.status && httpResponse.status >= 200 && httpResponse.status < 300)) {
|
|
return _handleMsGraphFailure(request, httpResponse);
|
|
} else if (request.method === "DELETE") { // 204 = 'No Content' dus niks terug te geven
|
|
return true;
|
|
}
|
|
|
|
/* Parse het alvast, 'batch' is al een geparsed object, en geen httpResponse */
|
|
if (request.batch) {
|
|
httpResponse = httpResponse.body;
|
|
} else { /* MS Graph returned altijd JSON */
|
|
try {
|
|
httpResponse = JSON.parse(httpResponse.responseText);
|
|
} catch (e) { /* Ok, dan niet */ }
|
|
}
|
|
|
|
if (request.method === "GET") { // Detecteer skip/delta-tokens en zet die in het response-object
|
|
if ("@odata.deltaLink" in httpResponse) { // Dit is de deltatoken die we opslaan als syncstate; "Tot hier zijn we gesynced"
|
|
httpResponse.deltatoken = httpResponse["@odata.deltaLink"].split("$deltatoken=")[1];
|
|
} else if ("@odata.nextLink" in httpResponse) { // Er is meer; we roepen ons zelf straks nogmaals aan met dit token
|
|
httpResponse.skiptoken = httpResponse["@odata.nextLink"].split("$skiptoken=")[1];
|
|
}
|
|
}
|
|
|
|
if (request.params.callback) {
|
|
return request.params.callback(request, httpResponse);
|
|
}
|
|
|
|
return httpResponse;
|
|
}
|
|
|
|
// Onderstaande functie wordt alleen aangeroepen als de response niet aanwezig, of onsuccesvol is
|
|
function _handleMsGraphFailure(request, httpResponse) {
|
|
if (httpResponse && (!httpResponse.readyState || httpResponse.readyState == 4) && httpResponse.status) {
|
|
try {
|
|
var msGraphResponse = httpResponse.responseText ? JSON.parse(httpResponse.responseText) : httpResponse.body; // Die laatste is voor batches
|
|
} catch (e) {
|
|
var msGraphResponse = httpResponse.responseText || "";
|
|
__DoLog("Error parsing MS Graph response (" + e.description + ")", "#FF0000");
|
|
__DoLog(msGraphResponse);
|
|
}
|
|
|
|
// Hier gaat iets fout
|
|
if (msGraphResponse !== null && typeof msGraphResponse == "object" && typeof msGraphResponse.error == "object") { // MS Graph errors
|
|
switch (msGraphResponse.error.code) {
|
|
case "ErrorItemNotFound":
|
|
__Log("Reservering kon niet in Exchange gevonden worden", "#FFFF44");
|
|
break;
|
|
case "ResyncRequired":
|
|
case "SyncStateNotFound":
|
|
__Log("[" + httpResponse.status + "] - MS Graph [" + msGraphResponse.error.code + "] - " + msGraphResponse.error.message, "#FFFF44");
|
|
__Log("Refreshing syncstate ...");
|
|
var userPrincipalName = request.url.substring(
|
|
request.url.indexOf("/users/") + "/users/".length,
|
|
request.url.lastIndexOf("/calendarView/")
|
|
);
|
|
return getCalendarItems(userPrincipalName, null, null); // delta token is gone, invalid of expired -> full sync
|
|
case "ApplicationThrottled":
|
|
__DoLog("[" + httpResponse.status + "] - MS Graph [ApplicationThrottled] - " + msGraphResponse.error.message, "#FF0000");
|
|
/* UNTESTED
|
|
var retryAfterXSeconds = parseInt(httpResponse.getResponseHeader("Retry-After"), 10);
|
|
__DoLog("volgens MS Graph moeten we het over " + retryAfterXSeconds + "s weer proberen", "#FFFF44");
|
|
if (!request || retryAfterXSeconds > 100) { // Dan niet hoor
|
|
return false;
|
|
}
|
|
var oSLNKDWF = new ActiveXObject("SLNKDWF.About");
|
|
oSLNKDWF.Sleep(retryAfterXSeconds * 1000);
|
|
return doHTTP(request);
|
|
*/
|
|
break;
|
|
case "FailedDependency":
|
|
__DoLog("[" + httpResponse.status + "] - MS Graph [FailedDependency] - " + msGraphResponse.error.message + " => Retrying individually ..", "#FFFF44");
|
|
delete request.batch;
|
|
delete request.dependsOn;
|
|
delete request.id;
|
|
request.url = "https://graph.microsoft.com/v1.0" + request.url;
|
|
request.body = JSON.stringify(request.body);
|
|
return doHTTP(request); // Individueel nog een keer proberen dan maar
|
|
// Vanaf hier lossen we de fouten niet zelf meer op
|
|
case "ErrorInvalidUser": // Gebruiker niet gevonden
|
|
case "ResourceNotFound": // Ruimte niet gevonden
|
|
case "MailboxNotEnabledForRESTAPI": // Gebruiker is inactief/soft-deleted in Exchange
|
|
default:
|
|
__DoLog("[" + httpResponse.status + "] - MS Graph [" + msGraphResponse.error.code + "] - " + msGraphResponse.error.message + " (request-id: " + httpResponse.getResponseHeader("request-id") + ")", "#FF0000");
|
|
if (request) {
|
|
__DoLog(request);
|
|
}
|
|
break;
|
|
}
|
|
} else if (msGraphResponse !== null && typeof msGraphResponse == "object" && typeof msGraphResponse.error == "string") { // Azure errors
|
|
__DoLog("[" + httpResponse.status + "] - Azure error: [" + msGraphResponse.error + "]: " + msGraphResponse.error_description, "#FF0000");
|
|
} else {
|
|
__DoLog("MS Graph HTTP-request NIET succesvol afgehandeld, onderstaand antwoord ontvangen", "#FF0000");
|
|
__DoLog(msGraphResponse);
|
|
}
|
|
} else {
|
|
__DoLog("Error: Geen response van MS Graph ontvangen (Configuratie/verbindings-problemen).", "#FF0000");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function getImportAppKey(appCode) {
|
|
var result = "";
|
|
var sql = "SELECT i.fac_import_app_key "
|
|
+ " FROM fac_import_app i"
|
|
+ " WHERE i.fac_import_app_code = " + safe.quoted_sql(appCode);
|
|
var oRs = Oracle.Execute(sql);
|
|
if (oRs.EOF) {
|
|
__DoLog("FATAL: Import '" + appCode + "' not found");
|
|
return false;
|
|
}
|
|
result = oRs("fac_import_app_key").Value;
|
|
oRs.Close();
|
|
return result;
|
|
}
|
|
|
|
function doImport(res_ruimte_key, zaalemail, import_type) {
|
|
if (typeof import_type === "undefined") {
|
|
var import_type = "EXCHFULL";
|
|
}
|
|
var import_app_key = getImportAppKey(import_type);
|
|
|
|
var fileStream = generateCalendarImport(zaalemail, import_app_key, true); // as Stream
|
|
if (!fileStream || !fileStream.Size) {
|
|
__Log("No action required after receiving MS Graph import", "#FFFF44");
|
|
return true; // Klaar
|
|
}
|
|
|
|
if (typeof DEZE !== "undefined") { /* Alleen vanuit de asp, en niet bij het runnen van refreshMSGraphSubscriptions.bat */
|
|
user_key = DEZE.user_key;
|
|
customerId = DEZE.customerId;
|
|
}
|
|
var procedure_params = ", " + (config.fullpast || 0) + ", " + (config.fullfuture || 90);
|
|
var res = impReadStream(fileStream,
|
|
import_app_key,
|
|
{ fac_home: custabspath + "/exchange/",
|
|
filepathname: "SYNC_" + zaalemail,
|
|
customerId: customerId,
|
|
ref_key: res_ruimte_key,
|
|
keep_old: 300, // Parallelle import 300 seconden ondersteunen (nodig voor deze import?)
|
|
user_key: user_key,
|
|
keep_backup: false, // mits fac_import_app_folder gezet
|
|
is_stream: true,
|
|
proc_params: procedure_params
|
|
});
|
|
if (res.success) {
|
|
var processres = impProcessStream(res.import_key, {
|
|
customerId: customerId,
|
|
proc_params: procedure_params
|
|
});
|
|
} else {
|
|
__DoLog(res);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
if (!Date.prototype.toISOString) {
|
|
Date.prototype.toISOString = function () {
|
|
function pad(n) { return n < 10 ? '0' + n : n }
|
|
return this.getUTCFullYear() + '-'
|
|
+ pad(this.getUTCMonth() + 1) + '-'
|
|
+ pad(this.getUTCDate()) + 'T'
|
|
+ pad(this.getUTCHours()) + ':'
|
|
+ pad(this.getUTCMinutes()) + ':'
|
|
+ pad(this.getUTCSeconds()) + 'Z';
|
|
};
|
|
}
|
|
|
|
function getGUID() {
|
|
// 12345678-1234-1234-1234-123456789012 lijkt het meest gebruikte format, laten wij dat ook maar aanhouden dan
|
|
var guids = [];
|
|
guids.push(oCrypto.hex_random(8));
|
|
guids.push(oCrypto.hex_random(4));
|
|
guids.push(oCrypto.hex_random(4));
|
|
guids.push(oCrypto.hex_random(4));
|
|
guids.push(oCrypto.hex_random(12));
|
|
return guids.join("-");
|
|
}
|
|
|
|
function safefilename(naam) // geen 'lage' karakters en geen (back)slashes, *,%,<,>, '"', ':', ';' '?' and '|' of '+'
|
|
{
|
|
return naam.replace(/[\x00-\x1F|\/|\\|\*|\%\<\>\"\:\;\?\|\+]+/g, "_"); // " syntax highlight correctie
|
|
}
|
|
|
|
function __Log2File(log_file, data) {
|
|
if(data.indexOf("error") > -1)
|
|
{
|
|
__Log("__Log2File is misgegaan omdat data een error heeft: " + data + ".. Deze zal worden overgeslagen.");
|
|
}
|
|
else
|
|
{
|
|
try {
|
|
var target = custabspath + "\\Exchange\\" + config.xmlfolder + safefilename(log_file);
|
|
var utf8Stream = new ActiveXObject("ADODB.Stream");
|
|
utf8Stream.Open();
|
|
utf8Stream.Type = 2;
|
|
utf8Stream.CharSet = "utf-8";
|
|
utf8Stream.WriteText(data);
|
|
utf8Stream.SaveToFile(target, 2);
|
|
utf8Stream.Close();
|
|
}
|
|
catch (e)
|
|
{
|
|
__DoLog("__Log2File {0} failed: {1}".format(target, e.description));
|
|
var oSLNKDWF = new ActiveXObject("SLNKDWF.About");
|
|
__DoLog("Perhaps folder does nog exist or {0} does not have modify permissions".format(oSLNKDWF.UserContext));
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// timeZone is hier altijd gelijk aan de server timeZone, dus geen conversie nodig
|
|
function parseISOString(ISOString) {
|
|
var s = ISOString.split(/\D+/); // Split op non-digits
|
|
s[6] = Math.round(+s[6] / 10000); // Precisie van de milliseconden van 7 -> 3
|
|
return new Date(+s[0], --s[1], +s[2], +s[3], +s[4], +s[5], +s[6]);
|
|
}
|
|
|
|
function unlockChilkat() {
|
|
try {
|
|
var glob = new ActiveXObject("Chilkat_9_5_0.Global");
|
|
} catch (e) {
|
|
__DoLog(e.description, "#FF0000");
|
|
}
|
|
var sql = "SELECT COALESCE(fac_setting_pvalue, fac_setting_default) fac_setting_value"
|
|
+ " FROM fac_setting"
|
|
+ " WHERE fac_setting_name = 'puo_chilkat_secret'";
|
|
var oRs = Oracle.Execute(sql);
|
|
var success = glob.UnlockBundle(oRs("fac_setting_value").Value);
|
|
oRs.Close();
|
|
if (success != 1) {
|
|
__DoLog("Chilkat_9_5_0 Unlock failed:\n" + oChilglob.LastErrorText, "#FF0000");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function inArray(needle, haystack) {
|
|
var length = haystack.length;
|
|
for(var i = 0; i < length; i++) {
|
|
if(haystack[i] == needle) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
String.prototype.format = function()
|
|
{
|
|
var formatted = this;
|
|
for (var i = 0; i < arguments.length; i++)
|
|
{
|
|
if (typeof arguments[i] == "string")
|
|
arguments[i] = arguments[i].replace(/\$/g, '$$$$');
|
|
var regexp = new RegExp('\\{'+i+'\\}', 'gi');
|
|
formatted = formatted.replace(regexp, arguments[i]);
|
|
}
|
|
return formatted;
|
|
}; |