Files
Facilitor/APPL/AUT/Login.inc
Jos Groot Lipman 861507aa88 FSN#41669 Basic Auth voor API2
svn path=/Website/trunk/; revision=35019
2017-08-21 08:21:26 +00:00

1640 lines
66 KiB
C++
Raw Blame History

<% /*
$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.
// stateless komen we hier al niet
function doLogin(prs_key, params)
{
__Log("==== doLogin " + prs_key);
params = params || {};
// Paranoia mode
var sql = "SELECT prs_perslid_login"
+ " , prs_perslid_einddatum"
+ " 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);
var einddatum = oRs("prs_perslid_einddatum").Value != null?new Date(oRs("prs_perslid_einddatum").Value): null;
oRs.Close();
if (S("prs_einddatum_login_grace") >= 0 && einddatum)
{
var last_allow_login = einddatum;
last_allow_login.setDate(last_allow_login.getDate() + S("prs_einddatum_login_grace"))
if (new Date().midnight() > last_allow_login)
{
doLogoff();
shared.simpel_page(L("lcl_einddatum_login_expired"));
}
}
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));
}
}
/* 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 cookie==>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 && !params.stateless) // 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"), 30)
+ " ," + 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);
}
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;
}
function doLoginStateless(prs_key, params)
{
/* global */ user_key = prs_key;
Session("user_key") = user_key; // Nu ben je pas *echt* ingelogd
/* global */ user = new Perslid(user_key); // wordt mogelijk nog overruled door imporsonate
Session("stateless") = 1;
Session.Abandon(); // Altijd, voor de zekerheid
}
// 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 30 dagen.
+ 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 iets verderop 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 new_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, new_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;
}
// Op basis van usernaam/wachtwoord gaan we iemand authenticeren
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 && S("aut_login_strip_domain") == 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 = [];
logins.push(" prs_perslid_apikey = " + safe.quoted_sql(username, 128)); // Die mag altijd wel
if (S("login_use_email"))
{
logins.push(" upper(prs_perslid_email) = " + safe.quoted_sql_upper(username));
}
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"
+ " , prs_perslid_apikey"
+ " 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);
oRs.Close();
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
|| oRs("prs_perslid_apikey").Value === username
)
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)
{
if (ident.stateless || params.stateless)
doLoginStateless(ident.user_key, params);
else
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') {
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')
{
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 sql = "SELECT *"
+ " FROM aut_sp"
+ " WHERE aut_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));
var sp_key = oRs("aut_sp_key").value;
if (oRs("aut_sp_loglevel").Value > 0)
__Logging = oRs("aut_sp_loglevel").Value;
if (oRs("fac_functie_key").Value)
user.checkAutorisation(oRs("fac_functie_key").Value); // dan moet je die hebben
var params = {
usermapping: oRs("aut_sp_usermapping").Value,
iss: oRs("aut_sp_issuer").Value,
aud: aud,
secret: oRs("aut_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
}
// aut_sp_map bevat de attributen die wij vrijgeven voor deze SP
if (sp_key > 0)
{
var sql = "SELECT *"
+ " FROM aut_sp_map"
+ " WHERE aut_sp_key = " + sp_key;
var oRs = Oracle.Execute(sql);
while (!oRs.Eof)
{
var clm = oRs("aut_sp_map_to").Value; // zo gaat hij heten in de JWT
switch (oRs("aut_sp_map_from").Value) // zie model_aut_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
// De jwt_verify moet straks wel op de originelen
result.orgheaderSeg = result.headerSeg;
result.orgpayloadSeg = result.payloadSeg;
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.orgheaderSeg + "." + decoded_jwt.orgpayloadSeg);
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 hasIdentifyVal = 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;
if (idpm.identify == 1)
hasIdentifyVal = true;
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, stateless: 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, stateless: params.by_bearer, isFACFACinternal: isFACFACinternal });
break;
case 99: // internal, prs_perslid_key
doLogin(parseInt(claim[idpm.from], 10), { stateless: 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, { stateless: 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 (!hasIdentifyVal)
shared.internal_error("IdP {0} has not supplied a value for any identifying attribute.".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
)
{
process_claim_update(claim, idp_data, params)
}
}
function process_claim_update(claim, idp_data, params)
{
var isFACFACinternal = idp_data.internal != 0;
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["middlename"] = 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;
case 12: persdata["externalid"] = 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)
{
// Eerst oude werkplekken ophalen
var sql = "SELECT pw.prs_werkplek_key, UPPER(alg_plaatsaanduiding) alg_plaatsaanduiding"
+ " FROM prs_perslidwerkplek pw, prs_werkplek wp, alg_v_plaatsaanduiding"
+ " WHERE pw.prs_perslid_key = " + user_key
+ " AND pw.prs_werkplek_key = wp.prs_werkplek_key"
+ " AND wp.prs_werkplek_type = 0" // alleen vaste plekken
+ " AND alg_onroerendgoed_keys = wp.prs_alg_ruimte_key"
+ " AND alg_onroerendgoed_type = 'R'";
var oRs = Oracle.Execute(sql);
var oldWP = {};
while (!oRs.Eof)
{
oldWP[oRs("alg_plaatsaanduiding").Value] = oRs("prs_werkplek_key").Value;
oRs.MoveNext();
}
oRs.Close();
// 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
persdata["workplace"] = persdata["workplace"] || "";
var workplacearr = persdata["workplace"].split(/[;\|]/);
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);
}
if (wpcode in oldWP)
{
delete oldWP[wpcode]; // Hoeven we straks niet te wissen
}
else // Toevoegen
{
var sql = "SELECT alg_onroerendgoed_keys"
+ " FROM alg_v_plaatsaanduiding"
+ " WHERE alg_onroerendgoed_type = 'R'"
+ " AND UPPER(alg_plaatsaanduiding) = " + safe.quoted_sql_upper(wpcode);
var oRs = Oracle.Execute(sql);
if (!oRs.Eof)
{
delete oldWP[wpcode.toUpperCase()]; // Die zal hergebruikt worden
var okey = oRs("alg_onroerendgoed_keys").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();
}
}
for (wpcode in oldWP) // restant opruimen
{
__Log("Persoon verwijderen van WP {0}, wp-key {1}".format(wpcode, oldWP[wpcode]));
if (S("prs_werkplek_implicit") == 1)
{
var sql = "DELETE FROM prs_werkplek"
+ " WHERE prs_werkplek_key = " + oldWP[wpcode];
}
else
{
var sql = "DELETE FROM prs_perslid_werkplek"
+ " WHERE prs_perslid_key = " + user_key
+ " AND prs_werkplek_key = " + oldWP[wpcode];
}
Oracle.Execute(sql);
}
}
}
%>
<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>