2013-10-15 13:58:16 +02:00
|
|
|
--
|
2015-02-02 00:05:09 +01:00
|
|
|
-- access.lua
|
|
|
|
--
|
|
|
|
-- This file is executed at every request on a protected domain or server.
|
|
|
|
-- You just have to read this file normally to understand how and when the
|
|
|
|
-- request is handled: redirected, forbidden, bypassed or served.
|
2013-10-15 13:58:16 +02:00
|
|
|
--
|
2014-04-10 20:42:43 +02:00
|
|
|
|
2015-02-12 12:08:52 +01:00
|
|
|
-- Get the `cache` persistent shared table
|
2015-05-16 09:42:26 +02:00
|
|
|
local cache = ngx.shared.cache
|
2015-02-12 12:08:52 +01:00
|
|
|
|
|
|
|
-- Generate a unique token if it has not been generated yet
|
|
|
|
srvkey = cache:get("srvkey")
|
|
|
|
if not srvkey then
|
2015-04-30 15:08:08 +02:00
|
|
|
srvkey = random_string()
|
2015-02-12 12:08:52 +01:00
|
|
|
cache:add("srvkey", srvkey)
|
|
|
|
end
|
2014-04-17 14:51:47 +02:00
|
|
|
|
2015-02-02 00:05:09 +01:00
|
|
|
-- Import helpers
|
2015-05-16 09:42:26 +02:00
|
|
|
local hlp = require "helpers"
|
2013-10-15 10:11:39 +02:00
|
|
|
|
2020-03-29 18:02:49 +02:00
|
|
|
-- Initialize and get configuration
|
|
|
|
hlp.refresh_config()
|
|
|
|
local conf = hlp.get_config()
|
|
|
|
|
2019-10-03 20:42:01 +02:00
|
|
|
-- Load logging module
|
|
|
|
local logger = require("log")
|
|
|
|
|
2015-02-02 00:05:09 +01:00
|
|
|
-- Just a note for the client to know that he passed through the SSO
|
2013-10-16 11:57:53 +02:00
|
|
|
ngx.header["X-SSO-WAT"] = "You've just been SSOed"
|
2013-10-15 10:11:39 +02:00
|
|
|
|
2020-04-01 00:43:59 +02:00
|
|
|
local is_logged_in = hlp.refresh_logged_in()
|
2020-03-31 02:20:40 +02:00
|
|
|
|
2013-10-15 13:58:16 +02:00
|
|
|
--
|
2015-02-02 00:05:09 +01:00
|
|
|
-- 1. LOGIN
|
2013-10-15 13:58:16 +02:00
|
|
|
--
|
2015-02-02 00:05:09 +01:00
|
|
|
-- example: https://mydomain.org/?sso_login=a6e5320f
|
|
|
|
--
|
|
|
|
-- If the `sso_login` URI argument is set, try a cross-domain authentication
|
|
|
|
-- with the token passed as argument
|
2013-10-20 17:24:44 +02:00
|
|
|
--
|
2014-04-17 12:21:11 +02:00
|
|
|
if ngx.var.host ~= conf["portal_domain"] and ngx.var.request_method == "GET" then
|
|
|
|
uri_args = ngx.req.get_uri_args()
|
|
|
|
if uri_args[conf.login_arg] then
|
|
|
|
cda_key = uri_args[conf.login_arg]
|
2015-02-12 12:08:52 +01:00
|
|
|
|
2015-05-04 19:29:55 +02:00
|
|
|
-- Use the `cache` shared table where a username is associated with
|
2015-02-12 12:08:52 +01:00
|
|
|
-- a CDA key
|
2017-05-17 10:44:26 +02:00
|
|
|
user = cache:get("CDA|"..cda_key)
|
2015-05-04 19:29:55 +02:00
|
|
|
if user then
|
|
|
|
hlp.set_auth_cookie(user, ngx.var.host)
|
2019-10-03 20:42:01 +02:00
|
|
|
logger.info("Cross-domain authentication: "..user.." connected on "..ngx.var.host)
|
2017-05-17 10:44:26 +02:00
|
|
|
cache:delete("CDA|"..cda_key)
|
2014-04-17 12:21:11 +02:00
|
|
|
end
|
2015-02-12 12:08:52 +01:00
|
|
|
|
|
|
|
uri_args[conf.login_arg] = nil
|
|
|
|
return hlp.redirect(ngx.var.uri..hlp.uri_args_string(uri_args))
|
2014-02-04 16:28:54 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
|
2015-02-02 00:05:09 +01:00
|
|
|
--
|
|
|
|
-- 2. PORTAL
|
|
|
|
--
|
|
|
|
-- example: https://mydomain.org/ssowat*
|
|
|
|
--
|
|
|
|
-- If the URL matches the portal URL, serve a portal file or proceed to a
|
2015-02-15 12:31:23 +01:00
|
|
|
-- portal operation
|
2015-02-02 00:05:09 +01:00
|
|
|
--
|
2020-03-31 02:20:40 +02:00
|
|
|
if (ngx.var.host == conf["portal_domain"] or is_logged_in)
|
2015-02-12 12:08:52 +01:00
|
|
|
and hlp.string.starts(ngx.var.uri, string.sub(conf["portal_path"], 1, -2))
|
2013-10-20 17:24:44 +02:00
|
|
|
then
|
2015-02-12 12:08:52 +01:00
|
|
|
|
|
|
|
-- `GET` method will serve a portal file
|
2013-10-20 17:24:44 +02:00
|
|
|
if ngx.var.request_method == "GET" then
|
|
|
|
|
2014-11-13 20:27:01 +01:00
|
|
|
-- Force portal scheme
|
|
|
|
if ngx.var.scheme ~= conf["portal_scheme"] then
|
2015-02-12 12:08:52 +01:00
|
|
|
return hlp.redirect(conf.portal_url)
|
2014-11-13 20:27:01 +01:00
|
|
|
end
|
|
|
|
|
2015-02-02 00:05:09 +01:00
|
|
|
-- Add a trailing `/` if not present
|
2013-10-21 20:43:12 +02:00
|
|
|
if ngx.var.uri.."/" == conf["portal_path"] then
|
2015-02-12 12:08:52 +01:00
|
|
|
return hlp.redirect(conf.portal_url)
|
2013-10-21 20:43:12 +02:00
|
|
|
end
|
|
|
|
|
2017-02-23 23:15:30 +01:00
|
|
|
-- Get request arguments
|
2013-10-20 17:24:44 +02:00
|
|
|
uri_args = ngx.req.get_uri_args()
|
2015-02-12 12:08:52 +01:00
|
|
|
|
|
|
|
-- Logout is also called via a `GET` method
|
|
|
|
-- TODO: change this ?
|
2013-10-20 17:24:44 +02:00
|
|
|
if uri_args.action and uri_args.action == 'logout' then
|
2019-10-03 20:42:01 +02:00
|
|
|
logger.debug("Logging out")
|
2015-02-12 12:08:52 +01:00
|
|
|
return hlp.logout()
|
2013-10-20 17:24:44 +02:00
|
|
|
|
2015-02-15 12:31:23 +01:00
|
|
|
-- If the `r` URI argument is set, it means that we want to
|
|
|
|
-- be redirected (typically after a login phase)
|
2020-03-31 02:20:40 +02:00
|
|
|
elseif is_logged_in and uri_args.r then
|
2017-02-23 23:15:30 +01:00
|
|
|
-- Decode back url
|
2014-04-17 12:21:11 +02:00
|
|
|
back_url = ngx.decode_base64(uri_args.r)
|
2015-02-15 12:31:23 +01:00
|
|
|
|
2017-02-23 23:15:30 +01:00
|
|
|
-- If `back_url` contains line break, someone is probably trying to
|
|
|
|
-- pass some additional headers
|
|
|
|
if string.match(back_url, "(.*)\n") then
|
|
|
|
hlp.flash("fail", hlp.t("redirection_error_invalid_url"))
|
2019-10-03 20:42:01 +02:00
|
|
|
logger.error("Redirection url is invalid")
|
2017-02-23 23:15:30 +01:00
|
|
|
return hlp.redirect(conf.portal_url)
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Get managed domains
|
|
|
|
local managed_domain = false
|
|
|
|
for _, domain in ipairs(conf["domains"]) do
|
|
|
|
local escaped_domain = domain:gsub("-", "%%-") -- escape dash for pattern matching
|
|
|
|
if string.match(back_url, "^http[s]?://"..escaped_domain.."/") then
|
2019-10-03 20:42:01 +02:00
|
|
|
logger.debug("Redirection to a managed domain found")
|
2017-02-23 23:15:30 +01:00
|
|
|
managed_domain = true
|
|
|
|
break
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- If redirection does not match one of the managed domains
|
|
|
|
-- redirect to portal home page
|
|
|
|
if not managed_domain then
|
|
|
|
hlp.flash("fail", hlp.t("redirection_error_unmanaged_domain"))
|
2019-10-03 20:42:01 +02:00
|
|
|
logger.error("Redirection to an external domain aborted")
|
2017-02-23 23:15:30 +01:00
|
|
|
return hlp.redirect(conf.portal_url)
|
|
|
|
end
|
|
|
|
|
|
|
|
|
2015-02-15 12:31:23 +01:00
|
|
|
-- In case the `back_url` is not on the same domain than the
|
|
|
|
-- current one, create a redirection with a CDA key
|
2017-02-23 23:14:03 +01:00
|
|
|
local ngx_host_escaped = ngx.var.host:gsub("-", "%%-") -- escape dash for pattern matching
|
|
|
|
if not string.match(back_url, "^http[s]?://"..ngx_host_escaped.."/")
|
2014-04-17 12:21:11 +02:00
|
|
|
and not string.match(back_url, ".*"..conf.login_arg.."=%d+$") then
|
2015-05-16 09:42:26 +02:00
|
|
|
local cda_key = hlp.set_cda_key()
|
2014-04-17 12:21:11 +02:00
|
|
|
if string.match(back_url, ".*?.*") then
|
|
|
|
back_url = back_url.."&"
|
|
|
|
else
|
|
|
|
back_url = back_url.."?"
|
|
|
|
end
|
|
|
|
back_url = back_url.."sso_login="..cda_key
|
|
|
|
end
|
2015-02-15 12:31:23 +01:00
|
|
|
|
2015-02-12 12:08:52 +01:00
|
|
|
return hlp.redirect(back_url)
|
2014-01-31 21:25:46 +01:00
|
|
|
|
2015-02-15 12:31:23 +01:00
|
|
|
|
|
|
|
-- In case we want to serve portal login or assets for portal, just
|
|
|
|
-- serve it
|
2020-03-31 02:20:40 +02:00
|
|
|
elseif is_logged_in
|
2015-02-15 12:31:23 +01:00
|
|
|
or ngx.var.uri == conf["portal_path"]
|
|
|
|
or (hlp.string.starts(ngx.var.uri, conf["portal_path"].."assets")
|
2013-11-27 03:48:25 +01:00
|
|
|
and (not ngx.var.http_referer
|
2015-02-15 12:31:23 +01:00
|
|
|
or hlp.string.starts(ngx.var.http_referer, conf.portal_url)))
|
2013-10-20 18:25:24 +02:00
|
|
|
then
|
2019-03-19 16:52:43 +01:00
|
|
|
-- If this is an asset, enable caching
|
|
|
|
if hlp.string.starts(ngx.var.uri, conf["portal_path"].."assets")
|
|
|
|
then
|
|
|
|
return hlp.serve(ngx.var.uri, "static_asset")
|
|
|
|
else
|
|
|
|
return hlp.serve(ngx.var.uri)
|
|
|
|
end
|
2013-10-20 17:24:44 +02:00
|
|
|
|
2015-02-15 12:31:23 +01:00
|
|
|
|
|
|
|
-- If all the previous cases have failed, redirect to portal
|
2013-10-20 17:24:44 +02:00
|
|
|
else
|
2015-02-15 13:03:01 +01:00
|
|
|
hlp.flash("info", hlp.t("please_login"))
|
2019-10-03 20:42:01 +02:00
|
|
|
logger.debug("User should log in to be able to access "..ngx.var.uri)
|
2019-03-20 03:17:17 +01:00
|
|
|
-- 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, ..)
|
|
|
|
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))
|
2013-10-20 17:24:44 +02:00
|
|
|
end
|
|
|
|
|
2015-02-15 12:31:23 +01:00
|
|
|
|
|
|
|
-- `POST` method is basically use to achieve editing operations
|
2013-10-20 17:24:44 +02:00
|
|
|
elseif ngx.var.request_method == "POST" then
|
|
|
|
|
2015-02-15 12:31:23 +01:00
|
|
|
-- CSRF protection, only proceed if we are editing from the same
|
|
|
|
-- domain
|
2015-02-12 12:08:52 +01:00
|
|
|
if hlp.string.starts(ngx.var.http_referer, conf.portal_url) then
|
|
|
|
if hlp.string.ends(ngx.var.uri, conf["portal_path"].."password.html")
|
|
|
|
or hlp.string.ends(ngx.var.uri, conf["portal_path"].."edit.html")
|
2013-10-21 13:13:43 +02:00
|
|
|
then
|
2019-10-03 20:42:01 +02:00
|
|
|
logger.debug("User attempts to edit its information")
|
2015-02-12 12:08:52 +01:00
|
|
|
return hlp.edit_user()
|
2013-10-21 13:13:43 +02:00
|
|
|
else
|
2019-10-03 20:42:01 +02:00
|
|
|
logger.debug("User attempts to log in")
|
2015-02-12 12:08:52 +01:00
|
|
|
return hlp.login()
|
2013-10-21 13:13:43 +02:00
|
|
|
end
|
2013-10-20 17:24:44 +02:00
|
|
|
else
|
|
|
|
-- Redirect to portal
|
2015-02-15 13:03:01 +01:00
|
|
|
hlp.flash("fail", hlp.t("please_login_from_portal"))
|
2019-10-03 20:42:01 +02:00
|
|
|
logger.debug("Invalid POST request not coming from the portal url...")
|
2015-02-12 12:08:52 +01:00
|
|
|
return hlp.redirect(conf.portal_url)
|
2013-10-20 17:24:44 +02:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-09-20 17:47:24 +02:00
|
|
|
--
|
|
|
|
-- 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
|
2020-09-21 14:40:37 +02:00
|
|
|
assets = {
|
|
|
|
["/ynh_portal.js"] = "js/ynh_portal.js",
|
|
|
|
["/ynh_overlay.css"] = "css/ynh_overlay.css"
|
|
|
|
}
|
2020-09-20 17:47:24 +02:00
|
|
|
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
|
|
|
|
|
2015-02-15 12:31:23 +01:00
|
|
|
|
|
|
|
--
|
2020-09-20 18:00:49 +02:00
|
|
|
-- 3. REDIRECTED URLS
|
2015-02-15 12:31:23 +01:00
|
|
|
--
|
|
|
|
-- If the URL matches one of the `redirected_urls` in the configuration file,
|
|
|
|
-- just redirect to the target URL/URI
|
|
|
|
--
|
2020-09-20 18:00:49 +02:00
|
|
|
|
2014-04-10 17:35:28 +02:00
|
|
|
function detect_redirection(redirect_url)
|
2015-02-12 12:08:52 +01:00
|
|
|
if hlp.string.starts(redirect_url, "http://")
|
|
|
|
or hlp.string.starts(redirect_url, "https://") then
|
|
|
|
return hlp.redirect(redirect_url)
|
|
|
|
elseif hlp.string.starts(redirect_url, "/") then
|
|
|
|
return hlp.redirect(ngx.var.scheme.."://"..ngx.var.host..redirect_url)
|
2014-04-10 17:35:28 +02:00
|
|
|
else
|
2015-02-12 12:08:52 +01:00
|
|
|
return hlp.redirect(ngx.var.scheme.."://"..redirect_url)
|
2014-04-10 17:35:28 +02:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
if conf["redirected_urls"] then
|
|
|
|
for url, redirect_url in pairs(conf["redirected_urls"]) do
|
2015-02-12 12:08:52 +01:00
|
|
|
if url == ngx.var.host..ngx.var.uri..hlp.uri_args_string()
|
|
|
|
or url == ngx.var.scheme.."://"..ngx.var.host..ngx.var.uri..hlp.uri_args_string()
|
|
|
|
or url == ngx.var.uri..hlp.uri_args_string() then
|
2019-10-03 20:42:01 +02:00
|
|
|
logger.debug("Requested URI is in redirected_urls")
|
2014-04-10 17:35:28 +02:00
|
|
|
detect_redirection(redirect_url)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
if conf["redirected_regex"] then
|
|
|
|
for regex, redirect_url in pairs(conf["redirected_regex"]) do
|
2020-01-17 08:01:24 +01:00
|
|
|
if hlp.match(ngx.var.host..ngx.var.uri..hlp.uri_args_string(), regex)
|
|
|
|
or hlp.match(ngx.var.scheme.."://"..ngx.var.host..ngx.var.uri..hlp.uri_args_string(), regex)
|
|
|
|
or hlp.match(ngx.var.uri..hlp.uri_args_string(), regex) then
|
2019-10-03 20:42:01 +02:00
|
|
|
logger.debug("Requested URI is in redirected_regex")
|
2014-04-10 17:35:28 +02:00
|
|
|
detect_redirection(redirect_url)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2014-01-31 21:25:46 +01:00
|
|
|
|
2017-04-02 23:47:54 +02:00
|
|
|
--
|
2020-09-20 17:57:23 +02:00
|
|
|
-- 4. IDENTIFY THE RELEVANT PERMISSION
|
2020-05-21 21:53:04 +02:00
|
|
|
--
|
2020-09-20 17:57:23 +02:00
|
|
|
-- In particular, the conf is filled with permissions such as:
|
2020-05-21 21:53:04 +02:00
|
|
|
--
|
2020-09-20 17:57:23 +02:00
|
|
|
-- "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.
|
2020-05-21 21:53:04 +02:00
|
|
|
--
|
|
|
|
|
2020-09-20 17:57:23 +02:00
|
|
|
permission = nil
|
|
|
|
longest_url_match = ""
|
2020-05-21 22:56:52 +02:00
|
|
|
|
2021-01-20 01:28:08 +01:00
|
|
|
ngx_full_url = ngx.var.host..ngx.var.uri
|
|
|
|
|
2020-09-20 17:57:23 +02:00
|
|
|
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
|
2021-01-20 01:28:08 +01:00
|
|
|
-- We want to match the beginning of the url
|
|
|
|
if not string.starts(url, "^") then
|
|
|
|
url = "^"..url
|
|
|
|
end
|
2020-05-21 22:56:52 +02:00
|
|
|
|
2021-01-20 01:28:08 +01:00
|
|
|
local m = hlp.match(ngx_full_url, url)
|
2020-09-20 17:57:23 +02:00
|
|
|
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
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2020-05-21 21:53:04 +02:00
|
|
|
|
|
|
|
--
|
2017-04-02 23:47:54 +02:00
|
|
|
--
|
2020-09-20 18:00:37 +02:00
|
|
|
-- 5. APPLY PERMISSION
|
2017-04-02 23:47:54 +02:00
|
|
|
--
|
|
|
|
--
|
|
|
|
|
2020-09-20 18:00:37 +02:00
|
|
|
-- 1st case : client has access
|
2017-04-02 23:47:54 +02:00
|
|
|
|
2020-09-20 18:00:37 +02:00
|
|
|
if hlp.has_access(permission) then
|
2020-01-29 12:24:51 +01:00
|
|
|
|
2020-04-01 00:43:59 +02:00
|
|
|
if is_logged_in then
|
2020-12-17 17:06:19 +01:00
|
|
|
-- If the user is logged in, refresh_cache
|
|
|
|
hlp.refresh_user_cache()
|
2020-04-01 00:43:59 +02:00
|
|
|
|
2020-12-17 17:06:19 +01:00
|
|
|
-- If Basic Authorization header are enable for this permission,
|
|
|
|
-- add it to the response
|
|
|
|
if permission["auth_header"] then
|
|
|
|
hlp.set_headers()
|
2020-04-01 00:43:59 +02:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
return hlp.pass()
|
2013-10-20 17:24:44 +02:00
|
|
|
|
2020-09-20 18:00:37 +02:00
|
|
|
-- 2nd case : no access ... redirect to portal / login form
|
2020-05-21 21:51:55 +02:00
|
|
|
else
|
|
|
|
|
2020-09-20 18:00:37 +02:00
|
|
|
if is_logged_in then
|
|
|
|
return hlp.redirect(conf.portal_url)
|
|
|
|
else
|
|
|
|
-- Only display this if HTTPS. For HTTP, we can't know if the user really is
|
|
|
|
-- logged in or not, because the cookie is available only in HTTP...
|
|
|
|
if ngx.var.scheme == "https" then
|
|
|
|
hlp.flash("info", hlp.t("please_login"))
|
|
|
|
end
|
|
|
|
|
|
|
|
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
|
2020-05-21 21:51:55 +02:00
|
|
|
end
|