diff --git a/access.lua b/access.lua index c24ae09..1ec8672 100644 --- a/access.lua +++ b/access.lua @@ -239,6 +239,14 @@ if conf["redirected_regex"] then 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 -- @@ -247,66 +255,15 @@ end -- has to be sent, even if the user is already authenticated. -- -if conf["skipped_urls"] then - for _, url in ipairs(conf["skipped_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("Skipping "..ngx.var.uri) - return hlp.pass() - 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 +if longest_skipped_match ~= "" +and string.len(longest_skipped_match) >= string.len(longest_protected_match) +and string.len(longest_skipped_match) > string.len(longest_unprotected_match) then + logger.debug("Skipping "..ngx.var.uri) + return hlp.pass() end -- --- 5. Protected URLs --- --- 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) +-- 5. Specific files (used in YunoHost) -- -- 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) end -if hlp.is_logged_in() then +function serveYnhpanel() + logger.debug("Serving ynhpanel") + -- serve ynhpanel files serveAsset("/ynh_portal.js", "js/ynh_portal.js") 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 -- if you call it from "portal/assets/themes/" the ls fails 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 not hlp.has_access() then @@ -356,51 +342,8 @@ if hlp.is_logged_in() then end - -- --- 7. 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 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 +-- 7. Basic HTTP Authentication -- -- If the `Authorization` header is set before reaching the SSO, we want to -- 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. -- 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 -- logged in, therefore redirects to SSO, which redirects to the back_url, which -- 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() return hlp.redirect(conf.portal_url.."?r="..ngx.encode_base64(back_url)) diff --git a/helpers.lua b/helpers.lua index fd45715..0df0551 100644 --- a/helpers.lua +++ b/helpers.lua @@ -262,100 +262,92 @@ function log_access(user, uri) 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 function has_access(user) user = user or authUser - if not conf["users"][user] then - conf = config.get_config() - end + logger.debug("User "..user.." try to access "..ngx.var.uri) + + -- Get the longest url permission + longest_permission_match = longest_url_path(permission_matches()) or "" - -- If there are no `users` directive, or if the user has no ACL set, he can - -- access the URL by default - 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 + logger.debug("Longest permission match : "..longest_permission_match) - -- Loop through user's ACLs and return if the URL is authorized. - 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 - -- a non-global domain name. - if ngx.var.host == conf["local_portal_domain"] then - 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) + -- If no permission matches, it means that there is no permission defined for this url. + if longest_permission_match == "" then + logger.debug("No access rules defined for user "..user..", assuming it cannot access.") return false 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 + + logger.debug("User "..user.." cannot access "..ngx.var.uri) + return false end - -function protected_matches() - if not conf["protected_urls"] then - conf["protected_urls"] = {} - end - if not conf["protected_regex"] then - conf["protected_regex"] = {} +function permission_matches() + if not conf["permissions"] then + conf["permissions"] = {} 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) + for url, permission in pairs(conf["permissions"]) do + if string.starts(ngx.var.host..ngx.var.uri..uri_args_string(), url) then + logger.debug("Url permission 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 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) local longest = nil @@ -369,6 +361,9 @@ function longest_url_path(urls) longest = current end end + if longest and string.ends(longest, "/") then + longest = string.sub(longest, 1, -2) + end return longest end