<commit_before>--
-- helpers.lua
--
-- This is a file called at every request by the `access.lua` file. It contains
-- a set of useful functions related to HTTP and LDAP.
--

module('helpers', package.seeall)

local cache = ngx.shared.cache
local conf = config.get_config()

-- Read a FS stored file
function read_file(file)
    local f = io.open(file, "rb")
    if not f then return false end
    local content = f:read("*all")
    f:close()
    return content
end


-- Lua has no sugar :D
function is_in_table(t, v)
    for key, value in ipairs(t) do
        if value == v then return key end
    end
end


-- Get the index of a value in a table
function index_of(t,val)
    for k,v in ipairs(t) do
        if v == val then return k end
    end
end


-- Test whether a string starts with another
function string.starts(String, Start)
   return string.sub(String, 1, string.len(Start)) == Start
end


-- Test whether a string ends with another
function string.ends(String, End)
   return End=='' or string.sub(String, -string.len(End)) == End
end


-- Find a string by its translate key in the right language
function t(key)
   if conf.lang and i18n[conf.lang] and i18n[conf.lang][key] then
       return i18n[conf.lang][key]
   else
       return i18n[conf["default_language"]][key] or ""
   end
end


-- Store a message in the flash shared table in order to display it at the
-- next response
function flash(wat, message)
    if wat == "fail"
    or wat == "win"
    or wat == "info"
    then
        flashs[wat] = message
    end
end


-- Hash a string using hmac_sha512, return a hexa string
function hmac_sha512(key, message)
    local cache_key = key..":"..message

    if not cache:get(cache_key) then
        -- lua ecosystem is a disaster and it was not possible to find a good
        -- easily multiplatform integrable code for this
        --
        -- this is really dirty and probably leak the key and the message in the process list
        -- but if someone got there I guess we really have other problems so this is acceptable
        -- and also this is way better than the previous situation
        local pipe = io.popen("echo -n '" ..message:gsub("'", "'\\''").. "' | openssl sha512 -hmac '" ..key:gsub("'", "'\\''").. "'")

        -- openssl returns something like this:
        -- root@yunohost:~# echo -n "qsd" | openssl sha512 -hmac "key"
        -- (stdin)= f1c2b1658fe64c5a3d16459f2f4eea213e4181905c190235b060ab2a4e7d6a41c15ea2c246828537a1e32ae524b7a7ed309e6d296089194c3e3e3efb98c1fbe3
        --
        -- so we need to remove the "(stdin)= " at the beginning
        local hash = pipe:read():sub(string.len("(stdin)= ") + 1)
        pipe:close()

        cache:set(cache_key, hash, conf["session_timeout"])
        return hash
    else
        return cache:get(cache_key)
    end
end


-- Convert a table of arguments to an URI string
function uri_args_string(args)
    if not args then
        args = ngx.req.get_uri_args()
    end
    String = "?"
    for k,v in pairs(args) do
        String = String..tostring(k).."="..tostring(v).."&"
    end
    return string.sub(String, 1, string.len(String) - 1)
end


-- Set the Cross-Domain-Authentication key for a specific user
function set_cda_key()
    local cda_key = random_string()
    cache:set("CDA|"..cda_key, authUser, 10)
    return cda_key
end


-- Compute and set the authentication cookie
--
-- Sets 3 cookies containing:
-- * The username
-- * The expiration time
-- * A hash of those information along with the client IP address and a unique
--   session key
--
-- It enables the SSO to quickly retrieve the username and the session
-- expiration time, and to prove their authenticity to avoid session hijacking.
--
function set_auth_cookie(user, domain)
    local maxAge = conf["session_max_timeout"]
    local expire = ngx.req.start_time() + maxAge
    local session_key = cache:get("session_"..user)
    if not session_key then
        session_key = random_string()
        cache:add("session_"..user, session_key, conf["session_max_timeout"])
    end
    local hash = hmac_sha512(srvkey,
               user..
               "|"..expire..
               "|"..session_key)
    local cookie_str = "; Domain=."..domain..
                       "; Path=/"..
                       "; Expires="..os.date("%a, %d %b %Y %X UTC;", expire)..
                       "; Secure"

    ngx.header["Set-Cookie"] = {
        "SSOwAuthUser="..user..cookie_str,
        "SSOwAuthHash="..hash..cookie_str,
        "SSOwAuthExpire="..expire..cookie_str
    }
end


-- Expires the 3 session cookies
function delete_cookie()
    conf = config.get_config()

    local expired_time = "Thu, 01 Jan 1970 00:00:00 UTC;"
    for _, domain in ipairs(conf["domains"]) do
        local cookie_str = "; Domain=."..domain..
                           "; Path=/"..
                           "; Expires="..expired_time..
                           "; Secure"
        ngx.header["Set-Cookie"] = {
            "SSOwAuthUser="..cookie_str,
            "SSOwAuthHash="..cookie_str,
            "SSOwAuthExpire="..cookie_str
        }
    end
end


-- Expires the redirection cookie
function delete_redirect_cookie()
    local expired_time = "Thu, 01 Jan 1970 00:00:00 UTC;"
    local cookie_str = "; Path="..conf["portal_path"]..
                       "; Expires="..expired_time..
                       "; Secure"
    ngx.header["Set-Cookie"] = "SSOwAuthRedirect=;" ..cookie_str
end


-- Validate authentification
--
-- Check if the session cookies are set, and rehash server + client information
-- to match the session hash.
--
function is_logged_in()
    local expireTime = ngx.var.cookie_SSOwAuthExpire
    local user = ngx.var.cookie_SSOwAuthUser
    local authHash = ngx.var.cookie_SSOwAuthHash

    if expireTime and expireTime ~= ""
    and authHash and authHash ~= ""
    and user and user ~= ""
    then
        -- Check expire time
        if (ngx.req.start_time() <= tonumber(expireTime)) then
            -- Check hash
            local session_key = cache:get("session_"..user)
            if session_key and session_key ~= "" then
                -- Check cache<commit_msg>[fix] CVE CSRF with cookie setting<commit_after>--
-- helpers.lua
--
-- This is a file called at every request by the `access.lua` file. It contains
-- a set of useful functions related to HTTP and LDAP.
--

module('helpers', package.seeall)

local cache = ngx.shared.cache
local conf = config.get_config()

-- Read a FS stored file
function read_file(file)
    local f = io.open(file, "rb")
    if not f then return false end
    local content = f:read("*all")
    f:close()
    return content
end


-- Lua has no sugar :D
function is_in_table(t, v)
    for key, value in ipairs(t) do
        if value == v then return key end
    end
end


-- Get the index of a value in a table
function index_of(t,val)
    for k,v in ipairs(t) do
        if v == val then return k end
    end
end


-- Test whether a string starts with another
function string.starts(String, Start)
   return string.sub(String, 1, string.len(Start)) == Start
end


-- Test whether a string ends with another
function string.ends(String, End)
   return End=='' or string.sub(String, -string.len(End)) == End
end


-- Find a string by its translate key in the right language
function t(key)
   if conf.lang and i18n[conf.lang] and i18n[conf.lang][key] then
       return i18n[conf.lang][key]
   else
       return i18n[conf["default_language"]][key] or ""
   end
end


-- Store a message in the flash shared table in order to display it at the
-- next response
function flash(wat, message)
    if wat == "fail"
    or wat == "win"
    or wat == "info"
    then
        flashs[wat] = message
    end
end


-- Hash a string using hmac_sha512, return a hexa string
function hmac_sha512(key, message)
    local cache_key = key..":"..message

    if not cache:get(cache_key) then
        -- lua ecosystem is a disaster and it was not possible to find a good
        -- easily multiplatform integrable code for this
        --
        -- this is really dirty and probably leak the key and the message in the process list
        -- but if someone got there I guess we really have other problems so this is acceptable
        -- and also this is way better than the previous situation
        local pipe = io.popen("echo -n '" ..message:gsub("'", "'\\''").. "' | openssl sha512 -hmac '" ..key:gsub("'", "'\\''").. "'")

        -- openssl returns something like this:
        -- root@yunohost:~# echo -n "qsd" | openssl sha512 -hmac "key"
        -- (stdin)= f1c2b1658fe64c5a3d16459f2f4eea213e4181905c190235b060ab2a4e7d6a41c15ea2c246828537a1e32ae524b7a7ed309e6d296089194c3e3e3efb98c1fbe3
        --
        -- so we need to remove the "(stdin)= " at the beginning
        local hash = pipe:read():sub(string.len("(stdin)= ") + 1)
        pipe:close()

        cache:set(cache_key, hash, conf["session_timeout"])
        return hash
    else
        return cache:get(cache_key)
    end
end


-- Convert a table of arguments to an URI string
function uri_args_string(args)
    if not args then
        args = ngx.req.get_uri_args()
    end
    String = "?"
    for k,v in pairs(args) do
        String = String..tostring(k).."="..tostring(v).."&"
    end
    return string.sub(String, 1, string.len(String) - 1)
end


-- Set the Cross-Domain-Authentication key for a specific user
function set_cda_key()
    local cda_key = random_string()
    cache:set("CDA|"..cda_key, authUser, 10)
    return cda_key
end


-- Compute and set the authentication cookie
--
-- Sets 3 cookies containing:
-- * The username
-- * The expiration time
-- * A hash of those information along with the client IP address and a unique
--   session key
--
-- It enables the SSO to quickly retrieve the username and the session
-- expiration time, and to prove their authenticity to avoid session hijacking.
--
function set_auth_cookie(user, domain)
    local maxAge = conf["session_max_timeout"]
    local expire = ngx.req.start_time() + maxAge
    local session_key = cache:get("session_"..user)
    if not session_key then
        session_key = random_string()
        cache:add("session_"..user, session_key, conf["session_max_timeout"])
    end
    local hash = hmac_sha512(srvkey,
               user..
               "|"..expire..
               "|"..session_key)
    local cookie_str = "; Domain=."..domain..
                       "; Path=/"..
                       "; Expires="..os.date("%a, %d %b %Y %X UTC;", expire)..
                       "; Secure"..
                       "; HttpOnly"..
                       "; SameSite=Strict"

    ngx.header["Set-Cookie"] = {
        "SSOwAuthUser="..user..cookie_str,
        "SSOwAuthHash="..hash..cookie_str,
        "SSOwAuthExpire="..expire..cookie_str
    }
end


-- Expires the 3 session cookies
function delete_cookie()
    conf = config.get_config()

    local expired_time = "Thu, 01 Jan 1970 00:00:00 UTC;"
    for _, domain in ipairs(conf["domains"]) do
        local cookie_str = "; Domain=."..domain..
                           "; Path=/"..
                           "; Expires="..expired_time..
                           "; Secure"..
                           "; HttpOnly"..
                           "; SameSite=Strict"
        ngx.header["Set-Cookie"] = {
            "SSOwAuthUser="..cookie_str,
            "SSOwAuthHash="..cookie_str,
            "SSOwAuthExpire="..cookie_str
        }
    end
end


-- Expires the redirection cookie
function delete_redirect_cookie()
    local expired_time = "Thu, 01 Jan 1970 00:00:00 UTC;"
    local cookie_str = "; Path="..conf["portal_path"]..
                       "; Expires="..expired_time..
                       "; Secure"..
                       "; HttpOnly"..
                       "; SameSite=Strict"
    ngx.header["Set-Cookie"] = "SSOwAuthRedirect=;" ..cookie_str
end


-- Validate authentification
--
-- Check if the session cookies are set, and rehash server + client information
-- to match the session hash.
--
function is_logged_in()
    local expireTime = ngx.var.cookie_SSOwAuthExpire
    local user = ngx.var.cookie_SSOwAuthUser
    local authHash = ngx.var.cookie_SSOwAuthHash

    if expireTime and expireTime ~= ""
    and authHash and authHash ~= ""
    and user and user ~= ""
    then
        -- Check expire time
        if (ngx.req.start_time() <= tonumber(expireTime)) then
            -- Check hash
            local session_key = cache:get("session_"..user)
            if session_key and session_key ~= "" then
                -- Check cache
{'fraction_non_alphanumeric': 0.11615519160438861, 'fraction_numerical': 0.021784067419303547, 'mean_word_length': 2.287767903815996, 'pattern_counts': {'":': 2, '<': 5, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3979425', 'n_tokens_mistral': 4104, 'n_tokens_neox': 3826, 'n_words': 1216}