# = Session # #-- # code: # George Moschovitis # # (c) 2002-2003 Navel, all rights reserved. # $Id: session.rb 84 2004-10-19 13:57:01Z gmosx $ #++ require "md5" require "n/utils/hash" require "n/app/user" module N; module App # = SessionManager # # This object manages Session Objects. Several utility methods # are also provided. # # === WARNING: # # This object is typically called in a distributed configuration. # Avoid writting methods that accept or return big objects! # # SOS: This object lives in the Cluster! # class SessionManager < N::SafeHash # the collection of online users. attr_reader :online def initialize super @online = N::SafeHash.new end # Return the number of anonymous sessions. # def anonymous_count return self.size - @online.size end # Login a named user. # The caller may pass user.to_html instead of the user name. # this method is distributed keep a lightweight # signature. By using @online.keys i get access to the # oids and in this way to the full objects. # def login(user_oid, user_name) @online[user_oid] = user_name end # Logout a named user. # def logout(user_oid) @online.delete(user_oid) end # Returns the session for the given user. # def session_for_user(user) return values().find { |s| user.oid == s.user.oid } end # Returns the session for the given user. # def session_for_name(name) return values().find { |s| name == s.user.name } end # garbage collect stale sessions # # === TODO: # # - add unit testing. # def self.garbage_collect! # gmosx: the only way to get it to ruby with # druby, aaarghhh!! Rethink this though! perhaps some # fixes i made allow for recoding this. for key in $sessions.keys() begin session = $sessions[key] if session.stale? $log.debug "Session finalized: logging out idle user '#{session.user}'" if $DBG session.logout() $sessions.delete(key) end rescue Exception, StandardError => e $log.error "Session gc errror #$!" end end end end # = Session # # This object encapsulates a WebApplication session. # # === Design: # # The session should be persistable to survive server shutdowns. # # === Usage # # State is a neccessary evil but try to avoid using session # variables as much as possible. Session state is typically # distributed to many servers so avoid storing complete objects # in session variables, only store oids and integer/strings. # class Session < Hash COOKIE_NAME = "nsid" attr_reader :session_id # keep the user to allow for session lookup based on user. # also do NOT keep the full user as an object in the session hash # to optimize druby usage! # attr_accessor :user # the last touch time attr_accessor :touch_time # # def initialize(sid, request = nil) super() @session_id = sid @touch_time = Time.now @user = N::AnonymousUser.instance() end # Login a user # # Returns false for banned users. # def login(request, user) return false if user.banned? @user = user @user.login(request) $sessions.login(@user.oid, @user.name) $log.info "User '#{user}' logged in!" return true end # Logout a user # def logout() self.clear() @user.logout() $sessions.logout(@user.oid) $log.info "User '#{user}' logged out!" @user = N::AnonymousUser.instance end # # def touch @touch_time = Time.now end # # def stale? timeout = @user.anonymous? ? $srv_anon_session_timeout : $srv_session_timeout return (Time.now - @touch_time > timeout) end # call this method to synchronize the session with other servers # in the cluster. # def synchronize! # gmosx: TODO, add a check if the session is allready # synchronized. $sessions[@session_id] = self end # Refresh session entities. # # Call this when you manually change entities stored in the # session. A typical scenario is when you change an online user. # # === Warning: the current version only refreshes the "USER" entity. # def refresh! @user = $db.get(@user.oid, @user.class) if @user end # Calculates a unique id. # # The session id must be unique, a monotonically increasing function # like time is appropriate. Random may produce equal ids? add a prefix # (SALT) to stop hackers from creating session_ids. # # TODO: # make the prefix configurable # def self.calculate_id time = Time.now # FIXME: make this more random? id = Digest::MD5.md5("SALT#{time.to_i} #{time.tv_usec}").to_s return id end # Initialize sessions garbage collection. # # === Warning: this is not used yet! # def self.initialize_gc(session_manager, interval) # gmosx, FIXME: store this in a variable. Thread.new { loop do sleep(interval) garbage_collect(session_manager.sessions) end } end end end; end # module