Rework authentication system
authorJo-Philipp Wich <jow@openwrt.org>
Tue, 7 Aug 2012 19:11:56 +0000 (19:11 +0000)
committerJo-Philipp Wich <jow@openwrt.org>
Tue, 7 Aug 2012 19:11:56 +0000 (19:11 +0000)
The validity of authentication tokens was determined by the
mtime of respective authentication tokens on filesystem
stored in $sessionpath.
Talking about hardware without RTC or without a prior
connection to a time server, date/time usually around 1970 -
so is the mtime of the authentication token file in
$sessionpath.

When now configuring an internet connection via LuCI, the
system might fetch the current date/time (e.g. via ntp)
which invalidates the token, returns "403 Forbidden" and
kicks the user out of the interface.

This patch changes the authentication system to use time values
based on the uptime of the machine - rather than values based upon
gettimeofday() and {a|m}time values - and save them inside the token.
That way can always determine the difference between login
(last interaction respectively) and the current time, in-
dependant of the system clock jumping backwards/forwards.

Warning: This patch removes the clean() function and respective calls.
This means, invalid tokens will NOT be determined and removed from
filesystem automatically anymore.
Before, every HTTP-call caused a scan for invalid tokens,
which is quite expensive. Instead consider using a cron job
deleting all stalled files periodically.

Contributed by T-Labs, Deutsche Telekom Innovation Laboratories

Signed-off-by: Mirko Vogt <mirko@openwrt.org>
libs/web/luasrc/sauth.lua
modules/rpc/luasrc/controller/rpc.lua

index 41681ab..ef9fa1e 100644 (file)
@@ -26,36 +26,62 @@ luci.config.sauth = luci.config.sauth or {}
 sessionpath = luci.config.sauth.sessionpath
 sessiontime = tonumber(luci.config.sauth.sessiontime) or 15 * 60
 
 sessionpath = luci.config.sauth.sessionpath
 sessiontime = tonumber(luci.config.sauth.sessiontime) or 15 * 60
 
---- Manually clean up expired sessions.
-function clean()
-       local now   = os.time()
-       local files = fs.dir(sessionpath)
-       
-       if not files then
-               return nil
-       end
-       
-       for file in files do
-               local fname = sessionpath .. "/" .. file
-               local stat = fs.stat(fname)
-               if stat and stat.type == "reg" and stat.mtime + sessiontime < now then
-                       fs.unlink(fname)
-               end 
-       end
-end
-
 --- Prepare session storage by creating the session directory.
 function prepare()
        fs.mkdir(sessionpath, 700)
 --- Prepare session storage by creating the session directory.
 function prepare()
        fs.mkdir(sessionpath, 700)
-        
        if not sane() then
                error("Security Exception: Session path is not sane!")
        end
 end
 
        if not sane() then
                error("Security Exception: Session path is not sane!")
        end
 end
 
+function encode(t)
+       return luci.util.get_bytecode({
+               user=t.user,
+               token=t.token,
+               secret=t.secret,
+               atime=luci.sys.uptime()
+       })
+end
+
+function decode(blob)
+       local t = loadstring(blob)()
+       return {
+               user = t.user,
+               token = t.token,
+               secret = t.secret,
+               atime = t.atime
+       }
+end
+
 --- Read a session and return its content.
 -- @param id   Session identifier
 -- @return             Session data
 --- Read a session and return its content.
 -- @param id   Session identifier
 -- @return             Session data
+local function _read(id)
+       local blob = fs.readfile(sessionpath .. "/" .. id)
+       return blob
+end
+
+--- Write session data to a session file.
+-- @param id   Session identifier
+-- @param data Session data
+local function _write(id, data)
+       local f = nixio.open(sessionpath .. "/" .. id, "w", 600)
+       f:writeall(data)
+       f:close()
+end
+
+function write(id, data)
+       if not sane() then
+               prepare()
+       end
+
+       if not id or #id == 0 or not id:match("^%w+$") then
+               error("Session ID is not sane!")
+       end
+
+       _write(id, data)
+end
+
 function read(id)
        if not id or #id == 0 then
                return
 function read(id)
        if not id or #id == 0 then
                return
@@ -63,12 +89,19 @@ function read(id)
        if not id:match("^%w+$") then
                error("Session ID is not sane!")
        end
        if not id:match("^%w+$") then
                error("Session ID is not sane!")
        end
-       clean()
        if not sane(sessionpath .. "/" .. id) then
                return
        end
        if not sane(sessionpath .. "/" .. id) then
                return
        end
-       fs.utimes(sessionpath .. "/" .. id)
-       return fs.readfile(sessionpath .. "/" .. id)
+
+       local blob = _read(id)
+       if decode(blob).atime + sessiontime < luci.sys.uptime()then
+               fs.unlink(sessionpath .. "/" .. id)
+               return
+       end
+       -- refresh atime in session
+       refreshed = encode(decode(blob))
+       write(id, refreshed)
+       return blob
 end
 
 
 end
 
 
@@ -81,24 +114,6 @@ function sane(file)
                        == (file and "rw-------" or "rwx------")
 end
 
                        == (file and "rw-------" or "rwx------")
 end
 
-
---- Write session data to a session file.
--- @param id   Session identifier
--- @param data Session data
-function write(id, data)
-       if not sane() then
-               prepare()
-       end
-       if not id:match("^%w+$") then
-               error("Session ID is not sane!")
-       end
-       
-       local f = nixio.open(sessionpath .. "/" .. id, "w", 600)
-       f:writeall(data)
-       f:close()
-end
-
-
 --- Kills a session
 -- @param id   Session identifier
 function kill(id)
 --- Kills a session
 -- @param id   Session identifier
 function kill(id)
index 6b09116..b989b59 100644 (file)
@@ -27,7 +27,7 @@ function index()
                if auth then -- if authentication token was given
                        local sdat = luci.sauth.read(auth)
                        if sdat then -- if given token is valid
                if auth then -- if authentication token was given
                        local sdat = luci.sauth.read(auth)
                        if sdat then -- if given token is valid
-                               user = loadstring(sdat)().user
+                               user = luci.sauth.decode(sdat).user
                                if user and luci.util.contains(accs, user) then
                                        return user, auth
                                end
                                if user and luci.util.contains(accs, user) then
                                        return user, auth
                                end
@@ -68,7 +68,7 @@ function rpc_auth()
                        secret = sys.uniqueid(16)
 
                        http.header("Set-Cookie", "sysauth=" .. sid.."; path=/")
                        secret = sys.uniqueid(16)
 
                        http.header("Set-Cookie", "sysauth=" .. sid.."; path=/")
-                       sauth.write(sid, util.get_bytecode({
+                       sauth.write(sid, sauth.encode({
                                user=user,
                                token=token,
                                secret=secret
                                user=user,
                                token=token,
                                secret=secret