blue_light_special 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (104) hide show
  1. data/LICENSE +20 -0
  2. data/README.rdoc +67 -0
  3. data/Rakefile +95 -0
  4. data/VERSION +1 -0
  5. data/app/controllers/blue_light_special/impersonations_controller.rb +44 -0
  6. data/app/controllers/blue_light_special/passwords_controller.rb +84 -0
  7. data/app/controllers/blue_light_special/sessions_controller.rb +70 -0
  8. data/app/controllers/blue_light_special/users_controller.rb +48 -0
  9. data/app/models/blue_light_special_mailer.rb +22 -0
  10. data/app/models/deliver_change_password_job.rb +19 -0
  11. data/app/models/deliver_welcome_job.rb +17 -0
  12. data/app/models/impersonation.rb +26 -0
  13. data/app/views/blue_light_special_mailer/change_password.html.erb +9 -0
  14. data/app/views/impersonations/index.html.erb +5 -0
  15. data/app/views/passwords/edit.html.erb +23 -0
  16. data/app/views/passwords/new.html.erb +15 -0
  17. data/app/views/sessions/new.html.erb +48 -0
  18. data/app/views/users/_form.html.erb +21 -0
  19. data/app/views/users/edit.html.erb +6 -0
  20. data/app/views/users/new.html.erb +6 -0
  21. data/app/views/users/show.html.erb +8 -0
  22. data/generators/blue_light_special/USAGE +1 -0
  23. data/generators/blue_light_special/blue_light_special_generator.rb +78 -0
  24. data/generators/blue_light_special/lib/insert_commands.rb +33 -0
  25. data/generators/blue_light_special/lib/rake_commands.rb +22 -0
  26. data/generators/blue_light_special/templates/README +20 -0
  27. data/generators/blue_light_special/templates/application.html.erb +50 -0
  28. data/generators/blue_light_special/templates/blue_light_special.rb +21 -0
  29. data/generators/blue_light_special/templates/blue_light_special.yml +42 -0
  30. data/generators/blue_light_special/templates/factories.rb +23 -0
  31. data/generators/blue_light_special/templates/migrations/create_users.rb +24 -0
  32. data/generators/blue_light_special/templates/migrations/update_users.rb +44 -0
  33. data/generators/blue_light_special/templates/style.css +31 -0
  34. data/generators/blue_light_special/templates/user.rb +3 -0
  35. data/generators/blue_light_special/templates/xd_receiver.html +10 -0
  36. data/generators/blue_light_special/templates/xd_receiver_ssl.html +10 -0
  37. data/generators/blue_light_special_admin/USAGE +1 -0
  38. data/generators/blue_light_special_admin/blue_light_special_admin_generator.rb +30 -0
  39. data/generators/blue_light_special_admin/lib/insert_commands.rb +33 -0
  40. data/generators/blue_light_special_admin/templates/README +16 -0
  41. data/generators/blue_light_special_admin/templates/app/controllers/admin/admin_controller.rb +14 -0
  42. data/generators/blue_light_special_admin/templates/app/controllers/admin/users_controller.rb +52 -0
  43. data/generators/blue_light_special_admin/templates/app/views/admin/users/_form.html.erb +25 -0
  44. data/generators/blue_light_special_admin/templates/app/views/admin/users/edit.html.erb +6 -0
  45. data/generators/blue_light_special_admin/templates/app/views/admin/users/index.html.erb +7 -0
  46. data/generators/blue_light_special_admin/templates/app/views/admin/users/new.html.erb +6 -0
  47. data/generators/blue_light_special_admin/templates/app/views/admin/users/show.html.erb +10 -0
  48. data/generators/blue_light_special_admin/templates/test/integration/admin/users_test.rb +201 -0
  49. data/generators/blue_light_special_tests/USAGE +1 -0
  50. data/generators/blue_light_special_tests/blue_light_special_tests_generator.rb +21 -0
  51. data/generators/blue_light_special_tests/templates/README +58 -0
  52. data/generators/blue_light_special_tests/templates/test/integration/edit_profile_test.rb +35 -0
  53. data/generators/blue_light_special_tests/templates/test/integration/facebook_test.rb +61 -0
  54. data/generators/blue_light_special_tests/templates/test/integration/impersonation_test.rb +39 -0
  55. data/generators/blue_light_special_tests/templates/test/integration/password_reset_test.rb +128 -0
  56. data/generators/blue_light_special_tests/templates/test/integration/sign_in_test.rb +66 -0
  57. data/generators/blue_light_special_tests/templates/test/integration/sign_out_test.rb +28 -0
  58. data/generators/blue_light_special_tests/templates/test/integration/sign_up_test.rb +47 -0
  59. data/lib/blue_light_special.rb +7 -0
  60. data/lib/blue_light_special/authentication.rb +138 -0
  61. data/lib/blue_light_special/configuration.rb +32 -0
  62. data/lib/blue_light_special/extensions/errors.rb +6 -0
  63. data/lib/blue_light_special/extensions/rescue.rb +5 -0
  64. data/lib/blue_light_special/routes.rb +55 -0
  65. data/lib/blue_light_special/user.rb +241 -0
  66. data/rails/init.rb +4 -0
  67. data/shoulda_macros/blue_light_special.rb +244 -0
  68. data/test/controllers/passwords_controller_test.rb +184 -0
  69. data/test/controllers/sessions_controller_test.rb +129 -0
  70. data/test/controllers/users_controller_test.rb +57 -0
  71. data/test/models/blue_light_special_mailer_test.rb +52 -0
  72. data/test/models/impersonation_test.rb +25 -0
  73. data/test/models/user_test.rb +213 -0
  74. data/test/rails_root/app/controllers/accounts_controller.rb +10 -0
  75. data/test/rails_root/app/controllers/application_controller.rb +6 -0
  76. data/test/rails_root/app/helpers/application_helper.rb +5 -0
  77. data/test/rails_root/app/helpers/confirmations_helper.rb +2 -0
  78. data/test/rails_root/app/helpers/passwords_helper.rb +2 -0
  79. data/test/rails_root/app/models/user.rb +3 -0
  80. data/test/rails_root/config/boot.rb +110 -0
  81. data/test/rails_root/config/environment.rb +22 -0
  82. data/test/rails_root/config/environments/development.rb +19 -0
  83. data/test/rails_root/config/environments/production.rb +1 -0
  84. data/test/rails_root/config/environments/test.rb +37 -0
  85. data/test/rails_root/config/initializers/blue_light_special.rb +4 -0
  86. data/test/rails_root/config/initializers/inflections.rb +10 -0
  87. data/test/rails_root/config/initializers/mime_types.rb +5 -0
  88. data/test/rails_root/config/initializers/requires.rb +13 -0
  89. data/test/rails_root/config/initializers/time_formats.rb +4 -0
  90. data/test/rails_root/config/routes.rb +9 -0
  91. data/test/rails_root/db/migrate/20100305173127_blue_light_special_create_users.rb +21 -0
  92. data/test/rails_root/db/migrate/20100305173129_create_delayed_jobs.rb +20 -0
  93. data/test/rails_root/public/dispatch.rb +10 -0
  94. data/test/rails_root/script/create_project.rb +52 -0
  95. data/test/rails_root/test/factories/user.rb +13 -0
  96. data/test/rails_root/test/functional/accounts_controller_test.rb +23 -0
  97. data/test/rails_root/test/integration/facebook_test.rb +49 -0
  98. data/test/rails_root/test/integration/impersonation_test.rb +38 -0
  99. data/test/rails_root/test/integration/password_reset_test.rb +127 -0
  100. data/test/rails_root/test/integration/sign_in_test.rb +72 -0
  101. data/test/rails_root/test/integration/sign_out_test.rb +28 -0
  102. data/test/rails_root/test/integration/sign_up_test.rb +84 -0
  103. data/test/test_helper.rb +21 -0
  104. metadata +219 -0
@@ -0,0 +1,47 @@
1
+ require 'test_helper'
2
+
3
+ class SignUpTest < ActionController::IntegrationTest
4
+
5
+ context 'Signing up as a new user' do
6
+
7
+ setup do
8
+ ActionMailer::Base.deliveries.clear
9
+ end
10
+
11
+ teardown do
12
+ ActionMailer::Base.deliveries.clear
13
+ end
14
+
15
+ context 'with invalid data' do
16
+
17
+ should 'show error messages' do
18
+ sign_up(:email => 'invalidemail', :password_confirmation => '', :first_name => '', :last_name => '')
19
+ assert_match /Email is invalid/, response.body
20
+ assert_match /First name.*blank/, response.body
21
+ assert_match /Last name.*blank/, response.body
22
+ assert_match /Password doesn't match confirmation/, response.body
23
+ end
24
+
25
+ end
26
+
27
+ context 'with valid data' do
28
+
29
+ should 'sign in the user' do
30
+ sign_up(:email => 'bob@bob.bob', :password => 'password', :password_confirmation => 'password')
31
+ assert controller.signed_in?
32
+ end
33
+
34
+ should 'send a welcome email' do
35
+ sign_up(:email => 'bob@bob.bob', :password => 'password', :password_confirmation => 'password')
36
+ user = User.find_by_email('bob@bob.bob')
37
+ Delayed::Job.work_off
38
+ sent = ActionMailer::Base.deliveries.last
39
+ assert_equal user.email, sent.recipients
40
+ assert_match /welcome/i, sent.subject
41
+ end
42
+
43
+ end
44
+
45
+ end
46
+
47
+ end
@@ -0,0 +1,7 @@
1
+ require 'blue_light_special/extensions/errors'
2
+ require 'blue_light_special/extensions/rescue'
3
+
4
+ require 'blue_light_special/configuration'
5
+ require 'blue_light_special/routes'
6
+ require 'blue_light_special/authentication'
7
+ require 'blue_light_special/user'
@@ -0,0 +1,138 @@
1
+ module BlueLightSpecial
2
+ module Authentication
3
+
4
+ def self.included(controller) # :nodoc:
5
+ controller.send(:include, InstanceMethods)
6
+ controller.extend(ClassMethods)
7
+ end
8
+
9
+ module ClassMethods
10
+ def self.extended(controller)
11
+ controller.helper_method :current_user, :signed_in?,
12
+ :signed_out?, :impersonating?
13
+ controller.hide_action :current_user, :current_user=,
14
+ :signed_in?, :signed_out?,
15
+ :sign_in, :sign_out,
16
+ :authenticate, :deny_access,
17
+ :impersonating?
18
+ end
19
+ end
20
+
21
+ module InstanceMethods
22
+ # User in the current cookie
23
+ #
24
+ # @return [User, nil]
25
+ def current_user
26
+ @_current_user ||= user_from_cookie
27
+ end
28
+
29
+ # Set the current user
30
+ #
31
+ # @param [User]
32
+ def current_user=(user)
33
+ @_current_user = user
34
+ end
35
+
36
+ # Is the current user signed in?
37
+ #
38
+ # @return [true, false]
39
+ def signed_in?
40
+ ! current_user.nil?
41
+ end
42
+
43
+ # Is the current user signed out?
44
+ #
45
+ # @return [true, false]
46
+ def signed_out?
47
+ current_user.nil?
48
+ end
49
+
50
+ # Deny the user access if they are signed out.
51
+ #
52
+ # @example
53
+ # before_filter :authenticate
54
+ def authenticate
55
+ deny_access unless signed_in?
56
+ end
57
+
58
+ # Sign user in to cookie.
59
+ #
60
+ # @param [User]
61
+ #
62
+ # @example
63
+ # sign_in(@user)
64
+ def sign_in(user)
65
+ if user
66
+ cookies[:remember_token] = {
67
+ :value => user.remember_token,
68
+ :expires => 1.year.from_now.utc
69
+ }
70
+ self.current_user = user
71
+ end
72
+ end
73
+
74
+ # Sign user out of cookie.
75
+ #
76
+ # @example
77
+ # sign_out
78
+ def sign_out
79
+ current_user.reset_remember_token! if current_user
80
+ cookies.delete(:remember_token)
81
+ self.current_user = nil
82
+ end
83
+
84
+ # Store the current location and redirect to sign in.
85
+ # Display a failure flash message if included.
86
+ #
87
+ # @param [String] optional flash message to display to denied user
88
+ def deny_access(flash_message = nil)
89
+ store_location
90
+ flash[:failure] = flash_message if flash_message
91
+ redirect_to(sign_in_url)
92
+ end
93
+
94
+ def impersonating?
95
+ !session[:admin_user_id].blank?
96
+ end
97
+
98
+
99
+ protected
100
+
101
+ def user_from_cookie
102
+ if token = cookies[:remember_token]
103
+ ::User.find_by_remember_token(token)
104
+ end
105
+ end
106
+
107
+ def sign_user_in(user)
108
+ warn "[DEPRECATION] sign_user_in: unnecessary. use sign_in(user) instead."
109
+ sign_in(user)
110
+ end
111
+
112
+ def store_location
113
+ if request.get?
114
+ session[:return_to] = request.request_uri
115
+ end
116
+ end
117
+
118
+ def redirect_back_or(default)
119
+ redirect_to(return_to || default)
120
+ clear_return_to
121
+ end
122
+
123
+ def return_to
124
+ session[:return_to] || params[:return_to]
125
+ end
126
+
127
+ def clear_return_to
128
+ session[:return_to] = nil
129
+ end
130
+
131
+ def redirect_to_root
132
+ redirect_to('/')
133
+ end
134
+
135
+ end
136
+
137
+ end
138
+ end
@@ -0,0 +1,32 @@
1
+ module BlueLightSpecial
2
+ class Configuration
3
+ attr_accessor :mailer_sender
4
+ attr_accessor :impersonation_hash
5
+ attr_accessor :use_facebook_connect
6
+ attr_accessor :facebook_api_key
7
+ attr_accessor :facebook_secret_key
8
+
9
+ def initialize
10
+ @mailer_sender = 'donotreply@example.com'
11
+ @impersonation_hash = 'e76e05e1ddf74560ffb64c02a1c1b26c'
12
+ @user_facebook_connect = false
13
+ end
14
+ end
15
+
16
+ class << self
17
+ attr_accessor :configuration
18
+ end
19
+
20
+ # Configure BlueLightSpecial someplace sensible,
21
+ # like config/initializers/blue_light_special.rb
22
+ #
23
+ # @example
24
+ # BlueLightSpecial.configure do |config|
25
+ # config.mailer_sender = 'donotreply@example.com'
26
+ # config.impersonation_hash = 'abc123def456...'
27
+ # end
28
+ def self.configure
29
+ self.configuration ||= Configuration.new
30
+ yield(configuration)
31
+ end
32
+ end
@@ -0,0 +1,6 @@
1
+ if defined?(ActionController)
2
+ module ActionController
3
+ class Forbidden < StandardError
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ if defined?(ActionDispatch::ShowExceptions) # Rails 3
2
+ ActionDispatch::ShowExceptions.rescue_responses.update('ActionController::Forbidden' => :forbidden)
3
+ elsif defined?(ActionController::Base)
4
+ ActionController::Base.rescue_responses.update('ActionController::Forbidden' => :forbidden)
5
+ end
@@ -0,0 +1,55 @@
1
+ module BlueLightSpecial
2
+ class Routes
3
+
4
+ # In your application's config/routes.rb, draw BlueLightSpecial's routes:
5
+ #
6
+ # @example
7
+ # map.resources :posts
8
+ # BlueLightSpecial::Routes.draw(map)
9
+ #
10
+ # If you need to override a BlueLightSpecial route, invoke your app route
11
+ # earlier in the file so Rails' router short-circuits when it finds
12
+ # your route:
13
+ #
14
+ # @example
15
+ # map.resources :users, :only => [:new, :create]
16
+ # BlueLightSpecial::Routes.draw(map)
17
+ def self.draw(map)
18
+ map.resources :passwords,
19
+ :controller => 'blue_light_special/passwords',
20
+ :only => [:new, :create]
21
+
22
+ map.resource :session,
23
+ :controller => 'blue_light_special/sessions',
24
+ :only => [:new, :create, :destroy]
25
+
26
+ map.resources :users, :controller => 'blue_light_special/users' do |users|
27
+ users.resource :password,
28
+ :controller => 'blue_light_special/passwords',
29
+ :only => [:create, :edit, :update]
30
+ end
31
+
32
+ map.resource :impersonation,
33
+ :controller => 'blue_light_special/impersonations',
34
+ :only => [:create, :destroy]
35
+ map.resources :impersonations,
36
+ :controller => 'blue_light_special/impersonations',
37
+ :only => :index
38
+
39
+ map.sign_up 'sign_up',
40
+ :controller => 'blue_light_special/users',
41
+ :action => 'new'
42
+ map.sign_in 'sign_in',
43
+ :controller => 'blue_light_special/sessions',
44
+ :action => 'new'
45
+ map.fb_connect 'fb_connect',
46
+ :controller => 'blue_light_special/sessions',
47
+ :action => 'create'
48
+ map.sign_out 'sign_out',
49
+ :controller => 'blue_light_special/sessions',
50
+ :action => 'destroy',
51
+ :method => :delete
52
+ end
53
+
54
+ end
55
+ end
@@ -0,0 +1,241 @@
1
+ require 'digest/sha1'
2
+
3
+ module BlueLightSpecial
4
+ module User
5
+
6
+ Admin = 'admin'
7
+
8
+ # Hook for all BlueLightSpecial::User modules.
9
+ #
10
+ # If you need to override parts of BlueLightSpecial::User,
11
+ # extend and include à la carte.
12
+ #
13
+ # @example
14
+ # extend ClassMethods
15
+ # include InstanceMethods
16
+ # include AttrAccessor
17
+ # include Callbacks
18
+ #
19
+ # @see ClassMethods
20
+ # @see InstanceMethods
21
+ # @see AttrAccessible
22
+ # @see AttrAccessor
23
+ # @see Validations
24
+ # @see Callbacks
25
+ def self.included(model)
26
+ model.extend(ClassMethods)
27
+
28
+ model.send(:include, InstanceMethods)
29
+ model.send(:include, AttrAccessor)
30
+ model.send(:include, Validations)
31
+ model.send(:include, Callbacks)
32
+ end
33
+
34
+ module AttrAccessor
35
+ # Hook for attr_accessor virtual attributes.
36
+ #
37
+ # :password, :password_confirmation
38
+ def self.included(model)
39
+ model.class_eval do
40
+ attr_accessor :password, :password_confirmation
41
+ end
42
+ end
43
+ end
44
+
45
+ module Validations
46
+ # Hook for validations.
47
+ #
48
+ # :email must be present, unique, formatted
49
+ #
50
+ # If password is required,
51
+ # :password must be present, confirmed
52
+ def self.included(model)
53
+ model.class_eval do
54
+ validates_presence_of :email, :unless => :email_optional?
55
+ validates_uniqueness_of :email, :case_sensitive => false, :allow_blank => true
56
+ validates_format_of :email, :with => %r{.+@.+\..+}, :allow_blank => true
57
+
58
+ validates_presence_of :password, :unless => :password_optional?
59
+ validates_confirmation_of :password, :unless => :password_optional?
60
+
61
+ validates_presence_of :first_name, :last_name
62
+ end
63
+ end
64
+ end
65
+
66
+ module Callbacks
67
+ # Hook for callbacks.
68
+ #
69
+ # salt, token, password encryption are handled before_save.
70
+ def self.included(model)
71
+ model.class_eval do
72
+ before_save :initialize_salt,
73
+ :encrypt_password
74
+ before_create :generate_remember_token
75
+ after_create :send_welcome_email
76
+ end
77
+ end
78
+ end
79
+
80
+ module InstanceMethods
81
+ # Am I authenticated with given password?
82
+ #
83
+ # @param [String] plain-text password
84
+ # @return [true, false]
85
+ # @example
86
+ # user.authenticated?('password')
87
+ def authenticated?(password)
88
+ encrypted_password == encrypt(password)
89
+ end
90
+
91
+ # Set the remember token.
92
+ #
93
+ # @deprecated Use {#reset_remember_token!} instead
94
+ def remember_me!
95
+ warn "[DEPRECATION] remember_me!: use reset_remember_token! instead"
96
+ reset_remember_token!
97
+ end
98
+
99
+ # Reset the remember token.
100
+ #
101
+ # @example
102
+ # user.reset_remember_token!
103
+ def reset_remember_token!
104
+ generate_remember_token
105
+ save(false)
106
+ end
107
+
108
+ # Mark my account as forgotten password.
109
+ #
110
+ # @example
111
+ # user.forgot_password!
112
+ def forgot_password!
113
+ generate_password_reset_token
114
+ save(false)
115
+ end
116
+
117
+ # Update my password.
118
+ #
119
+ # @param [String, String] password and password confirmation
120
+ # @return [true, false] password was updated or not
121
+ # @example
122
+ # user.update_password('new-password', 'new-password')
123
+ def update_password(new_password, new_password_confirmation)
124
+ self.password = new_password
125
+ self.password_confirmation = new_password_confirmation
126
+ if valid?
127
+ self.password_reset_token = nil
128
+ end
129
+ save
130
+ end
131
+
132
+ def facebook_user?
133
+ !self.facebook_uid.blank?
134
+ end
135
+
136
+ ##
137
+ # Returns +true+ if the user is an admin.
138
+ #
139
+ def admin?
140
+ self.role == Admin
141
+ end
142
+
143
+ ##
144
+ # Returns the user's full name.
145
+ #
146
+ def name
147
+ "#{self.first_name} #{self.last_name}"
148
+ end
149
+
150
+ protected
151
+
152
+ def generate_hash(string)
153
+ Digest::SHA1.hexdigest(string)
154
+ end
155
+
156
+ def initialize_salt
157
+ if new_record?
158
+ self.salt = generate_hash("--#{Time.now.utc}--#{password}--#{rand}--")
159
+ end
160
+ end
161
+
162
+ def encrypt_password
163
+ return if password.blank?
164
+ self.encrypted_password = encrypt(password)
165
+ end
166
+
167
+ def encrypt(string)
168
+ generate_hash("--#{salt}--#{string}--")
169
+ end
170
+
171
+ def generate_password_reset_token
172
+ self.password_reset_token = encrypt("--#{Time.now.utc}--#{password}--#{rand}--")
173
+ end
174
+
175
+ def generate_remember_token
176
+ self.remember_token = encrypt("--#{Time.now.utc}--#{encrypted_password}--#{id}--#{rand}--")
177
+ end
178
+
179
+ # Always false. Override to allow other forms of authentication
180
+ # (username, facebook, etc).
181
+ # @return [Boolean] true if the email field be left blank for this user
182
+ def email_optional?
183
+ false
184
+ end
185
+
186
+ # True if the password has been set and the password is not being
187
+ # updated. Override to allow other forms of # authentication (username,
188
+ # facebook, etc).
189
+ # @return [Boolean] true if the password field can be left blank for this user
190
+ def password_optional?
191
+ facebook_user? || (encrypted_password.present? && password.blank?)
192
+ end
193
+
194
+ def password_required?
195
+ # warn "[DEPRECATION] password_required?: use !password_optional? instead"
196
+ !password_optional?
197
+ end
198
+
199
+ def send_welcome_email
200
+ Delayed::Job.enqueue DeliverWelcomeJob.new(self.id)
201
+ end
202
+
203
+ end
204
+
205
+ module ClassMethods
206
+ # Authenticate with email and password.
207
+ #
208
+ # @param [String, String] email and password
209
+ # @return [User, nil] authenticated user or nil
210
+ # @example
211
+ # User.authenticate("email@example.com", "password")
212
+ def authenticate(email, password)
213
+ return nil unless user = find_by_email(email)
214
+ return user if user.authenticated?(password)
215
+ end
216
+
217
+ def find_facebook_user(facebook_session, facebook_uid)
218
+ return nil unless BlueLightSpecial.configuration.use_facebook_connect && facebook_session && facebook_uid
219
+
220
+ begin
221
+ facebook_user = MiniFB::Session.new(BlueLightSpecial.configuration.facebook_api_key,
222
+ BlueLightSpecial.configuration.facebook_secret_key,
223
+ facebook_session, facebook_uid).user
224
+ rescue MiniFB::FaceBookError
225
+ facebook_user = nil
226
+ end
227
+ return nil unless facebook_user
228
+
229
+ user = ::User.find_by_facebook_uid(facebook_uid) || ::User.find_by_email(facebook_user['email']) || ::User.new
230
+ user.tap do |user|
231
+ user.facebook_uid = facebook_uid
232
+ user.email = facebook_user['email']
233
+ user.first_name = facebook_user['first_name']
234
+ user.last_name = facebook_user['last_name']
235
+ user.save
236
+ end
237
+ end
238
+ end
239
+
240
+ end
241
+ end