luci-base: expose luci.dispatcher.build_url() as url() in templates
[project/luci.git] / modules / luci-base / luasrc / dispatcher.lua
index 319eb46..5d9d1b0 100644 (file)
@@ -1,33 +1,9 @@
---[[
-LuCI - Dispatcher
+-- 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.
 
-Description:
-The request dispatcher and module dispatcher generators
-
-FileId:
-$Id$
-
-License:
-Copyright 2008 Steven Barth <steven@midlink.org>
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-
-]]--
-
---- LuCI web dispatcher.
 local fs = require "nixio.fs"
 local sys = require "luci.sys"
-local init = require "luci.init"
 local util = require "luci.util"
 local http = require "luci.http"
 local nixio = require "nixio", require "nixio.util"
@@ -47,9 +23,6 @@ local index = nil
 local fi
 
 
---- Build the URL relative to the server webroot from given virtual path.
--- @param ...  Virtual path
--- @return             Relative URL
 function build_url(...)
        local path = {...}
        local url = { http.getenv("SCRIPT_NAME") or "" }
@@ -73,9 +46,6 @@ function build_url(...)
        return table.concat(url, "")
 end
 
---- Check whether a dispatch node shall be visible
--- @param node Dispatch node
--- @return             Boolean indicating whether the node should be visible
 function node_visible(node)
    if node then
          return not (
@@ -88,9 +58,6 @@ function node_visible(node)
    return false
 end
 
---- Return a sorted table of visible childs within a given node
--- @param node Dispatch node
--- @return             Ordered table of child node names
 function node_childs(node)
        local rv = { }
        if node then
@@ -110,9 +77,6 @@ function node_childs(node)
 end
 
 
---- Send a 404 error code and render the "error404" template if available.
--- @param message      Custom error message (optional)
--- @return                     false
 function error404(message)
        http.status(404, "Not Found")
        message = message or "Not Found"
@@ -125,9 +89,6 @@ function error404(message)
        return false
 end
 
---- Send a 500 error code and render the "error500" template if available.
--- @param message      Custom error message (optional)#
--- @return                     false
 function error500(message)
        util.perror(message)
        if not context.template_header_sent then
@@ -152,16 +113,29 @@ function authenticator.htmlauth(validator, accs, default)
                return user
        end
 
-       require("luci.i18n")
-       require("luci.template")
-       context.path = {}
-       luci.template.render("sysauth", {duser=default, fuser=user})
+       if context.urltoken.stok then
+               context.urltoken.stok = nil
+
+               local cookie = 'sysauth=%s; expires=%s; path=%s/' %{
+                   http.getcookie('sysauth') or 'x',
+                       'Thu, 01 Jan 1970 01:00:00 GMT',
+                       build_url()
+               }
+
+               http.header("Set-Cookie", cookie)
+               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
+
        return false
 
 end
 
---- Dispatch an HTTP request.
--- @param request      LuCI HTTP Request object
 function httpdispatch(request, prefix)
        http.context.request = request
 
@@ -200,8 +174,6 @@ function httpdispatch(request, prefix)
        --context._disable_memtrace()
 end
 
---- Dispatches a LuCI virtual path.
--- @param request      Virtual path
 function dispatch(request)
        --context._disable_memtrace = require "luci.debug".trap_memtrace("l")
        local ctx = context
@@ -313,6 +285,8 @@ function dispatch(request)
                   resource    = luci.config.main.resourcebase;
                   ifattr      = function(...) return _ifattr(...) end;
                   attr        = function(...) return _ifattr(true, ...) end;
+                  token       = ctx.urltoken.stok;
+                  url         = build_url;
                }, {__index=function(table, key)
                        if key == "controller" then
                                return build_url()
@@ -364,15 +338,15 @@ function dispatch(request)
 
                if not util.contains(accs, user) then
                        if authen then
-                               ctx.urltoken.stok = nil
                                local user, sess = authen(sys.user.checkpasswd, accs, def)
+                               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 = {
@@ -382,14 +356,19 @@ 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())
+                                       if sess and token then
+                                               http.header("Set-Cookie", 'sysauth=%s; path=%s/' %{
+                                                  sess, build_url()
+                                               })
+
+                                               ctx.urltoken.stok = token
                                                ctx.authsession = sess
                                                ctx.authuser = user
+
+                                               http.redirect(build_url(unpack(ctx.requestpath)))
                                        end
                                end
                        else
@@ -402,11 +381,28 @@ function dispatch(request)
                end
        end
 
+       if c and type(c.target) == "table" and c.target.post == true then
+               if http.getenv("REQUEST_METHOD") ~= "POST" then
+                       http.status(405, "Method Not Allowed")
+                       http.header("Allow", "POST")
+                       return
+               end
+
+               if http.formvalue("token") ~= ctx.urltoken.stok then
+                       http.status(403, "Forbidden")
+                       luci.template.render("csrftoken")
+                       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
 
@@ -469,7 +465,6 @@ function dispatch(request)
        end
 end
 
---- Generate the dispatching index using the native file-cache based strategy.
 function createindex()
        local controllers = { }
        local base = "%s/controller/" % util.libpath()
@@ -533,7 +528,6 @@ function createindex()
        end
 end
 
---- Create the dispatching tree from the index.
 -- Build the index before if it does not exist yet.
 function createtree()
        if not index then
@@ -572,9 +566,6 @@ function createtree()
        return tree
 end
 
---- Register a tree modifier.
--- @param      func    Modifier function
--- @param      order   Modifier order value (optional)
 function modifier(func, order)
        context.modifiers[#context.modifiers+1] = {
                func = func,
@@ -584,12 +575,6 @@ function modifier(func, order)
        }
 end
 
---- Clone a node of the dispatching tree to another position.
--- @param      path    Virtual path destination
--- @param      clone   Virtual path source
--- @param      title   Destination node title (optional)
--- @param      order   Destination node order value (optional)
--- @return                     Dispatching tree node
 function assign(path, clone, title, order)
        local obj  = node(unpack(path))
        obj.nodes  = nil
@@ -603,12 +588,6 @@ function assign(path, clone, title, order)
        return obj
 end
 
---- Create a new dispatching node and define common parameters.
--- @param      path    Virtual path
--- @param      target  Target function to call when dispatched.
--- @param      title   Destination node title
--- @param      order   Destination node order value (optional)
--- @return                     Dispatching tree node
 function entry(path, target, title, order)
        local c = node(unpack(path))
 
@@ -620,17 +599,11 @@ function entry(path, target, title, order)
        return c
 end
 
---- Fetch or create a dispatching node without setting the target module or
 -- enabling the node.
--- @param      ...             Virtual path
--- @return                     Dispatching tree node
 function get(...)
        return _create_node({...})
 end
 
---- Fetch or create a new dispatching node.
--- @param      ...             Virtual path
--- @return                     Dispatching tree node
 function node(...)
        local c = _create_node({...})
 
@@ -690,13 +663,10 @@ function _firstchild()
    dispatch(path)
 end
 
---- Alias the first (lowest order) page automatically
 function firstchild()
    return { type = "firstchild", target = _firstchild }
 end
 
---- Create a redirect to another dispatching node.
--- @param      ...             Virtual path destination
 function alias(...)
        local req = {...}
        return function(...)
@@ -708,9 +678,6 @@ function alias(...)
        end
 end
 
---- Rewrite the first x path values of the request.
--- @param      n               Number of path values to replace
--- @param      ...             Virtual path to replace removed path values with
 function rewrite(n, ...)
        local req = {...}
        return function(...)
@@ -749,20 +716,25 @@ local function _call(self, ...)
        end
 end
 
---- Create a function-call dispatching target.
--- @param      name    Target function of local controller
--- @param      ...             Additional parameters passed to the function
 function call(name, ...)
        return {type = "call", argv = {...}, name = name, target = _call}
 end
 
+function post(name, ...)
+       return {
+               type = "call",
+               post = true,
+               argv = { ... },
+               name = name,
+               target = _call
+       }
+end
+
 
 local _template = function(self, ...)
        require "luci.template".render(self.view)
 end
 
---- Create a template render dispatching target.
--- @param      name    Template to be rendered
 function template(name)
        return {type = "template", view = name, target = _template}
 end
@@ -772,6 +744,15 @@ local function _cbi(self, ...)
        local cbi = require "luci.cbi"
        local tpl = require "luci.template"
        local http = require "luci.http"
+       local disp = require "luci.dispatcher"
+
+       if http.formvalue("cbi.submit") == "1" and
+          http.formvalue("token") ~= disp.context.urltoken.stok
+       then
+               http.status(403, "Forbidden")
+               luci.template.render("csrftoken")
+               return
+       end
 
        local config = self.config or {}
        local maps = cbi.load(self.model, ...)
@@ -868,8 +849,6 @@ local function _cbi(self, ...)
        end
 end
 
---- Create a CBI model dispatching target.
--- @param      model   CBI model to be rendered
 function cbi(model, config)
        return {type = "cbi", config = config, model = model, target = _cbi}
 end
@@ -882,9 +861,6 @@ local function _arcombine(self, ...)
        target:target(unpack(argv))
 end
 
---- Create a combined dispatching target for non argv and argv requests.
--- @param trg1 Overview Target
--- @param trg2 Detail Target
 function arcombine(trg1, trg2)
        return {type = "arcombine", env = getfenv(), target = _arcombine, targets = {trg1, trg2}}
 end
@@ -894,6 +870,15 @@ local function _form(self, ...)
        local cbi = require "luci.cbi"
        local tpl = require "luci.template"
        local http = require "luci.http"
+       local disp = require "luci.dispatcher"
+
+       if http.formvalue("cbi.submit") == "1" and
+          http.formvalue("token") ~= disp.context.urltoken.stok
+       then
+               http.status(403, "Forbidden")
+               luci.template.render("csrftoken")
+               return
+       end
 
        local maps = luci.cbi.load(self.model, ...)
        local state = nil
@@ -913,19 +898,12 @@ local function _form(self, ...)
        tpl.render("footer")
 end
 
---- Create a CBI form model dispatching target.
--- @param      model   CBI form model tpo be rendered
 function form(model)
        return {type = "cbi", model = model, target = _form}
 end
 
---- Access the luci.i18n translate() api.
--- @class  function
--- @name   translate
--- @param  text    Text to translate
 translate = i18n.translate
 
---- No-op function used to mark translation entries for menu labels.
 -- This function does not actually translate the given argument but
 -- is used by build/i18n-scan.pl to find translatable entries.
 function _(text)