b4um_generators 0.1.1 → 0.2.8
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.
- checksums.yaml +4 -4
- data/.rubocop.yml +2 -2
- data/README.de.md +177 -5
- data/README.md +175 -5
- data/lib/b4um_generators/version.rb +1 -1
- data/lib/generators/b4um/attachment/attachment_generator.rb +514 -0
- data/lib/generators/b4um/authentication/authentication_generator.rb +769 -0
- data/lib/generators/b4um/authentication/templates/new.html.erb.tt +35 -0
- data/lib/generators/b4um/authentication/templates/sessions_controller.rb.tt +25 -0
- data/lib/generators/b4um/help/help_generator.rb +210 -5
- data/lib/generators/b4um/infinite_scroll/infinite_scroll_generator.rb +31 -0
- data/lib/generators/b4um/infinite_scroll/templates/infinite_scroll_controller.js +2 -2
- data/lib/generators/b4um/install/install_generator.rb +141 -2
- data/lib/generators/b4um/install/templates/b4um/forms.css +161 -1
- data/lib/generators/b4um/install/templates/b4um/navigation.css +13 -0
- data/lib/generators/b4um/install/templates/b4um/resources.css +18 -0
- data/lib/generators/b4um/install/templates/b4um.css +14 -0
- data/lib/generators/b4um/install/templates/navigation_helper.rb +13 -0
- data/lib/generators/b4um/readable/readable_generator.rb +77 -0
- data/lib/generators/b4um/search/search_generator.rb +58 -0
- metadata +7 -2
|
@@ -0,0 +1,769 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators"
|
|
4
|
+
|
|
5
|
+
module B4um
|
|
6
|
+
module Generators
|
|
7
|
+
class AuthenticationGenerator < Rails::Generators::Base
|
|
8
|
+
source_root File.expand_path("templates", __dir__)
|
|
9
|
+
|
|
10
|
+
desc "Installs session-based authentication for B4UM."
|
|
11
|
+
|
|
12
|
+
argument :authentication_model,
|
|
13
|
+
type: :string,
|
|
14
|
+
required: true,
|
|
15
|
+
banner: "MODEL"
|
|
16
|
+
|
|
17
|
+
class_option :protect,
|
|
18
|
+
type: :string,
|
|
19
|
+
required: false,
|
|
20
|
+
desc: "Comma-separated controllers to protect"
|
|
21
|
+
|
|
22
|
+
def validate_authentication_model
|
|
23
|
+
model_path = File.join(
|
|
24
|
+
destination_root,
|
|
25
|
+
"app/models",
|
|
26
|
+
"#{authentication_model_name}.rb"
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
unless File.exist?(model_path)
|
|
30
|
+
raise Thor::Error,
|
|
31
|
+
"Authentication model #{authentication_class_name} was not found. " \
|
|
32
|
+
"Expected: app/models/#{authentication_model_name}.rb"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
model = File.read(model_path)
|
|
36
|
+
|
|
37
|
+
unless model.match?(/\bhas_secure_password\b/)
|
|
38
|
+
raise Thor::Error,
|
|
39
|
+
"Authentication model #{authentication_class_name} must use " \
|
|
40
|
+
"has_secure_password."
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
return if password_digest_column_defined?
|
|
44
|
+
|
|
45
|
+
raise Thor::Error,
|
|
46
|
+
"Authentication model #{authentication_class_name} requires a " \
|
|
47
|
+
"password_digest column. " \
|
|
48
|
+
"Create the model with: " \
|
|
49
|
+
"bin/rails generate b4um:scaffold " \
|
|
50
|
+
"#{authentication_class_name} email:string password_digest:string"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def validate_protected_controllers
|
|
54
|
+
protected_controller_names.each do |controller_name|
|
|
55
|
+
controller_path = File.join(
|
|
56
|
+
"app/controllers",
|
|
57
|
+
"#{controller_name.underscore}_controller.rb"
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
full_controller_path = File.join(
|
|
61
|
+
destination_root,
|
|
62
|
+
controller_path
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
next if File.exist?(full_controller_path)
|
|
66
|
+
|
|
67
|
+
raise Thor::Error,
|
|
68
|
+
"Protected controller #{controller_name.camelize}Controller " \
|
|
69
|
+
"was not found: #{controller_path}"
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def validate_bcrypt
|
|
74
|
+
gemfile_path = File.join(
|
|
75
|
+
destination_root,
|
|
76
|
+
"Gemfile"
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
unless File.exist?(gemfile_path)
|
|
80
|
+
raise Thor::Error,
|
|
81
|
+
"Gemfile not found. bcrypt is required for authentication."
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
gemfile = File.read(gemfile_path)
|
|
85
|
+
|
|
86
|
+
return if gemfile.match?(
|
|
87
|
+
/^\s*gem\s+["']bcrypt["'](?:\s*,.*)?$/
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
raise Thor::Error,
|
|
91
|
+
"bcrypt is required for authentication. " \
|
|
92
|
+
"Run bin/rails generate b4um:install and enable bcrypt, " \
|
|
93
|
+
"or add gem \"bcrypt\" to your Gemfile and run bundle install."
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def create_sessions_controller
|
|
97
|
+
template(
|
|
98
|
+
"sessions_controller.rb.tt",
|
|
99
|
+
"app/controllers/sessions_controller.rb"
|
|
100
|
+
)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def create_login_view
|
|
104
|
+
template(
|
|
105
|
+
"new.html.erb.tt",
|
|
106
|
+
"app/views/sessions/new.html.erb"
|
|
107
|
+
)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def add_authentication_helpers
|
|
111
|
+
controller_path = "app/controllers/application_controller.rb"
|
|
112
|
+
full_controller_path = File.join(destination_root, controller_path)
|
|
113
|
+
|
|
114
|
+
unless File.exist?(full_controller_path)
|
|
115
|
+
raise Thor::Error,
|
|
116
|
+
"ApplicationController not found: #{controller_path}"
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
controller = File.read(full_controller_path)
|
|
120
|
+
|
|
121
|
+
return if controller.include?(
|
|
122
|
+
"helper_method :#{current_authentication_method}, :logged_in?"
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
inject_into_file(
|
|
126
|
+
controller_path,
|
|
127
|
+
before: /^end\s*$/
|
|
128
|
+
) do
|
|
129
|
+
<<~RUBY
|
|
130
|
+
|
|
131
|
+
helper_method :#{current_authentication_method}, :logged_in?
|
|
132
|
+
|
|
133
|
+
private
|
|
134
|
+
|
|
135
|
+
def #{current_authentication_method}
|
|
136
|
+
@#{current_authentication_method} ||= #{authentication_class_name}.find_by(id: session[:#{authentication_model_name}_id])
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def logged_in?
|
|
140
|
+
#{current_authentication_method}.present?
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def require_login
|
|
144
|
+
return if logged_in?
|
|
145
|
+
|
|
146
|
+
redirect_to login_path, alert: "Please log in first."
|
|
147
|
+
end
|
|
148
|
+
RUBY
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def add_session_routes
|
|
153
|
+
routes_path = "config/routes.rb"
|
|
154
|
+
full_routes_path = File.join(destination_root, routes_path)
|
|
155
|
+
|
|
156
|
+
routes = File.read(full_routes_path)
|
|
157
|
+
|
|
158
|
+
return if routes.include?(
|
|
159
|
+
'get "login", to: "sessions#new", as: :login'
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
inject_into_file(
|
|
163
|
+
routes_path,
|
|
164
|
+
before: /^end\s*$/
|
|
165
|
+
) do
|
|
166
|
+
<<~RUBY
|
|
167
|
+
get "login", to: "sessions#new", as: :login
|
|
168
|
+
post "login", to: "sessions#create"
|
|
169
|
+
delete "logout", to: "sessions#destroy", as: :logout
|
|
170
|
+
RUBY
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def add_navigation_links
|
|
175
|
+
navigation_path = "app/views/shared/_navigation.html.erb"
|
|
176
|
+
full_navigation_path = File.join(
|
|
177
|
+
destination_root,
|
|
178
|
+
navigation_path
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
return unless File.exist?(full_navigation_path)
|
|
182
|
+
|
|
183
|
+
navigation = File.read(full_navigation_path)
|
|
184
|
+
|
|
185
|
+
if navigation.include?(
|
|
186
|
+
'navigation_button_to "Logout", logout_path, method: :delete'
|
|
187
|
+
)
|
|
188
|
+
add_register_navigation_link(
|
|
189
|
+
navigation_path,
|
|
190
|
+
navigation
|
|
191
|
+
)
|
|
192
|
+
return
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
marker = "<%# B4UM_NAVIGATION_LINKS %>"
|
|
196
|
+
|
|
197
|
+
return unless navigation.include?(marker)
|
|
198
|
+
|
|
199
|
+
controller_name = authentication_model_name.pluralize
|
|
200
|
+
new_resource_path =
|
|
201
|
+
"new_#{authentication_model_name}_path"
|
|
202
|
+
|
|
203
|
+
authentication_links = <<~ERB
|
|
204
|
+
<% if logged_in? %>
|
|
205
|
+
<%= navigation_button_to "Logout", logout_path, method: :delete %>
|
|
206
|
+
<% else %>
|
|
207
|
+
<%= navigation_link_to "Register",
|
|
208
|
+
#{new_resource_path},
|
|
209
|
+
controller: :#{controller_name},
|
|
210
|
+
action: :new %>
|
|
211
|
+
<%= navigation_link_to "Login",
|
|
212
|
+
login_path,
|
|
213
|
+
controller: :sessions,
|
|
214
|
+
action: :new %>
|
|
215
|
+
<% end %>
|
|
216
|
+
|
|
217
|
+
<%# B4UM_NAVIGATION_LINKS %>
|
|
218
|
+
ERB
|
|
219
|
+
|
|
220
|
+
gsub_file(
|
|
221
|
+
navigation_path,
|
|
222
|
+
marker,
|
|
223
|
+
authentication_links.chomp
|
|
224
|
+
)
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def protect_authentication_resource
|
|
228
|
+
controller_path = authentication_resource_controller_path
|
|
229
|
+
full_controller_path = File.join(
|
|
230
|
+
destination_root,
|
|
231
|
+
controller_path
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
return unless File.exist?(full_controller_path)
|
|
235
|
+
|
|
236
|
+
add_authentication_resource_callbacks(controller_path)
|
|
237
|
+
add_authentication_resource_authorization(controller_path)
|
|
238
|
+
redirect_destroyed_authentication_resource_to_root(controller_path)
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def sign_in_after_registration
|
|
242
|
+
controller_path = authentication_resource_controller_path
|
|
243
|
+
|
|
244
|
+
full_controller_path = File.join(
|
|
245
|
+
destination_root,
|
|
246
|
+
controller_path
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
return unless File.exist?(full_controller_path)
|
|
250
|
+
|
|
251
|
+
controller = File.read(full_controller_path)
|
|
252
|
+
|
|
253
|
+
session_assignment =
|
|
254
|
+
"session[:#{authentication_model_name}_id] = " \
|
|
255
|
+
"@#{authentication_model_name}.id"
|
|
256
|
+
|
|
257
|
+
return if controller.include?(session_assignment)
|
|
258
|
+
|
|
259
|
+
save_pattern =
|
|
260
|
+
/^(\s*)if @#{Regexp.escape(authentication_model_name)}\.save\s*$/
|
|
261
|
+
|
|
262
|
+
return unless controller.match?(save_pattern)
|
|
263
|
+
|
|
264
|
+
inject_into_file(
|
|
265
|
+
controller_path,
|
|
266
|
+
after: save_pattern
|
|
267
|
+
) do
|
|
268
|
+
"\n\\1 #{session_assignment}"
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
def add_profile_navigation
|
|
273
|
+
navigation_path = "app/views/shared/_navigation.html.erb"
|
|
274
|
+
|
|
275
|
+
full_navigation_path = File.join(
|
|
276
|
+
destination_root,
|
|
277
|
+
navigation_path
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
return unless File.exist?(full_navigation_path)
|
|
281
|
+
|
|
282
|
+
navigation = File.read(full_navigation_path)
|
|
283
|
+
|
|
284
|
+
resource_name = authentication_class_name.pluralize
|
|
285
|
+
controller_name = authentication_model_name.pluralize
|
|
286
|
+
|
|
287
|
+
resource_pattern = /
|
|
288
|
+
^[ \t]*<%=\s*navigation_link_to\s+"#{Regexp.escape(resource_name)}",
|
|
289
|
+
.*?
|
|
290
|
+
controller:\s*:#{Regexp.escape(controller_name)}
|
|
291
|
+
\s*%>\s*
|
|
292
|
+
/mx
|
|
293
|
+
|
|
294
|
+
if navigation.match?(resource_pattern)
|
|
295
|
+
gsub_file(
|
|
296
|
+
navigation_path,
|
|
297
|
+
resource_pattern,
|
|
298
|
+
""
|
|
299
|
+
)
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
navigation = File.read(full_navigation_path)
|
|
303
|
+
|
|
304
|
+
return if navigation.include?(
|
|
305
|
+
'navigation_link_to "Profile"'
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
login_marker = "<% if logged_in? %>"
|
|
309
|
+
|
|
310
|
+
return unless navigation.include?(login_marker)
|
|
311
|
+
|
|
312
|
+
profile_link = <<~ERB
|
|
313
|
+
<% if logged_in? %>
|
|
314
|
+
<%= navigation_link_to "Profile",
|
|
315
|
+
#{current_authentication_method},
|
|
316
|
+
controller: :#{controller_name},
|
|
317
|
+
action: :show %>
|
|
318
|
+
ERB
|
|
319
|
+
|
|
320
|
+
gsub_file(
|
|
321
|
+
navigation_path,
|
|
322
|
+
login_marker,
|
|
323
|
+
profile_link.chomp
|
|
324
|
+
)
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
def protect_controllers
|
|
328
|
+
protected_controller_names.each do |controller_name|
|
|
329
|
+
controller_path = File.join(
|
|
330
|
+
"app/controllers",
|
|
331
|
+
"#{controller_name.underscore}_controller.rb"
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
full_controller_path = File.join(
|
|
335
|
+
destination_root,
|
|
336
|
+
controller_path
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
controller = File.read(full_controller_path)
|
|
340
|
+
|
|
341
|
+
next if controller.include?(
|
|
342
|
+
"before_action :require_login, except: [:index, :show]"
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
inject_into_file(
|
|
346
|
+
controller_path,
|
|
347
|
+
after: /^class .*Controller < ApplicationController\s*$/
|
|
348
|
+
) do
|
|
349
|
+
"\n before_action :require_login, except: [:index, :show]"
|
|
350
|
+
end
|
|
351
|
+
end
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
def protect_show_actions
|
|
355
|
+
protected_controller_names.each do |controller_name|
|
|
356
|
+
protect_show_actions_for(controller_name)
|
|
357
|
+
end
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
def protect_index_actions
|
|
361
|
+
protected_controller_names.each do |controller_name|
|
|
362
|
+
protect_index_actions_for(controller_name)
|
|
363
|
+
end
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
private
|
|
367
|
+
|
|
368
|
+
def add_register_navigation_link(navigation_path, navigation)
|
|
369
|
+
return if navigation.include?(
|
|
370
|
+
'navigation_link_to "Register",'
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
controller_name = authentication_model_name.pluralize
|
|
374
|
+
new_resource_path =
|
|
375
|
+
"new_#{authentication_model_name}_path"
|
|
376
|
+
|
|
377
|
+
login_pattern = /
|
|
378
|
+
^([ \t]*)<%=\s*navigation_link_to\s+"Login",
|
|
379
|
+
/x
|
|
380
|
+
|
|
381
|
+
return unless navigation.match?(login_pattern)
|
|
382
|
+
|
|
383
|
+
register_link = <<~ERB
|
|
384
|
+
<%= navigation_link_to "Register",
|
|
385
|
+
#{new_resource_path},
|
|
386
|
+
controller: :#{controller_name},
|
|
387
|
+
action: :new %>
|
|
388
|
+
ERB
|
|
389
|
+
|
|
390
|
+
gsub_file(
|
|
391
|
+
navigation_path,
|
|
392
|
+
login_pattern
|
|
393
|
+
) do |match|
|
|
394
|
+
indentation = match[/\A[ \t]*/]
|
|
395
|
+
|
|
396
|
+
indented_register = register_link.lines.map do |line|
|
|
397
|
+
"#{indentation}#{line}"
|
|
398
|
+
end.join
|
|
399
|
+
|
|
400
|
+
"#{indented_register}#{match}"
|
|
401
|
+
end
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
def authentication_resource_controller_path
|
|
405
|
+
File.join(
|
|
406
|
+
"app/controllers",
|
|
407
|
+
"#{authentication_model_name.pluralize}_controller.rb"
|
|
408
|
+
)
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
def redirect_destroyed_authentication_resource_to_root(controller_path)
|
|
412
|
+
resource_path =
|
|
413
|
+
"#{authentication_model_name.pluralize}_path"
|
|
414
|
+
|
|
415
|
+
redirect =
|
|
416
|
+
"redirect_to #{resource_path}, status: :see_other"
|
|
417
|
+
|
|
418
|
+
return unless controller_contains?(controller_path, redirect)
|
|
419
|
+
|
|
420
|
+
gsub_file(
|
|
421
|
+
controller_path,
|
|
422
|
+
redirect,
|
|
423
|
+
"redirect_to root_path, status: :see_other"
|
|
424
|
+
)
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
def add_authentication_resource_callbacks(controller_path)
|
|
428
|
+
add_require_login_callback(controller_path)
|
|
429
|
+
add_prevent_authentication_index_callback(controller_path)
|
|
430
|
+
add_require_current_authentication_callback(controller_path)
|
|
431
|
+
add_clear_authentication_session_callback(controller_path)
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
def add_require_login_callback(controller_path)
|
|
435
|
+
callback = "before_action :require_login, except: %i[ new create ]"
|
|
436
|
+
|
|
437
|
+
return if controller_contains?(controller_path, callback)
|
|
438
|
+
|
|
439
|
+
inject_into_file(
|
|
440
|
+
controller_path,
|
|
441
|
+
after: /^class .*Controller < ApplicationController\s*$/
|
|
442
|
+
) do
|
|
443
|
+
"\n #{callback}"
|
|
444
|
+
end
|
|
445
|
+
end
|
|
446
|
+
|
|
447
|
+
def add_prevent_authentication_index_callback(controller_path)
|
|
448
|
+
callback =
|
|
449
|
+
"before_action :prevent_authentication_index, only: :index"
|
|
450
|
+
|
|
451
|
+
return if controller_contains?(controller_path, callback)
|
|
452
|
+
|
|
453
|
+
inject_into_file(
|
|
454
|
+
controller_path,
|
|
455
|
+
after: /^\s*before_action :require_login, except: %i\[ new create \]\s*$/
|
|
456
|
+
) do
|
|
457
|
+
"\n #{callback}"
|
|
458
|
+
end
|
|
459
|
+
end
|
|
460
|
+
|
|
461
|
+
def add_require_current_authentication_callback(controller_path)
|
|
462
|
+
callback =
|
|
463
|
+
"before_action :require_current_#{authentication_model_name}, " \
|
|
464
|
+
"only: %i[ show edit update destroy ]"
|
|
465
|
+
|
|
466
|
+
return if controller_contains?(controller_path, callback)
|
|
467
|
+
|
|
468
|
+
set_callback =
|
|
469
|
+
/^\s*before_action :set_#{authentication_model_name}, only: %i\[ show edit update destroy \]\s*$/
|
|
470
|
+
|
|
471
|
+
if controller_matches?(controller_path, set_callback)
|
|
472
|
+
inject_into_file(
|
|
473
|
+
controller_path,
|
|
474
|
+
after: set_callback
|
|
475
|
+
) do
|
|
476
|
+
"\n #{callback}"
|
|
477
|
+
end
|
|
478
|
+
else
|
|
479
|
+
inject_into_file(
|
|
480
|
+
controller_path,
|
|
481
|
+
after: /^class .*Controller < ApplicationController\s*$/
|
|
482
|
+
) do
|
|
483
|
+
"\n #{callback}"
|
|
484
|
+
end
|
|
485
|
+
end
|
|
486
|
+
end
|
|
487
|
+
|
|
488
|
+
def add_clear_authentication_session_callback(controller_path)
|
|
489
|
+
callback =
|
|
490
|
+
"after_action :clear_authentication_session, only: :destroy"
|
|
491
|
+
|
|
492
|
+
return if controller_contains?(controller_path, callback)
|
|
493
|
+
|
|
494
|
+
current_user_callback =
|
|
495
|
+
/^\s*before_action :require_current_#{authentication_model_name}, only: %i\[ show edit update destroy \]\s*$/
|
|
496
|
+
|
|
497
|
+
inject_into_file(
|
|
498
|
+
controller_path,
|
|
499
|
+
after: current_user_callback
|
|
500
|
+
) do
|
|
501
|
+
"\n #{callback}"
|
|
502
|
+
end
|
|
503
|
+
end
|
|
504
|
+
|
|
505
|
+
def add_authentication_resource_authorization(controller_path)
|
|
506
|
+
add_prevent_authentication_index_method(controller_path)
|
|
507
|
+
add_require_current_authentication_method(controller_path)
|
|
508
|
+
add_clear_authentication_session_method(controller_path)
|
|
509
|
+
end
|
|
510
|
+
|
|
511
|
+
def add_prevent_authentication_index_method(controller_path)
|
|
512
|
+
method_name = "def prevent_authentication_index"
|
|
513
|
+
|
|
514
|
+
return if controller_contains?(controller_path, method_name)
|
|
515
|
+
|
|
516
|
+
inject_private_method(
|
|
517
|
+
controller_path,
|
|
518
|
+
<<~RUBY
|
|
519
|
+
def prevent_authentication_index
|
|
520
|
+
redirect_to #{current_authentication_method}
|
|
521
|
+
end
|
|
522
|
+
RUBY
|
|
523
|
+
)
|
|
524
|
+
end
|
|
525
|
+
|
|
526
|
+
def add_require_current_authentication_method(controller_path)
|
|
527
|
+
method_name =
|
|
528
|
+
"def require_current_#{authentication_model_name}"
|
|
529
|
+
|
|
530
|
+
return if controller_contains?(controller_path, method_name)
|
|
531
|
+
|
|
532
|
+
inject_private_method(
|
|
533
|
+
controller_path,
|
|
534
|
+
<<~RUBY
|
|
535
|
+
def require_current_#{authentication_model_name}
|
|
536
|
+
return if @#{authentication_model_name} == #{current_authentication_method}
|
|
537
|
+
|
|
538
|
+
redirect_to root_path, alert: "Access denied."
|
|
539
|
+
end
|
|
540
|
+
RUBY
|
|
541
|
+
)
|
|
542
|
+
end
|
|
543
|
+
|
|
544
|
+
def add_clear_authentication_session_method(controller_path)
|
|
545
|
+
method_name = "def clear_authentication_session"
|
|
546
|
+
|
|
547
|
+
return if controller_contains?(controller_path, method_name)
|
|
548
|
+
|
|
549
|
+
inject_private_method(
|
|
550
|
+
controller_path,
|
|
551
|
+
<<~RUBY
|
|
552
|
+
def clear_authentication_session
|
|
553
|
+
session.delete(:#{authentication_model_name}_id)
|
|
554
|
+
end
|
|
555
|
+
RUBY
|
|
556
|
+
)
|
|
557
|
+
end
|
|
558
|
+
|
|
559
|
+
def inject_private_method(controller_path, method_body)
|
|
560
|
+
full_controller_path = File.join(
|
|
561
|
+
destination_root,
|
|
562
|
+
controller_path
|
|
563
|
+
)
|
|
564
|
+
|
|
565
|
+
content = File.read(full_controller_path)
|
|
566
|
+
|
|
567
|
+
class_end_position = content.rindex(/^end\s*$/)
|
|
568
|
+
|
|
569
|
+
unless class_end_position
|
|
570
|
+
raise Thor::Error,
|
|
571
|
+
"Could not find the closing class end in #{controller_path}"
|
|
572
|
+
end
|
|
573
|
+
|
|
574
|
+
indented_body = method_body.lines.map do |line|
|
|
575
|
+
line.strip.empty? ? "\n" : " #{line}"
|
|
576
|
+
end.join
|
|
577
|
+
|
|
578
|
+
updated_content = content.dup
|
|
579
|
+
|
|
580
|
+
updated_content.insert(
|
|
581
|
+
class_end_position,
|
|
582
|
+
"\n#{indented_body}"
|
|
583
|
+
)
|
|
584
|
+
|
|
585
|
+
File.write(
|
|
586
|
+
full_controller_path,
|
|
587
|
+
updated_content
|
|
588
|
+
)
|
|
589
|
+
end
|
|
590
|
+
|
|
591
|
+
def controller_contains?(controller_path, content)
|
|
592
|
+
full_controller_path = File.join(
|
|
593
|
+
destination_root,
|
|
594
|
+
controller_path
|
|
595
|
+
)
|
|
596
|
+
|
|
597
|
+
File.read(full_controller_path).include?(content)
|
|
598
|
+
end
|
|
599
|
+
|
|
600
|
+
def controller_matches?(controller_path, pattern)
|
|
601
|
+
full_controller_path = File.join(
|
|
602
|
+
destination_root,
|
|
603
|
+
controller_path
|
|
604
|
+
)
|
|
605
|
+
|
|
606
|
+
File.read(full_controller_path).match?(pattern)
|
|
607
|
+
end
|
|
608
|
+
|
|
609
|
+
def protect_index_actions_for(controller_name)
|
|
610
|
+
index_path = File.join(
|
|
611
|
+
"app/views",
|
|
612
|
+
controller_name.underscore,
|
|
613
|
+
"index.html.erb"
|
|
614
|
+
)
|
|
615
|
+
|
|
616
|
+
full_index_path = File.join(
|
|
617
|
+
destination_root,
|
|
618
|
+
index_path
|
|
619
|
+
)
|
|
620
|
+
|
|
621
|
+
return unless File.exist?(full_index_path)
|
|
622
|
+
|
|
623
|
+
content = File.read(full_index_path)
|
|
624
|
+
|
|
625
|
+
return if content.include?("<% if logged_in? %>")
|
|
626
|
+
|
|
627
|
+
pattern = /
|
|
628
|
+
^[ \t]*<%=\s*link_to\s+"New\s+[^"]+",.*?%>
|
|
629
|
+
/mx
|
|
630
|
+
|
|
631
|
+
return unless content.match?(pattern)
|
|
632
|
+
|
|
633
|
+
wrap_protected_action(index_path, pattern)
|
|
634
|
+
end
|
|
635
|
+
|
|
636
|
+
def protect_show_actions_for(controller_name)
|
|
637
|
+
show_path = File.join(
|
|
638
|
+
"app/views",
|
|
639
|
+
controller_name.underscore,
|
|
640
|
+
"show.html.erb"
|
|
641
|
+
)
|
|
642
|
+
|
|
643
|
+
full_show_path = File.join(destination_root, show_path)
|
|
644
|
+
|
|
645
|
+
return unless File.exist?(full_show_path)
|
|
646
|
+
|
|
647
|
+
content = File.read(full_show_path)
|
|
648
|
+
|
|
649
|
+
return if content.include?("<% if logged_in? %>")
|
|
650
|
+
|
|
651
|
+
protect_show_edit_action(show_path, content)
|
|
652
|
+
protect_show_destroy_action(show_path, content)
|
|
653
|
+
end
|
|
654
|
+
|
|
655
|
+
def protect_show_edit_action(show_path, content)
|
|
656
|
+
pattern = /
|
|
657
|
+
^[ \t]*<%=\s*link_to\s+"Edit",.*?%>
|
|
658
|
+
/mx
|
|
659
|
+
|
|
660
|
+
return unless content.match?(pattern)
|
|
661
|
+
|
|
662
|
+
wrap_protected_action(show_path, pattern)
|
|
663
|
+
end
|
|
664
|
+
|
|
665
|
+
def protect_show_destroy_action(show_path, content)
|
|
666
|
+
pattern = /
|
|
667
|
+
^[ \t]*<%=\s*button_to\s+"Destroy",.*?%>
|
|
668
|
+
/mx
|
|
669
|
+
|
|
670
|
+
return unless content.match?(pattern)
|
|
671
|
+
|
|
672
|
+
wrap_protected_action(show_path, pattern)
|
|
673
|
+
end
|
|
674
|
+
|
|
675
|
+
def wrap_protected_action(view_path, pattern)
|
|
676
|
+
gsub_file(view_path, pattern) do |match|
|
|
677
|
+
lines = match.lines
|
|
678
|
+
indentation = lines.first[/\A\s*/]
|
|
679
|
+
|
|
680
|
+
indented_match = lines.map do |line|
|
|
681
|
+
"#{indentation} #{line.delete_prefix(indentation)}"
|
|
682
|
+
end.join
|
|
683
|
+
|
|
684
|
+
[
|
|
685
|
+
"#{indentation}<% if logged_in? %>",
|
|
686
|
+
indented_match.chomp,
|
|
687
|
+
"#{indentation}<% end %>"
|
|
688
|
+
].join("\n")
|
|
689
|
+
end
|
|
690
|
+
end
|
|
691
|
+
|
|
692
|
+
def protected_controller_names
|
|
693
|
+
options[:protect]
|
|
694
|
+
.to_s
|
|
695
|
+
.split(",")
|
|
696
|
+
.map(&:strip)
|
|
697
|
+
.reject(&:empty?)
|
|
698
|
+
end
|
|
699
|
+
|
|
700
|
+
def authentication_class_name
|
|
701
|
+
authentication_model.to_s.classify
|
|
702
|
+
end
|
|
703
|
+
|
|
704
|
+
def authentication_model_name
|
|
705
|
+
authentication_model.to_s.underscore
|
|
706
|
+
end
|
|
707
|
+
|
|
708
|
+
def current_authentication_method
|
|
709
|
+
"current_#{authentication_model_name}"
|
|
710
|
+
end
|
|
711
|
+
|
|
712
|
+
def password_digest_column_defined?
|
|
713
|
+
password_digest_defined_in_schema? ||
|
|
714
|
+
password_digest_defined_in_migrations?
|
|
715
|
+
end
|
|
716
|
+
|
|
717
|
+
def password_digest_defined_in_schema?
|
|
718
|
+
schema_path = File.join(
|
|
719
|
+
destination_root,
|
|
720
|
+
"db/schema.rb"
|
|
721
|
+
)
|
|
722
|
+
|
|
723
|
+
return false unless File.exist?(schema_path)
|
|
724
|
+
|
|
725
|
+
schema = File.read(schema_path)
|
|
726
|
+
table_name = authentication_model_name.pluralize
|
|
727
|
+
|
|
728
|
+
table_match = schema.match(
|
|
729
|
+
/create_table\s+["']#{Regexp.escape(table_name)}["'].*?do\s+\|t\|(.*?)^\s*end/m
|
|
730
|
+
)
|
|
731
|
+
|
|
732
|
+
return false unless table_match
|
|
733
|
+
|
|
734
|
+
table_match[1].match?(
|
|
735
|
+
/t\.\w+\s+["']password_digest["']/
|
|
736
|
+
)
|
|
737
|
+
end
|
|
738
|
+
|
|
739
|
+
def password_digest_defined_in_migrations?
|
|
740
|
+
migration_paths = Dir.glob(
|
|
741
|
+
File.join(
|
|
742
|
+
destination_root,
|
|
743
|
+
"db/migrate/*.rb"
|
|
744
|
+
)
|
|
745
|
+
)
|
|
746
|
+
|
|
747
|
+
table_name = authentication_model_name.pluralize
|
|
748
|
+
|
|
749
|
+
migration_paths.any? do |migration_path|
|
|
750
|
+
migration = File.read(migration_path)
|
|
751
|
+
|
|
752
|
+
references_table =
|
|
753
|
+
migration.match?(
|
|
754
|
+
/create_table\s+[:'"]#{Regexp.escape(table_name)}['"]?/
|
|
755
|
+
) ||
|
|
756
|
+
migration.match?(
|
|
757
|
+
/change_table\s+[:'"]#{Regexp.escape(table_name)}['"]?/
|
|
758
|
+
) ||
|
|
759
|
+
migration.match?(
|
|
760
|
+
/add_column\s+[:'"]#{Regexp.escape(table_name)}['"]?/
|
|
761
|
+
)
|
|
762
|
+
|
|
763
|
+
references_table &&
|
|
764
|
+
migration.match?(/\bpassword_digest\b/)
|
|
765
|
+
end
|
|
766
|
+
end
|
|
767
|
+
end
|
|
768
|
+
end
|
|
769
|
+
end
|