Merge pull request #156 from kay0u/refactor-skipped/protected/unprotecter

Refactor skipped/protected/unprotecter
This commit is contained in:
Kayou 2020-03-15 13:02:32 +01:00 committed by GitHub
commit ca1ae6710d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 115 additions and 177 deletions

View file

@ -239,6 +239,14 @@ if conf["redirected_regex"] then
end end
local longest_protected_match = hlp.longest_url_path(hlp.get_matches("protected")) or ""
local longest_skipped_match = hlp.longest_url_path(hlp.get_matches("skipped")) or ""
local longest_unprotected_match = hlp.longest_url_path(hlp.get_matches("unprotected")) or ""
logger.debug("longest skipped "..longest_skipped_match)
logger.debug("longest unprotected "..longest_unprotected_match)
logger.debug("longest protected "..longest_protected_match)
-- --
-- 4. Skipped URLs -- 4. Skipped URLs
-- --
@ -247,66 +255,15 @@ end
-- has to be sent, even if the user is already authenticated. -- has to be sent, even if the user is already authenticated.
-- --
if conf["skipped_urls"] then if longest_skipped_match ~= ""
for _, url in ipairs(conf["skipped_urls"]) do and string.len(longest_skipped_match) >= string.len(longest_protected_match)
if (hlp.string.starts(ngx.var.host..ngx.var.uri..hlp.uri_args_string(), url) and string.len(longest_skipped_match) > string.len(longest_unprotected_match) then
or hlp.string.starts(ngx.var.uri..hlp.uri_args_string(), url))
then
logger.debug("Skipping "..ngx.var.uri) logger.debug("Skipping "..ngx.var.uri)
return hlp.pass() return hlp.pass()
end end
end
end
if conf["skipped_regex"] then
for _, regex in ipairs(conf["skipped_regex"]) do
if (hlp.match(ngx.var.host..ngx.var.uri..hlp.uri_args_string(), regex)
or hlp.match(ngx.var.uri..hlp.uri_args_string(), regex))
then
logger.debug("Skipping "..ngx.var.uri)
return hlp.pass()
end
end
end
-- --
-- 5. Protected URLs -- 5. Specific files (used in YunoHost)
--
-- If the URL matches one of the `protected_urls` in the configuration file,
-- we have to protect it even if the URL is also set in the `unprotected_urls`.
-- It could be useful if you want to unprotect every URL except a few
-- particular ones.
--
function is_protected()
if not conf["protected_urls"] then
conf["protected_urls"] = {}
end
if not conf["protected_regex"] then
conf["protected_regex"] = {}
end
for _, url in ipairs(conf["protected_urls"]) do
if hlp.string.starts(ngx.var.host..ngx.var.uri..hlp.uri_args_string(), url)
or hlp.string.starts(ngx.var.uri..hlp.uri_args_string(), url) then
logger.debug(ngx.var.uri.." is in protected_urls")
return true
end
end
for _, regex in ipairs(conf["protected_regex"]) do
if hlp.match(ngx.var.host..ngx.var.uri..hlp.uri_args_string(), regex)
or hlp.match(ngx.var.uri..hlp.uri_args_string(), regex) then
logger.debug(ngx.var.uri.." is in protected_regex")
return true
end
end
logger.debug(ngx.var.uri.." is not in protected_urls/regex")
return false
end
--
-- 6. Specific files (used in YunoHost)
-- --
-- We want to serve specific portal assets right at the root of the domain. -- We want to serve specific portal assets right at the root of the domain.
-- --
@ -334,7 +291,9 @@ function serveThemeFile(filename)
serveAsset("/ynhtheme/"..filename, "themes/"..conf.theme.."/"..filename) serveAsset("/ynhtheme/"..filename, "themes/"..conf.theme.."/"..filename)
end end
if hlp.is_logged_in() then function serveYnhpanel()
logger.debug("Serving ynhpanel")
-- serve ynhpanel files -- serve ynhpanel files
serveAsset("/ynh_portal.js", "js/ynh_portal.js") serveAsset("/ynh_portal.js", "js/ynh_portal.js")
serveAsset("/ynh_overlay.css", "css/ynh_overlay.css") serveAsset("/ynh_overlay.css", "css/ynh_overlay.css")
@ -343,6 +302,33 @@ if hlp.is_logged_in() then
-- but I didn't succeed to figure out where is the current location of the script -- but I didn't succeed to figure out where is the current location of the script
-- if you call it from "portal/assets/themes/" the ls fails -- if you call it from "portal/assets/themes/" the ls fails
scandir("/usr/share/ssowat/portal/assets/themes/"..conf.theme, serveThemeFile) scandir("/usr/share/ssowat/portal/assets/themes/"..conf.theme, serveThemeFile)
end
--
-- 6. Unprotected URLs
--
-- If the URL matches one of the `unprotected_urls` in the configuration file,
-- it means that the URL should not be protected by the SSO *but* headers have
-- to be sent if the user is already authenticated.
--
-- It means that you can let anyone access to an app, but if a user has already
-- been authenticated on the portal, he can have his authentication headers
-- passed to the app.
--
if longest_unprotected_match ~= ""
and string.len(longest_unprotected_match) > string.len(longest_protected_match) then
if hlp.is_logged_in() then
serveYnhpanel()
hlp.set_headers()
end
logger.debug(ngx.var.uri.." is in unprotected_urls")
return hlp.pass()
end
if hlp.is_logged_in() then
serveYnhpanel()
-- If user has no access to this URL, redirect him to the portal -- If user has no access to this URL, redirect him to the portal
if not hlp.has_access() then if not hlp.has_access() then
@ -356,51 +342,8 @@ if hlp.is_logged_in() then
end end
-- --
-- 7. Unprotected URLs -- 7. Basic HTTP Authentication
--
-- If the URL matches one of the `unprotected_urls` in the configuration file,
-- it means that the URL should not be protected by the SSO *but* headers have
-- to be sent if the user is already authenticated.
--
-- It means that you can let anyone access to an app, but if a user has already
-- been authenticated on the portal, he can have his authentication headers
-- passed to the app.
--
if conf["unprotected_urls"] then
for _, url in ipairs(conf["unprotected_urls"]) do
if (hlp.string.starts(ngx.var.host..ngx.var.uri..hlp.uri_args_string(), url)
or hlp.string.starts(ngx.var.uri..hlp.uri_args_string(), url))
and not is_protected() then
if hlp.is_logged_in() then
hlp.set_headers()
end
logger.debug(ngx.var.uri.." is in unprotected_urls")
return hlp.pass()
end
end
end
if conf["unprotected_regex"] then
for _, regex in ipairs(conf["unprotected_regex"]) do
if (hlp.match(ngx.var.host..ngx.var.uri..hlp.uri_args_string(), regex)
or hlp.match(ngx.var.uri..hlp.uri_args_string(), regex))
and not is_protected() then
if hlp.is_logged_in() then
hlp.set_headers()
end
logger.debug(ngx.var.uri.." is in unprotected_regex")
return hlp.pass()
end
end
end
--
-- 8. Basic HTTP Authentication
-- --
-- If the `Authorization` header is set before reaching the SSO, we want to -- If the `Authorization` header is set before reaching the SSO, we want to
-- match user and password against the user database. -- match user and password against the user database.
@ -436,7 +379,7 @@ end
-- --
-- 9. Redirect to login -- 8. Redirect to login
-- --
-- If no previous rule has matched, just redirect to the portal login. -- If no previous rule has matched, just redirect to the portal login.
-- The default is to protect every URL by default. -- The default is to protect every URL by default.
@ -452,6 +395,6 @@ end
-- when trying to access http://main.domain.tld/ (SSOwat finds that user aint -- when trying to access http://main.domain.tld/ (SSOwat finds that user aint
-- logged in, therefore redirects to SSO, which redirects to the back_url, which -- logged in, therefore redirects to SSO, which redirects to the back_url, which
-- redirect to SSO, ..) -- redirect to SSO, ..)
logger.debug("No rule found for this url. By default, redirecting to portal") logger.debug("No rule found for "..ngx.var.uri..". By default, redirecting to portal")
local back_url = "https://" .. ngx.var.host .. ngx.var.uri .. hlp.uri_args_string() local back_url = "https://" .. ngx.var.host .. ngx.var.uri .. hlp.uri_args_string()
return hlp.redirect(conf.portal_url.."?r="..ngx.encode_base64(back_url)) return hlp.redirect(conf.portal_url.."?r="..ngx.encode_base64(back_url))

View file

@ -262,100 +262,92 @@ function log_access(user, uri)
end 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 `permissions` directive
-- of the configuration file -- of the configuration file
function has_access(user) function has_access(user)
user = user or authUser user = user or authUser
if not conf["users"][user] then logger.debug("User "..user.." try to access "..ngx.var.uri)
conf = config.get_config()
end
-- If there are no `users` directive, or if the user has no ACL set, he can -- Get the longest url permission
-- access the URL by default longest_permission_match = longest_url_path(permission_matches()) or ""
if not conf["users"] or not conf["users"][user] then
logger.debug("No access rules defined for user "..user..", assuming it can access..")
return true
end
-- Loop through user's ACLs and return if the URL is authorized. logger.debug("Longest permission match : "..longest_permission_match)
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 -- If no permission matches, it means that there is no permission defined for this url.
-- a non-global domain name. if longest_permission_match == "" then
if ngx.var.host == conf["local_portal_domain"] then logger.debug("No access rules defined for user "..user..", assuming it cannot access.")
url = string.gsub(url, conf["original_portal_domain"], conf["local_portal_domain"])
end
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)
log_access(user, longest_allowed_match)
return true
else
logger.debug("Logged-in user cannot access "..ngx.var.uri)
return false return false
end end
-- All user in this permission
allowed_users = conf["permissions"][longest_permission_match]
-- The user has permission to access the content if he is in the list of this one
if allowed_users then
for _, u in pairs(allowed_users) do
if u == user then
logger.debug("User "..user.." can access "..ngx.var.uri)
log_access(user, longest_permission_match)
return true
end
end
end end
logger.debug("User "..user.." cannot access "..ngx.var.uri)
function protected_matches() return false
if not conf["protected_urls"] then
conf["protected_urls"] = {}
end end
if not conf["protected_regex"] then
conf["protected_regex"] = {} function permission_matches()
if not conf["permissions"] then
conf["permissions"] = {}
end end
local url_matches = {} local url_matches = {}
for _, url in ipairs(conf["protected_urls"]) do for url, permission in pairs(conf["permissions"]) do
if string.starts(ngx.var.host..ngx.var.uri..uri_args_string(), url) if string.starts(ngx.var.host..ngx.var.uri..uri_args_string(), url) then
or string.starts(ngx.var.uri..uri_args_string(), url) then logger.debug("Url permission match current uri : "..url)
logger.debug("protected_url match current uri : "..url)
table.insert(url_matches, 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
end end
return url_matches return url_matches
end end
function get_matches(section)
if not conf[section.."_urls"] then
conf[section.."_urls"] = {}
end
if not conf[section.."_regex"] then
conf[section.."_regex"] = {}
end
local url_matches = {}
for _, url in ipairs(conf[section.."_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(section.."_url match current uri : "..url)
table.insert(url_matches, url)
end
end
for _, regex in ipairs(conf[section.."_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(section.."_regex match current uri : "..regex.." with "..m1)
table.insert(url_matches, m1)
end
if m2 then
logger.debug(section.."_regex match current uri : "..regex.." with "..m2)
table.insert(url_matches, m2)
end
end
return url_matches
end
function longest_url_path(urls) function longest_url_path(urls)
local longest = nil local longest = nil
@ -369,6 +361,9 @@ function longest_url_path(urls)
longest = current longest = current
end end
end end
if longest and string.ends(longest, "/") then
longest = string.sub(longest, 1, -2)
end
return longest return longest
end end