Skip to content

Commit

Permalink
Add conditional_sessions plugin, for using the sessions plugin for on…
Browse files Browse the repository at this point in the history
…ly a subset of requests
  • Loading branch information
jeremyevans committed Nov 8, 2024
1 parent e760a7d commit e6c84e8
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 0 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
= master

* Add conditional_sessions plugin, for using the sessions plugin for only a subset of requests (jeremyevans)

* In permissions_policy plugin, add response.skip_permissions_policy! to avoid setting header (jeremyevans)

* Make Roda.freeze work if already frozen when using the autoload_{hash_branches,named_routes} plugins (jeremyevans)
Expand Down
67 changes: 67 additions & 0 deletions lib/roda/plugins/conditional_sessions.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# frozen-string-literal: true

class Roda
module RodaPlugins
# The conditional_sessions plugin loads the sessions plugin. However,
# it only allows sessions if the block passed to the plugin returns
# truthy. The block is evaluated in request context. This is designed for
# use in applications that want to use sessions for some requests,
# and want to be sure that sessions are not used for other requests.
# For example, if you want to make sure that sessions are not used for
# requests with paths starting with /static, you could do:
#
# plugin :conditional_sessions, secret: ENV["SECRET"] do
# !path_info.start_with?('/static')
# end
#
# The the request session, session_created_at, and session_updated_at methods
# raise a RodaError exception when sessions are not allowed. The request
# persist_session and route scope clear_session methods do nothing when
# sessions are not allowed.
module ConditionalSessions
# Pass all options to the sessions block, and use the block to define
# a request method for whether sessions are allowed.
def self.load_dependencies(app, opts=OPTS, &block)
app.plugin :sessions, opts
app::RodaRequest.class_eval do
define_method(:use_sessions?, &block)
alias use_sessions? use_sessions?
end
end

module InstanceMethods
# Do nothing if not using sessions.
def clear_session
super if @_request.use_sessions?
end
end

module RequestMethods
# Raise RodaError if not using sessions.
def session
raise RodaError, "session called on request not using sessions" unless use_sessions?
super
end

# Raise RodaError if not using sessions.
def session_created_at
raise RodaError, "session_created_at called on request not using sessions" unless use_sessions?
super
end

# Raise RodaError if not using sessions.
def session_updated_at
raise RodaError, "session_updated_at called on request not using sessions" unless use_sessions?
super
end

# Do nothing if not using sessions.
def persist_session(headers, session)
super if use_sessions?
end
end
end

register_plugin(:conditional_sessions, ConditionalSessions)
end
end
73 changes: 73 additions & 0 deletions spec/plugin/conditional_sessions_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
require_relative "../spec_helper"

if RUBY_VERSION >= '2'
describe "conditional_sessions plugin" do
include CookieJar

before do
allow = @allow = String.new('f')
app(:bare) do
plugin :conditional_sessions, :secret=>'1'*64 do
allow != 'f'
end

route do |r|
r.get('s', String, String){|k, v| v.force_encoding('UTF-8'); session[k] = v}
r.get('g', String){|k| session[k].to_s}
r.get('cat'){r.session_created_at.to_i.to_s}
r.get('uat'){r.session_updated_at.to_i.to_s}
r.get('cs'){clear_session.to_s}
r.get('ps', String, String){|k, v| r.persist_session(response.headers, k => v); env.delete("rack.session"); nil}
''
end
end
end

it "allows sessions if allowed" do
@allow.replace('t')
body('/s/foo/bar').must_equal 'bar'
body('/g/foo').must_equal 'bar'
body('/cat').to_i.must_be(:>=, Time.now.to_i - 1)
body('/uat').to_i.must_be(:>=, Time.now.to_i - 1)

body('/s/foo/baz').must_equal 'baz'
body('/g/foo').must_equal 'baz'

body('/ps/foo/quux').must_equal ''
body('/g/foo').must_equal 'quux'

body('/cs').must_equal ''
body('/g/foo').must_equal ''
end

it "raises on session if sessions not allowed" do
proc{body('/s/foo/bar')}.must_raise Roda::RodaError
end

it "raises on session_created_at if sessions not allowed" do
proc{body('/cat')}.must_raise Roda::RodaError
end

it "raises on session_updated_at if sessions not allowed" do
proc{body('/uat')}.must_raise Roda::RodaError
end

it "has clear_session do nothing if sessions are not alowed" do
@allow.replace('t')
body('/s/foo/bar').must_equal 'bar'
@allow.replace('f')
body('/cs').must_equal ''
@allow.replace('t')
body('/g/foo').must_equal 'bar'
end

it "has persist_session do nothing if sessions are not alowed" do
@allow.replace('t')
body('/s/foo/bar').must_equal 'bar'
@allow.replace('f')
body('/ps/foo/quux').must_equal ''
@allow.replace('t')
body('/g/foo').must_equal 'bar'
end
end
end
1 change: 1 addition & 0 deletions www/pages/documentation.erb
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@
</ul></li>
<li>Other: <ul>
<li><a href="rdoc/classes/Roda/RodaPlugins/CommonLogger.html">common_logger</a>: Adds support for logging in common log format.</li>
<li><a href="rdoc/classes/Roda/RodaPlugins/ConditionalSessions.html">conditional_sessions</a>: Allows for using the session plugin for a subset of requests.</li>
<li><a href="rdoc/classes/Roda/RodaPlugins/Csrf.html">csrf</a>: Older CSRF plugin for backwards compatibility using <a href="https://github.com/baldowl/rack_csrf">rack_csrf</a>.</li>
<li><a href="rdoc/classes/Roda/RodaPlugins/Environments.html">environments</a>: Adds support for handling different execution environments (development/test/production).</li>
<li><a href="rdoc/classes/Roda/RodaPlugins/EarlyHints.html">early_hints</a>: Adds support for using 103 Early Hints responses when using a compatible server.</li>
Expand Down

0 comments on commit e6c84e8

Please sign in to comment.