restful_authentication 1.1.6

Sign up to get free protection for your applications and to get access to all the features.
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