winton-authentication 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG ADDED
@@ -0,0 +1,74 @@
1
+ 2008-06-23 - Sean Huber (shuber@huberry.com)
2
+ * Renamed authentication_message/redirect_path to unauthenticated_message/redirect_path
3
+ * logged_in? now accepts a block that will yield when true
4
+
5
+ 2008-06-09 - Sean Huber (shuber@huberry.com)
6
+ * unauthenticated method stores request_uri in session[:return_to]
7
+
8
+ 2008-06-09 - Sean Huber (shuber@huberry.com)
9
+ * Removed accidently left over hard coded logic from password_required?
10
+
11
+ 2008-06-09 - Sean Huber (shuber@huberry.com)
12
+ * Reloading a model sets password_changed? to false
13
+
14
+ 2008-06-05 - Sean Huber (shuber@huberry.com)
15
+ * Changed email addresses in CHANGELOG
16
+
17
+ 2008-06-05 - Sean Huber (shuber@huberry.com)
18
+ * Added find_current_user functional tests
19
+
20
+ 2008-06-05 - Sean Huber (shuber@huberry.com)
21
+ * Renamed test files
22
+ * find_current_user always returns current_user
23
+
24
+ 2008-06-05 - Sean Huber (shuber@huberry.com)
25
+ * Renamed login_required to authentication_required
26
+
27
+ 2008-06-05 - Sean Huber (shuber@huberry.com)
28
+ * Renamed @searched_for_current_user to @queried_for_current_user
29
+
30
+ 2008-06-05 - Sean Huber (shuber@huberry.com)
31
+ * Added extra uses_authentication field options
32
+ * Updated README
33
+
34
+ 2008-06-05 - Sean Huber (shuber@huberry.com)
35
+ * current_user returns nil unless logged_in?
36
+ * Updated README
37
+
38
+ 2008-06-05 - Sean Huber (shuber@huberry.com)
39
+ * Updated README
40
+
41
+ 2008-06-05 - Sean Huber (shuber@huberry.com)
42
+ * Updated README
43
+
44
+ 2008-06-04 - Sean Huber (shuber@huberry.com)
45
+ * Updated README
46
+
47
+ 2008-06-04 - Sean Huber (shuber@huberry.com)
48
+ * Updated README
49
+
50
+ 2008-06-04 - Sean Huber (shuber@huberry.com)
51
+ * Controller no longer has to call uses_authentication - logic included automatically
52
+ * Updated README
53
+
54
+ 2008-06-04 - Sean Huber (shuber@huberry.com)
55
+ * Updated README
56
+
57
+ 2008-06-04 - Sean Huber (shuber@huberry.com)
58
+ * Updated README
59
+
60
+ 2008-06-04 - Sean Huber (shuber@huberry.com)
61
+ * Updated README
62
+
63
+ 2008-06-04 - Sean Huber (shuber@huberry.com)
64
+ * Added functional tests
65
+
66
+ 2008-06-04 - Sean Huber (shuber@huberry.com)
67
+ * Fixed functional test exception
68
+
69
+ 2008-06-04 - Sean Huber (shuber@huberry.com)
70
+ * Controller and model must call uses_authentication
71
+
72
+ 2008-06-04 - Sean Huber (shuber@huberry.com)
73
+ * Initial import from access_control_list repository
74
+ * Login field is configurable
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Sean Huber (shuber@huberry.com)
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,82 @@
1
+ Huberry::Authentication
2
+ =======================
3
+
4
+ A rails plugin that handles authentication
5
+
6
+
7
+ Installation
8
+ ------------
9
+
10
+ script/plugin install git://github.com/shuber/authentication.git
11
+
12
+
13
+ Example
14
+ -------
15
+
16
+ class User < ActiveRecord::Base
17
+ # Accepts an optional hash of options
18
+ # :login_field - The field to use for logins (e.g. username or email) (defaults to :email)
19
+ # :password_field - (defaults to :password)
20
+ # :hashed_password_field - (defaults to :hashed_password)
21
+ # :salt_field - (defaults to :salt)
22
+ uses_authentication :login_field => :username
23
+ end
24
+
25
+ class ApplicationController < ActionController::Base
26
+ # Set optional authentication options here
27
+ # self.authentication_model = The model that uses authentication (defaults to 'User')
28
+ # self.unauthenticated_message = The error flash message to set when unauthenticated (defaults to 'Login to continue')
29
+ # self.unauthenticated_redirect_path = The path to redirect to when unauthenticated (can be a symbol of a method) (defaults to '/')
30
+ end
31
+
32
+ class UsersController < ApplicationController
33
+ before_filter :login_required, :only => [:index]
34
+
35
+ def index
36
+ render :text => 'test'
37
+ end
38
+ end
39
+
40
+
41
+ Controller Methods
42
+ ------------------
43
+
44
+ # Returns the current user or nil if a user is not logged in
45
+ current_user
46
+
47
+ # Checks if the current user is authenticated (optionaly accepts a block that is yielded when true)
48
+ logged_in?
49
+
50
+ # Login a user
51
+ login(user)
52
+
53
+ # A before filter to require authentication - redirects to the controller class's authentication_redirect_path if unauthenticated
54
+ login_required
55
+
56
+ # Logout the current user
57
+ logout
58
+
59
+
60
+ Model Methods
61
+ -------------
62
+
63
+ # Class method that authenticates a user based on a login and password - returns a user instance or false
64
+ User.authenticate(login, password)
65
+
66
+ # Checks if the password passed to it matches the current user instance's password
67
+ authenticated?(password)
68
+
69
+ # Checks if the current user instance's password has just been changed
70
+ password_changed?
71
+
72
+ # Resets the password - will generate a new random password if one is not specified
73
+ reset_password(new_password = nil)
74
+
75
+ # Resets the password and saves - will generate a new random password if one is not specified
76
+ reset_password!(new_password = nil)
77
+
78
+
79
+ Contact
80
+ -------
81
+
82
+ Problems, comments, and suggestions all welcome: [shuber@huberry.com](mailto:shuber@huberry.com)
data/Rakefile ADDED
@@ -0,0 +1,22 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+
5
+ desc 'Default: run the authentication tests'
6
+ task :default => :test
7
+
8
+ desc 'Test the authentication plugin.'
9
+ Rake::TestTask.new(:test) do |t|
10
+ t.libs << 'lib'
11
+ t.pattern = 'test/*_test.rb'
12
+ t.verbose = true
13
+ end
14
+
15
+ desc 'Generate documentation for the authentication plugin.'
16
+ Rake::RDocTask.new(:rdoc) do |rdoc|
17
+ rdoc.rdoc_dir = 'rdoc'
18
+ rdoc.title = 'Authentication'
19
+ rdoc.options << '--line-numbers' << '--inline-source'
20
+ rdoc.rdoc_files.include('README')
21
+ rdoc.rdoc_files.include('lib/**/*.rb')
22
+ end
data/init.rb ADDED
@@ -0,0 +1,7 @@
1
+ require 'huberry/authentication/controller_methods'
2
+ require 'huberry/authentication/model_methods'
3
+
4
+ ActionController::Base.extend Huberry::Authentication::ControllerMethods
5
+ ActiveRecord::Base.extend Huberry::Authentication::ModelMethods
6
+
7
+ $:.unshift File.dirname(__FILE__) + '/lib'
@@ -0,0 +1,61 @@
1
+ module Huberry
2
+ module Authentication
3
+ module ControllerMethods
4
+ def self.extended(base)
5
+ base.class_eval do
6
+ include InstanceMethods
7
+
8
+ cattr_accessor :authentication_model, :unauthenticated_message, :unauthenticated_redirect_path
9
+ self.authentication_model = 'User'
10
+ self.unauthenticated_message = 'Login to continue'
11
+ self.unauthenticated_redirect_path = '/'
12
+
13
+ attr_accessor :current_user
14
+ helper_method :current_user, :logged_in?
15
+ end
16
+ end
17
+
18
+ module InstanceMethods
19
+ protected
20
+
21
+ def authentication_required
22
+ unauthenticated unless logged_in?
23
+ end
24
+
25
+ def find_current_user(force_query = false)
26
+ if @queried_for_current_user.nil? || force_query
27
+ @queried_for_current_user = true
28
+ self.current_user = self.class.authentication_model.to_s.constantize.find(session[:user_id]) rescue nil
29
+ end
30
+ self.current_user
31
+ end
32
+
33
+ def logged_in?
34
+ find_current_user if self.current_user.nil?
35
+ if self.current_user.nil?
36
+ block_given? ? nil : false
37
+ else
38
+ block_given? ? yield : true
39
+ end
40
+ end
41
+
42
+ def login(user)
43
+ self.current_user = user
44
+ session[:user_id] = user.id
45
+ end
46
+
47
+ def logout
48
+ self.current_user = nil
49
+ session[:user_id] = nil
50
+ end
51
+
52
+ def unauthenticated
53
+ session[:return_to] = request.request_uri
54
+ flash[:error] = self.class.unauthenticated_message.to_s
55
+ redirect_to respond_to?(self.class.unauthenticated_redirect_path) ? send(self.class.unauthenticated_redirect_path) : self.class.unauthenticated_redirect_path.to_s
56
+ false
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,84 @@
1
+ require 'digest/sha2'
2
+
3
+ module Huberry
4
+ module Authentication
5
+ module ModelMethods
6
+ def uses_authentication(options = {})
7
+ extend ClassMethods
8
+ include InstanceMethods
9
+
10
+ cattr_accessor :hashed_password_field, :login_field, :password_field, :salt_field
11
+ self.hashed_password_field = options[:hashed_password_field] || :hashed_password
12
+ self.login_field = options[:login_field] || :email
13
+ self.password_field = options[:password_field] || :password
14
+ self.salt_field = options[:salt_field] || :salt
15
+
16
+ attr_accessor self.password_field, "#{self.password_field}_confirmation".to_sym
17
+
18
+ validates_presence_of self.login_field
19
+ validates_presence_of self.password_field, :if => :password_required?
20
+ validates_confirmation_of self.password_field, :if => :password_required?
21
+
22
+ before_save :hash_password
23
+
24
+ alias_method_chain :reload, :authentication
25
+ end
26
+ end
27
+
28
+ module ClassMethods
29
+ def authenticate(login, password)
30
+ user = send("find_by_#{self.login_field}", login)
31
+ user && user.authenticated?(password) ? user : false
32
+ end
33
+
34
+ def digest(str)
35
+ Digest::SHA256.hexdigest(str.to_s)
36
+ end
37
+ end
38
+
39
+ module InstanceMethods
40
+ def authenticated?(password)
41
+ send(self.class.hashed_password_field) == self.class.digest(password.to_s + send(self.class.salt_field).to_s)
42
+ end
43
+
44
+ def password_changed?
45
+ !!@password_changed
46
+ end
47
+
48
+ def reload_with_authentication(*args)
49
+ reload_without_authentication(*args)
50
+ @password_changed = false
51
+ end
52
+
53
+ def reset_password(new_password = nil)
54
+ new_password = generate_salt[0..7] if new_password.blank?
55
+ send("#{self.class.salt_field}=", nil)
56
+ send("#{self.class.password_field}=", new_password)
57
+ send("#{self.class.password_field}_confirmation=", new_password)
58
+ @password_changed = true
59
+ end
60
+
61
+ def reset_password!(new_password = nil)
62
+ reset_password(new_password)
63
+ save
64
+ end
65
+
66
+ protected
67
+
68
+ def generate_salt
69
+ self.class.digest("--#{Time.now}--#{rand}--")
70
+ end
71
+
72
+ def hash_password
73
+ if password_changed? || password_required?
74
+ send("#{self.class.salt_field}=", generate_salt) if send("#{self.class.salt_field}").blank?
75
+ send("#{self.class.hashed_password_field}=", self.class.digest(send(self.class.password_field).to_s + send(self.class.salt_field).to_s))
76
+ end
77
+ end
78
+
79
+ def password_required?
80
+ send(self.class.hashed_password_field).blank? || !send(self.class.password_field).blank?
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,114 @@
1
+ require File.dirname(__FILE__) + '/init'
2
+
3
+ class TestController < ActionController::Base
4
+ before_filter :authentication_required, :only => :login_required
5
+
6
+ def login_required
7
+ render :text => 'test'
8
+ end
9
+
10
+ def login_not_required
11
+ render :text => 'test'
12
+ end
13
+
14
+ def rescue_action(e)
15
+ raise e
16
+ end
17
+ end
18
+
19
+ ActionController::Routing::Routes.append do |map|
20
+ map.connect 'login_required', :controller => 'test', :action => 'login_required'
21
+ map.connect 'login_not_required', :controller => 'test', :action => 'login_not_required'
22
+ end
23
+
24
+ class ControllerTest < Test::Unit::TestCase
25
+
26
+ def setup
27
+ create_users_table
28
+ @user = create_user
29
+
30
+ @controller = TestController.new
31
+ @request = ActionController::TestRequest.new
32
+ @response = ActionController::TestResponse.new
33
+
34
+ @controller.instance_variable_set('@_session', @request.session)
35
+ end
36
+
37
+ def teardown
38
+ drop_all_tables
39
+ end
40
+
41
+ def test_logged_in?
42
+ assert !@controller.send(:logged_in?)
43
+
44
+ @controller.send :login, @user
45
+ assert @controller.send(:logged_in?)
46
+
47
+ @controller.send :logout
48
+ assert !@controller.send(:logged_in?)
49
+ end
50
+
51
+ def test_logged_in_yields_block_on_true
52
+ assert_equal nil, @controller.send(:logged_in?) { 'some_value' }
53
+
54
+ @controller.send :login, @user
55
+ assert_equal 'some_value', @controller.send(:logged_in?) { 'some_value' }
56
+ end
57
+
58
+ def test_current_user
59
+ assert_nil @controller.send(:current_user)
60
+
61
+ @controller.send :login, @user
62
+ assert_equal @user, @controller.send(:current_user)
63
+
64
+ @controller.send :logout
65
+ assert_nil @controller.send(:current_user)
66
+ end
67
+
68
+ def test_should_require_login
69
+ get :login_required
70
+ assert_response :redirect
71
+ assert flash.has_key?(:error)
72
+ assert_equal @controller.unauthenticated_message, flash[:error]
73
+ assert_redirected_to '/'
74
+ end
75
+
76
+ def test_should_not_require_login
77
+ get :login_not_required
78
+ assert_response :success
79
+ end
80
+
81
+ def test_should_login_and_get_authentication_required
82
+ @controller.send :login, @user
83
+ get :login_required
84
+ assert_response :success
85
+ end
86
+
87
+ def test_find_current_user_with_valid_user
88
+ @request.session[:user_id] = @user.id
89
+ assert_equal @user, @controller.send(:find_current_user)
90
+ end
91
+
92
+ def test_find_current_user_with_nil_user
93
+ assert_nil @controller.send(:find_current_user)
94
+ end
95
+
96
+ def test_find_current_user_with_invalid_user
97
+ @request.session[:user_id] = 2
98
+ assert_nil @controller.send(:find_current_user)
99
+ end
100
+
101
+ def test_find_current_user_force_query
102
+ assert_nil @controller.send(:find_current_user)
103
+
104
+ @request.session[:user_id] = @user.id
105
+ assert_nil @controller.send(:find_current_user)
106
+ assert_equal @user, @controller.send(:find_current_user, true)
107
+ end
108
+
109
+ def test_unauthenticated_sets_return_to_session_variable
110
+ get :login_required
111
+ assert_equal @controller.session[:return_to], '/login_required'
112
+ end
113
+
114
+ end
@@ -0,0 +1,99 @@
1
+ require File.dirname(__FILE__) + '/init'
2
+
3
+ class ModelTest < Test::Unit::TestCase
4
+
5
+ def setup
6
+ create_users_table
7
+ end
8
+
9
+ def teardown
10
+ drop_all_tables
11
+ end
12
+
13
+ def test_should_create_user
14
+ assert_difference 'User.count' do
15
+ create_user
16
+ end
17
+ end
18
+
19
+ def test_should_not_create_without_email
20
+ assert_no_difference 'User.count' do
21
+ @user = create_user :email => ''
22
+ end
23
+ assert @user.errors.on(:email)
24
+ end
25
+
26
+ def test_should_not_create_without_password
27
+ assert_no_difference 'User.count' do
28
+ @user = create_user :password => ''
29
+ end
30
+ assert @user.errors.on(:password)
31
+ end
32
+
33
+ def test_should_not_create_without_password_confirmation
34
+ assert_no_difference 'User.count' do
35
+ @user = create_user :password_confirmation => ''
36
+ end
37
+ assert @user.errors.on(:password)
38
+ end
39
+
40
+ def test_should_generate_salt_on_create
41
+ @user = create_user
42
+ assert !@user.salt.blank?
43
+ end
44
+
45
+ def test_should_hash_password_on_create
46
+ @user = create_user
47
+ assert !@user.hashed_password.blank?
48
+ end
49
+
50
+ def test_should_destroy
51
+ @user = create_user
52
+ assert_difference 'User.count', -1 do
53
+ @user.destroy
54
+ end
55
+ end
56
+
57
+ def test_should_authenticate
58
+ @user = create_user
59
+ assert_equal @user, User.authenticate(@user.email, @user.password)
60
+ end
61
+
62
+ def test_should_not_authenticate_with_invalid_email
63
+ @user = create_user
64
+ assert !User.authenticate('invalid', @user.password)
65
+ end
66
+
67
+ def test_should_not_authenticate_with_invalid_password
68
+ @user = create_user
69
+ assert !User.authenticate(@user.email, 'invalid')
70
+ end
71
+
72
+ def test_should_reset_password
73
+ @user = create_user
74
+ assert !@user.password_changed?
75
+ old_password = @user.password
76
+
77
+ @user.reset_password('new_password')
78
+ assert_not_equal 'new_password', old_password
79
+ assert_equal 'new_password', @user.password
80
+ assert @user.password_changed?
81
+ end
82
+
83
+ def test_should_reset_password!
84
+ @user = create_user
85
+ assert @user.reset_password!('new_password')
86
+ end
87
+
88
+ def test_reload_should_set_password_changed_to_false
89
+ @user = create_user
90
+ assert !@user.password_changed?
91
+
92
+ @user.reset_password!('new_password')
93
+ assert @user.password_changed?
94
+
95
+ @user.reload
96
+ assert !@user.password_changed?
97
+ end
98
+
99
+ end
@@ -0,0 +1,25 @@
1
+ module TableTestHelper
2
+
3
+ def create_users_table
4
+ silence_stream(STDOUT) do
5
+ ActiveRecord::Schema.define(:version => 1) do
6
+ create_table :users do |t|
7
+ t.string :email
8
+ t.string :hashed_password
9
+ t.string :salt
10
+ end
11
+ end
12
+ end
13
+ end
14
+
15
+ def drop_all_tables
16
+ ActiveRecord::Base.connection.tables.each do |table|
17
+ drop_table(table)
18
+ end
19
+ end
20
+
21
+ def drop_table(table)
22
+ ActiveRecord::Base.connection.drop_table(table)
23
+ end
24
+
25
+ end
@@ -0,0 +1,11 @@
1
+ module UserTestHelper
2
+
3
+ def create_user(options = {})
4
+ User.create(valid_user_hash(options))
5
+ end
6
+
7
+ def valid_user_hash(options = {})
8
+ { :email => 'test@test.com', :password => 'test', :password_confirmation => 'test' }.merge(options)
9
+ end
10
+
11
+ end
data/test/init.rb ADDED
@@ -0,0 +1,45 @@
1
+ $:.reject! { |path| path.include? 'TextMate' }
2
+ require 'test/unit'
3
+
4
+ # Require and include test helpers
5
+ #
6
+ Dir[File.join(File.dirname(__FILE__), 'helpers', '*_test_helper.rb')].each do |helper|
7
+ require helper
8
+ /(.*?)_test_helper\.rb/.match File.basename(helper)
9
+ class_name = $1.split('_').collect{ |name| name.downcase.capitalize }.join('') + 'TestHelper'
10
+ Test::Unit::TestCase.send :include, Object.const_get(class_name) if Object.const_defined?(class_name)
11
+ end
12
+
13
+ # Load ActiveRecord
14
+ #
15
+ require 'rubygems'
16
+ gem 'activerecord'
17
+ require 'active_record'
18
+ ActiveRecord::Base.establish_connection :adapter => 'sqlite3', :dbfile => ':memory:'
19
+
20
+ # Load ActionPack
21
+ #
22
+ gem 'actionpack'
23
+ require 'action_pack'
24
+ require 'action_controller'
25
+ require 'action_controller/routing'
26
+ require 'action_controller/assertions'
27
+ require 'action_controller/test_process'
28
+
29
+ # Routing
30
+ #
31
+ class ActionController::Routing::RouteSet
32
+ def append
33
+ yield Mapper.new(self)
34
+ install_helpers
35
+ end
36
+ end
37
+
38
+ # Require the main init.rb for the plugin
39
+ #
40
+ require File.join(File.dirname(File.dirname(__FILE__)), 'init')
41
+
42
+ # Basic user class
43
+ class User < ActiveRecord::Base
44
+ uses_authentication
45
+ end
metadata ADDED
@@ -0,0 +1,63 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: winton-authentication
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Sean Huber
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-08-16 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: A rails plugin that handles authentication
17
+ email: shuber@huberry.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files: []
23
+
24
+ files:
25
+ - CHANGELOG
26
+ - init.rb
27
+ - lib/huberry/authentication/controller_methods.rb
28
+ - lib/huberry/authentication/model_methods.rb
29
+ - MIT-LICENSE
30
+ - Rakefile
31
+ - README.markdown
32
+ has_rdoc: false
33
+ homepage: http://github.com/shuber/authentication
34
+ post_install_message:
35
+ rdoc_options: []
36
+
37
+ require_paths:
38
+ - lib
39
+ required_ruby_version: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: "0"
44
+ version:
45
+ required_rubygems_version: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: "0"
50
+ version:
51
+ requirements: []
52
+
53
+ rubyforge_project:
54
+ rubygems_version: 1.2.0
55
+ signing_key:
56
+ specification_version: 2
57
+ summary: A rails plugin that handles authentication
58
+ test_files:
59
+ - test/_controller_test.rb
60
+ - test/_model_test.rb
61
+ - test/helpers/table_test_helper.rb
62
+ - test/helpers/user_test_helper.rb
63
+ - test/init.rb