HTTP basic auth
parent
5fde1663f5
commit
5f44ced065
|
@ -6,7 +6,7 @@
|
||||||
<h1>Single Sign-On for <code>example.org</code></h1>
|
<h1>Single Sign-On for <code>example.org</code></h1>
|
||||||
<!--MESSAGES-->
|
<!--MESSAGES-->
|
||||||
<form method="POST" action="login">
|
<form method="POST" action="login">
|
||||||
<input type="hidden" value="BACK_URL">
|
<input type="hidden" name="back" value="BACK_URL">
|
||||||
<label>User-name <input type="text" name="login"></label>
|
<label>User-name <input type="text" name="login"></label>
|
||||||
<label>Password <input type="password" name="password"></label>
|
<label>Password <input type="password" name="password"></label>
|
||||||
<button type="submit">Log in</button>
|
<button type="submit">Log in</button>
|
||||||
|
|
|
@ -10,9 +10,12 @@ end
|
||||||
|
|
||||||
if not b64["encode_base64"] then
|
if not b64["encode_base64"] then
|
||||||
b64.encode_base64 = function(plaintext)
|
b64.encode_base64 = function(plaintext)
|
||||||
local plain = b64.encode_base64url(plaintext)
|
local plain, err = b64.encode_base64url(plaintext)
|
||||||
|
if not plain then
|
||||||
|
return nil, err
|
||||||
|
end
|
||||||
plain = plain:gsub("_", "/")
|
plain = plain:gsub("_", "/")
|
||||||
return plain:gsub("%-", "+")
|
return plain:gsub("%-", "+"), nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -47,15 +47,23 @@ local function inject_data(html, req_data, warnings)
|
||||||
return html
|
return html
|
||||||
end
|
end
|
||||||
|
|
||||||
local function check_login(req_data)
|
local function check_credentials_and_get_profile(user, password)
|
||||||
req_data = nginx.with_post_parameters(req_data)
|
|
||||||
local user = req_data.query_params["login"] or ""
|
|
||||||
local password = req_data.query_params["password"] or ""
|
|
||||||
local user_data = auth.read_user(user, password)
|
local user_data = auth.read_user(user, password)
|
||||||
if user_data then
|
if user_data then
|
||||||
log.debug("Credentials accepted for user " .. user_data.name)
|
log.debug("Credentials accepted for user " .. user_data.name)
|
||||||
local profile = prof.build_profile(user, password, user_data.name, user_data.email)
|
local profile = prof.build_profile(user, password, user_data.name, user_data.email)
|
||||||
profile = sites.with_sites(profile, user)
|
return sites.with_sites(profile, user)
|
||||||
|
else
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function check_login(req_data)
|
||||||
|
req_data = nginx.with_post_parameters(req_data)
|
||||||
|
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)
|
||||||
|
if profile then
|
||||||
log.debug("Building JWS")
|
log.debug("Building JWS")
|
||||||
local jws, tslimit = crypto.get_jws_and_tslimit(profile)
|
local jws, tslimit = crypto.get_jws_and_tslimit(profile)
|
||||||
if not jws then
|
if not jws then
|
||||||
|
@ -100,5 +108,6 @@ end
|
||||||
|
|
||||||
return {
|
return {
|
||||||
answer_request = answer_request,
|
answer_request = answer_request,
|
||||||
|
check_credentials_and_get_profile = check_credentials_and_get_profile,
|
||||||
set_root = set_root,
|
set_root = set_root,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
local ngx = require("ngx")
|
local ngx = require("ngx")
|
||||||
|
local b64 = require("ssso_base64")
|
||||||
local util = require("ssso_util")
|
local util = require("ssso_util")
|
||||||
local conf = require("ssso_config")
|
local conf = require("ssso_config")
|
||||||
|
|
||||||
|
@ -43,6 +44,36 @@ local function with_post_parameters(req_data)
|
||||||
return req_data
|
return req_data
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function str_starts_with(str, begin)
|
||||||
|
local i, _ = str:find(util.str_to_pattern(begin))
|
||||||
|
return 1 == i
|
||||||
|
end
|
||||||
|
|
||||||
|
local function get_basic_auth()
|
||||||
|
local header = ngx.var.Authentication
|
||||||
|
if (not header) or (#header < 7) or (not str_starts_with(header, "Basic ")) then
|
||||||
|
return nil, nil
|
||||||
|
end
|
||||||
|
local text, _ = b64.decode_base64(header:sub(7))
|
||||||
|
if not text then
|
||||||
|
ngx.log(ngx.DEBUG, "Invalid Authentication header: " .. header)
|
||||||
|
return nil, nil
|
||||||
|
end
|
||||||
|
local colon
|
||||||
|
colon, _ = text:find(":")
|
||||||
|
if not colon then
|
||||||
|
return nil, nil
|
||||||
|
end
|
||||||
|
local login, password = "", ""
|
||||||
|
if colon > 1 then
|
||||||
|
login = text:sub(1, colon - 1)
|
||||||
|
end
|
||||||
|
if colon < #text then
|
||||||
|
password = text:sub(colon + 1, #text)
|
||||||
|
end
|
||||||
|
return login, password
|
||||||
|
end
|
||||||
|
|
||||||
local function get_jws_cookie()
|
local function get_jws_cookie()
|
||||||
return ngx.var.cookie_SSSO_TOKEN
|
return ngx.var.cookie_SSSO_TOKEN
|
||||||
end
|
end
|
||||||
|
@ -85,8 +116,7 @@ local function matches(req_data, lua_pattern)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function starts_with(req_data, prefix)
|
local function starts_with(req_data, prefix)
|
||||||
local i, _ = req_data.target:find(util.str_to_pattern(prefix))
|
return str_starts_with(req_data.target, prefix)
|
||||||
return 1 == i
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local function has_param(req_data, param, value)
|
local function has_param(req_data, param, value)
|
||||||
|
@ -138,6 +168,7 @@ return {
|
||||||
add_header = add_header,
|
add_header = add_header,
|
||||||
answer_not_found = answer_not_found,
|
answer_not_found = answer_not_found,
|
||||||
forward_request = forward_request,
|
forward_request = forward_request,
|
||||||
|
get_basic_auth = get_basic_auth,
|
||||||
get_jws_cookie = get_jws_cookie,
|
get_jws_cookie = get_jws_cookie,
|
||||||
get_request = get_request,
|
get_request = get_request,
|
||||||
get_seconds_since_epoch = get_seconds_since_epoch,
|
get_seconds_since_epoch = get_seconds_since_epoch,
|
||||||
|
|
|
@ -1,14 +1,25 @@
|
||||||
local crypto = require("ssso_crypto")
|
local crypto = require("ssso_crypto")
|
||||||
|
local login = require("ssso_login")
|
||||||
local nginx = require("ssso_nginx")
|
local nginx = require("ssso_nginx")
|
||||||
|
|
||||||
local function get_session()
|
local function get_session()
|
||||||
local cookie = nginx.get_jws_cookie()
|
local session, jws, tslimit
|
||||||
|
local user, password = nginx.get_basic_auth()
|
||||||
|
|
||||||
if not cookie or cookie == "" then
|
if user and password then
|
||||||
return nil, 401
|
session = login.check_credentials_and_get_profile(user, password)
|
||||||
|
if session then
|
||||||
|
jws, tslimit = crypto.get_jws_and_tslimit(session)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local session, jws, tslimit = crypto.get_data_and_new_jws(cookie)
|
if not session 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)
|
||||||
|
end
|
||||||
|
|
||||||
if session then
|
if session then
|
||||||
nginx.set_jws_cookie(jws, tslimit)
|
nginx.set_jws_cookie(jws, tslimit)
|
||||||
|
|
|
@ -310,4 +310,80 @@ function test_with_post_parameters_merges_post_parameters_to_request_data()
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function test_get_basic_auth_with_no_header_returns_nil()
|
||||||
|
-- given
|
||||||
|
ngx.reset_header()
|
||||||
|
-- when
|
||||||
|
local u, p = ng.get_basic_auth()
|
||||||
|
-- then
|
||||||
|
lu.assertNil(u)
|
||||||
|
lu.assertNil(p)
|
||||||
|
end
|
||||||
|
|
||||||
|
function test_get_basic_auth_with_non_basic_header_returns_nil()
|
||||||
|
-- given
|
||||||
|
ngx.reset_header()
|
||||||
|
ngx.var.Authentication = "Bearer uuid"
|
||||||
|
-- when
|
||||||
|
local u, p = ng.get_basic_auth()
|
||||||
|
-- then
|
||||||
|
lu.assertNil(u)
|
||||||
|
lu.assertNil(p)
|
||||||
|
end
|
||||||
|
|
||||||
|
function test_get_basic_auth_with_basic_header_but_no_base64_returns_nil()
|
||||||
|
-- given
|
||||||
|
ngx.reset_header()
|
||||||
|
ngx.var.Authentication = "Basic "
|
||||||
|
-- when
|
||||||
|
local u, p = ng.get_basic_auth()
|
||||||
|
-- then
|
||||||
|
lu.assertNil(u)
|
||||||
|
lu.assertNil(p)
|
||||||
|
end
|
||||||
|
|
||||||
|
function test_get_basic_auth_with_basic_header_but_invalid_base64_returns_nil()
|
||||||
|
-- given
|
||||||
|
ngx.reset_header()
|
||||||
|
ngx.var.Authentication = "Basic !!!!"
|
||||||
|
-- when
|
||||||
|
local u, p = ng.get_basic_auth()
|
||||||
|
-- then
|
||||||
|
lu.assertNil(u)
|
||||||
|
lu.assertNil(p)
|
||||||
|
end
|
||||||
|
|
||||||
|
function test_get_basic_auth_with_valid_basic_header_returns_auth()
|
||||||
|
-- given
|
||||||
|
ngx.reset_header()
|
||||||
|
ngx.var.Authentication = "Basic dTpw"
|
||||||
|
-- when
|
||||||
|
local u, p = ng.get_basic_auth()
|
||||||
|
-- then
|
||||||
|
lu.assertEquals(u, "u")
|
||||||
|
lu.assertEquals(p, "p")
|
||||||
|
end
|
||||||
|
|
||||||
|
function test_get_basic_auth_works_with_an_empty_login()
|
||||||
|
-- given
|
||||||
|
ngx.reset_header()
|
||||||
|
ngx.var.Authentication = "Basic OnA="
|
||||||
|
-- when
|
||||||
|
local u, p = ng.get_basic_auth()
|
||||||
|
-- then
|
||||||
|
lu.assertEquals(u, "")
|
||||||
|
lu.assertEquals(p, "p")
|
||||||
|
end
|
||||||
|
|
||||||
|
function test_get_basic_auth_works_with_an_empty_password()
|
||||||
|
-- given
|
||||||
|
ngx.reset_header()
|
||||||
|
ngx.var.Authentication = "Basic dTo="
|
||||||
|
-- when
|
||||||
|
local u, p = ng.get_basic_auth()
|
||||||
|
-- then
|
||||||
|
lu.assertEquals(u, "u")
|
||||||
|
lu.assertEquals(p, "")
|
||||||
|
end
|
||||||
|
|
||||||
os.exit(lu.LuaUnit.run())
|
os.exit(lu.LuaUnit.run())
|
||||||
|
|
|
@ -1,11 +1,15 @@
|
||||||
local lu = require("luaunit")
|
local lu = require("luaunit")
|
||||||
local sess = require("ssso_sessions")
|
local ngx = require("ngx")
|
||||||
|
local b64 = require("ssso_base64")
|
||||||
local conf = require("ssso_config")
|
local conf = require("ssso_config")
|
||||||
local crypt = require("ssso_crypto")
|
local crypt = require("ssso_crypto")
|
||||||
local ngx = require("ngx")
|
local login = require("ssso_login")
|
||||||
|
local sess = require("ssso_sessions")
|
||||||
|
local sites = require("ssso_sites")
|
||||||
|
|
||||||
local here = debug.getinfo(1).source:sub(2, -20)
|
local here = debug.getinfo(1).source:sub(2, -20)
|
||||||
conf.load_conf(here)
|
conf.load_conf(here)
|
||||||
|
sites.load_sites(here)
|
||||||
|
|
||||||
function test_no_session_and_hint_401_if_no_cookie()
|
function test_no_session_and_hint_401_if_no_cookie()
|
||||||
-- given
|
-- given
|
||||||
|
@ -45,6 +49,7 @@ end
|
||||||
function test_session_and_cookie_renewal_if_good_cookie()
|
function test_session_and_cookie_renewal_if_good_cookie()
|
||||||
-- given
|
-- given
|
||||||
ngx.req.reset()
|
ngx.req.reset()
|
||||||
|
ngx.reset_header()
|
||||||
ngx.reset_var()
|
ngx.reset_var()
|
||||||
local data = {u = "bob"}
|
local data = {u = "bob"}
|
||||||
local c, _ = crypt.get_jws_and_tslimit(data)
|
local c, _ = crypt.get_jws_and_tslimit(data)
|
||||||
|
@ -58,4 +63,56 @@ function test_session_and_cookie_renewal_if_good_cookie()
|
||||||
lu.assertStrMatches(ngx.header["Set-Cookie"].v, "SSSO_TOKEN=[^%.]+%.[^%.]+%.[^%.]+; Path=/; Expires=1626550390; Secure")
|
lu.assertStrMatches(ngx.header["Set-Cookie"].v, "SSSO_TOKEN=[^%.]+%.[^%.]+%.[^%.]+; Path=/; Expires=1626550390; Secure")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function test_good_basic_auth_credentials_generate_a_session_and_a_cookie()
|
||||||
|
-- given
|
||||||
|
ngx.req.reset()
|
||||||
|
ngx.reset_header()
|
||||||
|
ngx.reset_var()
|
||||||
|
ngx.var.Authentication = "Basic " .. b64.encode_base64("bob:goodpassword")
|
||||||
|
local expected = login.check_credentials_and_get_profile("bob", "goodpassword")
|
||||||
|
-- when
|
||||||
|
local s, h = sess.get_session()
|
||||||
|
-- then
|
||||||
|
lu.assertEquals(h, 200)
|
||||||
|
lu.assertEquals(s, expected)
|
||||||
|
lu.assertNil(ngx.header["Set-Cookie"].link)
|
||||||
|
lu.assertStrMatches(ngx.header["Set-Cookie"].v, "SSSO_TOKEN=[^%.]+%.[^%.]+%.[^%.]+; Path=/; Expires=1626550390; Secure")
|
||||||
|
end
|
||||||
|
|
||||||
|
function test_basic_auth_takes_precedence_over_cookie()
|
||||||
|
-- given
|
||||||
|
ngx.req.reset()
|
||||||
|
ngx.reset_header()
|
||||||
|
ngx.reset_var()
|
||||||
|
local data = {u = "forget me"}
|
||||||
|
local c, _ = crypt.get_jws_and_tslimit(data)
|
||||||
|
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.assertNil(ngx.header["Set-Cookie"].link)
|
||||||
|
lu.assertStrMatches(ngx.header["Set-Cookie"].v, "SSSO_TOKEN=[^%.]+%.[^%.]+%.[^%.]+; Path=/; Expires=1626550390; Secure")
|
||||||
|
end
|
||||||
|
|
||||||
|
function test_basic_auth_ignored_if_invalid()
|
||||||
|
-- given
|
||||||
|
ngx.req.reset()
|
||||||
|
ngx.reset_header()
|
||||||
|
ngx.reset_var()
|
||||||
|
local data = {u = "do not forget me"}
|
||||||
|
local c, _ = crypt.get_jws_and_tslimit(data)
|
||||||
|
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.assertNil(ngx.header["Set-Cookie"].link)
|
||||||
|
lu.assertStrMatches(ngx.header["Set-Cookie"].v, "SSSO_TOKEN=[^%.]+%.[^%.]+%.[^%.]+; Path=/; Expires=1626550390; Secure")
|
||||||
|
end
|
||||||
|
|
||||||
os.exit(lu.LuaUnit.run())
|
os.exit(lu.LuaUnit.run())
|
||||||
|
|
Loading…
Reference in New Issue