camaleon_cms 2.9.2 → 2.9.4

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 (216) 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 +14 -10
  8. data/app/apps/plugins/front_cache/config/initializer.rb +1 -1
  9. data/app/apps/plugins/front_cache/config/locales/translation.yml +0 -7
  10. data/app/apps/plugins/front_cache/front_cache_helper.rb +109 -37
  11. data/app/apps/plugins/front_cache/views/admin/settings.html.erb +0 -5
  12. data/app/apps/plugins/visibility_post/config/config.json +1 -0
  13. data/app/apps/plugins/visibility_post/config/routes_front.txt +1 -0
  14. data/app/apps/plugins/visibility_post/front_controller.rb +23 -0
  15. data/app/apps/plugins/visibility_post/visibility_post_helper.rb +141 -70
  16. data/app/apps/themes/camaleon_first/main_helper.rb +18 -12
  17. data/app/apps/themes/default/assets/css/style.css.scss +24 -0
  18. data/app/apps/themes/default/assets/genericons/genericons.css +3 -8
  19. data/app/apps/themes/default/default_helper.rb +2 -1
  20. data/app/apps/themes/default/views/layouts/index.html.erb +3 -1
  21. data/app/apps/themes/new/assets/css/styles.css.scss +24 -0
  22. data/app/apps/themes/new/custom_helper.rb +5 -9
  23. data/app/apps/themes/new/views/layouts/_header.html.erb +3 -1
  24. data/app/assets/javascripts/camaleon_cms/admin/_data.js +16 -0
  25. data/app/assets/javascripts/camaleon_cms/admin/_post.js +5 -2
  26. data/app/assets/javascripts/camaleon_cms/admin/nav_menu.js +14 -3
  27. data/app/assets/javascripts/camaleon_cms/admin/uploader/_media_manager.js +5 -0
  28. data/app/controllers/camaleon_cms/admin/appearances/nav_menus_controller.rb +8 -6
  29. data/app/controllers/camaleon_cms/admin/appearances/themes_controller.rb +8 -5
  30. data/app/controllers/camaleon_cms/admin/appearances/widgets/assign_controller.rb +7 -2
  31. data/app/controllers/camaleon_cms/admin/appearances/widgets/main_controller.rb +2 -1
  32. data/app/controllers/camaleon_cms/admin/categories_controller.rb +19 -4
  33. data/app/controllers/camaleon_cms/admin/comments_controller.rb +0 -1
  34. data/app/controllers/camaleon_cms/admin/installers_controller.rb +32 -1
  35. data/app/controllers/camaleon_cms/admin/media_controller.rb +66 -14
  36. data/app/controllers/camaleon_cms/admin/plugins_controller.rb +1 -3
  37. data/app/controllers/camaleon_cms/admin/post_tags_controller.rb +10 -4
  38. data/app/controllers/camaleon_cms/admin/posts/drafts_controller.rb +40 -11
  39. data/app/controllers/camaleon_cms/admin/posts_controller.rb +55 -22
  40. data/app/controllers/camaleon_cms/admin/sessions_controller.rb +237 -44
  41. data/app/controllers/camaleon_cms/admin/settings/custom_fields_controller.rb +80 -13
  42. data/app/controllers/camaleon_cms/admin/settings/post_types_controller.rb +5 -2
  43. data/app/controllers/camaleon_cms/admin/settings/sites_controller.rb +26 -3
  44. data/app/controllers/camaleon_cms/admin/settings_controller.rb +5 -3
  45. data/app/controllers/camaleon_cms/admin/user_roles_controller.rb +7 -5
  46. data/app/controllers/camaleon_cms/admin/users_controller.rb +78 -11
  47. data/app/controllers/camaleon_cms/admin_controller.rb +136 -26
  48. data/app/controllers/camaleon_cms/camaleon_controller.rb +37 -92
  49. data/app/controllers/camaleon_cms/frontend_controller.rb +146 -74
  50. data/app/controllers/concerns/camaleon_cms/admin/custom_fields_concern.rb +20 -5
  51. data/app/controllers/concerns/camaleon_cms/frontend_concern.rb +46 -17
  52. data/app/controllers/concerns/camaleon_cms/frontend_visited_state_concern.rb +72 -0
  53. data/app/controllers/concerns/camaleon_cms/hook_lifecycle_concern.rb +104 -0
  54. data/app/controllers/concerns/camaleon_cms/request_context_concern.rb +113 -0
  55. data/app/controllers/concerns/camaleon_cms/runtime_admin_menu_concern.rb +237 -0
  56. data/app/controllers/concerns/camaleon_cms/runtime_captcha_image_concern.rb +13 -0
  57. data/app/controllers/concerns/camaleon_cms/runtime_html_content_concern.rb +143 -0
  58. data/app/controllers/concerns/camaleon_cms/runtime_shortcode_theme_concern.rb +246 -0
  59. data/app/controllers/concerns/camaleon_cms/runtime_state_concern.rb +13 -0
  60. data/app/controllers/concerns/camaleon_cms/runtime_uploader_concern.rb +54 -0
  61. data/app/controllers/concerns/camaleon_cms/session_captcha_runtime_concern.rb +19 -0
  62. data/app/controllers/concerns/camaleon_cms/session_runtime_concern.rb +40 -0
  63. data/app/decorators/camaleon_cms/application_decorator.rb +11 -8
  64. data/app/decorators/camaleon_cms/category_decorator.rb +3 -2
  65. data/app/decorators/camaleon_cms/post_comment_decorator.rb +13 -5
  66. data/app/decorators/camaleon_cms/post_decorator.rb +68 -50
  67. data/app/decorators/camaleon_cms/post_type_decorator.rb +1 -1
  68. data/app/decorators/camaleon_cms/site_decorator.rb +33 -25
  69. data/app/decorators/camaleon_cms/term_taxonomy_decorator.rb +21 -10
  70. data/app/decorators/camaleon_cms/theme_decorator.rb +3 -3
  71. data/app/decorators/camaleon_cms/user_decorator.rb +26 -5
  72. data/app/helpers/camaleon_cms/admin/application_helper.rb +5 -8
  73. data/app/helpers/camaleon_cms/admin/breadcrumb_helper.rb +1 -1
  74. data/app/helpers/camaleon_cms/admin/custom_fields_helper.rb +24 -23
  75. data/app/helpers/camaleon_cms/admin/menus_helper.rb +249 -139
  76. data/app/helpers/camaleon_cms/admin/post_type_helper.rb +34 -17
  77. data/app/helpers/camaleon_cms/camaleon_helper.rb +31 -39
  78. data/app/helpers/camaleon_cms/captcha_helper.rb +73 -53
  79. data/app/helpers/camaleon_cms/comment_helper.rb +19 -7
  80. data/app/helpers/camaleon_cms/content_helper.rb +13 -6
  81. data/app/helpers/camaleon_cms/email_helper.rb +7 -3
  82. data/app/helpers/camaleon_cms/frontend/application_helper.rb +6 -1
  83. data/app/helpers/camaleon_cms/frontend/content_select_helper.rb +34 -20
  84. data/app/helpers/camaleon_cms/frontend/nav_menu_helper.rb +143 -66
  85. data/app/helpers/camaleon_cms/frontend/seo_helper.rb +39 -28
  86. data/app/helpers/camaleon_cms/frontend/site_helper.rb +32 -12
  87. data/app/helpers/camaleon_cms/hooks_helper.rb +14 -4
  88. data/app/helpers/camaleon_cms/html_helper.rb +58 -37
  89. data/app/helpers/camaleon_cms/plugins_helper.rb +23 -24
  90. data/app/helpers/camaleon_cms/session_helper.rb +188 -44
  91. data/app/helpers/camaleon_cms/short_code_helper.rb +181 -85
  92. data/app/helpers/camaleon_cms/site_helper.rb +34 -13
  93. data/app/helpers/camaleon_cms/theme_helper.rb +35 -19
  94. data/app/helpers/camaleon_cms/uploader_helper.rb +19 -473
  95. data/app/helpers/camaleon_cms/user_roles_helper.rb +5 -4
  96. data/app/mailers/camaleon_cms/html_mailer.rb +4 -4
  97. data/app/models/camaleon_cms/ability.rb +15 -7
  98. data/app/models/camaleon_cms/category.rb +6 -6
  99. data/app/models/camaleon_cms/custom_field.rb +15 -6
  100. data/app/models/camaleon_cms/custom_field_group.rb +39 -22
  101. data/app/models/camaleon_cms/custom_fields_relationship.rb +140 -2
  102. data/app/models/camaleon_cms/media.rb +8 -2
  103. data/app/models/camaleon_cms/meta.rb +2 -2
  104. data/app/models/camaleon_cms/nav_menu.rb +5 -3
  105. data/app/models/camaleon_cms/nav_menu_item.rb +16 -8
  106. data/app/models/camaleon_cms/plugin.rb +3 -4
  107. data/app/models/camaleon_cms/post.rb +137 -27
  108. data/app/models/camaleon_cms/post_comment.rb +10 -6
  109. data/app/models/camaleon_cms/post_default.rb +42 -12
  110. data/app/models/camaleon_cms/post_relationship.rb +6 -4
  111. data/app/models/camaleon_cms/post_tag.rb +4 -5
  112. data/app/models/camaleon_cms/post_type.rb +21 -13
  113. data/app/models/camaleon_cms/site.rb +39 -29
  114. data/app/models/camaleon_cms/term_relationship.rb +10 -7
  115. data/app/models/camaleon_cms/term_taxonomy.rb +58 -4
  116. data/app/models/camaleon_cms/theme.rb +2 -4
  117. data/app/models/camaleon_cms/user.rb +17 -5
  118. data/app/models/camaleon_cms/user_role.rb +81 -6
  119. data/app/models/camaleon_cms/widget/assigned.rb +11 -6
  120. data/app/models/camaleon_cms/widget/main.rb +12 -8
  121. data/app/models/camaleon_cms/widget/sidebar.rb +3 -8
  122. data/app/models/camaleon_record.rb +89 -5
  123. data/app/models/concerns/camaleon_cms/categories_tags_for_posts.rb +36 -42
  124. data/app/models/concerns/camaleon_cms/common_relationships.rb +18 -5
  125. data/app/models/concerns/camaleon_cms/content_shortcode_gate.rb +73 -0
  126. data/app/models/concerns/camaleon_cms/custom_fields_read.rb +111 -54
  127. data/app/models/concerns/camaleon_cms/metas.rb +31 -6
  128. data/app/models/concerns/camaleon_cms/normalize_attrs.rb +2 -8
  129. data/app/models/concerns/camaleon_cms/site_default_settings.rb +120 -16
  130. data/app/models/concerns/camaleon_cms/user_methods.rb +44 -19
  131. data/app/models/current_request.rb +8 -1
  132. data/app/uploaders/camaleon_cms_aws_uploader.rb +31 -10
  133. data/app/uploaders/camaleon_cms_local_uploader.rb +76 -19
  134. data/app/uploaders/camaleon_cms_uploader.rb +23 -10
  135. data/app/validators/camaleon_cms/post_uniq_validator.rb +5 -4
  136. data/app/validators/camaleon_cms/uniq_validator.rb +1 -1
  137. data/app/validators/camaleon_cms/user_url_validator.rb +215 -36
  138. data/app/views/camaleon_cms/admin/categories/index.html.erb +2 -2
  139. data/app/views/camaleon_cms/admin/installers/form.html.erb +5 -0
  140. data/app/views/camaleon_cms/admin/installers/welcome.html.erb +30 -6
  141. data/app/views/camaleon_cms/admin/plugins/_plugins_list.html.erb +2 -2
  142. data/app/views/camaleon_cms/admin/post_tags/index.html.erb +2 -2
  143. data/app/views/camaleon_cms/admin/posts/_sidebar.html.erb +1 -1
  144. data/app/views/camaleon_cms/admin/posts/form.html.erb +3 -2
  145. data/app/views/camaleon_cms/admin/posts/index.html.erb +5 -5
  146. data/app/views/camaleon_cms/admin/sessions/back_to_parent.html.erb +39 -0
  147. data/app/views/camaleon_cms/admin/sessions/logout_confirm.html.erb +27 -0
  148. data/app/views/camaleon_cms/admin/settings/_email_settings.html.erb +4 -1
  149. data/app/views/camaleon_cms/admin/settings/custom_fields/_get_items.html.erb +1 -1
  150. data/app/views/camaleon_cms/admin/settings/custom_fields/_render.html.erb +19 -4
  151. data/app/views/camaleon_cms/admin/settings/custom_fields/_render_category_simple.html.erb +1 -1
  152. data/app/views/camaleon_cms/admin/settings/custom_fields/form.html.erb +4 -3
  153. data/app/views/camaleon_cms/admin/settings/shortcodes.html.erb +4 -4
  154. data/app/views/camaleon_cms/admin/settings/site.html.erb +1 -1
  155. data/app/views/camaleon_cms/admin/settings/sites/form.html.erb +1 -1
  156. data/app/views/camaleon_cms/admin/user_roles/form.html.erb +24 -3
  157. data/app/views/camaleon_cms/admin/users/form.html.erb +8 -4
  158. data/app/views/camaleon_cms/admin/users/index.html.erb +1 -1
  159. data/app/views/camaleon_cms/default_theme/partials/_render_custom_field.html.erb +8 -2
  160. data/app/views/camaleon_cms/default_theme/sitemap.html.erb +1 -1
  161. data/app/views/camaleon_cms/default_theme/sitemap.xml.builder +1 -1
  162. data/app/views/layouts/camaleon_cms/admin/_header.html.erb +1 -1
  163. data/config/initializers/action_view.rb +42 -5
  164. data/config/initializers/active_record_extension.rb +13 -27
  165. data/config/initializers/assets.rb +52 -23
  166. data/config/initializers/custom_initializers.rb +1 -1
  167. data/config/locales/camaleon_cms/admin/ar.yml +1 -0
  168. data/config/locales/camaleon_cms/admin/de.yml +1 -0
  169. data/config/locales/camaleon_cms/admin/en.yml +36 -0
  170. data/config/locales/camaleon_cms/admin/es.yml +1 -0
  171. data/config/locales/camaleon_cms/admin/fr.yml +1 -0
  172. data/config/locales/camaleon_cms/admin/it.yml +1 -0
  173. data/config/locales/camaleon_cms/admin/nl.yml +1 -0
  174. data/config/locales/camaleon_cms/admin/pt-BR.yml +1 -0
  175. data/config/locales/camaleon_cms/admin/pt.yml +1 -0
  176. data/config/locales/camaleon_cms/admin/ru.yml +1 -0
  177. data/config/locales/camaleon_cms/admin/uk.yml +1 -0
  178. data/config/locales/camaleon_cms/admin/zh-CH.yml +1 -0
  179. data/config/routes/admin.rb +33 -15
  180. data/config/routes/frontend.rb +21 -15
  181. data/lib/camaleon_cms/assets_precompile.rb +86 -0
  182. data/lib/camaleon_cms/captcha_image_generation.rb +65 -0
  183. data/lib/camaleon_cms/content_security.rb +158 -0
  184. data/lib/camaleon_cms/engine.rb +45 -21
  185. data/lib/camaleon_cms/media_security_headers.rb +27 -0
  186. data/lib/camaleon_cms/setup_token.rb +69 -0
  187. data/lib/camaleon_cms/shortcode_registry.rb +102 -0
  188. data/lib/camaleon_cms/svg_content_checker.rb +123 -0
  189. data/lib/camaleon_cms/task_reporter.rb +14 -0
  190. data/lib/camaleon_cms/unsafe_markup.rb +207 -0
  191. data/lib/camaleon_cms/uploader_content_security.rb +248 -0
  192. data/lib/camaleon_cms/uploader_image_processing.rb +166 -0
  193. data/lib/camaleon_cms/uploader_path_security.rb +146 -0
  194. data/lib/camaleon_cms/uploader_pipeline.rb +350 -0
  195. data/lib/camaleon_cms/uploader_support.rb +86 -0
  196. data/lib/camaleon_cms/version.rb +1 -1
  197. data/lib/camaleon_cms.rb +23 -0
  198. data/lib/ext/hash.rb +28 -2
  199. data/lib/ext/string.rb +10 -5
  200. data/lib/ext/translator.rb +10 -2
  201. data/lib/generators/camaleon_cms/gem_plugin_template/app/controllers/plugins/my_plugin/admin_controller.rb +5 -1
  202. data/lib/generators/camaleon_cms/install_generator.rb +7 -4
  203. data/lib/generators/camaleon_cms/install_template/plugin_routes.rb +5 -12
  204. data/lib/generators/camaleon_cms/theme_generator.rb +5 -10
  205. data/lib/generators/camaleon_cms/theme_template/views/layouts/index.html.erb +3 -1
  206. data/lib/plugin_routes.rb +412 -368
  207. data/lib/tasks/camaleon_cms/camaleon_tasks.rake +2 -1
  208. data/lib/tasks/cross_site_field_groups.rake +74 -0
  209. data/lib/tasks/custom_fields_roles.rake +15 -13
  210. data/lib/tasks/orphaned_comments.rake +42 -0
  211. data/lib/tasks/private_upload_acls.rake +31 -0
  212. data/lib/tasks/site_custom_field_groups.rake +36 -0
  213. data/lib/tasks/unsafe_stored_content.rake +42 -0
  214. data/lib/tasks/unsafe_stored_uploads.rake +70 -0
  215. data/lib/tasks/user_field_groups.rake +27 -0
  216. metadata +125 -4
@@ -10,7 +10,8 @@ namespace :camaleon_cms do
10
10
  include Rails.application.routes.url_helpers
11
11
  $current_site = CamaleonCms::Site.find(ENV['site_id'].to_i)
12
12
  cama_uploader_init_connection
13
- @fog_connection_bucket_dir.files.all.each do |file|
13
+ # TODO: The `fog` gem isn't used anymore - this whole task should be refactored
14
+ @fog_connection_bucket_dir.files.all.each do |file| # rubocop:disable Rails/FindEach
14
15
  puts file.inspect
15
16
  cama_uploader_generate_thumbnail(file.key, file.key, '')
16
17
  end
@@ -0,0 +1,74 @@
1
+ namespace :camaleon_cms do
2
+ # A custom field group carries tenancy in `parent_id` (which site owns and administers it) and
3
+ # placement in `object_class` + `objectid` (which record's admin page displays it). Placement reads
4
+ # are not scoped by site, so a group whose two disagree renders on the site owning the placement
5
+ # target while staying invisible in that site's own field group list, which is tenancy-scoped.
6
+ #
7
+ # Re-home rather than delete: a mismatch is not proof of an injection -- a plugin stamping
8
+ # parent_id from the wrong site produces the same shape -- and deleting would take the group's
9
+ # field definitions and their values with it. Setting parent_id to the site that already renders
10
+ # the group restores the invariant and surfaces it where an administrator can judge and remove it.
11
+ desc 'Re-home custom field groups whose owning site differs from the site owning their placement'
12
+ task rehome_cross_site_field_groups: :environment do
13
+ report = CamaleonCms::TaskReporter
14
+ report.call 'Re-homing cross-site custom field groups...'
15
+ rehomed_count = 0
16
+ skipped_count = 0
17
+
18
+ CamaleonCms::CustomFieldGroup.unscoped.where.not(object_class: '_fields').find_each do |group|
19
+ site_id = cama_placement_site_id(group)
20
+
21
+ if site_id.blank?
22
+ report.call " Skipped group id=#{group.id} slug=#{group.slug} " \
23
+ "(#{group.object_class}/#{group.objectid} does not resolve to a site)"
24
+ skipped_count += 1
25
+ next
26
+ end
27
+
28
+ next if site_id == group.parent_id
29
+
30
+ begin
31
+ report.call "✓ Re-homed group id=#{group.id} slug=#{group.slug} " \
32
+ "from site_id=#{group.parent_id} to site_id=#{site_id} " \
33
+ "(placement #{group.object_class}/#{group.objectid})"
34
+ group.update_column(:parent_id, site_id) # rubocop:disable Rails/SkipsModelValidations
35
+ rehomed_count += 1
36
+ rescue StandardError => e
37
+ report.call "✗ Failed to re-home group id=#{group.id}: #{e.message}"
38
+ skipped_count += 1
39
+ end
40
+ end
41
+
42
+ report.call "\nSummary:"
43
+ report.call " Re-homed: #{rehomed_count} field groups"
44
+ report.call " Skipped: #{skipped_count} field groups"
45
+ report.call "\nDone. Review the field group list of any site that gained a group."
46
+ end
47
+ end
48
+
49
+ # Resolve the site owning a group's placement target, or nil when it cannot be determined.
50
+ # Best-effort by design: a placement naming a record that no longer exists is left alone rather
51
+ # than guessed at or deleted.
52
+ def cama_placement_site_id(group)
53
+ objectid = group.objectid
54
+ return nil if objectid.blank?
55
+
56
+ case group.object_class
57
+ when 'PostType', 'PostType_Post', 'PostType_Category', 'PostType_PostTag'
58
+ CamaleonCms::PostType.find_by(id: objectid)&.parent_id
59
+ when 'Theme' then CamaleonCms::Theme.find_by(id: objectid)&.parent_id
60
+ when 'Plugin' then CamaleonCms::Plugin.find_by(id: objectid)&.parent_id
61
+ when 'NavMenu' then CamaleonCms::NavMenu.find_by(id: objectid)&.parent_id
62
+ when 'Widget::Main' then CamaleonCms::Widget::Main.find_by(id: objectid)&.parent_id
63
+ when 'Post' then CamaleonCms::Post.find_by(id: objectid)&.post_type&.parent_id
64
+ when 'Category', 'Category_Post' then CamaleonCms::Category.find_by(id: objectid)&.post_type_parent&.parent_id
65
+ when 'PostTag' then CamaleonCms::PostTag.find_by(id: objectid)&.post_type&.parent_id
66
+ else
67
+ # Site, the configured user model, and models registered through the custom_field_custom_models
68
+ # hook are all placed with the site's own id, so the placement target is the site itself.
69
+ CamaleonCms::Site.find_by(id: objectid)&.id
70
+ end
71
+ rescue StandardError => e
72
+ CamaleonCms::TaskReporter.call " Could not resolve placement for group id=#{group.id}: #{e.message}"
73
+ nil
74
+ end
@@ -1,7 +1,8 @@
1
1
  namespace :camaleon_cms do
2
2
  desc 'Backfill user roles to include custom_fields manager permission'
3
3
  task backfill_custom_fields_permission: :environment do
4
- Rails.logger.info 'Backfilling custom_fields manager permission for existing user roles...'
4
+ report = CamaleonCms::TaskReporter
5
+ report.call 'Backfilling custom_fields manager permission for existing user roles...'
5
6
  CamaleonCms::UserRole.find_each do |role|
6
7
  key = "_manager_#{role.parent_id}"
7
8
  begin
@@ -10,20 +11,21 @@ namespace :camaleon_cms do
10
11
  if current_role.blank? || (!current_role.is_a?(Hash) || current_role['custom_fields'].blank?)
11
12
  current_role = (current_role.is_a?(Hash) ? current_role : {}).merge!('custom_fields' => 1)
12
13
  role.set_meta(key, current_role)
13
- Rails.logger.info "Updated role=#{role.slug} site_id=#{role.parent_id}"
14
+ report.call "Updated role=#{role.slug} site_id=#{role.parent_id}"
14
15
  else
15
- Rails.logger.info "Skipped role=#{role.slug} site_id=#{role.parent_id} (already has custom_fields)"
16
+ report.call "Skipped role=#{role.slug} site_id=#{role.parent_id} (already has custom_fields)"
16
17
  end
17
18
  rescue StandardError => e
18
- Rails.logger.info "Failed to update role=#{role.slug}: #{e.message}"
19
+ report.call "Failed to update role=#{role.slug}: #{e.message}"
19
20
  end
20
21
  end
21
- Rails.logger.info 'Done.'
22
+ report.call 'Done.'
22
23
  end
23
24
 
24
25
  desc 'Backfill admin user roles to include select_eval permission'
25
26
  task backfill_select_eval_permission: :environment do
26
- Rails.logger.info 'Backfilling select_eval permission for admin roles...'
27
+ report = CamaleonCms::TaskReporter
28
+ report.call 'Backfilling select_eval permission for admin roles...'
27
29
  updated_count = 0
28
30
  skipped_count = 0
29
31
 
@@ -37,20 +39,20 @@ namespace :camaleon_cms do
37
39
  if !current_meta[:select_eval]
38
40
  updated_meta = current_meta.merge(select_eval: 1)
39
41
  role.set_meta(key, updated_meta)
40
- Rails.logger.info "✓ Updated admin role site_id=#{site_id}"
42
+ report.call "✓ Updated admin role site_id=#{site_id}"
41
43
  updated_count += 1
42
44
  else
43
- Rails.logger.info " Skipped admin role site_id=#{site_id} (already has select_eval)"
45
+ report.call " Skipped admin role site_id=#{site_id} (already has select_eval)"
44
46
  skipped_count += 1
45
47
  end
46
48
  rescue StandardError => e
47
- Rails.logger.info "✗ Failed to update admin role site_id=#{site_id}: #{e.message}"
49
+ report.call "✗ Failed to update admin role site_id=#{site_id}: #{e.message}"
48
50
  end
49
51
  end
50
52
 
51
- Rails.logger.info "\nSummary:"
52
- Rails.logger.info " Updated: #{updated_count} admin roles"
53
- Rails.logger.info " Skipped: #{skipped_count} admin roles"
54
- Rails.logger.info "\nDone! All admin roles now have select_eval permission."
53
+ report.call "\nSummary:"
54
+ report.call " Updated: #{updated_count} admin roles"
55
+ report.call " Skipped: #{skipped_count} admin roles"
56
+ report.call "\nDone! All admin roles now have select_eval permission."
55
57
  end
56
58
  end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :camaleon_cms do
4
+ desc 'Reassign comments whose user no longer exists to the owning site\'s anonymous user'
5
+ task reassign_orphaned_comments: :environment do
6
+ user_table = CamaManager.get_user_class_name.constantize.table_name
7
+ comment_table = CamaleonCms::PostComment.table_name
8
+
9
+ # `user_id IS NULL` on its own does not mean "orphaned": guest comments are a first-class
10
+ # shape — user_id is nullable and the row carries author/author_email/author_url instead —
11
+ # and they have always had a null user_id. The regression nulled the user_id of *registered*
12
+ # users' comments, and those never carry an author string, which is exactly why
13
+ # PostCommentDecorator#the_author_name falls back to the user's name. Requiring a blank
14
+ # author therefore separates the rows this task exists to repair from the ones it must not
15
+ # touch. Rows with a dangling (non-null, non-existent) user_id are repaired regardless.
16
+ orphans = CamaleonCms::PostComment
17
+ .where("(#{comment_table}.user_id IS NULL AND " \
18
+ "(#{comment_table}.author IS NULL OR #{comment_table}.author = '')) " \
19
+ "OR (#{comment_table}.user_id IS NOT NULL AND #{comment_table}.user_id NOT IN " \
20
+ "(SELECT id FROM #{user_table}))")
21
+ reassigned = 0
22
+ skipped = 0
23
+
24
+ orphans.find_each do |comment|
25
+ site = comment.post&.post_type&.site
26
+ if site.nil?
27
+ skipped += 1
28
+ next
29
+ end
30
+
31
+ comment.update_column(:user_id, site.get_anonymous_user.id) # rubocop:disable Rails/SkipsModelValidations
32
+ reassigned += 1
33
+ end
34
+
35
+ summary = "camaleon_cms:reassign_orphaned_comments reassigned #{reassigned} comment(s)" \
36
+ "#{skipped > 0 ? ", skipped #{skipped} without a resolvable site" : ''}"
37
+ Rails.logger.info(summary)
38
+ # An operator runs this from a terminal, where Rails.logger writes to a file they are not
39
+ # watching. Without this the task looks like it did nothing.
40
+ puts summary
41
+ end
42
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :camaleon_cms do
4
+ desc 'Re-apply the owner-only ACL to S3 objects under each AWS site\'s private upload prefix ' \
5
+ '(uploads stored before the private-ACL fix were world-readable). ' \
6
+ 'Set CAMA_S3_INNER_FOLDER for setups whose uploader hook configures an inner_folder.'
7
+ task repair_private_upload_acls: :environment do
8
+ inner_folder = ENV['CAMA_S3_INNER_FOLDER'].to_s
9
+ repaired_total = 0
10
+ swept_sites = 0
11
+
12
+ CamaleonCms::Site.find_each do |site_record|
13
+ site = site_record.decorate
14
+ next unless %w[aws s3].include?(site.get_option('filesystem_type', 'local').to_s.downcase)
15
+
16
+ uploader = CamaleonCmsAwsUploader.new({ current_site: site,
17
+ aws_settings: { 'inner_folder' => inner_folder } }, nil)
18
+ repaired = uploader.repair_private_acls!
19
+ swept_sites += 1
20
+ repaired_total += repaired
21
+ puts " #{site.the_slug}: #{repaired} object(s) re-ACLed"
22
+ end
23
+
24
+ summary = "camaleon_cms:repair_private_upload_acls re-ACLed #{repaired_total} object(s) " \
25
+ "across #{swept_sites} AWS site(s)"
26
+ Rails.logger.info(summary)
27
+ # An operator runs this from a terminal, where Rails.logger writes to a file they are not
28
+ # watching. Without this the task looks like it did nothing.
29
+ puts summary
30
+ end
31
+ end
@@ -0,0 +1,36 @@
1
+ namespace :camaleon_cms do
2
+ # Site#get_field_groups matches a group's placement (object_class + objectid) instead of its
3
+ # tenancy (parent_id). `objectid` has no presence validation, so a group could be persisted with
4
+ # object_class 'Site' and a NULL objectid by bypassing add_custom_field_group. Those rows are
5
+ # invisible on the site settings page under the placement-scoped read, so give them the objectid
6
+ # they should always have had: the site that owns them.
7
+ desc 'Backfill objectid for site custom field groups that were saved without one'
8
+ task backfill_site_field_group_objectid: :environment do
9
+ report = CamaleonCms::TaskReporter
10
+ report.call 'Backfilling objectid for site custom field groups...'
11
+ updated_count = 0
12
+ skipped_count = 0
13
+
14
+ CamaleonCms::CustomField.unscoped.where(object_class: 'Site', objectid: nil).find_each do |group|
15
+ if group.parent_id.blank?
16
+ report.call " Skipped group id=#{group.id} slug=#{group.slug} (no owning site)"
17
+ skipped_count += 1
18
+ next
19
+ end
20
+
21
+ begin
22
+ group.update_column(:objectid, group.parent_id) # rubocop:disable Rails/SkipsModelValidations
23
+ report.call "✓ Updated group id=#{group.id} slug=#{group.slug} site_id=#{group.parent_id}"
24
+ updated_count += 1
25
+ rescue StandardError => e
26
+ report.call "✗ Failed to update group id=#{group.id}: #{e.message}"
27
+ skipped_count += 1
28
+ end
29
+ end
30
+
31
+ report.call "\nSummary:"
32
+ report.call " Updated: #{updated_count} field groups"
33
+ report.call " Skipped: #{skipped_count} field groups"
34
+ report.call "\nDone."
35
+ end
36
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :camaleon_cms do
4
+ namespace :security do
5
+ # The scan-and-reject gates refuse dangerous content at save time but never rewrite what is
6
+ # already stored (nothing is ever rewritten under this model). This task lists the stored
7
+ # content that would fail today's gates -- post content and gated custom-field values -- so an
8
+ # operator can review and clean it up by hand. Read-only: it changes nothing.
9
+ desc 'List stored post content and custom-field values that would fail the scan-and-reject gates'
10
+ task scan_content: :environment do
11
+ report = CamaleonCms::TaskReporter
12
+ report.call 'Scanning stored content against the scan-and-reject gates (read-only)...'
13
+ flagged = 0
14
+
15
+ CamaleonCms::Post.unscoped.where.not(content: [nil, '']).find_each do |post|
16
+ next unless CamaleonCms::UnsafeMarkup.unsafe_html?(
17
+ post.content, tags: CamaleonCms::Post::CONTENT_ALLOWED_TAGS,
18
+ attributes: CamaleonCms::Post::CONTENT_ALLOWED_ATTRIBUTES
19
+ )
20
+
21
+ flagged += 1
22
+ report.call "✗ Post id=#{post.id} (#{post.post_class}) '#{post.slug.to_s.truncate(60)}': " \
23
+ 'content would be rejected'
24
+ end
25
+
26
+ # Reuse the model's own gate dispatch (which covers editor, field_attrs and URI field types),
27
+ # so the scan can never diverge from what the save-time validation would refuse.
28
+ CamaleonCms::CustomFieldsRelationship.unscoped.where.not(value: [nil, ''])
29
+ .includes(:custom_field).find_each do |row|
30
+ field_key = row.custom_field&.options&.[](:field_key).to_s
31
+ next unless CamaleonCms::CustomFieldsRelationship.gate_rejection_reason(field_key, row.value)
32
+
33
+ flagged += 1
34
+ report.call "✗ Custom-field value id=#{row.id} field='#{row.custom_field_slug}' " \
35
+ "(#{field_key}) on #{row.object_class} ##{row.objectid}: value would be rejected"
36
+ end
37
+
38
+ report.call "Done. #{flagged} stored item(s) would be rejected by today's gates."
39
+ report.call 'Nothing was modified; review and clean up the listed items by hand.'
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :camaleon_cms do
4
+ namespace :security do
5
+ # The upload rules refuse dangerous content at upload time but never re-examine what is already
6
+ # stored (nothing is ever rewritten under this model). This task lists the stored files today's
7
+ # rules would refuse, so an operator can review them by hand. Read-only: it changes nothing.
8
+ #
9
+ # The companion for authored content is camaleon_cms:security:scan_content.
10
+ desc 'List stored media files that would fail the upload scan rules'
11
+ task scan_uploads: :environment do
12
+ report = CamaleonCms::TaskReporter
13
+ # Reuse the real scanner rather than reimplementing its rules, so the report can never
14
+ # disagree with what an upload of the same bytes would be told today.
15
+ scanner = Class.new { include CamaleonCms::UploaderContentSecurity }.new
16
+ flagged = 0
17
+ scanned = 0
18
+
19
+ report.call 'Scanning stored media against the upload scan rules (read-only)...'
20
+
21
+ # Scans every file under `root`, reporting those today's rules would refuse. `label` names the
22
+ # location and `base` anchors the reported path. Closes over the counters so the public and
23
+ # private roots share one pass rather than duplicating the scan logic (and risking drift).
24
+ # FNM_DOTMATCH so dotfiles are scanned: a basename that is entirely an extension (`.svg`, `.js`)
25
+ # is exactly what the scanner fail-closes on. `.`/`..` are directory entries File.file? drops.
26
+ scan_tree = lambda do |root, base, label|
27
+ next unless root.directory?
28
+
29
+ Dir.glob(root.join('**', '*'), File::FNM_DOTMATCH).sort.each do |path|
30
+ next unless File.file?(path)
31
+
32
+ scanned += 1
33
+ relative = Pathname.new(path).relative_path_from(base)
34
+ reason = begin
35
+ scanner.content_unsafe?(File.binread(path), filename: File.basename(path))
36
+ rescue StandardError => e
37
+ # A file the scan cannot read at all is reported rather than skipped: an unreadable
38
+ # entry is exactly what an operator wants to know about, and swallowing it would hand
39
+ # them a false all-clear.
40
+ "unreadable (#{e.class})"
41
+ end
42
+ next unless reason
43
+
44
+ flagged += 1
45
+ report.call "✗ #{label} /#{relative}: would be rejected (#{reason})"
46
+ end
47
+ end
48
+
49
+ CamaleonCms::Site.unscoped.find_each do |site|
50
+ # Site#upload_directory honours the `media_slug_folder` config, which stores uploads under
51
+ # media/<slug> instead of media/<id>. Hardcoding the id scanned nothing on a slug-configured
52
+ # install and printed a clean "0 would be rejected" -- a false all-clear.
53
+ scan_tree.call(Pathname.new(site.upload_directory), Rails.public_path, "Site #{site.id}")
54
+ end
55
+
56
+ # Private media is scanned at upload time exactly like public media, so files stored before
57
+ # these rules apply are equally worth surfacing. It lives outside the public root, under its
58
+ # own directory, so it is scanned separately and reported relative to the app root.
59
+ scan_tree.call(Rails.root.join(CamaleonCmsUploader::PRIVATE_DIRECTORY), Rails.root, 'Private media')
60
+
61
+ report.call "Done. #{scanned} stored file(s) scanned, #{flagged} would be rejected by today's rules."
62
+ report.call 'Nothing was modified.'
63
+ if flagged > 0
64
+ report.call 'A finding is not by itself evidence of compromise: these rules are newer than ' \
65
+ 'the files, so content uploaded legitimately under the previous rules is ' \
66
+ 'listed too. Review each one before acting.'
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :camaleon_cms do
4
+ # Through 2.9.2 the custom-fields settings form emitted the raw `user_model` config string as
5
+ # the users placement option, so a namespaced host user model (e.g. 'Admin::User') stored its
6
+ # user field groups under the qualified name — which the demodulized association scope and the
7
+ # 2.9.2 allowed-slugs save filter cannot see. Re-key those group placements to the demodulized
8
+ # contract. Only the group rows move: their fields hang off parent_id under '_fields', group
9
+ # metas key off 'CustomFieldGroup', and value rows were always written under the demodulized
10
+ # name. Keyed to the current config: a host that renamed its user model after writing groups
11
+ # should temporarily restore the historical value while running the task.
12
+ desc 'Re-key user custom field groups stored under a namespaced user_model name'
13
+ task demodulize_user_field_groups: :environment do
14
+ configured = PluginRoutes.static_system_info['user_model'].presence.to_s
15
+ demodulized = configured.demodulize
16
+
17
+ if configured.blank? || configured == demodulized
18
+ puts 'Nothing to re-key: the configured user model is not namespaced.'
19
+ next
20
+ end
21
+
22
+ count = CamaleonCms::CustomField.unscoped
23
+ .where(object_class: configured)
24
+ .update_all(object_class: demodulized) # rubocop:disable Rails/SkipsModelValidations
25
+ puts "Re-keyed #{count} user field group(s) from '#{configured}' to '#{demodulized}'."
26
+ end
27
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: camaleon_cms
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.9.2
4
+ version: 2.9.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Owen Peredo Diaz
@@ -57,14 +57,14 @@ dependencies:
57
57
  requirements:
58
58
  - - "~>"
59
59
  - !ruby/object:Gem::Version
60
- version: 0.1.0
60
+ version: 0.1.13
61
61
  type: :runtime
62
62
  prerelease: false
63
63
  version_requirements: !ruby/object:Gem::Requirement
64
64
  requirements:
65
65
  - - "~>"
66
66
  - !ruby/object:Gem::Version
67
- version: 0.1.0
67
+ version: 0.1.13
68
68
  - !ruby/object:Gem::Dependency
69
69
  name: cama_meta_tag
70
70
  requirement: !ruby/object:Gem::Requirement
@@ -449,6 +449,76 @@ dependencies:
449
449
  - - ">="
450
450
  - !ruby/object:Gem::Version
451
451
  version: '0'
452
+ - !ruby/object:Gem::Dependency
453
+ name: rubocop-capybara
454
+ requirement: !ruby/object:Gem::Requirement
455
+ requirements:
456
+ - - ">="
457
+ - !ruby/object:Gem::Version
458
+ version: '0'
459
+ type: :development
460
+ prerelease: false
461
+ version_requirements: !ruby/object:Gem::Requirement
462
+ requirements:
463
+ - - ">="
464
+ - !ruby/object:Gem::Version
465
+ version: '0'
466
+ - !ruby/object:Gem::Dependency
467
+ name: rubocop-factory_bot
468
+ requirement: !ruby/object:Gem::Requirement
469
+ requirements:
470
+ - - ">="
471
+ - !ruby/object:Gem::Version
472
+ version: '0'
473
+ type: :development
474
+ prerelease: false
475
+ version_requirements: !ruby/object:Gem::Requirement
476
+ requirements:
477
+ - - ">="
478
+ - !ruby/object:Gem::Version
479
+ version: '0'
480
+ - !ruby/object:Gem::Dependency
481
+ name: rubocop-performance
482
+ requirement: !ruby/object:Gem::Requirement
483
+ requirements:
484
+ - - ">="
485
+ - !ruby/object:Gem::Version
486
+ version: '0'
487
+ type: :development
488
+ prerelease: false
489
+ version_requirements: !ruby/object:Gem::Requirement
490
+ requirements:
491
+ - - ">="
492
+ - !ruby/object:Gem::Version
493
+ version: '0'
494
+ - !ruby/object:Gem::Dependency
495
+ name: rubocop-rails
496
+ requirement: !ruby/object:Gem::Requirement
497
+ requirements:
498
+ - - ">="
499
+ - !ruby/object:Gem::Version
500
+ version: '0'
501
+ type: :development
502
+ prerelease: false
503
+ version_requirements: !ruby/object:Gem::Requirement
504
+ requirements:
505
+ - - ">="
506
+ - !ruby/object:Gem::Version
507
+ version: '0'
508
+ - !ruby/object:Gem::Dependency
509
+ name: rubocop-rake
510
+ requirement: !ruby/object:Gem::Requirement
511
+ requirements:
512
+ - - ">="
513
+ - !ruby/object:Gem::Version
514
+ version: '0'
515
+ type: :development
516
+ prerelease: false
517
+ version_requirements: !ruby/object:Gem::Requirement
518
+ requirements:
519
+ - - ">="
520
+ - !ruby/object:Gem::Version
521
+ version: '0'
452
522
  - !ruby/object:Gem::Dependency
453
523
  name: rubocop-rspec
454
524
  requirement: !ruby/object:Gem::Requirement
@@ -463,6 +533,20 @@ dependencies:
463
533
  - - ">="
464
534
  - !ruby/object:Gem::Version
465
535
  version: '0'
536
+ - !ruby/object:Gem::Dependency
537
+ name: rubocop-rspec_rails
538
+ requirement: !ruby/object:Gem::Requirement
539
+ requirements:
540
+ - - ">="
541
+ - !ruby/object:Gem::Version
542
+ version: '0'
543
+ type: :development
544
+ prerelease: false
545
+ version_requirements: !ruby/object:Gem::Requirement
546
+ requirements:
547
+ - - ">="
548
+ - !ruby/object:Gem::Version
549
+ version: '0'
466
550
  - !ruby/object:Gem::Dependency
467
551
  name: sqlite3
468
552
  requirement: !ruby/object:Gem::Requirement
@@ -509,6 +593,8 @@ files:
509
593
  - app/apps/plugins/front_cache/views/admin/settings.html.erb
510
594
  - app/apps/plugins/visibility_post/assets/js/form.js
511
595
  - app/apps/plugins/visibility_post/config/config.json
596
+ - app/apps/plugins/visibility_post/config/routes_front.txt
597
+ - app/apps/plugins/visibility_post/front_controller.rb
512
598
  - app/apps/plugins/visibility_post/visibility_post_helper.rb
513
599
  - app/apps/themes/camaleon_first/assets/css/magnific-ver.css
514
600
  - app/apps/themes/camaleon_first/assets/css/main.css
@@ -787,6 +873,17 @@ files:
787
873
  - app/controllers/camaleon_cms/frontend_controller.rb
788
874
  - app/controllers/concerns/camaleon_cms/admin/custom_fields_concern.rb
789
875
  - app/controllers/concerns/camaleon_cms/frontend_concern.rb
876
+ - app/controllers/concerns/camaleon_cms/frontend_visited_state_concern.rb
877
+ - app/controllers/concerns/camaleon_cms/hook_lifecycle_concern.rb
878
+ - app/controllers/concerns/camaleon_cms/request_context_concern.rb
879
+ - app/controllers/concerns/camaleon_cms/runtime_admin_menu_concern.rb
880
+ - app/controllers/concerns/camaleon_cms/runtime_captcha_image_concern.rb
881
+ - app/controllers/concerns/camaleon_cms/runtime_html_content_concern.rb
882
+ - app/controllers/concerns/camaleon_cms/runtime_shortcode_theme_concern.rb
883
+ - app/controllers/concerns/camaleon_cms/runtime_state_concern.rb
884
+ - app/controllers/concerns/camaleon_cms/runtime_uploader_concern.rb
885
+ - app/controllers/concerns/camaleon_cms/session_captcha_runtime_concern.rb
886
+ - app/controllers/concerns/camaleon_cms/session_runtime_concern.rb
790
887
  - app/decorators/camaleon_cms/application_decorator.rb
791
888
  - app/decorators/camaleon_cms/category_decorator.rb
792
889
  - app/decorators/camaleon_cms/custom_field_decorator.rb
@@ -859,6 +956,7 @@ files:
859
956
  - app/models/camaleon_record.rb
860
957
  - app/models/concerns/camaleon_cms/categories_tags_for_posts.rb
861
958
  - app/models/concerns/camaleon_cms/common_relationships.rb
959
+ - app/models/concerns/camaleon_cms/content_shortcode_gate.rb
862
960
  - app/models/concerns/camaleon_cms/custom_fields_read.rb
863
961
  - app/models/concerns/camaleon_cms/metas.rb
864
962
  - app/models/concerns/camaleon_cms/normalize_attrs.rb
@@ -916,8 +1014,10 @@ files:
916
1014
  - app/views/camaleon_cms/admin/posts/index.html.erb
917
1015
  - app/views/camaleon_cms/admin/posts/show.html.erb
918
1016
  - app/views/camaleon_cms/admin/search.html.erb
1017
+ - app/views/camaleon_cms/admin/sessions/back_to_parent.html.erb
919
1018
  - app/views/camaleon_cms/admin/sessions/forgot.html.erb
920
1019
  - app/views/camaleon_cms/admin/sessions/login.html.erb
1020
+ - app/views/camaleon_cms/admin/sessions/logout_confirm.html.erb
921
1021
  - app/views/camaleon_cms/admin/sessions/register.html.erb
922
1022
  - app/views/camaleon_cms/admin/settings/_configuration_settings.html.erb
923
1023
  - app/views/camaleon_cms/admin/settings/_email_settings.html.erb
@@ -1141,7 +1241,21 @@ files:
1141
1241
  - db/migrate/20180124132318_create_media.rb
1142
1242
  - db/migrate/20180704211100_adjust_field_length.rb
1143
1243
  - lib/camaleon_cms.rb
1244
+ - lib/camaleon_cms/assets_precompile.rb
1245
+ - lib/camaleon_cms/captcha_image_generation.rb
1246
+ - lib/camaleon_cms/content_security.rb
1144
1247
  - lib/camaleon_cms/engine.rb
1248
+ - lib/camaleon_cms/media_security_headers.rb
1249
+ - lib/camaleon_cms/setup_token.rb
1250
+ - lib/camaleon_cms/shortcode_registry.rb
1251
+ - lib/camaleon_cms/svg_content_checker.rb
1252
+ - lib/camaleon_cms/task_reporter.rb
1253
+ - lib/camaleon_cms/unsafe_markup.rb
1254
+ - lib/camaleon_cms/uploader_content_security.rb
1255
+ - lib/camaleon_cms/uploader_image_processing.rb
1256
+ - lib/camaleon_cms/uploader_path_security.rb
1257
+ - lib/camaleon_cms/uploader_pipeline.rb
1258
+ - lib/camaleon_cms/uploader_support.rb
1145
1259
  - lib/camaleon_cms/version.rb
1146
1260
  - lib/captcha/bumpyroad.ttf
1147
1261
  - lib/captcha/captcha_0.jpg
@@ -1188,7 +1302,14 @@ files:
1188
1302
  - lib/plugin_routes.rb
1189
1303
  - lib/tasks/camaleon_cms/camaleon_tasks.rake
1190
1304
  - lib/tasks/camaleon_cms/rspec_test.rake
1305
+ - lib/tasks/cross_site_field_groups.rake
1191
1306
  - lib/tasks/custom_fields_roles.rake
1307
+ - lib/tasks/orphaned_comments.rake
1308
+ - lib/tasks/private_upload_acls.rake
1309
+ - lib/tasks/site_custom_field_groups.rake
1310
+ - lib/tasks/unsafe_stored_content.rake
1311
+ - lib/tasks/unsafe_stored_uploads.rake
1312
+ - lib/tasks/user_field_groups.rake
1192
1313
  homepage: https://camaleon.website
1193
1314
  licenses:
1194
1315
  - MIT
@@ -1210,7 +1331,7 @@ requirements:
1210
1331
  - rails >= 6.1
1211
1332
  - ruby >= 3.0
1212
1333
  - imagemagick
1213
- rubygems_version: 3.6.9
1334
+ rubygems_version: 4.0.17
1214
1335
  specification_version: 4
1215
1336
  summary: Camaleon is a CMS for Ruby on Rails as an alternative to Wordpress.
1216
1337
  test_files: []