luci-base: dispatcher.lua: add support for handling menu ACL annotations

Signed-off-by: Jo-Philipp Wich <jo@mein.io>
This commit is contained in:
Jo-Philipp Wich 2020-04-15 22:12:39 +02:00
parent 06af541c37
commit 125916f2f4

View file

@ -134,6 +134,35 @@ local function check_uci_depends(conf)
return true return true
end end
local function check_acl_depends(require_groups, groups)
if type(require_groups) == "table" and #require_groups > 0 then
local writable = false
for _, group in ipairs(require_groups) do
local read = false
local write = false
if type(groups) == "table" and type(groups[group]) == "table" then
for _, perm in ipairs(groups[group]) do
if perm == "read" then
read = true
elseif perm == "write" then
write = true
end
end
end
if not read and not write then
return nil
elseif write then
writable = true
end
end
return writable
end
return true
end
local function check_depends(spec) local function check_depends(spec)
if type(spec.depends) ~= "table" then if type(spec.depends) ~= "table" then
return true return true
@ -493,6 +522,7 @@ end
local function session_retrieve(sid, allowed_users) local function session_retrieve(sid, allowed_users)
local sdat = util.ubus("session", "get", { ubus_rpc_session = sid }) local sdat = util.ubus("session", "get", { ubus_rpc_session = sid })
local sacl = util.ubus("session", "access", { ubus_rpc_session = sid })
if type(sdat) == "table" and if type(sdat) == "table" and
type(sdat.values) == "table" and type(sdat.values) == "table" and
@ -501,42 +531,38 @@ local function session_retrieve(sid, allowed_users)
util.contains(allowed_users, sdat.values.username)) util.contains(allowed_users, sdat.values.username))
then then
uci:set_session_id(sid) uci:set_session_id(sid)
return sid, sdat.values return sid, sdat.values, type(sacl) == "table" and sacl or {}
end end
return nil, nil return nil, nil, nil
end end
local function session_setup(user, pass, allowed_users) local function session_setup(user, pass)
if util.contains(allowed_users, user) then local login = util.ubus("session", "login", {
local login = util.ubus("session", "login", { username = user,
username = user, password = pass,
password = pass, timeout = tonumber(luci.config.sauth.sessiontime)
timeout = tonumber(luci.config.sauth.sessiontime) })
local rp = context.requestpath
and table.concat(context.requestpath, "/") or ""
if type(login) == "table" and
type(login.ubus_rpc_session) == "string"
then
util.ubus("session", "set", {
ubus_rpc_session = login.ubus_rpc_session,
values = { token = sys.uniqueid(16) }
}) })
local rp = context.requestpath io.stderr:write("luci: accepted login on /%s for %s from %s\n"
and table.concat(context.requestpath, "/") or "" %{ rp, user or "?", http.getenv("REMOTE_ADDR") or "?" })
if type(login) == "table" and return session_retrieve(login.ubus_rpc_session)
type(login.ubus_rpc_session) == "string"
then
util.ubus("session", "set", {
ubus_rpc_session = login.ubus_rpc_session,
values = { token = sys.uniqueid(16) }
})
io.stderr:write("luci: accepted login on /%s for %s from %s\n"
%{ rp, user, http.getenv("REMOTE_ADDR") or "?" })
return session_retrieve(login.ubus_rpc_session)
end
io.stderr:write("luci: failed login on /%s for %s from %s\n"
%{ rp, user, http.getenv("REMOTE_ADDR") or "?" })
end end
return nil, nil io.stderr:write("luci: failed login on /%s for %s from %s\n"
%{ rp, user or "?", http.getenv("REMOTE_ADDR") or "?" })
end end
local function check_authentication(method) local function check_authentication(method)
@ -635,7 +661,28 @@ local function merge_trees(node_a, node_b)
return node_a return node_a
end end
function menu_json() local function apply_tree_acls(node, acl)
if type(node.children) == "table" then
for _, child in pairs(node.children) do
apply_tree_acls(child, acl)
end
end
local perm
if type(node.depends) == "table" then
perm = check_acl_depends(node.depends.acl, acl["access-group"])
else
perm = true
end
if perm == nil then
node.satisfied = false
elseif perm == false then
node.readonly = true
end
end
function menu_json(acl)
local tree = context.tree or createtree() local tree = context.tree or createtree()
local lua_tree = tree_to_json(tree, { local lua_tree = tree_to_json(tree, {
action = { action = {
@ -645,7 +692,13 @@ function menu_json()
}) })
local json_tree = createtree_json() local json_tree = createtree_json()
return merge_trees(lua_tree, json_tree) local menu_tree = merge_trees(lua_tree, json_tree)
if acl then
apply_tree_acls(menu_tree, acl)
end
return menu_tree
end end
local function init_template_engine(ctx) local function init_template_engine(ctx)
@ -738,6 +791,8 @@ function dispatch(request)
local requested_path_node = {} local requested_path_node = {}
local requested_path_args = {} local requested_path_args = {}
local required_path_acls = {}
for i, s in ipairs(request) do for i, s in ipairs(request) do
if type(page.children) ~= "table" or not page.children[s] then if type(page.children) ~= "table" or not page.children[s] then
page = nil page = nil
@ -755,6 +810,21 @@ function dispatch(request)
suid = page.setuser or suid suid = page.setuser or suid
sgid = page.setgroup or sgid sgid = page.setgroup or sgid
if type(page.depends) == "table" and type(page.depends.acl) == "table" then
for _, group in ipairs(page.depends.acl) do
local found = false
for _, item in ipairs(required_path_acls) do
if item == group then
found = true
break
end
end
if not found then
required_path_acls[#required_path_acls + 1] = group
end
end
end
requested_path_full[i] = s requested_path_full[i] = s
requested_path_node[i] = s requested_path_node[i] = s
@ -778,16 +848,16 @@ function dispatch(request)
ctx.requested = ctx.requested or page ctx.requested = ctx.requested or page
if type(auth) == "table" and type(auth.methods) == "table" and #auth.methods > 0 then if type(auth) == "table" and type(auth.methods) == "table" and #auth.methods > 0 then
local sid, sdat local sid, sdat, sacl
for _, method in ipairs(auth.methods) do for _, method in ipairs(auth.methods) do
sid, sdat = check_authentication(method) sid, sdat, sacl = check_authentication(method)
if sid and sdat then if sid and sdat and sacl then
break break
end end
end end
if not (sid and sdat) and auth.login then if not (sid and sdat and sacl) and auth.login then
local user = http.getenv("HTTP_AUTH_USER") local user = http.getenv("HTTP_AUTH_USER")
local pass = http.getenv("HTTP_AUTH_PASS") local pass = http.getenv("HTTP_AUTH_PASS")
@ -796,7 +866,9 @@ function dispatch(request)
pass = http.formvalue("luci_password") pass = http.formvalue("luci_password")
end end
sid, sdat = session_setup(user, pass, { "root" }) if user and pass then
sid, sdat, sacl = session_setup(user, pass)
end
if not sid then if not sid then
context.path = {} context.path = {}
@ -815,7 +887,7 @@ function dispatch(request)
return return
end end
if not sid or not sdat then if not sid or not sdat or not sacl then
http.status(403, "Forbidden") http.status(403, "Forbidden")
http.header("X-LuCI-Login-Required", "yes") http.header("X-LuCI-Login-Required", "yes")
return return
@ -824,6 +896,17 @@ function dispatch(request)
ctx.authsession = sid ctx.authsession = sid
ctx.authtoken = sdat.token ctx.authtoken = sdat.token
ctx.authuser = sdat.username ctx.authuser = sdat.username
ctx.authacl = sacl
end
if #required_path_acls > 0 then
local perm = check_acl_depends(required_path_acls, ctx.authacl and ctx.authacl["access-group"])
if perm == nil then
http.status(403, "Forbidden")
return
end
page.readonly = not perm
end end
local action = (page and type(page.action) == "table") and page.action or {} local action = (page and type(page.action) == "table") and page.action or {}