Skip to content
Permalink
Browse files

web UI auth: store session data on server side

  • Loading branch information...
tomjelinek committed Feb 5, 2016
1 parent bc6ad90 commit e9b28833d54a47ec441f6dbad0db96e1fc662a5b
Showing with 178 additions and 4 deletions.
  1. +4 −1 pcsd/pcsd.conf
  2. +31 −3 pcsd/pcsd.rb
  3. +71 −0 pcsd/session.rb
  4. +1 −0 pcsd/test/test_all_suite.rb
  5. +71 −0 pcsd/test/test_session.rb
@@ -1,6 +1,9 @@
# pcsd configuration file
# Set PCSD_DEBUG to true for advanced pcsd debugging information
PCSD_DEBUG=false
RACK_ENV=production
# Set DISABLE_GUI to true to disable GUI frontend in pcsd
DISABLE_GUI=false
# Set web UI sesions lifetime
SESSION_LIFETIME=3600
# Do not change
RACK_ENV=production
@@ -19,6 +19,7 @@
require 'wizard.rb'
require 'cfgsync.rb'
require 'permissions.rb'
require 'session.rb'

Dir["wizards/*.rb"].each {|file| require file}

@@ -43,12 +44,19 @@ def generate_cookie_secret
File.open(COOKIE_FILE, 'w', 0700) {|f| f.write(secret)}
end

use Rack::Session::Cookie,
:expire_after => 60 * 60,
session_lifetime = ENV['SESSION_LIFETIME'].to_i()
session_lifetime = 60 * 60 unless session_lifetime > 0
use SessionPoolLifetime,
:expire_after => session_lifetime,
:secret => secret,
:secure => true, # only send over HTTPS
:httponly => true # don't provide to javascript

# session storage instance
# will be created by Rack later and fetched in "before" filter
$session_storage = nil
$session_storage_env = {}

#use Rack::SSL

if development?
@@ -66,6 +74,13 @@ def generate_cookie_secret

before do
@auth_user = nil

# get session storage instance from env
if not $session_storage and env[:__session_storage]
$session_storage = env[:__session_storage]
$session_storage_env = env
end

if request.path != '/login' and not request.path == "/logout" and not request.path == '/remote/auth'
protected!
end
@@ -116,6 +131,19 @@ def generate_cookie_secret
end
}

$thread_session_expired = Thread.new {
while true
sleep(60 * 5)
begin
if $session_storage
$session_storage.drop_expired($session_storage_env)
end
rescue => e
$logger.warn("Exception while removing expired sessions: #{e}")
end
end
}

helpers do
def protected!
gui_request = ( # these are URLs for web pages
@@ -334,7 +362,7 @@ def getParamList(params)
get('/login'){ erb :login, :layout => :main }

get '/logout' do
session.clear
session.destroy
erb :login, :layout => :main
end

@@ -0,0 +1,71 @@
require 'rack/session/pool'

class SessionPoolLifetime < Rack::Session::Pool

def initialize(app, options={})
super
@pool_timestamp = Hash.new()
end

def call(env)
# save session storage to env so we can get it later
env[:__session_storage] = self
super
end

def get_session(env, sid)
with_lock(env) do
now = Time.now()
# delete the session if expired
if @default_options[:expire_after] and sid and @pool_timestamp[sid] and
@pool_timestamp[sid] < (now - @default_options[:expire_after])
then
delete_session(sid)
end
# create new session if nonexistent
unless sid and session = @pool[sid]
sid, session = generate_sid, {}
@pool.store sid, session
end
# bump session's access time
@pool_timestamp[sid] = now
[sid, session]
end
end

def set_session(env, session_id, new_session, options)
with_lock(env) do
@pool.store session_id, new_session
# bump session's access time
@pool_timestamp[session_id] = Time.now()
session_id
end
end

def destroy_session(env, session_id, options)
with_lock(env) do
delete_session(session_id)
generate_sid unless options[:drop]
end
end

def drop_expired(env)
return unless lifetime = @default_options[:expire_after]
with_lock(env) {
threshold = Time.now() - lifetime
@pool_timestamp.select { |sid, timestamp|
timestamp < threshold
}.keys.each { |sid|
delete_session(sid)
}
}
end

private

def delete_session(sid)
@pool.delete(sid)
@pool_timestamp.delete(sid)
end
end

@@ -5,6 +5,7 @@
require 'test_cluster.rb'
require 'test_cluster_entity.rb'
require 'test_auth.rb'
require 'test_session.rb'
require 'test_permissions.rb'
require 'test_config.rb'
require 'test_cfgsync.rb'
@@ -0,0 +1,71 @@
require 'test/unit'

require 'pcsd_test_utils.rb'
require 'session.rb'

class TestSessionPool < Test::Unit::TestCase

def setup()
@env = {
'rack.multithread' => true,
}
end

def fixture_get_pool(lifetime)
pool = SessionPoolLifetime.new(nil, {:expire_after => lifetime,})
(1..3).each { |i| pool.set_session(@env, "sid#{i}", {'value' => i}, {}) }
return pool
end

def test_drop_expired_on_get()
lifetime = 2
pool = fixture_get_pool(lifetime)
# touch sessions each second
lifetime.times {
sleep(1)
assert_equal({'value' => 1}, pool.get_session(@env, 'sid1')[1])
assert_equal({'value' => 3}, pool.get_session(@env, 'sid3')[1])
}
# after @lifetime passes the unused session gets removed on access
sleep(1)
assert_equal({'value' => 1}, pool.get_session(@env, 'sid1')[1])
assert_equal({'value' => 3}, pool.get_session(@env, 'sid3')[1])
assert_equal({}, pool.get_session(@env, 'sid2')[1])
end

def test_drop_expired_explicit()
lifetime = 2
pool = fixture_get_pool(lifetime)
# touch sessions each second (otherwise they will be removed on access)
lifetime.times {
sleep(1)
pool.get_session(@env, 'sid2')
pool.set_session(@env, 'sid3', {'value' => 33}, {})
}
sleep(1)

pool.drop_expired(@env)
assert_equal(
{
'sid2' => {'value' => 2,},
'sid3' => {'value' => 33,},
},
pool.pool
)
end

def test_no_lifetime()
pool = fixture_get_pool(nil)
sleep(1)
assert_equal({'value' => 1}, pool.get_session(@env, 'sid1')[1])
assert_equal({'value' => 2}, pool.get_session(@env, 'sid2')[1])
assert_equal({'value' => 3}, pool.get_session(@env, 'sid3')[1])
sleep(1)
pool.drop_expired(@env)
assert_equal({'value' => 1}, pool.get_session(@env, 'sid1')[1])
assert_equal({'value' => 2}, pool.get_session(@env, 'sid2')[1])
assert_equal({'value' => 3}, pool.get_session(@env, 'sid3')[1])
end

end

0 comments on commit e9b2883

Please sign in to comment.
You can’t perform that action at this time.