thoughtbot-clearance 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
data/README.textile ADDED
@@ -0,0 +1,178 @@
1
+ h1. Clearance
2
+
3
+ Simple, complete Ruby web app authentication.
4
+
5
+ "We have clearance, Clarence.":http://www.youtube.com/v/mNRXJEE3Nz8
6
+
7
+ h2. Features
8
+
9
+ * email & password
10
+ * modules, not a generator
11
+ * gem, not a plugin
12
+ * tests included using "shoulda":http://thoughtbot.com/projects/shoulda & "factory_girl":http://thoughtbot.com/projects/factory_girl
13
+
14
+ h2. Gem installation (Rails 2.1+)
15
+
16
+ Specify the gem dependency in your config/environment.rb file:
17
+
18
+ Rails::Initializer.run do |config|
19
+ # ...
20
+ config.gem 'thoughtbot-shoulda', :lib => 'shoulda', :source => "http://gems.github.com"
21
+ config.gem 'thoughtbot-factory_girl', :lib => 'factory_girl', :source => "http://gems.github.com"
22
+ config.gem "dancroak-clearance", :lib => 'clearance', :source => 'http://gems.github.com/'
23
+ end
24
+
25
+ Then:
26
+
27
+ rake gems:install
28
+ rake gems:unpack
29
+
30
+ h2. Tests
31
+
32
+ The tests use "Shoulda":http://thoughtbot.com/projects/shoulda and "Factory Girl":http://thoughtbot.com/projects/factory_girl. You should create a User Factory:
33
+
34
+ Factory.sequence :email do |n|
35
+ "user#{n}@example.com"
36
+ end
37
+
38
+ Factory.define :user do |user|
39
+ user.email { Factory.next :email }
40
+ user.password 'sekrit'
41
+ user.password_confirmation 'sekrit'
42
+ end
43
+
44
+ In test/test_helper.rb:
45
+
46
+ class Test::Unit::TestCase
47
+ self.use_transactional_fixtures = true
48
+ self.use_instantiated_fixtures = false
49
+ include Clearance::TestHelper
50
+ end
51
+
52
+ In test/unit/user_test.rb:
53
+
54
+ require File.dirname(__FILE__) + '/../test_helper'
55
+
56
+ class UserTest < Test::Unit::TestCase
57
+ include Clearance::UserTest
58
+ end
59
+
60
+ In test/functional/sessions_controller_test.rb:
61
+
62
+ require File.dirname(__FILE__) + '/../test_helper'
63
+
64
+ class SessionsControllerTest < ActionController::TestCase
65
+ include Clearance::SessionsControllerTest
66
+
67
+ private
68
+
69
+ def url_after_create
70
+ root_url # the default
71
+ end
72
+
73
+ def url_after_destroy
74
+ login_url # the default
75
+ end
76
+ end
77
+
78
+ In test/functional/users_controller_test.rb:
79
+
80
+ require File.dirname(__FILE__) + '/../test_helper'
81
+
82
+ class UsersControllerTest < ActionController::TestCase
83
+ include Clearance::UsersControllerTest
84
+
85
+ private
86
+
87
+ def url_after_create
88
+ root_url # the default
89
+ end
90
+
91
+ def url_after_update
92
+ root_url # the default
93
+ end
94
+ end
95
+
96
+ h2. Schema
97
+
98
+ Change your User model so it has these attributes:
99
+
100
+ change_table(:users) do |t|
101
+ t.column :email, :string
102
+ t.column :crypted_password, :string, :limit => 40
103
+ t.column :salt, :string, :limit => 40
104
+ t.column :remember_token, :string
105
+ t.column :remember_token_expires_at, :datetime
106
+ end
107
+
108
+ add_index :users, [:email, :crypted_password]
109
+
110
+ h2. User Model
111
+
112
+ In app/models/user.rb:
113
+
114
+ class User < ActiveRecord::Base
115
+ include Clearance::Model
116
+ end
117
+
118
+ h2. Controllers
119
+
120
+ In app/controllers/application_controller.rb:
121
+
122
+ class ApplicationController < ActionController::Base
123
+ helper :all
124
+ protect_from_forgery
125
+ include Clearance::ApplicationController
126
+ end
127
+
128
+ In app/controllers/sessions_controller.rb:
129
+
130
+ class SessionsController < ApplicationController
131
+ include Clearance::SessionsController
132
+ end
133
+
134
+ In app/controllers/users_controller.rb:
135
+
136
+ class UsersController < ApplicationController
137
+ include Clearance::UsersController
138
+ end
139
+
140
+ h2. Routes
141
+
142
+ map.with_options :controller => 'sessions' do |m|
143
+ m.login '/login', :action => 'new'
144
+ m.logout '/logout', :action => 'destroy'
145
+ end
146
+ map.resource :session
147
+ map.resources :users
148
+
149
+ h2. Views
150
+
151
+ In app/views/sessions/new.html.erb
152
+
153
+ <% form_for :session, :url => session_path do |f| %>
154
+ <div class="group">
155
+ <%= f.label :email %>
156
+ <%= f.text_field :email %>
157
+ </div>
158
+ <div class="group">
159
+ <%= f.label :password %>
160
+ <%= f.password_field :password %>
161
+ </div>
162
+ <div class="checkbox">
163
+ <%= f.check_box :remember_me %>
164
+ <%= f.label :remember_me %>
165
+ </div>
166
+ <div class="group buttons">
167
+ <%= f.submit 'Login', :disable_with => 'Please wait...' %>
168
+ </div>
169
+ <% end %>
170
+
171
+ h2. Authors
172
+
173
+ * thoughtbot, inc.
174
+ * Dan Croak
175
+ * Josh Nichols
176
+ * Mike Breen
177
+ * Mike Burns
178
+ * Jason Morrison
data/clearance.gemspec ADDED
@@ -0,0 +1,21 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "clearance"
3
+ s.version = "0.1.4"
4
+ s.date = "2008-09-24"
5
+ s.summary = "Simple, complete Rails authentication."
6
+ s.email = "dcroak@thoughtbot.com"
7
+ s.homepage = "http://github.com/dancroak/clearance"
8
+ s.description = "Simple, complete Rails authentication scheme."
9
+ s.authors = ["thoughtbot, inc.", "Dan Croak", "Josh Nichols", "Mike Breen", "Mike Burns", "Jason Morrison"]
10
+ s.files = ["README.textile",
11
+ "clearance.gemspec",
12
+ "lib/clearance.rb",
13
+ "lib/clearance/app/controllers/application_controller.rb",
14
+ "lib/clearance/app/models/model.rb",
15
+ "lib/clearance/app/controllers/sessions_controller.rb",
16
+ "lib/clearance/test/functionals/sessions_controller_test.rb",
17
+ "lib/clearance/test/test_helper.rb",
18
+ "lib/clearance/test/units/user_test.rb",
19
+ "lib/clearance/app/controllers/users_controller.rb",
20
+ "lib/clearance/test/functionals/users_controller_test.rb"]
21
+ end
data/lib/clearance.rb ADDED
@@ -0,0 +1,8 @@
1
+ require 'clearance/app/controllers/application_controller'
2
+ require 'clearance/app/controllers/sessions_controller'
3
+ require 'clearance/app/controllers/users_controller'
4
+ require 'clearance/app/models/model'
5
+ require 'clearance/test/test_helper'
6
+ require 'clearance/test/functionals/sessions_controller_test'
7
+ require 'clearance/test/functionals/users_controller_test'
8
+ require 'clearance/test/units/user_test'
@@ -0,0 +1,70 @@
1
+ module Clearance
2
+ module ApplicationController
3
+
4
+ def self.included(base)
5
+ base.class_eval do
6
+ helper_method :current_user
7
+ helper_method :logged_in?
8
+
9
+ include InstanceMethods
10
+
11
+ protected
12
+ include ProtectedInstanceMethods
13
+ end
14
+ end
15
+
16
+ module InstanceMethods
17
+ def current_user
18
+ @current_user ||= (user_from_session || user_from_cookie)
19
+ end
20
+
21
+ def logged_in?
22
+ ! current_user.nil?
23
+ end
24
+ end
25
+
26
+ module ProtectedInstanceMethods
27
+ def authenticate
28
+ deny_access unless self.current_user
29
+ end
30
+
31
+ def user_from_session
32
+ User.find_by_id session[:user_id]
33
+ end
34
+
35
+ def user_from_cookie
36
+ user = User.find_by_remember_token(cookies[:auth_token]) if cookies[:auth_token]
37
+ user && user.remember_token? ? user : nil
38
+ end
39
+
40
+ def login(user)
41
+ create_session_for user
42
+ @current_user = user
43
+ end
44
+
45
+ def create_session_for(user)
46
+ session[:user_id] = user.id if user
47
+ end
48
+
49
+ def redirect_back_or(default)
50
+ session[:return_to] ? redirect_to(session[:return_to]) : redirect_to(default)
51
+ session[:return_to] = nil
52
+ end
53
+
54
+ def redirect_to_root
55
+ redirect_to root_url
56
+ end
57
+
58
+ def store_location
59
+ session[:return_to] = request.request_uri
60
+ end
61
+
62
+ def deny_access(flash_message = nil, opts = {})
63
+ opts[:redirect] ||= login_url
64
+ store_location
65
+ flash[:error] = flash_message if flash_message
66
+ redirect_to opts[:redirect]
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,73 @@
1
+ module Clearance
2
+ module SessionsController
3
+
4
+ def self.included(base)
5
+ base.class_eval do
6
+ skip_before_filter :authenticate
7
+ protect_from_forgery :except => :create
8
+ filter_parameter_logging :password
9
+
10
+ include InstanceMethods
11
+
12
+ protected
13
+ include ProtectedInstanceMethods
14
+ end
15
+ end
16
+
17
+ module InstanceMethods
18
+ def create
19
+ remember_me = params[:session][:remember_me] if params[:session]
20
+ login_via_password(params[:session][:email], params[:session][:password], remember_me)
21
+ end
22
+
23
+ def destroy
24
+ forget current_user
25
+ reset_session
26
+ flash[:notice] = 'You have been logged out.'
27
+ redirect_to url_after_destroy
28
+ end
29
+ end
30
+
31
+ module ProtectedInstanceMethods
32
+ def login_via_password(email, password, remember_me)
33
+ user = User.authenticate(email, password)
34
+ if login(user)
35
+ create_session_for(user)
36
+ remember(user) if remember_me == '1'
37
+ login_successful
38
+ else
39
+ login_failure
40
+ end
41
+ end
42
+
43
+ def login_successful
44
+ flash[:notice] = 'Logged in successfully'
45
+ redirect_back_or url_after_create
46
+ end
47
+
48
+ def login_failure(message = "Bad email or password.")
49
+ flash.now[:notice] = message
50
+ render :action => :new
51
+ end
52
+
53
+ def remember(user)
54
+ user.remember_me!
55
+ cookies[:auth_token] = { :value => user.remember_token,
56
+ :expires => user.remember_token_expires_at }
57
+ end
58
+
59
+ def forget(user)
60
+ user.forget_me! if user
61
+ cookies.delete :auth_token
62
+ end
63
+
64
+ def url_after_create
65
+ root_url
66
+ end
67
+
68
+ def url_after_destroy
69
+ login_url
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,81 @@
1
+ module Clearance
2
+ module UsersController
3
+
4
+ def self.included(base)
5
+ base.class_eval do
6
+ before_filter :authenticate, :except => [:new, :create]
7
+ before_filter :redirect_to_root, :only => [:new, :create], :if => :logged_in?
8
+ before_filter :ensure_user_is_accessing_self, :only => [:edit, :update, :show]
9
+
10
+ filter_parameter_logging :password
11
+
12
+ include InstanceMethods
13
+
14
+ private
15
+ include PrivateInstanceMethods
16
+ end
17
+ end
18
+
19
+ module InstanceMethods
20
+ def index
21
+ @users = User.find :all
22
+ end
23
+
24
+ def new
25
+ @user = User.new
26
+ end
27
+
28
+ def show
29
+ @user = User.find params[:id]
30
+ end
31
+
32
+ def create
33
+ @user = User.new params[:user]
34
+ if @user.save
35
+ current_user = @user
36
+ flash[:notice] = "User created and logged in."
37
+ redirect_back_or root_url
38
+ else
39
+ render :action => "new"
40
+ end
41
+ end
42
+
43
+ def edit
44
+ @user = User.find params[:id]
45
+ end
46
+
47
+ def update
48
+ @user = User.find params[:id]
49
+
50
+ if @user.update_attributes params[:user]
51
+ flash[:notice] = "User updated."
52
+ redirect_back_or root_url
53
+ else
54
+ render :action => "edit"
55
+ end
56
+ end
57
+
58
+ def destroy
59
+ @user = User.find params[:id]
60
+ @user.destroy
61
+ redirect_to root_url
62
+ end
63
+ end
64
+
65
+ module PrivateInstanceMethods
66
+ def ensure_user_is_accessing_self
67
+ return if current_user and current_user.respond_to?(:admin?) and current_user.admin?
68
+ deny_access('You cannot edit that user.', :redirect => root_url) unless current_user.id.to_i == params[:id].to_i
69
+ end
70
+
71
+ def url_after_create
72
+ root_url
73
+ end
74
+
75
+ def url_after_update
76
+ root_url
77
+ end
78
+ end
79
+
80
+ end
81
+ end
@@ -0,0 +1,78 @@
1
+ module Clearance
2
+ module Model
3
+
4
+ def self.included(base)
5
+ base.class_eval do
6
+
7
+ attr_accessible :email, :password, :password_confirmation
8
+ attr_accessor :password, :password_confirmation
9
+
10
+ validates_presence_of :email
11
+ validates_presence_of :password, :if => :password_required?
12
+ validates_confirmation_of :password, :if => :password_required?
13
+ validates_uniqueness_of :email
14
+
15
+ before_save :initialize_salt, :encrypt_password
16
+
17
+ extend ClassMethods
18
+ include InstanceMethods
19
+
20
+ protected
21
+
22
+ include ProtectedInstanceMethods
23
+
24
+ end
25
+ end
26
+
27
+ module ClassMethods
28
+ def authenticate(email, password)
29
+ user = find_by_email email
30
+ user && user.authenticated?(password) ? user : nil
31
+ end
32
+ end
33
+
34
+ module InstanceMethods
35
+ def authenticated?(password)
36
+ crypted_password == encrypt(password)
37
+ end
38
+
39
+ def encrypt(password)
40
+ Digest::SHA1.hexdigest "--#{salt}--#{password}--"
41
+ end
42
+
43
+ def remember_token?
44
+ remember_token_expires_at && Time.now.utc < remember_token_expires_at
45
+ end
46
+
47
+ def remember_me!
48
+ remember_me_until 2.weeks.from_now.utc
49
+ end
50
+
51
+ def remember_me_until(time)
52
+ self.update_attribute :remember_token_expires_at, time
53
+ self.update_attribute :remember_token, encrypt("#{email}--#{remember_token_expires_at}")
54
+ end
55
+
56
+ def forget_me!
57
+ self.update_attribute :remember_token_expires_at, nil
58
+ self.update_attribute :remember_token, nil
59
+ end
60
+ end
61
+
62
+ module ProtectedInstanceMethods
63
+ def initialize_salt
64
+ self.salt = Digest::SHA1.hexdigest("--#{Time.now.to_s}--#{email}--") if new_record?
65
+ end
66
+
67
+ def encrypt_password
68
+ return if password.blank?
69
+ self.crypted_password = encrypt(password)
70
+ end
71
+
72
+ def password_required?
73
+ crypted_password.blank? || !password.blank?
74
+ end
75
+ end
76
+
77
+ end
78
+ end
@@ -0,0 +1,82 @@
1
+ module Clearance
2
+ module SessionsControllerTest
3
+
4
+ def self.included(base)
5
+ base.class_eval do
6
+ context "Given a user" do
7
+ setup { @user = Factory :user }
8
+
9
+ should_filter :password
10
+
11
+ context "on GET to /sessions/new" do
12
+ setup { get :new }
13
+
14
+ should_respond_with :success
15
+ should_render_template :new
16
+ should_not_set_the_flash
17
+ should_have_form :action => "session_path",
18
+ :fields => { "session[email]" => :text,
19
+ "session[password]" => :password,
20
+ "session[remember_me]" => :checkbox }
21
+ end
22
+
23
+ context "a POST to #create with good credentials" do
24
+ setup do
25
+ post :create, :session => { :email => @user.email, :password => @user.password }
26
+ end
27
+
28
+ should_set_the_flash_to /success/i
29
+ should_redirect_to '@controller.send(:url_after_create)'
30
+ # TODO: should set session
31
+ end
32
+
33
+ context "a POST to #create with bad credentials" do
34
+ setup do
35
+ post :create, :session => { :email => @user.email, :password => "bad value" }
36
+ end
37
+
38
+ should_set_the_flash_to /bad/i
39
+ should_render_template :new
40
+ # TODO: should not set session
41
+ end
42
+
43
+ # TODO: two tests for remember me - success and failure
44
+ end
45
+
46
+ context "While logged out" do
47
+ setup { logout }
48
+
49
+ context "logging out again" do
50
+ setup { delete :destroy }
51
+ should_redirect_to '@controller.send(:url_after_destroy)'
52
+ end
53
+ end
54
+
55
+ logged_in_user_context do
56
+ context "a DELETE to #destroy without a cookie" do
57
+ setup { delete :destroy }
58
+
59
+ should_set_the_flash_to(/logged out/i)
60
+ should_redirect_to '@controller.send(:url_after_destroy)'
61
+ end
62
+
63
+ context 'a DELETE to #destroy with a cookie' do
64
+ setup do
65
+ cookies['auth_token'] = CGI::Cookie.new 'token', 'value'
66
+ delete :destroy
67
+ end
68
+
69
+ should 'delete the cookie' do
70
+ assert cookies['auth_token'].empty?
71
+ end
72
+
73
+ should 'delete the remember me token in users table' do
74
+ assert_nil @user.reload.remember_token
75
+ assert_nil @user.reload.remember_token_expires_at
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,113 @@
1
+ module Clearance
2
+ module UsersControllerTest
3
+
4
+ def self.included(base)
5
+ base.class_eval do
6
+ public_context do
7
+
8
+ context "on GET to /users/new" do
9
+ setup { get :new }
10
+ should_respond_with :success
11
+ should_render_template :new
12
+ should_not_set_the_flash
13
+ should_have_form :action => "users_path",
14
+ :method => :post,
15
+ :fields => { :email => :text,
16
+ :password => :password,
17
+ :password_confirmation => :password }
18
+ end
19
+
20
+ context "on POST to /users" do
21
+ setup do
22
+ post :create, :user => {
23
+ :email => Factory.next(:email),
24
+ :password => 'skerit',
25
+ :password_confirmation => 'skerit'
26
+ }
27
+ end
28
+
29
+ should_set_the_flash_to /created/i
30
+ should_redirect_to "@controller.send(:url_after_create)"
31
+ should_assign_to :user
32
+ should_change 'User.count', :by => 1
33
+ end
34
+
35
+ should_deny_access_on "get :edit, :id => 1", :redirect => "login_url"
36
+ should_deny_access_on "put :update, :id => 1", :redirect => "login_url"
37
+ should_deny_access_on "get :show, :id => 1", :redirect => "login_url"
38
+ should_deny_access_on "delete :destroy, :id => 1", :redirect => "login_url"
39
+
40
+ end
41
+
42
+ logged_in_user_context do
43
+
44
+ should_deny_access_on "get :new"
45
+ should_deny_access_on "post :create, :user => {}"
46
+ should_filter :password
47
+
48
+ context "viewing their account" do
49
+ context "on GET to /users/:id/show" do
50
+ setup { get :show, :id => @user.to_param }
51
+ should_respond_with :success
52
+ should_render_template :show
53
+ should_not_set_the_flash
54
+
55
+ should 'assign to @user' do
56
+ assert_equal @user, assigns(:user)
57
+ end
58
+ end
59
+
60
+ should_deny_access_on "delete :destroy, :id => @user.to_param"
61
+
62
+ context "on GET to /users/:id/edit" do
63
+ setup { get :edit, :id => @user.to_param }
64
+
65
+ should_respond_with :success
66
+ should_render_template :edit
67
+ should_not_set_the_flash
68
+ should_assign_to :user
69
+ should_have_form :action => "user_path(@user)",
70
+ :method => :put,
71
+ :fields => { :email => :text,
72
+ :password => :password,
73
+ :password_confirmation => :password }
74
+ end
75
+
76
+ context "on PUT to /users/:id" do
77
+ setup do
78
+ put :update,
79
+ :id => @user.to_param,
80
+ :user => { :email => "none@example.com" }
81
+ end
82
+ should_set_the_flash_to /updated/i
83
+ should_redirect_to "@controller.send(:url_after_update)"
84
+ should_assign_to :user
85
+ should "update the user's attributes" do
86
+ assert_equal "none@example.com", assigns(:user).email
87
+ end
88
+ end
89
+
90
+ context "on PUT to /users/:id with invalid attributes" do
91
+ setup { put :update, :id => @user.to_param, :user => {:email => ''} }
92
+ should_not_set_the_flash
93
+ should_assign_to :user
94
+ should_render_template 'edit'
95
+ should "display errors" do
96
+ assert_select '#errorExplanation'
97
+ end
98
+ end
99
+ end
100
+
101
+ context "trying to access another user's account" do
102
+ setup { @user = Factory :user }
103
+
104
+ should_deny_access_on "get :show, :id => @user.to_param", :flash => /cannot edit/i
105
+ should_deny_access_on "get :edit, :id => @user.to_param", :flash => /cannot edit/i
106
+ should_deny_access_on "put :update, :id => @user.to_param, :user => {}", :flash => /cannot edit/i
107
+ end
108
+ end
109
+ end
110
+ end
111
+
112
+ end
113
+ end
@@ -0,0 +1,100 @@
1
+ module Clearance
2
+ module TestHelper
3
+
4
+ def self.included(base)
5
+ base.class_eval do
6
+ include InstanceMethods
7
+ extend ClassMethods
8
+ end
9
+ end
10
+
11
+ module InstanceMethods
12
+ def login_as(user = nil)
13
+ user ||= Factory(:user)
14
+ @request.session[:user_id] = user.id
15
+ return user
16
+ end
17
+
18
+ def logout
19
+ @request.session[:user_id] = nil
20
+ end
21
+ end
22
+
23
+ module ClassMethods
24
+ def should_deny_access_on(command, opts = {})
25
+ opts[:redirect] ||= "root_url"
26
+
27
+ context "on #{command}" do
28
+ setup { eval command }
29
+ should_redirect_to opts[:redirect]
30
+ if opts[:flash]
31
+ should_set_the_flash_to opts[:flash]
32
+ else
33
+ should_not_set_the_flash
34
+ end
35
+ end
36
+ end
37
+
38
+ def should_filter(*keys)
39
+ keys.each do |key|
40
+ should "filter #{key}" do
41
+ assert @controller.respond_to?(:filter_parameters),
42
+ "The key #{key} is not filtered"
43
+ filtered = @controller.send(:filter_parameters, {key.to_s => key.to_s})
44
+ assert_equal '[FILTERED]', filtered[key.to_s],
45
+ "The key #{key} is not filtered"
46
+ end
47
+ end
48
+ end
49
+
50
+ # should_have_form :action => 'admin_users_path',
51
+ # :method => :get,
52
+ # :fields => { 'email' => :text }
53
+ # TODO: http_method should be pulled out
54
+ def should_have_form(opts)
55
+ model = self.name.gsub(/ControllerTest$/, '').singularize.downcase
56
+ model = model[model.rindex('::')+2..model.size] if model.include?('::')
57
+ http_method, hidden_http_method = form_http_method opts[:method]
58
+ should "have a #{model} form" do
59
+ assert_select "form[action=?][method=#{http_method}]", eval(opts[:action]) do
60
+ if hidden_http_method
61
+ assert_select "input[type=hidden][name=_method][value=#{hidden_http_method}]"
62
+ end
63
+ opts[:fields].each do |attribute, type|
64
+ attribute = attribute.is_a?(Symbol) ? "#{model}[#{attribute.to_s}]" : attribute
65
+ assert_select "input[type=#{type.to_s}][name=?]", attribute
66
+ end
67
+ assert_select "input[type=submit]"
68
+ end
69
+ end
70
+ end
71
+
72
+ def form_http_method(http_method)
73
+ http_method = http_method.nil? ? 'post' : http_method.to_s
74
+ if http_method == "post" || http_method == "get"
75
+ return http_method, nil
76
+ else
77
+ return "post", http_method
78
+ end
79
+ end
80
+
81
+ def logged_in_user_context(&blk)
82
+ context "A logged in user" do
83
+ setup do
84
+ @user = Factory :user
85
+ login_as @user
86
+ end
87
+ merge_block(&blk)
88
+ end
89
+ end
90
+
91
+ def public_context(&blk)
92
+ context "The public" do
93
+ setup { logout }
94
+ merge_block(&blk)
95
+ end
96
+ end
97
+ end
98
+
99
+ end
100
+ end
@@ -0,0 +1,186 @@
1
+ module Clearance
2
+ module UserTest
3
+
4
+ def self.included(base)
5
+ base.class_eval do
6
+ should_require_attributes :email, :password
7
+
8
+ should "require password validation on create" do
9
+ user = Factory.build(:user, :password => 'blah', :password_confirmation => 'boogidy')
10
+ assert !user.save
11
+ assert_match(/confirmation/i, user.errors.on(:password))
12
+ end
13
+
14
+ should "create a crypted_password on save" do
15
+ assert_not_nil Factory(:user, :crypted_password => nil).crypted_password
16
+ end
17
+
18
+ context 'updating a password' do
19
+ setup do
20
+ @user = Factory(:user)
21
+ assert_not_nil @user.crypted_password
22
+ @crypt = @user.crypted_password
23
+ assert_not_nil @user.salt
24
+ @salt = @user.salt
25
+ @user.password = 'a_new_password'
26
+ @user.password_confirmation = 'a_new_password'
27
+ assert @user.save
28
+ end
29
+
30
+ should 'update a crypted_password' do
31
+ @user.reload
32
+ assert @user.crypted_password != @crypt
33
+ end
34
+ end
35
+
36
+ context 'A user' do
37
+ setup do
38
+ @password = 'sekrit'
39
+ @salt = 'salt'
40
+ User.any_instance.stubs(:initialize_salt)
41
+ @user = Factory(:user, :password => @password, :salt => @salt)
42
+ end
43
+
44
+ should "require password validation on update" do
45
+ @user.update_attributes(:password => "blah", :password_confirmation => "boogidy")
46
+ assert !@user.save
47
+ assert_match(/confirmation/i, @user.errors.on(:password))
48
+ end
49
+
50
+ should_require_unique_attributes :email
51
+
52
+ context 'authenticating a user' do
53
+ context 'with good credentials' do
54
+ setup do
55
+ @result = User.authenticate @user.email, 'sekrit'
56
+ end
57
+
58
+ should 'return true' do
59
+ assert @result
60
+ end
61
+ end
62
+
63
+ context 'with bad credentials' do
64
+ setup do
65
+ @result = User.authenticate @user.email, 'horribly_wrong_password'
66
+ end
67
+
68
+ should 'return false' do
69
+ assert !@result
70
+ end
71
+ end
72
+ end
73
+
74
+ context 'authenticated?' do
75
+ context 'with good credentials' do
76
+ setup do
77
+ @result = @user.authenticated? @password
78
+ end
79
+
80
+ should 'return true' do
81
+ assert @result
82
+ end
83
+ end
84
+
85
+ context 'with bad credentials' do
86
+ setup do
87
+ @result = @user.authenticated? 'horribly_wrong_password'
88
+ end
89
+
90
+ should 'return false' do
91
+ assert !@result
92
+ end
93
+ end
94
+ end
95
+
96
+ context 'encrypt' do
97
+ setup do
98
+ @crypted = @user.encrypt(@password)
99
+ @expected = Digest::SHA1.hexdigest("--#{@salt}--#{@password}--")
100
+ end
101
+
102
+ should 'create a Hash using SHA1 encryption' do
103
+ assert_equal @expected, @crypted
104
+ assert_not_equal @password, @crypted
105
+ end
106
+ end
107
+
108
+ context 'remember_me!' do
109
+ setup do
110
+ assert_nil @user.remember_token
111
+ assert_nil @user.remember_token_expires_at
112
+ @user.remember_me!
113
+ end
114
+
115
+ should 'set the remember token and expiration date' do
116
+ assert_not_nil @user.remember_token
117
+ assert_not_nil @user.remember_token_expires_at
118
+ end
119
+
120
+ should 'remember_token?' do
121
+ assert @user.remember_token?
122
+ end
123
+
124
+ context 'forget_me!' do
125
+ setup do
126
+ @user.forget_me!
127
+ end
128
+
129
+ should 'unset the remember token and expiration date' do
130
+ assert_nil @user.remember_token
131
+ assert_nil @user.remember_token_expires_at
132
+ end
133
+
134
+ should 'not remember_token?' do
135
+ assert ! @user.remember_token?
136
+ end
137
+ end
138
+ end
139
+
140
+ context 'remember_token?' do
141
+ context 'when token expires in the future' do
142
+ setup do
143
+ @user.update_attribute :remember_token_expires_at, 2.weeks.from_now.utc
144
+ end
145
+
146
+ should 'be true' do
147
+ assert @user.remember_token?
148
+ end
149
+ end
150
+
151
+ context 'when token expired' do
152
+ setup do
153
+ @user.update_attribute :remember_token_expires_at, 2.weeks.ago.utc
154
+ end
155
+
156
+ should 'be false' do
157
+ assert ! @user.remember_token?
158
+ end
159
+ end
160
+ end
161
+
162
+ context "User.authenticate with a valid email and password" do
163
+ setup do
164
+ @found_user = User.authenticate @user.email, @user.password
165
+ end
166
+
167
+ should "find that user" do
168
+ assert_equal @user, @found_user
169
+ end
170
+ end
171
+
172
+ context "When sent authenticate with an invalid email and password" do
173
+ setup do
174
+ @found_user = User.authenticate "not", "valid"
175
+ end
176
+
177
+ should "find nothing" do
178
+ assert_nil @found_user
179
+ end
180
+ end
181
+ end
182
+
183
+ end
184
+ end
185
+ end
186
+ end
metadata ADDED
@@ -0,0 +1,68 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: thoughtbot-clearance
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.4
5
+ platform: ruby
6
+ authors:
7
+ - thoughtbot, inc.
8
+ - Dan Croak
9
+ - Josh Nichols
10
+ - Mike Breen
11
+ - Mike Burns
12
+ - Jason Morrison
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2008-09-24 00:00:00 -07:00
18
+ default_executable:
19
+ dependencies: []
20
+
21
+ description: Simple, complete Rails authentication scheme.
22
+ email: dcroak@thoughtbot.com
23
+ executables: []
24
+
25
+ extensions: []
26
+
27
+ extra_rdoc_files: []
28
+
29
+ files:
30
+ - README.textile
31
+ - clearance.gemspec
32
+ - lib/clearance.rb
33
+ - lib/clearance/app/controllers/application_controller.rb
34
+ - lib/clearance/app/models/model.rb
35
+ - lib/clearance/app/controllers/sessions_controller.rb
36
+ - lib/clearance/test/functionals/sessions_controller_test.rb
37
+ - lib/clearance/test/test_helper.rb
38
+ - lib/clearance/test/units/user_test.rb
39
+ - lib/clearance/app/controllers/users_controller.rb
40
+ - lib/clearance/test/functionals/users_controller_test.rb
41
+ has_rdoc: false
42
+ homepage: http://github.com/dancroak/clearance
43
+ post_install_message:
44
+ rdoc_options: []
45
+
46
+ require_paths:
47
+ - lib
48
+ required_ruby_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: "0"
53
+ version:
54
+ required_rubygems_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: "0"
59
+ version:
60
+ requirements: []
61
+
62
+ rubyforge_project:
63
+ rubygems_version: 1.2.0
64
+ signing_key:
65
+ specification_version: 2
66
+ summary: Simple, complete Rails authentication.
67
+ test_files: []
68
+