Rework authentication system

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>
This commit is contained in:
Jo-Philipp Wich 2012-08-07 19:11:56 +00:00
parent 69aa218335
commit a58370ab74
2 changed files with 57 additions and 42 deletions

View file

@ -26,36 +26,62 @@ luci.config.sauth = luci.config.sauth or {}
sessionpath = luci.config.sauth.sessionpath sessionpath = luci.config.sauth.sessionpath
sessiontime = tonumber(luci.config.sauth.sessiontime) or 15 * 60 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. --- Prepare session storage by creating the session directory.
function prepare() function prepare()
fs.mkdir(sessionpath, 700) fs.mkdir(sessionpath, 700)
if not sane() then if not sane() then
error("Security Exception: Session path is not sane!") error("Security Exception: Session path is not sane!")
end end
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. --- Read a session and return its content.
-- @param id Session identifier -- @param id Session identifier
-- @return Session data -- @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) function read(id)
if not id or #id == 0 then if not id or #id == 0 then
return return
@ -63,12 +89,19 @@ function read(id)
if not id:match("^%w+$") then if not id:match("^%w+$") then
error("Session ID is not sane!") error("Session ID is not sane!")
end end
clean()
if not sane(sessionpath .. "/" .. id) then if not sane(sessionpath .. "/" .. id) then
return return
end 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------") == (file and "rw-------" or "rwx------")
end 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 --- Kills a session
-- @param id Session identifier -- @param id Session identifier
function kill(id) function kill(id)

View file

@ -27,7 +27,7 @@ function index()
if auth then -- if authentication token was given if auth then -- if authentication token was given
local sdat = luci.sauth.read(auth) local sdat = luci.sauth.read(auth)
if sdat then -- if given token is valid 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 if user and luci.util.contains(accs, user) then
return user, auth return user, auth
end end
@ -68,7 +68,7 @@ function rpc_auth()
secret = sys.uniqueid(16) secret = sys.uniqueid(16)
http.header("Set-Cookie", "sysauth=" .. sid.."; path=/") http.header("Set-Cookie", "sysauth=" .. sid.."; path=/")
sauth.write(sid, util.get_bytecode({ sauth.write(sid, sauth.encode({
user=user, user=user,
token=token, token=token,
secret=secret secret=secret