diff --git a/access.lua b/access.lua index 8291bca..cdb7716 100644 --- a/access.lua +++ b/access.lua @@ -235,7 +235,8 @@ else logger:debug("Denied unauthenticated access to "..ngx.var.uri.." (corresponding perm: "..permission["id"]..")") has_access = false else - logger:debug("User "..authUser.." tries to access "..ngx.var.uri.." (corresponding perm: "..permission["id"]..")") + logger:debug( + "User "..authUser.." tries to access "..ngx.var.uri.." (corresponding perm: "..permission["id"]..")") -- The user has permission to access the content if s.he is in the list of allowed users if element_is_in_table(authUser, permission["users"]) then @@ -275,11 +276,12 @@ end -- 6. EFFECTIVELY PASS OR DENY ACCESS -- -- If the user has access (either because app is public OR logged in + authorized) --- -> pass + possibly inject the Basic Auth header on the fly such that the app can know which user is logged in +-- -> pass + possibly inject the Basic Auth header on the fly such that the app can know which user is logged in -- -- Otherwise, the user can't access --- -> either because not logged in at all, in that case, redirect to the portal WITH a callback url to redirect to after logging in --- -> or because user is logged in, but has no access .. in that case just redirect to the portal +-- -> either because not logged in at all, in that case, redirect to the portal WITH a callback url to redirect to +-- after logging in +-- -> or because user is logged in, but has no access .. in that case just redirect to the portal -- ########################################################################### function set_basic_auth_header() diff --git a/config.lua b/config.lua index aaec382..f8e90cb 100644 --- a/config.lua +++ b/config.lua @@ -30,7 +30,8 @@ function get_cookie_secret() local cookie_secret_path = conf_["cookie_secret_file"] or "/etc/yunohost/.ssowat_cookie_secret" if file_can_be_opened_for_reading(cookie_secret_path) == false then - ngx.log(ngx.STDERR, "Cookie secret file doesn't exist (yet?) or can't be opened for reading. Authentication will be disabled for now.") + ngx.log(ngx.STDERR, "Cookie secret file doesn't exist (yet?) or can't be opened for reading. " .. + "Authentication will be disabled for now.") return nil end @@ -40,7 +41,8 @@ function get_cookie_secret() cookie_secret_file:close() return cookie_secret else - ngx.log(ngx.STDERR, "Cookie secret file doesn't exist (yet?) or can't be opened for reading. Authentication will be disabled for now.") + ngx.log(ngx.STDERR, "Cookie secret file doesn't exist (yet?) or can't be opened for reading. " .. + "Authentication will be disabled for now.") return nil end end @@ -48,10 +50,12 @@ end function compare_attributes(file_attributes1, file_attributes2) if file_attributes1 == nil and file_attributes2 == nil then return true - elseif file_attributes1 == nil and file_attributes2 ~= nil or file_attributes1 ~= nil and file_attributes2 == nil then + elseif file_attributes1 == nil and file_attributes2 ~= nil or + file_attributes1 ~= nil and file_attributes2 == nil then return false end - return file_attributes1["modification"] == file_attributes2["modification"] and file_attributes1["size"] == file_attributes2["size"] + return file_attributes1["modification"] == file_attributes2["modification"] and + file_attributes1["size"] == file_attributes2["size"] end function get_config() @@ -60,7 +64,8 @@ function get_config() local new_config_attributes = lfs.attributes(conf_path, {"modification", "size"}) local new_config_persistent_attributes = lfs.attributes(conf_path..".persistent", {"modification", "size"}) - if compare_attributes(new_config_attributes, config_attributes) and compare_attributes(new_config_persistent_attributes, config_persistent_attributes) then + if compare_attributes(new_config_attributes, config_attributes) and + compare_attributes(new_config_persistent_attributes, config_persistent_attributes) then return conf -- If the file is being written, its size may be 0 and reloading fails, return the last valid config elseif new_config_attributes == nil or new_config_attributes["size"] == 0 then @@ -70,7 +75,7 @@ function get_config() -- If the timestamp of the modification or the size is different, reload the configuration. config_attributes = new_config_attributes config_persistent_attributes = new_config_persistent_attributes - + local conf_file = assert(io.open(conf_path, "r"), "Configuration file is missing") conf = json.decode(conf_file:read("*all")) conf_file:close() diff --git a/vendor/luajwtjitsi/luajwtjitsi.lua b/vendor/luajwtjitsi/luajwtjitsi.lua index bbd383b..b59ceeb 100644 --- a/vendor/luajwtjitsi/luajwtjitsi.lua +++ b/vendor/luajwtjitsi/luajwtjitsi.lua @@ -10,13 +10,13 @@ local pkey = require 'openssl.pkey' -- @param algo The digest algorithm to user when generating the signature: sha256, sha384, or sha512. -- @return The signature or nil and an error message. local function signRS (data, key, algo) - local privkey = pkey.new(key) - if privkey == nil then - return nil, 'Not a private PEM key' - else - local datadigest = digest.new(algo):update(data) - return privkey:sign(datadigest) - end + local privkey = pkey.new(key) + if privkey == nil then + return nil, 'Not a private PEM key' + else + local datadigest = digest.new(algo):update(data) + return privkey:sign(datadigest) + end end -- Verifies an RSA signature on the data. @@ -26,82 +26,83 @@ end -- @param algo The digest algorithm to user when generating the signature: sha256, sha384, or sha512. -- @return True if the signature is valid, false otherwise. Also returns false if the key is invalid. local function verifyRS (data, signature, key, algo) - local pubkey = pkey.new(key) - if pubkey == nil then - return false - end + local pubkey = pkey.new(key) + if pubkey == nil then + return false + end - local datadigest = digest.new(algo):update(data) - return pubkey:verify(signature, datadigest) + local datadigest = digest.new(algo):update(data) + return pubkey:verify(signature, datadigest) end local alg_sign = { - ['HS256'] = function(data, key) return hmac.new(key, 'sha256'):final(data) end, - ['HS384'] = function(data, key) return hmac.new(key, 'sha384'):final(data) end, - ['HS512'] = function(data, key) return hmac.new(key, 'sha512'):final(data) end, - ['RS256'] = function(data, key) return signRS(data, key, 'sha256') end, - ['RS384'] = function(data, key) return signRS(data, key, 'sha384') end, - ['RS512'] = function(data, key) return signRS(data, key, 'sha512') end + ['HS256'] = function(data, key) return hmac.new(key, 'sha256'):final(data) end, + ['HS384'] = function(data, key) return hmac.new(key, 'sha384'):final(data) end, + ['HS512'] = function(data, key) return hmac.new(key, 'sha512'):final(data) end, + ['RS256'] = function(data, key) return signRS(data, key, 'sha256') end, + ['RS384'] = function(data, key) return signRS(data, key, 'sha384') end, + ['RS512'] = function(data, key) return signRS(data, key, 'sha512') end } local alg_verify = { - ['HS256'] = function(data, signature, key) return signature == alg_sign['HS256'](data, key) end, - ['HS384'] = function(data, signature, key) return signature == alg_sign['HS384'](data, key) end, - ['HS512'] = function(data, signature, key) return signature == alg_sign['HS512'](data, key) end, - ['RS256'] = function(data, signature, key) return verifyRS(data, signature, key, 'sha256') end, - ['RS384'] = function(data, signature, key) return verifyRS(data, signature, key, 'sha384') end, - ['RS512'] = function(data, signature, key) return verifyRS(data, signature, key, 'sha512') end + ['HS256'] = function(data, signature, key) return signature == alg_sign['HS256'](data, key) end, + ['HS384'] = function(data, signature, key) return signature == alg_sign['HS384'](data, key) end, + ['HS512'] = function(data, signature, key) return signature == alg_sign['HS512'](data, key) end, + ['RS256'] = function(data, signature, key) return verifyRS(data, signature, key, 'sha256') end, + ['RS384'] = function(data, signature, key) return verifyRS(data, signature, key, 'sha384') end, + ['RS512'] = function(data, signature, key) return verifyRS(data, signature, key, 'sha512') end } -- Splits a token into segments, separated by '.'. -- @param token The full token to be split. -- @return A table of segments. local function split_token(token) - local segments={} - for str in string.gmatch(token, "([^\\.]+)") do - table.insert(segments, str) - end - return segments + local segments={} + for str in string.gmatch(token, "([^\\.]+)") do + table.insert(segments, str) + end + return segments end -- Parses a JWT token into it's header, body, and signature. -- @param token The JWT token to be parsed. -- @return A JSON header and body represented as a table, and a signature. local function parse_token(token) - local segments=split_token(token) - if #segments ~= 3 then - return nil, nil, nil, "Invalid token" - end - - local header, err = cjson_safe.decode(basexx.from_url64(segments[1])) - if err then - return nil, nil, nil, "Invalid header" - end - - local body, err = cjson_safe.decode(basexx.from_url64(segments[2])) - if err then - return nil, nil, nil, "Invalid body" - end - - local sig, err = basexx.from_url64(segments[3]) - if err then - return nil, nil, nil, "Invalid signature" - end - - return header, body, sig + local segments = split_token(token) + if #segments ~= 3 then + return nil, nil, nil, "Invalid token" + end + local err, header, body, sig + + header, err = cjson_safe.decode(basexx.from_url64(segments[1])) + if err then + return nil, nil, nil, "Invalid header" + end + + body, err = cjson_safe.decode(basexx.from_url64(segments[2])) + if err then + return nil, nil, nil, "Invalid body" + end + + sig, err = basexx.from_url64(segments[3]) + if err then + return nil, nil, nil, "Invalid signature" + end + + return header, body, sig end -- Removes the signature from a JWT token. -- @param token A JWT token. -- @return The token without its signature. local function strip_signature(token) - local segments=split_token(token) - if #segments ~= 3 then - return nil, nil, nil, "Invalid token" - end + local segments=split_token(token) + if #segments ~= 3 then + return nil, nil, nil, "Invalid token" + end - table.remove(segments) - return table.concat(segments, ".") + table.remove(segments) + return table.concat(segments, ".") end -- Verifies that a claim is in a list of allowed claims. Allowed claims can be exact values, or the @@ -110,16 +111,16 @@ end -- @param acceptedClaims A table of accepted claims. -- @return True if the claim was allowed, false otherwise. local function verify_claim(claim, acceptedClaims) - for i, accepted in ipairs(acceptedClaims) do - if accepted == '*' then - return true; - end - if claim == accepted then - return true; + for i, accepted in ipairs(acceptedClaims) do + if accepted == '*' then + return true; + end + if claim == accepted then + return true; + end end - end - return false; + return false; end local M = {} @@ -131,44 +132,46 @@ local M = {} -- @param header Additional values to put in the JWT header. -- @param The resulting JWT token, or nil and an error message. function M.encode(data, key, alg, header) - if type(data) ~= 'table' then return nil, "Argument #1 must be table" end - if type(key) ~= 'string' then return nil, "Argument #2 must be string" end + if type(data) ~= 'table' then return nil, "Argument #1 must be table" end + if type(key) ~= 'string' then return nil, "Argument #2 must be string" end - alg = alg or "HS256" + alg = alg or "HS256" - if not alg_sign[alg] then - return nil, "Algorithm not supported" - end + if not alg_sign[alg] then + return nil, "Algorithm not supported" + end - header = header or {} + header = header or {} - header['typ'] = 'JWT' - header['alg'] = alg + header['typ'] = 'JWT' + header['alg'] = alg - local headerEncoded, err = cjson_safe.encode(header) - if headerEncoded == nil then - return nil, err - end + local err, headerEncoded, dataEncoded - local dataEncoded, err = cjson_safe.encode(data) - if dataEncoded == nil then - return nil, err - end + headerEncoded, err = cjson_safe.encode(header) + if headerEncoded == nil then + return nil, err + end - local segments = { - basexx.to_url64(headerEncoded), - basexx.to_url64(dataEncoded) - } + dataEncoded, err = cjson_safe.encode(data) + if dataEncoded == nil then + return nil, err + end + + local segments = { + basexx.to_url64(headerEncoded), + basexx.to_url64(dataEncoded) + } - local signing_input = table.concat(segments, ".") - local signature, error = alg_sign[alg](signing_input, key) - if signature == nil then - return nil, error - end + local signing_input = table.concat(segments, ".") + local signature, error = alg_sign[alg](signing_input, key) + if signature == nil then + return nil, error + end - segments[#segments+1] = basexx.to_url64(signature) + segments[#segments+1] = basexx.to_url64(signature) - return table.concat(segments, ".") + return table.concat(segments, ".") end -- Verify that the token is valid, and if it is return the decoded JSON payload data. @@ -182,78 +185,78 @@ end -- be checked against this list. -- @return A table representing the JSON body of the token, or nil and an error message. function M.verify(token, expectedAlgo, key, acceptedIssuers, acceptedAudiences) - if type(token) ~= 'string' then return nil, "token argument must be string" end - if type(expectedAlgo) ~= 'string' then return nil, "algorithm argument must be string" end - if type(key) ~= 'string' then return nil, "key argument must be string" end - if acceptedIssuers ~= nil and type(acceptedIssuers) ~= 'table' then - return nil, "acceptedIssuers argument must be table" - end - if acceptedAudiences ~= nil and type(acceptedAudiences) ~= 'table' then - return nil, "acceptedAudiences argument must be table" - end - - if not alg_verify[expectedAlgo] then - return nil, "Algorithm not supported" - end - - local header, body, sig, err = parse_token(token) - if err ~= nil then - return nil, err - end - - -- Validate header - if not header.typ or header.typ ~= "JWT" then - return nil, "Invalid typ" - end - - if not header.alg or header.alg ~= expectedAlgo then - return nil, "Invalid or incorrect alg" - end - - -- Validate signature - if not alg_verify[expectedAlgo](strip_signature(token), sig, key) then - return nil, 'Invalid signature' - end - - -- Validate body - if body.exp and type(body.exp) ~= "number" then - return nil, "exp must be number" - end - - if body.nbf and type(body.nbf) ~= "number" then - return nil, "nbf must be number" - end - - - if body.exp and os.time() >= body.exp then - return nil, "Not acceptable by exp" - end - - if body.nbf and os.time() < body.nbf then - return nil, "Not acceptable by nbf" - end - - if acceptedIssuers ~= nil then - local issClaim = body.iss; - if issClaim == nil then - return nil, "'iss' claim is missing"; + if type(token) ~= 'string' then return nil, "token argument must be string" end + if type(expectedAlgo) ~= 'string' then return nil, "algorithm argument must be string" end + if type(key) ~= 'string' then return nil, "key argument must be string" end + if acceptedIssuers ~= nil and type(acceptedIssuers) ~= 'table' then + return nil, "acceptedIssuers argument must be table" + end + if acceptedAudiences ~= nil and type(acceptedAudiences) ~= 'table' then + return nil, "acceptedAudiences argument must be table" end - if not verify_claim(issClaim, acceptedIssuers) then - return nil, "invalid 'iss' claim"; + + if not alg_verify[expectedAlgo] then + return nil, "Algorithm not supported" + end + + local header, body, sig, err = parse_token(token) + if err ~= nil then + return nil, err + end + + -- Validate header + if not header.typ or header.typ ~= "JWT" then + return nil, "Invalid typ" end - end - if acceptedAudiences ~= nil then - local audClaim = body.aud; - if audClaim == nil then - return nil, "'aud' claim is missing"; + if not header.alg or header.alg ~= expectedAlgo then + return nil, "Invalid or incorrect alg" end - if not verify_claim(audClaim, acceptedAudiences) then - return nil, "invalid 'aud' claim"; + + -- Validate signature + if not alg_verify[expectedAlgo](strip_signature(token), sig, key) then + return nil, 'Invalid signature' + end + + -- Validate body + if body.exp and type(body.exp) ~= "number" then + return nil, "exp must be number" + end + + if body.nbf and type(body.nbf) ~= "number" then + return nil, "nbf must be number" + end + + + if body.exp and os.time() >= body.exp then + return nil, "Not acceptable by exp" + end + + if body.nbf and os.time() < body.nbf then + return nil, "Not acceptable by nbf" + end + + if acceptedIssuers ~= nil then + local issClaim = body.iss; + if issClaim == nil then + return nil, "'iss' claim is missing"; + end + if not verify_claim(issClaim, acceptedIssuers) then + return nil, "invalid 'iss' claim"; + end + end + + if acceptedAudiences ~= nil then + local audClaim = body.aud; + if audClaim == nil then + return nil, "'aud' claim is missing"; + end + if not verify_claim(audClaim, acceptedAudiences) then + return nil, "invalid 'aud' claim"; + end end - end - return body + return body end return M