camaleon_cms 2.9.2 → 2.9.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 (211) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +17 -4
  3. data/Rakefile +3 -16
  4. data/app/apps/plugins/attack/admin_controller.rb +9 -6
  5. data/app/apps/plugins/attack/attack_helper.rb +35 -25
  6. data/app/apps/plugins/authoring_post/authoring_post_helper.rb +23 -16
  7. data/app/apps/plugins/front_cache/admin_controller.rb +15 -10
  8. data/app/apps/plugins/front_cache/config/initializer.rb +1 -1
  9. data/app/apps/plugins/front_cache/front_cache_helper.rb +33 -9
  10. data/app/apps/plugins/visibility_post/config/config.json +1 -0
  11. data/app/apps/plugins/visibility_post/config/routes_front.txt +1 -0
  12. data/app/apps/plugins/visibility_post/front_controller.rb +23 -0
  13. data/app/apps/plugins/visibility_post/visibility_post_helper.rb +141 -70
  14. data/app/apps/themes/camaleon_first/main_helper.rb +18 -12
  15. data/app/apps/themes/default/assets/css/style.css.scss +24 -0
  16. data/app/apps/themes/default/assets/genericons/genericons.css +3 -8
  17. data/app/apps/themes/default/default_helper.rb +2 -1
  18. data/app/apps/themes/default/views/layouts/index.html.erb +3 -1
  19. data/app/apps/themes/new/assets/css/styles.css.scss +24 -0
  20. data/app/apps/themes/new/custom_helper.rb +5 -9
  21. data/app/apps/themes/new/views/layouts/_header.html.erb +3 -1
  22. data/app/assets/javascripts/camaleon_cms/admin/_data.js +16 -0
  23. data/app/assets/javascripts/camaleon_cms/admin/_post.js +5 -2
  24. data/app/assets/javascripts/camaleon_cms/admin/nav_menu.js +14 -3
  25. data/app/assets/javascripts/camaleon_cms/admin/uploader/_media_manager.js +5 -0
  26. data/app/controllers/camaleon_cms/admin/appearances/nav_menus_controller.rb +8 -6
  27. data/app/controllers/camaleon_cms/admin/appearances/themes_controller.rb +8 -5
  28. data/app/controllers/camaleon_cms/admin/appearances/widgets/assign_controller.rb +7 -2
  29. data/app/controllers/camaleon_cms/admin/appearances/widgets/main_controller.rb +2 -1
  30. data/app/controllers/camaleon_cms/admin/categories_controller.rb +19 -4
  31. data/app/controllers/camaleon_cms/admin/comments_controller.rb +0 -1
  32. data/app/controllers/camaleon_cms/admin/installers_controller.rb +32 -1
  33. data/app/controllers/camaleon_cms/admin/media_controller.rb +66 -14
  34. data/app/controllers/camaleon_cms/admin/plugins_controller.rb +1 -3
  35. data/app/controllers/camaleon_cms/admin/post_tags_controller.rb +10 -4
  36. data/app/controllers/camaleon_cms/admin/posts/drafts_controller.rb +40 -11
  37. data/app/controllers/camaleon_cms/admin/posts_controller.rb +55 -22
  38. data/app/controllers/camaleon_cms/admin/sessions_controller.rb +237 -44
  39. data/app/controllers/camaleon_cms/admin/settings/custom_fields_controller.rb +76 -13
  40. data/app/controllers/camaleon_cms/admin/settings/post_types_controller.rb +5 -2
  41. data/app/controllers/camaleon_cms/admin/settings/sites_controller.rb +26 -3
  42. data/app/controllers/camaleon_cms/admin/settings_controller.rb +5 -3
  43. data/app/controllers/camaleon_cms/admin/user_roles_controller.rb +7 -5
  44. data/app/controllers/camaleon_cms/admin/users_controller.rb +78 -11
  45. data/app/controllers/camaleon_cms/admin_controller.rb +136 -26
  46. data/app/controllers/camaleon_cms/camaleon_controller.rb +37 -92
  47. data/app/controllers/camaleon_cms/frontend_controller.rb +140 -70
  48. data/app/controllers/concerns/camaleon_cms/admin/custom_fields_concern.rb +20 -5
  49. data/app/controllers/concerns/camaleon_cms/frontend_concern.rb +46 -17
  50. data/app/controllers/concerns/camaleon_cms/frontend_visited_state_concern.rb +72 -0
  51. data/app/controllers/concerns/camaleon_cms/hook_lifecycle_concern.rb +104 -0
  52. data/app/controllers/concerns/camaleon_cms/request_context_concern.rb +113 -0
  53. data/app/controllers/concerns/camaleon_cms/runtime_admin_menu_concern.rb +237 -0
  54. data/app/controllers/concerns/camaleon_cms/runtime_captcha_image_concern.rb +13 -0
  55. data/app/controllers/concerns/camaleon_cms/runtime_html_content_concern.rb +143 -0
  56. data/app/controllers/concerns/camaleon_cms/runtime_shortcode_theme_concern.rb +246 -0
  57. data/app/controllers/concerns/camaleon_cms/runtime_state_concern.rb +13 -0
  58. data/app/controllers/concerns/camaleon_cms/runtime_uploader_concern.rb +54 -0
  59. data/app/controllers/concerns/camaleon_cms/session_captcha_runtime_concern.rb +19 -0
  60. data/app/controllers/concerns/camaleon_cms/session_runtime_concern.rb +40 -0
  61. data/app/decorators/camaleon_cms/application_decorator.rb +11 -8
  62. data/app/decorators/camaleon_cms/category_decorator.rb +3 -2
  63. data/app/decorators/camaleon_cms/post_comment_decorator.rb +13 -5
  64. data/app/decorators/camaleon_cms/post_decorator.rb +66 -50
  65. data/app/decorators/camaleon_cms/post_type_decorator.rb +1 -1
  66. data/app/decorators/camaleon_cms/site_decorator.rb +29 -23
  67. data/app/decorators/camaleon_cms/term_taxonomy_decorator.rb +21 -10
  68. data/app/decorators/camaleon_cms/theme_decorator.rb +3 -3
  69. data/app/decorators/camaleon_cms/user_decorator.rb +26 -5
  70. data/app/helpers/camaleon_cms/admin/application_helper.rb +5 -8
  71. data/app/helpers/camaleon_cms/admin/breadcrumb_helper.rb +1 -1
  72. data/app/helpers/camaleon_cms/admin/custom_fields_helper.rb +24 -23
  73. data/app/helpers/camaleon_cms/admin/menus_helper.rb +249 -139
  74. data/app/helpers/camaleon_cms/admin/post_type_helper.rb +34 -17
  75. data/app/helpers/camaleon_cms/camaleon_helper.rb +31 -39
  76. data/app/helpers/camaleon_cms/captcha_helper.rb +73 -53
  77. data/app/helpers/camaleon_cms/comment_helper.rb +19 -7
  78. data/app/helpers/camaleon_cms/content_helper.rb +13 -6
  79. data/app/helpers/camaleon_cms/email_helper.rb +7 -3
  80. data/app/helpers/camaleon_cms/frontend/content_select_helper.rb +34 -20
  81. data/app/helpers/camaleon_cms/frontend/nav_menu_helper.rb +143 -66
  82. data/app/helpers/camaleon_cms/frontend/seo_helper.rb +39 -28
  83. data/app/helpers/camaleon_cms/frontend/site_helper.rb +32 -12
  84. data/app/helpers/camaleon_cms/hooks_helper.rb +14 -4
  85. data/app/helpers/camaleon_cms/html_helper.rb +58 -37
  86. data/app/helpers/camaleon_cms/plugins_helper.rb +23 -24
  87. data/app/helpers/camaleon_cms/session_helper.rb +188 -44
  88. data/app/helpers/camaleon_cms/short_code_helper.rb +181 -85
  89. data/app/helpers/camaleon_cms/site_helper.rb +34 -13
  90. data/app/helpers/camaleon_cms/theme_helper.rb +35 -19
  91. data/app/helpers/camaleon_cms/uploader_helper.rb +19 -473
  92. data/app/helpers/camaleon_cms/user_roles_helper.rb +5 -4
  93. data/app/mailers/camaleon_cms/html_mailer.rb +4 -4
  94. data/app/models/camaleon_cms/ability.rb +15 -7
  95. data/app/models/camaleon_cms/category.rb +6 -6
  96. data/app/models/camaleon_cms/custom_field.rb +15 -6
  97. data/app/models/camaleon_cms/custom_field_group.rb +39 -22
  98. data/app/models/camaleon_cms/custom_fields_relationship.rb +133 -2
  99. data/app/models/camaleon_cms/media.rb +8 -2
  100. data/app/models/camaleon_cms/meta.rb +2 -2
  101. data/app/models/camaleon_cms/nav_menu.rb +5 -3
  102. data/app/models/camaleon_cms/nav_menu_item.rb +16 -8
  103. data/app/models/camaleon_cms/plugin.rb +3 -4
  104. data/app/models/camaleon_cms/post.rb +113 -27
  105. data/app/models/camaleon_cms/post_comment.rb +10 -6
  106. data/app/models/camaleon_cms/post_default.rb +42 -12
  107. data/app/models/camaleon_cms/post_relationship.rb +6 -4
  108. data/app/models/camaleon_cms/post_tag.rb +4 -5
  109. data/app/models/camaleon_cms/post_type.rb +21 -13
  110. data/app/models/camaleon_cms/site.rb +39 -29
  111. data/app/models/camaleon_cms/term_relationship.rb +10 -7
  112. data/app/models/camaleon_cms/term_taxonomy.rb +52 -4
  113. data/app/models/camaleon_cms/theme.rb +2 -4
  114. data/app/models/camaleon_cms/user.rb +17 -5
  115. data/app/models/camaleon_cms/user_role.rb +66 -6
  116. data/app/models/camaleon_cms/widget/assigned.rb +11 -6
  117. data/app/models/camaleon_cms/widget/main.rb +12 -8
  118. data/app/models/camaleon_cms/widget/sidebar.rb +3 -8
  119. data/app/models/camaleon_record.rb +89 -5
  120. data/app/models/concerns/camaleon_cms/categories_tags_for_posts.rb +20 -38
  121. data/app/models/concerns/camaleon_cms/common_relationships.rb +18 -5
  122. data/app/models/concerns/camaleon_cms/custom_fields_read.rb +111 -54
  123. data/app/models/concerns/camaleon_cms/metas.rb +31 -6
  124. data/app/models/concerns/camaleon_cms/normalize_attrs.rb +2 -8
  125. data/app/models/concerns/camaleon_cms/site_default_settings.rb +120 -16
  126. data/app/models/concerns/camaleon_cms/user_methods.rb +44 -19
  127. data/app/models/current_request.rb +8 -1
  128. data/app/uploaders/camaleon_cms_aws_uploader.rb +31 -10
  129. data/app/uploaders/camaleon_cms_local_uploader.rb +76 -19
  130. data/app/uploaders/camaleon_cms_uploader.rb +23 -10
  131. data/app/validators/camaleon_cms/post_uniq_validator.rb +5 -4
  132. data/app/validators/camaleon_cms/uniq_validator.rb +1 -1
  133. data/app/validators/camaleon_cms/user_url_validator.rb +215 -36
  134. data/app/views/camaleon_cms/admin/categories/index.html.erb +2 -2
  135. data/app/views/camaleon_cms/admin/installers/form.html.erb +5 -0
  136. data/app/views/camaleon_cms/admin/installers/welcome.html.erb +30 -6
  137. data/app/views/camaleon_cms/admin/plugins/_plugins_list.html.erb +2 -2
  138. data/app/views/camaleon_cms/admin/post_tags/index.html.erb +2 -2
  139. data/app/views/camaleon_cms/admin/posts/_sidebar.html.erb +1 -1
  140. data/app/views/camaleon_cms/admin/posts/form.html.erb +3 -2
  141. data/app/views/camaleon_cms/admin/posts/index.html.erb +5 -5
  142. data/app/views/camaleon_cms/admin/sessions/back_to_parent.html.erb +39 -0
  143. data/app/views/camaleon_cms/admin/sessions/logout_confirm.html.erb +27 -0
  144. data/app/views/camaleon_cms/admin/settings/_email_settings.html.erb +4 -1
  145. data/app/views/camaleon_cms/admin/settings/custom_fields/_get_items.html.erb +1 -1
  146. data/app/views/camaleon_cms/admin/settings/custom_fields/_render.html.erb +19 -4
  147. data/app/views/camaleon_cms/admin/settings/custom_fields/_render_category_simple.html.erb +1 -1
  148. data/app/views/camaleon_cms/admin/settings/custom_fields/form.html.erb +4 -3
  149. data/app/views/camaleon_cms/admin/settings/shortcodes.html.erb +4 -4
  150. data/app/views/camaleon_cms/admin/settings/site.html.erb +1 -1
  151. data/app/views/camaleon_cms/admin/settings/sites/form.html.erb +1 -1
  152. data/app/views/camaleon_cms/admin/user_roles/form.html.erb +24 -3
  153. data/app/views/camaleon_cms/admin/users/form.html.erb +8 -4
  154. data/app/views/camaleon_cms/admin/users/index.html.erb +1 -1
  155. data/app/views/camaleon_cms/default_theme/partials/_render_custom_field.html.erb +8 -2
  156. data/app/views/camaleon_cms/default_theme/sitemap.html.erb +1 -1
  157. data/app/views/camaleon_cms/default_theme/sitemap.xml.builder +1 -1
  158. data/app/views/layouts/camaleon_cms/admin/_header.html.erb +1 -1
  159. data/config/initializers/action_view.rb +42 -5
  160. data/config/initializers/active_record_extension.rb +13 -27
  161. data/config/initializers/assets.rb +52 -23
  162. data/config/initializers/custom_initializers.rb +1 -1
  163. data/config/locales/camaleon_cms/admin/ar.yml +1 -0
  164. data/config/locales/camaleon_cms/admin/de.yml +1 -0
  165. data/config/locales/camaleon_cms/admin/en.yml +33 -0
  166. data/config/locales/camaleon_cms/admin/es.yml +1 -0
  167. data/config/locales/camaleon_cms/admin/fr.yml +1 -0
  168. data/config/locales/camaleon_cms/admin/it.yml +1 -0
  169. data/config/locales/camaleon_cms/admin/nl.yml +1 -0
  170. data/config/locales/camaleon_cms/admin/pt-BR.yml +1 -0
  171. data/config/locales/camaleon_cms/admin/pt.yml +1 -0
  172. data/config/locales/camaleon_cms/admin/ru.yml +1 -0
  173. data/config/locales/camaleon_cms/admin/uk.yml +1 -0
  174. data/config/locales/camaleon_cms/admin/zh-CH.yml +1 -0
  175. data/config/routes/admin.rb +33 -15
  176. data/config/routes/frontend.rb +21 -15
  177. data/lib/camaleon_cms/assets_precompile.rb +86 -0
  178. data/lib/camaleon_cms/captcha_image_generation.rb +65 -0
  179. data/lib/camaleon_cms/content_security.rb +158 -0
  180. data/lib/camaleon_cms/engine.rb +47 -9
  181. data/lib/camaleon_cms/media_security_headers.rb +27 -0
  182. data/lib/camaleon_cms/setup_token.rb +69 -0
  183. data/lib/camaleon_cms/svg_content_checker.rb +123 -0
  184. data/lib/camaleon_cms/task_reporter.rb +14 -0
  185. data/lib/camaleon_cms/unsafe_markup.rb +207 -0
  186. data/lib/camaleon_cms/uploader_content_security.rb +248 -0
  187. data/lib/camaleon_cms/uploader_image_processing.rb +166 -0
  188. data/lib/camaleon_cms/uploader_path_security.rb +146 -0
  189. data/lib/camaleon_cms/uploader_pipeline.rb +350 -0
  190. data/lib/camaleon_cms/uploader_support.rb +86 -0
  191. data/lib/camaleon_cms/version.rb +1 -1
  192. data/lib/camaleon_cms.rb +11 -0
  193. data/lib/ext/hash.rb +28 -2
  194. data/lib/ext/string.rb +10 -5
  195. data/lib/ext/translator.rb +10 -2
  196. data/lib/generators/camaleon_cms/gem_plugin_template/app/controllers/plugins/my_plugin/admin_controller.rb +5 -1
  197. data/lib/generators/camaleon_cms/install_generator.rb +7 -4
  198. data/lib/generators/camaleon_cms/install_template/plugin_routes.rb +5 -12
  199. data/lib/generators/camaleon_cms/theme_generator.rb +5 -10
  200. data/lib/generators/camaleon_cms/theme_template/views/layouts/index.html.erb +3 -1
  201. data/lib/plugin_routes.rb +412 -368
  202. data/lib/tasks/camaleon_cms/camaleon_tasks.rake +2 -1
  203. data/lib/tasks/cross_site_field_groups.rake +74 -0
  204. data/lib/tasks/custom_fields_roles.rake +15 -13
  205. data/lib/tasks/orphaned_comments.rake +42 -0
  206. data/lib/tasks/private_upload_acls.rake +31 -0
  207. data/lib/tasks/site_custom_field_groups.rake +36 -0
  208. data/lib/tasks/unsafe_stored_content.rake +42 -0
  209. data/lib/tasks/unsafe_stored_uploads.rake +70 -0
  210. data/lib/tasks/user_field_groups.rake +27 -0
  211. metadata +123 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7fc3c9c70942e53b927d67366df21d19a549e0a349e8e87ca08f12977c3c5b08
4
- data.tar.gz: d0b3948628d2c7c5d5efa55e6bc6c2ef1b90413d2a53764ac85ed281eceab630
3
+ metadata.gz: e0997f6bbf586f6f6c5618c97814c0a291fa98bcb54adbd7736a0449f60edc33
4
+ data.tar.gz: e73b0849f7b21ffc7bd7510d71c328706b022cfefaf738194836b9e332e4f062
5
5
  SHA512:
6
- metadata.gz: f6795ac94a6097bafa86c8336f209cb395f2b102db44da96d091ad55b5373bb6a1ed5af876f6facb72ba313d6fa803201d0605e2e55b0f699304dab2df470c6b
7
- data.tar.gz: 6045503558fb4aa769b6d3bbd8dfac05357c9e6427134ed2670b3dc08aae94df7b865d69a80f6bdbe950492a439d79345b1f57b94808fbeb78f6acb5868ec90e
6
+ metadata.gz: 74c4843293dd8fbfa585cc784834866be63a7e330b7c9d21ecbbeb9a9e6753168d5c2502705db0fdecee9f89b2ff3b3d102e699c41384fb398f6332be98464cf
7
+ data.tar.gz: f152511e4e5022003ea2003a275cd677912cce73ac55bb9dbc8528094b6ddcf529b445627ba3750c4bbcebf5412b7107049c43daa0f3e3f5390539d2ba6c4eff
data/README.md CHANGED
@@ -81,14 +81,13 @@ https://camaleon.website/store/plugins
81
81
  * Image/iFrame Lazy Loader - https://github.com/brian-kephart/camaleon_lazy_loader
82
82
  * Import / Export - https://github.com/owen2345/camaleon_export_import
83
83
  * Lightbox - https://github.com/owen2345/CamaImageLightbox
84
- * Autocomplete - https://github.com/gaelfokou/cama_autocomplete
85
84
  * Permissions for External Menus - https://github.com/owen2345/cama_external_menu
86
85
  * TinyMCE Template Integration - https://github.com/owen2345/Camaleon-Tinymce-Templates
87
86
  * Download Manager - https://github.com/max2320/camaleon-download
88
87
  * OAuth - https://github.com/owen2345/camaleon_oauth
89
- * Visual Editor - Paid Plugin ($) - https://camaleon.website/store/plugins/camaleon_editor
90
- * Spree Commerce Integration - Paid Plugin ($) - https://camaleon.website/store/plugins/camaleon-spree
91
- * Admin AJAX - Paid Plugin ($) - https://camaleon.website/store/plugins/admin_ajax
88
+ * Visual Editor - Paid Plugin ($) - https://camaleon.website/store/plugins/camaleon_editor (Github: https://github.com/owen2345/camaleon_editor)
89
+ * Spree Commerce Integration - Paid Plugin ($) - https://camaleon.website/store/plugins/camaleon-spree (Github: https://github.com/owen2345/camaleon-spree)
90
+ * Admin AJAX - Paid Plugin ($) - https://camaleon.website/store/plugins/admin_ajax (Github: https://github.com/owen2345/admin-ajax)
92
91
  * **See here for a complete Gemfile**: https://github.com/owen2345/camaleon-cms/blob/master/docs/example_gemfile.rb
93
92
 
94
93
  ## Camaleon CMS has many useful frontend Themes such as:
@@ -190,6 +189,13 @@ RAILS_ENV=test bundle exec rake app:db:test:prepare
190
189
 
191
190
  See [docs/security/permissions.md](docs/security/permissions.md) for details on manager permissions, custom fields, and security considerations.
192
191
 
192
+ ## Hooks
193
+
194
+ Plugins and themes extend Camaleon by handling named hooks. See [docs/hooks.md](docs/hooks.md) for the
195
+ mechanism (registration via `config.json`, the mutable `args` payload) and a code-derived inventory of the
196
+ hooks core fires, including the session/auth hooks (`user_before_login`, `user_before_register`,
197
+ `after_login`, …).
198
+
193
199
  ## Contributing
194
200
  * Fork it.
195
201
  * Create a branch (git checkout -b my_feature_branch)
@@ -197,6 +203,13 @@ See [docs/security/permissions.md](docs/security/permissions.md) for details on
197
203
  * Push to the branch (git push origin my_feature_branch)
198
204
  * Create a pull request from your branch into master (Please be sure to provide enough detail for us to understand what this change is doing)
199
205
 
206
+ ## Releasing
207
+
208
+ Releases are cut by the manually-started **Release** GitHub Actions workflow, which builds the gem,
209
+ publishes it to RubyGems, tags the commit and creates the GitHub release. See
210
+ [docs/releasing.md](docs/releasing.md) for the full procedure, what the workflow checks before it
211
+ publishes, and the fully manual fallback.
212
+
200
213
  ## Version History
201
214
 
202
215
  https://camaleon.website/version-history.html
data/Rakefile CHANGED
@@ -4,26 +4,13 @@ rescue LoadError
4
4
  puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
5
  end
6
6
 
7
- require 'rdoc/task'
8
-
9
- RDoc::Task.new(:rdoc) do |rdoc|
10
- rdoc.rdoc_dir = 'rdoc'
11
- rdoc.title = 'CamaleonCms'
12
- rdoc.options << '--line-numbers'
13
- rdoc.rdoc_files.include('README.rdoc')
14
- rdoc.rdoc_files.include('lib/**/*.rb')
15
- end
16
-
17
7
  APP_RAKEFILE = File.expand_path('spec/dummy/Rakefile', __dir__)
18
8
  load 'rails/tasks/engine.rake'
19
9
 
20
- load 'rails/tasks/statistics.rake'
21
-
22
- Bundler::GemHelper.install_tasks
23
-
24
- Dir[File.join(File.dirname(__FILE__), 'tasks/**/*.rake')].each { |f| load f }
10
+ # Deliberately no Bundler::GemHelper.install_tasks: its `rake release` would tag `v<version>` and
11
+ # push a gem that none of the release checks had seen. Releases go through the Release workflow —
12
+ # see docs/releasing.md, which also covers building and publishing by hand.
25
13
 
26
- require 'rspec/core'
27
14
  require 'rspec/core/rake_task'
28
15
 
29
16
  desc 'Run all specs in spec directory (excluding plugin specs)'
@@ -6,12 +6,15 @@ module Plugins
6
6
  end
7
7
 
8
8
  def save_settings
9
- current_site.set_meta('attack_config', { get: { sec: params[:attack][:get_sec], max: params[:attack][:get_max] },
10
- post: { sec: params[:attack][:post_sec] || 20,
11
- max: params[:attack][:post_max] },
12
- msg: params[:attack][:msg],
13
- ban: params[:attack][:ban],
14
- cleared: Time.now })
9
+ current_site.set_meta(
10
+ 'attack_config', {
11
+ get: { sec: params[:attack][:get_sec], max: params[:attack][:get_max] },
12
+ post: { sec: params[:attack][:post_sec] || 20, max: params[:attack][:post_max] },
13
+ msg: params[:attack][:msg],
14
+ ban: params[:attack][:ban],
15
+ cleared: Time.zone.now.iso8601
16
+ }
17
+ )
15
18
  flash[:notice] = t('plugin.attack.messages.settings_saved').to_s
16
19
  redirect_to action: :settings
17
20
  end
@@ -1,14 +1,14 @@
1
1
  module Plugins
2
2
  module Attack
3
3
  module AttackHelper
4
- # here all actions on plugin destroying
4
+ # Here all actions on the plugin are destroyed
5
5
  # plugin: plugin model
6
6
  def attack_on_destroy(_plugin)
7
7
  current_site.attack.destroy_all
8
8
  end
9
9
 
10
- # here all actions on going to active
11
- # you can run sql commands like this:
10
+ # Here all actions on going to active
11
+ # you can run SQL commands like this:
12
12
  # results = ActiveRecord::Base.connection.execute(query);
13
13
  # plugin: plugin model
14
14
  def attack_on_active(_plugin)
@@ -16,15 +16,13 @@ module Plugins
16
16
  post: { sec: 20, max: 5 },
17
17
  msg: I18n.t('plugin.attack.form.request_limit_exceeded').to_s,
18
18
  ban: 5,
19
- cleared: Time.now })
19
+ cleared: Time.zone.now.iso8601 })
20
20
 
21
- unless ActiveRecord::Base.connection.table_exists? 'plugins_attacks'
22
- ActiveRecord::Base.connection.create_table :plugins_attacks do |t|
23
- t.string :path, index: true
24
- t.string :browser_key, index: true
25
- t.belongs_to :site, index: true
26
- t.datetime 'created_at'
27
- end
21
+ ActiveRecord::Base.connection.create_table :plugins_attacks, if_not_exists: true do |t|
22
+ t.string :path, index: true
23
+ t.string :browser_key, index: true
24
+ t.belongs_to :site, index: true
25
+ t.datetime 'created_at'
28
26
  end
29
27
  CamaleonCms::Site.class_eval do
30
28
  has_many :attack, class_name: 'Plugins::Attack::Models::Attack'
@@ -38,9 +36,9 @@ module Plugins
38
36
  end
39
37
 
40
38
  def attack_app_before_load
41
- cache_ban = Rails.cache.read(cama_get_session_id)
39
+ cache_ban = Rails.cache.read(attack_ban_cache_key)
42
40
  if cache_ban.present? # render banned message if it was banned
43
- render html: cache_ban.html_safe, layout: false
41
+ render html: cache_ban.to_s, layout: false
44
42
  return
45
43
  end
46
44
 
@@ -50,44 +48,56 @@ module Plugins
50
48
 
51
49
  private
52
50
 
51
+ # Throttle and ban on the client IP, not the session id (audit finding H2). A cookieless /
52
+ # cookie-rotating request presented a fresh session id every time, so the per-key counter never
53
+ # accumulated: the throttle never tripped and its per-request DB insert ran unbounded. Keying on
54
+ # the IP makes the count accumulate, trip the limit, and stop inserting.
55
+ def attack_client_key
56
+ request.remote_ip.to_s
57
+ end
58
+
59
+ def attack_ban_cache_key
60
+ "plugins_attack_ban:#{attack_client_key}"
61
+ end
62
+
53
63
  def attack_check_request
54
64
  return unless current_site
55
65
 
56
66
  config = current_site.get_meta('attack_config')
57
- q = current_site.attack.where(browser_key: cama_get_session_id, path: attack_request_key)
58
- return unless config.present?
67
+ query = current_site.attack.where(browser_key: attack_client_key, path: attack_request_key)
68
+ return if config.blank?
59
69
 
60
70
  # clear past requests
61
71
  if begin
62
- Time.parse(config[:cleared])
72
+ Time.zone.parse(config[:cleared])
63
73
  rescue StandardError
64
74
  2.hours.ago
65
75
  end < 1.hour.ago
66
76
  current_site.attack.where('plugins_attacks.created_at < ?', 1.hour.ago).delete_all
67
- config[:cleared] = Time.now.to_s
77
+ config[:cleared] = Time.zone.now.iso8601 # Use ISO8601 for reliable parsing
68
78
  current_site.set_meta('attack_config', config)
69
79
  end
70
80
 
71
81
  # post request
72
82
  if request.post? || request.patch?
73
- r = q.where(created_at: config[:post][:sec].to_i.seconds.ago..Time.now)
83
+ r = query.where(created_at: config[:post][:sec].to_i.seconds.ago..Time.zone.now)
74
84
  if r.count > config[:post][:max].to_i
75
- Rails.cache.write(cama_get_session_id, config[:msg], expires_in: config[:ban].to_i.minutes)
76
- # send an email to administrator with request info (ip, browser, if logged then send user info
77
- render html: config[:msg].html_safe
85
+ Rails.cache.write(attack_ban_cache_key, config[:msg], expires_in: config[:ban].to_i.minutes)
86
+ # Email administrator with request info (ip, browser, if logged, then send user info
87
+ render html: config[:msg].to_s
78
88
  return
79
89
  end
80
90
 
81
91
  # get request
82
92
  else
83
- r = q.where(created_at: config[:get][:sec].to_i.seconds.ago..Time.now)
93
+ r = query.where(created_at: config[:get][:sec].to_i.seconds.ago..Time.zone.now)
84
94
  if r.count > config[:get][:max].to_i
85
- Rails.cache.write(cama_get_session_id, config[:msg], expires_in: config[:ban].to_i.minutes)
86
- render html: config[:msg].html_safe
95
+ Rails.cache.write(attack_ban_cache_key, config[:msg], expires_in: config[:ban].to_i.minutes)
96
+ render html: config[:msg].to_s
87
97
  return
88
98
  end
89
99
  end
90
- q.create
100
+ query.create
91
101
  end
92
102
 
93
103
  def attack_request_key(method = nil)
@@ -24,27 +24,34 @@ module Plugins
24
24
  private
25
25
 
26
26
  def plugin_authoring_form_html(post)
27
- "
28
- <div class='form-group'>
29
- <label class='control-label'>#{t('camaleon_cms.admin.table.author')}</label>
30
- <select id='post_user_id' #{if can?(:edit_other,
31
- post.post_type) && (can?(:edit_publish,
32
- post.post_type) || !post.published?)
33
- ''
34
- else
35
- 'disabled'
36
- end} name='post[user_id]' class='form-control select valid' aria-invalid='false'>#{plugin_authoring_authors_list(post)}</select>
37
- </div>
38
- "
27
+ disabled = !(can?(:edit_other, post.post_type) && (can?(:edit_publish, post.post_type) || !post.published?))
28
+
29
+ label = tag.label(t('camaleon_cms.admin.table.author'), class: 'control-label')
30
+ select = content_tag(
31
+ :select,
32
+ safe_join(plugin_authoring_authors_list(post)),
33
+ id: 'post_user_id', name: 'post[user_id]',
34
+ class: 'form-control select valid',
35
+ disabled: disabled,
36
+ 'aria-invalid' => 'false'
37
+ )
38
+
39
+ tag.div(safe_join([label, select]), class: 'form-group')
39
40
  end
40
41
 
41
42
  def plugin_authoring_authors_list(post)
42
43
  author_id = post.new_record? ? cama_current_user.id : post.author.id
43
- ret = ''
44
- current_site.users.where('role <> ?', 'client').order(:username).each do |user|
45
- ret += "<option value='#{user.id}' #{user.id.eql?(author_id) ? 'selected' : ''}>#{user.username.titleize}#{user.fullname.eql?(user.username.titleize) ? '' : " (#{user.fullname})"}</option>"
44
+
45
+ current_site.users.where('role <> ?', 'client').order(:username).map do |user|
46
+ selected = user.id == author_id
47
+ titleized_username = user.username.titleize
48
+ display = if user.fullname == titleized_username
49
+ titleized_username
50
+ else
51
+ "#{titleized_username} (#{user.fullname})"
52
+ end
53
+ tag.option(display, value: user.id, selected: selected)
46
54
  end
47
- ret
48
55
  end
49
56
  end
50
57
  end
@@ -5,19 +5,24 @@ module Plugins
5
5
 
6
6
  def settings
7
7
  @caches = current_site.get_meta('front_cache_elements', { paths: [] })
8
- @caches[:paths] << '' unless @caches[:paths].present?
8
+ @caches[:paths] << '' if @caches[:paths].blank?
9
9
  end
10
10
 
11
11
  def save_settings
12
- current_site.set_meta('front_cache_elements', { paths: (params[:cache][:paths] || []).compact_blank || [],
13
- posts: params[:cache][:posts] || [],
14
- post_types: params[:cache][:post_type] || [],
15
- skip_posts: params[:cache][:skip_posts] || [],
16
- cache_login: params[:cache][:cache_login],
17
- home: params[:cache][:home],
18
- preserve_cache_on_restart: params[:cache][:preserve_cache_on_restart],
19
- invalidate_only: params[:cache][:invalidate_only],
20
- cache_counter: current_site.get_meta('front_cache_elements')[:cache_counter] || 0 })
12
+ current_site.set_meta(
13
+ 'front_cache_elements',
14
+ {
15
+ paths: (params[:cache][:paths] || []).compact_blank || [],
16
+ posts: params[:cache][:posts] || [],
17
+ post_types: params[:cache][:post_type] || [],
18
+ skip_posts: params[:cache][:skip_posts] || [],
19
+ cache_login: params[:cache][:cache_login],
20
+ home: params[:cache][:home],
21
+ preserve_cache_on_restart: params[:cache][:preserve_cache_on_restart],
22
+ invalidate_only: params[:cache][:invalidate_only],
23
+ cache_counter: current_site.get_meta('front_cache_elements')[:cache_counter] || 0
24
+ }
25
+ )
21
26
  flash[:notice] = t('plugin.front_cache.message.settings_saved').to_s
22
27
  redirect_to action: :settings
23
28
  end
@@ -3,7 +3,7 @@ if begin
3
3
  rescue StandardError
4
4
  false
5
5
  end
6
- CamaleonCms::Site.all.each do |site|
6
+ CamaleonCms::Site.find_each do |site|
7
7
  site.set_option('refresh_cache', true)
8
8
  end
9
9
  end
@@ -13,25 +13,35 @@ module Plugins
13
13
 
14
14
  cache_key = front_cache_plugin_cache_key
15
15
  @caches = current_site.get_meta('front_cache_elements')
16
- if !flash.keys.present? && front_cache_exist?(cache_key) # recover cache item
16
+ if flash.keys.blank? && front_cache_exist?(cache_key) # recover cache item
17
17
  Rails.logger.info "Camaleon CMS - readed cache: #{front_cache_plugin_get_path(cache_key)}"
18
18
  response.headers['PLUGIN_FRONT_CACHE'] = 'TRUE'
19
19
  args = { data: front_cache_get(cache_key).gsub('{{form_authenticity_token}}', form_authenticity_token) }
20
20
  hooks_run('front_cache_reading_cache', args)
21
+ # rubocop:disable Rails/OutputSafety -- This replays a trusted cached page body that was already rendered by Rails.
21
22
  render html: args[:data].html_safe
23
+ # rubocop:enable Rails/OutputSafety
22
24
  return
23
25
  end
24
26
 
25
27
  @_plugin_do_cache = false
26
- if @caches[:paths].include?(request.original_url) || @caches[:paths].include?(request.path_info) || front_cache_plugin_match_path_patterns?(request.original_url, request.path_info) || (params[:action] == 'index' && params[:controller] == 'camaleon_cms/frontend' && @caches[:home].present?) # cache paths and home page
28
+ # cache paths and home page
29
+ if @caches[:paths].include?(request.original_url) || @caches[:paths].include?(request.path_info) ||
30
+ front_cache_plugin_match_path_patterns?(request.original_url, request.path_info) ||
31
+ (params[:action] == 'index' && params[:controller] == 'camaleon_cms/frontend' && @caches[:home].present?)
27
32
  @_plugin_do_cache = true
28
- elsif params[:action] == 'post' && params[:controller] == 'camaleon_cms/frontend' && !params[:draft_id].present?
29
- if (post = current_site.the_posts.find_by_slug(params[:slug]))
33
+ elsif params[:action] == 'post' && params[:controller] == 'camaleon_cms/frontend' && params[:draft_id].blank?
34
+ if (post = current_site.the_posts.find_by_slug(params[:slug])) # rubocop:disable Rails/DynamicFindBy
30
35
  post = post.decorate
31
- if post.can_visit? && post.visibility != 'private'
36
+ # Never cache non-public posts. A password-protected post is unlocked per session (visibility_post
37
+ # audit M2), but the page cache is keyed on the URL alone, so caching an unlocked render would
38
+ # serve the protected body to visitors who never entered the password. Private posts are already
39
+ # excluded; password posts must be too.
40
+ if post.can_visit? && !%w[private password].include?(post.visibility)
32
41
  if (@caches[:skip_posts] || []).include?(post.id.to_s)
33
42
  @_plugin_do_cache = false
34
- elsif (@caches[:post_types] || []).include?(post.post_type_id.to_s) || (@caches[:posts] || []).include?(post.id.to_s)
43
+ elsif (@caches[:post_types] || []).include?(post.post_type_id.to_s) ||
44
+ (@caches[:posts] || []).include?(post.id.to_s)
35
45
  @_plugin_do_cache = true
36
46
  end
37
47
  end
@@ -43,7 +53,7 @@ module Plugins
43
53
 
44
54
  def front_cache_front_after_load
45
55
  cache_key = front_cache_plugin_cache_key
46
- return unless @_plugin_do_cache && !flash.keys.present?
56
+ return unless @_plugin_do_cache && flash.keys.blank?
47
57
 
48
58
  body =
49
59
  response
@@ -135,12 +145,26 @@ module Plugins
135
145
  end
136
146
 
137
147
  def front_cache_plugin_match_path_patterns?(key, key2)
138
- @caches[:paths].any? { |path_pattern| key =~ Regexp.new(path_pattern) || key2 =~ Regexp.new(path_pattern) }
148
+ front_cache_compiled_path_patterns.any? { |pattern| key =~ pattern || key2 =~ pattern }
149
+ end
150
+
151
+ # Security (audit Low): compile the admin-configured patterns once per request and skip any that
152
+ # are malformed -- Regexp.new was rebuilt on every request and raised (500) on an invalid pattern
153
+ # (RegexpError) or a non-String pattern reachable via crafted settings params (TypeError).
154
+ def front_cache_compiled_path_patterns
155
+ @front_cache_compiled_path_patterns ||= (@caches[:paths] || []).filter_map do |pattern|
156
+ Regexp.new(pattern)
157
+ rescue RegexpError, TypeError
158
+ nil
159
+ end
139
160
  end
140
161
 
141
162
  def front_cache_plugin_cache_key
142
163
  uri = [request.protocol + request.host_with_port, request.fullpath].join('/')
143
- uri.parameterize
164
+ # Security (audit Low): a lossless key. parameterize lowercased and collapsed every run of
165
+ # non-alphanumerics to '-', so distinct URLs shared one cache entry -- a visitor to one
166
+ # cacheable URL could be served the cached body of another.
167
+ Digest::SHA256.hexdigest(uri)
144
168
  end
145
169
  end
146
170
  end
@@ -17,6 +17,7 @@
17
17
  "created_post":["plugin_visibility_create_post"],
18
18
  "updated_post":["plugin_visibility_create_post"],
19
19
  "post_the_content": ["plugin_visibility_post_the_content"],
20
+ "post_the_excerpt": ["plugin_visibility_post_the_excerpt"],
20
21
  "filter_post": ["plugin_visibility_filter_post"],
21
22
  "post_can_visit": ["plugin_visibility_can_visit"],
22
23
  "list_post_extra_columns": ["plugin_visibility_extra_columns"]
@@ -0,0 +1 @@
1
+ post 'unlock/:post_id' => 'front#unlock', as: :unlock, constraints: { post_id: /[0-9]+/ }
@@ -0,0 +1,23 @@
1
+ module Plugins
2
+ module VisibilityPost
3
+ class FrontController < CamaleonCms::Apps::PluginsFrontController
4
+ # Security (audit M2): unlocks a password-protected post for the current session. Replaces
5
+ # the old GET `post_password` parameter flow, which put the password in URLs, logs and
6
+ # referrers and compared it with `==`. The password travels in a POST body, is compared in
7
+ # constant time, and the unlocked state is a session-side marker -- never the password.
8
+ def unlock
9
+ post = current_site.the_posts.where(id: params[:post_id]).first
10
+ return page_not_found if post.blank? || post.visibility != 'password'
11
+
12
+ submitted = params[:post_password].to_s
13
+ if submitted.present? && ActiveSupport::SecurityUtils.secure_compare(submitted, post.visibility_value.to_s)
14
+ session[VisibilityPostHelper::SESSION_UNLOCKED_KEY] =
15
+ Array(session[VisibilityPostHelper::SESSION_UNLOCKED_KEY]) | [post.id]
16
+ else
17
+ flash[:cama_visibility_post_error] = true
18
+ end
19
+ redirect_to post.decorate.the_url(as_path: true)
20
+ end
21
+ end
22
+ end
23
+ end