diff --git a/access.lua b/access.lua index bfe0fe1..f1b3d2d 100644 --- a/access.lua +++ b/access.lua @@ -1,7 +1,8 @@ -- -- Load configuration -- -local conf_file = assert(io.open("cache.json", "r"), "Configuration file is missing") +cookies = {} +local conf_file = assert(io.open(conf_path, "r"), "Configuration file is missing") local conf = json.decode(conf_file:read("*all")) local portal_url = conf["portal_scheme"].."://".. conf["main_domain"].. @@ -15,6 +16,12 @@ ngx.header["X-YNH-SSO"] = "You've just been SSOed" -- -- Useful functions -- +function is_in_table (t, v) + for key, value in ipairs(t) do + if value == v then return key end + end +end + function string.starts (String, Start) return string.sub(String, 1, string.len(Start)) == Start end @@ -23,60 +30,60 @@ function string.ends (String, End) return End=='' or string.sub(String, -string.len(End)) == End end -function set_auth_cookie (user) +function cook (cookie_str) + table.insert(cookies, cookie_str) +end + +function set_auth_cookie (user, domain) local maxAge = 60 * 60 * 24 * 7 -- 1 week local expire = ngx.req.start_time() + maxAge local hash = ngx.md5(auth_key.. "|" ..ngx.var.remote_addr.. "|"..user.. "|"..expire) - local cookie_str = "; Domain=."..ngx.var.host.. + local cookie_str = "; Domain=."..domain.. "; Path=/".. "; Max-Age="..maxAge - ngx.header["Set-Cookie"] = { - "YnhAuthUser="..user..cookie_str, - "YnhAuthHash="..hash..cookie_str, - "YnhAuthExpire="..expire..cookie_str - } + cook("YnhAuthUser="..user..cookie_str) + cook("YnhAuthHash="..hash..cookie_str) + cook("YnhAuthExpire="..expire..cookie_str) end function set_token_cookie () local token = tostring(math.random(111111, 999999)) tokens[token] = token - ngx.header["Set-Cookie"] = { + cook( "YnhAuthToken="..token.. "; Path="..conf["portal_path"].. "; Max-Age=3600" - } + ) end function set_redirect_cookie (redirect_url) - ngx.header["Set-Cookie"] = { + cook( "YnhAuthRedirect="..redirect_url.. + "; Domain=."..conf["main_domain"].. "; Path="..conf["portal_path"].. "; Max-Age=3600" - } + ) end function delete_cookie () expired_time = ngx.req.start_time() - 3600 -- expired yesterday - ngx.header["Set-Cookie"] = { - "YnhAuthUser=;" ..expired_time, - "YnhAuthHash=;" ..expired_time, - "YnhAuthExpire=;" ..expired_time - } + cook("YnhAuthUser=;" ..expired_time) + cook("YnhAuthHash=;" ..expired_time) + cook("YnhAuthExpire=;" ..expired_time) end function delete_onetime_cookie () expired_time = ngx.req.start_time() - 3600 -- expired yesterday - ngx.header["Set-Cookie"] = { - "YnhAuthToken=;" ..expired_time, - "YnhAuthRedirect=;"..expired_time - } + cook("YnhAuthToken=;" ..expired_time) + cook("YnhAuthRedirect=;"..expired_time) end function check_cookie () + -- Check if cookie is set if not ngx.var.cookie_YnhAuthExpire or not ngx.var.cookie_YnhAuthUser @@ -103,38 +110,58 @@ function check_cookie () end function authenticate (user, password) - return lualdap.open_simple ( + connected = lualdap.open_simple ( "localhost", "uid=".. user ..",ou=users,dc=yunohost,dc=org", password ) + + if connected and not cache[user] then + cache[user] = { password=password } + end + + return connected end function set_headers (user) - ldap = lualdap.open_simple("localhost") - for dn, attribs in ldap:search { - base = "uid=".. user ..",ou=users,dc=yunohost,dc=org", - scope = "base", - sizelimit = 1, - attrs = {"uid", "givenName", "sn", "homeDirectory", "mail"} - } do - for name, value in pairs (attribs) do - ngx.header["X-YNH-".. name:upper()] = value + if not cache[user]["uid"] then + ldap = lualdap.open_simple("localhost") + for dn, attribs in ldap:search { + base = "uid=".. user ..",ou=users,dc=yunohost,dc=org", + scope = "base", + sizelimit = 1, + attrs = {"uid", "givenName", "sn", "homeDirectory", "mail"} + } do + for k,v in pairs(attribs) do cache[user][k] = v end end end + + ngx.header["Authorization"] = "Basic "..ngx.encode_base64( + cache[user]["uid"]..":"..cache[user]["password"] + ) end function display_login_form () local args = ngx.req.get_uri_args() + -- Redirected from another domain + if args.r then + local redirect_url = ngx.decode_base64(args.r) + set_redirect_cookie(redirect_url) + return redirect(portal_url) + end + if args.action and args.action == 'logout' then -- Logout delete_cookie() - return ngx.redirect(portal_url) - else + return redirect(portal_url) + elseif ngx.var.cookie_YnhAuthToken then -- Display normal form + return pass + else + -- Set token set_token_cookie() - return + return redirect(portal_url) end end @@ -147,26 +174,31 @@ function do_login () if token and tokens[token] then tokens[token] = nil + ngx.status = ngx.HTTP_CREATED if authenticate(args.user, args.password) then - set_auth_cookie(args.user) - --ngx.status = ngx.HTTP_CREATED - --ngx.exit(ngx.HTTP_OK) - - -- Redirect to precedent page local redirect_url = ngx.var.cookie_YnhAuthRedirect - if redirect_url then - return ngx.redirect(ngx.unescape_uri(redirect_url)) + if not redirect_url then redirect_url = portal_url end + connections[args.user] = {} + connections[args.user]["redirect_url"] = redirect_url + connections[args.user]["domains"] = {} + for _, value in ipairs(conf["domains"]) do + table.insert(connections[args.user]["domains"], value) end + + -- Connect to the first domain (self) + return redirect(ngx.var.scheme.."://"..ngx.var.http_host.."/?ssoconnect="..args.user) end - return ngx.redirect(portal_url) end + return redirect(portal_url) +end + +function redirect (url) + ngx.header["Set-Cookie"] = cookies + return ngx.redirect(url, ngx.HTTP_MOVED_PERMANENTLY) end function pass () - if not ngx.header["Content-Type"] then - ngx.header["Content-Type"] = "text/html" - end delete_onetime_cookie() return end @@ -175,6 +207,33 @@ end -- Routing -- +-- Connection +if ngx.var.request_method == "GET" then + local args = ngx.req.get_uri_args() + + -- /?ssoconnect=user + local user = args.ssoconnect + if user and connections[user] then + -- Set Authentication cookie + set_auth_cookie(user, ngx.var.host) + -- Remove domain from connection table + domain_key = is_in_table(connections[user]["domains"], ngx.var.host) + table.remove(connections[user]["domains"], domain_key) + + if table.getn(connections[user]["domains"]) == 0 then + -- All the redirections has been made + local redirect_url = connections[user]["redirect_url"] + connections[user] = nil + return redirect(ngx.unescape_uri(redirect_url)) + else + -- Redirect to the next domain + for _, domain in ipairs(connections[user]["domains"]) do + return redirect(ngx.var.scheme.."://"..domain.."/?ssoconnect="..user) + end + end + end +end + -- Portal if ngx.var.host == conf["main_domain"] and string.starts(ngx.var.uri, conf["portal_path"]) @@ -209,6 +268,7 @@ if check_cookie() then return pass end + -- Connect with HTTP Auth if credentials are brought local auth_header = ngx.req.get_headers()["Authorization"] if auth_header then @@ -220,12 +280,12 @@ if auth_header then end end -if not ngx.var.cookie_YnhAuthRedirect and not string.ends(ngx.var.uri, "favicon.ico") then - -- Else redirect to portal - local back_url = ngx.escape_uri(ngx.var.scheme .. "://" .. ngx.var.http_host .. ngx.var.uri) - set_redirect_cookie(back_url) - return ngx.redirect(portal_url) +-- Else redirect to portal +local back_url = ngx.escape_uri(ngx.var.scheme .. "://" .. ngx.var.http_host .. ngx.var.uri) +if set_redirect_cookie(back_url) then + -- From same domain + return redirect(portal_url) else - ngx.status = ngx.HTTP_UNAUTHORIZED - return ngx.exit(ngx.HTTP_OK) + -- From another domain + return redirect(portal_url.."?r="..ngx.encode_base64(back_url)) end diff --git a/conf.json b/conf.json index 4613cc9..8e750f9 100644 --- a/conf.json +++ b/conf.json @@ -3,6 +3,10 @@ "portal_path": "/ssowat/", "portal_port": "443", "portal_scheme": "https", + "domains": [ + "mydomain.com", + "myotherdomain.com" + ], "skipped_urls": [ "mydomain.com/megusta", "myotherdomain.com/somuchwin" diff --git a/init.lua b/init.lua index d8606a0..d17c041 100644 --- a/init.lua +++ b/init.lua @@ -9,3 +9,8 @@ auth_key = math.random(1111111, 9999999) -- Shared table tokens = {} +cache = {} +connections = {} + +-- Path of the configuration +conf_path = 'conf.json'