mirror of
https://github.com/YunoHost/SSOwat.git
synced 2024-09-03 20:06:27 +02:00
Rework access
This commit is contained in:
parent
d8c74604c0
commit
0fc89d0fc9
2 changed files with 111 additions and 184 deletions
89
access.lua
89
access.lua
|
@ -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
|
||||||
|
|
206
helpers.lua
206
helpers.lua
|
@ -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
|
||||||
|
|
Loading…
Add table
Reference in a new issue