trusty-cms 7.1.2 → 7.1.3

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 (56) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +4 -3
  3. data/Gemfile.lock +58 -46
  4. data/README.md +2 -0
  5. data/app/controllers/admin/resource_controller.rb +1 -1
  6. data/app/controllers/application_controller.rb +15 -0
  7. data/app/models/asset.rb +29 -5
  8. data/app/models/standard_tags.rb +13 -7
  9. data/app/models/user.rb +13 -0
  10. data/lib/trusty_cms/site_scope_auth_reporter.rb +108 -0
  11. data/lib/trusty_cms/version.rb +1 -1
  12. data/package.json +1 -1
  13. data/spec/controllers/admin/snippets_controller_spec.rb +95 -0
  14. data/spec/controllers/site_controller_spec.rb +63 -0
  15. data/spec/factories/site.rb +8 -0
  16. data/spec/helpers/admin/references_helper_spec.rb +54 -0
  17. data/spec/helpers/admin/url_helper_spec.rb +71 -0
  18. data/spec/helpers/application_helper_spec.rb +102 -0
  19. data/spec/helpers/scoped_helper_spec.rb +60 -0
  20. data/spec/lib/active_record_extensions_spec.rb +65 -0
  21. data/spec/lib/login_system_spec.rb +61 -0
  22. data/spec/lib/ostruct_spec.rb +33 -0
  23. data/spec/lib/string_extensions_spec.rb +48 -0
  24. data/spec/lib/symbol_extensions_spec.rb +14 -0
  25. data/spec/lib/trusty_cms/config_cache_spec.rb +73 -0
  26. data/spec/models/admins_site_spec.rb +19 -0
  27. data/spec/models/asset_spec.rb +224 -0
  28. data/spec/models/asset_type_spec.rb +137 -0
  29. data/spec/models/file_not_found_page_spec.rb +33 -0
  30. data/spec/models/haml_filter_spec.rb +30 -0
  31. data/spec/models/menu_renderer_spec.rb +63 -0
  32. data/spec/models/page_attachment_spec.rb +29 -0
  33. data/spec/models/page_context_spec.rb +57 -0
  34. data/spec/models/page_field_spec.rb +15 -0
  35. data/spec/models/page_part_spec.rb +33 -0
  36. data/spec/models/page_spec.rb +184 -0
  37. data/spec/models/rails_page_spec.rb +32 -0
  38. data/spec/models/site_spec.rb +93 -0
  39. data/spec/models/snippet_finder_spec.rb +27 -0
  40. data/spec/models/snippet_tags_spec.rb +45 -0
  41. data/spec/models/standard_tags/children_spec.rb +159 -0
  42. data/spec/models/standard_tags/content_spec.rb +168 -0
  43. data/spec/models/standard_tags/meta_spec.rb +86 -0
  44. data/spec/models/standard_tags_spec.rb +32 -0
  45. data/spec/models/status_spec.rb +63 -0
  46. data/spec/models/text_filter_spec.rb +47 -0
  47. data/spec/models/trusty_cms/config_spec.rb +69 -0
  48. data/spec/models/trusty_cms/page_response_cache_director_spec.rb +72 -0
  49. data/spec/models/user_action_observer_spec.rb +39 -0
  50. data/spec/models/user_serialize_telemetry_spec.rb +124 -0
  51. data/spec/rails_helper.rb +13 -6
  52. data/spec/requests/admin/snippets_spec.rb +81 -0
  53. data/spec/spec_helper.rb +17 -3
  54. data/trusty_cms.gemspec +3 -3
  55. data/yarn.lock +273 -259
  56. metadata +91 -8
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8f28f9884a07d4a5b4c582c2e490af233fa40d9e275bf531c0e103f8a16fb1d2
4
- data.tar.gz: 1296987f36ad6cdd537e654978fca42b74519b1c727474c3725d1e81afc6a662
3
+ metadata.gz: 9f50633a419203ee6b0b6e35814de7af711b70dd362d5fe0f712543d31f1fc75
4
+ data.tar.gz: f5ccf44bd927e9a392e9979388c9d6f65ace4c1f965516d64936ee1da5e50d53
5
5
  SHA512:
6
- metadata.gz: 30e7296886c47d33276f7612b24c24a310a45c99c60c7907b52bdbf6b11c264c8ae4705ccfe4e187c7b0bd2d11cc6077d12ad7f54e9e9e3cf413a283bd7211a3
7
- data.tar.gz: f41957d9f3c0e03e8e4af00eda15710e577350659aa7b41f6182c01b31d9e8db3d326c9fe791dd009b5cae6c92ef1bc564597bd5fc0815fb4c0b2c9f283e5d9a
6
+ metadata.gz: 8324822cde6d47651420dc28038f51e629e1b411c276a0ddded87dac9587a571de5218e36452f929d0e5c55c4106aa47a21cf72f14e06c78b545c849cfafed6a
7
+ data.tar.gz: 4e4dc362cd53e8124eaba08331db95eb085fe28b8778776ceef121a7e3b993f379b26d2c56c17538962adca2e2b47eb0e834c16d6f67f4a45b4a6cbab9a33cb3
data/Gemfile CHANGED
@@ -16,18 +16,19 @@ group :development, :test do
16
16
  gem 'acts_as_list'
17
17
  gem 'database_cleaner'
18
18
  gem 'devise-two-factor'
19
- gem 'factory_bot_rails', '6.4.4'
19
+ gem 'factory_bot_rails', '6.5.1'
20
20
  gem 'file_validators'
21
21
  gem 'jsbundling-rails', '~> 1.3'
22
22
  gem 'launchy', '~> 3.0.1'
23
23
  gem 'mysql2'
24
24
  gem 'paper_trail', '~> 16.0.0'
25
- gem 'paper_trail-association_tracking', '~> 2.2.1'
25
+ gem 'paper_trail-association_tracking', '~> 2.3.0'
26
26
  gem 'poltergeist', '~> 1.18.1'
27
27
  gem 'pry-byebug'
28
- gem 'psych', '5.2.2'
28
+ gem 'psych', '5.4.0'
29
29
  gem 'rails-observers'
30
30
  gem 'ransack'
31
+ gem 'rspec_junit_formatter'
31
32
  gem 'rspec-rails', '~> 7.1.1'
32
33
  gem 'simplecov'
33
34
  gem 'simplecov-lcov'
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- trusty-cms (7.1.2)
4
+ trusty-cms (7.1.3)
5
5
  RedCloth (= 4.3.4)
6
6
  activestorage-validator
7
7
  acts_as_list (>= 0.9.5, < 1.3.0)
@@ -24,15 +24,15 @@ PATH
24
24
  mutex_m
25
25
  mysql2
26
26
  paper_trail (~> 16.0.0)
27
- paper_trail-association_tracking (~> 2.2.1)
28
- psych (= 5.2.2)
27
+ paper_trail-association_tracking (>= 2.2.1, < 2.4.0)
28
+ psych (= 5.4.0)
29
29
  rack (>= 2.0.1, < 3.2.0)
30
30
  rack-cache (~> 1.7)
31
31
  radius (~> 0.8)
32
32
  rails (~> 7.2.2.1)
33
33
  rake (< 14.0)
34
34
  ransack (~> 4.2.1)
35
- rdoc (>= 5.1, < 7.0)
35
+ rdoc (>= 5.1, < 9.0)
36
36
  roadie-rails
37
37
  rqrcode
38
38
  sass-rails
@@ -119,7 +119,7 @@ GEM
119
119
  minitest (>= 5.1)
120
120
  securerandom (>= 0.3)
121
121
  tzinfo (~> 2.0, >= 2.0.5)
122
- acts_as_list (1.2.4)
122
+ acts_as_list (1.2.6)
123
123
  activerecord (>= 6.1)
124
124
  activesupport (>= 6.1)
125
125
  acts_as_tree (2.9.1)
@@ -131,7 +131,8 @@ GEM
131
131
  benchmark (0.5.0)
132
132
  bigdecimal (4.1.2)
133
133
  builder (3.3.0)
134
- byebug (11.1.3)
134
+ byebug (13.0.0)
135
+ reline (>= 0.6.0)
135
136
  capybara (3.40.0)
136
137
  addressable
137
138
  matrix
@@ -146,11 +147,12 @@ GEM
146
147
  chunky_png (1.4.0)
147
148
  cliver (0.3.2)
148
149
  coderay (1.1.3)
149
- concurrent-ruby (1.3.6)
150
+ concurrent-ruby (1.3.7)
150
151
  connection_pool (3.0.2)
151
- crass (1.0.6)
152
- css_parser (1.22.0)
152
+ crass (1.0.7)
153
+ css_parser (3.0.0)
153
154
  addressable
155
+ ssrf_filter (~> 1.5)
154
156
  csso-rails (0.9.0)
155
157
  execjs (>= 1)
156
158
  csv (3.3.0)
@@ -181,11 +183,11 @@ GEM
181
183
  erb (6.0.4)
182
184
  erubi (1.13.1)
183
185
  execjs (2.10.0)
184
- factory_bot (6.5.0)
185
- activesupport (>= 5.0.0)
186
- factory_bot_rails (6.4.4)
186
+ factory_bot (6.6.0)
187
+ activesupport (>= 6.1.0)
188
+ factory_bot_rails (6.5.1)
187
189
  factory_bot (~> 6.5)
188
- railties (>= 5.0.0)
190
+ railties (>= 6.1.0)
189
191
  ffi (1.17.0-aarch64-linux-gnu)
190
192
  ffi (1.17.0-aarch64-linux-musl)
191
193
  ffi (1.17.0-arm-linux-gnu)
@@ -216,7 +218,7 @@ GEM
216
218
  csv
217
219
  mini_mime (>= 1.0.0)
218
220
  multi_xml (>= 0.5.2)
219
- i18n (1.14.8)
221
+ i18n (1.15.2)
220
222
  concurrent-ruby (~> 1.0)
221
223
  image_processing (1.13.0)
222
224
  mini_magick (>= 4.9.5, < 5)
@@ -246,7 +248,7 @@ GEM
246
248
  libv8-node (18.19.0.0-x86_64-linux)
247
249
  libv8-node (18.19.0.0-x86_64-linux-musl)
248
250
  logger (1.7.0)
249
- loofah (2.25.1)
251
+ loofah (2.25.2)
250
252
  crass (~> 1.0.2)
251
253
  nokogiri (>= 1.12.0)
252
254
  mail (2.8.1)
@@ -284,46 +286,47 @@ GEM
284
286
  net-smtp (0.5.1)
285
287
  net-protocol
286
288
  nio4r (2.7.4)
287
- nokogiri (1.19.3)
289
+ nokogiri (1.19.4)
288
290
  mini_portile2 (~> 2.8.2)
289
291
  racc (~> 1.4)
290
- nokogiri (1.19.3-aarch64-linux-gnu)
292
+ nokogiri (1.19.4-aarch64-linux-gnu)
291
293
  racc (~> 1.4)
292
- nokogiri (1.19.3-aarch64-linux-musl)
294
+ nokogiri (1.19.4-aarch64-linux-musl)
293
295
  racc (~> 1.4)
294
- nokogiri (1.19.3-arm-linux-gnu)
296
+ nokogiri (1.19.4-arm-linux-gnu)
295
297
  racc (~> 1.4)
296
- nokogiri (1.19.3-arm-linux-musl)
298
+ nokogiri (1.19.4-arm-linux-musl)
297
299
  racc (~> 1.4)
298
- nokogiri (1.19.3-arm64-darwin)
300
+ nokogiri (1.19.4-arm64-darwin)
299
301
  racc (~> 1.4)
300
- nokogiri (1.19.3-x86_64-darwin)
302
+ nokogiri (1.19.4-x86_64-darwin)
301
303
  racc (~> 1.4)
302
- nokogiri (1.19.3-x86_64-linux-gnu)
304
+ nokogiri (1.19.4-x86_64-linux-gnu)
303
305
  racc (~> 1.4)
304
- nokogiri (1.19.3-x86_64-linux-musl)
306
+ nokogiri (1.19.4-x86_64-linux-musl)
305
307
  racc (~> 1.4)
306
308
  orm_adapter (0.5.0)
307
309
  paper_trail (16.0.0)
308
310
  activerecord (>= 6.1)
309
311
  request_store (~> 1.4)
310
- paper_trail-association_tracking (2.2.1)
312
+ paper_trail-association_tracking (2.3.0)
311
313
  paper_trail (>= 12.0)
312
314
  poltergeist (1.18.1)
313
315
  capybara (>= 2.1, < 4)
314
316
  cliver (~> 0.3.1)
315
317
  websocket-driver (>= 0.2.0)
316
- pp (0.6.3)
318
+ pp (0.6.4)
317
319
  prettyprint
318
320
  prettyprint (0.2.0)
319
321
  prism (1.9.0)
320
- pry (0.14.2)
322
+ pry (0.16.0)
321
323
  coderay (~> 1.1)
322
324
  method_source (~> 1.0)
323
- pry-byebug (3.10.1)
324
- byebug (~> 11.0)
325
- pry (>= 0.13, < 0.15)
326
- psych (5.2.2)
325
+ reline (>= 0.6.0)
326
+ pry-byebug (3.12.0)
327
+ byebug (~> 13.0)
328
+ pry (>= 0.13, < 0.17)
329
+ psych (5.4.0)
327
330
  date
328
331
  stringio
329
332
  public_suffix (7.0.5)
@@ -357,8 +360,8 @@ GEM
357
360
  activesupport (>= 5.0.0)
358
361
  minitest
359
362
  nokogiri (>= 1.6)
360
- rails-html-sanitizer (1.7.0)
361
- loofah (~> 2.25)
363
+ rails-html-sanitizer (1.7.1)
364
+ loofah (~> 2.25, >= 2.25.2)
362
365
  nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
363
366
  rails-observers (0.1.5)
364
367
  activemodel (>= 4.0)
@@ -375,9 +378,14 @@ GEM
375
378
  activerecord (>= 6.1.5)
376
379
  activesupport (>= 6.1.5)
377
380
  i18n
378
- rdoc (6.17.0)
381
+ rbs (4.0.3)
382
+ logger
383
+ prism (>= 1.6.0)
384
+ tsort
385
+ rdoc (8.0.0)
379
386
  erb
380
- psych (>= 4.0.0)
387
+ prism (>= 1.6.0)
388
+ rbs (>= 4.0.0)
381
389
  tsort
382
390
  regexp_parser (2.9.2)
383
391
  reline (0.6.3)
@@ -387,17 +395,17 @@ GEM
387
395
  responders (3.2.0)
388
396
  actionpack (>= 7.0)
389
397
  railties (>= 7.0)
390
- roadie (5.2.1)
391
- css_parser (~> 1.4)
398
+ roadie (5.3.1)
399
+ css_parser (>= 1.4, < 4.0)
392
400
  nokogiri (~> 1.15)
393
- roadie-rails (3.3.0)
394
- railties (>= 5.1, < 8.1)
401
+ roadie-rails (3.4.0)
402
+ railties (>= 5.1, < 8.2)
395
403
  roadie (~> 5.0)
396
404
  rotp (6.3.0)
397
- rqrcode (3.1.0)
405
+ rqrcode (3.2.0)
398
406
  chunky_png (~> 1.0)
399
407
  rqrcode_core (~> 2.0)
400
- rqrcode_core (2.0.0)
408
+ rqrcode_core (2.1.0)
401
409
  rspec-core (3.13.4)
402
410
  rspec-support (~> 3.13.0)
403
411
  rspec-expectations (3.13.5)
@@ -415,6 +423,8 @@ GEM
415
423
  rspec-mocks (~> 3.13)
416
424
  rspec-support (~> 3.13)
417
425
  rspec-support (3.13.4)
426
+ rspec_junit_formatter (0.6.0)
427
+ rspec-core (>= 2, < 4, != 2.12.0)
418
428
  ruby-vips (2.2.2)
419
429
  ffi (~> 1.12)
420
430
  logger
@@ -443,6 +453,7 @@ GEM
443
453
  actionpack (>= 6.1)
444
454
  activesupport (>= 6.1)
445
455
  sprockets (>= 3.0.0)
456
+ ssrf_filter (1.5.0)
446
457
  stringex (2.8.6)
447
458
  stringio (3.2.0)
448
459
  temple (0.10.3)
@@ -456,11 +467,11 @@ GEM
456
467
  concurrent-ruby (~> 1.0)
457
468
  uglifier (4.2.1)
458
469
  execjs (>= 0.3.0, < 3)
459
- uri (1.0.2)
470
+ uri (1.0.4)
460
471
  useragent (0.16.11)
461
472
  warden (1.2.9)
462
473
  rack (>= 2.0.9)
463
- websocket-driver (0.8.0)
474
+ websocket-driver (0.8.2)
464
475
  base64
465
476
  websocket-extensions (>= 0.1.0)
466
477
  websocket-extensions (0.1.5)
@@ -486,19 +497,20 @@ DEPENDENCIES
486
497
  acts_as_list
487
498
  database_cleaner
488
499
  devise-two-factor
489
- factory_bot_rails (= 6.4.4)
500
+ factory_bot_rails (= 6.5.1)
490
501
  file_validators
491
502
  jsbundling-rails (~> 1.3)
492
503
  launchy (~> 3.0.1)
493
504
  mysql2
494
505
  paper_trail (~> 16.0.0)
495
- paper_trail-association_tracking (~> 2.2.1)
506
+ paper_trail-association_tracking (~> 2.3.0)
496
507
  poltergeist (~> 1.18.1)
497
508
  pry-byebug
498
- psych (= 5.2.2)
509
+ psych (= 5.4.0)
499
510
  rails-observers
500
511
  ransack
501
512
  rspec-rails (~> 7.1.1)
513
+ rspec_junit_formatter
502
514
  simplecov
503
515
  simplecov-lcov
504
516
  trusty-cms!
data/README.md CHANGED
@@ -12,6 +12,8 @@ Rails engine and is built to be installed into an existing Rails 7 application a
12
12
 
13
13
  CircleCI: [![CircleCI](https://circleci.com/gh/pgharts/trusty-cms/tree/master.svg?style=shield)](https://circleci.com/gh/pgharts/trusty-cms/tree/master)
14
14
 
15
+ Coverage: [![Coverage](https://qlty.sh/badges/4cf4f7cc-d0e8-4559-93ec-685866f5645b/test_coverage.svg)](https://qlty.sh/gh/pgharts/projects/trusty-cms)
16
+
15
17
  Qlty: [![Maintainability](https://qlty.sh/gh/pgharts/projects/trusty-cms/maintainability.svg)](https://qlty.sh/gh/pgharts/projects/trusty-cms)
16
18
 
17
19
  TrustyCMS features:
@@ -42,7 +42,7 @@ class Admin::ResourceController < ApplicationController
42
42
  r.update.publish(:xml, :json) { head :ok }
43
43
  r.update.default { redirect_to redirect_url }
44
44
 
45
- r.destroy.publish(:xml, :json) { head :deleted }
45
+ r.destroy.publish(:xml, :json) { head :ok }
46
46
  r.destroy.default { redirect_to redirect_url }
47
47
  end
48
48
 
@@ -5,6 +5,14 @@ class ApplicationController < ActionController::Base
5
5
  prepend_view_path("#{TRUSTY_CMS_ROOT}/app/views")
6
6
 
7
7
  protect_from_forgery with: :exception
8
+ # Capture the request host before authentication runs so the site-scope logout
9
+ # telemetry (issue #1040) can compare the host's site to the (global) current
10
+ # site. Thread.current is per-thread and safe across concurrent requests —
11
+ # unlike the class-level Page.current_site the telemetry is investigating.
12
+ # An around_action (prepended so it wraps authenticate_user!) guarantees the
13
+ # value is cleared even if the request raises, so it can't leak into the next
14
+ # request handled by the same thread.
15
+ prepend_around_action :with_request_host_for_telemetry
8
16
  before_action :authenticate_user!
9
17
  before_action :configure_permitted_parameters, if: :devise_controller?
10
18
  before_action :set_timezone
@@ -53,6 +61,13 @@ class ApplicationController < ActionController::Base
53
61
 
54
62
  private
55
63
 
64
+ def with_request_host_for_telemetry
65
+ Thread.current[:trusty_request_host] = request.host
66
+ yield
67
+ ensure
68
+ Thread.current[:trusty_request_host] = nil
69
+ end
70
+
56
71
  def set_mailer
57
72
  ActionMailer::Base.default_url_options[:host] = request.host_with_port
58
73
  end
data/app/models/asset.rb CHANGED
@@ -83,8 +83,17 @@ class Asset < ActiveRecord::Base
83
83
  self[:asset_file_size]
84
84
  end
85
85
 
86
- delegate :active_storage_styles, :style_dimensions, :style_format,
87
- to: :asset_type
86
+ def active_storage_styles
87
+ asset_type&.active_storage_styles || {}
88
+ end
89
+
90
+ def style_dimensions(style_name)
91
+ asset_type&.style_dimensions(style_name)
92
+ end
93
+
94
+ def style_format(style_name)
95
+ asset_type&.style_format(style_name)
96
+ end
88
97
 
89
98
  def pdf?
90
99
  content_type == 'application/pdf'
@@ -340,13 +349,28 @@ class Asset < ActiveRecord::Base
340
349
 
341
350
  # this is a convenience for image-pickers
342
351
  def self.thumbnail_options
343
- asset_sizes = thumbnail_sizes.map do |k, v|
352
+ # The :original style is represented by the explicit entry prepended below,
353
+ # so skip it here to avoid a duplicate (and blank) "original" option.
354
+ asset_sizes = thumbnail_sizes.reject { |k, _| k.to_sym == :original }.map do |k, v|
344
355
  size_id = k
345
- size_description = "#{k}: "
346
- size_description << (v.is_a?(Array) ? v.join(' as ') : v)
356
+ size_description = "#{k}: #{describe_style(v)}"
347
357
  [size_description, size_id]
348
358
  end.sort_by { |pair| pair.last.to_s }
349
359
  asset_sizes.unshift ['Original (as uploaded)', 'original']
350
360
  asset_sizes
351
361
  end
362
+
363
+ # Renders a style definition into a short human-readable description for a
364
+ # picker. ActiveStorage styles are hashes (e.g. {geometry: '100x100#',
365
+ # format: :png}); older definitions may be arrays or plain strings.
366
+ def self.describe_style(style)
367
+ case style
368
+ when Hash
369
+ [style[:geometry], style[:format]].compact.join(' as ')
370
+ when Array
371
+ style.join(' as ')
372
+ else
373
+ style.to_s
374
+ end
375
+ end
352
376
  end
@@ -52,8 +52,7 @@ module StandardTags
52
52
  }
53
53
  tag 'children:count' do |tag|
54
54
  options = children_find_options(tag)
55
- options.delete(:order) # Order is irrelevant
56
- tag.locals.children.count(options)
55
+ tag.locals.children.where(options[:conditions]).count
57
56
  end
58
57
 
59
58
  desc %{
@@ -66,7 +65,7 @@ module StandardTags
66
65
  }
67
66
  tag 'children:first' do |tag|
68
67
  options = children_find_options(tag)
69
- children = tag.locals.children.where(options)
68
+ children = tag.locals.children.where(options[:conditions]).order(options[:order])
70
69
  if first = children.first
71
70
  tag.locals.page = first
72
71
  tag.expand
@@ -83,7 +82,7 @@ module StandardTags
83
82
  }
84
83
  tag 'children:last' do |tag|
85
84
  options = children_find_options(tag)
86
- children = tag.locals.children.where(options)
85
+ children = tag.locals.children.where(options[:conditions]).order(options[:order])
87
86
  if last = children.last
88
87
  tag.locals.page = last
89
88
  tag.expand
@@ -942,7 +941,14 @@ module StandardTags
942
941
  paging = pagination_find_options(tag)
943
942
  result = []
944
943
  tag.locals.previous_headers = {}
945
- displayed_children = paging ? findable.paginate(options.merge(paging)) : findable.all.where(options[:conditions]).order(options[:order])
944
+ scoped = findable.where(options[:conditions]).order(options[:order])
945
+ if paging
946
+ displayed_children = scoped.paginate(paging)
947
+ else
948
+ scoped = scoped.limit(options[:limit]) if options[:limit]
949
+ scoped = scoped.offset(options[:offset]) if options[:offset]
950
+ displayed_children = scoped
951
+ end
946
952
  displayed_children.each_with_index do |item, i|
947
953
  tag.locals.child = item
948
954
  tag.locals.page = item
@@ -989,13 +995,13 @@ module StandardTags
989
995
 
990
996
  status = attr[:status]
991
997
  if status == 'all'
992
- options[:conditions] = ['virtual = ?', false]
998
+ options[:conditions] = ['`virtual` = ?', false]
993
999
  else
994
1000
  stat = Status[status]
995
1001
  if stat.nil?
996
1002
  raise TagError.new(%{`status' attribute of `each' tag must be set to a valid status})
997
1003
  else
998
- options[:conditions] = ['(virtual = ?) and (status_id = ?)', false, stat.id]
1004
+ options[:conditions] = ['(`virtual` = ?) and (status_id = ?)', false, stat.id]
999
1005
  end
1000
1006
  end
1001
1007
  options
data/app/models/user.rb CHANGED
@@ -1,3 +1,5 @@
1
+ require 'trusty_cms/site_scope_auth_reporter'
2
+
1
3
  class User < ActiveRecord::Base
2
4
  has_many :pages, foreign_key: :created_by_id
3
5
  self.table_name = 'admins'
@@ -78,4 +80,15 @@ class User < ActiveRecord::Base
78
80
  errors.add :password, 'Complexity requirement not met. Length should be 12 characters and include: 1 uppercase, 1 lowercase, 1 digit and 1 special character.'
79
81
  end
80
82
 
83
+ # Observe-only instrument for the intermittent logout bug (issue #1040). This
84
+ # keeps Devise's exact (site-scoped) behavior — it returns the scoped lookup
85
+ # unchanged, so a mismatch still logs the user out exactly as before — and only
86
+ # reports when a valid user was excluded by the site scope. See
87
+ # TrustyCms::SiteScopeAuthReporter. The behavior-change fix is separate (PR #1041).
88
+ def self.serialize_from_session(key, salt)
89
+ record = to_adapter.get(key)
90
+ TrustyCms::SiteScopeAuthReporter.report_miss(key) if record.nil?
91
+ record if record && record.authenticatable_salt == salt
92
+ end
93
+
81
94
  end
@@ -0,0 +1,108 @@
1
+ module TrustyCms
2
+ # Observe-only telemetry for the intermittent CMS logout bug (diagnostic for
3
+ # issue #1040). It changes NO behavior.
4
+ #
5
+ # When the multi_site extension is active, `User` carries a site-scoped
6
+ # default_scope (an INNER JOIN on admins_sites for `Page.current_site`).
7
+ # Devise deserializes the session user every request through that scope, so if
8
+ # `current_site` points at a site the user is not assigned to, the lookup
9
+ # returns nil and the user is logged out. Because `current_site` is
10
+ # process-global mutable state shared across Puma threads, we suspect this is
11
+ # usually a race (the user IS on a site they belong to, but the global was
12
+ # clobbered by a concurrent request for another host).
13
+ #
14
+ # This reporter fires only when a session lookup misses BUT the user actually
15
+ # exists (unscoped) — i.e. a valid user was excluded by the site scope — and
16
+ # records enough context to tell a race from a genuine cross-site access or an
17
+ # unmatched-host fallback. It never raises into the auth path.
18
+ module SiteScopeAuthReporter
19
+ MESSAGE = 'CMS site-scope logout (possible current_site race)'.freeze
20
+
21
+ module_function
22
+
23
+ def report_miss(key)
24
+ return unless multi_site_scoped?
25
+
26
+ excluded = User.unscoped { User.to_adapter.get(key) }
27
+ return if excluded.nil? # ordinary unknown/expired session — not our bug
28
+
29
+ context = build_context(excluded)
30
+ Rails.logger.warn("[multi_site] #{MESSAGE} #{context.to_json}")
31
+ Honeybadger.notify(MESSAGE, context: context) if defined?(Honeybadger)
32
+ nil
33
+ rescue StandardError => e
34
+ # Telemetry must never break authentication.
35
+ Rails.logger.error("[multi_site] SiteScopeAuthReporter error: #{e.class}: #{e.message}")
36
+ nil
37
+ end
38
+
39
+ # Only meaningful when User is actually site-scoped by the multi_site
40
+ # extension; otherwise the scoped and unscoped lookups are identical.
41
+ def multi_site_scoped?
42
+ User.respond_to?(:is_site_scoped?) && User.is_site_scoped?
43
+ end
44
+
45
+ def build_context(user)
46
+ current = current_site
47
+ host = Thread.current[:trusty_request_host]
48
+ expected = expected_site_for(host)
49
+ site_ids = user.site_ids
50
+
51
+ {
52
+ user_id: user.id,
53
+ user_admin: user.admin?,
54
+ user_site_ids: site_ids,
55
+ current_site_id: current&.id,
56
+ current_site_name: current&.name,
57
+ current_site_domain_blank: current.present? && current.domain.blank?,
58
+ request_host: host,
59
+ expected_site_id: expected&.id,
60
+ expected_site_name: expected&.name,
61
+ classification: classify(site_ids, current, expected, host),
62
+ thread: Thread.current.object_id,
63
+ at: Time.now.utc.iso8601,
64
+ }
65
+ end
66
+
67
+ # race -> user belongs to the host's site, but current_site is
68
+ # something else (the global was clobbered)
69
+ # genuine_cross_site -> user hit a host whose site they are not assigned to
70
+ # host_unmatched -> request host matched no site (asset/unknown host);
71
+ # current_site falls back to the default site
72
+ # no_host_captured -> ran outside a captured request (e.g. rake/console)
73
+ def classify(site_ids, current, expected, host)
74
+ return 'no_host_captured' if host.blank?
75
+ return 'host_unmatched' if expected.nil?
76
+ return 'race' if site_ids.include?(expected.id) && (current.nil? || site_ids.exclude?(current.id))
77
+ return 'genuine_cross_site' if site_ids.exclude?(expected.id)
78
+
79
+ 'unknown'
80
+ end
81
+
82
+ def current_site
83
+ Page.current_site if Page.respond_to?(:current_site)
84
+ end
85
+
86
+ # Mirrors Site.find_for_host's matching but WITHOUT the Site.default /
87
+ # catchall fallback, so telemetry never creates a record and returns nil when
88
+ # the host matches no site (which is what drives the host_unmatched
89
+ # classification). A blank domain compiles to a regexp that matches every
90
+ # host, so blank domains are matched only by exact base_domain, never by the
91
+ # regexp.
92
+ def expected_site_for(host)
93
+ return nil if host.blank?
94
+
95
+ Site.all.detect do |site|
96
+ host == site.base_domain || domain_regexp_match?(site, host)
97
+ end
98
+ end
99
+
100
+ def domain_regexp_match?(site, host)
101
+ return false if site.domain.blank?
102
+
103
+ !(host =~ Regexp.compile(site.domain)).nil?
104
+ rescue RegexpError
105
+ false
106
+ end
107
+ end
108
+ end
@@ -1,3 +1,3 @@
1
1
  module TrustyCms
2
- VERSION = '7.1.2'.freeze
2
+ VERSION = '7.1.3'.freeze
3
3
  end
data/package.json CHANGED
@@ -22,7 +22,7 @@
22
22
  "tablesaw": "^3.1.2"
23
23
  },
24
24
  "devDependencies": {
25
- "esbuild": "^0.25.0",
25
+ "esbuild": "^0.28.1",
26
26
  "stylelint": "^13.3.2",
27
27
  "stylelint-config-recommended": "^3.0.0",
28
28
  "stylelint-order": "^4.0.0",