Merge pull request #147 from YunoHost/allowed-vs-protected

More extensive check between allowed rules vs. protected rules
This commit is contained in:
Alexandre Aubin 2019-10-15 23:40:55 +02:00 committed by GitHub
commit 4643b75e74
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 96 additions and 21 deletions

View file

@ -208,17 +208,15 @@ end
-- If the URL matches one of the `redirected_urls` in the configuration file, -- If the URL matches one of the `redirected_urls` in the configuration file,
-- just redirect to the target URL/URI -- just redirect to the target URL/URI
-- --
-- A match function that uses PCRE regex as default -- The 'match' function uses PCRE regex as default
-- If '%.' is found in the regex, we assume it's a LUA regex (legacy code) -- If '%.' is found in the regex, we assume it's a LUA regex (legacy code)
-- 'match' returns the matched text.
function match(s, regex) function match(s, regex)
if not string.find(regex, '%%%.') then if not string.find(regex, '%%%.') then
if rex.match(s, regex) then return rex.match(s, regex)
return true else
return string.match(s,regex)
end end
elseif string.match(s,regex) then
return true
end
return false
end end
function detect_redirection(redirect_url) function detect_redirection(redirect_url)
@ -291,7 +289,6 @@ function is_protected()
return false return false
end end
-- --
-- 5. Skipped URLs -- 5. Skipped URLs
-- --

View file

@ -11,6 +11,10 @@ local cache = ngx.shared.cache
local conf = config.get_config() local conf = config.get_config()
local logger = require("log") local logger = require("log")
-- url parser, c.f. https://rosettacode.org/wiki/URL_parser#Lua
local url_parser = require "socket.url"
-- Read a FS stored file -- Read a FS stored file
function read_file(file) function read_file(file)
local f = io.open(file, "rb") local f = io.open(file, "rb")
@ -234,11 +238,11 @@ function is_logged_in()
return false return false
end end
function log_access(user, app) function log_access(user, uri)
local key = "ACC|"..user.."|"..app local key = "ACC|"..user.."|"..uri
local block = cache:get(key) local block = cache:get(key)
if block == nil then if block == nil then
logger.info("User "..user.."@"..ngx.var.remote_addr.." accesses "..app) logger.info("User "..user.."@"..ngx.var.remote_addr.." accesses "..uri)
cache:set(key, "block", 60) cache:set(key, "block", 60)
end end
end end
@ -246,9 +250,8 @@ end
-- Check whether a user is allowed to access a URL using the `users` directive -- Check whether a user is allowed to access a URL using the `users` directive
-- of the configuration file -- of the configuration file
function has_access(user, url) function has_access(user)
user = user or authUser user = user or authUser
url = url or ngx.var.host..ngx.var.uri
if not conf["users"][user] then if not conf["users"][user] then
conf = config.get_config() conf = config.get_config()
@ -262,22 +265,97 @@ function has_access(user, url)
end end
-- Loop through user's ACLs and return if the URL is authorized. -- Loop through user's ACLs and return if the URL is authorized.
for u, app in pairs(conf["users"][user]) do allowed_url_matches = {}
for url, app in pairs(conf["users"][user]) do
-- Replace the original domain by a local one if you are connected from -- Replace the original domain by a local one if you are connected from
-- a non-global domain name. -- a non-global domain name.
if ngx.var.host == conf["local_portal_domain"] then if ngx.var.host == conf["local_portal_domain"] then
u = string.gsub(u, conf["original_portal_domain"], conf["local_portal_domain"]) url = string.gsub(url, conf["original_portal_domain"], conf["local_portal_domain"])
end end
if string.starts(url, string.sub(u, 1, -2)) then if string.ends(url, "/") then
url = string.sub(url, 1, -1)
end
if string.starts(ngx.var.host..ngx.var.uri, url) then
logger.debug("User is allowed to access this match : "..url)
table.insert(allowed_url_matches,url)
end
end
-- Keep only the longest match and compare it to the longest protected
-- match e.g. we don't want to allow the user to access /foo/admin if
-- /foo/admin is protected, but this user is only allowed to access /foo
local longest_allowed_match = longest_url_path(allowed_url_matches) or ""
local longest_protected_match = longest_url_path(protected_matches()) or ""
logger.debug("Longest allowed match : "..longest_allowed_match)
logger.debug("Longest protected match : "..longest_protected_match)
-- For the user to be able to access the content, at least one rule should
-- exist and it should be the longest match
if longest_allowed_match ~= ""
and string.len(longest_allowed_match) >= string.len(longest_protected_match) then
logger.debug("Logged-in user can access "..ngx.var.uri) logger.debug("Logged-in user can access "..ngx.var.uri)
log_access(user, app) log_access(user, longest_allowed_match)
return true return true
end else
end
logger.debug("Logged-in user cannot access "..ngx.var.uri) logger.debug("Logged-in user cannot access "..ngx.var.uri)
return false return false
end
end
function protected_matches()
if not conf["protected_urls"] then
conf["protected_urls"] = {}
end
if not conf["protected_regex"] then
conf["protected_regex"] = {}
end
local url_matches = {}
for _, url in ipairs(conf["protected_urls"]) do
if string.starts(ngx.var.host..ngx.var.uri..uri_args_string(), url)
or string.starts(ngx.var.uri..uri_args_string(), url) then
logger.debug("protected_url match current uri : "..url)
table.insert(url_matches, url)
else
logger.debug("no match from "..url.." to "..ngx.var.uri)
end
end
for _, regex in ipairs(conf["protected_regex"]) do
local m1 = match(ngx.var.host..ngx.var.uri..uri_args_string(), regex)
local m2 = match(ngx.var.uri..uri_args_string(), regex)
if m1 then
logger.debug("protected_regex match current uri : "..regex.." with "..m1)
table.insert(url_matches, m1)
end
if m2 then
logger.debug("protected_regex match current uri : "..regex.." with "..m2)
table.insert(url_matches, m2)
end
end
return url_matches
end
function longest_url_path(urls)
local longest = nil
for _, url in ipairs(urls) do
-- Turn the url into a path, e.g.:
-- /foo/bar -> /foo/bar
-- domain.tld/foo/bar -> /foo/bar
-- https://domain.tld:1234/foo/bar -> /foo/bar
current = url_parser.parse(url).path
if not longest or string.len(longest) < string.len(current) then
longest = current
end
end
return longest
end end