SSOwat/access.lua
2015-02-12 12:08:52 +01:00

294 lines
9.2 KiB
Lua

--
-- 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.
--
-- Get the `cache` persistent shared table
cache = ngx.shared.cache
-- Generate a unique token if it has not been generated yet
srvkey = cache:get("srvkey")
if not srvkey then
math.randomseed(os.time())
srvkey = tostring(math.random(1111111, 9999999))
cache:add("srvkey", srvkey)
end
-- Initialize and get configuration
config = require "config"
conf = config.get_config()
-- Initialize the non-persistent cookie table
cookies = {}
-- Import helpers
hlp = require "helpers"
-- Just a note for the client to know that he passed through the SSO
ngx.header["X-SSO-WAT"] = "You've just been SSOed"
--
-- 1. LOGIN
--
-- 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
--
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]
-- Use the `login` shared table where a username is associated with
-- a CDA key
if login[cda_key] then
hlp.set_auth_cookie(login[cda_key], ngx.var.host)
ngx.log(ngx.NOTICE, "Cross-domain authentication: "..login[cda_key].." connected on "..ngx.var.host)
login[cda_key] = nil
end
uri_args[conf.login_arg] = nil
return hlp.redirect(ngx.var.uri..hlp.uri_args_string(uri_args))
end
end
--
-- 2. PORTAL
--
-- example: https://mydomain.org/ssowat*
--
-- If the URL matches the portal URL, serve a portal file or proceed to a
-- portal operations
--
if ngx.var.host == conf["portal_domain"]
and hlp.string.starts(ngx.var.uri, string.sub(conf["portal_path"], 1, -2))
then
-- `GET` method will serve a portal file
if ngx.var.request_method == "GET" then
-- Force portal scheme
if ngx.var.scheme ~= conf["portal_scheme"] then
return hlp.redirect(conf.portal_url)
end
-- Add a trailing `/` if not present
if ngx.var.uri.."/" == conf["portal_path"] then
return hlp.redirect(conf.portal_url)
end
uri_args = ngx.req.get_uri_args()
-- Logout is also called via a `GET` method
-- TODO: change this ?
if uri_args.action and uri_args.action == 'logout' then
return hlp.logout()
elseif hlp.is_logged_in() and uri_args.r then
back_url = ngx.decode_base64(uri_args.r)
if not string.match(back_url, "^http[s]?://"..ngx.var.host.."/")
and not string.match(back_url, ".*"..conf.login_arg.."=%d+$") then
cda_key = tostring(math.random(1111111, 9999999))
login[cda_key] = ngx.var.cookie_SSOwAuthUser
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
return hlp.redirect(back_url)
elseif hlp.is_logged_in() -- Authenticated
or ngx.var.uri == conf["portal_path"] -- OR Want to serve portal login
or (string.starts(ngx.var.uri, conf["portal_path"].."assets")
and (not ngx.var.http_referer
or hlp.string.starts(ngx.var.http_referer, conf.portal_url))) -- OR Want to serve assets for portal login
then
-- Serve normal portal
return hlp.serve(ngx.var.uri)
else
-- Redirect to portal
hlp.flash("info", t("please_login"))
return hlp.redirect(conf.portal_url)
end
elseif ngx.var.request_method == "POST" then
-- CSRF protection
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")
then
return hlp.edit_user()
else
return hlp.login()
end
else
-- Redirect to portal
hlp.flash("fail", t("please_login_from_portal"))
return hlp.redirect(conf.portal_url)
end
end
end
-- Redirected urls
function detect_redirection(redirect_url)
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)
else
return hlp.redirect(ngx.var.scheme.."://"..redirect_url)
end
end
if conf["redirected_urls"] then
for url, redirect_url in pairs(conf["redirected_urls"]) do
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
detect_redirection(redirect_url)
end
end
end
if conf["redirected_regex"] then
for regex, redirect_url in pairs(conf["redirected_regex"]) do
if string.match(ngx.var.host..ngx.var.uri..hlp.uri_args_string(), regex)
or string.match(ngx.var.scheme.."://"..ngx.var.host..ngx.var.uri..hlp.uri_args_string(), regex)
or string.match(ngx.var.uri..hlp.uri_args_string(), regex) then
detect_redirection(redirect_url)
end
end
end
-- URL that must be protected
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, url)
or hlp.string.starts(ngx.var.uri, url) then
return true
end
end
for _, regex in ipairs(conf["protected_regex"]) do
if string.match(ngx.var.host..ngx.var.uri, regex)
or string.match(ngx.var.uri, regex) then
return true
end
end
return false
end
-- Skipped urls
-- i.e. http://mydomain.org/no_protection/
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))
and not is_protected() then
return hlp.pass()
end
end
end
if conf["skipped_regex"] then
for _, regex in ipairs(conf["skipped_regex"]) do
if (string.match(ngx.var.host..ngx.var.uri..hlp.uri_args_string(), regex)
or string.match(ngx.var.uri..hlp.uri_args_string(), regex))
and not is_protected() then
return hlp.pass()
end
end
end
-- Unprotected urls
-- i.e. http://mydomain.org/no_protection+headers/
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
return hlp.pass()
end
end
end
if conf["unprotected_regex"] then
for _, regex in ipairs(conf["unprotected_regex"]) do
if (string.match(ngx.var.host..ngx.var.uri..hlp.uri_args_string(), regex)
or string.match(ngx.var.uri..hlp.uri_args_string(), regex))
and not is_protected() then
if hlp.is_logged_in() then
hlp.set_headers()
end
return hlp.pass()
end
end
end
-- Cookie validation
--
if hlp.is_logged_in() then
if string.match(ngx.var.uri, "^/ynhpanel.js$") then
hlp.serve("/yunohost/sso/assets/js/ynhpanel.js")
end
if string.match(ngx.var.uri, "^/ynhpanel.css$") then
hlp.serve("/yunohost/sso/assets/css/ynhpanel.css")
end
if string.match(ngx.var.uri, "^/ynhpanel.json$") then
hlp.serve("/yunohost/sso/assets/js/ynhpanel.json")
end
if not hlp.has_access() then
return hlp.redirect(conf.portal_url)
end
hlp.set_headers()
return hlp.pass()
end
-- Login with HTTP Auth if credentials are brought
--
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 = hlp.authenticate(user, password)
if user then
hlp.set_headers(user)
return hlp.pass()
end
end
-- Else redirect to portal
--
hlp.flash("info", t("please_login"))
local back_url = ngx.var.scheme .. "://" .. ngx.var.host .. ngx.var.uri .. hlp.uri_args_string()
return hlp.redirect(conf.portal_url.."?r="..ngx.encode_base64(back_url))