<%@language = "javascript" %>
<% /*
    $Revision$
    $Id$

    Met vernieuwde kennis zou ik dit bestand authenticate.asp noemen

    We weten niet wie de gebruiker is.
    Probeer op allerlei manieren te authentiseren
    Als het lukt geven we een user_key terug in Session("user_key");
    Als het niet lukt zal doorgaans door common.inc verder gegaan worden in de interactieve Logon.asp

    LET OP: Dit bestand wordt via Server.Transfer vanuit Common.inc aangeroepen

    We onderscheiden twee soorten authenticatie
    Statefull
        Dit is voor een interactieve gebruiker met een browser. Initieel wordt op
        allerlei manieren authenticatie geprobeerd maar daarna wordt de user_key
        in een Session opgeslagen en de volgende keer wordt via een Session-coolie
        vanuit die session de authenticatie gedaan
        In prs_perslid_login wordt het laatste moment van inloggen bijgewrkt
        Er wordt een fac_session record aangemaakt (waar we verder weinig mee doen)
    Stateless
        Dit is voor API aanroepen. ELKE aanroep komt de authenticatie opnieuw
        mee. Dit wordt niet geregistreerd in prs_perslid_login
        Er wordt een Session.abandon gedaan om niet te veel IIS sessies te houden
*/
DOCTYPE_Disable = 1;
ANONYMOUS_Allowed = 1;
LOGIN_try = 1; // prevent recursion
NO_ADDHEADER = 1; // common.inc voert wat globale acties hierdoor niet meer uit
THIS_FILE = 'appl/aut/logintry.asp';

if (Response.Expires > 0)
    FCLTEXPIRES = Response.Expires; // Aanroeper wist het beter?
%>
<!-- #include file="../Shared/common.inc" -->
<!-- #include file="./login.inc" -->
<!-- #include file="../api/api.inc" -->
<!-- #include file="../api2/api2.inc" -->
<!-- #include file="../api2/model_prs_perslid.inc" -->
<!-- #include file="../api2/model_aut_client.inc" -->
<!-- #include file="../api2/model_aut_idp.inc" -->
<%
__Log("== Entering loginTry.asp ==");

// De stateless varianten proberen we eerst, die worden tenslotte potentieel vaak uitgevoerd
if (user_key < 0)
{
    var APIKEY;
    if (S("fac_api_key_in_url"))
        APIKEY = getQParam("APIKEY", "");
    if (!APIKEY && Request.ServerVariables("HTTP_X_FACILITOR_API_KEY").Count)
        APIKEY = String(Request.ServerVariables("HTTP_X_FACILITOR_API_KEY")); // Meegegeven als X-FACILITOR-API-Key
    if (APIKEY)
    {
        var sql = "SELECT prs_perslid_key, prs_perslid_naam, prs_perslid_oslogin, prs_perslid_loglevel, prs_perslid_flags"
                + "  FROM prs_perslid"
                + " WHERE prs_perslid_verwijder IS NULL"
                + "   AND prs_perslid_apikey = " + safe.quoted_sql(APIKEY);
        var oRs = Oracle.Execute(sql);
        if (!oRs.Eof)
        {
            __Log("API2 User is: {0} ({1})".format(oRs("prs_perslid_naam").Value, oRs("prs_perslid_oslogin").Value));
            if (oRs("prs_perslid_flags").Value & 16) //isWriteonlyApi2User
            {
                Session("isWriteonlyApi2User") = true;
            }
            Session("userident") = "{0} (APIKEY: {1})".format(oRs("prs_perslid_naam").Value, APIKEY.substr(0,3) + "..."); // Handig voor in logging
            doLoginStateless(oRs("prs_perslid_key").Value);
            var loglevel = oRs("prs_perslid_loglevel").Value;
            if (loglevel > 0) // We doen geen __Logging=3, dan zou voor een gebruiker zo maar de logging in beeld komen
            {
                // __Logging = 1; // *dit* bestand interessert me niet genoeg
                Session("logging") = 1; // Stateless maar voor de server.redirect e.d. toch zetten
                Session("userident") = Session("userident") + " (forced logging)";
            }
        }
        // else negeren
        oRs.Close()
    }
}

if (user_key < 0)
{
    var auth = String(Request.ServerVariables("HTTP_AUTHORIZATION"));
    if (auth.match(/^Basic /i))
    {
        var b64 = auth.substring(6);
        var plain = decode_b64(b64);
        if (plain.split(":").length > 1)
        {
            var ww = plain.split(":");
            ww.shift();
            tryLogin(plain.split(":")[0], ww.join(":"), { stateless: true }); // Behoudt eventuele ':' in wachtwoord
        }
    }
}

// Ingescande QR-code bookmark gaat naar \appl\prs\prs_perslid_qr_confirm.asp

if (user_key < 0) // Probeer de user_key uit een cookie te halen
{
    var FcltId = "" + Request.Cookies("fcltid");
    if (FcltId != null && FcltId != "")
    {
        setUserFromSession (FcltId);
        if (user_key > 0)
            makeSessionCookie("Remember Login"); // Atijd nieuwe
    }
}

if (user_key < 0)
{
    var auth = String(Request.ServerVariables("HTTP_X_FACILITOR_ACCESS_TOKEN")); // Deze is aangemaakt in /aut/getapptoken.asp
    if (auth.match(/^1\$/)) // Mode 1$xxxxxxx
    {
        __Log("Found HTTP_X_FACILITOR_ACCESS_TOKEN");
        var model_client_perslid = new model_aut_client_perslid({ internal: true });
        model_client_perslid.autfunction = false; // even niet moeilijk doen
        var cp_data = api2.GET(model_client_perslid, { filter: { "accesstoken": auth } });
        if (cp_data)
        {   // niet zuiver: een HTTP_X_FACILITOR_ACCESS_TOKEN moet volgens mij stateless zijn
			// Daar kan de NewNexus App/webview echter niet goed tegen
            var app_version = getQParam("app_version", "<unknown>");
            var app_build   = getQParamInt("app_build", null);
            var fresh_cp_data = {
                login: new Date(),
                app_build: app_build,
                app_version: app_version
            };
            doLogin(cp_data.person.id);
            model_client_perslid.REST_PUT({}, fresh_cp_data, cp_data.id);
            Session("aut_client_key") = cp_data.autclient.id;
            Session("aut_client_name") = cp_data.autclient.name;
            Session("app_build") = app_build;
        }
    }
}

// jwt claim
// Niet zo maar een getFParam proberen. Dat nekt API2/PUT's binaryRead
var jwt = getQParam("jwt", "");
if (user_key < 0 && !jwt)
{
    var auth = String(Request.ServerVariables("HTTP_AUTHORIZATION"));
    if (auth.match(/^Bearer /))
    {
        __Log("Found Authorization: Bearer");
        jwt = auth.substring(7);
        var by_bearer = true; // deze is stateless dus geen sessie aanmaken.
        Session.Abandon();
    }
}
if ((user_key < 0 || getQParamInt("jwtforce", 0) == 1) && jwt)
{
    var claim = jwt_decode(jwt);

    if (claim.err)
        shared.internal_error("Invalid JWT: " + claim.err);

    __Log(claim.payload);

    var filter = {
                   type: 4, // IDP
                   issuer: claim.payload.iss,
                   audience: claim.payload.aud
                 }

    var idp_data_arr = new model_aut_idp({ internal: true }).REST_GET({ filter: filter , include: [ "idpmappings" ]});
    if (!idp_data_arr.length)
        shared.internal_error("Unknown JWT issuer {0} for audience {1}".format(claim.payload.iss, claim.payload.aud));
    var idp_data = idp_data_arr[0];
    if (idp_data.loglevel > 0)
    {
        __Logging = idp_data.loglevel;
        __Log("Logging for this IdP ({0}) is forced on".format(idp_data.name), '#f00');
    }

    var verify = jwt_verify(claim, idp_data.secret, idp_data.clockskew, idp_data.duration);
    if (verify.err)
        shared.internal_error("Invalid JWT: " + verify.err);

    if (!idp_data.duration) // single use tokens wil ik niet weer zien
    {
        // 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 oRs2 = Oracle.Execute( sql );
        if (!oRs2.eof)
            shared.internal_error("Invalid JWT: it has been used before.");
        oRs2.Close();
    }

    // en claim.jit registreren/ controleren in fac_session
    var isFACFACinternal = idp_data.internal != 0;
    if (isFACFACinternal && !claim.payload.fclt_realuser && !by_bearer) {
        shared.internal_error("Missing fclt_realuser in claim. It is required for IDP_internal.");
    } else if (claim.payload.fclt_realuser) {
        Session("fclt_realuser") = claim.payload.fclt_realuser; // Komt mooi in eventuele AiAi logging terecht.
        if (claim.payload.fclt_realuserreason)
            Session("fclt_realuserreason") = claim.payload.fclt_realuserreason;
    }

    // Ok, de claim valideert. Nu kijken of we er iets mee kunnen
    if (idp_data.idpmappings.length == 0) // deze twee worden hardcoded ondersteund.
    {
        idp_data.idpmappings.push({
                "identify": 1,
                "name": {
                  "id": 1,
                  "name": "login"
                },
                "from": "username",
                "default": null
              });
        if (isFACFACinternal)
        {
            idp_data.idpmappings.push({
                    "identify": 1,
                    "name": {
                      "id": 9,
                      "name": "email"
                    },
                    "from": "email",
                    "default": null
                  });
            idp_data.idpmappings.push({
                    "identify": 1,
                    "name": {
                      "id": 99,
                      "name": "login"
                    },
                    "from": "perslid_key",
                    "default": null
                  });
        }
    }
    process_claim(claim.payload, idp_data, { by_bearer: by_bearer });

    if (user_key > 0) // Misschien is hij net aanmaakt zelfs
    {
        // TODO: Als prs_perslid_credentials_datum > claim.payload.iat dan toch niet accepteren

        if (!idp_data.duration) // single use tokens wil ik niet weer zien
        {                       // ook al zijn ze alleen geldig van -clockskew tot +clockskew
            // 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 * " + idp_data.clockskew + ", "
                   +        safe.quoted_sql(agent, 256) + ", "
                   +        safe.quoted_sql(ip, 64) + ")";
            Oracle.Execute(sql);

			// Lang geldige tokens registeren we niet: die  worden soms (SCIM)  duizenden keren vlak achter elkaar gebruikt
			// Dan wil ik niet heel veel UPDATE's krijgen
            shared.registeraction("IDP_Login", { daily: 2, refkey: idp_data.id }); // Per dag tellen voor de statistieken
        }

        // Onthouden hoe je bent binnengekomen zodat logout naar logout_url kan leiden
        Session("idp_key") = idp_data.id;

        if (!by_bearer) // bearer is stateless/ single request dus geen redirect
        {
            var return_to = getQParam("return_to", "/") || "/";
            // validate: enkele / voor root, /? voor root met params
            //           en anders iets in de /appl folder. Andere redirects kennen we niet
            if (return_to != "/" &&
                !return_to.match(/^\/\?/i) &&
                !return_to.match(/^\/appl\/?/i)
               )
            {
                __DoLog("Invalid return_to: " + return_to, "#f00");
                return_to = "/";
            }
            // Ga weer helemaal terug naar de pagina waar je mee begon.
            // Ook mooi als iemand er een bookmark naar legt.
            Response.Redirect(rooturl + return_to);
        }
    }
}

var sso = getQParam("sso", "");
if (user_key < 0 && sso && sso != "0") // "0" is een hardcoded special case
    trySSO(sso); // keert niet terug bij geldige ssocode

if (user_key < 0)
{
    if (Session("fallback_user_key") > 0) // (QRC) bookmark met fallback user_key
    {
        __Log("Login by fallback_user_key {0}".format(Session("fallback_user_key")));
        doLogin(Session("fallback_user_key"), { noFacSession: true });// gezet in Shorturl.asp
        Session("login_by_fallback") = true; // fac_like_deep.asp wist dan sessie
        Session.Contents.Remove("fallback_user_key");
    }
}

// Pas héél laat api_user_key zetten om te voorkomen dat je
// bestaande sessie ongeldig wordt (Vinder/ telefoongids)
// JGL (later) maar volgens mij komt je dan hier helemaal niet dus is het niet echt relevant
var APIname = getQParam("API", "");
if (user_key < 0 && APIname)
{
    var API = new API_func(); // controleert vanzelf

    if (API.apidata.prs_perslid_key)
        doLogin(API.apidata.prs_perslid_key, { stateless: true });
}

// LogOff.asp kan Session("no_sso") gezet hebben
// Ook: met &sso=0
if (Request.QueryString("sso") == "0")
    Session("no_sso") = 1;

// Integrated authentication?
if (user_key < 0 && S("os_logon")
        && typeof Session("no_sso") == "undefined"
        && !Request.ServerVariables("HTTP_X_FACILITOR_API_KEY").Count
        && !Request.QueryString("APIKEY").Count)
{
    var extern=false;
    var i;
    for (var i in S("os_logon_exclude")) // via proxy binnen?
    {
        if (Request.ServerVariables(S("os_logon_exclude")[i]).count>0)
            extern = true;
    }
    if (!extern)
        IntegratedSSO(); // Voor licentieklanten
}

// Tenslotte proberen we automatische iDP's
var nosso = getQParamInt("nosso", 0) == 1;
if (user_key < 0 && !nosso
        && typeof Session("no_sso") == "undefined"
        && Request.ServerVariables("REQUEST_METHOD") == "GET"
        && !Request.ServerVariables("HTTP_X_FACILITOR_API_KEY").Count
        && !Request.QueryString("APIKEY").Count)
{
    var ip = String(Request.ServerVariables("REMOTE_ADDR"));
    var sql = "SELECT aut_idp_code"
            + "     , aut_idp_ipfilter"
            + "  FROM aut_idp"
            + " WHERE aut_idp_internal = 0"
            + "   AND aut_idp_ipauto = 1"
            + "   AND aut_idp_ipfilter IS NOT NULL"
            + " ORDER BY aut_idp_ipfilter"; // liefst wil ik sorteren met CIDR met de meeste significante bits (specifiekste) eerst?
    var oRs = Oracle.Execute(sql);
    while (user_key < 0 && !oRs.Eof)
    {
        var ip_restrict = oRs("aut_idp_ipfilter").Value;
        if (IP.isIPv4(ip) && IP.inAnySubnet(ip, ip_restrict))
            trySSO(oRs("aut_idp_code").Value);
        oRs.MoveNext();
    }
    oRs.Close();
}

if (user_key > 0) // dan hebben we (nu) een nieuwe user
{
    // Alvast nieuwe user_key loggen zodat je ziet wie er inlogt.
    if (typeof NO_ADDHEADER == "undefined" && Request.Servervariables("HTTP_FCLT_VERSION").Count > 0)
    {   // wordt opgepikt door FCLTAPI.DLL voor in de logging en daarna gestript. Niet in Fiddler dus
        Response.AddHeader ("FCLT_USERID", customerId + "\\" + String(user_key));
    }
}

__Log("== Leaving loginTry.asp ==");

%><% ASPPAGE_END(); %>
