diff options
Diffstat (limited to 'tests/extensions')
-rw-r--r--tests/extensions/harness.lua17
-rw-r--r--tests/extensions/test-auth.lua87
2 files changed, 68 insertions, 36 deletions
diff --git a/tests/extensions/harness.lua b/tests/extensions/harness.lua
index fc83a73..3df4719 100644
--- a/tests/extensions/harness.lua
+++ b/tests/extensions/harness.lua
@@ -78,6 +78,23 @@ function harness.load(script)
dofile(script)
end
+-- Load a script with some of its own text replaced first, for checks that
+-- need the commented-out examples of its configuration switched on. Each
+-- replacement has to match exactly once, so a reworded example fails here
+-- rather than leaving the check to test the unedited configuration.
+function harness.load_with(script, replacements)
+ local file = assert(io.open(script, "rb"))
+ local source = file:read("*a")
+ file:close()
+ for _, pair in ipairs(replacements) do
+ local start = source:find(pair[1], 1, true)
+ assert(start and not source:find(pair[1], start + 1, true),
+ "expected exactly one " .. pair[1])
+ source = source:sub(1, start - 1) .. pair[2] .. source:sub(start + #pair[1])
+ end
+ assert((loadstring or load)(source, "@" .. script))()
+end
+
local unpack_args = unpack or table.unpack
-- One whole filter round trip, an open with the given arguments, a write per
diff --git a/tests/extensions/test-auth.lua b/tests/extensions/test-auth.lua
index 97638bd..e0d2fe5 100644
--- a/tests/extensions/test-auth.lua
+++ b/tests/extensions/test-auth.lua
@@ -5,11 +5,11 @@
-- every check about this script's own logic, the cookie layout, the expiry
-- and field rules, the redirect vetting and the action flows, rather than
-- about OpenSSL. The signing secret is pinned by replacing get_secret, and
--- the account and access lookups are populated through each variant's own
--- channel, the documented account_hash and repo_userset swap points for the
--- inline script and fixture files reached through a redirected io.open for
--- the file one. Both variants carry the same data so the flow checks read
--- identically for the two.
+-- the accounts and access lists are filled through each variant's own
+-- channel, its commented-out examples switched on for the inline script and
+-- fixture files reached through a redirected io.open for the file one. Both
+-- variants carry the same data so the flow checks read identically for the
+-- two.
local test_directory = arg[0]:match("^(.*)/") or "."
local h = dofile(test_directory .. "/harness.lua")
@@ -43,31 +43,31 @@ if variant == "file" then
h.redirect_file("/etc/cgit-auth/repos", "auth-repos")
end
-h.load(script)
+if variant == "inline" then
+ h.load_with(script, {
+ { "local protected_repos = {\n", "local protected_repos = {\n" ..
+ "\t[\"secret-repo\"] = { alice = true, Bob = true },\n" ..
+ "\t[\"empty-repo\"] = {},\n" ..
+ "\t[\"bare-repo\"] = {},\n" },
+ { "local users = {\n", "local users = {\n" ..
+ "\tAlice = \"" .. stored_hash .. "\",\n" },
+ })
+else
+ h.load(script)
+end
function get_secret()
return string.rep("0123456789abcdef", 4)
end
-if variant == "inline" then
- local accounts = { alice = stored_hash }
- local access = {
- ["secret-repo"] = { alice = true, bob = true },
- ["empty-repo"] = {},
- ["bare-repo"] = {},
- }
- function account_hash(user)
- if user == nil then
- return nil
- end
- return accounts[user:lower()]
- end
- function repo_userset(repo)
- if repo == nil then
- return nil
- end
- return access[repo]
- end
+-- crypt is watched through its stand-in module, so a check can see whether
+-- a login spent it and on which hash.
+local unistd = require("posix.unistd")
+local real_crypt = unistd.crypt
+local crypt_settings = {}
+unistd.crypt = function(trypass, setting)
+ crypt_settings[#crypt_settings + 1] = setting
+ return real_crypt(trypass, setting)
end
-- The lookups themselves, which are the part the two variants do not share.
@@ -75,6 +75,8 @@ end
local hash = account_hash("ALICE")
h.equals("an account is found whatever its case", hash, stored_hash)
h.equals("an unknown account is not", account_hash("nobody"), nil)
+local _, stand_in = account_hash("nobody")
+h.equals("it brings another account's hash to spend instead", stand_in, stored_hash)
h.equals("a nil user is not", account_hash(nil), nil)
local userset = repo_userset("secret-repo")
@@ -278,27 +280,40 @@ out, ret = run_action("authenticate-post", {
})
h.contains("an unknown user is told nothing different", out, "Set-Cookie: cgitauth=; ")
+-- A failed login for a name with no account spends crypt on a configured
+-- hash, the same work a real check costs, so timing tells nothing.
+crypt_settings = {}
+run_action("authenticate-post", {
+ method = "POST",
+ body = "username=nobody&password=x&redirect=" .. url_encode(way_back),
+})
+h.equals("a missing account spends crypt on a configured hash", crypt_settings[1], stored_hash)
+
+if variant == "file" then
+ h.redirect_file("/etc/cgit-auth/users", "auth-users-missing")
+ crypt_settings = {}
+ run_action("authenticate-post", {
+ method = "POST",
+ body = "username=nobody&password=x&redirect=" .. url_encode(way_back),
+ })
+ h.equals("with no accounts at all nothing is spent", #crypt_settings, 0)
+ h.redirect_file("/etc/cgit-auth/users", "auth-users")
+end
+
-- crypt's work grows with the password, so one past the cap is turned down
-- before crypt sees it.
-local unistd = require("posix.unistd")
-local real_crypt = unistd.crypt
-local crypt_calls = 0
-unistd.crypt = function(...)
- crypt_calls = crypt_calls + 1
- return real_crypt(...)
-end
+crypt_settings = {}
out, ret = run_action("authenticate-post", {
method = "POST",
body = "username=Alice&password=" .. string.rep("a", 257) .. "&redirect=" .. url_encode(way_back),
})
h.contains("an overlong password is turned down", out, "Set-Cookie: cgitauth=; ")
-h.equals("without spending crypt on it", crypt_calls, 0)
-out, ret = run_action("authenticate-post", {
+h.equals("without spending crypt on it", #crypt_settings, 0)
+run_action("authenticate-post", {
method = "POST",
body = "username=Alice&password=" .. string.rep("a", 256) .. "&redirect=" .. url_encode(way_back),
})
-h.equals("a password at the cap is still checked", crypt_calls, 1)
-unistd.crypt = real_crypt
+h.equals("a password at the cap is still checked", #crypt_settings, 1)
out, ret = run_action("authenticate-post", {
method = "POST",