luci-mod-rpc: rework authentication and session handling
authorJo-Philipp Wich <jo@mein.io>
Fri, 6 Apr 2018 05:56:56 +0000 (07:56 +0200)
committerJo-Philipp Wich <jo@mein.io>
Fri, 6 Apr 2018 05:56:56 +0000 (07:56 +0200)
 - Use the ubus session.login procedure to authenticate credentials
 - Fix testing of allowed usernames
 - Support authentication via sysauth cookie

Fixes #1300, #1700, #1711

Signed-off-by: Jo-Philipp Wich <jo@mein.io>
modules/luci-mod-rpc/luasrc/controller/rpc.lua

index 759bb74..3326d57 100644 (file)
@@ -7,23 +7,45 @@ local pairs = pairs
 local print = print
 local pcall = pcall
 local table = table
 local print = print
 local pcall = pcall
 local table = table
+local type = type
+local tonumber = tonumber
 
 module "luci.controller.rpc"
 
 
 module "luci.controller.rpc"
 
-function index()
-       local function authenticator(validator, accs)
-               local auth = luci.http.formvalue("auth", true)
-               if auth then -- if authentication token was given
-                       local sdat = (luci.util.ubus("session", "get", { ubus_rpc_session = auth }) or { }).values
-                       if sdat then -- if given token is valid
-                               if sdat.user and luci.util.contains(accs, sdat.user) then
-                                       return sdat.user, auth
-                               end
-                       end
+
+local function session_retrieve(sid, allowed_users)
+       local util = require "luci.util"
+       local sdat = util.ubus("session", "get", {
+               ubus_rpc_session = sid
+       })
+
+       if type(sdat) == "table" and
+          type(sdat.values) == "table" and
+          type(sdat.values.token) == "string" and
+          type(sdat.values.secret) == "string" and
+          type(sdat.values.username) == "string" and
+          util.contains(allowed_users, sdat.values.username)
+       then
+               return sid, sdat.values
+       end
+
+       return nil
+end
+
+local function authenticator(validator, accs)
+       local auth = luci.http.formvalue("auth", true)
+               or luci.http.getcookie("sysauth")
+
+       if auth then -- if authentication token was given
+               local sid, sdat = session_retrieve(auth, accs)
+               if sdat then -- if given token is valid
+                       return sdat.username, sid
                end
                luci.http.status(403, "Forbidden")
        end
                end
                luci.http.status(403, "Forbidden")
        end
+end
 
 
+function index()
        local rpc = node("rpc")
        rpc.sysauth = "root"
        rpc.sysauth_authenticator = authenticator
        local rpc = node("rpc")
        rpc.sysauth = "root"
        rpc.sysauth_authenticator = authenticator
@@ -43,39 +65,48 @@ function rpc_auth()
        local ltn12   = require "luci.ltn12"
        local util    = require "luci.util"
 
        local ltn12   = require "luci.ltn12"
        local util    = require "luci.util"
 
-       local loginstat
-
        local server = {}
        server.challenge = function(user, pass)
        local server = {}
        server.challenge = function(user, pass)
-               local sid, token, secret
-
                local config = require "luci.config"
                local config = require "luci.config"
-
-               if sys.user.checkpasswd(user, pass) then
-                       local sdat = util.ubus("session", "create", { timeout = config.sauth.sessiontime })
+               local login = util.ubus("session", "login", {
+                       username = user,
+                       password = pass,
+                       timeout  = tonumber(config.sauth.sessiontime)
+               })
+
+               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),
+                                       secret = sys.uniqueid(16)
+                               }
+                       })
+
+                       local sid, sdat = session_retrieve(login.ubus_rpc_session, { user })
                        if sdat then
                        if sdat then
-                               sid = sdat.ubus_rpc_session
-                               token = sys.uniqueid(16)
-                               secret = sys.uniqueid(16)
-
-                               http.header("Set-Cookie", "sysauth="..sid.."; path=/")
-                               util.ubus("session", "set", {
-                                       ubus_rpc_session = sid,
-                                       values = {
-                                               user = user,
-                                               token = token,
-                                               secret = secret
-                                       }
-                               })
+                               return {
+                                       sid = sid,
+                                       token = sdat.token,
+                                       secret = sdat.secret
+                               }
                        end
                end
 
                        end
                end
 
-               return sid and {sid=sid, token=token, secret=secret}
+               return nil
        end
 
        server.login = function(...)
                local challenge = server.challenge(...)
        end
 
        server.login = function(...)
                local challenge = server.challenge(...)
-               return challenge and challenge.sid
+               if challenge then
+                       http.header("Set-Cookie", 'sysauth=%s; path=%s' %{
+                               challenge.sid,
+                               http.getenv("SCRIPT_NAME")
+                       })
+                       return challenge.sid
+               end
        end
 
        http.prepare_content("application/json")
        end
 
        http.prepare_content("application/json")