1575 lines
64 KiB
C++
1575 lines
64 KiB
C++
<% /*
|
||
$Revision$
|
||
$Id$
|
||
File: shared/login.inc
|
||
Description: Inlog functionaliteit
|
||
Parameters:
|
||
Context:
|
||
Note: LET OP: Dit bestand heeft een heel klein stukje VBScript in zich
|
||
Dat maakt dat je dit bestand *niet* meer overal (via common.inc
|
||
of zo) moet gaan includen
|
||
Note2: json2.js is nodig omdat user.options wordt gebruikt
|
||
Op zich moet je het dan maar aan diverse asp's toevoegen maar dan
|
||
moet je ook vele sso.asp's langs. Dan toch maar hier
|
||
|
||
*/
|
||
%>
|
||
<!-- #include file="../Shared/json2.js" -->
|
||
<!-- #include file="../api2/model_prs_perslid.inc" -->
|
||
<%
|
||
// Elders is prs_key geauthenticeerd. Registreer die hier als de actieve gebruiker.
|
||
function doLogin(prs_key, params)
|
||
{
|
||
__Log("==== doLogin " + prs_key);
|
||
params = params || {};
|
||
// Paranoia mode
|
||
var sql = "SELECT prs_perslid_login"
|
||
+ " FROM prs_perslid"
|
||
+ " WHERE prs_perslid_verwijder IS NULL"
|
||
+ " AND prs_perslid_key = " + prs_key;
|
||
var oRs = Oracle.Execute(sql);
|
||
if (oRs.Eof)
|
||
{
|
||
__DoLog("Niet bestaande of verwijderde persoon " + prs_key + " geweigerd in doLogin.")
|
||
eval("INTERNAL_ERROR_INVALID_LOGIN_" + prs_key);
|
||
}
|
||
var first_login = (oRs("prs_perslid_login").Value == null);
|
||
oRs.Close();
|
||
|
||
if ("isFACFACinternal" in params) // vanuit JWT-sso
|
||
{
|
||
var deze = new Perslid(prs_key);
|
||
// SSO naar een FACFAC gebruiker mag alleen als aut_idp_internal aan staat
|
||
if (deze.has("WEB_FACFAC") && !params.isFACFACinternal)
|
||
{
|
||
__DoLog("Illegal login WEB_FACFAC");
|
||
shared.internal_error("IDP '{0}' cannot be used for users with WEB_FACFAC (prs_key={1}).".format(params.idp_code, prs_key));
|
||
}
|
||
// Als aut_idp_internal aan staat mag alleen je alleen SSO doen naar een FACFAC gebruiker
|
||
// Tenzij S("idp_internal_anyuser") true is, dan mag je toch naar iedereen
|
||
// Dat doen we op OTA via custenc.wsc, dat doen we niet in PROD
|
||
if (params.isFACFACinternal && !S("idp_internal_anyuser") && !deze.has("WEB_FACFAC"))
|
||
{
|
||
shared.internal_error("IDP '{0}' can only be used for users with WEB_FACFAC (prs_key={1}).".format(params.idp_code, prs_key));
|
||
}
|
||
}
|
||
|
||
// Alvast nieuwe user_key loggen zodat je ziet wie er inlogt.
|
||
if (typeof NO_ADDHEADER == "undefined" && user_key != prs_key && Request.Servervariables("HTTP_FCLT_VERSION").Count > 0)
|
||
{ // wordt opgepikt door FCLTAPI.DLL voor in de logging en daarna gestript. Niet in Fiddler te zien dus
|
||
Response.AddHeader ("FCLT_USERID", customerId + "\\" + String(prs_key));
|
||
}
|
||
|
||
/* global */ user_key = prs_key;
|
||
//user_lang = oRs(1).Value; // globale moet er nog uit!
|
||
if (typeof LCL_Disable == "undefined")
|
||
{
|
||
__Log("==== LCL opnieuw laden voor nieuwe gebruiker");
|
||
lcl.loadLCL();
|
||
}
|
||
|
||
setASPFIXATION();
|
||
|
||
Session.Contents.Remove("has_no_remote_res"); // Dat weten we nu nog niet zeker
|
||
var ip = String(Request.ServerVariables("REMOTE_ADDR"));
|
||
Session("last_ip") = ip; // onthouden voor ip_locking
|
||
|
||
if (!params.noFacSession) // AAEN infobord krijgt er anders 10 per minuut
|
||
{
|
||
// Nu maar eens oudere opruimen. Effectief blijft altijd
|
||
// de laatste sessie van een gebruiker staan.
|
||
// Mogelijk ruim je ook toch verlopen sessies van anderen op. Geen probleem.
|
||
var sql = "DELETE FROM fac_session"
|
||
+ " WHERE fac_session_expire < SYSDATE";
|
||
Oracle.Execute(sql);
|
||
|
||
var agent = String(Request.ServerVariables("HTTP_USER_AGENT"));
|
||
|
||
// Welbeschouwd gebruiken we het volgende FAC_SESSION record nooit, we werken
|
||
// altijd met ASPSESSION==>IIS SESSION==>user_key
|
||
var sql = "INSERT INTO fac_session"
|
||
+ " (fac_session_sessionid_hash,"
|
||
+ " prs_perslid_key,"
|
||
+ " fac_session_expire,"
|
||
+ " fac_session_useragent,"
|
||
+ " fac_session_ip)"
|
||
+ " VALUES(fac.makehash(" + safe.quoted_sql(Session("FACSESSIONID")) + "), "
|
||
+ user_key + ","
|
||
+ " SYSDATE+1, " // 24 uur is genoeg
|
||
+ safe.quoted_sql(agent, 256) + ","
|
||
+ safe.quoted_sql(ip, 64) + ")";
|
||
Oracle.Execute(sql);
|
||
}
|
||
|
||
var registersql = "UPDATE prs_perslid"
|
||
+ " SET prs_perslid_login = SYSDATE"
|
||
+ " WHERE prs_perslid_key=" + user_key;
|
||
Oracle.Execute(registersql);
|
||
|
||
Session("user_key") = user_key; // Nu ben je pas *echt* ingelogd
|
||
/* global */ user = new Perslid(user_key);
|
||
|
||
Session.Contents.Remove("must_accept_terms");
|
||
if (S("fac_accept_terms") > 0
|
||
&& (!(Session("org_user_key") > 0))
|
||
&& !user.isGroupedUser()
|
||
&& !params.noFacSession
|
||
&& !user.has("WEB_FACTAB") // die er niet mee lastig vallen
|
||
)
|
||
{
|
||
var termsPath = custpath + "/bdradrfiles/" + L("lcl_terms_filename");
|
||
var fso = Server.CreateObject("Scripting.FileSystemObject");
|
||
var termsFile = Server.MapPath(termsPath);
|
||
if (!fso.FileExists(termsFile))
|
||
{
|
||
__DoLog("fac_accept_terms is set but '{0}' is not found.".format(termsPath), '#f00');
|
||
}
|
||
else
|
||
{
|
||
var uvers = String(user.terms_version()).split("|")[0]; // Haal de digest er af
|
||
if (uvers != L("lcl_terms_filename"))
|
||
Session("must_accept_terms") = 1; // Wordt opgepikt in common.inc
|
||
}
|
||
}
|
||
|
||
// Normaal gesproken zou ik hier new Date() gebruiken
|
||
// Omdat zelfs kleine afwijkingen al grote problemen zouden kunnen
|
||
// geven consequent altijd de Oracle-tijd gebruiken
|
||
var sql = "SELECT SYSDATE FROM DUAL";
|
||
var oRs = Oracle.Execute(sql);
|
||
Session("login_date") = new Date(oRs(0).Value).getTime();
|
||
oRs.Close();
|
||
|
||
// FACFAC tracken we altijd
|
||
if (!params.noFacSession) // fac_scan_cust genereert er anders te veel
|
||
{
|
||
if (user.has("WEB_FACFAC"))
|
||
shared.trackaction("PRSLOG", user_key, L("lcl_logged_on").format(Session("ASPFIXATION").slice(-6)));
|
||
}
|
||
|
||
if (first_login && S("fac_firstlogin_expire") > 0)
|
||
{
|
||
var sql = "INSERT INTO fac_menu"
|
||
+ " ( fac_menu_altlabel"
|
||
+ " , fac_menu_alturl"
|
||
+ " , fac_menu_altgroep"
|
||
+ " , prs_perslid_key"
|
||
+ " , fac_menu_volgnr"
|
||
+ " ) VALUES"
|
||
+ " (" + safe.quoted_sql(L("lcl_firstlogin_url"))
|
||
+ " ," + safe.quoted_sql(S("fac_firstlogin_url"))
|
||
+ " ,5"
|
||
+ " ," + user_key
|
||
+ " ,(SELECT 10 + COALESCE(MAX(fac_menu_volgnr), 0)"
|
||
+ " FROM fac_menu"
|
||
+ " WHERE prs_perslid_key = " + user_key
|
||
+ ")"
|
||
+ " )";
|
||
Oracle.Execute(sql, true);
|
||
}
|
||
else
|
||
{
|
||
__Log("Welcome.asp expired?");
|
||
var sql = "DELETE FROM fac_menu"
|
||
+ " WHERE fac_menu_altgroep = 5"
|
||
+ " AND fac_menu_alturl = " + safe.quoted_sql(S("fac_firstlogin_url"))
|
||
+ " AND prs_perslid_key = " + user_key
|
||
+ " AND fac_menu_aanmaak < SYSDATE - " + S("fac_firstlogin_expire");
|
||
Oracle.Execute(sql, true);
|
||
}
|
||
|
||
var fac_lang = getQParamSafe("fac_lang", "").toUpperCase(); // overrule via param
|
||
// Liever geen session maar m_connections heeft dit al nodig voor zijn fac.initsession
|
||
// Bovendien voorkomen we zo dat een simpele user.lang() al een _require_prs_perslid triggert
|
||
Session("user_lang") = fac_lang || user.dblang();
|
||
|
||
Session("time_zone") = user.timezone() || S("fac_server_timezone");
|
||
|
||
return true;
|
||
}
|
||
|
||
// Session.Abondon is gevaarlijk: dan verlies je ook CustomerID etc.
|
||
// Bovendien krijg je met IIS dan nog steeds geen nieuwe ASPSESSIONID
|
||
function doLogoff()
|
||
{
|
||
Session("no_sso") = 1; // Voorkom autosso
|
||
Session.Contents.Remove("user_key");
|
||
Session.Contents.Remove("ASPFIXATION");
|
||
Session.Contents.Remove("must_reset_password");
|
||
Session.Contents.Remove("login_by_fallback");
|
||
deleteSessionCookie("fcltid");
|
||
deleteCookie("ASPFIXATION");
|
||
}
|
||
|
||
// Inloggen via een fcltid-cookie of een session die met QR-code is gescand
|
||
function setUserFromSession (p_session)
|
||
{
|
||
// TODO: Hier wil ik ooit de user_key eigenlijk ook hebben uit
|
||
// een cookie zodat deze query efficienter wordt.
|
||
var sql = "SELECT prs_perslid_key, fac_session_data "
|
||
+ " FROM fac_session "
|
||
+ " WHERE fac_session_expire > sysdate "
|
||
+ " AND fac_session_sessionid_hash = fac.makehash(" + safe.quoted_sql(p_session) + ")";
|
||
var oRs = Oracle.Execute( sql );
|
||
if (!oRs.eof)
|
||
{
|
||
doLogin(oRs("prs_perslid_key").Value);
|
||
var sessionData = oRs("fac_session_data").value;
|
||
|
||
// verwijder de huidige sessie
|
||
sql = "DELETE fac_session"
|
||
+ " WHERE prs_perslid_key = " + user_key // index-performance
|
||
+ " AND fac_session_sessionid_hash = fac.makehash(" + safe.quoted_sql(p_session) + ")";
|
||
Oracle.Execute(sql);
|
||
// makeSessionCookie(sessionData); aanroeper bepaalt maar of er een nieuwe sessie komt
|
||
}
|
||
oRs.Close();
|
||
}
|
||
|
||
function makeSessionCookie (sessionData)
|
||
{
|
||
var sessionId = shared.random(32);
|
||
|
||
// maak nieuwe sessie aan
|
||
var agent = String(Request.ServerVariables("HTTP_USER_AGENT"));
|
||
var ip = String(Request.ServerVariables("REMOTE_ADDR"));
|
||
|
||
sql = "INSERT INTO fac_session ( "
|
||
+ " fac_session_sessionid_hash, "
|
||
+ " prs_perslid_key, "
|
||
+ " fac_session_data, "
|
||
+ " fac_session_expire,"
|
||
+ " fac_session_useragent,"
|
||
+ " fac_session_ip) "
|
||
+ " VALUES ( "
|
||
+ "fac.makehash(" + safe.quoted_sql(sessionId) + "), "
|
||
+ user_key + ", "
|
||
+ safe.quoted_sql(sessionData) + ", "
|
||
+ " sysdate + " + S("login_remember_days") + ", " // sessie timeout op een half jaar.
|
||
+ safe.quoted_sql(agent, 256) + ","
|
||
+ safe.quoted_sql(ip, 64) + " )"
|
||
Oracle.Execute(sql);
|
||
|
||
// set de nieuwe sessionID als cookie.
|
||
Response.Cookies("fcltid")=sessionId;
|
||
Response.Cookies("fcltid").Path = rooturl + "/";
|
||
if (S("auto_https") & 2)
|
||
Response.Cookies("fcltid").Secure=true;
|
||
VBexpireCookie("fcltid", "d", S("login_remember_days"));
|
||
|
||
// fcltcust is niet per se nodig voor Facilitor maar wel handig bij interne ontwikkeling
|
||
Response.Cookies("fcltcust") = customerId;
|
||
Response.Cookies("fcltcust").Path = rooturl + "/";
|
||
if (S("auto_https") & 2)
|
||
Response.Cookies("fcltcust").Secure=true;
|
||
VBexpireCookie("fcltcust", "d", S("login_remember_days"));
|
||
}
|
||
|
||
function deleteSessionCookie (cookiename)
|
||
{
|
||
var session = String(Request.Cookies(cookiename));
|
||
if (session)
|
||
{
|
||
var sql = "DELETE fac_session"
|
||
+ " WHERE prs_perslid_key = " + user_key // index-performance
|
||
+ " AND fac_session_sessionid_hash = fac.makehash(" + safe.quoted_sql(session) + ")";
|
||
Oracle.Execute(sql);
|
||
}
|
||
|
||
// Cookie wissen
|
||
deleteCookie(cookiename);
|
||
}
|
||
|
||
function deleteCookie (cookiename)
|
||
{
|
||
Response.Cookies(cookiename)="";
|
||
Response.Cookies(cookiename).Path = rooturl + "/";
|
||
if (S("auto_https") & 2)
|
||
Response.Cookies(cookiename).Secure=true;
|
||
VBexpireCookie(cookiename, "yyyy", -1);
|
||
}
|
||
|
||
function pbkdf2(wachtwoord, passsalt, workfactor, len)
|
||
{
|
||
//__Log("P: {0}, S: {1}, W: {2}, L:{3}".format(wachtwoord, passsalt, workfactor, len))
|
||
if (workfactor < 1 ||
|
||
workfactor > 20) // workfactor 2^24 (16777216) duurt op FACWS001 zo'n 45 seconde
|
||
// Daarom voorlopig limiteren op 2^20 (1048576, zo'n 3 seconde)
|
||
{
|
||
__DoLog(workfactor);
|
||
INTERNAL_ERROR_BADWORKFACTOR;
|
||
}
|
||
len = len || S("prs_password_hash_length"); // default 20
|
||
// __Log("Wachtwoord: {0}, salt: {1}, workfactor: {2}, len: {3}".format(wachtwoord, passsalt, Math.pow(2, workfactor), len));
|
||
var oSLNKDWF = new ActiveXObject("SLNKDWF.About");
|
||
var oCrypto = new ActiveXObject("SLNKDWF.Crypto");
|
||
var usStart = oSLNKDWF.usTimer;
|
||
var is_hash = oCrypto.hex_pbkdf2(wachtwoord, passsalt, Math.pow(2, workfactor), len);
|
||
var tm = ((oSLNKDWF.usTimer - usStart)/1000).toFixed(1);
|
||
__Log("Calculating hash with workfactor {0} ({1}) took {2} ms".format(workfactor, Math.pow(2, workfactor), tm));
|
||
return is_hash;
|
||
}
|
||
|
||
function otpcodes(otpsecret)
|
||
{
|
||
if (!otpsecret)
|
||
return false;
|
||
var arr = otpsecret.split("$");
|
||
switch (arr[0])
|
||
{
|
||
case "1": // TOTP, formaat 1$30$size$offset$seed
|
||
if (arr.length != 5)
|
||
{
|
||
__Log(arr);
|
||
INTERNAL_ERROR_BADTOTP;
|
||
}
|
||
var result = { tokentime: new Date(),
|
||
otpstep: parseInt(arr[1], 10),
|
||
otpsize: parseInt(arr[2], 10),
|
||
otpoffset: parseInt(arr[3], 10),
|
||
otpseed: arr[4],
|
||
codes: [] };
|
||
//Response.Write("<p>Current time: {0}".format(toISODateTimeString(result.tokentime, true)));
|
||
if (result.otpoffset != 0)
|
||
{
|
||
result.tokentime.setSeconds(result.tokentime.getSeconds() + result.otpoffset)
|
||
//Response.Write("</br>Token time: {0} ({1} s)".format(toISODateTimeString(result.tokentime, true), result.otpoffset));
|
||
}
|
||
var ts = Math.round(result.tokentime.getTime() / 1000);
|
||
var cnt = Math.floor(ts / result.otpstep) ; // Google doet 30 seconde
|
||
result.tokenpassed = (ts / result.otpstep - cnt); // zo ver zijn we al in de periode gevorderd
|
||
__Log("OTP time {0} counter {1}".format(toISODateTimeString(result.tokentime, true), cnt));
|
||
var oCrypto = new ActiveXObject("SLNKDWF.Crypto");
|
||
for (var i = -Math.floor(S("prs_password_otp_window") / result.otpstep); i <= Math.floor(S("prs_password_otp_window") / result.otpstep); i++)
|
||
{
|
||
var onecode = { otpshould : oCrypto.hotp(result.otpseed, cnt + i, result.otpsize),
|
||
dtsfrom: new Date(((cnt + i) * result.otpstep - result.otpoffset) * 1000),
|
||
dtsto: new Date(((cnt + i + 1) * result.otpstep - result.otpoffset)* 1000),
|
||
offset: i * result.otpstep,
|
||
counter: cnt + i
|
||
}
|
||
result.codes.push(onecode);
|
||
}
|
||
|
||
break;
|
||
default:
|
||
INTERNAL_ERROR_BADOTP;
|
||
}
|
||
return result;
|
||
}
|
||
|
||
function testpassword(prs_key, wachtwoord, pmobile)
|
||
{
|
||
if (!wachtwoord)
|
||
return false;
|
||
|
||
var sql = " SELECT prs_perslid_key"
|
||
+ " , prs_perslid_flags"
|
||
+ " , prs_perslid_authenticatie"
|
||
+ " , prs_perslid_authenticatie_exp"
|
||
+ " , prs_perslid_salt"
|
||
+ " , prs_perslid_wachtwoord_hash"
|
||
+ " , prs_perslid_oslogin"
|
||
+ " , prs_perslid_apikey"
|
||
+ " FROM prs_perslid"
|
||
+ " WHERE prs_perslid_key = " + prs_key;
|
||
var oRs = Oracle.Execute(sql);
|
||
|
||
var passsalt = oRs("prs_perslid_salt").Value;
|
||
var passhash = oRs("prs_perslid_wachtwoord_hash").Value;
|
||
var mobauth = oRs("prs_perslid_authenticatie").Value;
|
||
var mobauthexp = new Date(oRs("prs_perslid_authenticatie_exp").Value);
|
||
var apikey = oRs("prs_perslid_apikey").Value;
|
||
var oslogin = oRs("prs_perslid_oslogin").Value;
|
||
oRs.Close();
|
||
|
||
if (pmobile==1 && mobauth) // Mobile 'verzonnen' wachtwoord
|
||
{
|
||
var mobww = wachtwoord.toLowerCase(); // wij sturen lowercase base32
|
||
mobww = mobww.replace(/0/i, 'o'); // 0 / 1 / 8 komen daar niet in voor
|
||
mobww = mobww.replace(/1/i, 'l');
|
||
mobww = mobww.replace(/8/i, 'b');
|
||
if (mobauth == mobww && mobauthexp && new Date() <= mobauthexp)
|
||
{
|
||
return true; // Goed
|
||
}
|
||
__Log("Mobile token check failed");
|
||
// Wel doorgaan met gewoon wachtwoord controle, dat staan we ook toe
|
||
}
|
||
|
||
// geen wachtwoord
|
||
if (!passhash)
|
||
return false;
|
||
|
||
// APItoken wachtwoord 1452611295:0pBCO67Br4Cs7kkm+zszW0JhjlM
|
||
// Voor AAFM#34930 app-authenticatie
|
||
// Aangemaakt in model_persons.inc/fnApiToken
|
||
if (apikey && wachtwoord.match(/[0-9]{10}\:.*/))
|
||
{
|
||
if (protectHMAC.verify(oslogin, wachtwoord, { sleutel: apikey, expire: 60*24*S("fac_apitoken_auth_expire"), relaxed: true }))
|
||
return true;
|
||
}
|
||
|
||
// gewoon wachtwoord
|
||
// Noot: we zouden hier kunnen testen op
|
||
// AND (prs_perslid_wachtwoord_exp IS NULL OR prs_perslid_wachtwoord_exp > SYSDATE)
|
||
// Met een verlopen wachtwoord mag je echter wel inloggen maar wordt je (via common.inc)
|
||
// wel gedwongen daarna je wachtwoord te wijzigen
|
||
var arr = passhash.split("$");
|
||
if (arr.length == 1) // Old style
|
||
{
|
||
var sql = "SELECT 1"
|
||
+ " FROM prs_perslid"
|
||
+ " WHERE prs.testpassword(prs_perslid_key, " + safe.quoted_sql(wachtwoord) + ") = 1"
|
||
+ " AND prs_perslid_key = " + prs_key
|
||
var oRs = Oracle.Execute(sql);
|
||
var found = !oRs.Eof;
|
||
oRs.Close();
|
||
if (!found)
|
||
return false;
|
||
// else TODO upgraden!
|
||
var workfactor = 0;
|
||
}
|
||
else // new style
|
||
{
|
||
switch (arr[0])
|
||
{
|
||
case "1": // PBKDF2, 1$workfactor$hash
|
||
var workfactor = parseInt(arr[1], 10);
|
||
var should_hash = arr[2];
|
||
if (arr.length != 3 ||
|
||
isNaN(workfactor) ||
|
||
should_hash.length < 40) // workfactor 2^24 (16777216) duurt op FACWS001 zo'n 45 seconde
|
||
// Daarom voorlopig limiteren op 2^20 (1048576, zo'n 3 seconde)
|
||
{
|
||
__DoLog(arr);
|
||
INTERNAL_ERROR_BADHASH;
|
||
}
|
||
var is_hash = pbkdf2(wachtwoord, passsalt, workfactor, should_hash.length / 2);
|
||
if (should_hash != is_hash)
|
||
{
|
||
__Log("Hash mismatch {0}<>{1}".format(should_hash, is_hash));
|
||
return false;
|
||
}
|
||
break;
|
||
default:
|
||
__DoLog(arr);
|
||
INTERNAL_ERROR_UNKNOWN_PASSHASH;
|
||
}
|
||
}
|
||
if (workfactor != S("prs_password_hash_factor"))
|
||
setpassword(prs_key, wachtwoord);
|
||
return true;
|
||
}
|
||
|
||
|
||
function setpassword(prs_key, wachtwoord, expired)
|
||
{
|
||
if (S("prs_password_hash_factor") == 0 || !wachtwoord) // Old style
|
||
{
|
||
var sql = "BEGIN prs.setpassword(" + prs_key + ", " + safe.quoted_sql(wachtwoord) + "); END;";
|
||
Oracle.Execute(sql);
|
||
}
|
||
else
|
||
{
|
||
var passsalt = shared.random(32);
|
||
var workfactor = S("prs_password_hash_factor");
|
||
var is_hash = pbkdf2(wachtwoord, passsalt, workfactor);
|
||
var sql = "UPDATE prs_perslid" // Ooit expire op SYSDATE + fac.getsetting ('prs_password_expiration') als die is gevuld
|
||
+ " SET prs_perslid_wachtwoord_exp = " + (expired?"SYSDATE":"NULL")
|
||
+ " , prs_perslid_salt = " + safe.quoted_sql(passsalt)
|
||
+ " , prs_perslid_wachtwoord_hash = " + safe.quoted_sql('1${0}${1}'.format(workfactor, is_hash))
|
||
+ " WHERE prs_perslid_key = " + prs_key;
|
||
Oracle.Execute(sql);
|
||
}
|
||
}
|
||
|
||
function testotp (prs_key, otprequest)
|
||
{
|
||
var sql = " SELECT prs_perslid_otpsecret"
|
||
+ " , prs_perslid_otpcounter"
|
||
+ " FROM prs_perslid"
|
||
+ " WHERE prs_perslid_key = " + prs_key;
|
||
|
||
var oRs = Oracle.Execute(sql);
|
||
|
||
var otpsecret = oRs("prs_perslid_otpsecret").Value;
|
||
var otpcounter = oRs("prs_perslid_otpcounter").Value || -1;
|
||
oRs.Close();
|
||
|
||
return verify_otp(prs_key, otprequest, otpsecret, otpcounter);
|
||
}
|
||
|
||
function verify_otp (prs_key, otprequest, otpsecret, otpcounter)
|
||
{
|
||
var otpresult = otpcodes(otpsecret);
|
||
|
||
__Log("Otprequest: " + otprequest);
|
||
var otp_oke = false;
|
||
if (otprequest.length == otpresult.otpsize && otprequest.match(/^[0-9]*$/)) // quick check exact 6 cijfers
|
||
{
|
||
for (var i = 0; i < otpresult.codes.length && !otp_oke; i++)
|
||
{
|
||
var code = otpresult.codes[i];
|
||
__Log(code);
|
||
if (code.counter > otpcounter) // Hij mag niet eerder toegepast zijn
|
||
{
|
||
var otpshould = code.otpshould;
|
||
if (otprequest == code.otpshould)
|
||
{
|
||
otp_oke = true;
|
||
otpcounter = code.counter; // TODO: We zouden moeten bijwerken
|
||
var sql = "UPDATE prs_perslid"
|
||
+ " SET prs_perslid_otpcounter = " + otpcounter
|
||
+ " WHERE prs_perslid_key = " + prs_key;
|
||
Oracle.Execute(sql);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
if (!otp_oke)
|
||
{
|
||
__Log("OTP check failed");
|
||
__Log(otpresult);
|
||
return false;
|
||
}
|
||
return otp_oke;
|
||
}
|
||
|
||
function getIdentity(username, wachtwoord, params)
|
||
{
|
||
var result = { success: false, fail_reason: L("lcl_login_wrong") };
|
||
params = params || {};
|
||
|
||
if (!username || username == 'undefined')
|
||
return result;
|
||
|
||
if (username.indexOf("\\") > -1)
|
||
username = username.split("\\")[1]; // strip domain name
|
||
|
||
// Brute force protection
|
||
var lockout_name = customerId + "_LOGINATTEMPTS";
|
||
var dtExpire = new Date();
|
||
dtExpire.setMinutes(dtExpire.getMinutes() - S("prs_login_lockout_expire"));
|
||
Application.Lock();
|
||
{
|
||
var lockout = myJSON.parse(Application(lockout_name) || "[]");
|
||
var founddata = null;
|
||
for (var i = 0; i < lockout.length; i++)
|
||
{
|
||
var lockdata = lockout[i];
|
||
if (lockdata.lastdate < dtExpire) // Als laatste fout poging 15 minuten geleden is vergeten we alles
|
||
{
|
||
lockout.splice(i, 1); // verwijderen
|
||
i--;
|
||
continue;
|
||
}
|
||
if (lockdata.username == username.toLowerCase())
|
||
{
|
||
founddata = lockdata;
|
||
lockdata.count ++;
|
||
lockdata.lastdate = new Date();
|
||
}
|
||
}
|
||
if (!founddata)
|
||
{
|
||
founddata = { username: username.toLowerCase(),
|
||
count: 1,
|
||
firstdate: new Date(),
|
||
lastdate: new Date()
|
||
}
|
||
lockout.push(founddata);
|
||
}
|
||
Application(lockout_name) = JSON.stringify(lockout).replace(/\{/g, "\n{");
|
||
}
|
||
Application.UnLock();
|
||
|
||
if (founddata && founddata.count > S("prs_login_attempts"))
|
||
{
|
||
var dtRetry = new Date();
|
||
dtRetry.setMinutes(dtRetry.getMinutes() + S("prs_login_lockout_expire"));
|
||
if (founddata.count == S("prs_login_attempts") + 1 // Alleen eerste keer loggen
|
||
|| Application("otap_environment") == "O") // Maar altijd op OTAP
|
||
{
|
||
__DoLog("Meer ({0}) dan {1} inlogpogingen voor {2}".format(founddata.count, S("prs_login_attempts"), username), "#FF0000");
|
||
}
|
||
result.fail_reason = L("lcl_prs_login_lockout").format(username, toISODateTimeString(dtRetry));
|
||
return result;
|
||
}
|
||
if (founddata && founddata.count > 1)
|
||
{
|
||
var oSLNKDWF = new ActiveXObject("SLNKDWF.About");
|
||
// maximaal 80 seconde slapen, anders ASP-timeout
|
||
var sleepsec = Math.min(80, S("prs_login_lockout_delay") * Math.pow(S("prs_login_lockout_delayfactor"), founddata.count - 1));
|
||
oSLNKDWF.Sleep(1000 * sleepsec);
|
||
}
|
||
|
||
var logins = [];
|
||
if (S("login_use_email"))
|
||
{
|
||
logins.push(" upper(prs_perslid_email) = " + safe.quoted_sql_upper(username));
|
||
}
|
||
else if (getQParam("API", ""))
|
||
{
|
||
logins.push(" prs_perslid_apikey = " + safe.quoted_sql_upper(username, 128));
|
||
wachtwoord = null;
|
||
}
|
||
else
|
||
{
|
||
logins.push(" prs_perslid_oslogin = " + safe.quoted_sql_upper(username, 30));
|
||
logins.push(" prs_perslid_oslogin2 = " + safe.quoted_sql_upper(username, 30));
|
||
}
|
||
var sql = " SELECT prs_perslid_key, "
|
||
+ " prs_perslid_flags"
|
||
+ " , prs_perslid_otpsecret"
|
||
+ " , prs_perslid_otpcounter"
|
||
+ " FROM prs_perslid"
|
||
+ " WHERE prs_perslid_verwijder IS NULL"
|
||
+ " AND (" + logins.join(" OR ") + ")"
|
||
+ " AND BITAND(prs_perslid_flags, 1+4+8) = 0"; // 2==unconfirmed staan we nog heel even toe
|
||
|
||
var oRs = Oracle.Execute(sql);
|
||
if (oRs.Eof) // Gebruikersnaam niet eens gevonden
|
||
{ // Wel ongeveer lengte van een passwordhash vertragen
|
||
var oSLNKDWF = new ActiveXObject("SLNKDWF.About");
|
||
var oCrypto = new ActiveXObject("SLNKDWF.Crypto");
|
||
var workfactor = S("prs_password_hash_factor");
|
||
var usStart = oSLNKDWF.usTimer;
|
||
var test_hash = oCrypto.hex_pbkdf2("password", "salt", Math.pow(2, workfactor - 5), 20); // 1/32e van een echt wachtwoord als test
|
||
var tmicro = oSLNKDWF.usTimer - usStart;
|
||
var oSLNKDWF = new ActiveXObject("SLNKDWF.About");
|
||
var sleepmsec = Math.min(80000, tmicro / 1000 * 32);
|
||
oSLNKDWF.Sleep(sleepmsec);
|
||
return result;
|
||
}
|
||
|
||
var otpsecret = oRs("prs_perslid_otpsecret").Value;
|
||
var otpcounter = oRs("prs_perslid_otpcounter").Value || -1;
|
||
|
||
var found = false;
|
||
if (/* nog niet vanuit SAML/default.asp params.noPassword && */ wachtwoord == null) // SSO
|
||
found = true; // En zijn we verder wel klaar
|
||
else
|
||
found = testpassword(oRs("prs_perslid_key").Value, wachtwoord, params.mobile);
|
||
|
||
if (!found)
|
||
return result;
|
||
|
||
if ((oRs("prs_perslid_flags").Value & 2) == 2)
|
||
{
|
||
result.fail_reason = L("lcl_self_register_unconfirmed");
|
||
return result;
|
||
}
|
||
|
||
if (!otpsecret || !wachtwoord)
|
||
{
|
||
var deze = new Perslid(oRs("prs_perslid_key").Value);
|
||
if (wachtwoord && deze.has("WEB_FACFAC"))
|
||
{
|
||
result.fail_reason = L("lcl_login_needs_otpsecret");
|
||
return result;
|
||
}
|
||
result = { success: true, user_key: oRs("prs_perslid_key").Value };
|
||
}
|
||
else if (params.otpcode && testotp(oRs("prs_perslid_key").Value, params.otpcode))
|
||
result = { success: true, user_key: oRs("prs_perslid_key").Value };
|
||
else // Wordt opgepikt door login_save.asp
|
||
result = { success: true, otp_user_key: oRs("prs_perslid_key").Value };
|
||
|
||
oRs.Close();
|
||
|
||
if (result.user_key > 0)
|
||
{ // Success! Wis eventuele lockout
|
||
Application.Lock();
|
||
var lockout = myJSON.parse(Application(lockout_name) || "[]");
|
||
for (var i = 0; i < lockout.length; i++)
|
||
{
|
||
var lockdata = lockout[i];
|
||
if (lockdata.username == username.toLowerCase())
|
||
{
|
||
lockout.splice(i, 1); // verwijderen
|
||
i--;
|
||
}
|
||
}
|
||
Application(lockout_name) = JSON.stringify(lockout);
|
||
Application.UnLock();
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
//
|
||
// zet Session("user_key") als username en wachtwoord geldig zijn.
|
||
// Login na verzending via sms moet binnen 1 kwartier ingevuld zijn.
|
||
// resultaat: true bij succesvolle login, false bij niet succesvol
|
||
// drie username opties:
|
||
// - prs_perslid_oslogin
|
||
// - prs_perslid_oslogin2
|
||
// - upper(prs_perslid_email)
|
||
// drie wachtwoord opties
|
||
// - leeg (single-signon)
|
||
// - prs_perslid_wachtwoord (of eigenlijk: prs_perslid_salt en prs_perslid_wachtwoord_hash)
|
||
// - prs_perslid_authenticatie (en prs_perslid_authenticatie_exp > sysdate)
|
||
// de laatste wordt gebruikt voor mobile/SMS
|
||
|
||
function tryLogin(username, wachtwoord, params)
|
||
{
|
||
params = params || {};
|
||
Session.Contents.Remove("org_user_key");
|
||
|
||
var ident = getIdentity(username, wachtwoord, params);
|
||
if (!ident.success)
|
||
return false;
|
||
|
||
if (ident.user_key > 0)
|
||
doLogin(ident.user_key, params);
|
||
|
||
return true;
|
||
}
|
||
|
||
// function SecureSSO
|
||
// Verzorgt de secure Single Signon communicatie protocol
|
||
//
|
||
// ssoProps
|
||
// strSharedKey: afgesproken shared key
|
||
// onSuccess: functie die aangeroepen wordt bij success
|
||
// We kunnen hier nog via twee routes komen: oude stijl (cust/xxxx/sso.asp)
|
||
// en nieuwe stijl (xxxx.facilitor.nl?sso=1)
|
||
// In het laatste geval zal ssoProps.sso ook 1 of 2 zijn
|
||
function SecureSSO(ssoProps)
|
||
{
|
||
var strAction, strReturnURL, strKey, strGUID, strCTID
|
||
var strUserName, strDecryptedCode, strControlID, strControlDecryptedCode, strLengthCode
|
||
//'* variables *******************************************************
|
||
//'*******************************************************************
|
||
Response.Buffer=true
|
||
%>
|
||
<HTML>
|
||
<HEAD>
|
||
<script type="text/javascript">
|
||
function fnSubmit() {
|
||
window.document.form.submit();
|
||
return;
|
||
}
|
||
</SCRIPT>
|
||
</HEAD>
|
||
<%
|
||
strReturnURL = getFParam("returnurl", "");
|
||
strReturnURL= strReturnURL.replace("<", "");
|
||
strReturnURL= strReturnURL.replace(">", "");
|
||
strAction = getFParam("action", "");
|
||
if (!strAction && ssoProps.ssoURL) // we zijn begonnen in Facilitor en moeten nog naar de klant
|
||
{
|
||
strReturnURL = ssoProps.ssoURL;
|
||
if (!strReturnURL)
|
||
{
|
||
__DoLog("Secure SSO login error 0");
|
||
Response.Write("Foute aanroep");
|
||
Response.End;
|
||
}
|
||
strAction = "requestid";
|
||
Session("SSO_QUERYSTRING") = String(Request.ServerVariables("QUERY_STRING")); // Deze onthouden we
|
||
Session("SSO_URL") = String(Request.ServerVariables("URL")); // Deze onthouden we
|
||
}
|
||
|
||
if (strAction == "requestid")
|
||
{
|
||
// * action = requestid *******************************************
|
||
%>
|
||
<BODY LANGUAGE="javascript" onload="return fnSubmit()">
|
||
<%
|
||
if (strReturnURL == "")
|
||
{
|
||
__DoLog("Secure SSO login error 1");
|
||
Response.write("Error: onvoldoende informatie ontvangen.")
|
||
Response.end
|
||
}
|
||
else
|
||
{
|
||
Response.write("Een moment aub..")
|
||
strGUID = GetGuid(64)
|
||
strCTID = GetGuid(strReturnURL.length)
|
||
// Save GUID
|
||
Session("GUID") = strGUID;
|
||
Session("CTID") = strCTID;
|
||
Session("GUIDEXPIRE") = (new Date()).valueOf()
|
||
+ (ssoProps.Timeout?ssoProps.Timeout:30)*1000;
|
||
if (Request.Form("Jumpto").Count>0) // Remember it (old style)
|
||
{
|
||
Session("FirstPage")=""+Request.Form("Jumpto")
|
||
}
|
||
%>
|
||
<form action='<%=strReturnURL%>' method="post" name="form" ID="Form1">
|
||
<input type="hidden" name="guid" value="<%=strGUID%>" ID="Hidden1">
|
||
<input type="hidden" name="ctid" value="<%=strCTID%>" ID="Hidden2">
|
||
<%
|
||
}
|
||
}
|
||
else if (strAction == "processcode")
|
||
{
|
||
// * action = processcode *****************************************
|
||
%>
|
||
<BODY>
|
||
<%
|
||
|
||
strUserName = String(Request.form("code"))
|
||
strControlID = String(Request.form("ctcode"))
|
||
strLengthCode = Request.form("ltcode")
|
||
strGUID = Session("GUID")
|
||
strCTID = Session("CTID")
|
||
var expire = Session("GUIDEXPIRE");
|
||
// Clean session memory
|
||
Session.Contents.Remove("GUID");
|
||
Session.Contents.Remove("CTID");
|
||
Session.Contents.Remove("GUIDEXPIRE");
|
||
if (typeof expire == "undefined" || !expire || (new Date()).valueOf() > expire ||
|
||
typeof strGUID == "undefined" || strUserName == "" || typeof strCTID == "undefined" || strControlID == "")
|
||
{
|
||
Session.Contents.Remove("FirstPage");
|
||
if (strReturnURL == "")
|
||
{
|
||
__DoLog("Secure SSO login error 2");
|
||
Response.write("Error: onvoldoende informatie ontvangen.")
|
||
Response.end
|
||
}
|
||
else
|
||
{
|
||
// FSN#25537 deze komt erg regelmatig voor maar oorzaak onbekend
|
||
__Log("Secure SSO login error 3");
|
||
Response.write("Error: onvoldoende informatie ontvangen.")
|
||
Response.End;
|
||
// Response.redirect(strReturnURL) kan oneindige loop geven
|
||
}
|
||
}
|
||
// Convert from ASC chars
|
||
strUserName = ConvertFromAsc(strUserName)
|
||
strControlID = ConvertFromAsc(strControlID)
|
||
// * decrypt ******************************************************
|
||
//First decoding phase
|
||
var strKey = (ssoProps.strSharedKey + strGUID).substr(0,strUserName.length);
|
||
strDecryptedCode = DeCrypt(strUserName)
|
||
//Second decoding phase
|
||
var strKey = strGUID.substr(0,strDecryptedCode.length);
|
||
strDecryptedCode = DeCrypt(strDecryptedCode)
|
||
// * decrypt Controlkey ********************************************
|
||
// First decoding phase
|
||
strKey = (ssoProps.strSharedKey + strCTID).substr(0,strControlID.length)
|
||
strControlDecryptedCode = DeCrypt(strControlID)
|
||
// Second decoding phase
|
||
strKey = strCTID.substr(0,strControlDecryptedCode.length)
|
||
strControlDecryptedCode = DeCrypt(strControlDecryptedCode)
|
||
// ltcode strLengthCode
|
||
if (strControlDecryptedCode == strReturnURL && parseInt(strLengthCode,10) == strDecryptedCode.length)
|
||
{
|
||
// For the ASP: User is authenticated, strDecryptedCode contains the validated Domain\Username
|
||
__Log("SSO Gebruikersnaam = " + strDecryptedCode)
|
||
if (ssoProps.fnparseName)
|
||
{
|
||
strDecryptedCode = ssoProps.fnparseName(strDecryptedCode)
|
||
//Response.write ("<p>Na fnparseName: " + strDecryptedCode)
|
||
}
|
||
if (tryLogin(strDecryptedCode, null, { noPassword: true }))
|
||
{
|
||
if (ssoProps.fnonSuccess)
|
||
ssoProps.fnonSuccess(user_key);
|
||
else // Alles goed!
|
||
{
|
||
var sso_qs = Session("SSO_QUERYSTRING")||"";
|
||
var sso_url = Session("SSO_URL")||"/default.asp";
|
||
Session.Contents.Remove("SSO_QUERYSTRING");
|
||
Session.Contents.Remove("SSO_URL");
|
||
Response.Redirect(sso_url + (sso_qs?"?":"") + sso_qs);
|
||
}
|
||
}
|
||
else
|
||
{ // Automatisch naar het inlogscherm
|
||
__DoLog("Secure SSO login niet gevonden binnen Facilitor: " + strDecryptedCode);
|
||
Response.Redirect(rooturl + "/default.asp");
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if (strReturnURL == "")
|
||
{
|
||
__DoLog("Secure SSO login error 4");
|
||
Response.write("Error: onvoldoende informatie ontvangen.")
|
||
Response.end
|
||
}
|
||
else
|
||
{
|
||
__Log("Secure SSO login error 5");
|
||
Response.Write("Decodeer fout");
|
||
Response.End;
|
||
Response.redirect(strReturnURL)
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
__Log("Secure SSO login error 6");
|
||
Response.Write("Foute aanroep");
|
||
Response.End;
|
||
}
|
||
// * Functions ********************************************************
|
||
function ConvertFromAsc(strAsc)
|
||
{
|
||
var iCount
|
||
var iChars
|
||
var sConvertFromAsc = ""
|
||
iCount = 0
|
||
do
|
||
{
|
||
iChars = parseInt(strAsc.substr(iCount,1))
|
||
iCount = iCount + 1
|
||
sConvertFromAsc = sConvertFromAsc + String.fromCharCode(parseInt(strAsc.substr(iCount,iChars)))
|
||
iCount = iCount + iChars
|
||
} while (iCount < strAsc.length);
|
||
return sConvertFromAsc;
|
||
}
|
||
|
||
function GetGuid(iDigits)
|
||
{
|
||
var lsGUID
|
||
var lsTemp
|
||
var TypeLib = Server.CreateObject("Scriptlet.TypeLib")
|
||
var lsTemp = ""
|
||
do
|
||
{
|
||
lsGUID = String(TypeLib.Guid).substr(0, 38)
|
||
lsTemp = lsTemp + lsGUID.substr(1,8) + lsGUID.substr(10,4) + lsGUID.substr(15,4) + lsGUID.substr(20,4) + lsGUID.substr(25,12)
|
||
} while (lsTemp.length < iDigits)
|
||
TypeLib = null
|
||
return lsTemp.substr(0,iDigits);
|
||
}
|
||
|
||
function DeCrypt(strEncrypted)
|
||
{
|
||
var strChar, iKeyChar, iStringChar, i
|
||
var strDecrypted = "";
|
||
for (i=0; i<strEncrypted.length; i++)
|
||
{
|
||
iKeyChar = strKey.charCodeAt(i);
|
||
iStringChar = strEncrypted.charCodeAt(i);
|
||
iDeCryptChar = iStringChar ^ iKeyChar
|
||
strDecrypted = strDecrypted + String.fromCharCode(iDeCryptChar);
|
||
}
|
||
return strDecrypted;
|
||
}
|
||
// *********************************************************************
|
||
%>
|
||
</form>
|
||
</BODY>
|
||
</HTML>
|
||
<%
|
||
Response.End;
|
||
}
|
||
|
||
var base64s = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||
|
||
function decode_b64(encStr) {
|
||
var bits, decOut = '', i = 0;
|
||
for(; i<encStr.length; i += 4){
|
||
bits =
|
||
(base64s.indexOf(encStr.charAt(i)) & 0xff) <<18 |
|
||
(base64s.indexOf(encStr.charAt(i +1)) & 0xff) <<12 |
|
||
(base64s.indexOf(encStr.charAt(i +2)) & 0xff) << 6 |
|
||
base64s.indexOf(encStr.charAt(i +3)) & 0xff;
|
||
decOut += String.fromCharCode(
|
||
(bits & 0xff0000) >>16, (bits & 0xff00) >>8, bits & 0xff);
|
||
}
|
||
if(encStr.charCodeAt(i -2) == 61)
|
||
undecOut=decOut.substring(0, decOut.length -2);
|
||
else if(encStr.charCodeAt(i -1) == 61)
|
||
undecOut=decOut.substring(0, decOut.length -1);
|
||
else undecOut=decOut;
|
||
|
||
return unescape(undecOut); //line add for chinese char
|
||
}
|
||
|
||
function SimpleSSO()
|
||
{
|
||
if (S("use_simple_sso") == 0)
|
||
return;
|
||
|
||
var username = String(Session("UID_DEC"));
|
||
__Log('User#1 = '+username);
|
||
// facilitorplace SSO decoded/descripted login?
|
||
// Bij decoded login moet de setting "S("use_simple_sso")" aan staan
|
||
if (username != '' && username!='undefined')
|
||
{
|
||
username = decode_b64(username);
|
||
Session.Contents.Remove("UID_DEC"); // nooit twee keer
|
||
__Log('User#2a = '+username);
|
||
}
|
||
if (username !='' && username!='undefined') {
|
||
// Strip domain name
|
||
while( (i = username.indexOf('\\')) >= 0 ) {
|
||
l = username.length;
|
||
if( i < l-1 ) username = username.substring(i+1,l);
|
||
}
|
||
tryLogin(username, null, { noPassword: true });
|
||
}
|
||
}
|
||
|
||
function IntegratedSSO()
|
||
{
|
||
var username = String( Request.ServerVariables("REMOTE_USER") ).toUpperCase();
|
||
__Log('REMOTE_USER = '+username);
|
||
if (username =='' || username=='UNDEFINED')
|
||
{
|
||
username = String( Request.ServerVariables("HTTP_USER") ).toUpperCase();
|
||
__Log('HTTP_USER = '+username);
|
||
if (username =='' || username=='UNDEFINED')
|
||
{
|
||
username = String( Request.ServerVariables("HTTP_LOGIN") ).toUpperCase();
|
||
__Log('HTTP_LOGIN = '+username);
|
||
if (username =='' || username=='UNDEFINED')
|
||
{
|
||
// HTTP_LOGIN, REMOTE_USER or HTTP_USER is not (yet) set. Forcing Windows Authentication
|
||
Response.Status = "401 Unauthorized";
|
||
shared.simpel_page("os_logon is set, trying 401 Unautorized<br>If this page stays, check IIS if integrated authentication is turned on.");
|
||
Response.End(); // Reloads current file
|
||
}
|
||
}
|
||
}
|
||
if (username !='' && username!='UNDEFINED')
|
||
{
|
||
// Strip domain name
|
||
while( (i = username.indexOf('\\')) >= 0 ) {
|
||
l = username.length;
|
||
if( i < l-1 ) username = username.substring(i+1,l);
|
||
}
|
||
tryLogin(username, null, { noPassword: true });
|
||
}
|
||
}
|
||
|
||
// aud is identificatie van de partij die de informatie *wil weten*
|
||
function jwt_create(perslid_key, aud)
|
||
{
|
||
var thisPrs = new Perslid(perslid_key)
|
||
|
||
var sp_key = -1;
|
||
|
||
var sql = "SELECT *"
|
||
+ " FROM fac_sp"
|
||
+ " WHERE fac_sp_audience = " + safe.quoted_sql(aud); // TODO ook issuer meenemen?
|
||
var oRs = Oracle.Execute(sql);
|
||
if (oRs.Eof)
|
||
shared.internal_error("Service provider for '{0}' is not configured for {1}".format(safe.html(aud), customerId));
|
||
sp_key = oRs("fac_sp_key").value;
|
||
var params = {
|
||
usermapping: oRs("fac_sp_usermapping").Value,
|
||
iss: oRs("fac_sp_issuer").Value,
|
||
aud: aud,
|
||
secret: oRs("fac_sp_secret").Value
|
||
};
|
||
oRs.Close();
|
||
|
||
var claim =
|
||
{
|
||
jti: "#" + (perslid_key) + "#" + new Date().getTime(),
|
||
iat: Math.round(new Date().getTime() / 1000),
|
||
iss: params.iss,
|
||
aud: params.aud
|
||
}
|
||
|
||
// fac_sp_map bevat de attributen die wij vrijgeven voor deze SP
|
||
|
||
if (sp_key > 0)
|
||
{
|
||
var sql = "SELECT *"
|
||
+ " FROM fac_sp_map"
|
||
+ " WHERE fac_sp_key = " + sp_key;
|
||
var oRs = Oracle.Execute(sql);
|
||
while (!oRs.Eof)
|
||
{
|
||
var clm = oRs("fac_sp_map_to").Value; // zo gaat hij heten in de JWT
|
||
switch (oRs("fac_sp_map_from").Value) // zie model_fac_sp_map.inc voor codering
|
||
{
|
||
// Zo veel mogelijk volgens http://openid.net/specs/openid-connect-basic-1_0.html#StandardClaims
|
||
case 1: claim[clm] = thisPrs.oslogin();
|
||
break;
|
||
case "sub": claim[clm] = String(perslid_key); // Als enige gegarandeerd en blijvend uniek voor deze issuer
|
||
break; // http://openid.net/specs/openid-connect-basic-1_0.html#ClaimStability
|
||
case 3: claim[clm] = thisPrs.prs_perslid_voornaam();
|
||
break;
|
||
case 2: claim[clm] = thisPrs.prs_perslid_achternaam();
|
||
break;
|
||
// case "middle_name": claim[clm] = thisPrs.prs_perslid_tussenvoegsel();
|
||
// break;
|
||
// case "name": claim[clm] = thisPrs.naam();
|
||
// break;
|
||
case 9: claim[clm] = thisPrs.prs_perslid_email();
|
||
break;
|
||
// case "gender": claim[clm] = { "0": "female", "1": "male" }[thisPrs.prs_perslid_geslacht()] || "";
|
||
// break;
|
||
case "locale": claim[clm] = { "nl": "nl-NL", // RFC5646
|
||
"en": "en-GB",
|
||
"de": "de-DE",
|
||
"fr": "fr-FR",
|
||
"no": "nn-NO",
|
||
"sv": "sv-SE",
|
||
"da": "da-DK",
|
||
"fi": "fi-FI" }[String(thisPrs.dblang()).toLowerCase()] || "";
|
||
break;
|
||
// case "zoneinfo": claim[clm] = thisPrs.prs_perslid_timezone();
|
||
// break;
|
||
// de custom claims
|
||
case 100: /* fclt_authorization */
|
||
var sql = "SELECT fac_groep_omschrijving"
|
||
+ " FROM fac_gebruikersgroep fgg, fac_groep fg"
|
||
+ " WHERE fgg.fac_groep_key = fg.fac_groep_key"
|
||
+ " AND SUBSTR(fac_groep_omschrijving, 1, 1) <> '_'"
|
||
+ " AND prs_perslid_key = " + user_key;
|
||
var oRs2 = Oracle.Execute(sql);
|
||
var aarr = [];
|
||
while (!oRs2.Eof)
|
||
{
|
||
aarr.push(oRs2("fac_groep_omschrijving").value);
|
||
oRs2.MoveNext();
|
||
}
|
||
oRs2.Close();
|
||
claim[clm] = aarr.join("|");
|
||
break;
|
||
case 101: /* fclt_occupation */
|
||
var wps = user.werkplekken();
|
||
var warr = [];
|
||
for (var i2 = 0; i2 < wps.length; i2++)
|
||
warr.push(wps[i2].prs_werkplek_aanduiding());
|
||
claim[clm] = warr.join("|");
|
||
break;
|
||
// case 102 virtuele ondersteunen we nog niet
|
||
}
|
||
oRs.MoveNext();
|
||
}
|
||
oRs.Close();
|
||
}
|
||
__Log(claim);
|
||
return jwt_encode(claim, params.secret);
|
||
}
|
||
|
||
function jwt_encode(claim, secret)
|
||
{
|
||
var header =
|
||
{
|
||
"typ":"JWT",
|
||
"alg":"HS256"
|
||
};
|
||
|
||
claim.ver = "1.0";
|
||
var oCrypto = new ActiveXObject("SLNKDWF.Crypto");
|
||
|
||
var sheader = oCrypto.base64(JSON.stringify(header, false)); // no padding
|
||
var sclaim = oCrypto.base64(JSON.stringify(claim, false));
|
||
var signature = oCrypto.hex_hmac_sha256(secret, sheader + "." + sclaim);
|
||
var sig64 = oCrypto.hex2base64(signature, false, true); // no padding, urlsafe
|
||
var jwt = sheader + "." + sclaim + "." + sig64;
|
||
|
||
return jwt;
|
||
}
|
||
|
||
function jwt_decode(token)
|
||
{
|
||
// check token
|
||
if (!token) {
|
||
return { err: 'No token supplied' };
|
||
}
|
||
// check segments
|
||
var segments = token.split('.');
|
||
if (segments.length !== 3) {
|
||
return { err: 'Not enough or too many segments' };
|
||
}
|
||
|
||
// All segment should be base64
|
||
var result = { headerSeg: segments[0],
|
||
payloadSeg: segments[1],
|
||
signature64: segments[2]
|
||
}
|
||
|
||
var oCrypto = new ActiveXObject("SLNKDWF.Crypto");
|
||
|
||
try
|
||
{
|
||
// base64 decode and parse JSON
|
||
|
||
// FSN#39763 SLNKDWF.DLL v4.16 heeft nog een bug in het decoderen van
|
||
// url-safe encoded teksten. Daarom hier voor-corrigeren
|
||
result.headerSeg = result.headerSeg.replace(/\-/g, "+").replace(/\_/g, "/");
|
||
result.payloadSeg = result.payloadSeg.replace(/\-/g, "+").replace(/\_/g, "/");
|
||
|
||
result.header = JSON.parse(oCrypto.base64_decode(result.headerSeg));
|
||
result.payload = JSON.parse(oCrypto.base64_decode(result.payloadSeg));
|
||
}
|
||
catch (s)
|
||
{
|
||
return { err: "Invalid JSON: {0} {1}".format(s.name, s.message) };
|
||
}
|
||
|
||
return result;
|
||
};
|
||
|
||
function jwt_verify(decoded_jwt, secret, skew, duration)
|
||
{
|
||
skew = skew || 0;
|
||
duration = duration || 0;
|
||
if (decoded_jwt.header.alg != "HS256")
|
||
return { err: "Only HS256 is supported" };
|
||
|
||
var oCrypto = new ActiveXObject("SLNKDWF.Crypto");
|
||
var sig = oCrypto.hex_hmac_sha256(secret, decoded_jwt.headerSeg + "." + decoded_jwt.payloadSeg);
|
||
var sig64 = oCrypto.hex2base64(sig, false, true); // no padding, urlsafe
|
||
|
||
var now = new Date().getTime() / 1000;
|
||
if (claim.payload.iat)
|
||
{
|
||
// Support for nbf and exp claims.
|
||
// According to the RFC, they should be in seconds.
|
||
if (claim.payload.nbf && now + skew < claim.payload.nbf ) {
|
||
return { err: 'Token not yet active' };
|
||
}
|
||
|
||
if (claim.payload.exp && now > claim.payload.exp + skew) {
|
||
return { err: 'Token expired' };
|
||
}
|
||
|
||
// Onze eigen duration/expiration controleren we ook nog
|
||
if (claim.payload.iat + duration < now - skew) {
|
||
__DoLog("Token expired. Now is {0}, got {1}, skew {2}".format(toISODateTimeString(new Date(now * 1000), true),
|
||
toISODateTimeString(new Date(claim.payload.iat * 1000), true),
|
||
skew));
|
||
return { err: 'Token expired' };
|
||
}
|
||
if (claim.payload.iat > now + skew) {
|
||
__DoLog("Token not yet active. Now is {0}, got {1}, skew {2}".format(toISODateTimeString(new Date(now * 1000), true),
|
||
toISODateTimeString(new Date(claim.payload.iat * 1000), true),
|
||
skew));
|
||
return { err: 'Token not yet active' };
|
||
}
|
||
}
|
||
if (decoded_jwt.signature64 == sig64)
|
||
return { success: true }
|
||
|
||
return { err: "Token signature did not verify" };
|
||
}
|
||
|
||
function trySSO(ssocode)
|
||
{
|
||
var sql = "SELECT *"
|
||
+ " FROM aut_idp"
|
||
+ " WHERE aut_idp_code = " + safe.quoted_sql_upper(ssocode); // een trigger zorgt dat aut_idp_code uppercase is
|
||
var oRs = Oracle.Execute(sql);
|
||
if (oRs.Eof)
|
||
shared.internal_error("Identity provider '{0}' is not configured for {1}".format(safe.html(ssocode), customerId));
|
||
|
||
var isFACFACinternal = oRs("aut_idp_internal").Value != 0;
|
||
var ip_restrict = oRs("aut_idp_ipfilter").Value;
|
||
if (isFACFACinternal && S("idp_internal_anyuser"))
|
||
ip_restrict = ""; // dan niet al te moeilijk doen
|
||
var ip_ok = true;
|
||
if (ip_restrict) // Application("otap_environment") != "O")
|
||
{
|
||
var ip = String(Request.ServerVariables("REMOTE_ADDR"));
|
||
if (ip != '::1' && ip != '127.0.0.1') // local access always ok
|
||
{
|
||
ip_ok = IP.inAnySubnet(ip, ip_restrict);
|
||
__Log("SSO IP-restrictie {0} versus remote {1}: {2}".format(ip_restrict, ip, ip_ok));
|
||
if (!ip_ok)
|
||
{ // Always allow Private networks (RFC 1918)
|
||
ip_ok = IP.inAnySubnet(ip, "192.168.0.0/16,172.16.0.0/12,10.0.0.0/8");
|
||
}
|
||
}
|
||
}
|
||
|
||
if (!ip_ok)
|
||
shared.internal_error("IP {0} not allowed for IDP '{0}'".format(ip, ssocode)); // TODO of 400 code forbidden?
|
||
|
||
var return_to = String(Request.ServerVariables("URL")).substr(rooturl.length) + "?" + String(Request.ServerVariables("QUERY_STRING"));
|
||
return_to = return_to.replace(/^\/default.asp/i, "/"); // default.asp vooraan hoeft niet, ik wil cleane url
|
||
|
||
if (oRs("aut_idp_type").Value == 3) // Oldstyle SecureSSO, die doet het verder zelf
|
||
{
|
||
SecureSSO({ strSharedKey: oRs("aut_idp_secret").Value,
|
||
Timeout: oRs("aut_idp_clockskew").Value,
|
||
ssoURL: oRs("aut_idp_remote_loginurl").Value,
|
||
sso: ssocode
|
||
});
|
||
/* keert niet terug */
|
||
}
|
||
else if (oRs("aut_idp_type").Value == 4) // JWT
|
||
{
|
||
var audience = oRs("aut_idp_audience").Value;
|
||
var issuer = oRs("aut_idp_issuer").Value;
|
||
var url = oRs("aut_idp_remote_loginurl").Value;
|
||
if (!url) // regulier bij Logcenter-sso CUSTOMER als gebruiker (nog) niet bekend is
|
||
shared.internal_error("User unknown and Identity Provider '{0}' has no login url".format(ssocode));
|
||
if (url.indexOf("://") < 0) // geen protocol?
|
||
url = HTTP.urlzelf() + "/" + url;
|
||
url += (url.indexOf("?")>=0?"&":"?") + "aud=" + safe.url(audience) + "&iss=" + safe.url(issuer) ;
|
||
var redirect_uri = HTTP.urlzelf() + "/"; // TODO: /appl/aut/jwt is misschien logischer als 'endpoint'
|
||
url += "&redirect_uri={0}&return_to={1}".format(safe.url(redirect_uri), safe.url(return_to));
|
||
}
|
||
else if (oRs("aut_idp_type").Value == 5) // SAML
|
||
{
|
||
// var url = oRs("aut_idp_remote_loginurl").Value; // /Shibboleth.sso/Login?entityID=https://idp.testshib.org/idp/shibboleth
|
||
// Je moet de entityId in de issuer invullen
|
||
var url = "/Shibboleth.sso/Login?entityID={0}".format(safe.url(oRs("aut_idp_issuer").Value))
|
||
// shib kent geen return_to parameter dus zelf maar in redirect_uri verwerken
|
||
var redirect_uri = HTTP.urlzelf() + "/appl/aut/saml/?return_to={0}&fac_id={1}".format(safe.url(return_to), customerId);
|
||
url += "&target={0}".format(safe.url(redirect_uri));
|
||
}
|
||
else
|
||
shared.internal_error("IDP '{0}' type {1} not supported yes.".format(params.idp_code, oRs("aut_idp_type").Value));
|
||
oRs.Close();
|
||
Response.Redirect(url); // die stuurt ons wel terug
|
||
Response.End;
|
||
}
|
||
|
||
// idp_data is inclusief include idpmappings
|
||
// Er is elders vastgesteld dat 'claim' geldige informatie over een gebruiker bevat.
|
||
// Verwerk dat nu: probeer in te loggen als de gebruiker en/of maak hem eventueel aan
|
||
function process_claim(claim, idp_data, params)
|
||
{
|
||
__Log("Entering process_claim")
|
||
__Log(claim);
|
||
|
||
params = params || {};
|
||
var hasIdentify = false;
|
||
var isFACFACinternal = idp_data.internal != 0;
|
||
for (var i =0; i < idp_data.idpmappings.length; i++)
|
||
{
|
||
var idpm = idp_data.idpmappings[i];
|
||
if (idpm.identify != 1)
|
||
continue;
|
||
hasIdentify = true;
|
||
if (!claim[idpm.from]) // niet meegegeven
|
||
continue;
|
||
switch (idpm.name.id)
|
||
{
|
||
case 1: // login
|
||
settings.overrule_setting("login_use_email", 0);
|
||
tryLogin(claim[idpm.from], null, { noPassword: true, idp_code: idp_data.code, noFacSession: params.by_bearer, isFACFACinternal: isFACFACinternal });
|
||
break;
|
||
case 9: // email
|
||
settings.overrule_setting("login_use_email", 1);
|
||
tryLogin(claim[idpm.from], null, { noPassword: true, idp_code: idp_data.code, noFacSession: params.by_bearer, isFACFACinternal: isFACFACinternal });
|
||
break;
|
||
case 99: // internal, prs_perslid_key
|
||
doLogin(parseInt(claim[idpm.from], 10), { noFacSession: params.by_bearer, idp_code: idp_data.code, isFACFACinternal: isFACFACinternal });
|
||
break;
|
||
default:
|
||
if (idpm.name.id > 1000) // Flexkenmerk
|
||
{
|
||
var kenmerk_key = idpm.name.id - 1000;
|
||
var sql = "SELECT pp.prs_perslid_key"
|
||
+ " FROM prs_perslid pp"
|
||
+ " , prs_kenmerklink pkl"
|
||
+ " WHERE pp.prs_perslid_key = pkl.prs_link_key"
|
||
+ " AND prs_perslid_verwijder IS NULL"
|
||
+ " AND pkl.prs_kenmerklink_niveau = 'P'"
|
||
+ " AND pkl.prs_kenmerk_key = " + kenmerk_key
|
||
+ " AND pkl.prs_kenmerklink_waarde = " + safe.quoted_sql(claim[idpm.from])
|
||
var oRs = Oracle.Execute(sql);
|
||
if (!oRs.Eof)
|
||
{
|
||
doLogin(oRs("prs_perslid_key").Value, { noFacSession: params.by_bearer, idp_code: idp_data.code, isFACFACinternal: isFACFACinternal });
|
||
}
|
||
oRs.Close();
|
||
}
|
||
}
|
||
if (user_key < 0)
|
||
__DoLog("Claimed {0} not found as {1}: {2}".format(idpm.name.name, idpm.from, claim[idpm.from]));
|
||
else
|
||
break; // ingelogd, niet verder zoeken
|
||
}
|
||
if (!hasIdentify)
|
||
shared.internal_error("IdP {0} has no identifying attribute defined.".format(idp_data.code));
|
||
|
||
if ( user_key < 0 && idp_data.autocreate.id & 1 // Misschien dan maar aanmaken?
|
||
|| user_key > 0 && idp_data.autocreate.id & 2 // en/ of bijwerken
|
||
)
|
||
{
|
||
var persdata = { };
|
||
for (var i =0; i < idp_data.idpmappings.length; i++)
|
||
{
|
||
var idpm = idp_data.idpmappings[i];
|
||
var val = idpm["default"];
|
||
if (idpm.from in claim)
|
||
val = claim[idpm.from];
|
||
|
||
switch (idpm.name.id) // zie model_aut_idp_map.inc voor codering
|
||
{
|
||
case 1: persdata["login"] = val; break;
|
||
case 2: persdata["lastname"] = val; break;
|
||
case 3: persdata["firstname"] = val; break;
|
||
case 4: persdata["prefix"] = val; break;
|
||
case 5: persdata["initials"] = val; break;
|
||
case 6: persdata["gender"] = val; break;
|
||
case 7: persdata["phone"] = val; break;
|
||
case 8: persdata["title"] = val; break;
|
||
case 9: persdata["email"] = val; break;
|
||
case 10: persdata["phone"] = val; break;
|
||
case 11: persdata["mobile"] = val; break;
|
||
// de foreigns
|
||
case 20: if (val)
|
||
persdata["function"] = { name: val }; break; // Kan omdat fields.function.desc_is_unique is gezet
|
||
case 21: if (val) // afdeling
|
||
{
|
||
// Zoek afdeling
|
||
var sql = "SELECT prs_afdeling_key"
|
||
+ " FROM prs_v_afdeling"
|
||
+ " WHERE prs_afdeling_verwijder IS NULL"
|
||
+ " AND prs_afdeling_upper = " + safe.quoted_sql_upper(val);
|
||
if (idp_data.department)
|
||
{
|
||
sql += " AND prs_afdeling_parentkey = " + idp_data.department.id;
|
||
}
|
||
else
|
||
{
|
||
if (idp_data.company)
|
||
sql += " AND prs_bedrijf_key = " + idp_data.company.id;
|
||
}
|
||
var oRs = Oracle.Execute(sql);
|
||
if (oRs.Eof)
|
||
{
|
||
__Log("Claimed department {0} not found".format(val));
|
||
// Er komt eventueel wel een fallback naar idp_data.department.id
|
||
}
|
||
else
|
||
{
|
||
var afd_key = oRs("prs_afdeling_key").Value;
|
||
oRs.MoveNext();
|
||
if (!oRs.Eof)
|
||
shared.internal_error("Claimed department {0} not unique".format(val));
|
||
|
||
persdata["department"] = afd_key;
|
||
}
|
||
oRs.Close();
|
||
break;
|
||
}
|
||
// De 1-n
|
||
case 100: persdata.authorisation = val; break;
|
||
case 101: persdata.workplace = val; break;
|
||
// case 102: persdata.workplacevirtual = val; break;
|
||
// case 103: reserved voor mandatering?
|
||
default:
|
||
if (idpm.name.id > 1000)
|
||
set_custom_field(persdata, idpm.name.id - 1000, val, "C");
|
||
break;
|
||
}
|
||
}
|
||
// Klantspecifieke check functie (hookfunction) voor de invoer
|
||
var pResult = new HookResult();
|
||
if (!custfunc.aut_process_claim(persdata, claim, idp_data, pResult))
|
||
{
|
||
abort_with_warning(pResult.errmsg);
|
||
}
|
||
|
||
if (!("department" in persdata))
|
||
{
|
||
if (!idp_data.department)
|
||
shared.internal_error("Department is not configured for Identity Provider {0} ({1})".format(idp_data.code, idp_data.name));
|
||
|
||
persdata["department"] = idp_data.department.id; // dan moet die ingevuld zijn
|
||
}
|
||
|
||
if (user_key < 0)
|
||
__Log("User automatically created with data:");
|
||
else
|
||
__Log("User automatically updated with data:");
|
||
__Log(persdata);
|
||
|
||
var persparams = {};
|
||
var person = new model_prs_perslid({ internal: true }); // Internal: true om dit (nog) anoniem te mogen doen
|
||
if (user_key > 0) // bijwerken
|
||
{
|
||
person.REST_PUT( persparams, persdata, user_key );
|
||
}
|
||
else // nieuwe
|
||
{
|
||
var prs = person.REST_POST( persparams, persdata );
|
||
__DoLog("Created user '{0} {1}' with key {2} for idp '{3}'".format(persdata["firstname"], persdata["lastname"], prs.key, idp_data.code));
|
||
// De nieuw aangemaakte gebruiker inloggen:
|
||
doLogin(prs.key, { idp_code: idp_data.code, isFACFACinternal: isFACFACinternal });
|
||
// En nu pas kunnen we tracken
|
||
shared.trackaction("PRSUPD", prs.key, "Created user '{0} {1}' for idp '{2}'".format(persdata["firstname"], persdata["lastname"], idp_data.code));
|
||
}
|
||
// Nu authorisatie groepen nog bijwerken
|
||
// Via het model was me even iets te hoog gegrepen: ik zou toch (nog) de id's er bij moeten halen
|
||
if ("authorisation" in persdata)
|
||
{ // authorisation bevat gebruikersgroepen gescheiden door '|' of ';'
|
||
/* SHIB: Within each CGI variable or header (see below), multiple attribute values
|
||
are separated by a semicolon, and semicolons in values are escaped with a backslash.
|
||
The data should be interpreted as UTF-8, which is a superset of ASCII.
|
||
*/
|
||
|
||
var autharr = persdata["authorisation"].toLowerCase().split(/[;\|]/); // lowerCase, insensitive dus
|
||
var sql = "DELETE FROM fac_gebruikersgroep"
|
||
+ " WHERE prs_perslid_key = " + user_key
|
||
+ " AND fac_groep_key NOT IN (SELECT fac_groep_key "
|
||
+ " FROM fac_groep"
|
||
+ " WHERE LOWER(fac_groep_omschrijving) IN ({0})".format(safe.quoted_sql_join(autharr))
|
||
+ " OR SUBSTR(fac_groep_omschrijving, 1, 1) = '_'" // Die blijven altijd
|
||
+ " )";
|
||
Oracle.Execute(sql);
|
||
var sql = "INSERT INTO fac_gebruikersgroep(prs_perslid_key, fac_groep_key)"
|
||
+ " SELECT " + user_key + ", fac_groep_key"
|
||
+ " FROM fac_groep fg"
|
||
+ " WHERE LOWER(fac_groep_omschrijving) IN ({0})".format(safe.quoted_sql_join(autharr))
|
||
+ " AND SUBSTR(fac_groep_omschrijving, 1, 1) <> '_'" // Die nooit
|
||
+ " AND NOT EXISTS (SELECT 1"
|
||
+ " FROM fac_gebruikersgroep fg2"
|
||
+ " WHERE fg2.fac_groep_key = fg.fac_groep_key"
|
||
+ " AND fg2.prs_perslid_key = " + user_key + ")";
|
||
Oracle.Execute(sql);
|
||
}
|
||
|
||
if ("workplace" in persdata && persdata["workplace"])
|
||
{ // workplace bevat ruimtes gescheiden door '|' of ';'
|
||
// (we ondersteunen alleen impliciete werkplekken, geen 'named')
|
||
// Codering moet volgens alg_v_plaatsaanduiding zijn (locatiecode-gebouwcode-verdiepingcode-ruimtenr)
|
||
// Als er een '@' voor staat is het een virtuele werkplek
|
||
var workplacearr = persdata["workplace"].split(/[;\|]/);
|
||
var keepwp = [];
|
||
for (var i = 0; i < workplacearr.length; i++)
|
||
{
|
||
var wpcode = workplacearr[i];
|
||
var virtual = 0;
|
||
if (wpcode.substr(0, 1) == '@')
|
||
{
|
||
virtual = 1;
|
||
wpcode = wpcode.substr(1);
|
||
}
|
||
var sql = "SELECT alg_onroerendgoed_keys, alg_onroerendgoed_type"
|
||
+ " FROM alg_v_plaatsaanduiding"
|
||
+ " WHERE alg_plaatsaanduiding = " + safe.quoted_sql_upper(wpcode);
|
||
var oRs = Oracle.Execute(sql);
|
||
if (!oRs.Eof)
|
||
{
|
||
var okey = oRs("alg_onroerendgoed_keys").Value;
|
||
var otype = oRs("alg_onroerendgoed_type").Value;
|
||
sql = "BEGIN"
|
||
+ " prs.movetoruimte ({0}, {1}, '{2}', {3}); ".format(user_key, okey, 'G', virtual) // G want maar <20><>n werkplek per gebouw
|
||
+ "END;";
|
||
Oracle.Execute(sql);
|
||
}
|
||
else
|
||
__Log("Workplace '{0}' not found".format(workplacearr[i]));
|
||
oRs.Close();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
%>
|
||
<script language="VBScript" runat="Server">
|
||
'' // Met de beste wil van de wereld kreeg ik dit niet werkend met JScript
|
||
'' // Op de SGF12 moest ik .Expires = '12/31/2012' doen
|
||
'' // Op mijn Vista PC moest ik .Expires = '31/12/2012' doen
|
||
'' // Daarom maar VBScript
|
||
Sub VBexpireCookie(cookiename, interval, number)
|
||
Dim datum
|
||
datum = DateAdd(interval, number, Now)
|
||
Response.Cookies(cookiename).Expires=datum
|
||
End Sub
|
||
</script>
|