FSN#37583 Authenticatie via JWT
svn path=/Website/trunk/; revision=30563
This commit is contained in:
@@ -30,6 +30,7 @@ scaffolding(this_model,
|
|||||||
"list": {
|
"list": {
|
||||||
"columns": [
|
"columns": [
|
||||||
"id",
|
"id",
|
||||||
|
"code",
|
||||||
"name",
|
"name",
|
||||||
"type",
|
"type",
|
||||||
"remote_loginurl"
|
"remote_loginurl"
|
||||||
|
|||||||
@@ -1578,4 +1578,53 @@ HTTP =
|
|||||||
return sitenoroot;
|
return sitenoroot;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Alleen nog maar ipv4
|
||||||
|
// http://stackoverflow.com/a/503238/569090
|
||||||
|
IP =
|
||||||
|
{
|
||||||
|
IPnumber: function(IPaddress) {
|
||||||
|
var ip = IPaddress.match(/^(\d+)\.(\d+)\.(\d+)\.(\d+)$/);
|
||||||
|
if(ip) {
|
||||||
|
return (+ip[1]<<24) + (+ip[2]<<16) + (+ip[3]<<8) + (+ip[4]);
|
||||||
|
}
|
||||||
|
// else ... ?
|
||||||
|
__DoLog("Invalid IPAddress: " + IPaddress);
|
||||||
|
INTERNAL_ERROR_INVALID_IP;
|
||||||
|
},
|
||||||
|
|
||||||
|
IPmask: function (maskSize) {
|
||||||
|
return -1<<(32-maskSize)
|
||||||
|
},
|
||||||
|
|
||||||
|
inSubnet: function(ip, subnet)
|
||||||
|
{
|
||||||
|
var long_ip = IP.IPnumber(ip);
|
||||||
|
var bits = 32;
|
||||||
|
if (subnet.indexOf("/") > 0)
|
||||||
|
{
|
||||||
|
bits = parseInt(subnet.split("/")[1], 10);
|
||||||
|
subnet = subnet.split("/")[0];
|
||||||
|
}
|
||||||
|
var mask = IP.IPmask(bits);
|
||||||
|
var long_subnet = IP.IPnumber(subnet);
|
||||||
|
|
||||||
|
return (long_ip & mask) == (long_subnet & mask);
|
||||||
|
},
|
||||||
|
|
||||||
|
// subnets gescheiden door komma's
|
||||||
|
inAnySubnet: function(ip, subnets)
|
||||||
|
{
|
||||||
|
if (!subnets)
|
||||||
|
INTERNAL_ERROR_NOSUBNETS; // 'k weet niet of default veilig of onveilig gewenst is
|
||||||
|
var arr = String(subnets).split(",");
|
||||||
|
for (var i = 0; i < arr.length; i++)
|
||||||
|
{
|
||||||
|
if (IP.inSubnet(ip, arr[i]))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
%>
|
%>
|
||||||
|
|||||||
@@ -89,6 +89,7 @@ if (user_key < 0)
|
|||||||
|
|
||||||
// jwt claim
|
// jwt claim
|
||||||
// TODO: altijd/ ook als user_key > 0?
|
// TODO: altijd/ ook als user_key > 0?
|
||||||
|
// TODO ook uit form POST ondersteunen?
|
||||||
var jwt = getQParam("jwt", "");
|
var jwt = getQParam("jwt", "");
|
||||||
if (user_key < 0 && !jwt)
|
if (user_key < 0 && !jwt)
|
||||||
{
|
{
|
||||||
@@ -101,42 +102,102 @@ if (user_key < 0 && !jwt)
|
|||||||
Session.Abandon();
|
Session.Abandon();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (user_key < 0 && jwt) // TODO of uit http-header authorization: Bearer of uit form POST
|
if (user_key < 0 && jwt)
|
||||||
{
|
{
|
||||||
var claim = jwt_decode(jwt);
|
var claim = jwt_decode(jwt);
|
||||||
// __DoLog(claim);
|
__Log(claim);
|
||||||
|
|
||||||
if (claim.err)
|
if (claim.err)
|
||||||
abort_with_warning("Invalid JWT: " + claim.err);
|
shared.internal_error("Invalid JWT: " + claim.err);
|
||||||
|
|
||||||
|
// We staan nooit twee keer dezelfde signature toe. Voorkomt alle replay-aanvallen
|
||||||
|
// Iets verderop onthouden we signature
|
||||||
|
var sql = "SELECT prs_perslid_key, fac_session_data "
|
||||||
|
+ " FROM fac_session "
|
||||||
|
+ " WHERE fac_session_expire > sysdate "
|
||||||
|
+ " AND fac_session_sessionid_hash = " + safe.quoted_sql(claim.signature64);
|
||||||
|
var oRs = Oracle.Execute( sql );
|
||||||
|
if (!oRs.eof)
|
||||||
|
shared.internal_error("Invalid JWT: it has been used before.");
|
||||||
|
oRs.Close();
|
||||||
|
|
||||||
var sql = "SELECT *"
|
var sql = "SELECT *"
|
||||||
+ " FROM fac_idp"
|
+ " FROM fac_idp"
|
||||||
+ " WHERE fac_idp_issuer = " + safe.quoted_sql(claim.payload.iss);
|
+ " WHERE fac_idp_issuer = " + safe.quoted_sql(claim.payload.iss);
|
||||||
var oRs = Oracle.Execute(sql);
|
var oRs = Oracle.Execute(sql);
|
||||||
if (oRs.Eof)
|
if (oRs.Eof)
|
||||||
abort_with_warning("Unknown JWT issuer: " + claim.payload.iss);
|
shared.internal_error("Unknown JWT issuer: " + claim.payload.iss);
|
||||||
|
|
||||||
var verify = jwt_verify(claim, oRs("fac_idp_secret").Value, oRs("fac_idp_clockskew").Value);
|
var verify = jwt_verify(claim, oRs("fac_idp_secret").Value, oRs("fac_idp_clockskew").Value);
|
||||||
if (verify.err)
|
if (verify.err)
|
||||||
abort_with_warning("Invalid JWT: " + verify.err);
|
shared.internal_error("Invalid JWT: " + verify.err);
|
||||||
// TODO: claim.payload.iss gebruiken om FAC_IDP_CODE te zoeken
|
|
||||||
// en jwt_verify doen
|
|
||||||
// en claim.jit registreren/ controleren in fac_session
|
// en claim.jit registreren/ controleren in fac_session
|
||||||
|
var isFACFACinternal = oRs("fac_idp_internal").Value != 0;
|
||||||
|
if (isFACFACinternal && !claim.payload.fclt_realuser)
|
||||||
|
{
|
||||||
|
shared.internal_error("Missing fclt_realuser in claim. It is required for IDP_internal.");
|
||||||
|
}
|
||||||
|
|
||||||
Session.Contents.Remove("ASPFIXATION"); // Niet moeilijk doen
|
// Ok, de claim is geldig. Nu kijken of we er iets mee kunnen
|
||||||
settings.overrule_setting("login_use_email", 0); // We hebben altijd login gescanned namelijk
|
if (claim.payload.username) // je mag username meegeven
|
||||||
if (claim.payload.username)
|
{
|
||||||
tryLogin(claim.payload.username, null, { noPassword: true, noFacSession: by_bearer });
|
settings.overrule_setting("login_use_email", 0);
|
||||||
|
tryLogin(claim.payload.username, null, { noPassword: true, noFacSession: by_bearer, isFACFACinternal: isFACFACinternal });
|
||||||
|
}
|
||||||
|
if (user_key < 0 && claim.payload.email) // je mag email meegeven
|
||||||
|
{
|
||||||
|
settings.overrule_setting("login_use_email", 1);
|
||||||
|
tryLogin(claim.payload.email, null, { noPassword: true, noFacSession: by_bearer, isFACFACinternal: isFACFACinternal });
|
||||||
|
}
|
||||||
if (user_key < 0 && claim.perslid_key > 0)
|
if (user_key < 0 && claim.perslid_key > 0)
|
||||||
doLogin(claim.payload.perslid_key, null, { noFacSession: by_bearer }); // je mag ook key meegeven
|
doLogin(claim.payload.perslid_key, { noFacSession: by_bearer, isFACFACinternal: isFACFACinternal }); // je mag ook key meegeven
|
||||||
Session("idp_key") = oRs("fac_idp_key").Value;
|
|
||||||
|
|
||||||
var realuser = Session("sso_sgf_realuser");
|
if (user_key > 0)
|
||||||
if (user_key > 0 && realuser)
|
{
|
||||||
shared.trackaction("PRSLOG", user_key, L("lcl_logged_on_sso").format(realuser, Request.ServerVariables("REMOTE_ADDR")));
|
// SSO naar een FACFAC gebruiker mag alleen als fac_idp_internal aan staat
|
||||||
Session.Contents.Remove("sso_sgf_realuser");
|
if (user.has("WEB_FACFAC") && !isFACFACinternal)
|
||||||
|
{
|
||||||
|
__DoLog("Illegal login WEB_FACFAC");
|
||||||
|
doLogoff();
|
||||||
|
shared.internal_error("This IDP cannot be used for users with WEB_FACFAC.");
|
||||||
|
}
|
||||||
|
// Als fac_idp_internal aan staat mag alleen SSO naar een FACFAC gebruiker
|
||||||
|
// Tenzij S("idp_internal_anyuser") true is, dan mag je naar iedereen
|
||||||
|
// Dat doen we op OTA via custenc.wsc, dat doen we niet in PROD
|
||||||
|
if (isFACFACinternal && !S("idp_internal_anyuser") && !user.has("WEB_FACFAC"))
|
||||||
|
{
|
||||||
|
doLogoff();
|
||||||
|
shared.internal_error("This IDP can only be used for users with WEB_FACFAC.");
|
||||||
|
}
|
||||||
|
|
||||||
if (!by_bearer) // bearer is stateless/ single request dus geen redirect
|
// Ongeldige lukken de volgende keer ook niet. Geldige wil ik niet weer zien
|
||||||
|
// De expiretijd gaat er alleen over wanneer ik mag opruimen. Neem daarbij uur speling
|
||||||
|
var agent = String(Request.ServerVariables("HTTP_USER_AGENT"));
|
||||||
|
var ip = String(Request.ServerVariables("REMOTE_ADDR"));
|
||||||
|
var sql = "INSERT INTO fac_session"
|
||||||
|
+ " (fac_session_sessionid_hash,"
|
||||||
|
+ " fac_session_data,"
|
||||||
|
+ " prs_perslid_key,"
|
||||||
|
+ " fac_session_expire,"
|
||||||
|
+ " fac_session_useragent,"
|
||||||
|
+ " fac_session_ip)"
|
||||||
|
+ " VALUES(" + safe.quoted_sql(claim.signature64) + ", "
|
||||||
|
+ " 'JWT replay preventer',"
|
||||||
|
+ user_key + ","
|
||||||
|
+ " SYSDATE + 1/24 + 1/24/60/60 * " + oRs("fac_idp_clockskew").Value + ", "
|
||||||
|
+ safe.quoted_sql(agent, 256) + ","
|
||||||
|
+ safe.quoted_sql(ip, 64) + ")";
|
||||||
|
Oracle.Execute(sql);
|
||||||
|
|
||||||
|
// Onthouden hoe je bent binnengekomen zodat logout naar logout_url kan leiden
|
||||||
|
Session("idp_key") = oRs("fac_idp_key").Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user_key > 0 && claim.payload.fclt_realuser)
|
||||||
|
shared.trackaction("PRSLOG", user_key, L("lcl_logged_on_sso").format(claim.payload.fclt_realuser, Request.ServerVariables("REMOTE_ADDR")));
|
||||||
|
|
||||||
|
if (!by_bearer && user_key > 0) // bearer is stateless/ single request dus geen redirect
|
||||||
{
|
{
|
||||||
var return_to = getQParam("return_to", "/") || "/";
|
var return_to = getQParam("return_to", "/") || "/";
|
||||||
// validate: enkele / voor root, /? voor root met params
|
// validate: enkele / voor root, /? voor root met params
|
||||||
@@ -163,19 +224,22 @@ if (user_key < 0 && getQParam("sso", ""))
|
|||||||
+ " WHERE fac_idp_code = " + safe.quoted_sql(sso);
|
+ " WHERE fac_idp_code = " + safe.quoted_sql(sso);
|
||||||
var oRs = Oracle.Execute(sql);
|
var oRs = Oracle.Execute(sql);
|
||||||
if (oRs.Eof)
|
if (oRs.Eof)
|
||||||
abort_with_warning("Identity provider '{0}' is not configured".format(sso));
|
shared.internal_error("Identity provider '{0}' is not configured".format(sso));
|
||||||
|
|
||||||
|
var isFACFACinternal = oRs("fac_idp_internal").Value != 0;
|
||||||
var ip_restrict = oRs("fac_idp_ipfilter").Value;
|
var ip_restrict = oRs("fac_idp_ipfilter").Value;
|
||||||
|
if (isFACFACinternal && S("idp_internal_anyuser"))
|
||||||
|
ip_restrict = ""; // dan niet al te moeilijk doen
|
||||||
var ip_ok = true;
|
var ip_ok = true;
|
||||||
if (ip_restrict)
|
if (ip_restrict) // Application("otap_environment") != "O")
|
||||||
{
|
{
|
||||||
var ip = String(Request.ServerVariables("REMOTE_ADDR"));
|
var ip = String(Request.ServerVariables("REMOTE_ADDR"));
|
||||||
ip_ok = new RegExp(ip_restrict.replace(/\./ig, "\\."), "ig").test(ip);
|
ip_ok = IP.inAnySubnet(ip, ip_restrict);
|
||||||
__Log("SSO IP-restrictie {0} versus remote {1}: {2}".format(ip_restrict, ip, ip_ok));
|
__Log("SSO IP-restrictie {0} versus remote {1}: {2}".format(ip_restrict, ip, ip_ok));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ip_ok)
|
if (!ip_ok)
|
||||||
abort_with_warning("IP {0} not allowed".format(ip)); // TODO of 400 code forbidden?
|
shared.internal_error("IP {0} not allowed".format(ip)); // TODO of 400 code forbidden?
|
||||||
|
|
||||||
if (oRs("fac_idp_type").Value == 3) // die doet het verder zelf
|
if (oRs("fac_idp_type").Value == 3) // die doet het verder zelf
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user