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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e0997f6bbf586f6f6c5618c97814c0a291fa98bcb54adbd7736a0449f60edc33
|
|
4
|
+
data.tar.gz: e73b0849f7b21ffc7bd7510d71c328706b022cfefaf738194836b9e332e4f062
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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:
|
|
@@ -173,15 +172,29 @@ Owen Peredo Diaz
|
|
|
173
172
|
https://camaleon.website/license.html
|
|
174
173
|
|
|
175
174
|
## Testing
|
|
176
|
-
|
|
175
|
+
> **Note:** Since this project is a gem, Rails commands (e.g., `rails routes`, `zeitwerk:check`) MUST be run from the `spec/dummy` folder. Use subshells to ensure you return to root: `(cd spec/dummy && bin/rails ...)`
|
|
176
|
+
|
|
177
|
+
- Init DB
|
|
178
|
+
|
|
177
179
|
```bash
|
|
178
180
|
RAILS_ENV=test bundle exec rake app:db:migrate
|
|
179
181
|
RAILS_ENV=test bundle exec rake app:db:test:prepare
|
|
180
182
|
```
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
bundle
|
|
184
|
-
|
|
183
|
+
|
|
184
|
+
- Run the tests: `bin/rspec`
|
|
185
|
+
- If the `rspec` binstub is not present, generate it: `bundle binstubs rspec-core`
|
|
186
|
+
- And then still run `bin/rspec`
|
|
187
|
+
|
|
188
|
+
## Permissions (Manager roles)
|
|
189
|
+
|
|
190
|
+
See [docs/security/permissions.md](docs/security/permissions.md) for details on manager permissions, custom fields, and security considerations.
|
|
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`, …).
|
|
185
198
|
|
|
186
199
|
## Contributing
|
|
187
200
|
* Fork it.
|
|
@@ -190,6 +203,13 @@ bundle exec rspec
|
|
|
190
203
|
* Push to the branch (git push origin my_feature_branch)
|
|
191
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)
|
|
192
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
|
+
|
|
193
213
|
## Version History
|
|
194
214
|
|
|
195
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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(
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
#
|
|
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
|
-
#
|
|
11
|
-
# you can run
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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(
|
|
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.
|
|
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
|
-
|
|
58
|
-
return
|
|
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.
|
|
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 =
|
|
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(
|
|
76
|
-
#
|
|
77
|
-
render html: config[:msg].
|
|
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 =
|
|
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(
|
|
86
|
-
render html: config[:msg].
|
|
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
|
-
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
44
|
-
current_site.users.where('role <> ?', 'client').order(:username).
|
|
45
|
-
|
|
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
|
|
@@ -2,21 +2,27 @@ module Plugins
|
|
|
2
2
|
module FrontCache
|
|
3
3
|
class AdminController < CamaleonCms::Apps::PluginsAdminController
|
|
4
4
|
include Plugins::FrontCache::FrontCacheHelper
|
|
5
|
+
|
|
5
6
|
def settings
|
|
6
7
|
@caches = current_site.get_meta('front_cache_elements', { paths: [] })
|
|
7
|
-
@caches[:paths] << ''
|
|
8
|
+
@caches[:paths] << '' if @caches[:paths].blank?
|
|
8
9
|
end
|
|
9
10
|
|
|
10
11
|
def save_settings
|
|
11
|
-
current_site.set_meta(
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
+
)
|
|
20
26
|
flash[:notice] = t('plugin.front_cache.message.settings_saved').to_s
|
|
21
27
|
redirect_to action: :settings
|
|
22
28
|
end
|
|
@@ -1,37 +1,47 @@
|
|
|
1
1
|
module Plugins
|
|
2
2
|
module FrontCache
|
|
3
3
|
module FrontCacheHelper
|
|
4
|
-
#
|
|
4
|
+
# cache all pages configured in this plugin's settings for public users
|
|
5
5
|
def front_cache_front_before_load
|
|
6
6
|
if current_site.get_option('refresh_cache') # clear cache every restart server unless option checked in settings
|
|
7
7
|
front_cache_clean unless current_site.get_meta('front_cache_elements')[:preserve_cache_on_restart]
|
|
8
8
|
current_site.set_option('refresh_cache', false)
|
|
9
9
|
end
|
|
10
10
|
|
|
11
|
-
# avoid cache if current visitor is logged in or development
|
|
11
|
+
# avoid cache if the current visitor is logged in, or we're in the development or test environment
|
|
12
12
|
return if signin? || Rails.env.development? || Rails.env.test? || !request.get?
|
|
13
13
|
|
|
14
14
|
cache_key = front_cache_plugin_cache_key
|
|
15
15
|
@caches = current_site.get_meta('front_cache_elements')
|
|
16
|
-
if
|
|
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
|
-
|
|
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' &&
|
|
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
|
-
|
|
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) ||
|
|
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,11 +53,15 @@ 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 &&
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
56
|
+
return unless @_plugin_do_cache && flash.keys.blank?
|
|
57
|
+
|
|
58
|
+
body =
|
|
59
|
+
response
|
|
60
|
+
.body.gsub(/csrf-token" content="(.*?)"/, 'csrf-token" content="{{form_authenticity_token}}"')
|
|
61
|
+
.gsub(
|
|
62
|
+
/name="authenticity_token" value="(.*?)"/, 'name="authenticity_token" value="{{form_authenticity_token}}"'
|
|
63
|
+
)
|
|
64
|
+
args = { data: body }
|
|
51
65
|
hooks_run('front_cache_writing_cache', args)
|
|
52
66
|
front_cache_plugin_cache_create(cache_key, args[:data])
|
|
53
67
|
Rails.logger.info "Camaleon CMS - cache saved as: #{front_cache_plugin_get_path(cache_key)}"
|
|
@@ -57,22 +71,27 @@ module Plugins
|
|
|
57
71
|
def front_cache_on_active(_plugin)
|
|
58
72
|
return if current_site.get_meta('front_cache_elements', nil).present?
|
|
59
73
|
|
|
60
|
-
current_site.set_meta(
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
74
|
+
current_site.set_meta(
|
|
75
|
+
'front_cache_elements',
|
|
76
|
+
{
|
|
77
|
+
paths: [],
|
|
78
|
+
posts: [],
|
|
79
|
+
post_types: [current_site.post_types.where(slug: 'page').first.id],
|
|
80
|
+
skip_posts: [],
|
|
81
|
+
home: true,
|
|
82
|
+
cache_login: true,
|
|
83
|
+
cache_counter: 0
|
|
84
|
+
}
|
|
85
|
+
)
|
|
67
86
|
end
|
|
68
87
|
|
|
69
88
|
# on uninstall plugin
|
|
70
|
-
def front_cache_on_inactive(
|
|
89
|
+
def front_cache_on_inactive(_plugin)
|
|
71
90
|
# current_site.delete_meta("front_cache_elements")
|
|
72
91
|
end
|
|
73
92
|
|
|
74
93
|
# cache actions (for logged users)
|
|
75
|
-
def front_cache_on_render(
|
|
94
|
+
def front_cache_on_render(_args); end
|
|
76
95
|
|
|
77
96
|
# expire cache for a page after comment registered or updated
|
|
78
97
|
def front_cache_before_load; end
|
|
@@ -126,12 +145,26 @@ module Plugins
|
|
|
126
145
|
end
|
|
127
146
|
|
|
128
147
|
def front_cache_plugin_match_path_patterns?(key, key2)
|
|
129
|
-
|
|
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
|
|
130
160
|
end
|
|
131
161
|
|
|
132
162
|
def front_cache_plugin_cache_key
|
|
133
163
|
uri = [request.protocol + request.host_with_port, request.fullpath].join('/')
|
|
134
|
-
|
|
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)
|
|
135
168
|
end
|
|
136
169
|
end
|
|
137
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
|