minimalist_authentication 0.5

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.
Files changed (56) hide show
  1. data/.gitignore +5 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README +45 -0
  4. data/Rakefile +35 -0
  5. data/init.rb +2 -0
  6. data/lib/app/views/sessions/_form.html.erb +12 -0
  7. data/lib/app/views/sessions/new.html.erb +1 -0
  8. data/lib/minimalist/authentication.rb +124 -0
  9. data/lib/minimalist/authorization.rb +49 -0
  10. data/lib/minimalist/sessions.rb +59 -0
  11. data/lib/minimalist/test_helper.rb +12 -0
  12. data/lib/minimalist/version.rb +3 -0
  13. data/lib/minimalist_authentication.rb +4 -0
  14. data/lib/tasks/minimalist_authentication_tasks.rake +4 -0
  15. data/minimalist_authentication.gemspec +15 -0
  16. data/test/.gitignore +1 -0
  17. data/test/authentication_test.rb +99 -0
  18. data/test/authorization_test.rb +77 -0
  19. data/test/factories.rb +8 -0
  20. data/test/rails_root/Gemfile +33 -0
  21. data/test/rails_root/Gemfile.lock +79 -0
  22. data/test/rails_root/README +256 -0
  23. data/test/rails_root/Rakefile +7 -0
  24. data/test/rails_root/app/controllers/application_controller.rb +5 -0
  25. data/test/rails_root/app/controllers/sessions_controller.rb +3 -0
  26. data/test/rails_root/app/helpers/application_helper.rb +2 -0
  27. data/test/rails_root/app/models/user.rb +3 -0
  28. data/test/rails_root/app/views/layouts/application.html.erb +14 -0
  29. data/test/rails_root/config.ru +4 -0
  30. data/test/rails_root/config/application.rb +42 -0
  31. data/test/rails_root/config/boot.rb +13 -0
  32. data/test/rails_root/config/database.yml +22 -0
  33. data/test/rails_root/config/environment.rb +5 -0
  34. data/test/rails_root/config/environments/development.rb +26 -0
  35. data/test/rails_root/config/environments/production.rb +49 -0
  36. data/test/rails_root/config/environments/test.rb +35 -0
  37. data/test/rails_root/config/initializers/backtrace_silencers.rb +7 -0
  38. data/test/rails_root/config/initializers/inflections.rb +10 -0
  39. data/test/rails_root/config/initializers/mime_types.rb +5 -0
  40. data/test/rails_root/config/initializers/secret_token.rb +7 -0
  41. data/test/rails_root/config/initializers/session_store.rb +8 -0
  42. data/test/rails_root/config/locales/en.yml +5 -0
  43. data/test/rails_root/config/routes.rb +5 -0
  44. data/test/rails_root/db/.gitignore +2 -0
  45. data/test/rails_root/db/schema.rb +21 -0
  46. data/test/rails_root/db/seeds.rb +7 -0
  47. data/test/rails_root/doc/README_FOR_APP +2 -0
  48. data/test/rails_root/lib/tasks/.gitkeep +0 -0
  49. data/test/rails_root/log/.gitignore +1 -0
  50. data/test/rails_root/log/.gitkeep +0 -0
  51. data/test/rails_root/script/rails +6 -0
  52. data/test/rails_root/test/performance/browsing_test.rb +9 -0
  53. data/test/rails_root/test/test_helper.rb +13 -0
  54. data/test/sessions_test.rb +30 -0
  55. data/test/test_helper.rb +11 -0
  56. metadata +161 -0
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ coverage
2
+ .svn
3
+ .loadpath
4
+ .project
5
+ .redcar
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 'WWIDEA, INC'
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 ADDED
@@ -0,0 +1,45 @@
1
+ MinimalistAuthentication
2
+ ========================
3
+
4
+ A Rails authentication plugin that takes a minimalist approach. It is designed to be simple to understand, use, and modify for your application.
5
+
6
+ This plugin was largely inspired by the restful-authentication plugin (http://github.com/technoweenie/restful-authentication/tree/master). I selected the essential methods for password based authentication, reorganized them, trimmed them down when possible, added a couple of features, and resisted the urge to start adding more.
7
+
8
+
9
+ Installation
10
+ ============
11
+ script/plugin install git://github.com/aaron/minimalist_authentication.git
12
+
13
+ ruby script/generate scaffold user active:boolean email:string crypted_password:string salt:string using_digest_version:integer last_logged_in_at:datetime
14
+
15
+
16
+ Example
17
+ =======
18
+
19
+ app/models/user.rb
20
+ class User < ActiveRecord::Base
21
+ include Minimalist::Authentication
22
+ end
23
+
24
+ app/controllers/application.rb
25
+ class ApplicationController < ActionController::Base
26
+ include Minimalist::Authorization
27
+
28
+ # Lock down everything by default
29
+ # use skip_before_filter to open up sepecific actions
30
+ prepend_before_filter :authorization_required
31
+ end
32
+
33
+ app/controllers/sessions_controller.rb
34
+ class SessionsController < ApplicationController
35
+ include Minimalist::Sessions
36
+ skip_before_filter :authorization_required, :only => [:new, :create]
37
+ end
38
+
39
+ test/test_helper.rb
40
+ class Test::Unit::TestCase
41
+ include Minimalist::TestHelper
42
+ end
43
+
44
+
45
+ Copyright (c) 2009 Aaron Baldwin, released under the MIT license
data/Rakefile ADDED
@@ -0,0 +1,35 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+
5
+ desc 'Default: run unit tests.'
6
+ task :default => :test
7
+
8
+ desc 'Test the minimalist_authentication plugin.'
9
+ Rake::TestTask.new(:test) do |t|
10
+ t.libs << 'lib'
11
+ t.libs << 'test'
12
+ t.pattern = 'test/**/*_test.rb'
13
+ t.verbose = true
14
+ end
15
+
16
+ desc 'Generate documentation for the minimalist_authentication plugin.'
17
+ Rake::RDocTask.new(:rdoc) do |rdoc|
18
+ rdoc.rdoc_dir = 'rdoc'
19
+ rdoc.title = 'MinimalistAuthentication'
20
+ rdoc.options << '--line-numbers' << '--inline-source'
21
+ rdoc.rdoc_files.include('README')
22
+ rdoc.rdoc_files.include('lib/**/*.rb')
23
+ end
24
+
25
+ desc 'Measures test coverage using rcov'
26
+ task :rcov do
27
+ rm_f "coverage"
28
+ rcov = "rcov --rails --text-summary -Ilib --exclude /gems/,/app/,/Library/"
29
+ system("#{rcov} --html #{Dir.glob('test/**/*_test.rb').join(' ')}")
30
+ if PLATFORM['darwin'] #Mac
31
+ system("open coverage/index.html")
32
+ elsif PLATFORM[/linux/] #Ubuntu, etc.
33
+ system("/etc/alternatives/x-www-browser coverage/index.html")
34
+ end
35
+ end
data/init.rb ADDED
@@ -0,0 +1,2 @@
1
+ # Include hook code here
2
+ require 'minimalist_authentication'
@@ -0,0 +1,12 @@
1
+ <%= form_tag session_path do %>
2
+ <div>
3
+ <%= label_tag 'email' %>
4
+ <%= text_field_tag 'email', @email %>
5
+ </div>
6
+ <div>
7
+ <%= label_tag 'password' %>
8
+ <%= password_field_tag 'password', nil %>
9
+ </div>
10
+
11
+ <p><%= submit_tag 'Log in', :class =>'submit' %></p>
12
+ <% end %>
@@ -0,0 +1 @@
1
+ <%= render :partial => 'form' %>
@@ -0,0 +1,124 @@
1
+ require 'digest/sha1'
2
+
3
+ module Minimalist
4
+ module Authentication
5
+ GUEST_USER_EMAIL = 'guest'
6
+ PREFERRED_DIGEST_VERSION = 2
7
+
8
+ def self.included( base )
9
+ base.extend(ClassMethods)
10
+ base.class_eval do
11
+ include InstanceMethods
12
+
13
+ attr_accessor :password
14
+ before_save :encrypt_password
15
+
16
+ validates_presence_of :email, :if => :validate_email_presence?
17
+ validates_uniqueness_of :email, :if => :validate_email_uniqueness?
18
+ validates_format_of :email, :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i, :if => :validate_email_format?
19
+ validates_presence_of :password, :if => :password_required?
20
+ validates_confirmation_of :password, :if => :password_required?
21
+ validates_length_of :password, :within => 6..40, :if => :password_required?
22
+
23
+ scope :active, :conditions => {:active => true}
24
+ end
25
+ end
26
+
27
+ module ClassMethods
28
+ def authenticate(email, password)
29
+ return if email.blank? || password.blank?
30
+ user = active.first(:conditions => {:email => email})
31
+ return unless user && user.authenticated?(password)
32
+ return user
33
+ end
34
+
35
+ def secure_digest(string, salt, version = 1)
36
+ case version
37
+ when 0 then Digest::MD5.hexdigest(string.to_s)
38
+ when 1 then Digest::SHA1.hexdigest("#{string}--#{salt}")
39
+ when 2 then Digest::SHA2.hexdigest("#{string}#{salt}", 512)
40
+ end
41
+ end
42
+
43
+ def make_token
44
+ secure_digest(Time.now, (1..10).map{ rand.to_s.gsub(/0\./,'') }.join, PREFERRED_DIGEST_VERSION)
45
+ end
46
+
47
+ def guest
48
+ new.tap do |user|
49
+ user.email = GUEST_USER_EMAIL
50
+ end
51
+ end
52
+ end
53
+
54
+ module InstanceMethods
55
+
56
+ def active?
57
+ active
58
+ end
59
+
60
+ def authenticated?(password)
61
+ if crypted_password == encrypt(password)
62
+ if self.respond_to?(:using_digest_version) and using_digest_version != PREFERRED_DIGEST_VERSION
63
+ new_salt = self.class.make_token
64
+ self.update_attribute(:crypted_password,self.class.secure_digest(password, new_salt, PREFERRED_DIGEST_VERSION))
65
+ self.update_attribute(:salt, new_salt)
66
+ self.update_attribute(:using_digest_version, PREFERRED_DIGEST_VERSION)
67
+ end
68
+ return true
69
+ else
70
+ return false
71
+ end
72
+ end
73
+
74
+ def logged_in
75
+ self.class.update_all("last_logged_in_at='#{Time.now.to_s(:db)}'", "id=#{self.id}") # use update_all to avoid updated_on trigger
76
+ end
77
+
78
+ def is_guest?
79
+ email == GUEST_USER_EMAIL
80
+ end
81
+
82
+ #######
83
+ private
84
+ #######
85
+
86
+ def password_required?
87
+ active? && (crypted_password.blank? || !password.blank?)
88
+ end
89
+
90
+ def encrypt(password)
91
+ self.class.secure_digest(password, salt, digest_version)
92
+ end
93
+
94
+ def encrypt_password
95
+ return if password.blank?
96
+ self.salt = self.class.make_token if new_record?
97
+ self.crypted_password = self.class.secure_digest(password, salt, (self.respond_to?(:using_digest_version) ? PREFERRED_DIGEST_VERSION : 1))
98
+ self.using_digest_version = PREFERRED_DIGEST_VERSION if self.respond_to?(:using_digest_version)
99
+ end
100
+
101
+ def digest_version
102
+ self.respond_to?(:using_digest_version) ? (using_digest_version || 1) : 1
103
+ end
104
+
105
+ # email validation
106
+ def validate_email?
107
+ # allows applications to turn off email validation
108
+ true
109
+ end
110
+
111
+ def validate_email_presence?
112
+ validate_email? && active?
113
+ end
114
+
115
+ def validate_email_format?
116
+ validate_email? && active?
117
+ end
118
+
119
+ def validate_email_uniqueness?
120
+ validate_email? && active?
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,49 @@
1
+ module Minimalist
2
+ module Authorization
3
+ def self.included( base )
4
+ base.class_eval do
5
+ include InstanceMethods
6
+ helper_method :current_user, :logged_in?, :authorized?
7
+ end
8
+ end
9
+
10
+ module InstanceMethods
11
+ #######
12
+ private
13
+ #######
14
+
15
+ def current_user
16
+ @current_user ||= (get_user_from_session || User.guest)
17
+ end
18
+
19
+ def get_user_from_session
20
+ User.find_by_id(session[:user_id]) if session[:user_id]
21
+ end
22
+
23
+ def authorization_required
24
+ authorized? || access_denied
25
+ end
26
+
27
+ def authorized?(action = action_name, resource = controller_name)
28
+ logged_in?
29
+ end
30
+
31
+ def logged_in?
32
+ !current_user.is_guest?
33
+ end
34
+
35
+ def access_denied
36
+ store_location if request.method == :get && !logged_in?
37
+ redirect_to new_session_path
38
+ end
39
+
40
+ def store_location
41
+ session[:return_to] = request.request_uri
42
+ end
43
+
44
+ def redirect_back_or_default(default)
45
+ redirect_to(session.delete(:return_to) || default)
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,59 @@
1
+ module Minimalist
2
+ module Sessions
3
+ def self.included( base )
4
+ base.class_eval do
5
+ include InstanceMethods
6
+ append_view_path File.join(File.dirname(__FILE__), '..', '/app/views')
7
+ end
8
+ end
9
+
10
+ module InstanceMethods
11
+ def show
12
+ redirect_to new_session_path
13
+ end
14
+
15
+ def new
16
+ end
17
+
18
+ def create
19
+ if user = User.authenticate(params[:email], params[:password])
20
+ user.logged_in
21
+ session[:user_id] = user.id
22
+ after_authentication(user)
23
+ redirect_back_or_default(login_redirect_to(user))
24
+ return
25
+ else
26
+ after_authentication_failure(user)
27
+ flash.now[:error] = "Couldn't log you in as '#{params[:email]}'"
28
+ render :action => 'new'
29
+ end
30
+ end
31
+
32
+ def destroy
33
+ session[:user_id] = nil
34
+ flash[:notice] = "You have been logged out."
35
+ redirect_to logout_redirect_to
36
+ end
37
+
38
+ #######
39
+ private
40
+ #######
41
+
42
+ def login_redirect_to(user)
43
+ '/'
44
+ end
45
+
46
+ def logout_redirect_to
47
+ '/'
48
+ end
49
+
50
+ def after_authentication(user)
51
+ # overide in application
52
+ end
53
+
54
+ def after_authentication_failure(user)
55
+ # overide in application
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,12 @@
1
+ module Minimalist
2
+ module TestHelper
3
+ # Sets the current user in the session from the user fixtures.
4
+ def login_as(user)
5
+ @request.session[:user_id] = user ? users(user).id : nil
6
+ end
7
+
8
+ def current_user
9
+ @current_user ||= User.find(@request.session[:user_id])
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,3 @@
1
+ module MinimalistAuthentication
2
+ VERSION = '0.5'
3
+ end
@@ -0,0 +1,4 @@
1
+ # MinimalistAuthentication
2
+ require 'minimalist/authentication'
3
+ require 'minimalist/authorization'
4
+ require 'minimalist/sessions'
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :minimalist_authentication do
3
+ # # Task goes here
4
+ # end
@@ -0,0 +1,15 @@
1
+ require File.expand_path('../lib/minimalist/version', __FILE__)
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = "minimalist_authentication"
5
+ s.version = MinimalistAuthentication::VERSION
6
+ s.platform = Gem::Platform::RUBY
7
+ s.authors = ['Aaron Baldwin', 'Jonathan S. Garvin', 'WWIDEA, Inc']
8
+ s.email = ["developers@wwidea.org"]
9
+ s.homepage = "https://github.com/wwidea/minimalist_authentication"
10
+ s.summary = %q{A Rails authentication plugin that takes a minimalist approach.}
11
+ s.description = %q{A Rails authentication plugin that takes a minimalist approach. It is designed to be simple to understand, use, and modify for your application.}
12
+ s.files = `git ls-files`.split("\n")
13
+ s.test_files = `git ls-files -- test/*`.split("\n")
14
+ s.require_paths = ["lib"]
15
+ end
data/test/.gitignore ADDED
@@ -0,0 +1 @@
1
+ debug.log
@@ -0,0 +1,99 @@
1
+ require 'test_helper'
2
+
3
+ class AuthenticationTest < ActiveSupport::TestCase
4
+
5
+ test "should not be able to set crypted_password through mass assignment" do
6
+ user = Factory(:user)
7
+ old_crypted_password = user.crypted_password
8
+ user.update_attributes(:crypted_password => 'should not work')
9
+ assert_equal(old_crypted_password, user.crypted_password)
10
+ end
11
+
12
+ test "should return active user" do
13
+ user = Factory(:user)
14
+ assert_equal([user], User.active)
15
+ end
16
+
17
+ test "should authenticate user" do
18
+ user = Factory(:user)
19
+ assert_equal(user, User.authenticate(user.email, 'password'))
20
+ end
21
+
22
+ test "should fail to authenticate when email is blank" do
23
+ user = Factory(:user)
24
+ assert_nil(User.authenticate('', 'password'))
25
+ end
26
+
27
+ test "should fail to authenticate when password is blank" do
28
+ user = Factory(:user)
29
+ assert_nil(User.authenticate(user.email, ''))
30
+ end
31
+
32
+ test "should fail to authenticate when user is not active" do
33
+ user = Factory(:user, :active => false)
34
+ assert_nil(User.authenticate(user.email, 'password'))
35
+ end
36
+
37
+ test "should fail to authenticate for incorrect password" do
38
+ user = Factory(:user)
39
+ assert_nil(User.authenticate(user.email, 'incorrect_password'))
40
+ end
41
+
42
+ test "should create salt and encrypted_password for new user" do
43
+ user = User.new(:email => 'test@testing.com', :password => 'testing')
44
+ assert(user.save)
45
+ assert_not_nil(user.salt)
46
+ assert_not_nil(user.crypted_password)
47
+ assert(user.authenticated?('testing'))
48
+ end
49
+
50
+ test "should update last_logged_in_at without updating updated_at timestamp" do
51
+ user = Factory(:user, :updated_at => 1.day.ago)
52
+ updated_at = user.updated_at
53
+ user.logged_in
54
+ assert(user.updated_at == updated_at)
55
+ end
56
+
57
+ test "guest should be guest" do
58
+ assert(User.guest.is_guest?)
59
+ end
60
+
61
+ test "should allow inactive user to pass validation without an email or password" do
62
+ assert(User.new.valid?)
63
+ end
64
+
65
+ test "should fail validation for active user without email" do
66
+ user = User.new(:active => true)
67
+ assert_equal(false, user.valid?)
68
+ assert(user.errors[:email])
69
+ end
70
+
71
+ test "should fail validation for active user without password" do
72
+ user = User.new(:active => true)
73
+ assert_equal(false, user.valid?)
74
+ assert(user.errors[:password])
75
+ end
76
+
77
+ test "should use latest digest version for new users" do
78
+ assert_equal(User::PREFERRED_DIGEST_VERSION,Factory(:user).using_digest_version)
79
+ end
80
+
81
+ test "should migrate legacy users to new digest version" do
82
+ #Setup a user using the old digest version.
83
+ #This wouldn't be necessary with fixtures.
84
+ legacy_user = User.create(:active => true, :email => 'legacy@user.com', :password => '123456', :password_confirmation => '123456')
85
+ legacy_user.password = nil
86
+ legacy_user.salt = 'my_salt'
87
+ legacy_user.crypted_password = User.secure_digest('my_password', 'my_salt', 1)
88
+ legacy_user.using_digest_version = nil
89
+ assert(legacy_user.save)
90
+ assert_equal(nil, legacy_user.reload.using_digest_version)
91
+ assert_equal('86f156baf9e4868e6dcf910b65775efdeaa347d8',legacy_user.crypted_password)
92
+
93
+ # Ok, now we can finally do the test.
94
+ legacy_crypted_password = legacy_user.crypted_password
95
+ assert(legacy_user.authenticated?('my_password'))
96
+ assert_equal(2,legacy_user.reload.using_digest_version)
97
+ assert_not_equal(legacy_crypted_password,legacy_user.crypted_password)
98
+ end
99
+ end