Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions lib/utopia/session/lazy_hash.rb
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,12 @@ def changed?
@changed
end

# The current time for session expiry and persistence.
# @returns [Time] The current time in UTC.
def now
Time.now.utc
end

# Persist the session values if they have changed or require updating.
# @parameter timeout [Numeric | Nil] The maximum age before an update is required.
# @yields {|values, updated_at| ...} The loaded values and their update time.
Expand All @@ -70,7 +76,7 @@ def persist(timeout = nil)
return unless needs_update?(timeout)

values = load!
updated_at = values[:updated_at] = Time.now.utc
updated_at = values[:updated_at] = now

result = yield(values, updated_at)
@changed = false
Expand All @@ -94,7 +100,7 @@ def needs_update?(timeout = nil)
# We want to be careful here and not call load! which isn't cheap operation.
if timeout and @values and updated_at = @values[:updated_at]
# If the last update was too long ago, we need update:
return true if updated_at < (Time.now - timeout)
return true if updated_at < (now - timeout)
end

return false
Expand All @@ -104,7 +110,7 @@ def needs_update?(timeout = nil)

# Load and return the underlying values.
def load!
@values ||= @loader.call
@values ||= @loader.call(now)
end
end
end
Expand Down
22 changes: 11 additions & 11 deletions lib/utopia/session/middleware.rb
Original file line number Diff line number Diff line change
Expand Up @@ -143,8 +143,8 @@ def normalize_same_site(same_site)
end

def prepare_session(request)
LazyHash.new do
self.load_session_values(request)
LazyHash.new do |now|
self.load_session_values(request, now)
end
end

Expand All @@ -155,22 +155,22 @@ def update_session(session_hash, headers)
end

# Constructs a valid session for the given request. These fields must match as per the checks performed in `valid_session?`:
def build_initial_session(request)
def build_initial_session(request, now)
{
user_agent: request.user_agent,
created_at: Time.now.utc,
updated_at: Time.now.utc,
created_at: now,
updated_at: now,
}
end

# Load session from user supplied cookie. If the data is invalid or otherwise fails validation, `build_iniital_session` is invoked.
# @return hash of values.
def load_session_values(request)
def load_session_values(request, now)
# Decrypt the data from the user if possible:
if data = request.cookies[@cookie_name]
begin
if values = decrypt(data)
validate_session!(request, values)
validate_session!(request, values, now)

return values
end
Expand All @@ -180,24 +180,24 @@ def load_session_values(request)
end

# If we couldn't create a session
return build_initial_session(request)
return build_initial_session(request, now)
end

def validate_session!(request, values)
def validate_session!(request, values, now)
if values[:user_agent] != request.user_agent
raise PayloadError, "Invalid session because supplied user agent #{request.user_agent.inspect} does not match session user agent #{values[:user_agent].inspect}!"
end

if expires_at = expires(values[:updated_at])
if expires_at < Time.now.utc
if expires_at < now
raise PayloadError, "Expired session cookie, user agent submitted a cookie that should have expired at #{expires_at}."
end
end

return true
end

def expires(updated_at=Time.now.utc)
def expires(updated_at)
if @expires_after
return updated_at + @expires_after
end
Expand Down
26 changes: 20 additions & 6 deletions test/utopia/session.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,14 @@
describe Utopia::Session do
include Sus::Fixtures::Protocol::HTTP::MiddlewareContext

let(:clock) {Struct.new(:now).new(Time.now.utc)}

let(:middleware) do
Utopia::Application.build(Protocol::HTTP::Middleware.for{|request|
mock(request.session) do |session|
session.replace(:now){clock.now}
end

case request.path.to_s
when "/login"
request.session["login"] = "true"
Expand Down Expand Up @@ -75,8 +81,7 @@
client.get "/session-set?key=foo&value=bar"
expect(last_response.headers).to have_keys("set-cookie")

# Sleep more than update_timeout
sleep 2
clock.now += 2

client.get "/session-set?key=foo&value=bar"
expect(last_response.headers).to have_keys("set-cookie")
Expand Down Expand Up @@ -272,8 +277,14 @@ def corrupt_payload(data, offset)
describe Utopia::Session do
include Sus::Fixtures::Protocol::HTTP::MiddlewareContext

let(:clock) {Struct.new(:now).new(Time.now.utc)}

let(:middleware) do
Utopia::Application.build(Protocol::HTTP::Middleware.for{|request|
mock(request.session) do |session|
session.replace(:now){clock.now}
end

case request.path.to_s
when "/session-set"
request.session[request.query_parameters["key"].to_sym] = request.query_parameters["value"]
Expand Down Expand Up @@ -316,7 +327,7 @@ def before

it "should fail if expired cookie is sent with the request" do
session_cookie = last_response.headers["set-cookie"].first.split(";")[0]
sleep 6 # sleep longer than the session timeout
clock.now += 6
client.set_cookie session_cookie

client.get "/session-get?key=foo"
Expand Down Expand Up @@ -369,9 +380,11 @@ def before
end

it "should need to be reloaded if old" do
current_time = Time.now.utc
hash = Utopia::Session::LazyHash.new do
{updated_at: Time.now - 3700}
{updated_at: current_time - 3700}
end
expect(hash).to receive(:now).and_return(current_time)

expect(hash.needs_update?(3600)).to be == false

Expand Down Expand Up @@ -401,16 +414,17 @@ def before
end

it "should persist changed values" do
current_time = Time.now.utc
hash = Utopia::Session::LazyHash.new do
{a: 10}
end
expect(hash).to receive(:now).and_return(current_time)

hash[:a] = 20
persisted_at = Time.now.utc

result = hash.persist do |values, updated_at|
expect(values[:a]).to be == 20
expect(updated_at).to be >= persisted_at
expect(updated_at).to be_equal(current_time)
expect(values[:updated_at]).to be_equal(updated_at)

:complete
Expand Down
Loading