diff --git a/access.lua b/access.lua index 4ca0f4a..6476d63 100644 --- a/access.lua +++ b/access.lua @@ -29,8 +29,7 @@ local logger = require("log") -- Just a note for the client to know that he passed through the SSO ngx.header["X-SSO-WAT"] = "You've just been SSOed" - -local is_logged_in = hlp.is_logged_in() +local is_logged_in = hlp.refresh_logged_in() -- -- 1. LOGIN @@ -282,66 +281,27 @@ function serveYnhpanel() scandir("/usr/share/ssowat/portal/assets/themes/"..conf.theme, serveThemeFile) end --- local longest_protected_match = hlp.longest_url_path(hlp.get_matches("protected")) or "" --- local longest_skipped_match = hlp.longest_url_path(hlp.get_matches("skipped")) or "" --- local longest_unprotected_match = hlp.longest_url_path(hlp.get_matches("unprotected")) or "" --- --- logger.debug("longest skipped "..longest_skipped_match) --- logger.debug("longest unprotected "..longest_unprotected_match) --- logger.debug("longest protected "..longest_protected_match) --- --- -- --- -- 4. Skipped URLs --- -- --- -- If the URL matches one of the `skipped_urls` in the configuration file, --- -- it means that the URL should not be protected by the SSO and no header --- -- has to be sent, even if the user is already authenticated. --- -- --- --- if longest_skipped_match ~= "" --- and string.len(longest_skipped_match) >= string.len(longest_protected_match) --- and string.len(longest_skipped_match) > string.len(longest_unprotected_match) then --- logger.debug("Skipping "..ngx.var.uri) --- return hlp.pass() --- end --- --- -- --- -- 6. Unprotected URLs --- -- --- -- If the URL matches one of the `unprotected_urls` in the configuration file, --- -- it means that the URL should not be protected by the SSO *but* headers have --- -- to be sent if the user is already authenticated. --- -- --- -- It means that you can let anyone access to an app, but if a user has already --- -- been authenticated on the portal, he can have his authentication headers --- -- passed to the app. --- -- --- --- if longest_unprotected_match ~= "" --- and string.len(longest_unprotected_match) > string.len(longest_protected_match) then --- if is_logged_in then --- serveYnhpanel() --- --- hlp.set_headers() --- end --- logger.debug(ngx.var.uri.." is in unprotected_urls") --- return hlp.pass() --- end --- --- if is_logged_in then --- serveYnhpanel() --- --- -- If user has no access to this URL, redirect him to the portal --- if not hlp.has_access() then --- return hlp.redirect(conf.portal_url) --- end --- --- -- If the user is authenticated and has access to the URL, set the headers --- -- and let it be --- hlp.set_headers() --- return hlp.pass() --- 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"] then + logger.debug("Set 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) + end + + return hlp.pass() +end -- -- 7. Basic HTTP Authentication @@ -364,11 +324,14 @@ if auth_header then logger.debug("User got authenticated through basic auth") -- If user has no access to this URL, redirect him to the portal - if not hlp.has_access(user) then + if not permission or not hlp.has_access(permission, user) then return hlp.redirect(conf.portal_url) end - hlp.set_headers(user) + if permission["auth_header"] then + logger.debug("Set Headers") + hlp.set_headers(user) + end return hlp.pass() end end diff --git a/helpers.lua b/helpers.lua index a32f81d..6fa9cd9 100644 --- a/helpers.lua +++ b/helpers.lua @@ -17,6 +17,8 @@ local url_parser = require "socket.url" -- Import Perl regular expressions library local rex = require "rex_pcre" +local is_logged_in = false + function refresh_config() conf = config.get_config() end @@ -224,7 +226,7 @@ end -- Check if the session cookies are set, and rehash server + client information -- to match the session hash. -- -function is_logged_in() +function refresh_logged_in() local expireTime = ngx.var.cookie_SSOwAuthExpire local user = ngx.var.cookie_SSOwAuthUser local authHash = ngx.var.cookie_SSOwAuthHash @@ -250,7 +252,8 @@ function is_logged_in() if hash ~= authHash then logger.info("Hash "..authHash.." rejected for "..user.."@"..ngx.var.remote_addr) end - return hash == authHash + is_logged_in = hash == authHash + return is_logged_in end end end @@ -268,34 +271,60 @@ 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(user) +function has_access(permission, user) user = user or authUser - logger.debug("User "..user.." try to access "..ngx.var.uri) - - -- Get the longest url permission - longest_permission_match = longest_url_path(permission_matches()) or "" - - logger.debug("Longest permission match : "..longest_permission_match) - - -- If no permission matches, it means that there is no permission defined for this url. - if longest_permission_match == "" then - logger.debug("No access rules defined for user "..user..", assuming it cannot access.") + if permission == nil then return false end + -- Public access + if user == nil or permission["public"] then + logger.debug("A visitor try to access "..ngx.var.uri) + return permission["public"] + end + + logger.debug("User "..user.." try to access "..ngx.var.uri) + -- All user in this permission - allowed_users = conf["permissions"][longest_permission_match] + 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 - logger.debug("User "..user.." can access "..ngx.var.uri) - log_access(user, longest_permission_match) + 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 end end @@ -305,76 +334,6 @@ function has_access(user) return false end -function permission_matches() - if not conf["permissions"] then - conf["permissions"] = {} - end - - local url_matches = {} - - for url, permission in pairs(conf["permissions"]) do - if string.starts(ngx.var.host..ngx.var.uri..uri_args_string(), url) then - logger.debug("Url permission match current uri : "..url) - - table.insert(url_matches, url) - end - end - - return url_matches -end - -function get_matches(section) - if not conf[section.."_urls"] then - conf[section.."_urls"] = {} - end - if not conf[section.."_regex"] then - conf[section.."_regex"] = {} - end - - local url_matches = {} - - for _, url in ipairs(conf[section.."_urls"]) do - if string.starts(ngx.var.host..ngx.var.uri..uri_args_string(), url) - or string.starts(ngx.var.uri..uri_args_string(), url) then - logger.debug(section.."_url match current uri : "..url) - table.insert(url_matches, url) - end - end - for _, regex in ipairs(conf[section.."_regex"]) do - local m1 = match(ngx.var.host..ngx.var.uri..uri_args_string(), regex) - local m2 = match(ngx.var.uri..uri_args_string(), regex) - if m1 then - logger.debug(section.."_regex match current uri : "..regex.." with "..m1) - table.insert(url_matches, m1) - end - if m2 then - logger.debug(section.."_regex match current uri : "..regex.." with "..m2) - table.insert(url_matches, m2) - end - end - - return url_matches -end - -function longest_url_path(urls) - local longest = nil - for _, url in ipairs(urls) do - -- Turn the url into a path, e.g.: - -- /foo/bar -> /foo/bar - -- domain.tld/foo/bar -> /foo/bar - -- https://domain.tld:1234/foo/bar -> /foo/bar - current = url_parser.parse(url).path - if not longest or string.len(longest) < string.len(current) then - longest = current - end - end - if longest and string.ends(longest, "/") then - longest = string.sub(longest, 1, -2) - end - return longest -end - - -- Authenticate a user against the LDAP database using a username or an email -- address. -- Reminder: conf["ldap_identifier"] is "uid" by default @@ -557,7 +516,7 @@ function serve(uri, cache) -- Load login.html as index if rel_path == "/" then - if is_logged_in() then + if is_logged_in then rel_path = "/portal.html" else rel_path = "/login.html" @@ -658,45 +617,48 @@ function get_data_for(view) -- Needed if the LDAP db is changed outside ssowat (from the cli for example). -- Not doing it for ynhpanel.json only for performance reasons, -- so the panel could show wrong first name, last name or main email address - if view ~= "ynhpanel.json" then - delete_user_info_cache(user) - end + -- TODO: What if we remove info during logout? + --if view ~= "ynhpanel.json" then + -- delete_user_info_cache(user) + --end -- Be sure cache is loaded - set_headers(user) + if user then + set_headers(user) - local mails = get_mails(user) - data = { - connected = true, - theme = conf.theme, - portal_url = conf.portal_url, - uid = user, - cn = cache:get(user.."-cn"), - sn = cache:get(user.."-sn"), - givenName = cache:get(user.."-givenName"), - mail = mails["mail"], - mailalias = mails["mailalias"], - maildrop = mails["maildrop"], - app = {} - } + local mails = get_mails(user) + data = { + connected = true, + theme = conf.theme, + portal_url = conf.portal_url, + uid = user, + cn = cache:get(user.."-cn"), + sn = cache:get(user.."-sn"), + givenName = cache:get(user.."-givenName"), + mail = mails["mail"], + mailalias = mails["mailalias"], + maildrop = mails["maildrop"], + app = {} + } - local sorted_apps = {} + local sorted_apps = {} - -- Add user's accessible URLs using the ACLs. - -- 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 then - url = permission['uris'][1] - name = permission['label'] + -- Add user's accessible URLs using the ACLs. + -- 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 + url = permission['uris'][1] + name = permission['label'] - if ngx.var.host == conf["local_portal_domain"] then - url = string.gsub(url, conf["original_portal_domain"], conf["local_portal_domain"]) + if ngx.var.host == conf["local_portal_domain"] then + url = string.gsub(url, conf["original_portal_domain"], conf["local_portal_domain"]) + end + + table.insert(sorted_apps, name) + table.sort(sorted_apps) + table.insert(data["app"], index_of(sorted_apps, name), { url = url, name = name }) end - - table.insert(sorted_apps, name) - table.sort(sorted_apps) - table.insert(data["app"], index_of(sorted_apps, name), { url = url, name = name }) end end end @@ -776,7 +738,7 @@ function edit_user() -- Ensure that user is logged in and has passed information -- before continuing. - if is_logged_in() and args + if is_logged_in and args then -- Set HTTP status to 201 @@ -1069,12 +1031,14 @@ function logout() local args = ngx.req.get_uri_args() -- Delete user cookie if logged in (that should always be the case) - if is_logged_in() then + if is_logged_in then delete_cookie() cache:delete("session_"..authUser) cache:delete(authUser.."-"..conf["ldap_identifier"]) -- Ugly trick to reload cache cache:delete(authUser.."-password") + delete_user_info_cache(authUser) flash("info", t("logged_out")) + is_logged_in = false end -- Redirect with the `r` URI argument if it exists or redirect to portal