Skip to content

Commit

Permalink
Merge pull request #177 from thoughtbot/rack
Browse files Browse the repository at this point in the history
Perform authentication in Rack middleware
  • Loading branch information
Dan Croak committed Dec 18, 2011
2 parents f37add6 + e18fee0 commit 498bff3
Show file tree
Hide file tree
Showing 17 changed files with 296 additions and 114 deletions.
9 changes: 7 additions & 2 deletions Gemfile.lock
Expand Up @@ -54,6 +54,8 @@ GEM
rspec (>= 2.6.0)
bcat (0.6.1)
rack (~> 1.0)
bourne (1.0)
mocha (= 0.9.8)
builder (2.1.2)
capybara (1.0.1)
mime-types (>= 1.16)
Expand Down Expand Up @@ -99,7 +101,8 @@ GEM
mime-types (~> 1.16)
treetop (~> 1.4.8)
mime-types (1.16)
mocha (0.9.12)
mocha (0.9.8)
rake
nokogiri (1.5.0)
polyglot (0.3.2)
rack (1.2.3)
Expand Down Expand Up @@ -147,6 +150,7 @@ GEM
sqlite3 (1.3.4)
term-ansicolor (1.0.6)
thor (0.14.6)
timecop (0.3.5)
treetop (1.4.10)
polyglot
polyglot (>= 0.3.1)
Expand All @@ -160,14 +164,15 @@ PLATFORMS
DEPENDENCIES
appraisal (~> 0.3.8)
aruba (~> 0.4.2)
bourne
bundler (~> 1.0.0)
capybara (~> 1.0.0)
clearance!
cucumber-rails (~> 1.0.2)
database_cleaner
factory_girl_rails
launchy
mocha
rspec-rails (~> 2.6.0)
shoulda-matchers!
sqlite3
timecop
32 changes: 27 additions & 5 deletions README.md
Expand Up @@ -86,6 +86,26 @@ Clearance will deliver one email on your app's behalf: when a user resets their
config.mailer_sender = "me@example.com"
end

Rack
----

Clearance adds its session to the Rack environment hash so middleware and other
Rack applications can interact with it:

class Bubblegum::Middleware
def initialize(app)
@app = app
end

def call(env)
if env[:clearance].signed_in?
env[:clearance].current_user.bubble_gum
end
@app.call(env)
end
end


Overriding defaults
-------------------

Expand Down Expand Up @@ -214,16 +234,18 @@ Then run your tests!

rake

Optional test matchers
----------------------
Testing
-------

Clearance comes with test matchers that are compatible with RSpec and Test::Unit.
If you want to write Rails functional tests or controller specs with Clearance,
you'll need to require the included test helpers and matchers.

To use them, require the test matchers. For example, in spec/support/clearance.rb:
For example, in spec/support/clearance.rb or test/test_helper.rb:

require 'clearance/testing'

You'll then have access to methods like:
This will make Clearance::Authentication methods work in your controllers
during functional tests and provide access to helper methods like:

sign_in
sign_in_as(user)
Expand Down
3 changes: 2 additions & 1 deletion clearance.gemspec
Expand Up @@ -30,7 +30,8 @@ Gem::Specification.new do |s|
s.add_development_dependency('cucumber-rails', '~> 1.0.2')
s.add_development_dependency('rspec-rails', '~> 2.6.0')
s.add_development_dependency('sqlite3')
s.add_development_dependency('mocha')
s.add_development_dependency('bourne')
s.add_development_dependency('timecop')

if s.respond_to? :specification_version then
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
Expand Down
1 change: 0 additions & 1 deletion features/support/env.rb
Expand Up @@ -53,4 +53,3 @@
rescue NameError
raise "You need to add database_cleaner to your Gemfile (in the :test group) if you wish to use it."
end

4 changes: 2 additions & 2 deletions gemfiles/3.0.9.gemfile.lock
Expand Up @@ -5,9 +5,9 @@ GIT
shoulda-matchers (1.0.0.beta3)

PATH
remote: /Users/croaky/dev/clearance
remote: /Users/jferris/Source/clearance
specs:
clearance (0.12.0)
clearance (0.13.0)
diesel (~> 0.1.5)
rails (>= 3.0)

Expand Down
6 changes: 3 additions & 3 deletions gemfiles/3.1.0.gemfile.lock
Expand Up @@ -5,9 +5,9 @@ GIT
shoulda-matchers (1.0.0.beta3)

PATH
remote: /Users/croaky/dev/clearance
remote: /Users/jferris/Source/clearance
specs:
clearance (0.12.0)
clearance (0.13.0)
diesel (~> 0.1.5)
rails (>= 3.0)

Expand Down Expand Up @@ -154,7 +154,7 @@ GEM
sprockets (2.0.0)
hike (~> 1.2)
rack (~> 1.0)
tilt (!= 1.3.0, ~> 1.1)
tilt (~> 1.1, != 1.3.0)
spruz (0.2.13)
sqlite3 (1.3.4)
term-ansicolor (1.0.6)
Expand Down
4 changes: 3 additions & 1 deletion lib/clearance.rb
@@ -1,5 +1,7 @@
require 'clearance/configuration'
require 'clearance/session'
require 'clearance/rack_session'
require 'clearance/authentication'
require 'clearance/user'
require 'clearance/engine'
require 'clearance/password_strategies'
require 'clearance/password_strategies'
28 changes: 9 additions & 19 deletions lib/clearance/authentication.rb
Expand Up @@ -10,32 +10,32 @@ module Authentication
:authorize, :deny_access
end

# User in the current cookie
# Finds the user from the rack clearance session
#
# @return [User, nil]
def current_user
@_current_user ||= user_from_cookie
clearance_session.current_user
end

# Set the current user
#
# @param [User]
def current_user=(user)
@_current_user = user
clearance_session.sign_in user
end

# Is the current user signed in?
#
# @return [true, false]
def signed_in?
! current_user.nil?
clearance_session.signed_in?
end

# Is the current user signed out?
#
# @return [true, false]
def signed_out?
current_user.nil?
!signed_in?
end

# Sign user in to cookie.
Expand All @@ -45,23 +45,15 @@ def signed_out?
# @example
# sign_in(@user)
def sign_in(user)
if user
cookies[:remember_token] = {
:value => user.remember_token,
:expires => Clearance.configuration.cookie_expiration.call
}
self.current_user = user
end
clearance_session.sign_in user
end

# Sign user out of cookie.
#
# @example
# sign_out
def sign_out
current_user.reset_remember_token! if current_user
cookies.delete(:remember_token)
self.current_user = nil
clearance_session.sign_out
end

# Find the user by the given params or return nil.
Expand Down Expand Up @@ -107,10 +99,8 @@ def handle_unverified_request

protected

def user_from_cookie
if token = cookies[:remember_token]
::User.find_by_remember_token(token)
end
def clearance_session
request.env[:clearance]
end

def store_location
Expand Down
2 changes: 2 additions & 0 deletions lib/clearance/engine.rb
Expand Up @@ -6,5 +6,7 @@ class Engine < Rails::Engine
initializer "clearance.filter" do |app|
app.config.filter_parameters += [:token, :password]
end

config.app_middleware.insert_after ActionDispatch::Cookies, Clearance::RackSession
end
end
15 changes: 15 additions & 0 deletions lib/clearance/rack_session.rb
@@ -0,0 +1,15 @@
module Clearance
class RackSession
def initialize(app)
@app = app
end

def call(env)
session = Clearance::Session.new(env)
env_with_clearance = env.merge(:clearance => session)
response = @app.call(env_with_clearance)
session.add_cookie_to_headers response[1]
response
end
end
end
55 changes: 55 additions & 0 deletions lib/clearance/session.rb
@@ -0,0 +1,55 @@
module Clearance
class Session
REMEMBER_TOKEN_COOKIE = "remember_token".freeze

def initialize(env)
@env = env
end

def signed_in?
current_user.present?
end

def current_user
@current_user ||= with_remember_token do |token|
::User.find_by_remember_token(token)
end
end

def sign_in(user)
@current_user = user
end

def sign_out
current_user.reset_remember_token! if signed_in?
@current_user = nil
cookies.delete(REMEMBER_TOKEN_COOKIE)
end

def add_cookie_to_headers(headers)
if signed_in?
Rack::Utils.set_cookie_header!(headers,
REMEMBER_TOKEN_COOKIE,
:value => current_user.remember_token,
:expires => Clearance.configuration.cookie_expiration.call,
:path => "/")
end
end

private

def with_remember_token
if token = remember_token
yield token
end
end

def remember_token
cookies[REMEMBER_TOKEN_COOKIE]
end

def cookies
@cookies ||= Rack::Request.new(@env).cookies
end
end
end
5 changes: 5 additions & 0 deletions lib/clearance/testing/helpers.rb
Expand Up @@ -13,6 +13,11 @@ def sign_in
def sign_out
@controller.current_user = nil
end

def setup_controller_request_and_response
super
@request.env[:clearance] = Clearance::Session.new(@request.env)
end
end
end
end
23 changes: 23 additions & 0 deletions spec/clearance/rack_session_spec.rb
@@ -0,0 +1,23 @@
require 'spec_helper'

describe Clearance::RackSession do
it "injects a clearance session into the environment" do
expected_session = "the session"
expected_session.stubs(:add_cookie_to_headers)
Clearance::Session.stubs(:new => expected_session)
headers = { "X-Roaring-Lobster" => "Red" }

app = Rack::Builder.new do
use Clearance::RackSession
run lambda { |env| Rack::Response.new(env[:clearance], 200, headers).finish }
end

env = Rack::MockRequest.env_for("/")

response = Rack::MockResponse.new(*app.call(env))

Clearance::Session.should have_received(:new).with(env)
response.body.should == expected_session
expected_session.should have_received(:add_cookie_to_headers).with(has_entries(headers))
end
end

0 comments on commit 498bff3

Please sign in to comment.