Rework access

This commit is contained in:
Kay0u 2020-04-01 00:43:59 +02:00
parent d8c74604c0
commit 0fc89d0fc9
No known key found for this signature in database
GPG key ID: AE1DCADB6415A156
2 changed files with 111 additions and 184 deletions

View file

@ -29,8 +29,7 @@ local logger = require("log")
-- Just a note for the client to know that he passed through the SSO -- Just a note for the client to know that he passed through the SSO
ngx.header["X-SSO-WAT"] = "You've just been SSOed" ngx.header["X-SSO-WAT"] = "You've just been SSOed"
local is_logged_in = hlp.refresh_logged_in()
local is_logged_in = hlp.is_logged_in()
-- --
-- 1. LOGIN -- 1. LOGIN
@ -282,66 +281,27 @@ function serveYnhpanel()
scandir("/usr/share/ssowat/portal/assets/themes/"..conf.theme, serveThemeFile) scandir("/usr/share/ssowat/portal/assets/themes/"..conf.theme, serveThemeFile)
end end
-- local longest_protected_match = hlp.longest_url_path(hlp.get_matches("protected")) or "" local permission = hlp.get_best_permission()
-- 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
-- --
-- -- If the URL matches one of the `skipped_urls` in the configuration file,
-- -- it means that the URL should not be protected by the SSO and no header
-- -- has to be sent, even if the user is already authenticated.
-- --
--
-- 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
--
-- --
-- -- 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 is_logged_in then
-- serveYnhpanel()
--
-- hlp.set_headers()
-- end
-- logger.debug(ngx.var.uri.." is in unprotected_urls")
-- return hlp.pass()
-- end
--
-- if is_logged_in then
-- serveYnhpanel()
--
-- -- If user has no access to this URL, redirect him to the portal
-- if not hlp.has_access() then
-- return hlp.redirect(conf.portal_url)
-- end
--
-- -- If the user is authenticated and has access to the URL, set the headers
-- -- and let it be
-- hlp.set_headers()
-- return hlp.pass()
-- end
if permission then
if is_logged_in then
serveYnhpanel()
-- If the user is authenticated and has access to the URL, set the headers
-- and let it be
if permission["auth_header"] then
logger.debug("Set Headers")
hlp.set_headers()
end
end
-- If user has no access to this URL, redirect him to the portal
if not hlp.has_access(permission) then
return hlp.redirect(conf.portal_url)
end
return hlp.pass()
end
-- --
-- 7. Basic HTTP Authentication -- 7. Basic HTTP Authentication
@ -364,11 +324,14 @@ if auth_header then
logger.debug("User got authenticated through basic auth") logger.debug("User got authenticated through basic auth")
-- 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(user) then if not permission or not hlp.has_access(permission, user) then
return hlp.redirect(conf.portal_url) return hlp.redirect(conf.portal_url)
end end
hlp.set_headers(user) if permission["auth_header"] then
logger.debug("Set Headers")
hlp.set_headers(user)
end
return hlp.pass() return hlp.pass()
end end
end end

View file

@ -17,6 +17,8 @@ local url_parser = require "socket.url"
-- Import Perl regular expressions library -- Import Perl regular expressions library
local rex = require "rex_pcre" local rex = require "rex_pcre"
local is_logged_in = false
function refresh_config() function refresh_config()
conf = config.get_config() conf = config.get_config()
end end
@ -224,7 +226,7 @@ end
-- Check if the session cookies are set, and rehash server + client information -- Check if the session cookies are set, and rehash server + client information
-- to match the session hash. -- to match the session hash.
-- --
function is_logged_in() function refresh_logged_in()
local expireTime = ngx.var.cookie_SSOwAuthExpire local expireTime = ngx.var.cookie_SSOwAuthExpire
local user = ngx.var.cookie_SSOwAuthUser local user = ngx.var.cookie_SSOwAuthUser
local authHash = ngx.var.cookie_SSOwAuthHash local authHash = ngx.var.cookie_SSOwAuthHash
@ -250,7 +252,8 @@ function is_logged_in()
if hash ~= authHash then if hash ~= authHash then
logger.info("Hash "..authHash.." rejected for "..user.."@"..ngx.var.remote_addr) logger.info("Hash "..authHash.." rejected for "..user.."@"..ngx.var.remote_addr)
end end
return hash == authHash is_logged_in = hash == authHash
return is_logged_in
end end
end end
end end
@ -268,34 +271,60 @@ function log_access(user, uri)
end end
end end
function get_best_permission()
if not conf["permissions"] then
conf["permissions"] = {}
end
local permission_match = nil
local longest_url_match = ""
for permission_name, permission in pairs(conf["permissions"]) do
if next(permission['uris']) ~= nil then
for _, url in pairs(permission['uris']) do
if string.starts(url, "re:") then
url = string.sub(url, 4, string.len(url))
end
local m = match(ngx.var.host..ngx.var.uri..uri_args_string(), url)
if m ~= nil and string.len(m) > string.len(longest_url_match) then
longest_url_match = m
permission_match = permission
logger.debug("Match "..m)
end
end
end
end
return permission_match
end
-- Check whether a user is allowed to access a URL using the `permissions` 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(permission, user)
user = user or authUser user = user or authUser
logger.debug("User "..user.." try to access "..ngx.var.uri) if permission == nil then
-- Get the longest url permission
longest_permission_match = longest_url_path(permission_matches()) or ""
logger.debug("Longest permission match : "..longest_permission_match)
-- 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 return false
end end
-- Public access
if user == nil or permission["public"] then
logger.debug("A visitor try to access "..ngx.var.uri)
return permission["public"]
end
logger.debug("User "..user.." try to access "..ngx.var.uri)
-- All user in this permission -- All user in this permission
allowed_users = conf["permissions"][longest_permission_match] allowed_users = permission["users"]
-- The user has permission to access the content if he is in the list of this one -- The user has permission to access the content if he is in the list of this one
if allowed_users then if allowed_users then
for _, u in pairs(allowed_users) do for _, u in pairs(allowed_users) do
if u == user then if u == user then
logger.debug("User "..user.." can access "..ngx.var.uri) logger.debug("User "..user.." can access "..ngx.var.host..ngx.var.uri..uri_args_string())
log_access(user, longest_permission_match) log_access(user, ngx.var.host..ngx.var.uri..uri_args_string())
return true return true
end end
end end
@ -305,76 +334,6 @@ function has_access(user)
return false return false
end end
function permission_matches()
if not conf["permissions"] then
conf["permissions"] = {}
end
local url_matches = {}
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)
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
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
if longest and string.ends(longest, "/") then
longest = string.sub(longest, 1, -2)
end
return longest
end
-- Authenticate a user against the LDAP database using a username or an email -- Authenticate a user against the LDAP database using a username or an email
-- address. -- address.
-- Reminder: conf["ldap_identifier"] is "uid" by default -- Reminder: conf["ldap_identifier"] is "uid" by default
@ -557,7 +516,7 @@ function serve(uri, cache)
-- Load login.html as index -- Load login.html as index
if rel_path == "/" then if rel_path == "/" then
if is_logged_in() then if is_logged_in then
rel_path = "/portal.html" rel_path = "/portal.html"
else else
rel_path = "/login.html" rel_path = "/login.html"
@ -658,45 +617,48 @@ function get_data_for(view)
-- Needed if the LDAP db is changed outside ssowat (from the cli for example). -- Needed if the LDAP db is changed outside ssowat (from the cli for example).
-- Not doing it for ynhpanel.json only for performance reasons, -- Not doing it for ynhpanel.json only for performance reasons,
-- so the panel could show wrong first name, last name or main email address -- so the panel could show wrong first name, last name or main email address
if view ~= "ynhpanel.json" then -- TODO: What if we remove info during logout?
delete_user_info_cache(user) --if view ~= "ynhpanel.json" then
end -- delete_user_info_cache(user)
--end
-- Be sure cache is loaded -- Be sure cache is loaded
set_headers(user) if user then
set_headers(user)
local mails = get_mails(user) local mails = get_mails(user)
data = { data = {
connected = true, connected = true,
theme = conf.theme, theme = conf.theme,
portal_url = conf.portal_url, portal_url = conf.portal_url,
uid = user, uid = user,
cn = cache:get(user.."-cn"), cn = cache:get(user.."-cn"),
sn = cache:get(user.."-sn"), sn = cache:get(user.."-sn"),
givenName = cache:get(user.."-givenName"), givenName = cache:get(user.."-givenName"),
mail = mails["mail"], mail = mails["mail"],
mailalias = mails["mailalias"], mailalias = mails["mailalias"],
maildrop = mails["maildrop"], maildrop = mails["maildrop"],
app = {} app = {}
} }
local sorted_apps = {} local sorted_apps = {}
-- Add user's accessible URLs using the ACLs. -- Add user's accessible URLs using the ACLs.
-- It is typically used to build the app list. -- It is typically used to build the app list.
for permission_name, permission in pairs(conf["permissions"]) do for permission_name, permission in pairs(conf["permissions"]) do
-- We want to display a tile, and uris is not empty -- We want to display a tile, and uris is not empty
if permission['show_tile'] and next(permission['uris']) ~= nil then if permission['show_tile'] and next(permission['uris']) ~= nil and has_access(permission, user) then
url = permission['uris'][1] url = permission['uris'][1]
name = permission['label'] name = permission['label']
if ngx.var.host == conf["local_portal_domain"] then if ngx.var.host == conf["local_portal_domain"] then
url = string.gsub(url, conf["original_portal_domain"], conf["local_portal_domain"]) url = string.gsub(url, conf["original_portal_domain"], conf["local_portal_domain"])
end
table.insert(sorted_apps, name)
table.sort(sorted_apps)
table.insert(data["app"], index_of(sorted_apps, name), { url = url, name = name })
end end
table.insert(sorted_apps, name)
table.sort(sorted_apps)
table.insert(data["app"], index_of(sorted_apps, name), { url = url, name = name })
end end
end end
end end
@ -776,7 +738,7 @@ function edit_user()
-- Ensure that user is logged in and has passed information -- Ensure that user is logged in and has passed information
-- before continuing. -- before continuing.
if is_logged_in() and args if is_logged_in and args
then then
-- Set HTTP status to 201 -- Set HTTP status to 201
@ -1069,12 +1031,14 @@ function logout()
local args = ngx.req.get_uri_args() local args = ngx.req.get_uri_args()
-- Delete user cookie if logged in (that should always be the case) -- Delete user cookie if logged in (that should always be the case)
if is_logged_in() then if is_logged_in then
delete_cookie() delete_cookie()
cache:delete("session_"..authUser) cache:delete("session_"..authUser)
cache:delete(authUser.."-"..conf["ldap_identifier"]) -- Ugly trick to reload cache cache:delete(authUser.."-"..conf["ldap_identifier"]) -- Ugly trick to reload cache
cache:delete(authUser.."-password") cache:delete(authUser.."-password")
delete_user_info_cache(authUser)
flash("info", t("logged_out")) flash("info", t("logged_out"))
is_logged_in = false
end end
-- Redirect with the `r` URI argument if it exists or redirect to portal -- Redirect with the `r` URI argument if it exists or redirect to portal