mirror of
https://github.com/YunoHost/SSOwat.git
synced 2024-09-03 20:06:27 +02:00
Merge pull request #172 from YunoHost/moar_refactoring
Moar refactoring (on top of permission rework)
This commit is contained in:
commit
c97372ee97
3 changed files with 126 additions and 137 deletions
154
access.lua
154
access.lua
|
@ -198,13 +198,41 @@ then
|
|||
end
|
||||
end
|
||||
|
||||
--
|
||||
-- 2 ... continued : portal assets that are available on every domains
|
||||
--
|
||||
-- For example: `https://whatever.org/ynhpanel.js` will serve the
|
||||
-- `/yunohost/sso/assets/js/ynhpanel.js` file.
|
||||
--
|
||||
|
||||
if is_logged_in then
|
||||
assets = {
|
||||
["/ynh_portal.js"] = "js/ynh_portal.js",
|
||||
["/ynh_overlay.css"] = "css/ynh_overlay.css"
|
||||
}
|
||||
theme_dir = "/usr/share/ssowat/portal/assets/themes/"..conf.theme
|
||||
local pfile = io.popen('find "'..theme_dir..'" -type f -exec realpath --relative-to "'..theme_dir..'" {} \\;')
|
||||
for filename in pfile:lines() do
|
||||
assets["/ynhtheme/"..filename] = "themes/"..conf.theme.."/"..filename
|
||||
end
|
||||
pfile:close()
|
||||
|
||||
for shortcut, full in pairs(assets) do
|
||||
if string.match(ngx.var.uri, "^"..shortcut.."$") then
|
||||
logger.debug("Serving static asset "..full)
|
||||
return hlp.serve("/yunohost/sso/assets/"..full, "static_asset")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--
|
||||
-- 3. Redirected URLs
|
||||
-- 3. REDIRECTED URLS
|
||||
--
|
||||
-- If the URL matches one of the `redirected_urls` in the configuration file,
|
||||
-- just redirect to the target URL/URI
|
||||
--
|
||||
|
||||
function detect_redirection(redirect_url)
|
||||
if hlp.string.starts(redirect_url, "http://")
|
||||
or hlp.string.starts(redirect_url, "https://") then
|
||||
|
@ -239,116 +267,73 @@ if conf["redirected_regex"] then
|
|||
end
|
||||
|
||||
--
|
||||
-- 4. Basic HTTP Authentication
|
||||
-- 4. IDENTIFY THE RELEVANT PERMISSION
|
||||
--
|
||||
-- If the `Authorization` header is set before reaching the SSO, we want to
|
||||
-- match user and password against the user database.
|
||||
-- In particular, the conf is filled with permissions such as:
|
||||
--
|
||||
-- It allows you to bypass the cookie-based procedure with a per-request
|
||||
-- authentication. Very usefull when you are trying to reach a specific URL
|
||||
-- via cURL for example.
|
||||
-- "foobar": {
|
||||
-- "auth_header": false,
|
||||
-- "label": "Foobar permission",
|
||||
-- "public": false,
|
||||
-- "show_tile": true,
|
||||
-- "uris": [
|
||||
-- "yolo.test/foobar",
|
||||
-- "re:^[^/]*/%.well%-known/foobar/.*$",
|
||||
-- ],
|
||||
-- "users": ["alice", "bob"]
|
||||
-- }
|
||||
--
|
||||
--
|
||||
-- And we find the best matching permission by trying to match the request uri
|
||||
-- against all the uris rules/regexes from the conf and keep the longest matching one.
|
||||
--
|
||||
|
||||
if not is_logged_in then
|
||||
local auth_header = ngx.req.get_headers()["Authorization"]
|
||||
permission = nil
|
||||
longest_url_match = ""
|
||||
|
||||
if auth_header then
|
||||
_, _, b64_cred = string.find(auth_header, "^Basic%s+(.+)$")
|
||||
_, _, user, password = string.find(ngx.decode_base64(b64_cred), "^(.+):(.+)$")
|
||||
user = hlp.authenticate(user, password)
|
||||
if user then
|
||||
logger.debug("User got authenticated through basic auth")
|
||||
|
||||
-- If user has no access to this URL, redirect him to the portal
|
||||
if not permission or not hlp.has_access(permission, user) then
|
||||
return hlp.redirect(conf.portal_url)
|
||||
for permission_name, permission_infos in pairs(conf["permissions"]) do
|
||||
if next(permission_infos['uris']) ~= nil then
|
||||
for _, url in pairs(permission_infos['uris']) do
|
||||
if string.starts(url, "re:") then
|
||||
url = string.sub(url, 4, string.len(url))
|
||||
end
|
||||
|
||||
if permission["auth_header"] then
|
||||
logger.debug("Set Headers")
|
||||
hlp.set_headers(user)
|
||||
local m = hlp.match(ngx.var.host..ngx.var.uri..hlp.uri_args_string(), url)
|
||||
if m ~= nil and string.len(m) > string.len(longest_url_match) then
|
||||
longest_url_match = m
|
||||
permission = permission_infos
|
||||
permission["id"] = permission_name
|
||||
end
|
||||
return hlp.pass()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--
|
||||
-- 5. Specific files (used in YunoHost)
|
||||
--
|
||||
-- We want to serve specific portal assets right at the root of the domain.
|
||||
-- 5. APPLY PERMISSION
|
||||
--
|
||||
-- For example: `https://mydomain.org/ynhpanel.js` will serve the
|
||||
-- `/yunohost/sso/assets/js/ynhpanel.js` file.
|
||||
--
|
||||
|
||||
function scandir(directory, callback)
|
||||
-- use find (and not ls) to list only files recursively and with their full path relative to the asked directory
|
||||
local pfile = io.popen('find "'..directory..'" -type f -exec realpath --relative-to "'..directory..'" {} \\;')
|
||||
for filename in pfile:lines() do
|
||||
callback(filename)
|
||||
end
|
||||
pfile:close()
|
||||
end
|
||||
-- 1st case : client has access
|
||||
|
||||
function serveAsset(shortcut, full)
|
||||
if string.match(ngx.var.uri, "^"..shortcut.."$") then
|
||||
logger.debug("Serving static asset "..full)
|
||||
hlp.serve("/yunohost/sso/assets/"..full, "static_asset")
|
||||
end
|
||||
end
|
||||
if hlp.has_access(permission) then
|
||||
|
||||
function serveThemeFile(filename)
|
||||
serveAsset("/ynhtheme/"..filename, "themes/"..conf.theme.."/"..filename)
|
||||
end
|
||||
|
||||
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")
|
||||
-- serve theme's files
|
||||
-- FIXME? I think it would be better here not to use an absolute path
|
||||
-- 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
|
||||
|
||||
local permission = hlp.get_best_permission()
|
||||
|
||||
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"] and hlp.has_access(permission) then
|
||||
logger.debug("Set Headers")
|
||||
-- If the user is logged in, we set some additional 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)
|
||||
-- If Basic Authorization header are disabled for this permission,
|
||||
-- remove them from the response
|
||||
if not permission["auth_header"] then
|
||||
ngx.req.clear_header("Authorization")
|
||||
end
|
||||
end
|
||||
|
||||
return hlp.pass()
|
||||
end
|
||||
|
||||
--
|
||||
-- 6. Redirect to login
|
||||
--
|
||||
-- If no previous rule has matched, just redirect to the portal login.
|
||||
-- The default is to protect every URL by default.
|
||||
--
|
||||
-- 2nd case : no access ... redirect to portal / login form
|
||||
else
|
||||
|
||||
-- Force the scheme to HTTPS. This is to avoid an issue with redirection loop
|
||||
-- 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 "..ngx.var.uri..". By default, redirecting to portal")
|
||||
if is_logged_in then
|
||||
return hlp.redirect(conf.portal_url)
|
||||
else
|
||||
|
@ -361,3 +346,4 @@ else
|
|||
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))
|
||||
end
|
||||
end
|
||||
|
|
|
@ -60,7 +60,8 @@ function get_config()
|
|||
allow_mail_authentication = true,
|
||||
default_language = "en",
|
||||
theme = "default",
|
||||
logging = "fatal" -- Only log fatal messages by default (so apriori nothing)
|
||||
logging = "fatal", -- Only log fatal messages by default (so apriori nothing)
|
||||
permissions = {}
|
||||
}
|
||||
|
||||
|
||||
|
|
82
helpers.lua
82
helpers.lua
|
@ -260,6 +260,30 @@ function refresh_logged_in()
|
|||
end
|
||||
end
|
||||
|
||||
-- If client set the `Authorization` header before reaching the SSO,
|
||||
-- we want to match user and password against the user database.
|
||||
--
|
||||
-- It allows to bypass the cookie-based procedure with a per-request
|
||||
-- authentication. This is useful to authenticate on the SSO during
|
||||
-- curl requests for example.
|
||||
|
||||
local auth_header = ngx.req.get_headers()["Authorization"]
|
||||
|
||||
if auth_header then
|
||||
_, _, b64_cred = string.find(auth_header, "^Basic%s+(.+)$")
|
||||
_, _, user, password = string.find(ngx.decode_base64(b64_cred), "^(.+):(.+)$")
|
||||
user = authenticate(user, password)
|
||||
if user then
|
||||
logger.debug("User got authenticated through basic auth")
|
||||
authUser = user
|
||||
is_logged_in = true
|
||||
else
|
||||
is_logged_in = false
|
||||
end
|
||||
return is_logged_in
|
||||
end
|
||||
|
||||
is_logged_in = false
|
||||
return false
|
||||
end
|
||||
|
||||
|
@ -272,67 +296,45 @@ function log_access(user, uri)
|
|||
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
|
||||
-- of the configuration file
|
||||
function has_access(permission, user)
|
||||
user = user or authUser
|
||||
|
||||
if permission == nil then
|
||||
logger.debug("No permission matching request for "..ngx.var.uri)
|
||||
return false
|
||||
end
|
||||
|
||||
-- Public access
|
||||
if user == nil or permission["public"] then
|
||||
user = user or "A visitor"
|
||||
logger.debug(user.." tries to access "..ngx.var.uri)
|
||||
logger.debug(user.." tries to access "..ngx.var.uri.." (corresponding perm: "..permission["id"]..")")
|
||||
return permission["public"]
|
||||
end
|
||||
|
||||
logger.debug("User "..user.." tries to access "..ngx.var.uri)
|
||||
logger.debug("User "..user.." tries to access "..ngx.var.uri.." (corresponding perm: "..permission["id"]..")")
|
||||
|
||||
-- All user in this permission
|
||||
allowed_users = permission["users"]
|
||||
|
||||
-- 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
|
||||
-- The user has permission to access the content if he is in the list of allowed users
|
||||
if element_is_in_table(user, permission["users"]) then
|
||||
logger.debug("User "..user.." can access "..ngx.var.host..ngx.var.uri..uri_args_string())
|
||||
log_access(user, ngx.var.host..ngx.var.uri..uri_args_string())
|
||||
return true
|
||||
else
|
||||
logger.debug("User "..user.." cannot access "..ngx.var.uri)
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
function element_is_in_table(element, table)
|
||||
if table then
|
||||
for _, el in pairs(table) do
|
||||
if el == element then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
logger.debug("User "..user.." cannot access "..ngx.var.uri)
|
||||
return false
|
||||
end
|
||||
|
||||
|
@ -512,7 +514,7 @@ end
|
|||
-- It is used to render the SSOwat portal *only*.
|
||||
function serve(uri, cache)
|
||||
|
||||
logger.debug("Serving portal uri "..uri.." (if the corresponding file exists)")
|
||||
logger.debug("Serving portal uri "..uri)
|
||||
|
||||
rel_path = string.gsub(uri, conf["portal_path"], "/")
|
||||
|
||||
|
@ -649,7 +651,7 @@ function get_data_for(view)
|
|||
-- It is typically used to build the app list.
|
||||
for permission_name, permission in pairs(conf["permissions"]) do
|
||||
-- We want to display a tile, and uris is not empty
|
||||
if permission['show_tile'] and next(permission['uris']) ~= nil and has_access(permission, user) then
|
||||
if permission['show_tile'] and next(permission['uris']) ~= nil and element_is_in_table(user, permission["users"]) then
|
||||
url = permission['uris'][1]
|
||||
name = permission['label']
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue