diff --git a/CHANGELOG b/CHANGELOG index 62794163..f319bce2 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -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) diff --git a/lib/roda/plugins/conditional_sessions.rb b/lib/roda/plugins/conditional_sessions.rb new file mode 100644 index 00000000..a967e5ac --- /dev/null +++ b/lib/roda/plugins/conditional_sessions.rb @@ -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 diff --git a/spec/plugin/conditional_sessions_spec.rb b/spec/plugin/conditional_sessions_spec.rb new file mode 100644 index 00000000..9bffe3bb --- /dev/null +++ b/spec/plugin/conditional_sessions_spec.rb @@ -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 diff --git a/www/pages/documentation.erb b/www/pages/documentation.erb index d4cc9e1a..43a542f2 100644 --- a/www/pages/documentation.erb +++ b/www/pages/documentation.erb @@ -155,6 +155,7 @@