mirror of
https://github.com/YunoHost/SSOwat.git
synced 2024-09-03 20:06:27 +02:00
Merge de20c91871
into daa799111e
This commit is contained in:
commit
82a63e2e92
6 changed files with 219 additions and 92 deletions
16
README.md
16
README.md
|
@ -66,7 +66,7 @@ If you use YunoHost, you may want to edit the `/etc/ssowat/conf.json.persistent`
|
|||
|
||||
## Available parameters
|
||||
|
||||
These are the SSOwat's configuration parameters. Only `portal_domain` and `skipped_urls` are required, but it is recommended to know the others to fully understand what you can do with SSOwat.
|
||||
These are the SSOwat's configuration parameters. Only `portal_domain` is required, but it is recommended to know the others to fully understand what you can do with SSOwat.
|
||||
|
||||
#### portal_domain
|
||||
|
||||
|
@ -74,11 +74,11 @@ Domain of the authentication portal. It has to be a domain, IP addresses will no
|
|||
|
||||
#### portal_path
|
||||
|
||||
URI of the authentication portal (**default**: `/ssowat`)
|
||||
URI of the authentication portal (**default**: `/ssowat/`). This path **must** end with “`/`”.
|
||||
|
||||
#### portal_port
|
||||
|
||||
Web port of the authentication portal (**default**: `443`)
|
||||
Web port of the authentication portal (**default**: `443` for `https`, `80` for `http`)
|
||||
|
||||
#### portal_scheme
|
||||
|
||||
|
@ -86,7 +86,7 @@ Whether authentication should use secure connection or not (**default**: `https`
|
|||
|
||||
#### domains
|
||||
|
||||
List of handle domains (**default**: similar to `portal_domain`)
|
||||
List of handled domains (**default**: similar to `portal_domain`)
|
||||
|
||||
#### ldap_host
|
||||
|
||||
|
@ -104,6 +104,10 @@ LDAP user identifier (**default**: `uid`)
|
|||
|
||||
User's attributes to fetch from LDAP (**default**: `["uid", "givenname", "sn", "cn", "homedirectory", "mail", "maildrop"]`)
|
||||
|
||||
#### ldap_enforce_crypt
|
||||
|
||||
Let SSOwat re-encrypt weakly-encrypted LDAP passwords into the safer sha-512 (crypt) (**default**: `true`)
|
||||
|
||||
#### allow_mail_authentication
|
||||
|
||||
Whether users can authenticate with their mail address (**default**: `true`)
|
||||
|
@ -160,6 +164,10 @@ Array of regular expressions to be matched against URLS **and** URIs and their r
|
|||
|
||||
2-level array containing usernames and their allowed URLs along with an App name (**example**: `{ "kload": { "kload.fr/myapp/": "My App" } }`)
|
||||
|
||||
#### logout
|
||||
|
||||
Associative array; when logging out of SSOwat, any existing cookie that is found as a key of this array triggers the associated logout URL. This only works on `http[s]://[*.]portal_domaini/`, though. (**example**: `{ "dcxd": "https://example.org/dotclear/admin/index.php?logout=1" }`)
|
||||
|
||||
#### default_language
|
||||
|
||||
Language code used by default in views (**default**: `en`)
|
||||
|
|
140
access.lua
140
access.lua
|
@ -6,6 +6,28 @@
|
|||
-- request is handled: redirected, forbidden, bypassed or served.
|
||||
--
|
||||
|
||||
-- Initialize and get configuration
|
||||
local conf = config.get_config()
|
||||
|
||||
-- Import helpers
|
||||
local hlp = require "helpers"
|
||||
|
||||
-- Store the request data
|
||||
req_data = {
|
||||
request_uri = ngx.var.request_uri,
|
||||
uri = ngx.var.uri,
|
||||
https = ngx.var.proxy_https or ngx.var.https,
|
||||
host = ngx.var.host,
|
||||
request_method = ngx.var.request_method,
|
||||
http_referer = ngx.var.http_referer
|
||||
}
|
||||
if req_data["https"] and req_data["https"] ~= "" then
|
||||
req_data["scheme"] = "https"
|
||||
else
|
||||
req_data["scheme"] = "http"
|
||||
end
|
||||
hlp.set_req_data(req_data)
|
||||
|
||||
-- Get the `cache` persistent shared table
|
||||
local cache = ngx.shared.cache
|
||||
|
||||
|
@ -16,16 +38,19 @@ if not srvkey then
|
|||
cache:add("srvkey", srvkey)
|
||||
end
|
||||
|
||||
-- Initialize and get configuration
|
||||
local conf = config.get_config()
|
||||
|
||||
-- Import helpers
|
||||
local 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"
|
||||
|
||||
|
||||
--
|
||||
-- 0. LOGOUT if requested, but only if logged in
|
||||
--
|
||||
local logout_ck = ngx.var.cookie_SSOwFullLogout
|
||||
if logout_ck and logout_ck ~= "" and hlp.is_logged_in() then
|
||||
return hlp.logout()
|
||||
end
|
||||
|
||||
|
||||
--
|
||||
-- 1. LOGIN
|
||||
--
|
||||
|
@ -34,7 +59,7 @@ ngx.header["X-SSO-WAT"] = "You've just been SSOed"
|
|||
-- 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
|
||||
if req_data["host"] ~= conf["portal_domain"] and req_data["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]
|
||||
|
@ -43,13 +68,13 @@ if ngx.var.host ~= conf["portal_domain"] and ngx.var.request_method == "GET" the
|
|||
-- a CDA key
|
||||
user = cache:get("CDA|"..cda_key)
|
||||
if user then
|
||||
hlp.set_auth_cookie(user, ngx.var.host)
|
||||
ngx.log(ngx.NOTICE, "Cross-domain authentication: "..user.." connected on "..ngx.var.host)
|
||||
hlp.set_auth_cookie(user, req_data["host"])
|
||||
ngx.log(ngx.NOTICE, "Cross-domain authentication: "..user.." connected on "..req_data["host"])
|
||||
cache:delete("CDA|"..cda_key)
|
||||
end
|
||||
|
||||
uri_args[conf.login_arg] = nil
|
||||
return hlp.redirect(ngx.var.uri..hlp.uri_args_string(uri_args))
|
||||
return hlp.redirect(req_data['request_uri'])
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -62,20 +87,16 @@ end
|
|||
-- If the URL matches the portal URL, serve a portal file or proceed to a
|
||||
-- portal operation
|
||||
--
|
||||
if ngx.var.host == conf["portal_domain"]
|
||||
and hlp.string.starts(ngx.var.uri, string.sub(conf["portal_path"], 1, -2))
|
||||
if req_data["host"] == conf["portal_domain"]
|
||||
and hlp.string.starts(req_data['request_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
|
||||
if req_data["request_method"] == "GET" then
|
||||
|
||||
-- Add a trailing `/` if not present
|
||||
if ngx.var.uri.."/" == conf["portal_path"] then
|
||||
if req_data['request_uri'].."/" == conf["portal_path"] then
|
||||
ngx.log(ngx.DEBUG, "REDIRECT MISSING /")
|
||||
return hlp.redirect(conf.portal_url)
|
||||
end
|
||||
|
||||
|
@ -98,6 +119,7 @@ then
|
|||
if string.match(back_url, "(.*)\n") then
|
||||
hlp.flash("fail", hlp.t("redirection_error_invalid_url"))
|
||||
ngx.log(ngx.ERR, "Redirection url is invalid")
|
||||
ngx.log(ngx.DEBUG, "REDIRECT \\N FOUND")
|
||||
return hlp.redirect(conf.portal_url)
|
||||
end
|
||||
|
||||
|
@ -124,7 +146,7 @@ then
|
|||
|
||||
-- In case the `back_url` is not on the same domain than the
|
||||
-- current one, create a redirection with a CDA key
|
||||
local ngx_host_escaped = ngx.var.host:gsub("-", "%%-") -- escape dash for pattern matching
|
||||
local ngx_host_escaped = req_data["host"]:gsub("-", "%%-") -- escape dash for pattern matching
|
||||
if not string.match(back_url, "^http[s]?://"..ngx_host_escaped.."/")
|
||||
and not string.match(back_url, ".*"..conf.login_arg.."=%d+$") then
|
||||
local cda_key = hlp.set_cda_key()
|
||||
|
@ -136,35 +158,43 @@ then
|
|||
back_url = back_url.."sso_login="..cda_key
|
||||
end
|
||||
|
||||
ngx.log(ngx.DEBUG, "REDIRECT BACK URL REQUESTED")
|
||||
return hlp.redirect(back_url)
|
||||
|
||||
|
||||
-- In case we want to serve portal login or assets for portal, just
|
||||
-- serve it
|
||||
elseif hlp.is_logged_in()
|
||||
or ngx.var.uri == conf["portal_path"]
|
||||
or (hlp.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 req_data['request_uri'] == conf["portal_path"]
|
||||
or hlp.string.starts(req_data['request_uri'], conf["portal_path"].."?")
|
||||
or (hlp.string.starts(req_data['request_uri'], conf["portal_path"].."assets")
|
||||
and (not req_data["http_referer"]
|
||||
or hlp.string.starts(req_data["http_referer"], conf.portal_url)))
|
||||
then
|
||||
return hlp.serve(ngx.var.uri)
|
||||
local uri = req_data['request_uri']
|
||||
local i, _ = string.find(uri, '?')
|
||||
if i then
|
||||
uri = string.sub(uri, 1, i-1)
|
||||
end
|
||||
return hlp.serve(uri)
|
||||
|
||||
|
||||
-- If all the previous cases have failed, redirect to portal
|
||||
else
|
||||
hlp.flash("info", hlp.t("please_login"))
|
||||
ngx.log(ngx.DEBUG, "REDIRECT GET CATCHALL…")
|
||||
return hlp.redirect(conf.portal_url)
|
||||
end
|
||||
|
||||
|
||||
-- `POST` method is basically use to achieve editing operations
|
||||
elseif ngx.var.request_method == "POST" then
|
||||
elseif req_data["request_method"] == "POST" then
|
||||
|
||||
-- CSRF protection, only proceed if we are editing from the same
|
||||
-- domain
|
||||
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")
|
||||
if hlp.string.starts(req_data["http_referer"], conf.portal_url) then
|
||||
if hlp.string.ends(req_data["uri"], conf["portal_path"].."password.html")
|
||||
or hlp.string.ends(req_data["uri"], conf["portal_path"].."edit.html")
|
||||
then
|
||||
return hlp.edit_user()
|
||||
else
|
||||
|
@ -173,6 +203,7 @@ then
|
|||
else
|
||||
-- Redirect to portal
|
||||
hlp.flash("fail", hlp.t("please_login_from_portal"))
|
||||
ngx.log(ngx.DEBUG, "REDIRECT POST CATCHALL…")
|
||||
return hlp.redirect(conf.portal_url)
|
||||
end
|
||||
end
|
||||
|
@ -191,17 +222,17 @@ function detect_redirection(redirect_url)
|
|||
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)
|
||||
return hlp.redirect(req_data["scheme"].."://"..req_data["host"]..redirect_url)
|
||||
else
|
||||
return hlp.redirect(ngx.var.scheme.."://"..redirect_url)
|
||||
return hlp.redirect(req_data["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
|
||||
if url == req_data["host"]..req_data['request_uri']
|
||||
or url == req_data["scheme"].."://"..req_data["host"]..req_data['request_uri']
|
||||
or url == req_data['request_uri'] then
|
||||
detect_redirection(redirect_url)
|
||||
end
|
||||
end
|
||||
|
@ -209,9 +240,9 @@ 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
|
||||
if string.match(req_data["host"]..req_data['request_uri'], regex)
|
||||
or string.match(req_data["scheme"].."://"..req_data["host"]..req_data['request_uri'], regex)
|
||||
or string.match(req_data['request_uri'], regex) then
|
||||
detect_redirection(redirect_url)
|
||||
end
|
||||
end
|
||||
|
@ -236,14 +267,14 @@ function is_protected()
|
|||
end
|
||||
|
||||
for _, url in ipairs(conf["protected_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) then
|
||||
if hlp.string.starts(req_data["host"]..req_data['request_uri'], url)
|
||||
or hlp.string.starts(req_data['request_uri'], url) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
for _, regex in ipairs(conf["protected_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) then
|
||||
if string.match(req_data["host"]..req_data['request_uri'], regex)
|
||||
or string.match(req_data['request_uri'], regex) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
@ -262,8 +293,8 @@ end
|
|||
|
||||
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))
|
||||
if (hlp.string.starts(req_data["host"]..req_data['request_uri'], url)
|
||||
or hlp.string.starts(req_data['request_uri'], url))
|
||||
and not is_protected() then
|
||||
return hlp.pass()
|
||||
end
|
||||
|
@ -272,8 +303,8 @@ 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))
|
||||
if (string.match(req_data["host"]..req_data['request_uri'], regex)
|
||||
or string.match(req_data['request_uri'], regex))
|
||||
and not is_protected() then
|
||||
return hlp.pass()
|
||||
end
|
||||
|
@ -291,18 +322,19 @@ end
|
|||
--
|
||||
|
||||
if hlp.is_logged_in() then
|
||||
if string.match(ngx.var.uri, "^/ynhpanel.js$") then
|
||||
if string.match(req_data['request_uri'], "^/ynhpanel.js$") then
|
||||
hlp.serve("/yunohost/sso/assets/js/ynhpanel.js")
|
||||
end
|
||||
if string.match(ngx.var.uri, "^/ynhpanel.css$") then
|
||||
if string.match(req_data['request_uri'], "^/ynhpanel.css$") then
|
||||
hlp.serve("/yunohost/sso/assets/css/ynhpanel.css")
|
||||
end
|
||||
if string.match(ngx.var.uri, "^/ynhpanel.json$") then
|
||||
if string.match(req_data['request_uri'], "^/ynhpanel.json$") then
|
||||
hlp.serve("/yunohost/sso/assets/js/ynhpanel.json")
|
||||
end
|
||||
|
||||
-- If user has no access to this URL, redirect him to the portal
|
||||
if not hlp.has_access() then
|
||||
ngx.log(ngx.DEBUG, "REDIRECT ACCESS DENIED")
|
||||
return hlp.redirect(conf.portal_url)
|
||||
end
|
||||
|
||||
|
@ -328,8 +360,8 @@ end
|
|||
|
||||
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))
|
||||
if (hlp.string.starts(req_data["host"]..req_data['request_uri'], url)
|
||||
or hlp.string.starts(req_data['request_uri'], url))
|
||||
and not is_protected() then
|
||||
if hlp.is_logged_in() then
|
||||
hlp.set_headers()
|
||||
|
@ -341,8 +373,8 @@ 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))
|
||||
if (string.match(req_data["host"]..req_data['request_uri'], regex)
|
||||
or string.match(req_data['request_uri'], regex))
|
||||
and not is_protected() then
|
||||
if hlp.is_logged_in() then
|
||||
hlp.set_headers()
|
||||
|
@ -376,6 +408,7 @@ if auth_header then
|
|||
|
||||
-- If user has no access to this URL, redirect him to the portal
|
||||
if not hlp.has_access(user) then
|
||||
ngx.log(ngx.DEBUG, "REDIRECT BASIC AUTH OK")
|
||||
return hlp.redirect(conf.portal_url)
|
||||
end
|
||||
|
||||
|
@ -392,5 +425,6 @@ end
|
|||
--
|
||||
|
||||
hlp.flash("info", hlp.t("please_login"))
|
||||
local back_url = ngx.var.scheme .. "://" .. ngx.var.host .. ngx.var.uri .. hlp.uri_args_string()
|
||||
local back_url = req_data["scheme"] .. "://" .. req_data["host"] .. req_data['request_uri']
|
||||
ngx.log(ngx.DEBUG, "REDIRECT BY DEFAULT")
|
||||
return hlp.redirect(conf.portal_url.."?r="..ngx.encode_base64(back_url))
|
||||
|
|
|
@ -26,5 +26,8 @@
|
|||
"example.org/myapp": "My other domain App",
|
||||
"example.com/myapp2": "My second App"
|
||||
}
|
||||
},
|
||||
"logout": {
|
||||
"dcxd": "https://example.org/dotclear/admin/index.php?logout=1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,16 +41,21 @@ function get_config()
|
|||
-- Default configuration values
|
||||
default_conf = {
|
||||
portal_scheme = "https",
|
||||
portal_path = "/ssowat",
|
||||
portal_path = "/ssowat/",
|
||||
local_portal_domain = "yunohost.local",
|
||||
domains = { conf["portal_domain"], "yunohost.local" },
|
||||
domains = { "yunohost.local", conf["portal_domain"] },
|
||||
session_timeout = 60 * 60 * 24, -- one day
|
||||
session_max_timeout = 60 * 60 * 24 * 7, -- one week
|
||||
login_arg = "sso_login",
|
||||
ldap_host = "localhost",
|
||||
ldap_group = "ou=users,dc=yunohost,dc=org",
|
||||
ldap_identifier = "uid",
|
||||
ldap_enforce_crypt = true,
|
||||
skipped_urls = {},
|
||||
users = {},
|
||||
logout = {},
|
||||
ldap_attributes = {"uid", "givenname", "sn", "cn", "homedirectory", "mail", "maildrop"},
|
||||
additional_headers = {["Remote-User"] = "uid"},
|
||||
allow_mail_authentication = true,
|
||||
default_language = "en"
|
||||
}
|
||||
|
|
8
headers.lua
Normal file
8
headers.lua
Normal file
|
@ -0,0 +1,8 @@
|
|||
-- Redirect to the SSO if logout is in progress
|
||||
if ngx.ctx.SSOwFullLogout then
|
||||
local next_cookie, back_url = ngx.ctx.SSOwFullLogout:match('^(.-)|(http.+)$')
|
||||
ngx.log(ngx.DEBUG, "LOGOUT STEP DONE; next: "..next_cookie..", back to: "..back_url)
|
||||
ngx.status = ngx.HTTP_TEMPORARY_REDIRECT
|
||||
ngx.header['Set-Cookie'] = {next_cookie}
|
||||
ngx.header.Location = back_url
|
||||
end
|
115
helpers.lua
115
helpers.lua
|
@ -9,6 +9,14 @@ module('helpers', package.seeall)
|
|||
|
||||
local cache = ngx.shared.cache
|
||||
local conf = config.get_config()
|
||||
local req_data
|
||||
|
||||
-- Local store for the request data, because:
|
||||
-- - some data is cleared along the process but is still needed (eg. request_uri)
|
||||
-- - repeatedly reading ngx.var or ngx.ctx is bad for memory usage and performance
|
||||
function set_req_data(data)
|
||||
req_data = data
|
||||
end
|
||||
|
||||
-- Read a FS stored file
|
||||
function read_file(file)
|
||||
|
@ -47,6 +55,10 @@ function string.ends(String, End)
|
|||
return End=='' or string.sub(String, -string.len(End)) == End
|
||||
end
|
||||
|
||||
-- Escape special characters in a string
|
||||
function string.pcre_escape(String)
|
||||
return ngx.re.gsub(String, '([]({+?\\*.})[])', '\\$1')
|
||||
end
|
||||
|
||||
-- Find a string by its translate key in the right language
|
||||
function t(key)
|
||||
|
@ -169,7 +181,8 @@ function delete_cookie()
|
|||
ngx.header["Set-Cookie"] = {
|
||||
"SSOwAuthUser="..cookie_str,
|
||||
"SSOwAuthHash="..cookie_str,
|
||||
"SSOwAuthExpire="..cookie_str
|
||||
"SSOwAuthExpire="..cookie_str,
|
||||
"SSOwFullLogout="..cookie_str
|
||||
}
|
||||
end
|
||||
end
|
||||
|
@ -225,7 +238,7 @@ end
|
|||
-- of the configuration file
|
||||
function has_access(user, url)
|
||||
user = user or authUser
|
||||
url = url or ngx.var.host..ngx.var.uri
|
||||
url = url or req_data["host"]..req_data['request_uri']
|
||||
|
||||
if not conf["users"][user] then
|
||||
conf = config.get_config()
|
||||
|
@ -242,7 +255,7 @@ function has_access(user, url)
|
|||
|
||||
-- Replace the original domain by a local one if you are connected from
|
||||
-- a non-global domain name.
|
||||
if ngx.var.host == conf["local_portal_domain"] then
|
||||
if req_data["host"] == conf["local_portal_domain"] then
|
||||
u = string.gsub(u, conf["original_portal_domain"], conf["local_portal_domain"])
|
||||
end
|
||||
|
||||
|
@ -293,7 +306,9 @@ function authenticate(user, password)
|
|||
-- cache shared table in order to eventually reuse it later when updating
|
||||
-- profile information or just passing credentials to an application.
|
||||
if connected then
|
||||
if conf['ldap_enforce_crypt'] then
|
||||
ensure_user_password_uses_strong_hash(connected, user, password)
|
||||
end
|
||||
cache:add(user.."-password", password, conf["session_timeout"])
|
||||
ngx.log(ngx.NOTICE, "Connected as: "..user)
|
||||
return user
|
||||
|
@ -322,20 +337,13 @@ end
|
|||
-- Set the authentication headers in order to pass credentials to the
|
||||
-- application underneath.
|
||||
function set_headers(user)
|
||||
|
||||
-- We definitely don't want to pass credentials on a non-encrypted
|
||||
-- connection.
|
||||
if ngx.var.scheme ~= "https" then
|
||||
return redirect("https://"..ngx.var.host..ngx.var.uri..uri_args_string())
|
||||
end
|
||||
|
||||
local user = user or authUser
|
||||
|
||||
-- If the password is not in cache or if the cache has expired, ask for
|
||||
-- logging.
|
||||
if not cache:get(user.."-password") then
|
||||
flash("info", t("please_login"))
|
||||
local back_url = ngx.var.scheme .. "://" .. ngx.var.host .. ngx.var.uri .. uri_args_string()
|
||||
local back_url = req_data["scheme"] .. "://" .. req_data["host"] .. req_data['request_uri']
|
||||
return redirect(conf.portal_url.."?r="..ngx.encode_base64(back_url))
|
||||
end
|
||||
|
||||
|
@ -545,9 +553,10 @@ function get_data_for(view)
|
|||
|
||||
-- Add user's accessible URLs using the ACLs.
|
||||
-- It is typically used to build the app list.
|
||||
if conf["users"][user] then
|
||||
for url, name in pairs(conf["users"][user]) do
|
||||
|
||||
if ngx.var.host == conf["local_portal_domain"] then
|
||||
if req_data["host"] == conf["local_portal_domain"] then
|
||||
url = string.gsub(url, conf["original_portal_domain"], conf["local_portal_domain"])
|
||||
end
|
||||
table.insert(sorted_apps, name)
|
||||
|
@ -555,6 +564,7 @@ function get_data_for(view)
|
|||
table.insert(data["app"], index_of(sorted_apps, name), { url = url, name = name })
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Pass all the translated strings to the view (to use with t_<key>)
|
||||
for k, v in pairs(i18n[conf["default_language"]]) do
|
||||
|
@ -575,12 +585,13 @@ end
|
|||
-- if it's not the case, it migrates the password to this new hash algorithm
|
||||
function ensure_user_password_uses_strong_hash(ldap, user, password)
|
||||
local current_hashed_password = nil
|
||||
conf = config.get_config()
|
||||
|
||||
for dn, attrs in ldap:search {
|
||||
base = "ou=users,dc=yunohost,dc=org",
|
||||
base = conf['ldap_group'],
|
||||
scope = "onelevel",
|
||||
sizelimit = 1,
|
||||
filter = "(uid="..user..")",
|
||||
filter = "("..conf['ldap_identifier'].."="..user..")",
|
||||
attrs = {"userPassword"}
|
||||
} do
|
||||
current_hashed_password = attrs["userPassword"]:sub(0, 10)
|
||||
|
@ -618,7 +629,7 @@ function edit_user()
|
|||
|
||||
-- In case of a password modification
|
||||
-- TODO: split this into a new function
|
||||
if string.ends(ngx.var.uri, "password.html") then
|
||||
if string.ends(req_data["uri"], "password.html") then
|
||||
|
||||
-- Check current password against the cached one
|
||||
if args.currentpassword
|
||||
|
@ -654,7 +665,7 @@ function edit_user()
|
|||
|
||||
-- In case of profile modification
|
||||
-- TODO: split this into a new function
|
||||
elseif string.ends(ngx.var.uri, "edit.html") then
|
||||
elseif string.ends(req_data["uri"], "edit.html") then
|
||||
|
||||
-- Check that needed arguments exist
|
||||
if args.givenName and args.sn and args.mail then
|
||||
|
@ -857,7 +868,7 @@ function login()
|
|||
local user = authenticate(args.user, args.password)
|
||||
if user then
|
||||
ngx.status = ngx.HTTP_CREATED
|
||||
set_auth_cookie(user, ngx.var.host)
|
||||
set_auth_cookie(user, req_data["host"])
|
||||
else
|
||||
ngx.status = ngx.HTTP_UNAUTHORIZED
|
||||
flash("fail", t("wrong_username_password"))
|
||||
|
@ -877,17 +888,75 @@ end
|
|||
-- It deletes session cached information to invalidate client side cookie
|
||||
-- information.
|
||||
function logout()
|
||||
conf = config.get_config()
|
||||
|
||||
-- We need this call since we are in a POST request
|
||||
local args = ngx.req.get_uri_args()
|
||||
|
||||
-- Delete user cookie if logged in (that should always be the case)
|
||||
if is_logged_in() then
|
||||
-- Login if not logged in (that should always be the case)
|
||||
if not is_logged_in() then
|
||||
return redirect(conf.portal_url)
|
||||
end
|
||||
|
||||
-- Loop over session cookies.
|
||||
-- For now, this will only work for domains under that of SSOwat.
|
||||
-- The SSOwFullLogout cookie always contains the next cookie to check.
|
||||
local cur_logout_step = ngx.var.cookie_SSOwFullLogout or '*'
|
||||
local url_re = "^(?:https?://(?:[^/]+\\.)?"..string.pcre_escape(conf['portal_domain'])..")?/"
|
||||
local sess_ck
|
||||
local sess_ck_val
|
||||
local logout_url
|
||||
local read_next_and_proceed = false
|
||||
local cookie_str = "; Domain=."..conf['portal_domain']..
|
||||
"; Path=/"..
|
||||
"; Expires="..os.date("%a, %d %b %Y %X UTC;", ngx.req.start_time() + 60)
|
||||
for sess_ck, logout_url in pairs(conf['logout']) do
|
||||
ngx.log(ngx.DEBUG, "LOGOUT step="..cur_logout_step..", evaluate="..sess_ck)
|
||||
|
||||
-- Run a logout URL, but point to the next, to avoid a loop
|
||||
if read_next_and_proceed then
|
||||
ngx.ctx.SSOwFullLogout = "SSOwFullLogout="..sess_ck..cookie_str..'|'..conf.portal_url
|
||||
ngx.log(ngx.DEBUG, "LOGOUT pass: "..req_data['request_uri'])
|
||||
return pass()
|
||||
|
||||
-- A cookie must be checked; do so
|
||||
elseif cur_logout_step == '*' or cur_logout_step == sess_ck then
|
||||
if ngx.re.match(logout_url, url_re) then
|
||||
|
||||
-- Not the right URI for this cookie; redirect
|
||||
if string.gsub(logout_url, '^https?://[^/]+/', '/') ~= req_data['request_uri'] then
|
||||
ngx.header["Set-Cookie"] = {"SSOwFullLogout="..sess_ck..cookie_str}
|
||||
ngx.log(ngx.DEBUG, "LOGOUT visit: "..logout_url)
|
||||
return redirect(logout_url)
|
||||
end
|
||||
sess_ck_val = ngx.var["cookie_"..sess_ck]
|
||||
|
||||
-- The cookie must be deleted
|
||||
if sess_ck_val and sess_ck_val ~= "" then
|
||||
read_next_and_proceed = true
|
||||
|
||||
-- No cookie; check next
|
||||
else
|
||||
ngx.log(ngx.DEBUG, "LOGOUT skip")
|
||||
cur_logout_step = '*'
|
||||
end
|
||||
else
|
||||
-- This URL is not handled :-(
|
||||
ngx.log(ngx.DEBUG, "LOGOUT unhandled")
|
||||
cur_logout_step = '*'
|
||||
end
|
||||
end
|
||||
end
|
||||
if read_next_and_proceed then
|
||||
ngx.ctx.SSOwFullLogout = "SSOwFullLogout=0"..cookie_str..'|'..conf.portal_url
|
||||
ngx.log(ngx.DEBUG, "LOGOUT pass: "..req_data['request_uri'])
|
||||
return pass()
|
||||
end
|
||||
|
||||
delete_cookie()
|
||||
cache:delete("session_"..authUser)
|
||||
cache:delete(authUser.."-"..conf["ldap_identifier"]) -- Ugly trick to reload cache
|
||||
flash("info", t("logged_out"))
|
||||
end
|
||||
|
||||
-- Redirect to portal anyway
|
||||
return redirect(conf.portal_url)
|
||||
|
@ -906,9 +975,9 @@ function pass()
|
|||
delete_redirect_cookie()
|
||||
|
||||
-- When we are in the SSOwat portal, we need a default `content-type`
|
||||
if string.ends(ngx.var.uri, "/")
|
||||
or string.ends(ngx.var.uri, ".html")
|
||||
or string.ends(ngx.var.uri, ".htm")
|
||||
if string.ends(req_data["uri"], "/")
|
||||
or string.ends(req_data["uri"], ".html")
|
||||
or string.ends(req_data["uri"], ".htm")
|
||||
then
|
||||
ngx.header["Content-Type"] = "text/html"
|
||||
end
|
||||
|
|
Loading…
Add table
Reference in a new issue