blue_light_special 0.2.0

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 (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