diff --git a/README.md b/README.md index 9306d838d..b688ae82a 100644 --- a/README.md +++ b/README.md @@ -210,7 +210,34 @@ For example: config.password_strategy = Clearance::PasswordStrategies::SHA1 # Blowfish config.password_strategy = Clearance::PasswordStrategies::Blowfish - + + +Routing Constraints +------------------- + +Clearance ships with Rails [routing constraints](http://guides.rubyonrails.org/routing.html#advanced-constraints). +These allow you to check if a user is signed in or signed out before they hit your controller - the check is moved down the stack. +You can use them like this: + + SweetBlog::Application.routes.draw do + # Signed-in admin users use this root path + constraints(Clearance::Constraints::SignedIn.new {|user| user.admin?}) do + root :to => 'admins/dashboard#index' + end + + # Signed-in non-admin users use this root path + constraints(Clearance::Constraints::SignedIn.new) do + root :to => 'organizations#show' + end + + # Signed-out users users use this fallback root path + constraints(Clearance::Constraints::SignedOut.new) do + root :to => 'high_voltage/pages#show', :id => 'home' + end + end + +Note that `Clearance::Constraints::SignedIn` yields a signed-in user object so +that you can perform additional checks. Optional Cucumber features -------------------------- diff --git a/gemfiles/3.1.4.gemfile.lock b/gemfiles/3.1.4.gemfile.lock index a44268cdc..6c7e61344 100644 --- a/gemfiles/3.1.4.gemfile.lock +++ b/gemfiles/3.1.4.gemfile.lock @@ -1,5 +1,5 @@ PATH - remote: /home/mike/clearance + remote: /Users/gabe/thoughtbot/open-source/clearance specs: clearance (0.16.2) diesel (~> 0.1.5) diff --git a/gemfiles/3.2.3.gemfile.lock b/gemfiles/3.2.3.gemfile.lock index 625262db1..4d11dc70b 100644 --- a/gemfiles/3.2.3.gemfile.lock +++ b/gemfiles/3.2.3.gemfile.lock @@ -1,5 +1,5 @@ PATH - remote: /home/mike/clearance + remote: /Users/gabe/thoughtbot/open-source/clearance specs: clearance (0.16.2) diesel (~> 0.1.5) diff --git a/lib/clearance.rb b/lib/clearance.rb index f4382e3fa..b3f4d331b 100644 --- a/lib/clearance.rb +++ b/lib/clearance.rb @@ -5,3 +5,4 @@ require 'clearance/user' require 'clearance/engine' require 'clearance/password_strategies' +require 'clearance/constraints' diff --git a/lib/clearance/constraints.rb b/lib/clearance/constraints.rb new file mode 100644 index 000000000..c1cdc8b26 --- /dev/null +++ b/lib/clearance/constraints.rb @@ -0,0 +1,2 @@ +require 'clearance/constraints/signed_in' +require 'clearance/constraints/signed_out' diff --git a/lib/clearance/constraints/signed_in.rb b/lib/clearance/constraints/signed_in.rb new file mode 100644 index 000000000..ffad50439 --- /dev/null +++ b/lib/clearance/constraints/signed_in.rb @@ -0,0 +1,28 @@ +module Clearance + module Constraints + class SignedIn + def initialize(&block) + @block = block || lambda { |user| true } + end + + def matches?(request) + @request = request + signed_in? && current_user_fulfills_additional_requirements? + end + + private + + def signed_in? + @request.env[:clearance].signed_in? + end + + def current_user_fulfills_additional_requirements? + @block.call(current_user) + end + + def current_user + @request.env[:clearance].current_user + end + end + end +end diff --git a/lib/clearance/constraints/signed_out.rb b/lib/clearance/constraints/signed_out.rb new file mode 100644 index 000000000..2c92e8a1d --- /dev/null +++ b/lib/clearance/constraints/signed_out.rb @@ -0,0 +1,9 @@ +module Clearance + module Constraints + class SignedOut + def matches?(request) + request.env[:clearance].signed_out? + end + end + end +end diff --git a/lib/clearance/session.rb b/lib/clearance/session.rb index 1995e279b..d59072230 100644 --- a/lib/clearance/session.rb +++ b/lib/clearance/session.rb @@ -10,6 +10,10 @@ def signed_in? current_user.present? end + def signed_out? + ! signed_in? + end + def current_user @current_user ||= with_remember_token do |token| Clearance.configuration.user_model.find_by_remember_token(token) diff --git a/spec/clearance/constraints/signed_in_spec.rb b/spec/clearance/constraints/signed_in_spec.rb new file mode 100644 index 000000000..7d333d32e --- /dev/null +++ b/spec/clearance/constraints/signed_in_spec.rb @@ -0,0 +1,52 @@ +require 'spec_helper' + +describe Clearance::Constraints::SignedIn do + it 'returns true when user is signed in' do + user = create(:user) + + signed_in_constraint = Clearance::Constraints::SignedIn.new + signed_in_constraint.matches?(request_with_remember_token(user.remember_token)).should be_true + end + + it 'returns false when user is not signed in' do + signed_in_constraint = Clearance::Constraints::SignedIn.new + signed_in_constraint.matches?(request_without_remember_token).should be_false + end + + it 'yields a signed-in user to a provided block' do + user = create(:user, :email => 'before@example.com') + signed_in_constraint = Clearance::Constraints::SignedIn.new do |user| + user.update_attribute(:email, 'after@example.com') + end + + signed_in_constraint.matches?(request_with_remember_token(user.remember_token)) + user.reload.email.should == 'after@example.com' + end + + it 'does not yield a user if they are not signed in' do + user = create(:user, :email => 'before@example.com') + + signed_in_constraint = Clearance::Constraints::SignedIn.new do |user| + user.update_attribute(:email, 'after@example.com') + end + + signed_in_constraint.matches?(request_without_remember_token) + user.reload.email.should == 'before@example.com' + end + + it 'matches if the user-provided block returns true' do + user = create(:user) + + signed_in_constraint = Clearance::Constraints::SignedIn.new { |user| true } + + signed_in_constraint.matches?(request_with_remember_token(user.remember_token)).should be_true + end + + it 'does not match if the user-provided block returns false' do + user = create(:user) + + signed_in_constraint = Clearance::Constraints::SignedIn.new { |user| false } + + signed_in_constraint.matches?(request_with_remember_token(user.remember_token)).should be_false + end +end diff --git a/spec/clearance/constraints/signed_out_spec.rb b/spec/clearance/constraints/signed_out_spec.rb new file mode 100644 index 000000000..f4e2aa20d --- /dev/null +++ b/spec/clearance/constraints/signed_out_spec.rb @@ -0,0 +1,15 @@ +require 'spec_helper' + +describe Clearance::Constraints::SignedOut do + it 'returns true when user is signed out' do + signed_out_constraint = Clearance::Constraints::SignedOut.new + signed_out_constraint.matches?(request_without_remember_token).should be_true + end + + it 'returns false when user is not signed out' do + user = create(:user) + + signed_out_constraint = Clearance::Constraints::SignedOut.new + signed_out_constraint.matches?(request_with_remember_token(user.remember_token)).should be_false + end +end diff --git a/spec/clearance/session_spec.rb b/spec/clearance/session_spec.rb index be7309d53..7d3fc62af 100644 --- a/spec/clearance/session_spec.rb +++ b/spec/clearance/session_spec.rb @@ -18,14 +18,14 @@ env = env_with_remember_token("bogus") session = Clearance::Session.new(env) - session.should_not be_signed_in + session.should be_signed_out session.current_user.should be_nil end it "returns nil without a remember token" do env = env_without_remember_token session = Clearance::Session.new(env) - session.should_not be_signed_in + session.should be_signed_out session.current_user.should be_nil end diff --git a/spec/support/request_with_remember_token.rb b/spec/support/request_with_remember_token.rb new file mode 100644 index 000000000..f541a56a7 --- /dev/null +++ b/spec/support/request_with_remember_token.rb @@ -0,0 +1,17 @@ +module RememberTokenHelpers + def request_with_remember_token(remember_token) + cookies = {'action_dispatch.cookies' => { + Clearance::Session::REMEMBER_TOKEN_COOKIE => remember_token + }} + env = { :clearance => Clearance::Session.new(cookies) } + Rack::Request.new(env) + end + + def request_without_remember_token + request_with_remember_token(nil) + end +end + +RSpec.configure do |config| + config.include RememberTokenHelpers +end