HTTP basic auth

develop
Yves G 2021-09-02 22:58:01 +02:00
parent 5fde1663f5
commit 5f44ced065
7 changed files with 203 additions and 16 deletions

View File

@ -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>

View File

@ -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

View File

@ -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,
} }

View File

@ -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,

View File

@ -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)

View File

@ -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())

View File

@ -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())