camaleon_cms 2.9.1 → 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.
- checksums.yaml +4 -4
- data/README.md +29 -9
- data/Rakefile +3 -16
- data/app/apps/plugins/attack/admin_controller.rb +9 -6
- data/app/apps/plugins/attack/attack_helper.rb +35 -25
- data/app/apps/plugins/authoring_post/authoring_post_helper.rb +23 -16
- data/app/apps/plugins/front_cache/admin_controller.rb +16 -10
- data/app/apps/plugins/front_cache/config/initializer.rb +1 -1
- data/app/apps/plugins/front_cache/front_cache_helper.rb +57 -24
- data/app/apps/plugins/visibility_post/config/config.json +1 -0
- data/app/apps/plugins/visibility_post/config/routes_front.txt +1 -0
- data/app/apps/plugins/visibility_post/front_controller.rb +23 -0
- data/app/apps/plugins/visibility_post/visibility_post_helper.rb +141 -70
- data/app/apps/themes/camaleon_first/main_helper.rb +18 -12
- data/app/apps/themes/default/assets/css/style.css.scss +24 -0
- data/app/apps/themes/default/assets/genericons/genericons.css +3 -8
- data/app/apps/themes/default/default_helper.rb +2 -1
- data/app/apps/themes/default/views/category.html.erb +1 -1
- data/app/apps/themes/default/views/layouts/index.html.erb +3 -1
- data/app/apps/themes/default/views/post_tag.html.erb +1 -1
- data/app/apps/themes/default/views/post_type.html.erb +1 -1
- data/app/apps/themes/default/views/search.html.erb +1 -1
- data/app/apps/themes/new/assets/css/styles.css.scss +24 -0
- data/app/apps/themes/new/custom_helper.rb +5 -9
- data/app/apps/themes/new/views/category.html.erb +1 -1
- data/app/apps/themes/new/views/layouts/_header.html.erb +3 -1
- data/app/apps/themes/new/views/post_tag.html.erb +1 -1
- data/app/apps/themes/new/views/post_type.html.erb +1 -1
- data/app/apps/themes/new/views/search.html.erb +1 -1
- data/app/assets/javascripts/camaleon_cms/admin/_data.js +16 -0
- data/app/assets/javascripts/camaleon_cms/admin/_post.js +5 -2
- data/app/assets/javascripts/camaleon_cms/admin/nav_menu.js +14 -3
- data/app/assets/javascripts/camaleon_cms/admin/uploader/_media_manager.js +5 -0
- data/app/controllers/camaleon_cms/admin/appearances/nav_menus_controller.rb +29 -10
- data/app/controllers/camaleon_cms/admin/appearances/themes_controller.rb +8 -5
- data/app/controllers/camaleon_cms/admin/appearances/widgets/assign_controller.rb +9 -2
- data/app/controllers/camaleon_cms/admin/appearances/widgets/main_controller.rb +4 -3
- data/app/controllers/camaleon_cms/admin/appearances/widgets/sidebar_controller.rb +2 -2
- data/app/controllers/camaleon_cms/admin/categories_controller.rb +27 -8
- data/app/controllers/camaleon_cms/admin/comments_controller.rb +0 -1
- data/app/controllers/camaleon_cms/admin/installers_controller.rb +32 -1
- data/app/controllers/camaleon_cms/admin/media_controller.rb +84 -19
- data/app/controllers/camaleon_cms/admin/plugins_controller.rb +1 -3
- data/app/controllers/camaleon_cms/admin/post_tags_controller.rb +15 -6
- data/app/controllers/camaleon_cms/admin/posts/drafts_controller.rb +40 -11
- data/app/controllers/camaleon_cms/admin/posts_controller.rb +58 -22
- data/app/controllers/camaleon_cms/admin/sessions_controller.rb +238 -45
- data/app/controllers/camaleon_cms/admin/settings/custom_fields_controller.rb +103 -18
- data/app/controllers/camaleon_cms/admin/settings/post_types_controller.rb +17 -5
- data/app/controllers/camaleon_cms/admin/settings/sites_controller.rb +33 -7
- data/app/controllers/camaleon_cms/admin/settings_controller.rb +11 -6
- data/app/controllers/camaleon_cms/admin/user_roles_controller.rb +9 -7
- data/app/controllers/camaleon_cms/admin/users_controller.rb +89 -18
- data/app/controllers/camaleon_cms/admin_controller.rb +144 -26
- data/app/controllers/camaleon_cms/apps/plugins_admin_controller.rb +5 -0
- data/app/controllers/camaleon_cms/camaleon_controller.rb +37 -92
- data/app/controllers/camaleon_cms/frontend_controller.rb +140 -70
- data/app/controllers/concerns/camaleon_cms/admin/custom_fields_concern.rb +44 -0
- data/app/controllers/concerns/camaleon_cms/frontend_concern.rb +46 -17
- data/app/controllers/concerns/camaleon_cms/frontend_visited_state_concern.rb +72 -0
- data/app/controllers/concerns/camaleon_cms/hook_lifecycle_concern.rb +104 -0
- data/app/controllers/concerns/camaleon_cms/request_context_concern.rb +113 -0
- data/app/controllers/concerns/camaleon_cms/runtime_admin_menu_concern.rb +237 -0
- data/app/controllers/concerns/camaleon_cms/runtime_captcha_image_concern.rb +13 -0
- data/app/controllers/concerns/camaleon_cms/runtime_html_content_concern.rb +143 -0
- data/app/controllers/concerns/camaleon_cms/runtime_shortcode_theme_concern.rb +246 -0
- data/app/controllers/concerns/camaleon_cms/runtime_state_concern.rb +13 -0
- data/app/controllers/concerns/camaleon_cms/runtime_uploader_concern.rb +54 -0
- data/app/controllers/concerns/camaleon_cms/session_captcha_runtime_concern.rb +19 -0
- data/app/controllers/concerns/camaleon_cms/session_runtime_concern.rb +40 -0
- data/app/decorators/camaleon_cms/application_decorator.rb +11 -8
- data/app/decorators/camaleon_cms/category_decorator.rb +3 -2
- data/app/decorators/camaleon_cms/post_comment_decorator.rb +13 -5
- data/app/decorators/camaleon_cms/post_decorator.rb +67 -51
- data/app/decorators/camaleon_cms/post_type_decorator.rb +1 -1
- data/app/decorators/camaleon_cms/site_decorator.rb +29 -23
- data/app/decorators/camaleon_cms/term_taxonomy_decorator.rb +21 -10
- data/app/decorators/camaleon_cms/theme_decorator.rb +3 -3
- data/app/decorators/camaleon_cms/user_decorator.rb +26 -5
- data/app/helpers/camaleon_cms/admin/application_helper.rb +21 -24
- data/app/helpers/camaleon_cms/admin/breadcrumb_helper.rb +1 -1
- data/app/helpers/camaleon_cms/admin/custom_fields_helper.rb +24 -23
- data/app/helpers/camaleon_cms/admin/menus_helper.rb +249 -139
- data/app/helpers/camaleon_cms/admin/post_type_helper.rb +51 -31
- data/app/helpers/camaleon_cms/camaleon_helper.rb +31 -39
- data/app/helpers/camaleon_cms/captcha_helper.rb +73 -53
- data/app/helpers/camaleon_cms/comment_helper.rb +87 -41
- data/app/helpers/camaleon_cms/content_helper.rb +13 -6
- data/app/helpers/camaleon_cms/email_helper.rb +7 -3
- data/app/helpers/camaleon_cms/frontend/content_select_helper.rb +34 -20
- data/app/helpers/camaleon_cms/frontend/nav_menu_helper.rb +144 -67
- data/app/helpers/camaleon_cms/frontend/seo_helper.rb +39 -28
- data/app/helpers/camaleon_cms/frontend/site_helper.rb +32 -12
- data/app/helpers/camaleon_cms/hooks_helper.rb +14 -4
- data/app/helpers/camaleon_cms/html_helper.rb +73 -38
- data/app/helpers/camaleon_cms/plugins_helper.rb +23 -24
- data/app/helpers/camaleon_cms/session_helper.rb +195 -39
- data/app/helpers/camaleon_cms/short_code_helper.rb +181 -85
- data/app/helpers/camaleon_cms/site_helper.rb +43 -9
- data/app/helpers/camaleon_cms/theme_helper.rb +35 -19
- data/app/helpers/camaleon_cms/uploader_helper.rb +19 -422
- data/app/helpers/camaleon_cms/user_roles_helper.rb +5 -4
- data/app/mailers/camaleon_cms/html_mailer.rb +4 -4
- data/app/models/camaleon_cms/ability.rb +65 -105
- data/app/models/camaleon_cms/category.rb +7 -5
- data/app/models/camaleon_cms/custom_field.rb +29 -6
- data/app/models/camaleon_cms/custom_field_group.rb +75 -21
- data/app/models/camaleon_cms/custom_fields_relationship.rb +133 -2
- data/app/models/camaleon_cms/media.rb +8 -2
- data/app/models/camaleon_cms/meta.rb +2 -2
- data/app/models/camaleon_cms/nav_menu.rb +6 -2
- data/app/models/camaleon_cms/nav_menu_item.rb +18 -8
- data/app/models/camaleon_cms/plugin.rb +4 -3
- data/app/models/camaleon_cms/post.rb +113 -27
- data/app/models/camaleon_cms/post_comment.rb +10 -6
- data/app/models/camaleon_cms/post_default.rb +42 -12
- data/app/models/camaleon_cms/post_relationship.rb +6 -4
- data/app/models/camaleon_cms/post_tag.rb +4 -3
- data/app/models/camaleon_cms/post_type.rb +22 -12
- data/app/models/camaleon_cms/site.rb +40 -28
- data/app/models/camaleon_cms/term_relationship.rb +10 -7
- data/app/models/camaleon_cms/term_taxonomy.rb +51 -5
- data/app/models/camaleon_cms/theme.rb +3 -3
- data/app/models/camaleon_cms/user.rb +17 -5
- data/app/models/camaleon_cms/user_role.rb +77 -4
- data/app/models/camaleon_cms/widget/assigned.rb +11 -6
- data/app/models/camaleon_cms/widget/main.rb +13 -7
- data/app/models/camaleon_cms/widget/sidebar.rb +3 -6
- data/app/models/camaleon_record.rb +123 -5
- data/app/models/concerns/camaleon_cms/categories_tags_for_posts.rb +20 -38
- data/app/models/concerns/camaleon_cms/common_relationships.rb +18 -5
- data/app/models/concerns/camaleon_cms/custom_fields_read.rb +115 -58
- data/app/models/concerns/camaleon_cms/metas.rb +32 -7
- data/app/models/concerns/camaleon_cms/normalize_attrs.rb +2 -8
- data/app/models/concerns/camaleon_cms/site_default_settings.rb +120 -16
- data/app/models/concerns/camaleon_cms/user_methods.rb +44 -19
- data/app/models/current_request.rb +23 -0
- data/app/uploaders/camaleon_cms_aws_uploader.rb +39 -11
- data/app/uploaders/camaleon_cms_local_uploader.rb +76 -19
- data/app/uploaders/camaleon_cms_uploader.rb +23 -10
- data/app/validators/camaleon_cms/post_uniq_validator.rb +21 -8
- data/app/validators/camaleon_cms/uniq_validator.rb +1 -1
- data/app/validators/camaleon_cms/user_url_validator.rb +215 -36
- data/app/views/camaleon_cms/admin/appearances/nav_menus/_left_menu_items.html.erb +2 -2
- data/app/views/camaleon_cms/admin/appearances/widgets/main/form.html.erb +1 -1
- data/app/views/camaleon_cms/admin/categories/index.html.erb +3 -3
- data/app/views/camaleon_cms/admin/comments/index.html.erb +2 -2
- data/app/views/camaleon_cms/admin/comments/list.html.erb +1 -1
- data/app/views/camaleon_cms/admin/installers/form.html.erb +5 -0
- data/app/views/camaleon_cms/admin/installers/welcome.html.erb +30 -6
- data/app/views/camaleon_cms/admin/plugins/_plugins_list.html.erb +2 -2
- data/app/views/camaleon_cms/admin/post_tags/index.html.erb +3 -3
- data/app/views/camaleon_cms/admin/posts/_sidebar.html.erb +2 -2
- data/app/views/camaleon_cms/admin/posts/form.html.erb +3 -2
- data/app/views/camaleon_cms/admin/posts/index.html.erb +6 -6
- data/app/views/camaleon_cms/admin/search.html.erb +1 -1
- data/app/views/camaleon_cms/admin/sessions/back_to_parent.html.erb +39 -0
- data/app/views/camaleon_cms/admin/sessions/logout_confirm.html.erb +27 -0
- data/app/views/camaleon_cms/admin/settings/_email_settings.html.erb +4 -1
- data/app/views/camaleon_cms/admin/settings/custom_fields/_get_items.html.erb +1 -1
- data/app/views/camaleon_cms/admin/settings/custom_fields/_render.html.erb +42 -6
- data/app/views/camaleon_cms/admin/settings/custom_fields/_render_category_simple.html.erb +1 -1
- data/app/views/camaleon_cms/admin/settings/custom_fields/fields/_select_eval.html.erb +1 -1
- data/app/views/camaleon_cms/admin/settings/custom_fields/form.html.erb +5 -4
- data/app/views/camaleon_cms/admin/settings/custom_fields/index.html.erb +1 -1
- data/app/views/camaleon_cms/admin/settings/post_types/index.html.erb +1 -1
- data/app/views/camaleon_cms/admin/settings/shortcodes.html.erb +4 -4
- data/app/views/camaleon_cms/admin/settings/site.html.erb +1 -1
- data/app/views/camaleon_cms/admin/settings/sites/form.html.erb +1 -1
- data/app/views/camaleon_cms/admin/settings/sites/index.html.erb +1 -1
- data/app/views/camaleon_cms/admin/user_roles/form.html.erb +102 -7
- data/app/views/camaleon_cms/admin/user_roles/index.html.erb +1 -1
- data/app/views/camaleon_cms/admin/users/form.html.erb +8 -4
- data/app/views/camaleon_cms/admin/users/index.html.erb +2 -2
- data/app/views/camaleon_cms/default_theme/partials/_render_custom_field.html.erb +8 -2
- data/app/views/camaleon_cms/default_theme/sitemap.html.erb +1 -1
- data/app/views/camaleon_cms/default_theme/sitemap.xml.builder +1 -1
- data/app/views/layouts/camaleon_cms/admin/_flash_messages.html.erb +2 -2
- data/app/views/layouts/camaleon_cms/admin/_header.html.erb +1 -1
- data/config/initializers/action_view.rb +42 -5
- data/config/initializers/active_record_extension.rb +13 -27
- data/config/initializers/assets.rb +52 -23
- data/config/initializers/custom_initializers.rb +3 -3
- data/config/locales/camaleon_cms/admin/ar.yml +7 -2
- data/config/locales/camaleon_cms/admin/de.yml +7 -2
- data/config/locales/camaleon_cms/admin/en.yml +39 -2
- data/config/locales/camaleon_cms/admin/es.yml +7 -2
- data/config/locales/camaleon_cms/admin/fr.yml +7 -2
- data/config/locales/camaleon_cms/admin/it.yml +7 -2
- data/config/locales/camaleon_cms/admin/nl.yml +8 -2
- data/config/locales/camaleon_cms/admin/pt-BR.yml +7 -2
- data/config/locales/camaleon_cms/admin/pt.yml +7 -2
- data/config/locales/camaleon_cms/admin/ru.yml +7 -2
- data/config/locales/camaleon_cms/admin/uk.yml +7 -2
- data/config/locales/camaleon_cms/admin/zh-CH.yml +7 -2
- data/config/routes/admin.rb +33 -15
- data/config/routes/frontend.rb +21 -15
- data/db/migrate/20150611161134_post_table_into_utf8.rb +14 -14
- data/db/migrate/20150926095310_rename_column_posts.rb +3 -3
- data/db/migrate/20151212095328_add_confirm_token_to_users.rb +3 -3
- data/db/migrate/20160504155652_add_feature_to_posts.rb +1 -1
- data/db/migrate/20160504155653_move_first_name_of_users.rb +2 -2
- data/db/migrate/20160609121449_add_group_to_custom_field_values.rb +1 -1
- data/db/migrate/20161215202255_drop_user_relationship_table.rb +1 -1
- data/db/migrate/20180124132318_create_media.rb +1 -1
- data/db/migrate/20180704211100_adjust_field_length.rb +1 -1
- data/lib/camaleon_cms/assets_precompile.rb +86 -0
- data/lib/camaleon_cms/captcha_image_generation.rb +65 -0
- data/lib/camaleon_cms/content_security.rb +158 -0
- data/lib/camaleon_cms/engine.rb +47 -9
- data/lib/camaleon_cms/media_security_headers.rb +27 -0
- data/lib/camaleon_cms/setup_token.rb +69 -0
- data/lib/camaleon_cms/svg_content_checker.rb +123 -0
- data/lib/camaleon_cms/task_reporter.rb +14 -0
- data/lib/camaleon_cms/unsafe_markup.rb +207 -0
- data/lib/camaleon_cms/uploader_content_security.rb +248 -0
- data/lib/camaleon_cms/uploader_image_processing.rb +166 -0
- data/lib/camaleon_cms/uploader_path_security.rb +146 -0
- data/lib/camaleon_cms/uploader_pipeline.rb +350 -0
- data/lib/camaleon_cms/uploader_support.rb +86 -0
- data/lib/camaleon_cms/version.rb +1 -1
- data/lib/camaleon_cms.rb +11 -0
- data/lib/ext/hash.rb +28 -2
- data/lib/ext/string.rb +13 -8
- data/lib/ext/translator.rb +10 -2
- data/lib/generators/camaleon_cms/gem_plugin_template/app/controllers/plugins/my_plugin/admin_controller.rb +5 -1
- data/lib/generators/camaleon_cms/install_generator.rb +7 -4
- data/lib/generators/camaleon_cms/install_template/plugin_routes.rb +5 -12
- data/lib/generators/camaleon_cms/theme_generator.rb +5 -10
- data/lib/generators/camaleon_cms/theme_template/views/layouts/index.html.erb +3 -1
- data/lib/plugin_routes.rb +412 -368
- data/lib/tasks/camaleon_cms/camaleon_tasks.rake +2 -1
- data/lib/tasks/cross_site_field_groups.rake +74 -0
- data/lib/tasks/custom_fields_roles.rake +58 -0
- data/lib/tasks/orphaned_comments.rake +42 -0
- data/lib/tasks/private_upload_acls.rake +31 -0
- data/lib/tasks/site_custom_field_groups.rake +36 -0
- data/lib/tasks/unsafe_stored_content.rake +42 -0
- data/lib/tasks/unsafe_stored_uploads.rake +70 -0
- data/lib/tasks/user_field_groups.rake +27 -0
- metadata +185 -7
|
@@ -12,9 +12,16 @@ module CamaleonCms
|
|
|
12
12
|
can :read, :all
|
|
13
13
|
else
|
|
14
14
|
# conditions:
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
# Fetch the role record fresh from the database for the current site to
|
|
16
|
+
# ensure up-to-date role meta (avoid stale cached role objects during
|
|
17
|
+
# tests or runtime meta updates).
|
|
18
|
+
current_user_role = if current_site.present?
|
|
19
|
+
current_site.user_roles.where(slug: user.role).first
|
|
20
|
+
else
|
|
21
|
+
user.get_role(current_site)
|
|
22
|
+
end || current_site.user_roles.new
|
|
23
|
+
@roles_manager = current_user_role.get_meta("_manager_#{current_site.id}", {}) || {}
|
|
24
|
+
@roles_post_type = current_user_role.get_meta("_post_type_#{current_site.id}", {}) || {}
|
|
18
25
|
|
|
19
26
|
ids_publish = @roles_post_type[:publish] || []
|
|
20
27
|
ids_edit = @roles_post_type[:edit] || []
|
|
@@ -24,83 +31,49 @@ module CamaleonCms
|
|
|
24
31
|
ids_delete_other = @roles_post_type[:delete_other] || []
|
|
25
32
|
ids_delete_publish = @roles_post_type[:delete_publish] || []
|
|
26
33
|
|
|
27
|
-
|
|
34
|
+
safe_can :posts, CamaleonCms::PostType do |pt|
|
|
28
35
|
(ids_edit + ids_edit_other + ids_edit_publish).to_i.include?(pt.id)
|
|
29
|
-
rescue StandardError
|
|
30
|
-
false
|
|
31
36
|
end
|
|
32
37
|
|
|
33
|
-
|
|
38
|
+
safe_can :create_post, CamaleonCms::PostType do |pt|
|
|
34
39
|
ids_edit.to_i.include?(pt.id)
|
|
35
|
-
rescue StandardError
|
|
36
|
-
false
|
|
37
40
|
end
|
|
38
|
-
|
|
41
|
+
safe_can :publish_post, CamaleonCms::PostType do |pt|
|
|
39
42
|
ids_publish.to_i.include?(pt.id)
|
|
40
|
-
rescue StandardError
|
|
41
|
-
false
|
|
42
43
|
end
|
|
43
|
-
|
|
44
|
+
safe_can :edit_other, CamaleonCms::PostType do |pt|
|
|
44
45
|
ids_edit_other.to_i.include?(pt.id)
|
|
45
|
-
rescue StandardError
|
|
46
|
-
false
|
|
47
46
|
end
|
|
48
|
-
|
|
47
|
+
safe_can :edit_publish, CamaleonCms::PostType do |pt|
|
|
49
48
|
ids_edit_publish.to_i.include?(pt.id)
|
|
50
|
-
rescue StandardError
|
|
51
|
-
false
|
|
52
49
|
end
|
|
53
50
|
|
|
54
|
-
|
|
51
|
+
safe_can :categories, CamaleonCms::PostType do |pt|
|
|
55
52
|
@roles_post_type[:manage_categories].to_i.include?(pt.id)
|
|
56
|
-
rescue StandardError
|
|
57
|
-
false
|
|
58
53
|
end
|
|
59
|
-
|
|
54
|
+
safe_can :post_tags, CamaleonCms::PostType do |pt|
|
|
60
55
|
@roles_post_type[:manage_tags].to_i.include?(pt.id)
|
|
61
|
-
|
|
62
|
-
|
|
56
|
+
end
|
|
57
|
+
safe_can :post_content_unfiltered_html, CamaleonCms::PostType do |pt|
|
|
58
|
+
ids_unfiltered = @roles_post_type[:post_content_unfiltered_html] || []
|
|
59
|
+
ids_unfiltered.to_i.include?(pt.id)
|
|
63
60
|
end
|
|
64
61
|
|
|
65
|
-
|
|
62
|
+
safe_can :update, CamaleonCms::Post do |post|
|
|
66
63
|
pt_id = post.post_type.id
|
|
67
64
|
r = false
|
|
68
|
-
r ||=
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
false
|
|
72
|
-
end
|
|
73
|
-
r ||= begin
|
|
74
|
-
ids_edit_publish.to_i.include?(pt_id) && post.published?
|
|
75
|
-
rescue StandardError
|
|
76
|
-
false
|
|
77
|
-
end
|
|
78
|
-
r ||= begin
|
|
79
|
-
ids_edit_other.to_i.include?(pt_id) && post.user_id != user.id
|
|
80
|
-
rescue StandardError
|
|
81
|
-
false
|
|
82
|
-
end
|
|
65
|
+
r ||= ids_edit.to_i.include?(pt_id) && post.user_id == user.id
|
|
66
|
+
r ||= ids_edit_publish.to_i.include?(pt_id) && post.published?
|
|
67
|
+
r ||= ids_edit_other.to_i.include?(pt_id) && post.user_id != user.id
|
|
83
68
|
r
|
|
84
69
|
end
|
|
85
70
|
|
|
86
|
-
|
|
71
|
+
safe_can :destroy, CamaleonCms::Post do |post|
|
|
87
72
|
pt_id = post.post_type.id
|
|
88
73
|
r = false
|
|
89
|
-
r ||=
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
false
|
|
93
|
-
end
|
|
94
|
-
r ||= begin
|
|
95
|
-
ids_delete_publish.to_i.include?(pt_id) && post.published?
|
|
96
|
-
rescue StandardError
|
|
97
|
-
false
|
|
98
|
-
end
|
|
99
|
-
r ||= begin
|
|
100
|
-
ids_delete_other.to_i.include?(pt_id) && post.user_id != user.id
|
|
101
|
-
rescue StandardError
|
|
102
|
-
false
|
|
103
|
-
end
|
|
74
|
+
r ||= ids_delete.to_i.include?(pt_id) && post.user_id == user.id
|
|
75
|
+
r ||= ids_delete_publish.to_i.include?(pt_id) && post.published?
|
|
76
|
+
r ||= ids_delete_other.to_i.include?(pt_id) && post.user_id != user.id
|
|
104
77
|
r
|
|
105
78
|
end
|
|
106
79
|
|
|
@@ -109,60 +82,13 @@ module CamaleonCms
|
|
|
109
82
|
@roles_post_type.each do |k, v|
|
|
110
83
|
next if %w[edit edit_other edit_publish publish manage_categories].include?(k.to_s)
|
|
111
84
|
|
|
112
|
-
|
|
85
|
+
safe_can k.to_sym, CamaleonCms::PostType do |pt|
|
|
113
86
|
v.include?(pt.id.to_s)
|
|
114
|
-
rescue StandardError
|
|
115
|
-
false
|
|
116
87
|
end
|
|
117
88
|
end
|
|
118
89
|
|
|
119
90
|
# others
|
|
120
|
-
|
|
121
|
-
can :manage, :media if @roles_manager[:media]
|
|
122
|
-
rescue StandardError
|
|
123
|
-
false
|
|
124
|
-
end
|
|
125
|
-
begin
|
|
126
|
-
can :manage, :comments if @roles_manager[:comments]
|
|
127
|
-
rescue StandardError
|
|
128
|
-
false
|
|
129
|
-
end
|
|
130
|
-
# can :manage, :forms if @roles_manager[:forms] rescue false
|
|
131
|
-
begin
|
|
132
|
-
can :manage, :themes if @roles_manager[:themes]
|
|
133
|
-
rescue StandardError
|
|
134
|
-
false
|
|
135
|
-
end
|
|
136
|
-
begin
|
|
137
|
-
can :manage, :widgets if @roles_manager[:widgets]
|
|
138
|
-
rescue StandardError
|
|
139
|
-
false
|
|
140
|
-
end
|
|
141
|
-
begin
|
|
142
|
-
can :manage, :nav_menu if @roles_manager[:nav_menu]
|
|
143
|
-
rescue StandardError
|
|
144
|
-
false
|
|
145
|
-
end
|
|
146
|
-
begin
|
|
147
|
-
can :manage, :plugins if @roles_manager[:plugins]
|
|
148
|
-
rescue StandardError
|
|
149
|
-
false
|
|
150
|
-
end
|
|
151
|
-
begin
|
|
152
|
-
can :manage, :users if @roles_manager[:users]
|
|
153
|
-
rescue StandardError
|
|
154
|
-
false
|
|
155
|
-
end
|
|
156
|
-
begin
|
|
157
|
-
can :manage, :settings if @roles_manager[:settings]
|
|
158
|
-
rescue StandardError
|
|
159
|
-
false
|
|
160
|
-
end
|
|
161
|
-
@roles_manager.try(:each) do |rol_manage_key, val_role|
|
|
162
|
-
can :manage, rol_manage_key.to_sym if val_role.to_s.cama_true?
|
|
163
|
-
rescue StandardError
|
|
164
|
-
false
|
|
165
|
-
end
|
|
91
|
+
define_manage_rules
|
|
166
92
|
end
|
|
167
93
|
cannot :impersonate, CamaleonCms::User do |u|
|
|
168
94
|
u.id == user.id
|
|
@@ -182,5 +108,39 @@ module CamaleonCms
|
|
|
182
108
|
def cannot?(*args)
|
|
183
109
|
!can?(*args)
|
|
184
110
|
end
|
|
111
|
+
|
|
112
|
+
private
|
|
113
|
+
|
|
114
|
+
# Wraps a can rule with block-level exception handling.
|
|
115
|
+
# Blocks are evaluated later during authorization checks (can? calls), so exceptions
|
|
116
|
+
# from accessing post properties, role metadata, etc. must be caught here to fail closed.
|
|
117
|
+
# Non-block calls (e.g., can :manage, :symbol) do not need wrapping; can itself is safe.
|
|
118
|
+
def safe_can(action, subject, &block)
|
|
119
|
+
if block_given?
|
|
120
|
+
can(action, subject) do |resource|
|
|
121
|
+
safely_false { yield(resource) }
|
|
122
|
+
end
|
|
123
|
+
else
|
|
124
|
+
# No block: can(action, subject) does not evaluate user logic; safe to call directly.
|
|
125
|
+
can(action, subject)
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Fails closed for permission checks when role/post metadata is malformed.
|
|
130
|
+
# Used by safe_can to guard block evaluation during authorization checks.
|
|
131
|
+
def safely_false
|
|
132
|
+
yield
|
|
133
|
+
rescue StandardError
|
|
134
|
+
false
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def define_manage_rules
|
|
138
|
+
%i[media comments themes widgets nav_menu plugins users settings custom_fields select_eval].each do |resource|
|
|
139
|
+
safe_can :manage, resource if @roles_manager[resource]
|
|
140
|
+
end
|
|
141
|
+
@roles_manager.each do |rol_manage_key, val_role|
|
|
142
|
+
safe_can :manage, rol_manage_key.to_sym if val_role.to_s.cama_true?
|
|
143
|
+
end
|
|
144
|
+
end
|
|
185
145
|
end
|
|
186
146
|
end
|
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
module CamaleonCms
|
|
2
2
|
class Category < CamaleonCms::TermTaxonomy
|
|
3
|
+
normalize_attrs(:description)
|
|
4
|
+
|
|
3
5
|
alias_attribute :site_id, :term_group
|
|
4
6
|
alias_attribute :post_type_id, :status
|
|
5
7
|
|
|
6
|
-
default_scope { where(taxonomy: :category) }
|
|
7
8
|
scope :no_empty, -> { where('count > 0') } # return all categories that contains at least one post
|
|
8
9
|
scope :empty, -> { where(count: [0, nil]) } # return all categories that does not contain any post
|
|
9
10
|
# scope :parents, -> { where("term_taxonomy.parent_id IS NULL") }
|
|
10
11
|
|
|
11
12
|
has_many :posts, foreign_key: :objectid, through: :term_relationships, source: :object
|
|
12
|
-
has_many :children, class_name: 'CamaleonCms::Category', foreign_key: :parent_id, dependent: :destroy
|
|
13
|
-
|
|
13
|
+
has_many :children, class_name: 'CamaleonCms::Category', foreign_key: :parent_id, dependent: :destroy,
|
|
14
|
+
inverse_of: :parent
|
|
15
|
+
belongs_to :parent, class_name: 'CamaleonCms::Category', optional: true
|
|
14
16
|
belongs_to :post_type_parent, class_name: 'CamaleonCms::PostType', foreign_key: :parent_id,
|
|
15
|
-
inverse_of: :categories,
|
|
16
|
-
belongs_to :site,
|
|
17
|
+
inverse_of: :categories, optional: true
|
|
18
|
+
belongs_to :site, optional: true
|
|
17
19
|
|
|
18
20
|
before_save :set_site
|
|
19
21
|
before_destroy :set_posts_in_default_category
|
|
@@ -14,17 +14,27 @@ module CamaleonCms
|
|
|
14
14
|
# status: nil -> visible on list group fields
|
|
15
15
|
# attr_accessible :object_class, :objectid, :description, :parent_id, :count, :name, :slug,
|
|
16
16
|
# :field_order, :status, :is_repeat
|
|
17
|
+
|
|
18
|
+
# Scope metas by object_class. Meta rows are keyed by (objectid, object_class), so a custom
|
|
19
|
+
# field whose numeric id collides with another model's id (e.g. a Post) would otherwise read
|
|
20
|
+
# the wrong "_default" meta and lose its field_key. See regression: TinyMCE editor field
|
|
21
|
+
# rendered as a plain text_box on Theme settings.
|
|
22
|
+
# rubocop:disable Rails/InverseOf
|
|
17
23
|
has_many :metas, -> { where(object_class: 'CustomField') }, foreign_key: :objectid, dependent: :destroy
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
belongs_to :
|
|
24
|
+
# rubocop:enable Rails/InverseOf
|
|
25
|
+
has_many :values, class_name: 'CamaleonCms::CustomFieldsRelationship', dependent: :destroy
|
|
26
|
+
|
|
27
|
+
belongs_to :custom_field_group, class_name: 'CamaleonCms::CustomFieldGroup', foreign_key: :parent_id,
|
|
28
|
+
optional: true, inverse_of: :fields
|
|
29
|
+
belongs_to :parent, class_name: 'CamaleonCms::CustomField', optional: true
|
|
30
|
+
belongs_to :owner, polymorphic: true, foreign_key: :objectid, foreign_type: :object_class, optional: true
|
|
22
31
|
|
|
23
32
|
validates :name, :object_class, presence: true
|
|
24
|
-
|
|
25
|
-
unless: ->(o) { o.is_a?(CamaleonCms::CustomFieldGroup) }
|
|
33
|
+
validates :slug, uniqueness: { scope: %i[parent_id object_class],
|
|
34
|
+
unless: ->(o) { o.is_a?(CamaleonCms::CustomFieldGroup) } }
|
|
26
35
|
|
|
27
36
|
before_validation :before_validating
|
|
37
|
+
before_update :check_select_eval_authorization
|
|
28
38
|
|
|
29
39
|
private
|
|
30
40
|
|
|
@@ -32,5 +42,18 @@ module CamaleonCms
|
|
|
32
42
|
self.slug ||= name
|
|
33
43
|
self.slug = slug.to_s.parameterize
|
|
34
44
|
end
|
|
45
|
+
|
|
46
|
+
# Prevent unauthorized modification of field_key to select_eval
|
|
47
|
+
def check_select_eval_authorization
|
|
48
|
+
return unless respond_to?(:options) && options.present?
|
|
49
|
+
|
|
50
|
+
# Check if field_key is being changed to select_eval
|
|
51
|
+
return unless options[:field_key] == 'select_eval'
|
|
52
|
+
# Allow if user has explicit permission
|
|
53
|
+
return if can?(:manage, :select_eval)
|
|
54
|
+
|
|
55
|
+
errors.add(:base, 'Not authorized to create or modify select_eval fields')
|
|
56
|
+
throw :abort
|
|
57
|
+
end
|
|
35
58
|
end
|
|
36
59
|
end
|
|
@@ -5,16 +5,22 @@ module CamaleonCms
|
|
|
5
5
|
alias_attribute :site_id, :parent_id
|
|
6
6
|
|
|
7
7
|
default_scope do
|
|
8
|
-
where("object_class != '_fields'")
|
|
9
|
-
.reorder("#{CamaleonCms::CustomField.table_name}.field_order ASC")
|
|
8
|
+
where("object_class != '_fields'").reorder("#{CamaleonCms::CustomField.table_name}.field_order ASC")
|
|
10
9
|
end
|
|
11
10
|
|
|
11
|
+
# Scope metas by object_class so a group's numeric id colliding with another model's id does
|
|
12
|
+
# not read the wrong meta rows (meta rows are keyed by both objectid and object_class).
|
|
13
|
+
# rubocop:disable Rails/InverseOf
|
|
12
14
|
has_many :metas, -> { where(object_class: 'CustomFieldGroup') }, foreign_key: :objectid, dependent: :destroy
|
|
15
|
+
# rubocop:enable Rails/InverseOf
|
|
16
|
+
|
|
13
17
|
has_many :fields, -> { where(object_class: '_fields') }, class_name: 'CamaleonCms::CustomField',
|
|
14
|
-
foreign_key: :parent_id, dependent: :destroy
|
|
15
|
-
|
|
18
|
+
foreign_key: :parent_id, dependent: :destroy,
|
|
19
|
+
inverse_of: :custom_field_group
|
|
20
|
+
belongs_to :site, foreign_key: :parent_id, optional: true, inverse_of: :custom_field_groups
|
|
21
|
+
belongs_to :owner, polymorphic: true, foreign_key: :objectid, foreign_type: :object_class, optional: true
|
|
16
22
|
|
|
17
|
-
|
|
23
|
+
validates :slug, uniqueness: { scope: %i[object_class objectid parent_id] }
|
|
18
24
|
|
|
19
25
|
before_validation :before_validating
|
|
20
26
|
|
|
@@ -30,7 +36,16 @@ module CamaleonCms
|
|
|
30
36
|
# ****** check all options for each case in Admin::CustomFieldsHelper ****
|
|
31
37
|
# SAMPLE: my_model.add_field({"name"=>"Sub Title", "slug"=>"subtitle"}, {"field_key"=>"text_box",
|
|
32
38
|
# "translate"=>true, default_value: "Get in Touch"})
|
|
39
|
+
# Adds a manual field to the group (used by admin UI)
|
|
40
|
+
# item: field attributes
|
|
41
|
+
# options: field options
|
|
33
42
|
def add_manual_field(item, options)
|
|
43
|
+
# Prevent creation of dangerous field types (select_eval) unless the actor is allowed.
|
|
44
|
+
if options[:field_key] == 'select_eval'
|
|
45
|
+
can?(:manage, :select_eval) ||
|
|
46
|
+
raise(CanCan::AccessDenied, 'Not authorized to create select_eval fields')
|
|
47
|
+
end
|
|
48
|
+
|
|
34
49
|
c = get_field(item[:slug] || item['slug'])
|
|
35
50
|
return c if c.present?
|
|
36
51
|
|
|
@@ -43,13 +58,15 @@ module CamaleonCms
|
|
|
43
58
|
end
|
|
44
59
|
alias add_field add_manual_field
|
|
45
60
|
|
|
46
|
-
# return a field with slug = slug from current group
|
|
61
|
+
# return a field with slug = slug from the current group
|
|
47
62
|
def get_field(slug)
|
|
48
|
-
fields.
|
|
63
|
+
fields.find_by_slug(slug) # rubocop:disable Rails/DynamicFindBy
|
|
49
64
|
end
|
|
50
65
|
|
|
51
66
|
# only used by form on admin panel (protected)
|
|
52
67
|
# return array of failed_fields and full_fields [[failed fields], [all fields]]
|
|
68
|
+
# items: hash of field items
|
|
69
|
+
# item_options: hash of options for each item
|
|
53
70
|
def add_fields(items, item_options)
|
|
54
71
|
fields.where.not(id: items.to_h.map { |_k, obj| obj['id'] }.uniq).destroy_all
|
|
55
72
|
cache_fields = []
|
|
@@ -57,12 +74,38 @@ module CamaleonCms
|
|
|
57
74
|
errors_saved = []
|
|
58
75
|
if items.present?
|
|
59
76
|
items.each do |i, item|
|
|
77
|
+
# allow string or symbol keys for incoming params
|
|
78
|
+
id_val = item['id'] || item[:id]
|
|
79
|
+
|
|
60
80
|
item[:field_order] = order_index
|
|
61
81
|
options = item_options[i] || {}
|
|
62
|
-
if
|
|
82
|
+
if id_val.present? && (field_item = fields.find_by(id: id_val)).present?
|
|
83
|
+
# If this is an existing select_eval field (or the incoming data would
|
|
84
|
+
# make it a select_eval) ensure the current actor has explicit
|
|
85
|
+
# permission. For updates, we preserve the form-like behaviour by
|
|
86
|
+
# collecting an error-like non-persisted field in errors_saved and
|
|
87
|
+
# skipping the update when unauthorized.
|
|
88
|
+
existing_key = (field_item.options || {})[:field_key].to_s
|
|
89
|
+
# consider field_key coming from the per-item options (options) or the item itself
|
|
90
|
+
incoming_key = (options[:field_key] || item[:field_key]).to_s
|
|
91
|
+
if (existing_key == 'select_eval' || incoming_key == 'select_eval') && !can?(:manage, :select_eval)
|
|
92
|
+
field_item.errors.add(:base, 'Not authorized to modify select_eval field')
|
|
93
|
+
errors_saved << field_item
|
|
94
|
+
next
|
|
95
|
+
end
|
|
96
|
+
|
|
63
97
|
saved = field_item.update(item)
|
|
64
98
|
cache_fields << field_item
|
|
65
99
|
else
|
|
100
|
+
# Check if the incoming options request creation of select_eval
|
|
101
|
+
incoming_key = (options[:field_key] || item[:field_key]).to_s
|
|
102
|
+
if incoming_key == 'select_eval' && !can?(:manage, :select_eval)
|
|
103
|
+
# Add an error-like non-persisted field to errors_saved to preserve behaviour
|
|
104
|
+
field_item = fields.new(item)
|
|
105
|
+
field_item.errors.add(:base, 'Not authorized to create select_eval field')
|
|
106
|
+
errors_saved << field_item
|
|
107
|
+
next
|
|
108
|
+
end
|
|
66
109
|
field_item = fields.new(item)
|
|
67
110
|
cache_fields << field_item
|
|
68
111
|
saved = field_item.save
|
|
@@ -79,6 +122,11 @@ module CamaleonCms
|
|
|
79
122
|
end
|
|
80
123
|
|
|
81
124
|
# generate the caption for this group
|
|
125
|
+
# The caption is rendered with `raw` by admin/settings/custom_fields/index.html.erb, so every
|
|
126
|
+
# interpolated value is escaped here, at the source: `the_title` already returns an escaped
|
|
127
|
+
# SafeBuffer, and the plain attributes below are escaped explicitly. `object_class` is included
|
|
128
|
+
# because the placement check admits any class name paired with the current site's id, so it is
|
|
129
|
+
# attacker-settable text like the model names are.
|
|
82
130
|
def get_caption
|
|
83
131
|
caption = ''
|
|
84
132
|
begin
|
|
@@ -89,16 +137,19 @@ module CamaleonCms
|
|
|
89
137
|
caption = "Fields for Categories in <b>#{site.post_types.find(objectid).decorate.the_title}</b>"
|
|
90
138
|
when 'PostType_PostTag'
|
|
91
139
|
caption = "Fields for Post tags in <b>#{site.post_types.find(objectid).decorate.the_title}</b>"
|
|
92
|
-
when '
|
|
93
|
-
|
|
140
|
+
when 'Main'
|
|
141
|
+
widget_name = CamaleonCms::Widget::Main.find(objectid).name.translate
|
|
142
|
+
caption = "Fields for Widget <b>(#{ERB::Util.html_escape(widget_name)})</b>"
|
|
94
143
|
when 'Theme'
|
|
95
|
-
|
|
144
|
+
theme_name = begin
|
|
96
145
|
site.themes.find(objectid).name
|
|
97
146
|
rescue StandardError
|
|
98
147
|
objectid
|
|
99
|
-
end
|
|
148
|
+
end
|
|
149
|
+
caption = "Field settings for Theme <b>(#{ERB::Util.html_escape(theme_name)})</b>"
|
|
100
150
|
when 'NavMenu'
|
|
101
|
-
|
|
151
|
+
menu_name = CamaleonCms::NavMenu.find(objectid).name
|
|
152
|
+
caption = "Field settings for Menus <b>(#{ERB::Util.html_escape(menu_name)})</b>"
|
|
102
153
|
when 'Site'
|
|
103
154
|
caption = 'Field settings the site'
|
|
104
155
|
when 'PostType'
|
|
@@ -107,25 +158,28 @@ module CamaleonCms
|
|
|
107
158
|
p = CamaleonCms::Post.find(objectid).decorate
|
|
108
159
|
caption = "Fields for content <b>(#{p.the_title})</b>"
|
|
109
160
|
else # 'Plugin' or other class
|
|
110
|
-
caption = "Fields for <b>#{object_class}</b>"
|
|
161
|
+
caption = "Fields for <b>#{ERB::Util.html_escape(object_class)}</b>"
|
|
111
162
|
end
|
|
112
163
|
rescue StandardError => e
|
|
113
164
|
Rails.logger.debug "Camaleon CMS - Menu Item Error: #{e.message} ==> Attrs: #{attributes}"
|
|
114
165
|
end
|
|
115
|
-
|
|
166
|
+
# rubocop:disable Rails/OutputSafety -- every interpolated value above is escaped at the source
|
|
167
|
+
caption.html_safe
|
|
168
|
+
# rubocop:enable Rails/OutputSafety
|
|
116
169
|
end
|
|
117
170
|
|
|
118
171
|
private
|
|
119
172
|
|
|
120
173
|
def before_validating
|
|
121
|
-
self.slug = "_group-#{name.to_s.parameterize}"
|
|
174
|
+
self.slug = "_group-#{name.to_s.parameterize}" if slug.blank?
|
|
122
175
|
end
|
|
123
176
|
|
|
124
177
|
# auto save the default field values
|
|
125
178
|
def auto_save_default_values(field, options)
|
|
126
179
|
class_name = object_class.split('_').first
|
|
127
|
-
return unless %w[Post Category Plugin
|
|
128
|
-
|
|
180
|
+
return unless %w[Post Category Plugin Theme].include?(class_name) &&
|
|
181
|
+
objectid &&
|
|
182
|
+
(options[:default_value].present? || options[:default_values].present?)
|
|
129
183
|
|
|
130
184
|
owner = if class_name == 'Theme'
|
|
131
185
|
"CamaleonCms::#{class_name}".constantize.find(objectid) # owner model
|
|
@@ -133,13 +187,13 @@ module CamaleonCms
|
|
|
133
187
|
begin
|
|
134
188
|
"CamaleonCms::#{class_name}".constantize.find(objectid)
|
|
135
189
|
rescue StandardError
|
|
136
|
-
"CamaleonCms::#{class_name}".constantize.
|
|
190
|
+
"CamaleonCms::#{class_name}".constantize.find_by_slug(objectid) # rubocop:disable Rails/DynamicFindBy
|
|
137
191
|
end
|
|
138
192
|
end
|
|
139
193
|
(options[:default_values] || [options[:default_value]] || []).each do |value|
|
|
140
194
|
if owner.present?
|
|
141
|
-
owner.custom_field_values
|
|
142
|
-
|
|
195
|
+
owner.custom_field_values
|
|
196
|
+
.create!(custom_field_id: field.id, custom_field_slug: field.slug, value: fix_meta_value(value))
|
|
143
197
|
end
|
|
144
198
|
end
|
|
145
199
|
end
|
|
@@ -7,23 +7,154 @@ module CamaleonCms
|
|
|
7
7
|
default_scope { order("#{CamaleonCms::CustomFieldsRelationship.table_name}.term_order ASC") }
|
|
8
8
|
|
|
9
9
|
# relations
|
|
10
|
-
belongs_to :custom_field,
|
|
10
|
+
belongs_to :custom_field, class_name: 'CamaleonCms::CustomField', optional: true
|
|
11
|
+
belongs_to :owner, polymorphic: true, foreign_key: :objectid, foreign_type: :object_class, optional: true
|
|
11
12
|
|
|
12
13
|
# validates :objectid, :custom_field_id, presence: true
|
|
13
14
|
validates :custom_field_id, presence: true # error on clone model
|
|
14
15
|
|
|
16
|
+
# Rendered positions that need a save-time gate (audit M17, scan-and-reject policy). An
|
|
17
|
+
# `editor` value is emitted as markup (`raw`); a `field_attrs` value is a JSON pair whose
|
|
18
|
+
# members are emitted as markup; the URI field types are emitted into href/src. Every other
|
|
19
|
+
# field type's value renders through escaping ERB as element content, where markup cannot
|
|
20
|
+
# execute, so it needs no gate.
|
|
21
|
+
MARKUP_FIELD_KEYS = %w[editor].freeze
|
|
22
|
+
JSON_MARKUP_FIELD_KEYS = %w[field_attrs].freeze
|
|
23
|
+
URI_FIELD_KEYS = %w[url image audio video file].freeze
|
|
24
|
+
GATED_FIELD_KEYS = (MARKUP_FIELD_KEYS + JSON_MARKUP_FIELD_KEYS + URI_FIELD_KEYS).freeze
|
|
25
|
+
|
|
26
|
+
validate :reject_untrusted_dangerous_value
|
|
27
|
+
|
|
15
28
|
after_save :set_parent_slug
|
|
16
29
|
after_save :update_model_owner # TODO: convert this model into polymorphic
|
|
17
30
|
|
|
31
|
+
# Opt-out for trusted server-side pipelines (imports, seeds, plugin code), mirroring
|
|
32
|
+
# Post#unfiltered_content!: a reader plus a bang enabler and NO writer, so mass assignment
|
|
33
|
+
# cannot reach it. Sticky for the lifetime of the instance.
|
|
34
|
+
attr_reader :unfiltered_value
|
|
35
|
+
|
|
36
|
+
def unfiltered_value!
|
|
37
|
+
@unfiltered_value = true
|
|
38
|
+
self
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
class << self
|
|
42
|
+
# Whether this field type's value is emitted into a markup or URL position (so it is gated).
|
|
43
|
+
def gated_field_key?(field_key)
|
|
44
|
+
GATED_FIELD_KEYS.include?(field_key.to_s)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Why the save-time gate would refuse this value for the field type, or nil if it passes:
|
|
48
|
+
# :too_large (over the parse ceiling), :html (disallowed markup), :uri (script-capable URL).
|
|
49
|
+
# Ignores author trust, so the validation (trust-gated) and the scan_content rake task (which
|
|
50
|
+
# reports every stored row) share one dispatch and cannot drift.
|
|
51
|
+
def gate_rejection_reason(field_key, value)
|
|
52
|
+
return if value.blank?
|
|
53
|
+
|
|
54
|
+
key = field_key.to_s
|
|
55
|
+
if MARKUP_FIELD_KEYS.include?(key)
|
|
56
|
+
markup_rejection_reason(value)
|
|
57
|
+
elsif JSON_MARKUP_FIELD_KEYS.include?(key)
|
|
58
|
+
return :too_large if CamaleonCms::UnsafeMarkup.too_large?(value)
|
|
59
|
+
|
|
60
|
+
:html if json_member_values(value).any? { |member| markup_unsafe?(member) }
|
|
61
|
+
elsif URI_FIELD_KEYS.include?(key)
|
|
62
|
+
:uri if CamaleonCms::UnsafeMarkup.dangerous_uri?(value)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
|
|
68
|
+
def markup_rejection_reason(value)
|
|
69
|
+
return :too_large if CamaleonCms::UnsafeMarkup.too_large?(value)
|
|
70
|
+
|
|
71
|
+
:html if markup_unsafe?(value)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def markup_unsafe?(value)
|
|
75
|
+
CamaleonCms::UnsafeMarkup.unsafe_html?(
|
|
76
|
+
value, tags: CamaleonCms::Post::CONTENT_ALLOWED_TAGS,
|
|
77
|
+
attributes: CamaleonCms::Post::CONTENT_ALLOWED_ATTRIBUTES
|
|
78
|
+
)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# A field_attrs value stores JSON whose string members render verbatim. Scan every DECODED
|
|
82
|
+
# member (Hash, Array or nested), not the stored bytes: the Rails JSON encoder unicode-escapes
|
|
83
|
+
# angle brackets (storing the escape instead of a literal bracket), so a byte-level scan of the
|
|
84
|
+
# stored string would pass markup that JSON.parse restores at render. Unparseable values are
|
|
85
|
+
# scanned as-is (fail closed on whatever the renderer would fall back to).
|
|
86
|
+
def json_member_values(value)
|
|
87
|
+
flatten_json_strings(JSON.parse(value))
|
|
88
|
+
rescue JSON::ParserError
|
|
89
|
+
[value.to_s]
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def flatten_json_strings(node)
|
|
93
|
+
case node
|
|
94
|
+
when Hash then node.values.flat_map { |member| flatten_json_strings(member) }
|
|
95
|
+
when Array then node.flat_map { |member| flatten_json_strings(member) }
|
|
96
|
+
else [node.to_s]
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
18
101
|
private
|
|
19
102
|
|
|
103
|
+
# Security (audit M17): the frontend renders an `editor`/`field_attrs` value verbatim and
|
|
104
|
+
# URI-type values into URL positions, so a value an untrusted author may not write is refused --
|
|
105
|
+
# never rewritten. Stored values therefore always equal authored values. The scan/dispatch lives
|
|
106
|
+
# in the class-level gate_rejection_reason so the scan_content rake task reuses it verbatim.
|
|
107
|
+
def reject_untrusted_dangerous_value
|
|
108
|
+
return if value.blank? || unfiltered_value
|
|
109
|
+
|
|
110
|
+
field_key = custom_field&.options&.[](:field_key).to_s
|
|
111
|
+
return unless self.class.gated_field_key?(field_key)
|
|
112
|
+
return if author_trusted_for_unfiltered_value?
|
|
113
|
+
|
|
114
|
+
reason = self.class.gate_rejection_reason(field_key, value)
|
|
115
|
+
return unless reason
|
|
116
|
+
|
|
117
|
+
errors.add(:base, cama_rejection_message(rejection_message_key(reason)))
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def rejection_message_key(reason)
|
|
121
|
+
case reason
|
|
122
|
+
when :too_large then 'value_too_large'
|
|
123
|
+
when :uri then 'value_rejected_uri'
|
|
124
|
+
else 'value_rejected_html'
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# The message must never be swallowed by a missing translation: only en.yml carries these keys,
|
|
129
|
+
# while the process locale follows the current admin/site language — fall back to English.
|
|
130
|
+
def cama_rejection_message(key)
|
|
131
|
+
full_key = "camaleon_cms.admin.custom_field.message.#{key}"
|
|
132
|
+
I18n.t(full_key, slug: custom_field_slug,
|
|
133
|
+
default: I18n.t(full_key, slug: custom_field_slug, locale: :en))
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Trust follows the post-content model: an admin may write anything; a post's field values may
|
|
137
|
+
# skip the gate when the saver holds post_content_unfiltered_html for the post type. Fails
|
|
138
|
+
# closed (the gate applies) without a request context — benign values still pass the scan.
|
|
139
|
+
def author_trusted_for_unfiltered_value?
|
|
140
|
+
user = CurrentRequest.user
|
|
141
|
+
site = CurrentRequest.site
|
|
142
|
+
return false if user.blank? || site.blank?
|
|
143
|
+
return true if user.admin?
|
|
144
|
+
|
|
145
|
+
parent = owner
|
|
146
|
+
return false unless parent.is_a?(CamaleonCms::Post) && parent.post_type.present?
|
|
147
|
+
|
|
148
|
+
CamaleonCms::Ability.new(user, site).can?(:post_content_unfiltered_html, parent.post_type)
|
|
149
|
+
end
|
|
150
|
+
|
|
20
151
|
def set_parent_slug
|
|
21
152
|
# self.update_column('custom_field_slug', self.custom_fields.slug)
|
|
22
153
|
end
|
|
23
154
|
|
|
24
155
|
# touch owner model
|
|
25
156
|
def update_model_owner
|
|
26
|
-
"CamaleonCms::#{object_class}".constantize.find(objectid).touch
|
|
157
|
+
"CamaleonCms::#{object_class}".constantize.find(objectid).touch # rubocop:disable Rails/SkipsModelValidations
|
|
27
158
|
rescue StandardError
|
|
28
159
|
nil
|
|
29
160
|
end
|