luci-base: luci.dispatcher: allow overriding sysauth template
[project/luci.git] / modules / luci-base / luasrc / dispatcher.lua
index a80dd70..0bd1945 100644 (file)
@@ -1,4 +1,5 @@
 -- Copyright 2008 Steven Barth <steven@midlink.org>
+-- Copyright 2008-2015 Jo-Philipp Wich <jow@openwrt.org>
 -- Licensed to the public under the Apache License 2.0.
 
 local fs = require "nixio.fs"
@@ -26,14 +27,6 @@ function build_url(...)
        local path = {...}
        local url = { http.getenv("SCRIPT_NAME") or "" }
 
-       local k, v
-       for k, v in pairs(context.urltoken) do
-               url[#url+1] = "/;"
-               url[#url+1] = http.urlencode(k)
-               url[#url+1] = "="
-               url[#url+1] = http.urlencode(v)
-       end
-
        local p
        for _, p in ipairs(path) do
                if p:match("^[a-zA-Z0-9_%-%.%%/,;]+$") then
@@ -42,6 +35,10 @@ function build_url(...)
                end
        end
 
+       if #path == 0 then
+               url[#url+1] = "/"
+       end
+
        return table.concat(url, "")
 end
 
@@ -104,7 +101,7 @@ function error500(message)
        return false
 end
 
-function authenticator.htmlauth(validator, accs, default)
+function authenticator.htmlauth(validator, accs, default, template)
        local user = http.formvalue("luci_username")
        local pass = http.formvalue("luci_password")
 
@@ -112,17 +109,11 @@ function authenticator.htmlauth(validator, accs, default)
                return user
        end
 
-       if context.urltoken.stok then
-               context.urltoken.stok = nil
-               http.header("Set-Cookie", "sysauth=; path="..build_url())
-               http.redirect(build_url())
-       else
-               require("luci.i18n")
-               require("luci.template")
-               context.path = {}
-               http.status(403, "Forbidden")
-               luci.template.render("sysauth", {duser=default, fuser=user})
-       end
+       require("luci.i18n")
+       require("luci.template")
+       context.path = {}
+       http.status(403, "Forbidden")
+       luci.template.render(template or "sysauth", {duser=default, fuser=user})
 
        return false
 
@@ -133,7 +124,6 @@ function httpdispatch(request, prefix)
 
        local r = {}
        context.request = r
-       context.urltoken = {}
 
        local pathinfo = http.urldecode(request:getenv("PATH_INFO") or "", true)
 
@@ -143,18 +133,8 @@ function httpdispatch(request, prefix)
                end
        end
 
-       local tokensok = true
        for node in pathinfo:gmatch("[^/]+") do
-               local tkey, tval
-               if tokensok then
-                       tkey, tval = node:match(";(%w+)=([a-fA-F0-9]*)")
-               end
-               if tkey then
-                       context.urltoken[tkey] = tval
-               else
-                       tokensok = false
-                       r[#r+1] = node
-               end
+               r[#r+1] = node
        end
 
        local stat, err = util.coxpcall(function()
@@ -166,6 +146,48 @@ function httpdispatch(request, prefix)
        --context._disable_memtrace()
 end
 
+local function require_post_security(target)
+       if type(target) == "table" then
+               if type(target.post) == "table" then
+                       local param_name, required_val, request_val
+
+                       for param_name, required_val in pairs(target.post) do
+                               request_val = http.formvalue(param_name)
+
+                               if (type(required_val) == "string" and
+                                   request_val ~= required_val) or
+                                  (required_val == true and
+                                   (request_val == nil or request_val == ""))
+                               then
+                                       return false
+                               end
+                       end
+
+                       return true
+               end
+
+               return (target.post == true)
+       end
+
+       return false
+end
+
+function test_post_security()
+       if http.getenv("REQUEST_METHOD") ~= "POST" then
+               http.status(405, "Method Not Allowed")
+               http.header("Allow", "POST")
+               return false
+       end
+
+       if http.formvalue("token") ~= context.authtoken then
+               http.status(403, "Forbidden")
+               luci.template.render("csrftoken")
+               return false
+       end
+
+       return true
+end
+
 function dispatch(request)
        --context._disable_memtrace = require "luci.debug".trap_memtrace("l")
        local ctx = context
@@ -175,6 +197,7 @@ function dispatch(request)
        assert(conf.main,
                "/etc/config/luci seems to be corrupt, unable to find section 'main'")
 
+       local i18n = require "luci.i18n"
        local lang = conf.main.lang or "auto"
        if lang == "auto" then
                local aclang = http.getenv("HTTP_ACCEPT_LANGUAGE") or ""
@@ -186,7 +209,10 @@ function dispatch(request)
                        end
                end
        end
-       require "luci.i18n".setlanguage(lang)
+       if lang == "auto" then
+               lang = i18n.default
+       end
+       i18n.setlanguage(lang)
 
        local c = ctx.tree
        local stat
@@ -199,7 +225,6 @@ function dispatch(request)
        ctx.args = args
        ctx.requestargs = ctx.requestargs or args
        local n
-       local token = ctx.urltoken
        local preq = {}
        local freq = {}
 
@@ -252,6 +277,13 @@ function dispatch(request)
                        if cond then
                                local env = getfenv(3)
                                local scope = (type(env.self) == "table") and env.self
+                               if type(val) == "table" then
+                                       if not next(val) then
+                                               return ''
+                                       else
+                                               val = util.serialize_json(val)
+                                       end
+                               end
                                return string.format(
                                        ' %s="%s"', tostring(key),
                                        util.pcdata(tostring( val
@@ -277,11 +309,14 @@ function dispatch(request)
                   resource    = luci.config.main.resourcebase;
                   ifattr      = function(...) return _ifattr(...) end;
                   attr        = function(...) return _ifattr(true, ...) end;
+                  url         = build_url;
                }, {__index=function(table, key)
                        if key == "controller" then
                                return build_url()
                        elseif key == "REQUEST_URI" then
                                return build_url(unpack(ctx.requestpath))
+                       elseif key == "token" then
+                               return ctx.authtoken
                        else
                                return rawget(table, key) or _G[key]
                        end
@@ -293,7 +328,7 @@ function dispatch(request)
                "Access Violation\nThe page at '" .. table.concat(request, "/") .. "/' " ..
                "has no parent node so the access to this location has been denied.\n" ..
                "This is a software bug, please report this message at " ..
-               "http://luci.subsignal.org/trac/newticket"
+               "https://github.com/openwrt/luci/issues"
        )
 
        if track.sysauth then
@@ -304,20 +339,17 @@ function dispatch(request)
                local def  = (type(track.sysauth) == "string") and track.sysauth
                local accs = def and {track.sysauth} or track.sysauth
                local sess = ctx.authsession
-               local verifytoken = false
                if not sess then
                        sess = http.getcookie("sysauth")
                        sess = sess and sess:match("^[a-f0-9]*$")
-                       verifytoken = true
                end
 
                local sdat = (util.ubus("session", "get", { ubus_rpc_session = sess }) or { }).values
-               local user
+               local user, token
 
                if sdat then
-                       if not verifytoken or ctx.urltoken.stok == sdat.token then
-                               user = sdat.user
-                       end
+                       user = sdat.user
+                       token = sdat.token
                else
                        local eu = http.getenv("HTTP_AUTH_USER")
                        local ep = http.getenv("HTTP_AUTH_PASS")
@@ -328,14 +360,15 @@ function dispatch(request)
 
                if not util.contains(accs, user) then
                        if authen then
-                               local user, sess = authen(sys.user.checkpasswd, accs, def)
+                               local user, sess = authen(sys.user.checkpasswd, accs, def, track.sysauth_template)
+                               local token
                                if not user or not util.contains(accs, user) then
                                        return
                                else
                                        if not sess then
-                                               local sdat = util.ubus("session", "create", { timeout = luci.config.sauth.sessiontime })
+                                               local sdat = util.ubus("session", "create", { timeout = tonumber(luci.config.sauth.sessiontime) })
                                                if sdat then
-                                                       local token = sys.uniqueid(16)
+                                                       token = sys.uniqueid(16)
                                                        util.ubus("session", "set", {
                                                                ubus_rpc_session = sdat.ubus_rpc_session,
                                                                values = {
@@ -345,15 +378,17 @@ function dispatch(request)
                                                                }
                                                        })
                                                        sess = sdat.ubus_rpc_session
-                                                       ctx.urltoken.stok = token
                                                end
                                        end
 
-                                       if sess then
-                                               http.header("Set-Cookie", "sysauth=" .. sess.."; path="..build_url())
-                                               http.redirect(build_url(unpack(ctx.requestpath)))
+                                       if sess and token then
+                                               http.header("Set-Cookie", 'sysauth=%s; path=%s' %{ sess, build_url() })
+
                                                ctx.authsession = sess
+                                               ctx.authtoken = token
                                                ctx.authuser = user
+
+                                               http.redirect(build_url(unpack(ctx.requestpath)))
                                        end
                                end
                        else
@@ -362,18 +397,22 @@ function dispatch(request)
                        end
                else
                        ctx.authsession = sess
+                       ctx.authtoken = token
                        ctx.authuser = user
                end
        end
 
+       if c and require_post_security(c.target) then
+               if not test_post_security(c) then
+                       return
+               end
+       end
+
        if track.setgroup then
                sys.process.setgroup(track.setgroup)
        end
 
        if track.setuser then
-               -- trigger ubus connection before dropping root privs
-               util.ubus()
-
                sys.process.setuser(track.setuser)
        end
 
@@ -691,6 +730,20 @@ function call(name, ...)
        return {type = "call", argv = {...}, name = name, target = _call}
 end
 
+function post_on(params, name, ...)
+       return {
+               type = "call",
+               post = params,
+               argv = { ... },
+               name = name,
+               target = _call
+       }
+end
+
+function post(...)
+       return post_on(true, ...)
+end
+
 
 local _template = function(self, ...)
        require "luci.template".render(self.view)
@@ -802,7 +855,13 @@ local function _cbi(self, ...)
 end
 
 function cbi(model, config)
-       return {type = "cbi", config = config, model = model, target = _cbi}
+       return {
+               type = "cbi",
+               post = { ["cbi.submit"] = "1" },
+               config = config,
+               model = model,
+               target = _cbi
+       }
 end
 
 
@@ -842,7 +901,12 @@ local function _form(self, ...)
 end
 
 function form(model)
-       return {type = "cbi", model = model, target = _form}
+       return {
+               type = "cbi",
+               post = { ["cbi.submit"] = "1" },
+               model = model,
+               target = _form
+       }
 end
 
 translate = i18n.translate