theworkinggroup-wristband 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. data/README.rdoc +130 -0
  2. data/Rakefile +39 -0
  3. data/generators/wristband/templates/app/controllers/sessions_controller.rb +50 -0
  4. data/generators/wristband/templates/app/controllers/users_controller.rb +47 -0
  5. data/generators/wristband/templates/app/models/user.rb +30 -0
  6. data/generators/wristband/templates/app/models/user_notifier.rb +54 -0
  7. data/generators/wristband/templates/app/views/sessions/new.html.haml +17 -0
  8. data/generators/wristband/templates/app/views/user_notifier/email_verification.text.html.rhtml +7 -0
  9. data/generators/wristband/templates/app/views/user_notifier/email_verification.text.plain.rhtml +9 -0
  10. data/generators/wristband/templates/app/views/user_notifier/forgot_password.text.html.rhtml +10 -0
  11. data/generators/wristband/templates/app/views/user_notifier/forgot_password.text.plain.rhtml +10 -0
  12. data/generators/wristband/templates/app/views/users/forgot_password.html.haml +10 -0
  13. data/generators/wristband/templates/app/views/users/index.html.haml +6 -0
  14. data/generators/wristband/templates/db/migrate/create_wristband_tables.rb +28 -0
  15. data/generators/wristband/wristband_generator.rb +69 -0
  16. data/init.rb +1 -0
  17. data/lib/wristband.rb +121 -0
  18. data/lib/wristband/application_extensions.rb +78 -0
  19. data/lib/wristband/authority_check.rb +160 -0
  20. data/lib/wristband/support.rb +28 -0
  21. data/lib/wristband/user_extensions.rb +89 -0
  22. data/test/database.yml +3 -0
  23. data/test/fixtures/crypted_password_users.yml +12 -0
  24. data/test/fixtures/plain_text_password_users.yml +4 -0
  25. data/test/fixtures/users.yml +23 -0
  26. data/test/schema.rb +34 -0
  27. data/test/test_helper.rb +40 -0
  28. data/test/unit/crypted_password_users_test.rb +81 -0
  29. data/test/unit/has_authorities_test.rb +49 -0
  30. data/test/unit/plain_text_password_user_test.rb +92 -0
  31. data/wristband.gemspec +32 -0
  32. metadata +97 -0
@@ -0,0 +1,89 @@
1
+ module Wristband
2
+ module UserExtensions
3
+ def self.included(base)
4
+ base.send(:extend, Wristband::UserExtensions::ClassMethods)
5
+ base.send(:include, Wristband::UserExtensions::InstanceMethods)
6
+ base.send(:extend, Wristband::Support)
7
+ end
8
+
9
+ module ClassMethods
10
+ def authenticate(username, password)
11
+ self.execute_authentication_chain(self, self.wristband[:before_authentication_chain]) == false and return
12
+ user = nil
13
+ wristband[:login_with_fields].find do |field|
14
+ user = send("find_by_#{field}", username)
15
+ end
16
+ (user and user.password_match?(password)) || return
17
+ self.execute_authentication_chain(user, self.wristband[:after_authentication_chain]) == false and return
18
+ user
19
+ end
20
+
21
+ def execute_authentication_chain(object, list)
22
+ list.each do |func|
23
+ case func
24
+ when Symbol,String
25
+ object.send(func) == false and return false
26
+ when Proc
27
+ func.call(object) == false and return false
28
+ end
29
+ end
30
+ end
31
+
32
+ def verify_email!(email_validation_key)
33
+ if user = find_by_email_validation_key(email_validation_key)
34
+ user.update_attribute(:validated_at, Time.now.to_s(:db))
35
+ user
36
+ else
37
+ raise UserVerificationError, 'We were not able to verify your account or it may have been verified already. Please contact us for assistance.'.t
38
+ end
39
+ end
40
+
41
+ end
42
+
43
+ module InstanceMethods
44
+
45
+ def has_authority_to?(action, options = { })
46
+ self.class.wristband[:authority_class].new(self, action, options).allowed_to?
47
+ end
48
+
49
+ def has_objections_to?(action, options = { })
50
+ self.class.wristband[:authority_class].new(self, action, options).denied_for_reasons
51
+ end
52
+
53
+ def initialize_salt
54
+ self.password_salt = Wristband::Support.random_salt
55
+ end
56
+
57
+ def initialize_token
58
+ self.remember_token = Wristband::Support.random_salt(16)
59
+ end
60
+
61
+ def encrypt_password
62
+ initialize_salt if new_record?
63
+ return if self.password.blank?
64
+ self.send("#{self.class.wristband[:password_column]}=", Wristband::Support.encrypt_with_salt(self.password, self.password_salt))
65
+ end
66
+
67
+ def password_match?(string)
68
+ if self.class.wristband[:plain_text_password]
69
+ self.send(self.class.wristband[:password_column]) == string
70
+ else
71
+ self.send(self.class.wristband[:password_column]) == Wristband::Support.encrypt_with_salt(string, self.password_salt)
72
+ end
73
+ end
74
+
75
+ def password_crypted?
76
+ self.password_salt and !self.password_salt.empty?
77
+ end
78
+
79
+ def password_crypt=(value)
80
+ if (value != read_attribute(:password_crypt))
81
+ initialize_token
82
+ end
83
+
84
+ write_attribute(:password_crypt, value)
85
+ end
86
+
87
+ end
88
+ end
89
+ end
data/test/database.yml ADDED
@@ -0,0 +1,3 @@
1
+ sqlite3:
2
+ :adapter: sqlite3
3
+ :dbfile: vendor/plugins/wristband/wristband.test.db
@@ -0,0 +1,12 @@
1
+ scott:
2
+ username: scott_tadman
3
+ email: scott@theworkinggroup.ca
4
+ password_crypt: passpass
5
+ role: admin
6
+
7
+ jack:
8
+ username: jack_neto
9
+ email: jack@theworkinggroup.ca
10
+ password_crypt: 7d103e2aee36a422ac5f619705e244b03c9e1328
11
+ password_salt: ad28d83c111a5ab70cef9d0896e5b18e84d899ef
12
+ role: regular_user
@@ -0,0 +1,4 @@
1
+ jack:
2
+ username: jack_neto
3
+ email: jack@theworkinggroup.ca
4
+ password: passpass
@@ -0,0 +1,23 @@
1
+ scott:
2
+ username: scott
3
+ email: scott@theworkinggroup.ca
4
+ password_crypt: passpass
5
+
6
+ jack:
7
+ username: jack
8
+ email: jack@theworkinggroup.ca
9
+ password_crypt: 7d103e2aee36a422ac5f619705e244b03c9e1328
10
+ password_salt: ad28d83c111a5ab70cef9d0896e5b18e84d899ef
11
+
12
+ oleg:
13
+ username: oleg
14
+ email: oleg@theworkinggroup.ca
15
+ password_crypt: 7d103e2aee36a422ac5f619705e244b03c9e1328
16
+ password_salt: ad28d83c111a5ab70cef9d0896e5b18e84d899ef
17
+
18
+ hesham:
19
+ username: hesham
20
+ email: hesham@theworkinggroup.ca
21
+ password_crypt: 7d103e2aee36a422ac5f619705e244b03c9e1328
22
+ password_salt: ad28d83c111a5ab70cef9d0896e5b18e84d899ef
23
+
data/test/schema.rb ADDED
@@ -0,0 +1,34 @@
1
+ ActiveRecord::Schema.define(:version => 1) do
2
+
3
+ create_table :crypted_password_users do |t|
4
+ t.string :username
5
+ t.string :email
6
+ t.string :password_crypt, :limit => 40
7
+ t.string :password_salt, :limit => 40
8
+ t.string :remember_token
9
+ t.string :role
10
+ t.datetime :created_at
11
+ t.datetime :updated_at
12
+ end
13
+
14
+ create_table :plain_text_password_users do |t|
15
+ t.string :username
16
+ t.string :email
17
+ t.string :password
18
+ t.string :remember_token
19
+ t.string :email_validation_key
20
+ t.datetime :created_at
21
+ t.datetime :updated_at
22
+ end
23
+
24
+ create_table :users do |t|
25
+ t.string :username
26
+ t.string :email
27
+ t.string :password_crypt, :limit => 40
28
+ t.string :password_salt, :limit => 40
29
+ t.string :remember_token
30
+ t.datetime :created_at
31
+ t.datetime :updated_at
32
+ end
33
+
34
+ end
@@ -0,0 +1,40 @@
1
+ $:.unshift(File.dirname(__FILE__) + '/../lib')
2
+
3
+ require 'test/unit'
4
+ require File.expand_path(File.join(File.dirname(__FILE__), '../../../../config/environment.rb'))
5
+ require 'rubygems'
6
+ # require 'active_support/breakpoint'
7
+ require 'active_record/fixtures'
8
+
9
+ config = YAML::load(IO.read( File.join(File.dirname(__FILE__),'database.yml')))
10
+
11
+ # cleanup logs and databases between test runs
12
+ #FileUtils.rm File.join(File.dirname(__FILE__), "debug.log"), :force => true
13
+ FileUtils.rm File.join(RAILS_ROOT, config['sqlite3'][:dbfile]), :force => true
14
+
15
+ ActiveRecord::Base.logger = Logger.new(File.join(File.dirname(__FILE__), "debug.log"))
16
+ ActiveRecord::Base.establish_connection(config[ENV['DB'] || 'sqlite3'])
17
+
18
+ load(File.join(File.dirname(__FILE__), "schema.rb"))
19
+
20
+ Test::Unit::TestCase.fixture_path = File.dirname(__FILE__) + "/fixtures/"
21
+ $LOAD_PATH.unshift(Test::Unit::TestCase.fixture_path)
22
+
23
+ class Test::Unit::TestCase #:nodoc:
24
+ def create_fixtures(*table_names)
25
+ if block_given?
26
+ Fixtures.create_fixtures(Test::Unit::TestCase.fixture_path, table_names) { yield }
27
+ else
28
+ Fixtures.create_fixtures(Test::Unit::TestCase.fixture_path, table_names)
29
+ end
30
+ end
31
+
32
+ # Turn off transactional fixtures if you're working with MyISAM tables in MySQL
33
+ self.use_transactional_fixtures = true
34
+
35
+ # Instantiated fixtures are slow, but give you @david where you otherwise would need people(:david)
36
+ self.use_instantiated_fixtures = false
37
+
38
+ # Instantiated fixtures are slow, but give you @david where you otherwise would need people(:david)
39
+
40
+ end
@@ -0,0 +1,81 @@
1
+ require File.join(File.dirname(__FILE__), '../test_helper')
2
+
3
+ class CryptedPasswordUser < ActiveRecord::Base
4
+ wristband :roles => [:admin, :regular_user]
5
+ end
6
+
7
+
8
+ class CryptedPasswordUserTest < Test::Unit::TestCase
9
+ fixtures :crypted_password_users
10
+
11
+ def setup
12
+ @scott = crypted_password_users(:scott)
13
+ end
14
+
15
+ def test_user_instance_methods
16
+ %w{
17
+ has_authority_to?
18
+ has_objections_to?
19
+ initialize_salt
20
+ initialize_token
21
+ encrypt_password
22
+ password_match?
23
+ password_crypted?
24
+ password_crypt=
25
+ is_admin?
26
+ is_regular_user?
27
+ }.each do |method|
28
+ assert @scott.respond_to?(method), "On '#{method}' method"
29
+ end
30
+ end
31
+
32
+ def test_user_class_methods
33
+ %w{
34
+ authenticate
35
+ execute_authentication_chain
36
+ verify_email!
37
+ wristband
38
+ }.each do |method|
39
+ assert CryptedPasswordUser.respond_to?(method), "On '#{method}' method"
40
+ end
41
+ end
42
+
43
+ def test_user_class_private_methods
44
+ %w{
45
+ random_string
46
+ encrypt_with_salt
47
+ random_salt
48
+ }.each do |method|
49
+ assert CryptedPasswordUser.private_methods.include?(method), "On '#{method}' method"
50
+ end
51
+ end
52
+
53
+ def test_assigned_options
54
+ assert_equal CryptedPasswordUser.wristband[:login_with_fields], [:username]
55
+ assert_equal CryptedPasswordUser.wristband[:plain_text_password], false
56
+ assert_equal CryptedPasswordUser.wristband[:before_authentication_chain], []
57
+ assert_equal CryptedPasswordUser.wristband[:after_authentication_chain], []
58
+ assert_equal CryptedPasswordUser.wristband[:password_column], :password_crypt
59
+ assert_equal CryptedPasswordUser.wristband[:roles], [:admin, :regular_user]
60
+ end
61
+
62
+ def test_authentication_by_username
63
+ assert_equal @scott, CryptedPasswordUser.authenticate(@scott.username, @scott.password_crypt)
64
+ end
65
+
66
+ def test_authentication_fails
67
+ assert_nil CryptedPasswordUser.authenticate('-bugus-', @scott.password_crypt)
68
+ assert_nil CryptedPasswordUser.authenticate(@scott.email, '-bugus-')
69
+ end
70
+
71
+ def test_password_match
72
+ assert @scott.password_match?(@scott.password_crypt)
73
+ assert crypted_password_users(:jack).password_match?('passpass')
74
+ end
75
+
76
+ def test_user_roles
77
+ assert crypted_password_users(:jack).is_regular_user?
78
+ assert crypted_password_users(:scott).is_admin?
79
+ end
80
+
81
+ end
@@ -0,0 +1,49 @@
1
+ require File.join(File.dirname(__FILE__), '../test_helper')
2
+
3
+ class UserAuthorityCheck < AuthorityCheck
4
+ before_check :is_admin?
5
+
6
+ def is_admin?
7
+ unless (@user.username.match(/^scott/i))
8
+ fail!("Only scott can be an admin.")
9
+ else
10
+ allow!
11
+ end
12
+ end
13
+
14
+ def wear_shoes?
15
+ unless (@user.username.match(/^s/i))
16
+ fail!("Only people with names that start with 'S' can wear shoes.")
17
+ end
18
+ end
19
+
20
+ def walk_outside?
21
+ wear_shoes?
22
+ unless (@user.username.match(/^j/i))
23
+ fail!("Only people with names that start with 'J' or 'S' can walk outside.")
24
+ end
25
+ end
26
+
27
+ end
28
+
29
+ class User < ActiveRecord::Base
30
+ wristband :has_authorities => true
31
+ end
32
+
33
+
34
+ class HasAuthoritiesTest < Test::Unit::TestCase
35
+ fixtures :users
36
+
37
+ def test_has_authority_to_with_fail
38
+ assert users(:scott).has_authority_to?(:wear_shoes)
39
+ assert !users(:jack).has_authority_to?(:wear_shoes)
40
+ assert_equal users(:jack).has_objections_to?(:wear_shoes), ["Only scott can be an admin.", "Only people with names that start with 'S' can wear shoes."]
41
+
42
+
43
+ assert users(:scott).has_authority_to?(:walk_outside)
44
+ assert !users(:jack).has_authority_to?(:walk_outside)
45
+ assert_equal users(:jack).has_objections_to?(:walk_outside), ["Only scott can be an admin.", "Only people with names that start with 'S' can wear shoes."]
46
+ assert !users(:oleg).has_authority_to?(:walk_outside)
47
+ assert_equal users(:oleg).has_objections_to?(:walk_outside), ["Only scott can be an admin.", "Only people with names that start with 'S' can wear shoes.", "Only people with names that start with 'J' or 'S' can walk outside."]
48
+ end
49
+ end
@@ -0,0 +1,92 @@
1
+ require File.join(File.dirname(__FILE__), '../test_helper')
2
+
3
+ class PlainTextPasswordUser < ActiveRecord::Base
4
+ wristband :login_with => [:username, :email],
5
+ :plain_text_password => true,
6
+ :after_authentication => :email_is_verified?,
7
+ :password_column => :password
8
+
9
+ def email_is_verified?
10
+ return self.email_validation_key.blank?
11
+ end
12
+ end
13
+
14
+
15
+
16
+ class PlainTextPasswordUserTest < Test::Unit::TestCase
17
+ fixtures :plain_text_password_users
18
+
19
+ def setup
20
+ @jack = plain_text_password_users(:jack)
21
+ end
22
+
23
+ def test_user_instance_methods
24
+ %w{
25
+ has_authority_to?
26
+ has_objections_to?
27
+ initialize_salt
28
+ initialize_token
29
+ encrypt_password
30
+ password_match?
31
+ password_crypted?
32
+ password_crypt=
33
+ }.each do |method|
34
+ assert @jack.respond_to?(method), "On '#{method}' method"
35
+ end
36
+ end
37
+
38
+ def test_user_class_methods
39
+ %w{
40
+ authenticate
41
+ execute_authentication_chain
42
+ verify_email!
43
+ wristband
44
+ }.each do |method|
45
+ assert PlainTextPasswordUser.respond_to?(method), "On '#{method}' method"
46
+ end
47
+ end
48
+
49
+ def test_user_class_private_methods
50
+ %w{
51
+ random_string
52
+ encrypt_with_salt
53
+ random_salt
54
+ }.each do |method|
55
+ assert PlainTextPasswordUser.private_methods.include?(method), "On '#{method}' method"
56
+ end
57
+ end
58
+
59
+ def test_assigned_options
60
+ assert_equal PlainTextPasswordUser.wristband[:login_with_fields], [:username, :email]
61
+ assert_equal PlainTextPasswordUser.wristband[:plain_text_password], true
62
+ assert_equal PlainTextPasswordUser.wristband[:before_authentication_chain], []
63
+ assert_equal PlainTextPasswordUser.wristband[:after_authentication_chain], [:email_is_verified?]
64
+ assert_equal PlainTextPasswordUser.wristband[:password_column], :password
65
+ assert_equal PlainTextPasswordUser.wristband[:roles], []
66
+ end
67
+
68
+ def test_authentication_by_username
69
+ assert_equal @jack, PlainTextPasswordUser.authenticate(@jack.username, @jack.password)
70
+ end
71
+
72
+ def test_authentication_by_email
73
+ assert_equal @jack, PlainTextPasswordUser.authenticate(@jack.email, @jack.password)
74
+ end
75
+
76
+ def test_authentication_fails
77
+ assert_nil PlainTextPasswordUser.authenticate('-bugus-', @jack.password)
78
+ assert_nil PlainTextPasswordUser.authenticate(@jack.email, '-bugus-')
79
+ end
80
+
81
+ def test_after_authentication_chain
82
+ @jack.update_attribute(:email_validation_key, Time.now)
83
+ assert_nil PlainTextPasswordUser.authenticate(@jack.username, @jack.password)
84
+
85
+ @jack.update_attribute(:email_validation_key, nil)
86
+ assert_equal @jack, PlainTextPasswordUser.authenticate(@jack.username, @jack.password)
87
+ end
88
+
89
+ def test_password_match
90
+ assert @jack.password_match?(@jack.password)
91
+ end
92
+ end
data/wristband.gemspec ADDED
@@ -0,0 +1,32 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{wristband}
5
+ s.version = "1.0.0"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Jack Neto"]
9
+ s.date = %q{2009-03-26}
10
+ s.description = %q{Simplifies the process of authenticating and authorizing users.}
11
+ s.email = %q{jack@theworkinggroup.ca}
12
+ s.extra_rdoc_files = ["lib/wristband/application_extensions.rb", "lib/wristband/authority_check.rb", "lib/wristband/support.rb", "lib/wristband/user_extensions.rb", "lib/wristband.rb", "README.rdoc"]
13
+ s.files = ["generators/wristband/templates/app/controllers/sessions_controller.rb", "generators/wristband/templates/app/controllers/users_controller.rb", "generators/wristband/templates/app/models/user.rb", "generators/wristband/templates/app/models/user_notifier.rb", "generators/wristband/templates/app/views/sessions/new.html.haml", "generators/wristband/templates/app/views/user_notifier/email_verification.text.html.rhtml", "generators/wristband/templates/app/views/user_notifier/email_verification.text.plain.rhtml", "generators/wristband/templates/app/views/user_notifier/forgot_password.text.html.rhtml", "generators/wristband/templates/app/views/user_notifier/forgot_password.text.plain.rhtml", "generators/wristband/templates/app/views/users/forgot_password.html.haml", "generators/wristband/templates/app/views/users/index.html.haml", "generators/wristband/templates/db/migrate/create_wristband_tables.rb", "generators/wristband/wristband_generator.rb", "init.rb", "lib/wristband/application_extensions.rb", "lib/wristband/authority_check.rb", "lib/wristband/support.rb", "lib/wristband/user_extensions.rb", "lib/wristband.rb", "Manifest", "Rakefile", "README.rdoc", "test/database.yml", "test/fixtures/crypted_password_users.yml", "test/fixtures/plain_text_password_users.yml", "test/fixtures/users.yml", "test/schema.rb", "test/test_helper.rb", "test/unit/crypted_password_users_test.rb", "test/unit/has_authorities_test.rb", "test/unit/plain_text_password_user_test.rb", "wristband.gemspec"]
14
+ s.has_rdoc = true
15
+ s.homepage = %q{http://github.com/theworkinggroup/wristband}
16
+ s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Wristband", "--main", "README.rdoc"]
17
+ s.require_paths = ["lib"]
18
+ s.rubyforge_project = %q{wristband}
19
+ s.rubygems_version = %q{1.3.1}
20
+ s.summary = %q{Simplifies the process of authenticating and authorizing users.}
21
+ s.test_files = ["test/test_helper.rb", "test/unit/crypted_password_users_test.rb", "test/unit/has_authorities_test.rb", "test/unit/plain_text_password_user_test.rb"]
22
+
23
+ if s.respond_to? :specification_version then
24
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
25
+ s.specification_version = 2
26
+
27
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
28
+ else
29
+ end
30
+ else
31
+ end
32
+ end