mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-10-27 06:02:51 +08:00
178 lines
6.1 KiB
Lua
178 lines
6.1 KiB
Lua
local source_root = assert(os.getenv("LINKEASE_AUTH_SOURCE_ROOT"), "LINKEASE_AUTH_SOURCE_ROOT is required")
|
|
package.path = source_root .. "/?.lua;" .. package.path
|
|
|
|
local function equal(actual, expected, message)
|
|
if actual ~= expected then
|
|
error((message or "values differ") .. ": got " .. tostring(actual) .. ", want " .. tostring(expected), 2)
|
|
end
|
|
end
|
|
|
|
local function has_header(headers, pattern)
|
|
for _, header in ipairs(headers) do
|
|
if header[1] == "Set-Cookie" and header[2]:match(pattern) then return true end
|
|
end
|
|
return false
|
|
end
|
|
|
|
local function scenario(options)
|
|
options = options or {}
|
|
local headers = {}
|
|
local result = {}
|
|
local cookies = options.cookies or {}
|
|
local environment = options.environment or { HTTP_HOST = "192.168.30.7:10000" }
|
|
local forms = options.forms or {}
|
|
|
|
package.loaded["luci.model.linkease.auth"] = nil
|
|
local auth = require "luci.model.linkease.auth"
|
|
local bridge = auth.new({
|
|
http = {
|
|
getcookie = function(name) return cookies[name] end,
|
|
getenv = function(name) return environment[name] end,
|
|
formvalue = function(name) return forms[name] end,
|
|
header = function(name, value) headers[#headers + 1] = { name, value } end,
|
|
redirect = function(value) result.redirect = value end,
|
|
status = function(code, message) result.status = code; result.message = message end
|
|
},
|
|
ubus = function(object, method, request)
|
|
if object == "session" and method == "get" and request.ubus_rpc_session == options.valid_sid then
|
|
return { values = { username = "root" } }
|
|
end
|
|
return nil
|
|
end,
|
|
lan_ip = function() return "192.168.30.7" end,
|
|
build_url = function(...)
|
|
local arguments = {...}
|
|
local url = "/cgi-bin/luci/admin/services/linkease_auth/auth_finish"
|
|
if arguments[5] then url = url .. "/" .. arguments[5] end
|
|
return url
|
|
end
|
|
})
|
|
return bridge, result, headers
|
|
end
|
|
|
|
local bridge, result, headers = scenario({
|
|
cookies = { sysauth = "valid-session" },
|
|
valid_sid = "valid-session",
|
|
forms = { ["return"] = "/apps/dockermanager/" }
|
|
})
|
|
bridge:auth()
|
|
equal(result.redirect, "/apps/dockermanager/")
|
|
equal(has_header(headers, "linkease_openwrt_sid=valid%-session"), true)
|
|
equal(has_header(headers, "linkease_openwrt_sid=.*Path=/apps"), true)
|
|
|
|
bridge, result, headers = scenario({ forms = { ["return"] = "/apps/kaiplus/" } })
|
|
bridge:auth()
|
|
local state = result.redirect:match("/auth_finish/([A-Za-z0-9_-]+)$")
|
|
equal(type(state), "string", "auth begin must encode return intent into the protected path")
|
|
|
|
bridge, result = scenario({
|
|
cookies = { sysauth = "valid-session" },
|
|
valid_sid = "valid-session"
|
|
})
|
|
bridge:auth_finish(state)
|
|
equal(result.redirect, "/apps/kaiplus/")
|
|
|
|
local first_bridge, first_result = scenario({
|
|
forms = { ["return"] = "/apps/dockermanager/containers/abc?tab=logs&follow=1" }
|
|
})
|
|
first_bridge:auth()
|
|
local first_state = first_result.redirect:match("/auth_finish/([A-Za-z0-9_-]+)$")
|
|
equal(type(first_state), "string", "deep link state must be encoded")
|
|
|
|
local second_bridge, second_result = scenario({
|
|
forms = { ["return"] = "/apps/kaiplus/?conversation=second" }
|
|
})
|
|
second_bridge:auth()
|
|
local second_state = second_result.redirect:match("/auth_finish/([A-Za-z0-9_-]+)$")
|
|
equal(type(second_state), "string", "second tab state must be encoded")
|
|
|
|
bridge, result = scenario({
|
|
cookies = { sysauth = "valid-session", linkease_openwrt_pending_return = "%2Fapps%2Fwrong%2F" },
|
|
valid_sid = "valid-session"
|
|
})
|
|
bridge:auth_finish(first_state)
|
|
equal(result.redirect, "/apps/dockermanager/containers/abc?tab=logs&follow=1", "first tab must use its own path state")
|
|
|
|
bridge, result = scenario({
|
|
cookies = { sysauth = "valid-session", linkease_openwrt_pending_return = "%2Fapps%2Fwrong%2F" },
|
|
valid_sid = "valid-session"
|
|
})
|
|
bridge:auth_finish(second_state)
|
|
equal(result.redirect, "/apps/kaiplus/?conversation=second", "second tab must use its own path state")
|
|
|
|
bridge, result = scenario({
|
|
cookies = { sysauth = "valid-session" },
|
|
valid_sid = "valid-session"
|
|
})
|
|
bridge:auth_finish("not_base64url_@@")
|
|
equal(result.redirect, "/apps/", "malformed state must fail closed")
|
|
|
|
bridge, result = scenario({
|
|
cookies = { sysauth = "valid-session" },
|
|
valid_sid = "valid-session"
|
|
})
|
|
bridge:auth_finish(string.rep("A", 4097))
|
|
equal(result.redirect, "/apps/", "oversized state must fail closed")
|
|
|
|
bridge, result = scenario({
|
|
cookies = { sysauth = "valid-session" },
|
|
valid_sid = "valid-session",
|
|
forms = { ["return"] = "http://192.168.30.7:8192/apps/dockermanager/" }
|
|
})
|
|
bridge:auth()
|
|
equal(result.redirect, "http://192.168.30.7:8192/apps/dockermanager/")
|
|
|
|
bridge, result = scenario({
|
|
cookies = { sysauth = "valid-session" },
|
|
valid_sid = "valid-session",
|
|
forms = { ["return"] = "http://192.168.30.7:3200/" }
|
|
})
|
|
bridge:auth()
|
|
equal(result.redirect, "http://192.168.30.7:3200/", "same-device external app port must be allowed")
|
|
|
|
bridge, result = scenario({
|
|
cookies = { sysauth = "valid-session" },
|
|
valid_sid = "valid-session",
|
|
forms = { ["return"] = "http://192.168.30.7:10000/cgi-bin/luci/" }
|
|
})
|
|
bridge:auth()
|
|
equal(result.redirect, "/apps/", "same-authority non-app path must remain rejected")
|
|
|
|
bridge, result = scenario({
|
|
cookies = { sysauth = "valid-session" },
|
|
valid_sid = "valid-session",
|
|
forms = { ["return"] = "http://evil.example/apps/dockermanager/" }
|
|
})
|
|
bridge:auth()
|
|
equal(result.redirect, "/apps/")
|
|
|
|
bridge, result = scenario({
|
|
cookies = { sysauth = "valid-session" },
|
|
valid_sid = "valid-session",
|
|
forms = { ["return"] = "/apps/dockermanager/%0d%0aLocation:%20http://evil.example/" }
|
|
})
|
|
bridge:auth()
|
|
equal(result.redirect, "/apps/dockermanager/%0d%0aLocation:%20http://evil.example/")
|
|
|
|
bridge, result = scenario({
|
|
cookies = { sysauth = "valid-session" },
|
|
valid_sid = "valid-session",
|
|
forms = { ["return"] = "/apps/dockermanager/\r\nLocation: http://evil.example/" }
|
|
})
|
|
bridge:auth()
|
|
equal(result.redirect, "/apps/", "raw control characters must be rejected")
|
|
|
|
bridge, result = scenario({
|
|
cookies = { sysauth = "valid-session" },
|
|
valid_sid = "valid-session",
|
|
forms = { ["return"] = "http://192.168.30.7:8192@evil.example/apps/dockermanager/" }
|
|
})
|
|
bridge:auth()
|
|
equal(result.redirect, "/apps/")
|
|
|
|
bridge, result = scenario()
|
|
bridge:auth_finish()
|
|
equal(result.status, 403)
|
|
|
|
print("linkease auth bridge tests passed")
|