restful_authentication 1.1.6

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 (64) hide show
  1. data/CHANGELOG +68 -0
  2. data/LICENSE +20 -0
  3. data/README.textile +232 -0
  4. data/Rakefile +54 -0
  5. data/TODO +15 -0
  6. data/generators/authenticated/USAGE +1 -0
  7. data/generators/authenticated/authenticated_generator.rb +493 -0
  8. data/generators/authenticated/lib/insert_routes.rb +69 -0
  9. data/generators/authenticated/templates/_model_partial.html.erb +8 -0
  10. data/generators/authenticated/templates/activation.erb +3 -0
  11. data/generators/authenticated/templates/authenticated_system.rb +189 -0
  12. data/generators/authenticated/templates/authenticated_test_helper.rb +12 -0
  13. data/generators/authenticated/templates/controller.rb +43 -0
  14. data/generators/authenticated/templates/features/accounts.feature +67 -0
  15. data/generators/authenticated/templates/features/sessions.feature +77 -0
  16. data/generators/authenticated/templates/features/step_definitions/ra_env.rb +7 -0
  17. data/generators/authenticated/templates/features/step_definitions/user_steps.rb +31 -0
  18. data/generators/authenticated/templates/helper.rb +2 -0
  19. data/generators/authenticated/templates/login.html.erb +14 -0
  20. data/generators/authenticated/templates/machinist_spec.rb +5 -0
  21. data/generators/authenticated/templates/machinist_test.rb +5 -0
  22. data/generators/authenticated/templates/mailer.rb +25 -0
  23. data/generators/authenticated/templates/migration.rb +24 -0
  24. data/generators/authenticated/templates/model.rb +83 -0
  25. data/generators/authenticated/templates/model_controller.rb +96 -0
  26. data/generators/authenticated/templates/model_helper.rb +93 -0
  27. data/generators/authenticated/templates/model_helper_spec.rb +157 -0
  28. data/generators/authenticated/templates/observer.rb +11 -0
  29. data/generators/authenticated/templates/signup.html.erb +19 -0
  30. data/generators/authenticated/templates/signup_notification.erb +8 -0
  31. data/generators/authenticated/templates/site_keys.rb +38 -0
  32. data/generators/authenticated/templates/spec/blueprints/user.rb +13 -0
  33. data/generators/authenticated/templates/spec/controllers/access_control_spec.rb +89 -0
  34. data/generators/authenticated/templates/spec/controllers/authenticated_system_spec.rb +107 -0
  35. data/generators/authenticated/templates/spec/controllers/sessions_controller_spec.rb +138 -0
  36. data/generators/authenticated/templates/spec/controllers/users_controller_spec.rb +197 -0
  37. data/generators/authenticated/templates/spec/fixtures/users.yml +60 -0
  38. data/generators/authenticated/templates/spec/helpers/users_helper_spec.rb +141 -0
  39. data/generators/authenticated/templates/spec/models/user_spec.rb +298 -0
  40. data/generators/authenticated/templates/tasks/auth.rake +33 -0
  41. data/generators/authenticated/templates/test/functional_test.rb +84 -0
  42. data/generators/authenticated/templates/test/mailer_test.rb +31 -0
  43. data/generators/authenticated/templates/test/model_functional_test.rb +91 -0
  44. data/generators/authenticated/templates/test/unit_test.rb +177 -0
  45. data/lib/authentication.rb +40 -0
  46. data/lib/authentication/by_cookie_token.rb +82 -0
  47. data/lib/authentication/by_password.rb +64 -0
  48. data/lib/authorization.rb +14 -0
  49. data/lib/authorization/aasm_roles.rb +64 -0
  50. data/lib/authorization/stateful_roles.rb +64 -0
  51. data/lib/restful_authentication.rb +6 -0
  52. data/lib/trustification.rb +14 -0
  53. data/lib/trustification/email_validation.rb +20 -0
  54. data/notes/AccessControl.txt +2 -0
  55. data/notes/Authentication.txt +5 -0
  56. data/notes/Authorization.txt +154 -0
  57. data/notes/RailsPlugins.txt +78 -0
  58. data/notes/SecurityFramework.graffle +0 -0
  59. data/notes/SecurityFramework.png +0 -0
  60. data/notes/SecurityPatterns.txt +163 -0
  61. data/notes/Tradeoffs.txt +126 -0
  62. data/notes/Trustification.txt +49 -0
  63. data/restful_authentication.gemspec +32 -0
  64. metadata +128 -0
@@ -0,0 +1,33 @@
1
+ require 'digest/sha1'
2
+ require 'erb'
3
+
4
+ def site_keys_file
5
+ File.join("config", "initializers", "site_keys.rb")
6
+ end
7
+
8
+ def secure_digest(*args)
9
+ Digest::SHA1.hexdigest(args.flatten.join('--'))
10
+ end
11
+
12
+ def make_token
13
+ secure_digest(Time.now, (1..10).map{ rand.to_s })
14
+ end
15
+
16
+ def make_site_keys_rb
17
+ site_key = secure_digest(Time.now, (1..10).map{ rand.to_s })
18
+ site_key_erb = <<-EOF
19
+ # key of 40 chars length
20
+ REST_AUTH_SITE_KEY = '#{site_key}'
21
+ REST_AUTH_DIGEST_STRETCHES = 10
22
+ EOF
23
+ end
24
+
25
+ namespace :auth do
26
+ namespace :gen do
27
+ desc "Generates config/initializers/site_keys.rb"
28
+ task :site_key do
29
+ file = ENV['file'] || site_keys_file
30
+ File.open(file, "w"){|f| f.write(make_site_keys_rb)}
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,84 @@
1
+ require File.dirname(__FILE__) + '/../test_helper'
2
+ require '<%= controller_file_name %>_controller'
3
+
4
+ # Re-raise errors caught by the controller.
5
+ class <%= controller_class_name %>Controller; def rescue_action(e) raise e end; end
6
+
7
+ class <%= controller_class_name %>ControllerTest < ActionController::TestCase
8
+ # Be sure to include AuthenticatedTestHelper in test/test_helper.rb instead
9
+ # Then, you can remove it from this and the units test.
10
+ include AuthenticatedTestHelper
11
+
12
+ def test_should_login_and_redirect
13
+ <%= file_name %> = <%= class_name %>.make
14
+ post :create, :login => <%= file_name %>.login, :password => <%= file_name %>.password
15
+ assert session[:<%= file_name %>_id]
16
+ assert_response :redirect
17
+ end
18
+
19
+ def test_should_fail_login_and_not_redirect
20
+ post :create, :login => 'quentin', :password => 'bad password'
21
+ assert_nil session[:<%= file_name %>_id]
22
+ assert_response :success
23
+ end
24
+
25
+ def test_should_logout
26
+ log_in
27
+ get :destroy
28
+ assert_nil session[:<%= file_name %>_id]
29
+ assert_response :redirect
30
+ end
31
+
32
+ def test_should_remember_me
33
+ <%= file_name %> = <%= class_name %>.make
34
+ @request.cookies["auth_token"] = nil
35
+ post :create, :login => <%= file_name %>.login, :password => <%= file_name %>.password, :remember_me => "1"
36
+ assert_not_nil @response.cookies["auth_token"]
37
+ end
38
+
39
+ def test_should_not_remember_me
40
+ @request.cookies["auth_token"] = nil
41
+ post :create, :login => 'quentin', :password => 'monkey', :remember_me => "0"
42
+ assert @response.cookies["auth_token"].blank?
43
+ end
44
+
45
+ def test_should_delete_token_on_logout
46
+ log_in
47
+ get :destroy
48
+ assert @response.cookies["auth_token"].blank?
49
+ end
50
+
51
+ def test_should_login_with_cookie
52
+ <%= file_name %> = <%= class_name %>.make
53
+ <%= file_name %>.remember_me
54
+ @request.cookies["auth_token"] = cookie_for(<%= file_name %>)
55
+ get :new
56
+ assert @controller.send(:logged_in?)
57
+ end
58
+
59
+ def test_should_fail_expired_cookie_login
60
+ <%= file_name %> = <%= class_name %>.make
61
+ <%= file_name %>.remember_me
62
+ <%= file_name %>.update_attribute :remember_token_expires_at, 5.minutes.ago
63
+ @request.cookies["auth_token"] = cookie_for(<%= file_name %>)
64
+ get :new
65
+ assert !@controller.send(:logged_in?)
66
+ end
67
+
68
+ def test_should_fail_cookie_login
69
+ <%= file_name %> = <%= class_name %>.make
70
+ <%= file_name %>.remember_me
71
+ @request.cookies["auth_token"] = auth_token('invalid_auth_token')
72
+ get :new
73
+ assert !@controller.send(:logged_in?)
74
+ end
75
+
76
+ protected
77
+ def auth_token(token)
78
+ CGI::Cookie.new('name' => 'auth_token', 'value' => token)
79
+ end
80
+
81
+ def cookie_for(<%= file_name %>)
82
+ auth_token <%= file_name %>.remember_token
83
+ end
84
+ end
@@ -0,0 +1,31 @@
1
+ require File.dirname(__FILE__) + '/../test_helper'
2
+ require '<%= file_name %>_mailer'
3
+
4
+ class <%= class_name %>MailerTest < Test::Unit::TestCase
5
+ FIXTURES_PATH = File.dirname(__FILE__) + '/../fixtures'
6
+ CHARSET = "utf-8"
7
+
8
+ include ActionMailer::Quoting
9
+
10
+ def setup
11
+ ActionMailer::Base.delivery_method = :test
12
+ ActionMailer::Base.perform_deliveries = true
13
+ ActionMailer::Base.deliveries = []
14
+
15
+ @expected = TMail::Mail.new
16
+ @expected.set_content_type "text", "plain", { "charset" => CHARSET }
17
+ end
18
+
19
+ def test_dummy_test
20
+ #do nothing
21
+ end
22
+
23
+ private
24
+ def read_fixture(action)
25
+ IO.readlines("#{FIXTURES_PATH}/<%= file_name %>_mailer/#{action}")
26
+ end
27
+
28
+ def encode(subject)
29
+ quoted_printable(subject, CHARSET)
30
+ end
31
+ end
@@ -0,0 +1,91 @@
1
+ require File.dirname(__FILE__) + '/../test_helper'
2
+ require '<%= model_controller_file_name %>_controller'
3
+
4
+ # Re-raise errors caught by the controller.
5
+ class <%= model_controller_class_name %>Controller; def rescue_action(e) raise e end; end
6
+
7
+ class <%= model_controller_class_name %>ControllerTest < ActionController::TestCase
8
+ # Be sure to include AuthenticatedTestHelper in test/test_helper.rb instead
9
+ # Then, you can remove it from this and the units test.
10
+ include AuthenticatedTestHelper
11
+
12
+ def test_should_allow_signup
13
+ assert_difference '<%= class_name %>.count' do
14
+ create_<%= file_name %>
15
+ assert_response :redirect
16
+ end
17
+ end
18
+
19
+ def test_should_require_login_on_signup
20
+ assert_no_difference '<%= class_name %>.count' do
21
+ create_<%= file_name %>(:login => nil)
22
+ assert assigns(:<%= file_name %>).errors.on(:login)
23
+ assert_response :success
24
+ end
25
+ end
26
+
27
+ def test_should_require_password_on_signup
28
+ assert_no_difference '<%= class_name %>.count' do
29
+ create_<%= file_name %>(:password => nil)
30
+ assert assigns(:<%= file_name %>).errors.on(:password)
31
+ assert_response :success
32
+ end
33
+ end
34
+
35
+ def test_should_require_password_confirmation_on_signup
36
+ assert_no_difference '<%= class_name %>.count' do
37
+ create_<%= file_name %>(:password_confirmation => nil)
38
+ assert assigns(:<%= file_name %>).errors.on(:password_confirmation)
39
+ assert_response :success
40
+ end
41
+ end
42
+
43
+ def test_should_require_email_on_signup
44
+ assert_no_difference '<%= class_name %>.count' do
45
+ create_<%= file_name %>(:email => nil)
46
+ assert assigns(:<%= file_name %>).errors.on(:email)
47
+ assert_response :success
48
+ end
49
+ end
50
+ <% if options[:stateful] %>
51
+ def test_should_sign_up_user_in_pending_state
52
+ create_<%= file_name %>
53
+ assigns(:<%= file_name %>).reload
54
+ assert assigns(:<%= file_name %>).pending?
55
+ end<% end %>
56
+
57
+ <% if options[:include_activation] %>
58
+ def test_should_sign_up_user_with_activation_code
59
+ create_<%= file_name %>
60
+ assigns(:<%= file_name %>).reload
61
+ assert_not_nil assigns(:<%= file_name %>).activation_code
62
+ end
63
+
64
+ def test_should_activate_user
65
+ assert_nil <%= class_name %>.authenticate('aaron', 'test')
66
+ get :activate, :activation_code => <%= table_name %>(:aaron).activation_code
67
+ assert_redirected_to '/<%= controller_routing_path %>/new'
68
+ assert_not_nil flash[:notice]
69
+ assert_equal <%= table_name %>(:aaron), <%= class_name %>.authenticate('aaron', 'monkey')
70
+ end
71
+
72
+ def test_should_not_activate_user_without_key
73
+ get :activate
74
+ assert_nil flash[:notice]
75
+ rescue ActionController::RoutingError
76
+ # in the event your routes deny this, we'll just bow out gracefully.
77
+ end
78
+
79
+ def test_should_not_activate_user_with_blank_key
80
+ get :activate, :activation_code => ''
81
+ assert_nil flash[:notice]
82
+ rescue ActionController::RoutingError
83
+ # well played, sir
84
+ end<% end %>
85
+
86
+ protected
87
+ def create_<%= file_name %>(options = {})
88
+ post :create, :<%= file_name %> => { :login => 'quire', :email => 'quire@example.com',
89
+ :password => 'quire69', :password_confirmation => 'quire69' }.merge(options)
90
+ end
91
+ end
@@ -0,0 +1,177 @@
1
+ require File.dirname(__FILE__) + '/../test_helper'
2
+
3
+ class <%= class_name %>Test < ActiveSupport::TestCase
4
+ # Be sure to include AuthenticatedTestHelper in test/test_helper.rb instead.
5
+ # Then, you can remove it from this and the functional test.
6
+ include AuthenticatedTestHelper
7
+
8
+ def test_should_create_<%= file_name %>
9
+ assert_difference '<%= class_name %>.count' do
10
+ <%= file_name %> = create_<%= file_name %>
11
+ assert !<%= file_name %>.new_record?, "#{<%= file_name %>.errors.full_messages.to_sentence}"
12
+ end
13
+ end
14
+ <% if options[:include_activation] %>
15
+ def test_should_initialize_activation_code_upon_creation
16
+ <%= file_name %> = create_<%= file_name %>
17
+ <%= file_name %>.reload
18
+ assert_not_nil <%= file_name %>.activation_code
19
+ end
20
+ <% end %><% if options[:stateful] %>
21
+ def test_should_create_and_start_in_pending_state
22
+ <%= file_name %> = create_<%= file_name %>
23
+ <%= file_name %>.reload
24
+ assert <%= file_name %>.pending?
25
+ end
26
+
27
+ <% end %>
28
+ def test_should_require_login
29
+ assert_no_difference '<%= class_name %>.count' do
30
+ u = create_<%= file_name %>(:login => nil)
31
+ assert u.errors.on(:login)
32
+ end
33
+ end
34
+
35
+ def test_should_require_password
36
+ assert_no_difference '<%= class_name %>.count' do
37
+ u = create_<%= file_name %>(:password => nil)
38
+ assert u.errors.on(:password)
39
+ end
40
+ end
41
+
42
+ def test_should_require_password_confirmation
43
+ assert_no_difference '<%= class_name %>.count' do
44
+ u = create_<%= file_name %>(:password_confirmation => nil)
45
+ assert u.errors.on(:password_confirmation)
46
+ end
47
+ end
48
+
49
+ def test_should_require_email
50
+ assert_no_difference '<%= class_name %>.count' do
51
+ u = create_<%= file_name %>(:email => nil)
52
+ assert u.errors.on(:email)
53
+ end
54
+ end
55
+
56
+ def test_should_reset_password
57
+ <%= file_name %> = <%= class_name %>.make
58
+ <%= file_name %>.update_attributes(:password => 'new password', :password_confirmation => 'new password')
59
+ assert_equal <%= file_name %>, <%= class_name %>.authenticate(<%= file_name %>.login, 'new password')
60
+ end
61
+
62
+ def test_should_not_rehash_password
63
+ <%= file_name %> = <%= class_name %>.make
64
+ <%= file_name %>.update_attributes(:login => 'quentin2')
65
+ assert_equal <%= file_name %>, <%= class_name %>.authenticate('quentin2', <%= file_name %>.password)
66
+ end
67
+
68
+ def test_should_authenticate_<%= file_name %>
69
+ <%= file_name %> = <%= class_name %>.make
70
+ assert_equal <%= file_name %>, <%= class_name %>.authenticate(<%= file_name %>.login, <%= file_name %>.password)
71
+ end
72
+
73
+ def test_should_set_remember_token
74
+ <%= file_name %> = <%= class_name %>.make
75
+ <%= file_name %>.remember_me
76
+ assert_not_nil <%= file_name %>.remember_token
77
+ assert_not_nil <%= file_name %>.remember_token_expires_at
78
+ end
79
+
80
+ def test_should_unset_remember_token
81
+ <%= file_name %> = <%= class_name %>.make
82
+ <%= file_name %>.remember_me
83
+ assert_not_nil <%= file_name %>.remember_token
84
+ <%= file_name %>.forget_me
85
+ assert_nil <%= file_name %>.remember_token
86
+ end
87
+
88
+ def test_should_remember_me_for_one_week
89
+ <%= file_name %> = <%= class_name %>.make
90
+ before = 1.week.from_now.utc
91
+ <%= file_name %>.remember_me_for 1.week
92
+ after = 1.week.from_now.utc
93
+ assert_not_nil <%= file_name %>.remember_token
94
+ assert_not_nil <%= file_name %>.remember_token_expires_at
95
+ assert <%= file_name %>.remember_token_expires_at.between?(before, after)
96
+ end
97
+
98
+ def test_should_remember_me_until_one_week
99
+ time = 1.week.from_now.utc
100
+ <%= file_name %> = <%= class_name %>.make
101
+ <%= file_name %>.remember_me_until time
102
+ assert_not_nil <%= file_name %>.remember_token
103
+ assert_not_nil <%= file_name %>.remember_token_expires_at
104
+ assert_equal <%= file_name %>.remember_token_expires_at, time
105
+ end
106
+
107
+ def test_should_remember_me_default_two_weeks
108
+ before = 2.weeks.from_now.utc
109
+ <%= file_name %> = <%= class_name %>.make
110
+ <%= file_name %>.remember_me
111
+ after = 2.weeks.from_now.utc
112
+ assert_not_nil <%= file_name %>.remember_token
113
+ assert_not_nil <%= file_name %>.remember_token_expires_at
114
+ assert <%= file_name %>.remember_token_expires_at.between?(before, after)
115
+ end
116
+ <% if options[:stateful] %>
117
+ def test_should_register_passive_<%= file_name %>
118
+ <%= file_name %> = create_<%= file_name %>(:password => nil, :password_confirmation => nil)
119
+ assert <%= file_name %>.passive?
120
+ <%= file_name %>.update_attributes(:password => 'new password', :password_confirmation => 'new password')
121
+ <%= file_name %>.register!
122
+ assert <%= file_name %>.pending?
123
+ end
124
+
125
+ def test_should_suspend_<%= file_name %>
126
+ <%= file_name %> = <%= class_name %>.make
127
+ <%= file_name %>.suspend!
128
+ assert <%= file_name %>.suspended?
129
+ end
130
+
131
+ def test_suspended_<%= file_name %>_should_not_authenticate
132
+ <%= file_name %> = <%= class_name %>.make
133
+ <%= file_name %>.suspend!
134
+ assert_not_equal <%= file_name %>, <%= class_name %>.authenticate(<%= file_name %>.login, <%= file_name %>.password)
135
+ end
136
+
137
+ def test_should_unsuspend_<%= file_name %>_to_active_state
138
+ <%= file_name %> = <%= class_name %>.make
139
+ <%= file_name %>.suspend!
140
+ assert <%= file_name %>.suspended?
141
+ <%= file_name %>.unsuspend!
142
+ assert <%= file_name %>.active?
143
+ end
144
+
145
+ def test_should_unsuspend_<%= file_name %>_with_nil_activation_code_and_activated_at_to_passive_state
146
+ <%= file_name %> = <%= class_name %>.make
147
+ <%= file_name %>.suspend!
148
+ <%= class_name %>.update_all :activation_code => nil, :activated_at => nil
149
+ assert <%= file_name %>.suspended?
150
+ <%= file_name %>.reload.unsuspend!
151
+ assert <%= file_name %>.passive?
152
+ end
153
+
154
+ def test_should_unsuspend_<%= file_name %>_with_activation_code_and_nil_activated_at_to_pending_state
155
+ <%= file_name %> = <%= class_name %>.make
156
+ <%= file_name %>.suspend!
157
+ <%= class_name %>.update_all :activation_code => 'foo-bar', :activated_at => nil
158
+ assert <%= file_name %>.suspended?
159
+ <%= file_name %>.reload.unsuspend!
160
+ assert <%= file_name %>.pending?
161
+ end
162
+
163
+ def test_should_delete_<%= file_name %>
164
+ <%= file_name %> = <%= class_name %>.make
165
+ assert_nil <%= file_name %>.deleted_at
166
+ <%= file_name %>.delete!
167
+ assert_not_nil <%= file_name %>.deleted_at
168
+ assert <%= file_name %>.deleted?
169
+ end
170
+ <% end %>
171
+ protected
172
+ def create_<%= file_name %>(options = {})
173
+ record = <%= class_name %>.new({ :login => 'quire', :email => 'quire@example.com', :password => 'quire69', :password_confirmation => 'quire69' }.merge(options))
174
+ record.<% if options[:stateful] %>register! if record.valid?<% else %>save<% end %>
175
+ record
176
+ end
177
+ end
@@ -0,0 +1,40 @@
1
+ module Authentication
2
+ mattr_accessor :login_regex, :bad_login_message,
3
+ :name_regex, :bad_name_message,
4
+ :email_name_regex, :domain_head_regex, :domain_tld_regex, :email_regex, :bad_email_message
5
+
6
+ self.login_regex = /\A\w[\w\.\-_@]+\z/ # ASCII, strict
7
+ # self.login_regex = /\A[[:alnum:]][[:alnum:]\.\-_@]+\z/ # Unicode, strict
8
+ # self.login_regex = /\A[^[:cntrl:]\\<>\/&]*\z/ # Unicode, permissive
9
+
10
+ self.bad_login_message = "use only letters, numbers, and .-_@ please.".freeze
11
+
12
+ self.name_regex = /\A[^[:cntrl:]\\<>\/&]*\z/ # Unicode, permissive
13
+ self.bad_name_message = "avoid non-printing characters and \\&gt;&lt;&amp;/ please.".freeze
14
+
15
+ self.email_name_regex = '[\w\.%\+\-]+'.freeze
16
+ self.domain_head_regex = '(?:[A-Z0-9\-]+\.)+'.freeze
17
+ self.domain_tld_regex = '(?:[A-Z]{2}|com|org|net|edu|gov|mil|biz|info|mobi|name|aero|jobs|museum)'.freeze
18
+ self.email_regex = /\A#{email_name_regex}@#{domain_head_regex}#{domain_tld_regex}\z/i
19
+ self.bad_email_message = "should look like an email address.".freeze
20
+
21
+ def self.included(recipient)
22
+ recipient.extend(ModelClassMethods)
23
+ recipient.class_eval do
24
+ include ModelInstanceMethods
25
+ end
26
+ end
27
+
28
+ module ModelClassMethods
29
+ def secure_digest(*args)
30
+ Digest::SHA1.hexdigest(args.flatten.join('--'))
31
+ end
32
+
33
+ def make_token
34
+ secure_digest(Time.now, (1..10).map{ rand.to_s })
35
+ end
36
+ end # class methods
37
+
38
+ module ModelInstanceMethods
39
+ end # instance methods
40
+ end