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:
parent
06af541c37
commit
125916f2f4
1 changed files with 118 additions and 35 deletions
|
@ -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 {}
|
||||||
|
|
Loading…
Reference in a new issue