refactor: use OOP where appropriate

Yves G 2021-10-02 23:45:31 +02:00
parent 20fc2dbf8b
commit 489350fe21
23 changed files with 382 additions and 397 deletions

View File

@ -29,7 +29,7 @@ test: test-env
${run_test_file} ${ROOT_DIR}/test/config.utest.lua
${run_test_file} ${ROOT_DIR}/test/auth.utest.lua
${run_test_file} ${ROOT_DIR}/test/nginx.utest.lua
${run_test_file} ${ROOT_DIR}/test/profile.utest.lua
${run_test_file} ${ROOT_DIR}/test/identity.utest.lua
${run_test_file} ${ROOT_DIR}/test/sites.utest.lua
${run_test_file} ${ROOT_DIR}/test/crypto.utest.lua
${run_test_file} ${ROOT_DIR}/test/login.utest.lua

View File

@ -9,11 +9,11 @@
local nginx = require("ssso_nginx")
local req_data = nginx.get_request()
local req_data = nginx.class__request:current()
if nginx.is(req_data, "/.well-known/webfinger")
and nginx.has_param(req_data, "rel", "http://openid.net/specs/connect/1.0/issuer")
and nginx.has_param(req_data, "resource")
if req_data:is("/.well-known/webfinger")
and req_data:has_param("rel", "http://openid.net/specs/connect/1.0/issuer")
and req_data:has_param("resource")
then
-- https://openid.net/specs/openid-connect-discovery-1_0.html
local oauth2 = require("ssso_oauth2")
@ -27,14 +27,14 @@ local sites = require("ssso_sites")
local sso_prefix = conf.get_sso_prefix()
local auth, status = sess.get_session()
if nginx.starts_with(req_data, sso_prefix) then
if req_data:starts_with(sso_prefix) then
-- SSO-specific URL
if nginx.starts_with(req_data, sso_prefix .. "/login") then
if req_data:starts_with(sso_prefix .. "/login") then
local login = require("ssso_login")
return login.answer_request(req_data)
elseif nginx.starts_with(req_data, sso_prefix .. "/oauth2") then
elseif req_data:starts_with(sso_prefix .. "/oauth2") then
local oauth2 = require("ssso_oauth2")
return oauth2.answer_request(req_data, auth)
elseif auth then

View File

@ -7,7 +7,7 @@ local function load_conf(prefix)
local file = assert(io.open(prefix .. name_suffix, "r"), "File " .. prefix .. name_suffix .. " not found")
conf = json.decode(file:read("*all"))
file:close()
assert(conf["auth"], "Simple-SSO configuration is missing a `auth` entry")
assert(conf["auth"], "Simple-SSO configuration is missing an `auth` entry")
assert(conf["sso_host"], "Simple-SSO configuration is missing a `sso_host` entry")
assert(conf["sso_prefix"], "Simple-SSO configuration is missing a `sso_prefix` entry")
assert(conf["session_seconds"], "Simple-SSO configuration is missing a `session_seconds` entry")

View File

@ -7,7 +7,6 @@ local b64 = require("ssso_base64")
local config = require("ssso_config")
local log = require("ssso_log")
local nginx = require("ssso_nginx")
local prof = require("ssso_profile")
local sites = require("ssso_sites")
local KEY_SIZE = 32 -- 256 bits for AES-256-GCMs key and SHA-256
@ -88,12 +87,12 @@ end
-- https://www.rfc-editor.org/rfc/rfc7519
-- https://openid.net/specs/openid-connect-core-1_0.html
local function get_jws_and_tslimit(data)
local user = prof.user(data)
local ser_data = prof.serialize(data) .. sites.serialize(data)
log.debug("Creating JWS with data: " .. ser_data:gsub("([\031\030\029\028])", function(s) return "[" .. s:byte() .. "]" end))
local crypted_data = encrypt(ser_data)
if not user or not crypted_data then
local function get_jws_and_tslimit(profile)
local user = profile:user()
local ser_profile = profile:serialize()
log.debug("Creating JWS with profile: " .. ser_profile:gsub("([\031\030\029\028\027\026])", function(s) return "[" .. s:byte() .. "]" end))
local crypted_profile = encrypt(ser_profile)
if not user or not crypted_profile then
return nil, nil
end
local iat = nginx.get_seconds_since_epoch()
@ -104,30 +103,29 @@ local function get_jws_and_tslimit(data)
aud = user,
exp = exp,
iat = iat,
x_ssso = b64.encode_base64url(crypted_data),
x_ssso = b64.encode_base64url(crypted_profile),
}
return to_jws(jwt), exp
end
local function get_data_and_new_jws(jws)
local function get_profile_and_new_jws(jws)
local jwt = to_jwt(jws)
local iat = nginx.get_seconds_since_epoch()
if jwt == nil or not jwt["x_ssso"] or not jwt["exp"] or jwt.exp < iat then
return nil, nil, nil
end
local ser_data = decrypt(b64.decode_base64url(jwt.x_ssso))
if not ser_data then
local ser_profile = decrypt(b64.decode_base64url(jwt.x_ssso))
if not ser_profile then
return nil, nil, nil
end
log.debug("Read data from JWS: " .. ser_data:gsub("([\031\030\029\028])", function(s) return "[" .. s:byte() .. "]" end))
local data, remainder, _ = prof.deserialize(ser_data)
data, _ = sites.deserialize_update(remainder, data)
log.debug("Read profile from JWS: " .. ser_profile:gsub("([\031\030\029\028])", function(s) return "[" .. s:byte() .. "]" end))
local profile = sites.class__profile:deserialize(ser_profile)
jwt.iat = iat
jwt.exp = iat + config.get_session_seconds()
return data, to_jws(jwt), jwt.exp
return profile, to_jws(jwt), jwt.exp
end
return {
get_jws_and_tslimit = get_jws_and_tslimit,
get_data_and_new_jws = get_data_and_new_jws,
get_profile_and_new_jws = get_profile_and_new_jws,
}

60
src/ssso_identity.lua Normal file
View File

@ -0,0 +1,60 @@
local b64 = require("ssso_base64")
local class__identity = {}
function class__identity:build(user, password, name, email)
local identity = {
u = user,
p = password,
n = name,
e = email,
}
setmetatable(identity, {__index = self})
return identity
end
function class__identity:serialize() -- TODO: test
return (self.u or "\025")
.. "\031" .. (self.p or "\025")
.. "\031" .. (self.n or "\025")
.. "\031" .. (self.e or "\025")
end
function class__identity:deserialize(ser) -- TODO: test
local identity
ser:gsub("^(.-)\031(.-)\031(.-)\031(.*)", function (u, p, n, e)
if u == "\025" then u = nil end
if p == "\025" then p = nil end
if n == "\025" then n = nil end
if e == "\025" then e = nil end
identity = self:build(u, p, n, e)
end)
return identity
end
function class__identity:format(template)
local s = template
s = s:gsub("\ru%.", self.u or "")
s = s:gsub("\rp%.", self.p or "")
s = s:gsub("\rn%.", self.n or "")
s = s:gsub("\re%.", self.e or "")
s = s:gsub("\rb64%(([^\r]-)%)%.", function(x) return b64.encode_base64(x) end)
s = s:gsub("\ru64%(([^\r]-)%)%.", function(x) return b64.encode_base64url(x) end)
return s
end
function class__identity:email()
return self.e
end
function class__identity:name()
return self.n
end
function class__identity:user()
return self.u
end
return {
class__identity = class__identity,
}

View File

@ -3,7 +3,6 @@ local conf = require("ssso_config")
local crypto = require("ssso_crypto")
local log = require("ssso_log")
local nginx = require("ssso_nginx")
local prof = require("ssso_profile")
local sites = require("ssso_sites")
local util = require("ssso_util")
@ -51,15 +50,14 @@ local function check_credentials_and_get_profile(user, password)
local user_data = auth.read_user(user, password)
if user_data then
log.debug("Credentials accepted for user " .. user_data.name)
local profile = prof.build_profile(user, password, user_data.name, user_data.email)
return sites.with_sites(profile, user)
return sites.class__profile:build_from_conf(user, password, user_data.name, user_data.email)
else
return nil
end
end
local function check_login(req_data)
req_data = nginx.with_post_parameters(req_data)
req_data = req_data:with_post_parameters()
local user = req_data.query_params["login"] or ""
local password = req_data.query_params["password"] or ""
local profile = check_credentials_and_get_profile(user, password)
@ -88,17 +86,17 @@ end
local function answer_request(req_data)
local login = conf.get_sso_prefix() .. "/login"
if nginx.is(req_data, login) then
if nginx.has_method(req_data, "POST") then
if req_data:is(login) then
if req_data:has_method("POST") then
log.info("Checking login")
return check_login(req_data)
else
local html = inject_data(contents("login.html"), req_data, nil)
return nginx.return_contents(html, "text/html; charset=UTF-8")
end
elseif nginx.is(req_data, login .. ".css") then
elseif req_data:is(login .. ".css") then
return nginx.return_contents(contents("login.css"), "text/css; charset=UTF-8")
elseif nginx.is(req_data, login .. ".js") then
elseif req_data:is(login .. ".js") then
return nginx.return_contents(contents("login.js"), "application/javascript; charset=UTF-8")
else
log.info("Unknown login file")

View File

@ -3,7 +3,14 @@ local b64 = require("ssso_base64")
local util = require("ssso_util")
local conf = require("ssso_config")
local function get_request()
local class__request = {}
local function str_starts_with(str, begin)
local i, _ = str:find(util.str_to_pattern(begin))
return 1 == i
end
function class__request:current()
local vars = ngx.var
local request = {
referer = vars.http_referer,
@ -11,6 +18,8 @@ local function get_request()
method = vars.request_method,
uri = vars.request_uri,
}
setmetatable(request, {__index = self})
if request.referer == "" then
request.referer = nil
end
@ -33,20 +42,35 @@ local function get_request()
return request
end
local function with_post_parameters(req_data)
function class__request:with_post_parameters()
ngx.req.read_body()
local args, _ = ngx.req.get_post_args()
if args then
for key, val in pairs(args) do
req_data.query_params[key] = val
self.query_params[key] = val
end
end
return req_data
return self
end
local function str_starts_with(str, begin)
local i, _ = str:find(util.str_to_pattern(begin))
return 1 == i
function class__request:has_method(method)
return string.upper(method) == string.upper(self.method)
end
function class__request:is(url)
return self.target == url
end
function class__request:matches(lua_pattern)
return self.target:match(lua_pattern)
end
function class__request:starts_with(prefix)
return str_starts_with(self.target, prefix)
end
function class__request:has_param(param, value)
return self.query_params[param] ~= nil and (value == nil or self.query_params[param] == value)
end
local function get_basic_auth()
@ -103,26 +127,6 @@ local function add_header(name, value)
ngx.req.set_header(name, value)
end
local function has_method(req_data, method)
return string.upper(method) == string.upper(req_data.method)
end
local function is(req_data, url)
return req_data.target == url
end
local function matches(req_data, lua_pattern)
return req_data.target:match(lua_pattern)
end
local function starts_with(req_data, prefix)
return str_starts_with(req_data.target, prefix)
end
local function has_param(req_data, param, value)
return req_data.query_params[param] ~= nil and (value == nil or req_data.query_params[param] == value)
end
local function answer_not_found(req_data)
return ngx.exit(404)
end
@ -167,21 +171,15 @@ return {
add_cookie = add_cookie,
add_header = add_header,
answer_not_found = answer_not_found,
class__request = class__request,
forward_request = forward_request,
get_basic_auth = get_basic_auth,
get_jws_cookie = get_jws_cookie,
get_request = get_request,
get_seconds_since_epoch = get_seconds_since_epoch,
has_method = has_method,
has_param = has_param,
is = is,
matches = matches,
redirect_to_login = redirect_to_login,
redirect_to_page = redirect_to_page,
redirect_to_portal = redirect_to_portal,
return_contents = return_contents,
set_jws_cookie = set_jws_cookie,
starts_with = starts_with,
answer_unexpected_error = answer_unexpected_error,
with_post_parameters = with_post_parameters,
}

View File

@ -1,7 +1,6 @@
local util = require("ssso_util")
local conf = require("ssso_config")
local nginx = require("ssso_nginx")
local prof = require("ssso_profile")
local sites = require("ssso_sites")
local root = ""
@ -18,14 +17,14 @@ local function contents(relative)
end
local function inject_data(html, profile)
html = html:gsub("SSSO_USER", util.str_to_html(prof.user(profile)))
html = html:gsub("SSSO_NAME", util.str_to_html(prof.name(profile)))
html = html:gsub("SSSO_EMAIL", util.str_to_html(prof.email(profile)))
html = html:gsub("SSSO_USER", util.str_to_html(profile:user()))
html = html:gsub("SSSO_NAME", util.str_to_html(profile:name()))
html = html:gsub("SSSO_EMAIL", util.str_to_html(profile:email()))
local links = ""
local allowed = sites.authorized_links(prof.user(profile))
local allowed = profile:authorized_links()
table.sort(allowed, function(a1, a2) return a1.label < a2.label end)
for _, allow in ipairs(allowed) do
links = links .. '<li><a href="' .. prof.format(allow.link, profile) .. '"><span>' .. util.str_to_html(prof.format(allow.label, profile)) .. "</span></a></li>"
links = links .. '<li><a href="' .. profile:format(allow.link) .. '"><span>' .. util.str_to_html(profile:format(allow.label)) .. "</span></a></li>"
end
if links ~= "" then
html = html:gsub('<nav id="sites"></nav>', '<nav id="sites"><ul>' .. links .. "</ul></nav>")
@ -37,12 +36,12 @@ end
local function answer_request(req_data, profile)
local portal = conf.get_sso_prefix() .. "/portal"
if nginx.is(req_data, portal) then
if req_data:is(portal) then
local html = inject_data(contents("portal.html"), profile)
return nginx.return_contents(html, "text/html; charset=UTF-8")
elseif nginx.is(req_data, portal .. ".css") then
elseif req_data:is(portal .. ".css") then
return nginx.return_contents(contents("portal.css"), "text/css; charset=UTF-8")
elseif nginx.is(req_data, portal .. ".js") then
elseif req_data:is(portal .. ".js") then
return nginx.return_contents(contents("portal.js"), "application/javascript; charset=UTF-8")
else
return nginx.answer_not_found(req_data)

View File

@ -1,63 +0,0 @@
local b64 = require("ssso_base64")
local function build_profile(user, password, name, email)
return {
u = user,
p = password,
n = name,
e = email,
}
end
local function serialize(profile)
return (profile.u or "\025") .. "\031" ..
(profile.p or "\025") .. "\031" ..
(profile.n or "\025") .. "\031" ..
(profile.e or "\025") .. "\031"
end
local function deserialize(ser)
local profile
local remainder = ser:gsub("^(.-)\031(.-)\031(.-)\031(.-)\031", function (u, p, n, e)
if u == "\025" then u = nil end
if p == "\025" then p = nil end
if n == "\025" then n = nil end
if e == "\025" then e = nil end
profile = build_profile(u, p, n, e)
return ""
end)
return profile, remainder
end
local function format(template, profile)
local s = template
s = s:gsub("\ru%.", profile.u or "")
s = s:gsub("\rp%.", profile.p or "")
s = s:gsub("\rn%.", profile.n or "")
s = s:gsub("\re%.", profile.e or "")
s = s:gsub("\rb64%(([^\r]-)%)%.", function(x) return b64.encode_base64(x) end)
s = s:gsub("\ru64%(([^\r]-)%)%.", function(x) return b64.encode_base64url(x) end)
return s
end
local function email(profile)
return profile.e
end
local function name(profile)
return profile.n
end
local function user(profile)
return profile.u
end
return {
build_profile = build_profile,
deserialize = deserialize, -- TODO: test
serialize = serialize, -- TODO: test
email = email,
format = format,
name = name,
user = user,
}

View File

@ -3,27 +3,27 @@ local login = require("ssso_login")
local nginx = require("ssso_nginx")
local function get_session()
local session, jws, tslimit
local profile, jws, tslimit
local user, password = nginx.get_basic_auth()
if user and password then
session = login.check_credentials_and_get_profile(user, password)
if session then
jws, tslimit = crypto.get_jws_and_tslimit(session)
profile = login.check_credentials_and_get_profile(user, password)
if profile then
jws, tslimit = crypto.get_jws_and_tslimit(profile)
end
end
if not session then
if not profile then
local cookie = nginx.get_jws_cookie()
if not cookie or cookie == "" then
return nil, 401
end
session, jws, tslimit = crypto.get_data_and_new_jws(cookie)
profile, jws, tslimit = crypto.get_profile_and_new_jws(cookie)
end
if session then
if profile then
nginx.set_jws_cookie(jws, tslimit)
return session, 200
return profile, 200
else
return nil, 403
end

View File

@ -1,6 +1,6 @@
local json = require("cjson.safe")
local id = require("ssso_identity")
local nginx = require("ssso_nginx")
local prof = require("ssso_profile")
local known_private_re = {}
local known_sites = {}
@ -26,7 +26,7 @@ end
local function is_known_private(req_data)
for _, r in ipairs(known_private_re) do
if nginx.matches(req_data, r) then
if req_data:matches(r) then
return true
end
end
@ -37,12 +37,12 @@ local function handle_request(req_data, auth)
if auth then
for _, site in ipairs(auth.ok) do
for _, r in ipairs(site.r) do
if nginx.matches(req_data, r) then
if req_data:matches(r) then
for _, a in ipairs(site.a) do
if a[1] == "C" then
nginx.add_cookie(a[2], prof.format(a[3], auth))
nginx.add_cookie(a[2], auth:format(a[3]))
elseif a[1] == "H" then
nginx.add_header(a[2], prof.format(a[3], auth))
nginx.add_header(a[2], auth:format(a[3]))
end
end
return nginx.forward_request(req_data)
@ -50,7 +50,7 @@ local function handle_request(req_data, auth)
end
end
for _, r in ipairs(auth.ko) do
if nginx.matches(req_data, r) then
if req_data:matches(r) then
return nginx.redirect_to_login(req_data, 403)
end
end
@ -83,12 +83,31 @@ local function format_pattern(pattern)
return ok
end
local function with_sites(profile, user)
local class__profile = {}
setmetatable(class__profile, {__index = id.class__identity})
function class__profile:build(delegate_identity, ok_list, ko_list)
local profile = {
delegate = delegate_identity,
ok = ok_list,
ko = ko_list,
}
setmetatable(profile, {__index = self})
return profile
end
function class__profile:build_from_lists(user, password, name, email, ok_list, ko_list)
local delegate_identity = id.class__identity:build(user, password, name, email)
return self:build(delegate_identity, ok_list, ko_list)
end
function class__profile:build_from_conf(user, password, name, email)
local f, site, go_on
local ok_list = {}
local ko_list = {}
for _, name in ipairs(known_sites) do
f = io.open(name, "r")
local delegate_identity = id.class__identity:build(user, password, name, email)
for _, known in ipairs(known_sites) do
f = io.open(known, "r")
if f then
site = json.decode(f:read("*all"))
f:close()
@ -120,14 +139,28 @@ local function with_sites(profile, user)
end
end
end
profile["ok"] = ok_list
profile["ko"] = ko_list
return profile
return self:build(delegate_identity, ok_list, ko_list)
end
local function serialize(profile)
function class__profile:email()
return self.delegate:email()
end
function class__profile:name()
return self.delegate:name()
end
function class__profile:user()
return self.delegate:user()
end
function class__profile:format(template)
return self.delegate:format(template)
end
function class__profile:serialize()
local ser_s = ""
for _, site in ipairs(profile.ok or {}) do
for _, site in ipairs(self.ok or {}) do
for _, r in ipairs(site.r) do
ser_s = ser_s .. r .. "\029"
end
@ -136,40 +169,40 @@ local function serialize(profile)
end
ser_s = ser_s .. "\031"
end
for _, r in ipairs(profile.ko or {}) do
for _, r in ipairs(self.ko or {}) do
ser_s = ser_s .. r .. "\030"
end
return ser_s
return ser_s .. "\026" .. self.delegate:serialize()
end
local function deserialize_update(ser, profile)
if not ser or ser == "" then
return profile
end
function class__profile:deserialize(ser)
local ok_list = {}
local ko_list = {}
local remainder = ser:gsub("(.-)\031", function (ser_ok)
local ok = {
r = {},
a = {},
}
ser_ok = ser_ok:gsub("(.-)\029", function(r) table.insert(ok.r, r); return "" end)
ser_ok:gsub("(.)([^=]-)=(.-)\028", function(t, n, v) table.insert(ok.a, {t, n, v}) end)
table.insert(ok_list, ok)
ser = ser:gsub("^(.-)\026", function (ser_sites)
ser_sites = ser_sites:gsub("(.-)\031", function (ser_ok)
local ok = {
r = {},
a = {},
}
ser_ok = ser_ok:gsub("(.-)\029", function(r) table.insert(ok.r, r); return "" end)
ser_ok:gsub("(.)([^=]-)=(.-)\028", function(t, n, v) table.insert(ok.a, {t, n, v}) end)
table.insert(ok_list, ok)
return ""
end)
ser_sites = ser_sites:gsub("(.-)\030", function (ko)
table.insert(ko_list, ko)
return ""
end)
return ""
end)
remainder = remainder:gsub("(.-)\030", function (ko)
table.insert(ko_list, ko)
return ""
end)
profile.ok = ok_list
profile.ko = ko_list
return profile, remainder
local delegate_identity = id.class__identity:deserialize(ser)
return self:build(delegate_identity, ok_list, ko_list)
end
local function authorized_links(user)
function class__profile:authorized_links()
local links = {}
local f, site, go_on
local user = self:user()
for _, name in ipairs(known_sites) do
f = io.open(name, "r")
if f then
@ -206,10 +239,7 @@ local function authorized_links(user)
end
return {
authorized_links = authorized_links,
deserialize_update = deserialize_update, -- TODO: test
class__profile = class__profile,
handle_request = handle_request,
load_sites = load_sites,
serialize = serialize, -- TODO: test
with_sites = with_sites,
}

View File

@ -1,14 +1,13 @@
local lu = require("luaunit")
local conf = require("ssso_config")
local crypt = require("ssso_crypto")
local sites = require("ssso_sites")
local here = debug.getinfo(1).source:sub(2, -18)
conf.load_conf(here)
local data = {
u = "u",
e = "u@h",
ok = {
local data = sites.class__profile:build_from_lists("u", nil, nil, "u@h",
{
{
r = {
"regex1",
@ -19,10 +18,10 @@ local data = {
}
},
},
ko = {
{
"regex2",
},
}
}
)
function test_jws_is_well_structured()
local jws, _ = crypt.get_jws_and_tslimit(data)
@ -31,17 +30,12 @@ end
function test_jws_can_be_decoded()
local jws, _ = crypt.get_jws_and_tslimit(data)
local stored, _, _ = crypt.get_data_and_new_jws(jws)
local stored, _, _ = crypt.get_profile_and_new_jws(jws)
lu.assertEquals(stored, data)
end
function test_data_must_contain_field_u()
local wrong = {
i = 1,
f = 2.3,
b = true,
n = nil,
}
function test_data_must_be_a_profile_with_a_user()
local wrong = sites.class__profile:build_from_conf(nil, "P", "N", "E")
local jws, ts = crypt.get_jws_and_tslimit(wrong)
lu.assertNil(jws)
lu.assertNil(ts)

61
test/identity.utest.lua Normal file
View File

@ -0,0 +1,61 @@
local lu = require("luaunit")
local id = require("ssso_identity")
function test_format_replaces_user_placeholders()
local identity = id.class__identity:build("U", nil, nil, nil)
local template = '{user: "\ru.", foo: "bar", name: "\ru."}'
lu.assertEquals(identity:format(template), '{user: "U", foo: "bar", name: "U"}')
end
function test_format_replaces_password_placeholders()
local identity = id.class__identity:build(nil, "P", nil, nil)
local template = '{pass: "\rp.", foo: "bar", secret: "\rp."}'
lu.assertEquals(identity:format(template), '{pass: "P", foo: "bar", secret: "P"}')
end
function test_format_replaces_name_placeholders()
local identity = id.class__identity:build(nil, nil, "N", nil)
local template = '{name: "\rn.", foo: "bar", nickname: "\rn."}'
lu.assertEquals(identity:format(template), '{name: "N", foo: "bar", nickname: "N"}')
end
function test_format_replaces_email_placeholders()
local identity = id.class__identity:build(nil, nil, nil, "user@host")
local template = '{user: "\re.", foo: "bar", mail: "\re."}'
lu.assertEquals(identity:format(template), '{user: "user@host", foo: "bar", mail: "user@host"}')
end
function test_format_replaces_base64_calls()
local identity = id.class__identity:build("👤", "🔒", nil, nil)
local template = 'Authorization: Basic \rb64(\ru.:\rp.).'
lu.assertEquals(identity:format(template), 'Authorization: Basic 8J+RpDrwn5SS')
end
function test_format_replaces_base64url_calls()
local identity = id.class__identity:build("👤", "🔒", nil, nil)
local template = '?authorization=Basic+\ru64(\ru.:\rp.).'
lu.assertEquals(identity:format(template), '?authorization=Basic+8J-RpDrwn5SS')
end
function test_email_returns_the_identity_s_email()
local identity = id.class__identity:build(nil, nil, nil, "E")
lu.assertEquals(identity:email(), "E")
end
function test_name_returns_the_identity_s_name()
local identity = id.class__identity:build(nil, nil, "N", nil)
lu.assertEquals(identity:name(), "N")
end
function test_user_returns_the_identity_s_user()
local identity = id.class__identity:build("U", nil, nil, nil)
lu.assertEquals(identity:user(), "U")
end
function test_build_identity_returns_the_given_information()
lu.assertEquals(id.class__identity:build("U", "P", "N", "E"), {u = "U", p = "P", n = "N", e = "E"})
end
os.exit(lu.LuaUnit.run())

View File

@ -15,7 +15,7 @@ function test_get_login_url_returns_html_with_back_url_substitution()
ngx.reset_resp_body()
ngx.var.request_method = "GET"
ngx.var.request_uri = "/ssso/login?back=/somewhere"
local r = ng.get_request()
local r = ng.class__request:current()
local expected = [[<html><head>
<link href="login.css">
<script src="login.js"></script>
@ -40,7 +40,7 @@ function test_login_css_url_returns_css()
ngx.reset_resp_body()
ngx.var.request_method = "BLABLA"
ngx.var.request_uri = "/ssso/login.css"
local r = ng.get_request()
local r = ng.class__request:current()
local expected = "/*CSS*/\n"
-- when
local resp = login.answer_request(r)
@ -58,7 +58,7 @@ function test_login_js_url_returns_js()
ngx.reset_resp_body()
ngx.var.request_method = "BLABLA"
ngx.var.request_uri = "/ssso/login.js"
local r = ng.get_request()
local r = ng.class__request:current()
local expected = "//JS\n"
-- when
local resp = login.answer_request(r)
@ -75,7 +75,7 @@ function test_unknown_login_url_returns_404()
ngx.reset_resp_body()
ngx.var.request_method = "BLABLA"
ngx.var.request_uri = "/ssso/login/unknown"
local r = ng.get_request()
local r = ng.class__request:current()
-- when
local resp = login.answer_request(r)
-- then
@ -90,7 +90,7 @@ function test_get_login_url_with_cause_401_returns_html_with_associated_message(
ngx.reset_resp_body()
ngx.var.request_method = "GET"
ngx.var.request_uri = "/ssso/login?cause=401"
local r = ng.get_request()
local r = ng.class__request:current()
local expected = [[<html><head>
<link href="login.css">
<script src="login.js"></script>
@ -115,7 +115,7 @@ function test_get_login_url_with_cause_403_returns_html_with_associated_message(
ngx.reset_resp_body()
ngx.var.request_method = "GET"
ngx.var.request_uri = "/ssso/login?cause=403"
local r = ng.get_request()
local r = ng.class__request:current()
local expected = [[<html><head>
<link href="login.css">
<script src="login.js"></script>
@ -141,7 +141,7 @@ function test_post_login_url_with_wrong_credentials_returns_html_with_associated
ngx.reset_post_var()
ngx.var.request_method = "POST"
ngx.var.request_uri = "/ssso/login"
local r = ng.get_request()
local r = ng.class__request:current()
local expected = [[<html><head>
<link href="login.css">
<script src="login.js"></script>
@ -169,7 +169,7 @@ function test_post_login_url_with_good_credentials_redirects_to_portal_with_sess
ngx.var.request_uri = "/ssso/login"
ngx.post_var.login = "goodlogin"
ngx.post_var.password = "goodpassword"
local r = ng.get_request()
local r = ng.class__request:current()
-- when
local resp = login.answer_request(r)
-- then
@ -190,7 +190,7 @@ function test_post_login_url_with_good_credentials_and_back_url_redirects_to_giv
ngx.post_var.login = "goodlogin"
ngx.post_var.password = "goodpassword"
ngx.post_var.back = "/somewhere"
local r = ng.get_request()
local r = ng.class__request:current()
-- when
local resp = login.answer_request(r)
-- then

View File

@ -15,7 +15,7 @@ function test_refe_host_meth_uri_taken_from_ngx()
ngx.var.request_method = "M"
ngx.var.request_uri = "U"
-- when
local r = ng.get_request()
local r = ng.class__request:current()
-- then
lu.assertEquals(r.referer, "R")
lu.assertEquals(r.host, "H")
@ -31,7 +31,7 @@ function test_empty_referer_reported_as_nil()
ngx.var.request_uri = "U"
ngx.var.http_referer = ""
-- when
local r = ng.get_request()
local r = ng.class__request:current()
-- then
lu.assertEquals(r.referer, nil)
end
@ -41,7 +41,7 @@ function test_query_params_split_from_uri_and_decoded()
ngx.reset_var()
ngx.var.request_uri = "U?P=V&Q=W"
-- when
local r = ng.get_request()
local r = ng.class__request:current()
-- then
lu.assertEquals(r.uri, "U?P=V&Q=W")
lu.assertEquals(r.target, "U")
@ -53,7 +53,7 @@ function test_default_scheme_is_http()
ngx.reset_var()
ngx.var.request_uri = "U"
-- when
local r = ng.get_request()
local r = ng.class__request:current()
-- then
lu.assertEquals(r.scheme, "http")
end
@ -64,7 +64,7 @@ function test_scheme_is_https_when_proxy_https_var()
ngx.var.request_uri = "U"
ngx.var.proxy_https = 1
-- when
local r = ng.get_request()
local r = ng.class__request:current()
-- then
lu.assertEquals(r.scheme, "https")
end
@ -75,7 +75,7 @@ function test_scheme_is_https_when_https_var()
ngx.var.request_uri = "U"
ngx.var.https = 1
-- when
local r = ng.get_request()
local r = ng.class__request:current()
-- then
lu.assertEquals(r.scheme, "https")
end
@ -142,10 +142,10 @@ function test_method_is_recognized_case_insensitive()
ngx.reset_var()
ngx.var.request_method = "get"
ngx.var.request_uri = "U"
local r = ng.get_request()
local r = ng.class__request:current()
-- when
local is_get = ng.has_method(r, "GET")
local is_post = ng.has_method(r, "POST")
local is_get = r:has_method("GET")
local is_post = r:has_method("POST")
-- then
lu.assertTrue(is_get and true)
lu.assertFalse(is_post or false)
@ -155,10 +155,10 @@ function test_uri_identity_ignores_query_parameters()
-- given
ngx.reset_var()
ngx.var.request_uri = "U?P=V&Q=W"
local r = ng.get_request()
local r = ng.class__request:current()
-- when
local is_without_qp = ng.is(r, "U")
local is_with_qp = ng.is(r, "U?P=V&Q=W")
local is_without_qp = r:is("U")
local is_with_qp = r:is("U?P=V&Q=W")
-- then
lu.assertTrue(is_without_qp and true)
lu.assertFalse(is_with_qp or false)
@ -168,10 +168,10 @@ function test_uri_match_ignores_query_parameters()
-- given
ngx.reset_var()
ngx.var.request_uri = "/aa?bb"
local r = ng.get_request()
local r = ng.class__request:current()
-- when
local matches_without_qp = ng.matches(r, "/a+$")
local matches_with_qp = ng.matches(r, "/a.*b")
local matches_without_qp = r:matches("/a+$")
local matches_with_qp = r:matches("/a.*b")
-- then
lu.assertTrue(matches_without_qp and true)
lu.assertFalse(matches_with_qp or false)
@ -181,10 +181,10 @@ function test_starts_with_ignores_query_parameters()
-- given
ngx.reset_var()
ngx.var.request_uri = "/aa?bb"
local r = ng.get_request()
local r = ng.class__request:current()
-- when
local start_without_qp = ng.starts_with(r, "/a")
local start_with_qp = ng.starts_with(r, "/aa?b")
local start_without_qp = r:starts_with("/a")
local start_with_qp = r:starts_with("/aa?b")
-- then
lu.assertTrue(start_without_qp and true)
lu.assertFalse(start_with_qp or false)
@ -194,10 +194,10 @@ function test_starts_with_must_start_with_given_value()
-- given
ngx.reset_var()
ngx.var.request_uri = "/aa?bb"
local r = ng.get_request()
local r = ng.class__request:current()
-- when
local start_in_middle = ng.starts_with(r, "aa")
local does_not_start = ng.starts_with(r, "x")
local start_in_middle = r:starts_with("aa")
local does_not_start = r:starts_with("x")
-- then
lu.assertFalse(start_in_middle or false)
lu.assertFalse(does_not_start or false)
@ -207,11 +207,11 @@ function test_has_param_works_disregarding_the_value()
-- given
ngx.reset_var()
ngx.var.request_uri = "/aa?bb&c=1"
local r = ng.get_request()
local r = ng.class__request:current()
-- when
local has_unknown_param = ng.has_param(r, "b")
local has_unvalued_param = ng.has_param(r, "bb")
local has_valued_param = ng.has_param(r, "c")
local has_unknown_param = r:has_param("b")
local has_unvalued_param = r:has_param("bb")
local has_valued_param = r:has_param("c")
-- then
lu.assertFalse(has_unknown_param or false)
lu.assertTrue(has_unvalued_param and true)
@ -222,10 +222,10 @@ function test_has_param_works_with_a_correct_value()
-- given
ngx.reset_var()
ngx.var.request_uri = "/aa?bb&c=1"
local r = ng.get_request()
local r = ng.class__request:current()
-- when
local has_unvalued_param = ng.has_param(r, "bb", true)
local has_valued_param = ng.has_param(r, "c", "1")
local has_unvalued_param = r:has_param("bb", true)
local has_valued_param = r:has_param("c", "1")
-- then
lu.assertTrue(has_unvalued_param and true)
lu.assertTrue(has_valued_param and true)
@ -235,11 +235,11 @@ function test_has_param_works_with_a_wrong_value()
-- given
ngx.reset_var()
ngx.var.request_uri = "/aa?bb&c=1"
local r = ng.get_request()
local r = ng.class__request:current()
-- when
local has_unknown_param = ng.has_param(r, "b", "x")
local has_unvalued_param = ng.has_param(r, "bb", "x")
local has_valued_param = ng.has_param(r, "c", "x")
local has_unknown_param = r:has_param("b", "x")
local has_unvalued_param = r:has_param("bb", "x")
local has_valued_param = r:has_param("c", "x")
-- then
lu.assertFalse(has_unknown_param or false)
lu.assertFalse(has_unvalued_param or false)
@ -284,7 +284,7 @@ function test_with_post_parameters_merges_post_parameters_to_request_data()
ngx.post_var.p = "5"
ngx.post_var.r = "hello"
-- when (1)
local r = ng.get_request()
local r = ng.class__request:current()
-- then (1)
lu.assertEquals(r, {
scheme = "http",
@ -296,7 +296,7 @@ function test_with_post_parameters_merges_post_parameters_to_request_data()
},
})
-- when (2)
r = ng.with_post_parameters(r)
r = r:with_post_parameters()
-- then (2)
lu.assertEquals(r, {
scheme = "http",

View File

@ -1,17 +1,14 @@
local lu = require("luaunit")
local ngx = require("ngx")
local crypto = require("ssso_crypto")
local sites = require("ssso_sites")
require("do_init")
function test_portal_url_returns_html_with_authorized_links_and_identity()
-- given
local jws, _ = crypto.get_jws_and_tslimit({
u = "guest",
p = "",
n = "Guest",
e = "guest@example.org",
})
local profile = sites.class__profile:build_from_lists("guest", "", "Guest", "guest@example.org")
local jws, _ = crypto.get_jws_and_tslimit(profile)
ngx.reset_resp_body()
ngx.reset_var()
ngx.var.cookie_SSSO_TOKEN = jws

View File

@ -1,12 +1,14 @@
local lu = require("luaunit")
local ngx = require("ngx")
local crypto = require("ssso_crypto")
local sites = require("ssso_sites")
require("do_init")
function test_portal_css_url_returns_css()
-- given
local jws, _ = crypto.get_jws_and_tslimit({u = "U", p = "P", n = "N", e = "u@h"})
local profile = sites.class__profile:build_from_lists("U", "P", "N", "u@h")
local jws, _ = crypto.get_jws_and_tslimit(profile)
ngx.reset_resp_body()
ngx.reset_var()
ngx.var.cookie_SSSO_TOKEN = jws

View File

@ -1,12 +1,14 @@
local lu = require("luaunit")
local ngx = require("ngx")
local crypto = require("ssso_crypto")
local sites = require("ssso_sites")
require("do_init")
function test_portal_js_url_returns_js()
-- given
local jws, _ = crypto.get_jws_and_tslimit({u = "U", p = "P", n = "N", e = "u@h"})
local profile = sites.class__profile:build_from_lists("U", "P", "N", "u@h")
local jws, _ = crypto.get_jws_and_tslimit(profile)
ngx.reset_resp_body()
ngx.reset_var()
ngx.var.cookie_SSSO_TOKEN = jws

View File

@ -1,12 +1,14 @@
local lu = require("luaunit")
local ngx = require("ngx")
local crypto = require("ssso_crypto")
local sites = require("ssso_sites")
require("do_init")
function test_unknown_portal_url_returns_404()
-- given
local jws, _ = crypto.get_jws_and_tslimit({u = "U", p = "P", n = "N", e = "u@h"})
local profile = sites.class__profile:build_from_lists("U", "P", "N", "u@h")
local jws, _ = crypto.get_jws_and_tslimit(profile)
ngx.reset_resp_body()
ngx.reset_var()
ngx.var.cookie_SSSO_TOKEN = jws

View File

@ -1,6 +1,5 @@
local lu = require("luaunit")
local ngx = require("ngx")
local crypto = require("ssso_crypto")
require("do_init")

View File

@ -1,81 +0,0 @@
local lu = require("luaunit")
local prf = require("ssso_profile")
function test_format_replaces_user_placeholders()
local profile = {
u = "U",
}
local template = '{user: "\ru.", foo: "bar", name: "\ru."}'
lu.assertEquals(prf.format(template, profile), '{user: "U", foo: "bar", name: "U"}')
end
function test_format_replaces_password_placeholders()
local profile = {
p = "P",
}
local template = '{pass: "\rp.", foo: "bar", secret: "\rp."}'
lu.assertEquals(prf.format(template, profile), '{pass: "P", foo: "bar", secret: "P"}')
end
function test_format_replaces_name_placeholders()
local profile = {
n = "N",
}
local template = '{name: "\rn.", foo: "bar", nickname: "\rn."}'
lu.assertEquals(prf.format(template, profile), '{name: "N", foo: "bar", nickname: "N"}')
end
function test_format_replaces_email_placeholders()
local profile = {
e = "user@host",
}
local template = '{user: "\re.", foo: "bar", mail: "\re."}'
lu.assertEquals(prf.format(template, profile), '{user: "user@host", foo: "bar", mail: "user@host"}')
end
function test_format_replaces_base64_calls()
local profile = {
u = "👤",
p = "🔒",
}
local template = 'Authorization: Basic \rb64(\ru.:\rp.).'
lu.assertEquals(prf.format(template, profile), 'Authorization: Basic 8J+RpDrwn5SS')
end
function test_format_replaces_base64url_calls()
local profile = {
u = "👤",
p = "🔒",
}
local template = '?authorization=Basic+\ru64(\ru.:\rp.).'
lu.assertEquals(prf.format(template, profile), '?authorization=Basic+8J-RpDrwn5SS')
end
function test_email_returns_the_profile_s_email()
local profile = {
e = "E",
}
lu.assertEquals(prf.email(profile), "E")
end
function test_name_returns_the_profile_s_name()
local profile = {
n = "N",
}
lu.assertEquals(prf.name(profile), "N")
end
function test_user_returns_the_profile_s_user()
local profile = {
u = "U",
}
lu.assertEquals(prf.user(profile), "U")
end
function test_build_profile_returns_the_given_information()
lu.assertEquals(prf.build_profile("U", "P", "N", "E"), {u = "U", p = "P", n = "N", e = "E"})
end
os.exit(lu.LuaUnit.run())

View File

@ -51,13 +51,13 @@ function test_session_and_cookie_renewal_if_good_cookie()
ngx.req.reset()
ngx.reset_header()
ngx.reset_var()
local data = {u = "bob"}
local c, _ = crypt.get_jws_and_tslimit(data)
local profile = sites.class__profile:build_from_lists("bob", nil, nil, nil, {}, {})
local c, _ = crypt.get_jws_and_tslimit(profile)
ngx.var.cookie_SSSO_TOKEN = c
-- when
local s, h = sess.get_session()
-- then
lu.assertEquals(s, data)
lu.assertEquals(s, profile)
lu.assertEquals(h, 200)
lu.assertNil(ngx.header["Set-Cookie"].link)
lu.assertStrMatches(ngx.header["Set-Cookie"].v, "SSSO_TOKEN=[^%.]+%.[^%.]+%.[^%.]+; Path=/; Expires=1626550390; Secure")
@ -84,15 +84,15 @@ function test_basic_auth_takes_precedence_over_cookie()
ngx.req.reset()
ngx.reset_header()
ngx.reset_var()
local data = {u = "forget me"}
local c, _ = crypt.get_jws_and_tslimit(data)
local profile = sites.class__profile:build_from_lists("forget me", nil, nil, nil, {}, {})
local c, _ = crypt.get_jws_and_tslimit(profile)
ngx.var.cookie_SSSO_TOKEN = c
ngx.var.Authentication = "Basic " .. b64.encode_base64("bob:goodpassword")
-- when
local s, h = sess.get_session()
-- then
lu.assertEquals(h, 200)
lu.assertEquals(s.u, "bob")
lu.assertEquals(s:user(), "bob")
lu.assertNil(ngx.header["Set-Cookie"].link)
lu.assertStrMatches(ngx.header["Set-Cookie"].v, "SSSO_TOKEN=[^%.]+%.[^%.]+%.[^%.]+; Path=/; Expires=1626550390; Secure")
end
@ -102,15 +102,15 @@ function test_basic_auth_ignored_if_invalid()
ngx.req.reset()
ngx.reset_header()
ngx.reset_var()
local data = {u = "do not forget me"}
local c, _ = crypt.get_jws_and_tslimit(data)
local profile = sites.class__profile:build_from_lists("do not forget me", nil, nil, nil, {}, {})
local c, _ = crypt.get_jws_and_tslimit(profile)
ngx.var.cookie_SSSO_TOKEN = c
ngx.var.Authentication = "Basic !!!!"
-- when
local s, h = sess.get_session()
-- then
lu.assertEquals(h, 200)
lu.assertEquals(s.u, "do not forget me")
lu.assertEquals(s:user(), "do not forget me")
lu.assertNil(ngx.header["Set-Cookie"].link)
lu.assertStrMatches(ngx.header["Set-Cookie"].v, "SSSO_TOKEN=[^%.]+%.[^%.]+%.[^%.]+; Path=/; Expires=1626550390; Secure")
end

View File

@ -13,7 +13,7 @@ function test_anonymous_access_to_unknown_site_accepted()
ngx.req.reset()
ngx.reset_var()
ngx.var.request_uri = "/unknown"
local r = ng.get_request()
local r = ng.class__request:current()
-- when
local resp = sites.handle_request(r, nil)
-- then
@ -26,7 +26,7 @@ function test_anonymous_access_to_public_site_accepted()
ngx.req.reset()
ngx.reset_var()
ngx.var.request_uri = "/public/page"
local r = ng.get_request()
local r = ng.class__request:current()
-- when
local resp = sites.handle_request(r, nil)
-- then
@ -39,7 +39,7 @@ function test_anonymous_access_to_public_page_of_mixed_site_accepted()
ngx.req.reset()
ngx.reset_var()
ngx.var.request_uri = "/mixed/bob/wiki/foo.adoc"
local r = ng.get_request()
local r = ng.class__request:current()
-- when
local resp = sites.handle_request(r, nil)
-- then
@ -52,7 +52,7 @@ function test_anonymous_access_to_private_page_of_mixed_site_redirected_401()
ngx.req.reset()
ngx.reset_var()
ngx.var.request_uri = "/mixed/bob/wiki/_new"
local r = ng.get_request()
local r = ng.class__request:current()
-- when
local resp = sites.handle_request(r, nil)
-- then
@ -65,7 +65,7 @@ function test_anonymous_access_to_private_site_redirected_401()
ngx.req.reset()
ngx.reset_var()
ngx.var.request_uri = "/private/page"
local r = ng.get_request()
local r = ng.class__request:current()
-- when
local resp = sites.handle_request(r, nil)
-- then
@ -78,12 +78,8 @@ function test_authenticated_access_to_unknown_site_accepted()
ngx.req.reset()
ngx.reset_var()
ngx.var.request_uri = "/unknown"
local r = ng.get_request()
local profile = {
u = "U",
ok = {},
ko = {},
}
local r = ng.class__request:current()
local profile = sites.class__profile:build_from_lists("U", nil, nil, nil, {}, {})
-- when
local resp = sites.handle_request(r, profile)
-- then
@ -96,11 +92,9 @@ function test_authenticated_access_to_public_site_accepted()
ngx.req.reset()
ngx.reset_var()
ngx.var.request_uri = "/public/page"
local r = ng.get_request()
local profile = {
u = "U",
p = "P",
ok = {
local r = ng.class__request:current()
local profile = sites.class__profile:build_from_lists("U", "P", nil, nil,
{
{
r = {
"^/public",
@ -110,8 +104,9 @@ function test_authenticated_access_to_public_site_accepted()
{"C", "X-PROXY-PASS", "\rp."},
},
},
}
}
},
{}
)
-- when
local resp = sites.handle_request(r, profile)
-- then
@ -125,14 +120,13 @@ function test_authenticated_access_to_public_site_can_be_denied()
ngx.req.reset()
ngx.reset_var()
ngx.var.request_uri = "/public/page"
local r = ng.get_request()
local profile = {
u = "banned",
ok = {},
ko = {
local r = ng.class__request:current()
local profile = sites.class__profile:build_from_lists("banned", nil, nil, nil,
{},
{
"^/public",
}
}
)
-- when
local resp = sites.handle_request(r, profile)
-- then
@ -145,11 +139,9 @@ function test_authenticated_access_to_public_page_of_mixed_site_accepted()
ngx.req.reset()
ngx.reset_var()
ngx.var.request_uri = "/mixed/bob/wiki/foo.adoc"
local r = ng.get_request()
local profile = {
u = "U",
p = "P",
ok = {
local r = ng.class__request:current()
local profile = sites.class__profile:build_from_lists("U", "P", nil, nil,
{
{
r = {
"^/public",
@ -179,8 +171,9 @@ function test_authenticated_access_to_public_page_of_mixed_site_accepted()
{"C", "X-PROXY-PASSWORD", "\rp."},
},
},
}
}
},
{}
)
-- when
local resp = sites.handle_request(r, profile)
-- then
@ -194,11 +187,9 @@ function test_authenticated_access_to_private_page_of_mixed_site_accepted()
ngx.req.reset()
ngx.reset_var()
ngx.var.request_uri = "/mixed/bob/wiki/_new"
local r = ng.get_request()
local profile = {
u = "U",
p = "P",
ok = {
local r = ng.class__request:current()
local profile = sites.class__profile:build_from_lists("U", "P", nil, nil,
{
{
r = {
"^/public",
@ -228,8 +219,9 @@ function test_authenticated_access_to_private_page_of_mixed_site_accepted()
{"C", "X-PROXY-PASSWORD", "\rp."},
},
},
}
}
},
{}
)
-- when
local resp = sites.handle_request(r, profile)
-- then
@ -243,11 +235,9 @@ function test_authenticated_access_to_private_site_accepted_with_the_right_user(
ngx.req.reset()
ngx.reset_var()
ngx.var.request_uri = "/private/page"
local r = ng.get_request()
local profile = {
u = "jean",
p = "P",
ok = {
local r = ng.class__request:current()
local profile = sites.class__profile:build_from_lists("jean", "P", nil, nil,
{
{
r = {
"^/private",
@ -256,8 +246,9 @@ function test_authenticated_access_to_private_site_accepted_with_the_right_user(
{"H", "Authorization", "Basic \rb64(\ru.:\rp.)."},
},
},
}
}
},
{}
)
-- when
local resp = sites.handle_request(r, profile)
-- then
@ -270,15 +261,13 @@ function test_authenticated_access_to_private_site_redirected_403_with_the_wrong
ngx.req.reset()
ngx.reset_var()
ngx.var.request_uri = "/private/page"
local r = ng.get_request()
local profile = {
u = "U",
p = "P",
ok = {},
ko = {
local r = ng.class__request:current()
local profile = sites.class__profile:build_from_lists("U", "P", nil, nil,
{},
{
"^/private",
}
}
)
-- when
local resp = sites.handle_request(r, profile)
-- then