shuber-authentication 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/CHANGELOG ADDED
@@ -0,0 +1,35 @@
1
+ 2009-01-08 - Sean Huber (shuber@huberry.com)
2
+ * Clean up CHANGELOG
3
+ * Convert to soft tabs
4
+ * Remove .gitignore
5
+ * Gemify
6
+ * Remove logic that rejected TextMate paths to fix the old builder.rb conflict
7
+
8
+ 2008-06-23 - Sean Huber (shuber@huberry.com)
9
+ * Renamed authentication_message/redirect_path to unauthenticated_message/redirect_path
10
+ * logged_in? now accepts a block that will yield when true
11
+
12
+ 2008-06-09 - Sean Huber (shuber@huberry.com)
13
+ * Reloading a model sets password_changed? to false
14
+ * Removed accidently left over hard coded logic from password_required?
15
+ * unauthenticated method stores request_uri in session[:return_to]
16
+
17
+ 2008-06-05 - Sean Huber (shuber@huberry.com)
18
+ * Updated README
19
+ * current_user returns nil unless logged_in?
20
+ * Added extra uses_authentication field options
21
+ * Renamed @searched_for_current_user to @queried_for_current_user
22
+ * Renamed login_required to authentication_required
23
+ * find_current_user always returns current_user
24
+ * Renamed test files
25
+ * Added find_current_user functional tests
26
+ * Changed email addresses in CHANGELOG
27
+
28
+ 2008-06-04 - Sean Huber (shuber@huberry.com)
29
+ * Initial import from access_control_list repository
30
+ * Login field is configurable
31
+ * Controller and model must call uses_authentication
32
+ * Fixed functional test exception
33
+ * Added functional tests
34
+ * Updated README
35
+ * Controller no longer has to call uses_authentication - logic included automatically
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,84 @@
1
+ authentication
2
+ ==============
3
+
4
+ A rails gem/plugin that handles authentication
5
+
6
+
7
+ Installation
8
+ ------------
9
+
10
+ gem install shuber-authentication --source http://gems.github.com
11
+ OR
12
+ script/plugin install git://github.com/shuber/authentication.git
13
+
14
+
15
+ Example
16
+ -------
17
+
18
+ class User < ActiveRecord::Base
19
+ # Accepts an optional hash of options
20
+ # :login_field - The field to use for logins (e.g. username or email) (defaults to :email)
21
+ # :password_field - (defaults to :password)
22
+ # :hashed_password_field - (defaults to :hashed_password)
23
+ # :salt_field - (defaults to :salt)
24
+ uses_authentication :login_field => :username
25
+ end
26
+
27
+ class ApplicationController < ActionController::Base
28
+ # Set optional authentication options here
29
+ # self.authentication_model = The model that uses authentication (defaults to 'User')
30
+ # self.unauthenticated_message = The error flash message to set when unauthenticated (defaults to 'Login to continue')
31
+ # self.unauthenticated_redirect_path = The path to redirect to when unauthenticated (can be a symbol of a method) (defaults to '/')
32
+ end
33
+
34
+ class UsersController < ApplicationController
35
+ before_filter :authentication_required, :only => [:index]
36
+
37
+ def index
38
+ render :text => 'test'
39
+ end
40
+ end
41
+
42
+
43
+ Controller Methods
44
+ ------------------
45
+
46
+ # Returns the current user or nil if a user is not logged in
47
+ current_user
48
+
49
+ # Checks if the current user is authenticated (optionaly accepts a block that is yielded when true)
50
+ logged_in?
51
+
52
+ # Login a user
53
+ login(user)
54
+
55
+ # A before filter to require authentication - redirects to the controller class's authentication_redirect_path if unauthenticated
56
+ authentication_required
57
+
58
+ # Logout the current user
59
+ logout
60
+
61
+
62
+ Model Methods
63
+ -------------
64
+
65
+ # Class method that authenticates a user based on a login and password - returns a user instance or false
66
+ User.authenticate(login, password)
67
+
68
+ # Checks if the password passed to it matches the current user instance's password
69
+ authenticated?(password)
70
+
71
+ # Checks if the current user instance's password has just been changed
72
+ password_changed?
73
+
74
+ # Resets the password - will generate a new random password if one is not specified
75
+ reset_password(new_password = nil)
76
+
77
+ # Resets the password and saves - will generate a new random password if one is not specified
78
+ reset_password!(new_password = nil)
79
+
80
+
81
+ Contact
82
+ -------
83
+
84
+ 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 gem/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 gem/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.markdown')
21
+ rdoc.rdoc_files.include('lib/**/*.rb')
22
+ end
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'authentication'
@@ -0,0 +1,5 @@
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
@@ -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,44 @@
1
+ require 'test/unit'
2
+
3
+ # Require and include test helpers
4
+ #
5
+ Dir[File.join(File.dirname(__FILE__), 'helpers', '*_test_helper.rb')].each do |helper|
6
+ require helper
7
+ /(.*?)_test_helper\.rb/.match File.basename(helper)
8
+ class_name = $1.split('_').collect{ |name| name.downcase.capitalize }.join('') + 'TestHelper'
9
+ Test::Unit::TestCase.send :include, Object.const_get(class_name) if Object.const_defined?(class_name)
10
+ end
11
+
12
+ # Load ActiveRecord
13
+ #
14
+ require 'rubygems'
15
+ gem 'activerecord'
16
+ require 'active_record'
17
+ ActiveRecord::Base.establish_connection :adapter => 'sqlite3', :dbfile => ':memory:'
18
+
19
+ # Load ActionPack
20
+ #
21
+ gem 'actionpack'
22
+ require 'action_pack'
23
+ require 'action_controller'
24
+ require 'action_controller/routing'
25
+ require 'action_controller/assertions'
26
+ require 'action_controller/test_process'
27
+
28
+ # Routing
29
+ #
30
+ class ActionController::Routing::RouteSet
31
+ def append
32
+ yield Mapper.new(self)
33
+ install_helpers
34
+ end
35
+ end
36
+
37
+ # Require the main init.rb for the plugin
38
+ #
39
+ require File.join(File.dirname(File.dirname(__FILE__)), 'init')
40
+
41
+ # Basic user class
42
+ class User < ActiveRecord::Base
43
+ uses_authentication
44
+ end
metadata ADDED
@@ -0,0 +1,67 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: shuber-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: 2009-01-08 00:00:00 -08:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: A rails gem/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/authentication.rb
28
+ - lib/huberry/authentication/controller_methods.rb
29
+ - lib/huberry/authentication/model_methods.rb
30
+ - MIT-LICENSE
31
+ - Rakefile
32
+ - README.markdown
33
+ - test/helpers/table_test_helper.rb
34
+ - test/helpers/user_test_helper.rb
35
+ - test/init.rb
36
+ has_rdoc: false
37
+ homepage: http://github.com/shuber/authentication
38
+ post_install_message:
39
+ rdoc_options:
40
+ - --line-numbers
41
+ - --inline-source
42
+ - --main
43
+ - README.markdown
44
+ require_paths:
45
+ - lib
46
+ required_ruby_version: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: "0"
51
+ version:
52
+ required_rubygems_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: "0"
57
+ version:
58
+ requirements: []
59
+
60
+ rubyforge_project:
61
+ rubygems_version: 1.2.0
62
+ signing_key:
63
+ specification_version: 2
64
+ summary: A rails gem/plugin that handles authentication
65
+ test_files:
66
+ - test/_controller_test.rb
67
+ - test/_model_test.rb