From 1aa73f625e6d7bfbff387faf49419d3aa749fbde Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 27 Jul 2022 20:06:38 +0000 Subject: [PATCH 001/137] Bump dotenv-rails from 2.7.6 to 2.8.1 Bumps [dotenv-rails](https://github.com/bkeepers/dotenv) from 2.7.6 to 2.8.1. - [Release notes](https://github.com/bkeepers/dotenv/releases) - [Changelog](https://github.com/bkeepers/dotenv/blob/master/Changelog.md) - [Commits](https://github.com/bkeepers/dotenv/compare/v2.7.6...v2.8.1) --- updated-dependencies: - dependency-name: dotenv-rails dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Gemfile.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 2715515c8a..5d9d20b4ce 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -121,9 +121,9 @@ GEM database_cleaner-core (~> 2.0.0) database_cleaner-core (2.0.1) diff-lcs (1.4.4) - dotenv (2.7.6) - dotenv-rails (2.7.6) - dotenv (= 2.7.6) + dotenv (2.8.1) + dotenv-rails (2.8.1) + dotenv (= 2.8.1) railties (>= 3.2) erubi (1.10.0) execjs (2.7.0) From 98f974116b1f94a4ba19356add96d0bfe00ff074 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 27 Jul 2022 20:06:19 +0000 Subject: [PATCH 002/137] Bump pg from 1.4.1 to 1.4.2 Bumps [pg](https://github.com/ged/ruby-pg) from 1.4.1 to 1.4.2. - [Release notes](https://github.com/ged/ruby-pg/releases) - [Changelog](https://github.com/ged/ruby-pg/blob/master/History.rdoc) - [Commits](https://github.com/ged/ruby-pg/compare/v1.4.1...v1.4.2) --- updated-dependencies: - dependency-name: pg dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 5d9d20b4ce..454c52d34d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -203,7 +203,7 @@ GEM racc (~> 1.4) parser (3.1.2.0) ast (~> 2.4.1) - pg (1.4.1) + pg (1.4.2) pry (0.12.2) coderay (~> 1.1.0) method_source (~> 0.9.0) From f5ea0250c6c24a70acf594e21e133329797a9e1d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 28 Jul 2022 20:05:51 +0000 Subject: [PATCH 003/137] Bump faker from 2.21.0 to 2.22.0 Bumps [faker](https://github.com/faker-ruby/faker) from 2.21.0 to 2.22.0. - [Release notes](https://github.com/faker-ruby/faker/releases) - [Changelog](https://github.com/faker-ruby/faker/blob/master/CHANGELOG.md) - [Commits](https://github.com/faker-ruby/faker/compare/v2.21.0...v2.22.0) --- updated-dependencies: - dependency-name: faker dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 454c52d34d..4e63d10ac5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -132,7 +132,7 @@ GEM factory_bot_rails (6.2.0) factory_bot (~> 6.2.0) railties (>= 5.0.0) - faker (2.21.0) + faker (2.22.0) i18n (>= 1.8.11, < 2) faraday (1.3.0) faraday-net_http (~> 1.0) From 8979fedcf05d207fbd37f712852d60393bcd8e72 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 3 Aug 2022 12:01:19 +0100 Subject: [PATCH 004/137] Bump webmock from 3.14.0 to 3.16.0 (#2234) Bumps [webmock](https://github.com/bblimke/webmock) from 3.14.0 to 3.16.0. - [Release notes](https://github.com/bblimke/webmock/releases) - [Changelog](https://github.com/bblimke/webmock/blob/master/CHANGELOG.md) - [Commits](https://github.com/bblimke/webmock/compare/v3.14.0...v3.16.0) --- updated-dependencies: - dependency-name: webmock dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 4e63d10ac5..cbfb4f7573 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -314,7 +314,7 @@ GEM nokogiri (~> 1.6) rubyzip (>= 1.3.0) selenium-webdriver (~> 4.0) - webmock (3.14.0) + webmock (3.16.0) addressable (>= 2.8.0) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) From ba1861e5e271186e1a60dcb7114ffc395073f7bb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 5 Aug 2022 20:07:12 +0000 Subject: [PATCH 005/137] Bump webmock from 3.16.0 to 3.17.0 Bumps [webmock](https://github.com/bblimke/webmock) from 3.16.0 to 3.17.0. - [Release notes](https://github.com/bblimke/webmock/releases) - [Changelog](https://github.com/bblimke/webmock/blob/master/CHANGELOG.md) - [Commits](https://github.com/bblimke/webmock/compare/v3.16.0...v3.17.0) --- updated-dependencies: - dependency-name: webmock dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index cbfb4f7573..25990d1734 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -314,7 +314,7 @@ GEM nokogiri (~> 1.6) rubyzip (>= 1.3.0) selenium-webdriver (~> 4.0) - webmock (3.16.0) + webmock (3.17.0) addressable (>= 2.8.0) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) From 7b3c6ace520858b35e3c1e0033555e1952e21d0f Mon Sep 17 00:00:00 2001 From: Pablo Brasero <36066+pablobm@users.noreply.github.com> Date: Mon, 8 Aug 2022 11:36:30 +0100 Subject: [PATCH 006/137] Drop support for Rails 5.x (#2201) Rails 5.0 and 5.1 already reached end-of-life, and 5.2 will reach it next week. Time to drop support for them. Additionally, removed gemfiles/sass_3_4.gemfile, which was a leftover from #1197 https://guides.rubyonrails.org/maintenance_policy.html#severe-security-issues https://github.com/thoughtbot/administrate/pull/1197 --- .circleci/config.yml | 3 -- Appraisals | 32 ------------ .../administrate/application_helper.rb | 6 +-- gemfiles/rails50.gemfile | 52 ------------------- gemfiles/rails51.gemfile | 52 ------------------- gemfiles/rails52.gemfile | 52 ------------------- gemfiles/sass_3_4.gemfile | 47 ----------------- spec/example_app/config/application.rb | 10 +--- .../config/initializers/disable_xml_params.rb | 5 -- 9 files changed, 2 insertions(+), 257 deletions(-) delete mode 100644 gemfiles/rails50.gemfile delete mode 100644 gemfiles/rails51.gemfile delete mode 100644 gemfiles/rails52.gemfile delete mode 100644 gemfiles/sass_3_4.gemfile delete mode 100644 spec/example_app/config/initializers/disable_xml_params.rb diff --git a/.circleci/config.yml b/.circleci/config.yml index 344842ac55..fe1271786a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -55,9 +55,6 @@ jobs: - shared_steps # Run the tests against the versions of Rails that support Ruby 2.7 - run: bundle exec appraisal install - - run: bundle exec appraisal rails50 rspec - - run: bundle exec appraisal rails51 rspec - - run: bundle exec appraisal rails52 rspec - run: bundle exec appraisal rails60 rspec - run: bundle exec appraisal rails61 rspec - run: bundle exec appraisal rails70 rspec diff --git a/Appraisals b/Appraisals index 16342df08b..5a95ffc65a 100644 --- a/Appraisals +++ b/Appraisals @@ -1,35 +1,3 @@ -appraise "rails50" do - gem "actionpack", "~> 5.0.0" - gem "actionview", "~> 5.0.0" - gem "activerecord", "~> 5.0.0" - gem "pg", "0.21.0" - - group :development, :test do - gem "rspec-rails" - end -end - -appraise "rails51" do - gem "actionpack", "~> 5.1.0" - gem "actionview", "~> 5.1.0" - gem "activerecord", "~> 5.1.0" - gem "pg", "0.21.0" - - group :development, :test do - gem "rspec-rails" - end -end - -appraise "rails52" do - gem "actionpack", "~> 5.2.0" - gem "actionview", "~> 5.2.0" - gem "activerecord", "~> 5.2.0" - - group :development, :test do - gem "rspec-rails" - end -end - appraise "rails60" do gem "rails", "~> 6.0.3.4" end diff --git a/app/helpers/administrate/application_helper.rb b/app/helpers/administrate/application_helper.rb index 1a950b0b7e..efc9378c8d 100644 --- a/app/helpers/administrate/application_helper.rb +++ b/app/helpers/administrate/application_helper.rb @@ -4,11 +4,7 @@ module ApplicationHelper SINGULAR_COUNT = 1 def application_title - if Rails::VERSION::MAJOR <= 5 - Rails.application.class.parent_name.titlecase - else - Rails.application.class.module_parent_name.titlecase - end + Rails.application.class.module_parent_name.titlecase end def render_field(field, locals = {}) diff --git a/gemfiles/rails50.gemfile b/gemfiles/rails50.gemfile deleted file mode 100644 index 7d8c39c186..0000000000 --- a/gemfiles/rails50.gemfile +++ /dev/null @@ -1,52 +0,0 @@ -# This file was generated by Appraisal - -source "https://rubygems.org" - -gem "administrate-field-image" -gem "faker" -gem "front_matter_parser" -gem "globalid" -gem "kaminari-i18n" -gem "pg", "0.21.0" -gem "pundit" -gem "redcarpet" -gem "sentry-raven" -gem "unicorn" -gem "actionpack", "~> 5.0.0" -gem "actionview", "~> 5.0.0" -gem "activerecord", "~> 5.0.0" - -group :development, :test do - gem "appraisal" - gem "awesome_print" - gem "bundler-audit", require: false - gem "byebug" - gem "dotenv-rails" - gem "factory_bot_rails" - gem "i18n-tasks", "0.9.37" - gem "pry-rails" - gem "yard" - gem "rspec-rails" -end - -group :test do - gem "ammeter" - gem "capybara" - gem "database_cleaner" - gem "formulaic" - gem "launchy" - gem "selenium-webdriver" - gem "shoulda-matchers" - gem "timecop" - gem "webdrivers" - gem "webmock" - gem "webrick" - gem "xpath", "3.2.0" -end - -group :staging, :production do - gem "rack-timeout" - gem "uglifier" -end - -gemspec path: "../" diff --git a/gemfiles/rails51.gemfile b/gemfiles/rails51.gemfile deleted file mode 100644 index db0284a315..0000000000 --- a/gemfiles/rails51.gemfile +++ /dev/null @@ -1,52 +0,0 @@ -# This file was generated by Appraisal - -source "https://rubygems.org" - -gem "administrate-field-image" -gem "faker" -gem "front_matter_parser" -gem "globalid" -gem "kaminari-i18n" -gem "pg", "0.21.0" -gem "pundit" -gem "redcarpet" -gem "sentry-raven" -gem "unicorn" -gem "actionpack", "~> 5.1.0" -gem "actionview", "~> 5.1.0" -gem "activerecord", "~> 5.1.0" - -group :development, :test do - gem "appraisal" - gem "awesome_print" - gem "bundler-audit", require: false - gem "byebug" - gem "dotenv-rails" - gem "factory_bot_rails" - gem "i18n-tasks", "0.9.37" - gem "pry-rails" - gem "yard" - gem "rspec-rails" -end - -group :test do - gem "ammeter" - gem "capybara" - gem "database_cleaner" - gem "formulaic" - gem "launchy" - gem "selenium-webdriver" - gem "shoulda-matchers" - gem "timecop" - gem "webdrivers" - gem "webmock" - gem "webrick" - gem "xpath", "3.2.0" -end - -group :staging, :production do - gem "rack-timeout" - gem "uglifier" -end - -gemspec path: "../" diff --git a/gemfiles/rails52.gemfile b/gemfiles/rails52.gemfile deleted file mode 100644 index 18f74820ed..0000000000 --- a/gemfiles/rails52.gemfile +++ /dev/null @@ -1,52 +0,0 @@ -# This file was generated by Appraisal - -source "https://rubygems.org" - -gem "administrate-field-image" -gem "faker" -gem "front_matter_parser" -gem "globalid" -gem "kaminari-i18n" -gem "pg" -gem "pundit" -gem "redcarpet" -gem "sentry-raven" -gem "unicorn" -gem "actionpack", "~> 5.2.0" -gem "actionview", "~> 5.2.0" -gem "activerecord", "~> 5.2.0" - -group :development, :test do - gem "appraisal" - gem "awesome_print" - gem "bundler-audit", require: false - gem "byebug" - gem "dotenv-rails" - gem "factory_bot_rails" - gem "i18n-tasks", "0.9.37" - gem "pry-rails" - gem "yard" - gem "rspec-rails" -end - -group :test do - gem "ammeter" - gem "capybara" - gem "database_cleaner" - gem "formulaic" - gem "launchy" - gem "selenium-webdriver" - gem "shoulda-matchers" - gem "timecop" - gem "webdrivers" - gem "webmock" - gem "webrick" - gem "xpath", "3.2.0" -end - -group :staging, :production do - gem "rack-timeout" - gem "uglifier" -end - -gemspec path: "../" diff --git a/gemfiles/sass_3_4.gemfile b/gemfiles/sass_3_4.gemfile deleted file mode 100644 index 0c0e1500b1..0000000000 --- a/gemfiles/sass_3_4.gemfile +++ /dev/null @@ -1,47 +0,0 @@ -# This file was generated by Appraisal - -source "https://rubygems.org" - -gem "administrate-field-image" -gem "autoprefixer-rails" -gem "faker" -gem "globalid" -gem "pg" -gem "redcarpet" -gem "sentry-raven" -gem "unicorn" -gem "sass", "~> 3.4" - -group :development, :test do - gem "appraisal" - gem "awesome_print" - gem "bundler-audit", require: false - gem "byebug" - gem "dotenv-rails" - gem "factory_bot_rails" - gem "i18n-tasks", "0.9.29" - gem "pry-rails" - gem "rspec-rails" -end - -group :test do - gem "ammeter" - gem "capybara", "3.17.0" - gem "database_cleaner" - gem "formulaic" - gem "launchy" - gem "poltergeist" - gem "pundit" - gem "shoulda-matchers" - gem "timecop" - gem "webmock" - gem "xpath", "3.2.0" -end - -group :staging, :production do - gem "rack-timeout" - gem "rails_stdout_logging" - gem "uglifier" -end - -gemspec path: "../" diff --git a/spec/example_app/config/application.rb b/spec/example_app/config/application.rb index fdb72bc28d..82bbe4f8dd 100644 --- a/spec/example_app/config/application.rb +++ b/spec/example_app/config/application.rb @@ -25,15 +25,7 @@ class Application < Rails::Application end config.action_controller.action_on_unpermitted_parameters = :raise - - if Rails::VERSION::MAJOR < 5 - # Do not swallow errors in after_commit/after_rollback callbacks. - config.active_record.raise_in_transactional_callbacks = true - end - - if Rails::VERSION::MAJOR >= 5 - config.active_record.time_zone_aware_types = %i(datetime time) - end + config.active_record.time_zone_aware_types = %i(datetime time) # Opt-out of FLoC: https://amifloced.org/ config.action_dispatch. diff --git a/spec/example_app/config/initializers/disable_xml_params.rb b/spec/example_app/config/initializers/disable_xml_params.rb deleted file mode 100644 index b088567f25..0000000000 --- a/spec/example_app/config/initializers/disable_xml_params.rb +++ /dev/null @@ -1,5 +0,0 @@ -# Protect against injection attacks -# http://www.kb.cert.org/vuls/id/380039 -if Rails::VERSION::MAJOR < 5 - ActionDispatch::ParamsParser::DEFAULT_PARSERS.delete(Mime::XML) -end From 51aeed4f6fd46b815722677eb3ad0c69af234c6c Mon Sep 17 00:00:00 2001 From: Pablo Brasero <36066+pablobm@users.noreply.github.com> Date: Mon, 8 Aug 2022 11:58:01 +0100 Subject: [PATCH 007/137] Allow overriding the sample app database config (#2181) --- .circleci/config.yml | 1 + .gitignore | 1 + bin/setup | 10 ++++++++++ .../config/{database.yml => database.yml.sample} | 0 4 files changed, 12 insertions(+) rename spec/example_app/config/{database.yml => database.yml.sample} (100%) diff --git a/.circleci/config.yml b/.circleci/config.yml index fe1271786a..b7051910c4 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -35,6 +35,7 @@ commands: # Setup the environment - run: cp .sample.env .env + - run: cp spec/example_app/config/database.yml.sample spec/example_app/config/database.yml # Setup the database - run: bundle exec rake db:setup diff --git a/.gitignore b/.gitignore index 0ced8f286b..0abba42a0c 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,7 @@ !/spec/example_app/tmp/.keep /spec/example_app/public/system /spec/example_app/public/assets +/spec/example_app/config/database.yml /tags pkg *.ipr diff --git a/bin/setup b/bin/setup index bac8ad9741..80e61dc218 100755 --- a/bin/setup +++ b/bin/setup @@ -6,6 +6,16 @@ # Exit if any subcommand fails set -e +EXAMPLE_APP_PATH="spec/example_app" +SAMPLE_DB_CONFIG_PATH="$EXAMPLE_APP_PATH/config/database.yml.sample" +DB_CONFIG_PATH="$EXAMPLE_APP_PATH/config/database.yml" +if [ ! -e "$DB_CONFIG_PATH" ]; then + cp "$SAMPLE_DB_CONFIG_PATH" "$DB_CONFIG_PATH" + echo "A new database config file was created at $DB_CONFIG_PATH." + echo "Please edit it to match your DB settings and then run this script again." + exit 0 +fi + # Set up Ruby dependencies via Bundler gem install bundler --conservative bundle check || bundle install diff --git a/spec/example_app/config/database.yml b/spec/example_app/config/database.yml.sample similarity index 100% rename from spec/example_app/config/database.yml rename to spec/example_app/config/database.yml.sample From da966e06ec9db2daff271080d5c9ba18a759b0c6 Mon Sep 17 00:00:00 2001 From: Nick Charlton Date: Mon, 8 Aug 2022 12:06:06 +0100 Subject: [PATCH 008/137] Bump i18n-tasks in Appraisal gemfiles --- gemfiles/rails60.gemfile | 2 +- gemfiles/rails61.gemfile | 2 +- gemfiles/rails70.gemfile | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gemfiles/rails60.gemfile b/gemfiles/rails60.gemfile index d2ad48fe2f..1efa3eca7d 100644 --- a/gemfiles/rails60.gemfile +++ b/gemfiles/rails60.gemfile @@ -21,7 +21,7 @@ group :development, :test do gem "byebug" gem "dotenv-rails" gem "factory_bot_rails" - gem "i18n-tasks", "0.9.37" + gem "i18n-tasks", "1.0.11" gem "pry-rails" gem "yard" end diff --git a/gemfiles/rails61.gemfile b/gemfiles/rails61.gemfile index 85c3bf63b7..2c17bfa0dc 100644 --- a/gemfiles/rails61.gemfile +++ b/gemfiles/rails61.gemfile @@ -21,7 +21,7 @@ group :development, :test do gem "byebug" gem "dotenv-rails" gem "factory_bot_rails" - gem "i18n-tasks", "0.9.37" + gem "i18n-tasks", "1.0.11" gem "pry-rails" gem "yard" end diff --git a/gemfiles/rails70.gemfile b/gemfiles/rails70.gemfile index 85c3bf63b7..2c17bfa0dc 100644 --- a/gemfiles/rails70.gemfile +++ b/gemfiles/rails70.gemfile @@ -21,7 +21,7 @@ group :development, :test do gem "byebug" gem "dotenv-rails" gem "factory_bot_rails" - gem "i18n-tasks", "0.9.37" + gem "i18n-tasks", "1.0.11" gem "pry-rails" gem "yard" end From f10b5567916703a2ac242c2e9808cfafcfc7fbff Mon Sep 17 00:00:00 2001 From: Pablo Brasero <36066+pablobm@users.noreply.github.com> Date: Mon, 8 Aug 2022 12:46:35 +0100 Subject: [PATCH 009/137] Unify Action Checks (#1941) When referring to a route in the code, we run two checks: * `valid_action?` is `true` if the route is defined, `false` otherwise. * `show_action?` is expected to be overridden by developers in order to implement authorization. For example, it's implemented by `Administrate::Punditize` in order to integrate Administrate with Pundit. It should return `true` if the current user can access a given route, `false` otherwise. These two check should (almost) always happen together. For this reason, our code is peppered with `if` statements where both are checked... and a few others where we forget one or the other. These checks should be unified into a single method call, in order to avoid issues like the one described at #1861. This introduces a new method, called `accessible_action?`. The original methods should still exist, as they do have their uses individually. The new method will delegate to the existing ones. We also rename the two existing methods to something that will make their intent clear: * `valid_action?` becomes `existing_action?` * `show_action?` becomes `authorized_action?` In order to provide a clear upgrade path, the old names still exist and work, but they show a deprecation warning when used. They can be removed properly at a later version of Administrate. --- .../administrate/application_controller.rb | 51 ++++- .../concerns/administrate/punditize.rb | 4 +- .../administrate/application_helper.rb | 22 +++ .../application/_collection.html.erb | 4 +- .../_collection_header_actions.html.erb | 4 +- .../_collection_item_actions.html.erb | 8 +- .../application/_index_header.html.erb | 2 +- .../application/_navigation.html.erb | 2 +- .../administrate/application/edit.html.erb | 2 +- .../administrate/application/show.html.erb | 4 +- app/views/fields/belongs_to/_index.html.erb | 2 +- app/views/fields/belongs_to/_show.html.erb | 2 +- app/views/fields/has_one/_index.html.erb | 3 +- app/views/fields/has_one/_show.html.erb | 3 +- app/views/fields/polymorphic/_index.html.erb | 3 +- app/views/fields/polymorphic/_show.html.erb | 2 +- docs/authorization.md | 37 ++-- docs/customizing_controller_actions.md | 17 +- lib/administrate.rb | 8 + lib/administrate/engine.rb | 2 + lib/administrate/not_authorized_error.rb | 18 ++ .../views/fields/belongs_to/_index_spec.rb | 23 ++- .../views/fields/belongs_to/_show_spec.rb | 8 +- .../views/fields/has_one/_index_spec.rb | 74 ++++--- .../views/fields/has_one/_show_spec.rb | 186 +++++++++++------- .../views/fields/polymorphic/_index_spec.rb | 67 +++++-- .../views/fields/polymorphic/_show_spec.rb | 2 +- .../admin/application_controller_spec.rb | 115 ++++++++--- .../admin/orders_controller_spec.rb | 8 +- .../_collection_header_actions.html.erb | 4 +- .../_collection_item_actions.html.erb | 8 +- .../admin/customers/_index_header.html.erb | 3 +- .../administrate/application_helper_spec.rb | 86 ++++++++ .../administrate/not_authorized_error_spec.rb | 51 +++++ 34 files changed, 626 insertions(+), 209 deletions(-) create mode 100644 lib/administrate/not_authorized_error.rb create mode 100644 spec/lib/administrate/not_authorized_error_spec.rb diff --git a/app/controllers/administrate/application_controller.rb b/app/controllers/administrate/application_controller.rb index 63a5674b33..114060da5e 100644 --- a/app/controllers/administrate/application_controller.rb +++ b/app/controllers/administrate/application_controller.rb @@ -105,10 +105,27 @@ def nav_link_state(resource) resource_name.to_s.pluralize == underscore_resource ? :active : :inactive end - helper_method :valid_action? - def valid_action?(name, resource = resource_class) - routes.include?([resource.to_s.underscore.pluralize, name.to_s]) + # Whether the named action route exists for the resource class. + # + # @param resource [Class, String, Symbol] A class of resources, or the name + # of a class of resources. + # @param action_name [String, Symbol] The name of an action that might be + # possible to perform on a resource or resource class. + # @return [Boolean] `true` if a route exists for the resource class and the + # action. `false` otherwise. + def existing_action?(resource, action_name) + routes.include?([resource.to_s.underscore.pluralize, action_name.to_s]) + end + helper_method :existing_action? + + # @deprecated Use {#existing_action} instead. Note that, in + # {#existing_action}, the order of parameters is reversed and + # there is no default value for the `resource` parameter. + def valid_action?(action_name, resource = resource_class) + Administrate.warn_of_deprecated_authorization_method(__method__) + existing_action?(resource, action_name) end + helper_method :valid_action? def routes @routes ||= Namespace.new(namespace).routes.to_set @@ -226,9 +243,26 @@ def show_search_bar? ).any? { |_name, attribute| attribute.searchable? } end - def show_action?(_action, _resource) + # Whether the current user is authorized to perform the named action on the + # resource. + # + # @param _resource [ActiveRecord::Base, Class, String, Symbol] The + # temptative target of the action, or the name of its class. + # @param _action_name [String, Symbol] The name of an action that might be + # possible to perform on a resource or resource class. + # @return [Boolean] `true` if the current user is authorized to perform the + # action on the resource. `false` otherwise. + def authorized_action?(_resource, _action_name) true end + helper_method :authorized_action? + + # @deprecated Use {#authorized_action} instead. Note that the order of + # parameters is reversed in {#authorized_action}. + def show_action?(action, resource) + Administrate.warn_of_deprecated_authorization_method(__method__) + authorized_action?(resource, action) + end helper_method :show_action? def new_resource @@ -237,7 +271,14 @@ def new_resource helper_method :new_resource def authorize_resource(resource) - resource + if authorized_action?(resource, action_name) + resource + else + raise Administrate::NotAuthorizedError.new( + action: action_name, + resource: resource, + ) + end end def paginate_resources(resources) diff --git a/app/controllers/concerns/administrate/punditize.rb b/app/controllers/concerns/administrate/punditize.rb index d49de39468..f655cc16c0 100644 --- a/app/controllers/concerns/administrate/punditize.rb +++ b/app/controllers/concerns/administrate/punditize.rb @@ -5,6 +5,8 @@ module Punditize include Pundit::Authorization included do + private + def scoped_resource policy_scope_admin super end @@ -13,7 +15,7 @@ def authorize_resource(resource) authorize resource end - def show_action?(action, resource) + def authorized_action?(resource, action) Pundit.policy!(pundit_user, resource).send("#{action}?".to_sym) end end diff --git a/app/helpers/administrate/application_helper.rb b/app/helpers/administrate/application_helper.rb index efc9378c8d..b314abd898 100644 --- a/app/helpers/administrate/application_helper.rb +++ b/app/helpers/administrate/application_helper.rb @@ -25,6 +25,28 @@ def model_from_resource(resource_name) dashboard.try(:model) || resource_name.to_sym end + # Unification of + # {Administrate::ApplicationController#existing_action? existing_action?} + # and + # {Administrate::ApplicationController#authorized_action? + # authorized_action?} + # + # @param target [ActiveRecord::Base, Class, Symbol, String] A resource, + # a class of resources, or the name of a class of resources. + # @param action_name [String, Symbol] The name of an action that might be + # possible to perform on a resource or resource class. + # @return [Boolean] Whether the action both (a) exists for the record class, + # and (b) the current user is authorized to perform it on the record + # instance or class. + def accessible_action?(target, action_name) + target = target.to_sym if target.is_a?(String) + target_class_or_class_name = + target.is_a?(ActiveRecord::Base) ? target.class : target + + existing_action?(target_class_or_class_name, action_name) && + authorized_action?(target, action_name) + end + def display_resource_name(resource_name, opts = {}) dashboard_from_resource(resource_name).resource_name( count: opts[:singular] ? SINGULAR_COUNT : PLURAL_MANY_COUNT, diff --git a/app/views/administrate/application/_collection.html.erb b/app/views/administrate/application/_collection.html.erb index 96b44bdfd8..044a53efdd 100644 --- a/app/views/administrate/application/_collection.html.erb +++ b/app/views/administrate/application/_collection.html.erb @@ -59,13 +59,13 @@ to display a collection of resources in an HTML table. <% resources.each do |resource| %> + <% if accessible_action?(resource, :show) %> <%= %(tabindex=0 role=link data-url=#{polymorphic_path([namespace, resource])}) %> <% end %> > <% collection_presenter.attributes_for(resource).each do |attribute| %> - <% if valid_action?(:show, resource.class) && show_action?(:show, resource) -%> + <% if accessible_action?(resource, :show) -%> +<% [existing_action?(collection_presenter.resource_name, :edit), + existing_action?(collection_presenter.resource_name, :destroy)].count(true).times do %> <% end %> diff --git a/app/views/administrate/application/_collection_item_actions.html.erb b/app/views/administrate/application/_collection_item_actions.html.erb index 381716c4ec..3bddff6c0f 100644 --- a/app/views/administrate/application/_collection_item_actions.html.erb +++ b/app/views/administrate/application/_collection_item_actions.html.erb @@ -1,17 +1,17 @@ -<% if valid_action?(:edit, collection_presenter.resource_name) %> +<% if existing_action?(collection_presenter.resource_name, :edit) %> <%= link_to( t("administrate.actions.edit"), [:edit, namespace, resource], class: "action-edit", - ) if show_action?(:edit, resource) %> + ) if accessible_action?(resource, :edit) %> <% end %> -<% if valid_action?(:destroy, collection_presenter.resource_name) %> +<% if existing_action?(collection_presenter.resource_name, :destroy) %> <%= link_to( t("administrate.actions.destroy"), [namespace, resource], class: "text-color-red", method: :delete, data: { confirm: t("administrate.actions.confirm") } - ) if show_action?(:destroy, resource) %> + ) if accessible_action?(resource, :destroy) %> <% end %> diff --git a/app/views/administrate/application/_index_header.html.erb b/app/views/administrate/application/_index_header.html.erb index 03a2fc9c79..ad069cac3a 100644 --- a/app/views/administrate/application/_index_header.html.erb +++ b/app/views/administrate/application/_index_header.html.erb @@ -23,6 +23,6 @@ ), [:new, namespace, page.resource_path.to_sym], class: "button", - ) if valid_action?(:new) && show_action?(:new, new_resource) %> + ) if accessible_action?(new_resource, :new) %> diff --git a/app/views/administrate/application/_navigation.html.erb b/app/views/administrate/application/_navigation.html.erb index 670aab66eb..a0f26641c1 100644 --- a/app/views/administrate/application/_navigation.html.erb +++ b/app/views/administrate/application/_navigation.html.erb @@ -15,6 +15,6 @@ as defined by the routes in the `admin/` namespace display_resource_name(resource), resource_index_route(resource), class: "navigation__link navigation__link--#{nav_link_state(resource)}" - ) if valid_action?(:index, resource) && show_action?(:index, model_from_resource(resource)) %> + ) if accessible_action?(model_from_resource(resource), :index) %> <% end %> diff --git a/app/views/administrate/application/edit.html.erb b/app/views/administrate/application/edit.html.erb index abacffce42..415de8a5c8 100644 --- a/app/views/administrate/application/edit.html.erb +++ b/app/views/administrate/application/edit.html.erb @@ -27,7 +27,7 @@ It displays a header, and renders the `_form` partial to do the heavy lifting. t("administrate.actions.show_resource", name: page.page_title), [namespace, page.resource], class: "button", - ) if valid_action?(:show) && show_action?(:show, page.resource) %> + ) if accessible_action?(page.resource, :show) %> diff --git a/app/views/administrate/application/show.html.erb b/app/views/administrate/application/show.html.erb index 41e5b787b3..71b4638ff7 100644 --- a/app/views/administrate/application/show.html.erb +++ b/app/views/administrate/application/show.html.erb @@ -28,7 +28,7 @@ as well as a link to its edit page. t("administrate.actions.edit_resource", name: page.page_title), [:edit, namespace, page.resource], class: "button", - ) if valid_action?(:edit) && show_action?(:edit, page.resource) %> + ) if accessible_action?(page.resource, :edit) %> <%= link_to( t("administrate.actions.destroy"), @@ -36,7 +36,7 @@ as well as a link to its edit page. class: "button button--danger", method: :delete, data: { confirm: t("administrate.actions.confirm") } - ) if valid_action?(:destroy) && show_action?(:destroy, page.resource) %> + ) if accessible_action?(page.resource, :destroy) %> diff --git a/app/views/fields/belongs_to/_index.html.erb b/app/views/fields/belongs_to/_index.html.erb index 0dece5cc4f..1e0ab2f4c9 100644 --- a/app/views/fields/belongs_to/_index.html.erb +++ b/app/views/fields/belongs_to/_index.html.erb @@ -16,7 +16,7 @@ By default, the relationship is rendered as a link to the associated object. %> <% if field.data %> - <% if valid_action?(:show, field.associated_class) && show_action?(:show, field.associated_class) %> + <% if accessible_action?(field.data, :show) %> <%= link_to( field.display_associated_resource, [namespace, field.data], diff --git a/app/views/fields/belongs_to/_show.html.erb b/app/views/fields/belongs_to/_show.html.erb index 15e2d36c2c..3bca714d68 100644 --- a/app/views/fields/belongs_to/_show.html.erb +++ b/app/views/fields/belongs_to/_show.html.erb @@ -16,7 +16,7 @@ By default, the relationship is rendered as a link to the associated object. %> <% if field.data %> - <% if valid_action?(:show, field.associated_class) && show_action?(:show, field.associated_class) %> + <% if accessible_action?(field.data, :show) %> <%= link_to( field.display_associated_resource, [namespace, field.data], diff --git a/app/views/fields/has_one/_index.html.erb b/app/views/fields/has_one/_index.html.erb index e6fc01a44f..4ff3ad1747 100644 --- a/app/views/fields/has_one/_index.html.erb +++ b/app/views/fields/has_one/_index.html.erb @@ -16,7 +16,8 @@ By default, the relationship is rendered as a link to the associated object. %> <% if field.linkable? %> - <%= link_to( + <%= link_to_if( + accessible_action?(field.data, :show), field.display_associated_resource, [namespace, field.data], ) %> diff --git a/app/views/fields/has_one/_show.html.erb b/app/views/fields/has_one/_show.html.erb index a4b3dc5fe8..542af5e09d 100644 --- a/app/views/fields/has_one/_show.html.erb +++ b/app/views/fields/has_one/_show.html.erb @@ -18,7 +18,8 @@ All show page attributes of has_one relationship would be rendered <% if field.linkable? %>
- <%= link_to( + <%= link_to_if( + accessible_action?(field.data, :show), field.display_associated_resource, [namespace, field.data], ) %> diff --git a/app/views/fields/polymorphic/_index.html.erb b/app/views/fields/polymorphic/_index.html.erb index 7c27a3b379..e9608f461f 100644 --- a/app/views/fields/polymorphic/_index.html.erb +++ b/app/views/fields/polymorphic/_index.html.erb @@ -17,7 +17,8 @@ By default, the relationship is rendered as a link to the associated object. %> <% if field.data %> - <%= link_to( + <%= link_to_if( + accessible_action?(field.data, :show), field.display_associated_resource, [namespace, field.data] ) %> diff --git a/app/views/fields/polymorphic/_show.html.erb b/app/views/fields/polymorphic/_show.html.erb index 27a5645aee..a656d2925e 100644 --- a/app/views/fields/polymorphic/_show.html.erb +++ b/app/views/fields/polymorphic/_show.html.erb @@ -17,7 +17,7 @@ By default, the relationship is rendered as a link to the associated object. %> <% if field.data %> - <% if valid_action?(:show, field.data.class) %> + <% if accessible_action?(field.data, :show) %> <%= link_to( field.display_associated_resource, [namespace, field.data], diff --git a/docs/authorization.md b/docs/authorization.md index 8e84875f0a..247199800b 100644 --- a/docs/authorization.md +++ b/docs/authorization.md @@ -49,23 +49,36 @@ end ## Authorization without Pundit -If you use a different authorization library, or you want to roll your own, -you just need to override a few methods in your controllers or -`Admin::ApplicationController`. For example: +Pundit is not necessary to implement authorization within Administrate. It is +simply a common solution that many in the community use, and for this reason +Administrate provides a plugin to work with it. However you can use a different +solution or roll out your own. + +To integrate a different authorization solution, you will need to +implement some methods in `Admin::ApplicationController` +or its subclasses. + +These are the methods to override, with examples: ```ruby -# Limit the scope of the given resource +# Used in listings, such as the `index` actions. It +# restricts the scope of records that a user can access. +# Returns an ActiveRecord scope. def scoped_resource super.where(user: current_user) end -# Raise an exception if the user is not permitted to access this resource -def authorize_resource(resource) - raise "Erg!" unless show_action?(params[:action], resource) -end - -# Hide links to actions if the user is not allowed to do them -def show_action?(action, resource) - current_user.can? action, resource +# Return true if the current user can access the given +# resource, false otherwise. +def authorized_action?(resource, action) + current_user.can?(resource, action) end ``` + +Additionally, the method `authorize_resource(resource)` +should throw an exception if the current user is not +allowed to access the given resource. Normally +you wouldn't need to override it, as the default +implementation uses `authorized_action?` to produce the +correct behaviour. However you may still want to override it +if you want to raise a custom error type. diff --git a/docs/customizing_controller_actions.md b/docs/customizing_controller_actions.md index 545bd91ad8..f9fedad26e 100644 --- a/docs/customizing_controller_actions.md +++ b/docs/customizing_controller_actions.md @@ -46,17 +46,22 @@ end ## Customizing Actions -To enable or disable certain actions you could override `valid_action?` method in your dashboard controller like this: +To disable certain actions globally, you can disable their +routes in `config/routes.rb`, using the usual Rails +facilities for this. For example: ```ruby -# disable 'edit' and 'destroy' links -def valid_action?(name, resource = resource_class) - %w[edit destroy].exclude?(name.to_s) && super +Rails.application.routes.draw do + # ... + namespace :admin do + # ... + + # Payments can only be listed or displayed + resources :payments, only: [:index, :show] + end end ``` -Action is one of `new`, `edit`, `show`, `destroy`. - ## Customizing Default Sorting To set the default sorting on the index action you could override `default_sorting_attribute` or `default_sorting_direction` in your dashboard controller like this: diff --git a/lib/administrate.rb b/lib/administrate.rb index 3dff5bd624..0a3c94ebb2 100644 --- a/lib/administrate.rb +++ b/lib/administrate.rb @@ -30,4 +30,12 @@ def self.warn_of_deprecated_method(klass, method) "does not use a deprecated API", ) end + + def self.warn_of_deprecated_authorization_method(method) + ActiveSupport::Deprecation.warn( + "The method `#{method}` is deprecated. " + + "Please use `accessible_action?` instead, " + + "or see the documentation for other options.", + ) + end end diff --git a/lib/administrate/engine.rb b/lib/administrate/engine.rb index 3961cf4f62..55a179758b 100644 --- a/lib/administrate/engine.rb +++ b/lib/administrate/engine.rb @@ -4,6 +4,8 @@ require "selectize-rails" require "sprockets/railtie" +require "administrate/namespace/resource" +require "administrate/not_authorized_error" require "administrate/page/form" require "administrate/page/show" require "administrate/page/collection" diff --git a/lib/administrate/not_authorized_error.rb b/lib/administrate/not_authorized_error.rb new file mode 100644 index 0000000000..bc65ab0705 --- /dev/null +++ b/lib/administrate/not_authorized_error.rb @@ -0,0 +1,18 @@ +module Administrate + class NotAuthorizedError < StandardError + def initialize(action:, resource:) + @action = action + @resource = resource + + case resource + when Module, String, Symbol + super("Not allowed to perform #{action.inspect} on #{resource.inspect}") + else + super( + "Not allowed to perform #{action.inspect} on the given " + + resource.class.name + ) + end + end + end +end diff --git a/spec/administrate/views/fields/belongs_to/_index_spec.rb b/spec/administrate/views/fields/belongs_to/_index_spec.rb index b3ea2cbbfb..5ea69f7c61 100644 --- a/spec/administrate/views/fields/belongs_to/_index_spec.rb +++ b/spec/administrate/views/fields/belongs_to/_index_spec.rb @@ -14,11 +14,25 @@ ) end + context "without an associated record" do + let(:belongs_to) do + instance_double( + "Administrate::Field::BelongsTo", + associated_class: associated_class, + data: nil, + ) + end + + it "displays nothing" do + render_belongs_to_index + expect(rendered.strip).to eq("") + end + end + context "if associated resource has a show route" do context "and the user has permission to access it" do it "displays link" do - allow(view).to receive(:valid_action?).and_return(true) - allow(view).to receive(:show_action?).and_return(true) + allow(view).to receive(:accessible_action?).and_return(true) render_belongs_to_index expect(rendered.strip).to include(link) end @@ -26,8 +40,7 @@ context "and the user does not have permission to access it" do it "hides link" do - allow(view).to receive(:valid_action?).and_return(true) - allow(view).to receive(:show_action?).and_return(false) + allow(view).to receive(:accessible_action?).and_return(false) render_belongs_to_index expect(rendered.strip).to_not include(link) end @@ -36,7 +49,7 @@ context "if associated resource has no show route" do it "hides link" do - allow(view).to receive(:valid_action?).and_return(false) + allow(view).to receive(:accessible_action?).and_return(false) render_belongs_to_index expect(rendered.strip).to_not include(link) end diff --git a/spec/administrate/views/fields/belongs_to/_show_spec.rb b/spec/administrate/views/fields/belongs_to/_show_spec.rb index b4d14bc917..f61a1ae416 100644 --- a/spec/administrate/views/fields/belongs_to/_show_spec.rb +++ b/spec/administrate/views/fields/belongs_to/_show_spec.rb @@ -17,8 +17,7 @@ context "if associated resource has a show route" do context "and the user has permission to access it" do it "displays link" do - allow(view).to receive(:valid_action?).and_return(true) - allow(view).to receive(:show_action?).and_return(true) + allow(view).to receive(:accessible_action?).and_return(true) render_belongs_to_show expect(rendered.strip).to include(link) end @@ -26,8 +25,7 @@ context "and the user does not have permission to access it" do it "hides link" do - allow(view).to receive(:valid_action?).and_return(true) - allow(view).to receive(:show_action?).and_return(false) + allow(view).to receive(:accessible_action?).and_return(false) render_belongs_to_show expect(rendered.strip).to_not include(link) end @@ -36,7 +34,7 @@ context "if associated resource has no show route" do it "hides link" do - allow(view).to receive(:valid_action?).and_return(false) + allow(view).to receive(:accessible_action?).and_return(false) render_belongs_to_show expect(rendered.strip).to_not include(link) end diff --git a/spec/administrate/views/fields/has_one/_index_spec.rb b/spec/administrate/views/fields/has_one/_index_spec.rb index 56a234ee9e..6093d745b2 100644 --- a/spec/administrate/views/fields/has_one/_index_spec.rb +++ b/spec/administrate/views/fields/has_one/_index_spec.rb @@ -1,41 +1,63 @@ require "rails_helper" describe "fields/has_one/_index", type: :view do - context "without an associated record" do - it "displays nothing" do - has_one = Administrate::Field::HasOne.new( - :product_meta_tag, - build(:product_meta_tag), - :index, - ) + let(:product) { create(:product) } + let(:product_path) { polymorphic_path([:admin, product]) } + let(:link) { "#{product.name}" } + let(:has_one) do + instance_double( + "Administrate::Field::HasOne", + data: product, + linkable?: true, + display_associated_resource: product.name, + ) + end - render( - partial: "fields/has_one/index", - locals: { field: has_one }, + context "without an associated record" do + let(:has_one) do + instance_double( + "Administrate::Field::HasOne", + linkable?: false, ) + end + it "displays nothing" do + allow(view).to receive(:accessible_action?).and_return(true) + render_has_one_index expect(rendered.strip).to eq("") end end - context "with an associated record" do - it "renders a link to the record" do - product = create(:product) - product_path = polymorphic_path([:admin, product]) - has_one = instance_double( - "Administrate::Field::HasOne", - data: product, - linkable?: true, - display_associated_resource: product.name, - ) + context "if associated resource has a show route" do + context "and the user has permission to access it" do + it "displays link" do + allow(view).to receive(:accessible_action?).and_return(true) + render_has_one_index + expect(rendered.strip).to include(link) + end + end - render( - partial: "fields/has_one/index", - locals: { field: has_one, namespace: :admin }, - ) + context "and the user does not have permission to access it" do + it "hides link" do + allow(view).to receive(:accessible_action?).and_return(false) + render_has_one_index + expect(rendered.strip).to_not include(link) + end + end + end - expected = "#{product.name}" - expect(rendered.strip).to eq(expected) + context "if associated resource has no show route" do + it "hides link" do + allow(view).to receive(:accessible_action?).and_return(false) + render_has_one_index + expect(rendered.strip).to_not include(link) end end + + def render_has_one_index + render( + partial: "fields/has_one/index", + locals: { field: has_one, namespace: :admin }, + ) + end end diff --git a/spec/administrate/views/fields/has_one/_show_spec.rb b/spec/administrate/views/fields/has_one/_show_spec.rb index 61cdcad0b2..76b7aeb986 100644 --- a/spec/administrate/views/fields/has_one/_show_spec.rb +++ b/spec/administrate/views/fields/has_one/_show_spec.rb @@ -2,6 +2,10 @@ require "administrate/field/has_one" describe "fields/has_one/_show", type: :view do + before do + allow(view).to receive(:accessible_action?).and_return(true) + end + context "without an associated record" do it "displays nothing" do has_one = Administrate::Field::HasOne.new( @@ -20,87 +24,123 @@ end context "with an associated record" do - it "renders a link to the record" do - product = create(:product) - product_path = polymorphic_path([:admin, product]) - nested_show = instance_double( - "Administrate::Page::Show", - resource: double( - class: ProductMetaTag, - ), - attributes: [], - resource_name: "Product Tag", - ) - has_one = instance_double( - "Administrate::Field::HasOne", - display_associated_resource: product.name, - data: product, - linkable?: true, - nested_show: nested_show, - ) + context "when linking the record is allowed" do + it "renders a link to the record" do + product = create(:product) + product_path = polymorphic_path([:admin, product]) + nested_show = instance_double( + "Administrate::Page::Show", + resource: double( + class: ProductMetaTag, + ), + attributes: [], + resource_name: "Product Tag", + ) + has_one = instance_double( + "Administrate::Field::HasOne", + display_associated_resource: product.name, + data: product, + linkable?: true, + nested_show: nested_show, + ) - render( - partial: "fields/has_one/show", - locals: { - field: has_one, - namespace: :admin, - resource_name: "product_meta_tag", - }, - ) + render( + partial: "fields/has_one/show", + locals: { + field: has_one, + namespace: :admin, + resource_name: "product_meta_tag", + }, + ) - link = "#{product.name}" - expect(rendered.strip).to include(link) - end + link = "#{product.name}" + expect(rendered.strip).to include(link) + end - it "renders nested attribute relationships" do - view.extend Administrate::ApplicationHelper - - product = create(:product) - page = create(:page, product: product) - - nested_has_many = instance_double( - "Administrate::Field::HasMany", - associated_collection: [page], - attribute: :page, - data: [page], - resources: [page], - html_class: "has-many", - name: "Page", - to_partial_path: "fields/has_many/index", - order_from_params: {}, - ) + it "renders nested attribute relationships" do + view.extend Administrate::ApplicationHelper - nested_show = instance_double( - "Administrate::Page::Show", - resource: double( - class: ProductMetaTag, - ), - attributes: [nested_has_many], - resource_name: "Product Tag", - ) + product = create(:product) + page = create(:page, product: product) - has_one = instance_double( - "Administrate::Field::HasOne", - display_associated_resource: product.name, - data: product, - linkable?: true, - nested_show: nested_show, - ) + nested_has_many = instance_double( + "Administrate::Field::HasMany", + associated_collection: [page], + attribute: :page, + data: [page], + resources: [page], + html_class: "has-many", + name: "Page", + to_partial_path: "fields/has_many/index", + order_from_params: {}, + ) - page_double = instance_double("Administrate::Page::Show") + nested_show = instance_double( + "Administrate::Page::Show", + resource: double( + class: ProductMetaTag, + ), + attributes: [nested_has_many], + resource_name: "Product Tag", + ) - render( - partial: "fields/has_one/show", - locals: { - field: has_one, - namespace: :admin, - page: page_double, - resource_name: "product_meta_tag", - }, - ) + has_one = instance_double( + "Administrate::Field::HasOne", + display_associated_resource: product.name, + data: product, + linkable?: true, + nested_show: nested_show, + ) + + page_double = instance_double("Administrate::Page::Show") + + render( + partial: "fields/has_one/show", + locals: { + field: has_one, + namespace: :admin, + page: page_double, + resource_name: "product_meta_tag", + }, + ) + + has_many_count = "1 page" + expect(rendered.strip).to include(has_many_count) + end + end + + context "when linking the record is not allowed" do + it "displays the record without a link" do + allow(view).to receive(:accessible_action?).and_return(false) + product = create(:product) + nested_show = instance_double( + "Administrate::Page::Show", + resource: double( + class: ProductMetaTag, + ), + attributes: [], + resource_name: "Product Tag", + ) + has_one = instance_double( + "Administrate::Field::HasOne", + display_associated_resource: product.name, + data: product, + linkable?: true, + nested_show: nested_show, + ) + + render( + partial: "fields/has_one/show", + locals: { + field: has_one, + namespace: :admin, + resource_name: "product_meta_tag", + }, + ) - has_many_count = "1 page" - expect(rendered.strip).to include(has_many_count) + expect(rendered.strip).to include(product.name) + expect(rendered.strip).not_to include("#{product.name}" } + let(:polymorphic) do + instance_double( + "Administrate::Field::Polymorphic", + data: product, + display_associated_resource: product.name, + ) + end - render( - partial: "fields/polymorphic/index", - locals: { field: polymorphic }, + context "without an associated record" do + let(:polymorphic) do + instance_double( + "Administrate::Field::Polymorphic", + data: nil, ) + end + it "displays nothing" do + render_polymorphic_index expect(rendered.strip).to eq("") end end - context "with an associated record" do - it "renders a link to the record" do - product = create(:product) - product_path = polymorphic_path([:admin, product]) - polymorphic = instance_double( - "Administrate::Field::Polymorphic", - data: product, - display_associated_resource: product.name, - ) + context "if associated resource has a show route" do + context "and the user has permission to access it" do + it "displays link" do + allow(view).to receive(:accessible_action?).and_return(true) + render_polymorphic_index + expect(rendered.strip).to include(link) + end + end - render( - partial: "fields/polymorphic/index", - locals: { field: polymorphic, namespace: :admin }, - ) + context "and the user does not have permission to access it" do + it "hides link" do + allow(view).to receive(:accessible_action?).and_return(false) + render_polymorphic_index + expect(rendered.strip).to_not include(link) + end + end + end - expected = "#{product.name}" - expect(rendered.strip).to eq(expected) + context "if associated resource has no show route" do + it "hides link" do + allow(view).to receive(:accessible_action?).and_return(false) + render_polymorphic_index + expect(rendered.strip).to_not include(link) end end + + def render_polymorphic_index + render( + partial: "fields/polymorphic/index", + locals: { field: polymorphic, namespace: :admin }, + ) + end end diff --git a/spec/administrate/views/fields/polymorphic/_show_spec.rb b/spec/administrate/views/fields/polymorphic/_show_spec.rb index 1b143a7c97..9e19f2d480 100644 --- a/spec/administrate/views/fields/polymorphic/_show_spec.rb +++ b/spec/administrate/views/fields/polymorphic/_show_spec.rb @@ -31,7 +31,7 @@ attribute: "product", ) - allow(view).to receive(:valid_action?).and_return(true) + allow(view).to receive(:accessible_action?).and_return(true) render( partial: "fields/polymorphic/show", diff --git a/spec/controllers/admin/application_controller_spec.rb b/spec/controllers/admin/application_controller_spec.rb index 8e6e20c5a1..4e87b88df9 100644 --- a/spec/controllers/admin/application_controller_spec.rb +++ b/spec/controllers/admin/application_controller_spec.rb @@ -1,41 +1,110 @@ require "rails_helper" -RSpec.describe Admin::OrdersController, type: :controller do - controller(Admin::OrdersController) do - def after_resource_destroyed_path(_requested_resource) - { action: :index, controller: :customers } +RSpec.describe Admin::ApplicationController, type: :controller do + describe "redirections after actions" do + controller(Admin::OrdersController) do + def after_resource_destroyed_path(_requested_resource) + { action: :index, controller: :customers } + end + + def after_resource_created_path(requested_resource) + [namespace, requested_resource.customer] + end + + def after_resource_updated_path(requested_resource) + [namespace, requested_resource.customer] + end end - def after_resource_created_path(requested_resource) - [namespace, requested_resource.customer] + it "redirect to custom route after destroy" do + order = create(:order) + + delete :destroy, params: { id: order.to_param } + expect(response).to redirect_to(admin_customers_path) end - def after_resource_updated_path(requested_resource) - [namespace, requested_resource.customer] + it "redirect to custom route after create" do + customer = create(:customer) + order_attributes = build(:order, customer: customer).attributes + params = order_attributes.except( + "id", + "created_at", + "updated_at", + "shipped_at", + ) + + post :create, params: { order: params } + expect(response).to redirect_to(admin_customer_path(customer)) + end + + it "redirect to custom route after update" do + order = create(:order) + order_params = { address_line_one: order.address_line_one } + + put :update, params: { id: order.to_param, order: order_params } + expect(response).to redirect_to(admin_customer_path(order.customer)) end end - it "redirect to custom route after destroy" do - order = create(:order) + describe "authorization" do + controller(Administrate::ApplicationController) do + def resource_class + Order + end - delete :destroy, params: { id: order.to_param } - expect(response).to redirect_to(admin_customers_path) + def dashboard + OrderDashboard + end + + def authorized_action?(resource, _action) + resource.address_zip == "666" + end + end + + it "authorizes allowed actions" do + resource = FactoryBot.create(:order, address_zip: "666") + expect { get :show, params: { id: resource.id } }. + not_to raise_error + end + + it "does not authorize disallowed actions" do + resource = FactoryBot.create(:order, address_zip: "667") + expect { get :show, params: { id: resource.id } }. + to raise_error(Administrate::NotAuthorizedError) + end end - it "redirect to custom route after create" do - customer = create(:customer) - order_attributes = build(:order, customer: customer).attributes - params = order_attributes.except("id", "created_at", "updated_at", "shipped_at") + describe "deprecated methods: show_action" do + controller(Administrate::ApplicationController) do + def index + show_action?(:index, Order) + end + end - post :create, params: { order: params } - expect(response).to redirect_to(admin_customer_path(customer)) + it "triggers a deprecation warning" do + allow(ActiveSupport::Deprecation).to receive(:warn) + get :index + expect(ActiveSupport::Deprecation).to( + have_received(:warn). + with(/`show_action\?` is deprecated/), + ) + end end - it "redirect to custom route after update" do - order = create(:order) - order_params = { address_line_one: order.address_line_one } + describe "deprecated methods: valid_action" do + controller(Administrate::ApplicationController) do + def index + valid_action?(:index, Order) + end + end - put :update, params: { id: order.to_param, order: order_params } - expect(response).to redirect_to(admin_customer_path(order.customer)) + it "triggers a deprecation warning" do + allow(ActiveSupport::Deprecation).to receive(:warn) + get :index + expect(ActiveSupport::Deprecation).to( + have_received(:warn). + with(/`valid_action\?` is deprecated/), + ) + end end end diff --git a/spec/controllers/admin/orders_controller_spec.rb b/spec/controllers/admin/orders_controller_spec.rb index 6bbe2f9588..8b16ba292b 100644 --- a/spec/controllers/admin/orders_controller_spec.rb +++ b/spec/controllers/admin/orders_controller_spec.rb @@ -82,21 +82,21 @@ def send_request(order:) end end - describe "#show_action?" do + describe "#authorized_action?" do it "shows edit actions for records by the user" do o = create(:order, customer: user) - expect(controller.show_action?(:edit, o)).to be true + expect(controller.send(:authorized_action?, o, :edit)).to be true end it "does not show edit actions for records from other users" do someone = create(:customer) o = create(:order, customer: someone) - expect(controller.show_action?(:edit, o)).to be false + expect(controller.send(:authorized_action?, o, :edit)).to be false end it "never shows destroy actions" do o = create :order, customer: user, address_state: "AZ" - expect(controller.show_action?(:destroy, o)).to be false + expect(controller.send(:authorized_action?, o, :destroy)).to be false end end end diff --git a/spec/example_app/app/views/admin/customers/_collection_header_actions.html.erb b/spec/example_app/app/views/admin/customers/_collection_header_actions.html.erb index 05c814078b..134b2dd130 100644 --- a/spec/example_app/app/views/admin/customers/_collection_header_actions.html.erb +++ b/spec/example_app/app/views/admin/customers/_collection_header_actions.html.erb @@ -1,7 +1,7 @@ <% [ - valid_action?(:edit, collection_presenter.resource_name), - valid_action?(:destroy, collection_presenter.resource_name), + existing_action?(collection_presenter.resource_name, :edit), + existing_action?(collection_presenter.resource_name, :destroy), true, # "Become" action ].count(true).times do %> diff --git a/spec/example_app/app/views/admin/customers/_collection_item_actions.html.erb b/spec/example_app/app/views/admin/customers/_collection_item_actions.html.erb index 9554cabdd6..a741a7290c 100644 --- a/spec/example_app/app/views/admin/customers/_collection_item_actions.html.erb +++ b/spec/example_app/app/views/admin/customers/_collection_item_actions.html.erb @@ -1,19 +1,19 @@ -<% if valid_action? :edit, collection_presenter.resource_name %> +<% if existing_action?(collection_presenter.resource_name, :edit) %> <%= link_to( t("administrate.actions.edit"), [:edit, namespace, resource], class: "action-edit", - ) if show_action? :edit, resource%> + ) if authorized_action?(resource, :edit) %> <% end %> -<% if valid_action? :destroy, collection_presenter.resource_name %> +<% if existing_action?(collection_presenter.resource_name, :destroy) %> <%= link_to( t("administrate.actions.destroy"), [namespace, resource], class: "text-color-red", method: :delete, data: { confirm: t("administrate.actions.confirm") } - ) if show_action? :destroy, resource %> + ) if authorized_action?(resource, :destroy) %> <% end %> diff --git a/spec/example_app/app/views/admin/customers/_index_header.html.erb b/spec/example_app/app/views/admin/customers/_index_header.html.erb index 8b54cc84d0..5716ec7908 100644 --- a/spec/example_app/app/views/admin/customers/_index_header.html.erb +++ b/spec/example_app/app/views/admin/customers/_index_header.html.erb @@ -28,7 +28,6 @@ ), [:new, namespace, page.resource_path.to_sym], class: "button", - ) if valid_action?(:new) && show_action?(:new, new_resource) %> + ) if accessible_action?(new_resource, :new) %> - diff --git a/spec/helpers/administrate/application_helper_spec.rb b/spec/helpers/administrate/application_helper_spec.rb index f2bbf42600..1d74e39e28 100644 --- a/spec/helpers/administrate/application_helper_spec.rb +++ b/spec/helpers/administrate/application_helper_spec.rb @@ -101,4 +101,90 @@ expect(sort_order("for anything else")).to eq("none") end end + + describe "#accessible_action?" do + context "when given a string target" do + it "checks the class it names with `existing_action?` and `authorized_action?`" do + class MyResource; end + ctx = double(existing_action?: true, authorized_action?: true) + ctx.extend(Administrate::ApplicationHelper) + + ctx.accessible_action?("my_resource", "foo") + + expect(ctx).to( + have_received(:existing_action?).with(:my_resource, "foo"), + ) + expect(ctx).to( + have_received(:authorized_action?).with(:my_resource, "foo"), + ) + ensure + remove_constants :MyResource + end + end + + context "when given a symbol target" do + it "checks the class it names with `existing_action?` and `authorized_action?`" do + class MyResource; end + ctx = double(existing_action?: true, authorized_action?: true) + ctx.extend(Administrate::ApplicationHelper) + + ctx.accessible_action?(:my_resource, "foo") + + expect(ctx).to( + have_received(:existing_action?).with(:my_resource, "foo"), + ) + expect(ctx).to( + have_received(:authorized_action?).with(:my_resource, "foo"), + ) + ensure + remove_constants :MyResource + end + end + + context "when given a class target" do + it "checks it with `existing_action?` and `authorized_action?`" do + class MyResource; end + ctx = double(existing_action?: true, authorized_action?: true) + ctx.extend(Administrate::ApplicationHelper) + + ctx.accessible_action?(MyResource, "foo") + + expect(ctx).to have_received(:existing_action?).with(MyResource, "foo") + expect(ctx).to( + have_received(:authorized_action?).with(MyResource, "foo"), + ) + ensure + remove_constants :MyResource + end + end + + context "when given an ActiveRecord::Base target" do + it "tests its class with `existing_action?` and the object with `authorized_action?`" do + ctx = double(existing_action?: true, authorized_action?: true) + ctx.extend(Administrate::ApplicationHelper) + + object = Product.new + ctx.accessible_action?(object, "foo") + + expect(ctx).to have_received(:existing_action?).with(Product, "foo") + expect(ctx).to have_received(:authorized_action?).with(object, "foo") + end + end + + context "when given an object target" do + it "checks its class with `existing_action?` and the object with `authorized_action?`" do + class MyResource; end + ctx = double(existing_action?: true, authorized_action?: true) + ctx.extend(Administrate::ApplicationHelper) + + object = MyResource.new + ctx.accessible_action?(object, "foo") + + expect(ctx).to have_received(:existing_action?).with(MyResource, "foo") + expect(ctx).to have_received(:authorized_action?).with(object, "foo") + ensure + remove_constants :MyResource + end + end + end end diff --git a/spec/lib/administrate/not_authorized_error_spec.rb b/spec/lib/administrate/not_authorized_error_spec.rb new file mode 100644 index 0000000000..a5196bf113 --- /dev/null +++ b/spec/lib/administrate/not_authorized_error_spec.rb @@ -0,0 +1,51 @@ +require "rails_helper" + +describe Administrate::NotAuthorizedError do + context "when the resource is a class or module" do + it "produces a message mentioning it directly" do + error = described_class.new( + resource: Administrate, + action: "foo", + ) + expect(error.message).to eq( + %{Not allowed to perform "foo" on Administrate}, + ) + end + end + + context "when the resource is a string" do + it "produces a message mentioning it directly" do + error = described_class.new( + resource: "User", + action: "foo", + ) + expect(error.message).to eq(%{Not allowed to perform "foo" on "User"}) + end + end + + context "when the resource is a symbol" do + it "produces a message mentioning it directly" do + error = described_class.new( + resource: :user, + action: "foo", + ) + expect(error.message).to eq(%{Not allowed to perform "foo" on :user}) + end + end + + context "when the resource is something else" do + it "produces a message that refers to the class of the resource" do + class TestStuff; end + + error = described_class.new( + resource: TestStuff.new, + action: "foo", + ) + expect(error.message).to eq( + %{Not allowed to perform "foo" on the given TestStuff}, + ) + ensure + remove_constants :TestStuff + end + end +end From 7cde0420233f5a92f6aad6b6a5cc9d5a220b4a2e Mon Sep 17 00:00:00 2001 From: Dharam Gollapudi Date: Mon, 8 Aug 2022 05:00:34 -0700 Subject: [PATCH 010/137] Fix typos and formatting in hiding dashboard docs (#2215) This also fixes a typo in the associate spec. --- docs/guides/hiding_dashboards_from_sidebar.md | 6 ++++-- spec/features/documentation_spec.rb | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/guides/hiding_dashboards_from_sidebar.md b/docs/guides/hiding_dashboards_from_sidebar.md index 2872f06858..d0937752ed 100644 --- a/docs/guides/hiding_dashboards_from_sidebar.md +++ b/docs/guides/hiding_dashboards_from_sidebar.md @@ -2,7 +2,8 @@ title: Hiding Dashboards from the Sidebar --- -Resources can be removed form the sidebar by removing their index action from the routes. For example: +Resources can be removed from the sidebar by removing their `index` action +from the routes. For example: ```ruby # config/routes.rb @@ -16,4 +17,5 @@ Rails.application.routes.draw do end ``` -In this case, only Orders and Products will appear in the sidebar, while Line Items can still appear as an association. +In this case, only Orders and Products will appear in the sidebar, while +Line Items can still appear as an association. diff --git a/spec/features/documentation_spec.rb b/spec/features/documentation_spec.rb index 3e9828ffa0..f4bdcf0eba 100644 --- a/spec/features/documentation_spec.rb +++ b/spec/features/documentation_spec.rb @@ -62,7 +62,7 @@ visit("/guides/hiding_dashboards_from_sidebar") expect(page).to have_css("div.main h1", text: "Hiding Dashboards from") - expect(page).to have_content("Resources can be removed form the sidebar") + expect(page).to have_content("Resources can be removed from the sidebar") end it "links to each documentation page" do From ea96c83e748dbffcd9347e61319eb0c0e0b38c29 Mon Sep 17 00:00:00 2001 From: Pablo Brasero <36066+pablobm@users.noreply.github.com> Date: Mon, 8 Aug 2022 13:20:00 +0100 Subject: [PATCH 011/137] Guess correct name for namespaced associations (#2235) Fixes #1978 This includes the namespace of the associated class. If the associated class is `System::Build`, the previous code would tell us that the name was `Build`. This code gets the right name. --- lib/administrate/field/associative.rb | 2 +- .../controllers/admin/blog/tags_controller.rb | 6 ++++ .../app/dashboards/blog/post_dashboard.rb | 2 ++ .../app/dashboards/blog/tag_dashboard.rb | 31 +++++++++++++++++++ spec/example_app/app/models/blog/post.rb | 2 ++ spec/example_app/app/models/blog/tag.rb | 7 +++++ .../app/policies/blog/tag_policy.rb | 4 +++ spec/example_app/config/routes.rb | 1 + .../20220804132651_create_blog_tags.rb | 9 ++++++ .../20220804133503_create_blog_posts_tags.rb | 9 ++++++ spec/example_app/db/schema.rb | 21 +++++++++++-- spec/example_app/db/seeds.rb | 20 +++++++----- spec/factories.rb | 4 +++ spec/features/associations_spec.rb | 13 ++++++++ 14 files changed, 120 insertions(+), 11 deletions(-) create mode 100644 spec/example_app/app/controllers/admin/blog/tags_controller.rb create mode 100644 spec/example_app/app/dashboards/blog/tag_dashboard.rb create mode 100644 spec/example_app/app/models/blog/tag.rb create mode 100644 spec/example_app/app/policies/blog/tag_policy.rb create mode 100644 spec/example_app/db/migrate/20220804132651_create_blog_tags.rb create mode 100644 spec/example_app/db/migrate/20220804133503_create_blog_posts_tags.rb create mode 100644 spec/features/associations_spec.rb diff --git a/lib/administrate/field/associative.rb b/lib/administrate/field/associative.rb index b24bee1054..b23be7500a 100644 --- a/lib/administrate/field/associative.rb +++ b/lib/administrate/field/associative.rb @@ -12,7 +12,7 @@ def self.associated_class(resource_class, attr) end def self.associated_class_name(resource_class, attr) - reflection(resource_class, attr).class_name + associated_class(resource_class, attr).name end def self.reflection(resource_class, attr) diff --git a/spec/example_app/app/controllers/admin/blog/tags_controller.rb b/spec/example_app/app/controllers/admin/blog/tags_controller.rb new file mode 100644 index 0000000000..1973251803 --- /dev/null +++ b/spec/example_app/app/controllers/admin/blog/tags_controller.rb @@ -0,0 +1,6 @@ +module Admin + module Blog + class TagsController < Admin::ApplicationController + end + end +end diff --git a/spec/example_app/app/dashboards/blog/post_dashboard.rb b/spec/example_app/app/dashboards/blog/post_dashboard.rb index 9d005a375d..9afbb0e14c 100644 --- a/spec/example_app/app/dashboards/blog/post_dashboard.rb +++ b/spec/example_app/app/dashboards/blog/post_dashboard.rb @@ -9,6 +9,7 @@ class PostDashboard < Administrate::BaseDashboard title: Field::String, published_at: Field::DateTime, body: Field::Text, + tags: Field::HasMany, } READ_ONLY_ATTRIBUTES = [ @@ -20,6 +21,7 @@ class PostDashboard < Administrate::BaseDashboard COLLECTION_ATTRIBUTES = [ :id, :title, + :tags, :published_at, ] diff --git a/spec/example_app/app/dashboards/blog/tag_dashboard.rb b/spec/example_app/app/dashboards/blog/tag_dashboard.rb new file mode 100644 index 0000000000..b0ce488f53 --- /dev/null +++ b/spec/example_app/app/dashboards/blog/tag_dashboard.rb @@ -0,0 +1,31 @@ +require "administrate/base_dashboard" + +module Blog + class TagDashboard < Administrate::BaseDashboard + ATTRIBUTE_TYPES = { + id: Field::Number, + name: Field::String, + created_at: Field::DateTime, + updated_at: Field::DateTime, + posts: Field::HasMany, + }.freeze + + COLLECTION_ATTRIBUTES = %i[ + id + name + posts + created_at + ].freeze + + FORM_ATTRIBUTES = %i[ + name + posts + ].freeze + + SHOW_PAGE_ATTRIBUTES = COLLECTION_ATTRIBUTES + + def display_resource(resource) + resource.name + end + end +end diff --git a/spec/example_app/app/models/blog/post.rb b/spec/example_app/app/models/blog/post.rb index 4f30e2ff4a..51d9760aad 100644 --- a/spec/example_app/app/models/blog/post.rb +++ b/spec/example_app/app/models/blog/post.rb @@ -1,5 +1,7 @@ module Blog class Post < ApplicationRecord + has_and_belongs_to_many :tags + validates :title, :body, presence: true end end diff --git a/spec/example_app/app/models/blog/tag.rb b/spec/example_app/app/models/blog/tag.rb new file mode 100644 index 0000000000..13dc77a738 --- /dev/null +++ b/spec/example_app/app/models/blog/tag.rb @@ -0,0 +1,7 @@ +module Blog + class Tag < ApplicationRecord + has_and_belongs_to_many :posts + + validates :name, presence: true + end +end diff --git a/spec/example_app/app/policies/blog/tag_policy.rb b/spec/example_app/app/policies/blog/tag_policy.rb new file mode 100644 index 0000000000..ca955a81e5 --- /dev/null +++ b/spec/example_app/app/policies/blog/tag_policy.rb @@ -0,0 +1,4 @@ +module Blog + class TagPolicy < ApplicationPolicy + end +end diff --git a/spec/example_app/config/routes.rb b/spec/example_app/config/routes.rb index 80dfc76555..f15e62d71b 100644 --- a/spec/example_app/config/routes.rb +++ b/spec/example_app/config/routes.rb @@ -16,6 +16,7 @@ namespace :blog do resources :posts + resources :tags end resources :stats, only: [:index] diff --git a/spec/example_app/db/migrate/20220804132651_create_blog_tags.rb b/spec/example_app/db/migrate/20220804132651_create_blog_tags.rb new file mode 100644 index 0000000000..b9df393efa --- /dev/null +++ b/spec/example_app/db/migrate/20220804132651_create_blog_tags.rb @@ -0,0 +1,9 @@ +class CreateBlogTags < ActiveRecord::Migration[6.1] + def change + create_table :blog_tags do |t| + t.string :name + + t.timestamps + end + end +end diff --git a/spec/example_app/db/migrate/20220804133503_create_blog_posts_tags.rb b/spec/example_app/db/migrate/20220804133503_create_blog_posts_tags.rb new file mode 100644 index 0000000000..f0daae1b93 --- /dev/null +++ b/spec/example_app/db/migrate/20220804133503_create_blog_posts_tags.rb @@ -0,0 +1,9 @@ +class CreateBlogPostsTags < ActiveRecord::Migration[6.1] + def change + create_table :blog_posts_tags do |t| + t.belongs_to :post + t.belongs_to :tag + t.timestamps + end + end +end diff --git a/spec/example_app/db/schema.rb b/spec/example_app/db/schema.rb index 308929609f..1627e28f21 100644 --- a/spec/example_app/db/schema.rb +++ b/spec/example_app/db/schema.rb @@ -2,15 +2,15 @@ # of editing this file, please use the migrations feature of Active Record to # incrementally modify your database, and then regenerate this schema definition. # -# This file is the source Rails uses to define your schema when running `rails -# db:schema:load`. When creating a new database, `rails db:schema:load` tends to +# This file is the source Rails uses to define your schema when running `bin/rails +# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to # be faster and is potentially less error prone than running all of your # migrations from scratch. Old migrations may fail to apply correctly if those # migrations use external dependencies or application code. # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2020_07_14_081950) do +ActiveRecord::Schema.define(version: 2022_08_04_133503) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -23,6 +23,21 @@ t.datetime "updated_at", null: false end + create_table "blog_posts_tags", force: :cascade do |t| + t.bigint "post_id" + t.bigint "tag_id" + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.index ["post_id"], name: "index_blog_posts_tags_on_post_id" + t.index ["tag_id"], name: "index_blog_posts_tags_on_tag_id" + end + + create_table "blog_tags", force: :cascade do |t| + t.string "name" + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + end + create_table "countries", id: :serial, force: :cascade do |t| t.string "code", null: false t.string "name" diff --git a/spec/example_app/db/seeds.rb b/spec/example_app/db/seeds.rb index 1c26eb9404..c9d0a20761 100644 --- a/spec/example_app/db/seeds.rb +++ b/spec/example_app/db/seeds.rb @@ -59,19 +59,25 @@ Product.find_each do |p| Page.create!( - title: "Something about #{p.name}", + title: "Rules of #{p.name}", body: Faker::Lorem.paragraph, product: p, ) - Page.create!( - title: "The secrets of the game #{p.name}", +end + +tag_secrets = Blog::Tag.create!(name: "secrets") +tag_recommendations = Blog::Tag.create!(name: "recommendations") + +Product.find_each do |p| + Blog::Post.create!( + title: "The secrets of #{p.name}", body: Faker::Lorem.paragraph, - product: p, + tags: [tag_secrets], ) - Page.create!( - title: "If you liked #{p.name}, you will love these games", + Blog::Post.create!( + title: "If you liked #{p.name}, you will love these products", body: Faker::Lorem.paragraph, - product: p, + tags: [tag_recommendations], ) end diff --git a/spec/factories.rb b/spec/factories.rb index 82752358df..d5c147d154 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -61,6 +61,10 @@ body { "Empty" } end + factory :blog_tag, class: "Blog::Tag" do + name { Faker::NatoPhoneticAlphabet.code_word.downcase } + end + factory :series do sequence(:name) { |n| "Series #{n}" } end diff --git a/spec/features/associations_spec.rb b/spec/features/associations_spec.rb new file mode 100644 index 0000000000..ff821ef6d5 --- /dev/null +++ b/spec/features/associations_spec.rb @@ -0,0 +1,13 @@ +require "rails_helper" + +describe "Associations" do + it "can associate to namespaced models on dashboards" do + post = create(:blog_post) + tag = create(:blog_tag, name: "foobarisms") + post.tags << tag + + visit admin_blog_post_url(post) + + expect(page).to have_css(".cell-data", text: "foobarisms") + end +end From d2cf0b8cc6b445164ffe156b17055be7577695da Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 Aug 2022 10:36:46 +0100 Subject: [PATCH 012/137] Bump selenium-webdriver from 4.3.0 to 4.4.0 (#2240) Bumps [selenium-webdriver](https://github.com/SeleniumHQ/selenium) from 4.3.0 to 4.4.0. - [Release notes](https://github.com/SeleniumHQ/selenium/releases) - [Changelog](https://github.com/SeleniumHQ/selenium/blob/trunk/rb/CHANGES) - [Commits](https://github.com/SeleniumHQ/selenium/compare/selenium-4.3.0...selenium-4.4.0) --- updated-dependencies: - dependency-name: selenium-webdriver dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 25990d1734..f5fa72d05c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -280,7 +280,7 @@ GEM sprockets-rails tilt selectize-rails (0.12.6) - selenium-webdriver (4.3.0) + selenium-webdriver (4.4.0) childprocess (>= 0.5, < 5.0) rexml (~> 3.2, >= 3.2.5) rubyzip (>= 1.2.2, < 3.0) From 66f5adf1b0dd4fe507a2feb7d7116e67540432df Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 Aug 2022 10:36:56 +0100 Subject: [PATCH 013/137] Bump webmock from 3.17.0 to 3.17.1 (#2241) Bumps [webmock](https://github.com/bblimke/webmock) from 3.17.0 to 3.17.1. - [Release notes](https://github.com/bblimke/webmock/releases) - [Changelog](https://github.com/bblimke/webmock/blob/master/CHANGELOG.md) - [Commits](https://github.com/bblimke/webmock/compare/v3.17.0...v3.17.1) --- updated-dependencies: - dependency-name: webmock dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index f5fa72d05c..12824ae6ab 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -314,7 +314,7 @@ GEM nokogiri (~> 1.6) rubyzip (>= 1.3.0) selenium-webdriver (~> 4.0) - webmock (3.17.0) + webmock (3.17.1) addressable (>= 2.8.0) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) From f2daeee1eff26df14459b87f97fae89a48d9a3f1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 Aug 2022 10:37:07 +0100 Subject: [PATCH 014/137] Bump pg from 1.4.2 to 1.4.3 (#2242) Bumps [pg](https://github.com/ged/ruby-pg) from 1.4.2 to 1.4.3. - [Release notes](https://github.com/ged/ruby-pg/releases) - [Changelog](https://github.com/ged/ruby-pg/blob/master/History.rdoc) - [Commits](https://github.com/ged/ruby-pg/compare/v1.4.2...v1.4.3) --- updated-dependencies: - dependency-name: pg dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 12824ae6ab..2720f3da9e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -203,7 +203,7 @@ GEM racc (~> 1.4) parser (3.1.2.0) ast (~> 2.4.1) - pg (1.4.2) + pg (1.4.3) pry (0.12.2) coderay (~> 1.1.0) method_source (~> 0.9.0) From 776a6d5ec59e32ed870ec53a468f308a0774d65d Mon Sep 17 00:00:00 2001 From: Pablo Brasero <36066+pablobm@users.noreply.github.com> Date: Thu, 11 Aug 2022 11:00:36 +0100 Subject: [PATCH 015/137] Fix HasOne association translations (#2200) The template `app/views/fields/has_one/_show.html.erb` wasn't using the correct i18n key to translate the field names of the associated record. This PR includes a heavy revamp of `spec/administrate/views/fields/has_one/_show_spec.rb`, which needed some TLC in order to work with it. The diff for `lib/administrate/field/associative.rb` looks a bit misleading. The actual change is the definition of associated_class_name is now above the private declaration. Fixes #2185 --- app/views/fields/has_one/_show.html.erb | 2 +- lib/administrate/field/associative.rb | 12 +- .../views/fields/has_one/_show_spec.rb | 250 ++++++++++-------- 3 files changed, 149 insertions(+), 115 deletions(-) diff --git a/app/views/fields/has_one/_show.html.erb b/app/views/fields/has_one/_show.html.erb index 542af5e09d..6e9497cc8c 100644 --- a/app/views/fields/has_one/_show.html.erb +++ b/app/views/fields/has_one/_show.html.erb @@ -28,7 +28,7 @@ All show page attributes of has_one relationship would be rendered
<%= t( - "helpers.label.#{resource_name}.#{attribute.name}", + "helpers.label.#{field.associated_class_name.underscore}.#{attribute.name}", default: attribute.name.titleize, ) %>
diff --git a/lib/administrate/field/associative.rb b/lib/administrate/field/associative.rb index b23be7500a..989fb6c899 100644 --- a/lib/administrate/field/associative.rb +++ b/lib/administrate/field/associative.rb @@ -31,12 +31,6 @@ def associated_class end end - private - - def associated_dashboard - "#{associated_class_name}Dashboard".constantize.new - end - def associated_class_name if option_given?(:class_name) deprecated_option(:class_name) @@ -48,6 +42,12 @@ def associated_class_name end end + private + + def associated_dashboard + "#{associated_class_name}Dashboard".constantize.new + end + def primary_key if option_given?(:primary_key) deprecated_option(:primary_key) diff --git a/spec/administrate/views/fields/has_one/_show_spec.rb b/spec/administrate/views/fields/has_one/_show_spec.rb index 76b7aeb986..9bbee8c5ce 100644 --- a/spec/administrate/views/fields/has_one/_show_spec.rb +++ b/spec/administrate/views/fields/has_one/_show_spec.rb @@ -3,10 +3,12 @@ describe "fields/has_one/_show", type: :view do before do + view.extend Administrate::ApplicationHelper allow(view).to receive(:accessible_action?).and_return(true) + allow(view).to receive(:namespace).and_return(:admin) end - context "without an associated record" do + context "without a persisted record" do it "displays nothing" do has_one = Administrate::Field::HasOne.new( :product_meta_tag, @@ -23,124 +25,156 @@ end end - context "with an associated record" do - context "when linking the record is allowed" do - it "renders a link to the record" do - product = create(:product) - product_path = polymorphic_path([:admin, product]) - nested_show = instance_double( - "Administrate::Page::Show", - resource: double( - class: ProductMetaTag, - ), - attributes: [], - resource_name: "Product Tag", - ) - has_one = instance_double( - "Administrate::Field::HasOne", - display_associated_resource: product.name, - data: product, - linkable?: true, - nested_show: nested_show, - ) - - render( - partial: "fields/has_one/show", - locals: { - field: has_one, - namespace: :admin, - resource_name: "product_meta_tag", - }, - ) + context "with a persisted record" do + before do + field_resource = create(:product_meta_tag) + @path_to_field_resource = polymorphic_path([:admin, field_resource]) + + nested_simple_field = instance_double( + "Administrate::Field::String", + name: "simple_string_field", + truncate: "string value", + html_class: "string", + to_partial_path: "fields/string/index", + ) - link = "#{product.name}" - expect(rendered.strip).to include(link) - end + nested_show_page_for_has_one = instance_double( + "Administrate::Page::Show", + resource: double( + class: ProductMetaTag, + ), + attributes: [ + nested_simple_field, + ], + ) + + @has_one_field = instance_double( + "Administrate::Field::HasOne", + display_associated_resource: "The Nested Resource", + data: field_resource, + linkable?: true, + nested_show: nested_show_page_for_has_one, + associated_class_name: "NestedHasOne", + ) + + @page_double = instance_double("Administrate::Page::Show") + end - it "renders nested attribute relationships" do - view.extend Administrate::ApplicationHelper - - product = create(:product) - page = create(:page, product: product) - - nested_has_many = instance_double( - "Administrate::Field::HasMany", - associated_collection: [page], - attribute: :page, - data: [page], - resources: [page], - html_class: "has-many", - name: "Page", - to_partial_path: "fields/has_many/index", - order_from_params: {}, - ) - - nested_show = instance_double( - "Administrate::Page::Show", - resource: double( - class: ProductMetaTag, - ), - attributes: [nested_has_many], - resource_name: "Product Tag", - ) - - has_one = instance_double( - "Administrate::Field::HasOne", - display_associated_resource: product.name, - data: product, - linkable?: true, - nested_show: nested_show, - ) - - page_double = instance_double("Administrate::Page::Show") - - render( - partial: "fields/has_one/show", - locals: { - field: has_one, - namespace: :admin, - page: page_double, - resource_name: "product_meta_tag", + def render_field + render( + partial: "fields/has_one/show", + locals: { + field: @has_one_field, + page: @page_double, + resource_name: "parent_resource", + }, + ) + end + + it "uses the correct labels for fields" do + I18n.backend.translations(do_init: true)[:en].deep_merge!( + helpers: { + label: { + nested_has_one: { + simple_string_field: "Just a Simple String", + }, }, - ) + }, + ) + + render_field + expect(rendered.strip).to include("Just a Simple String") + end - has_many_count = "1 page" - expect(rendered.strip).to include(has_many_count) + context "when linking the record is allowed" do + it "renders a link to the record" do + render_field + link = "The Nested Resource" + expect(rendered.strip).to include(link) end end context "when linking the record is not allowed" do it "displays the record without a link" do allow(view).to receive(:accessible_action?).and_return(false) - product = create(:product) - nested_show = instance_double( - "Administrate::Page::Show", - resource: double( - class: ProductMetaTag, - ), - attributes: [], - resource_name: "Product Tag", - ) - has_one = instance_double( - "Administrate::Field::HasOne", - display_associated_resource: product.name, - data: product, - linkable?: true, - nested_show: nested_show, - ) - - render( - partial: "fields/has_one/show", - locals: { - field: has_one, - namespace: :admin, - resource_name: "product_meta_tag", - }, - ) - - expect(rendered.strip).to include(product.name) - expect(rendered.strip).not_to include(" Date: Thu, 11 Aug 2022 17:07:09 +0100 Subject: [PATCH 016/137] Move bundle-audit to GitHub Actions (#2237) Following the GitHub Actions pattern of having one check per service, rather than one big check for faster feedback. This also means we no longer need to bundle `bundler-audit`. --- .circleci/config.yml | 3 --- .github/workflows/bundle-audit.yml | 13 +++++++++++++ Gemfile | 1 - Gemfile.lock | 6 +----- Rakefile | 2 -- gemfiles/rails60.gemfile | 1 - gemfiles/rails61.gemfile | 1 - gemfiles/rails70.gemfile | 1 - spec/example_app/lib/tasks/bundler_audit.rake | 12 ------------ 9 files changed, 14 insertions(+), 26 deletions(-) create mode 100644 .github/workflows/bundle-audit.yml delete mode 100644 spec/example_app/lib/tasks/bundler_audit.rake diff --git a/.circleci/config.yml b/.circleci/config.yml index b7051910c4..f670194495 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -43,9 +43,6 @@ commands: # Run the tests - run: bundle exec rspec - # Check vulnerabilities - - run: bundle exec rake bundler:audit - default_job: &default_job working_directory: ~/administrate diff --git a/.github/workflows/bundle-audit.yml b/.github/workflows/bundle-audit.yml new file mode 100644 index 0000000000..4a4adaa28c --- /dev/null +++ b/.github/workflows/bundle-audit.yml @@ -0,0 +1,13 @@ +--- +name: Bundler Audit +on: [push] + +jobs: + audit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: 'Bundler Audit' + uses: andrewmcodes/bundler-audit-action@main + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/Gemfile b/Gemfile index f840c8a75e..9985d3559d 100644 --- a/Gemfile +++ b/Gemfile @@ -16,7 +16,6 @@ gem "unicorn" group :development, :test do gem "appraisal" gem "awesome_print" - gem "bundler-audit", require: false gem "byebug" gem "dotenv-rails" gem "factory_bot_rails" diff --git a/Gemfile.lock b/Gemfile.lock index 2720f3da9e..111fe75d71 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -95,9 +95,6 @@ GEM parser (>= 2.4) smart_properties builder (3.2.4) - bundler-audit (0.9.1) - bundler (>= 1.2.0, < 3) - thor (~> 1.0) byebug (11.1.3) capybara (3.37.1) addressable @@ -338,7 +335,6 @@ DEPENDENCIES ammeter appraisal awesome_print - bundler-audit byebug capybara database_cleaner @@ -369,4 +365,4 @@ DEPENDENCIES yard BUNDLED WITH - 2.3.6 + 2.3.10 diff --git a/Rakefile b/Rakefile index 07a7957aea..ab9915d104 100644 --- a/Rakefile +++ b/Rakefile @@ -28,5 +28,3 @@ if defined? RSpec t.verbose = false end end - -task default: "bundler:audit" diff --git a/gemfiles/rails60.gemfile b/gemfiles/rails60.gemfile index 1efa3eca7d..37c8b24ed5 100644 --- a/gemfiles/rails60.gemfile +++ b/gemfiles/rails60.gemfile @@ -17,7 +17,6 @@ gem "rails", "~> 6.0.3.4" group :development, :test do gem "appraisal" gem "awesome_print" - gem "bundler-audit", require: false gem "byebug" gem "dotenv-rails" gem "factory_bot_rails" diff --git a/gemfiles/rails61.gemfile b/gemfiles/rails61.gemfile index 2c17bfa0dc..6d99eb282d 100644 --- a/gemfiles/rails61.gemfile +++ b/gemfiles/rails61.gemfile @@ -17,7 +17,6 @@ gem "rails", "~> 6.1" group :development, :test do gem "appraisal" gem "awesome_print" - gem "bundler-audit", require: false gem "byebug" gem "dotenv-rails" gem "factory_bot_rails" diff --git a/gemfiles/rails70.gemfile b/gemfiles/rails70.gemfile index 2c17bfa0dc..6d99eb282d 100644 --- a/gemfiles/rails70.gemfile +++ b/gemfiles/rails70.gemfile @@ -17,7 +17,6 @@ gem "rails", "~> 6.1" group :development, :test do gem "appraisal" gem "awesome_print" - gem "bundler-audit", require: false gem "byebug" gem "dotenv-rails" gem "factory_bot_rails" diff --git a/spec/example_app/lib/tasks/bundler_audit.rake b/spec/example_app/lib/tasks/bundler_audit.rake deleted file mode 100644 index 00c12637d4..0000000000 --- a/spec/example_app/lib/tasks/bundler_audit.rake +++ /dev/null @@ -1,12 +0,0 @@ -if Rails.env.development? || Rails.env.test? - require "bundler/audit/cli" - - namespace :bundler do - desc "Updates the ruby-advisory-db and runs audit" - task :audit do - %w(update check).each do |command| - Bundler::Audit::CLI.start [command] - end - end - end -end From 6127d30cb3c0591d5589c821050b4d7065fc565b Mon Sep 17 00:00:00 2001 From: David Wilkie Date: Tue, 9 Aug 2022 16:53:44 +0700 Subject: [PATCH 017/137] Move pagination into partial --- app/views/administrate/application/_pagination.html.erb | 1 + app/views/administrate/application/index.html.erb | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 app/views/administrate/application/_pagination.html.erb diff --git a/app/views/administrate/application/_pagination.html.erb b/app/views/administrate/application/_pagination.html.erb new file mode 100644 index 0000000000..608d3b6013 --- /dev/null +++ b/app/views/administrate/application/_pagination.html.erb @@ -0,0 +1 @@ +<%= paginate resources, param_name: '_page' %> diff --git a/app/views/administrate/application/index.html.erb b/app/views/administrate/application/index.html.erb index 5bd91874a1..b4754b9d3d 100644 --- a/app/views/administrate/application/index.html.erb +++ b/app/views/administrate/application/index.html.erb @@ -42,5 +42,5 @@ It renders the `_table` partial to display details about the resources. table_title: "page-title" ) %> - <%= paginate resources, param_name: '_page' %> + <%= render("pagination", resources: resources) %> From c2bb8e2880cd6dcea81e027610730dc2d0496b29 Mon Sep 17 00:00:00 2001 From: Jose Varela Date: Wed, 10 Aug 2022 00:13:40 -0500 Subject: [PATCH 018/137] Add guide on how to scope has_many relations --- docs/guides.md | 5 +++-- docs/guides/scoping_has_many_relations.md | 27 +++++++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 docs/guides/scoping_has_many_relations.md diff --git a/docs/guides.md b/docs/guides.md index 4718e97079..66e2f2bdfb 100644 --- a/docs/guides.md +++ b/docs/guides.md @@ -2,5 +2,6 @@ title: Guides --- -* [Hiding Dashboards from the Sidebar](./guides/hiding_dashboards_from_sidebar) -* [Customising the search](./guides/customising_search) +- [Hiding Dashboards from the Sidebar](./guides/hiding_dashboards_from_sidebar) +- [Customising the search](./guides/customising_search) +- [Scoping HasMany Relations](./guides/scoping_has_many_relations.md) diff --git a/docs/guides/scoping_has_many_relations.md b/docs/guides/scoping_has_many_relations.md new file mode 100644 index 0000000000..65bf3944cb --- /dev/null +++ b/docs/guides/scoping_has_many_relations.md @@ -0,0 +1,27 @@ +--- +title: Scoping HasMany Relations +--- + +To show a subset of a has_many relationship, create a new [has_many](https://apidock.com/rails/ActiveRecord/Associations/ClassMethods/has_many) relationship in your model (using the `scope` argument) and add it to the model's dashboard. + +## Creating a scoped has_many relationship + +Models can define subsets of a `has_many` relationship by passing a callable (i.e. proc or lambda) as its second argument. + +```ruby +class Customer < ApplicationRecord + has_many :orders + has_many :processed_orders, ->{ where(processed: true) }, class_name: "Order" +``` + +Since ActiveRecord infers the class name from the first argument, the new `has_many` relation needs to specify the model using the `class_name` option. + +## Add new relationship to dashboard + +Your new scoped relation can be used in the dashboard just like the original `HasMany`. Notice the new field needs to specifiy the class name as an option like you did in the model. + +```ruby +ATTRIBUTE_TYPES = { + orders: Field::HasMany, + processed_orders: Field::HasMany.with_options(class_name: 'Order') +``` From 7b15eb552c0eb4d660f8488363b9b94ab2ae61e4 Mon Sep 17 00:00:00 2001 From: Pablo Brasero <36066+pablobm@users.noreply.github.com> Date: Thu, 3 Mar 2022 17:47:59 +0000 Subject: [PATCH 019/137] Create codeql-analysis.yml --- .github/workflows/codeql-analysis.yml | 36 +++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 .github/workflows/codeql-analysis.yml diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000000..a925cf7993 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,36 @@ +--- +name: "CodeQL" + +on: + push: + schedule: + - cron: '44 6 * * 4' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: ['javascript', 'ruby'] + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + languages: ${{ matrix.language }} + + - name: Autobuild + uses: github/codeql-action/autobuild@v1 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 From efe7aa6f73d0cfd0632939a64fb1f773d8834cdb Mon Sep 17 00:00:00 2001 From: Pablo Brasero Date: Thu, 11 Aug 2022 15:27:15 +0100 Subject: [PATCH 020/137] Ensure we read from sanitised paths --- spec/example_app/README.md | 1 + .../app/controllers/docs_controller.rb | 22 +++++------ spec/example_app/app/models/doc_page.rb | 39 +++++++++++++++++-- spec/models/doc_page_spec.rb | 12 ++++-- 4 files changed, 56 insertions(+), 18 deletions(-) create mode 100644 spec/example_app/README.md diff --git a/spec/example_app/README.md b/spec/example_app/README.md new file mode 100644 index 0000000000..8551b1d99b --- /dev/null +++ b/spec/example_app/README.md @@ -0,0 +1 @@ +This file is here only for the sake of a spec. It's not really a README. diff --git a/spec/example_app/app/controllers/docs_controller.rb b/spec/example_app/app/controllers/docs_controller.rb index 08298a783c..78a61d8201 100644 --- a/spec/example_app/app/controllers/docs_controller.rb +++ b/spec/example_app/app/controllers/docs_controller.rb @@ -21,16 +21,16 @@ def show def render_page(name, title = nil) page = DocPage.find(name) - if page - title = title || page.title - @page_title = [title, "Administrate"].compact.join(" - ") - # rubocop:disable Rails/OutputSafety - render layout: "docs", html: page.body.html_safe - # rubocop:enable Rails/OutputSafety - else - render file: Rails.root.join("public", "404.html"), - layout: false, - status: :not_found - end + title = title || page.title + @page_title = [title, "Administrate"].compact.join(" - ") + # rubocop:disable Rails/OutputSafety + render layout: "docs", html: page.body.html_safe + # rubocop:enable Rails/OutputSafety + rescue DocPage::PageNotAllowed, DocPage::PageNotFound + render( + file: Rails.root.join("public", "404.html"), + layout: false, + status: :not_found, + ) end end diff --git a/spec/example_app/app/models/doc_page.rb b/spec/example_app/app/models/doc_page.rb index 4e318c65cf..deb3255ce1 100644 --- a/spec/example_app/app/models/doc_page.rb +++ b/spec/example_app/app/models/doc_page.rb @@ -1,12 +1,43 @@ class DocPage + class PageNotFound < StandardError + def initialize(page) + "Could not find page #{page.inspect}" + end + end + + class PageNotAllowed < StandardError + def initialize(page) + "Page #{page.inspect} is not allowed" + end + end + class << self def find(page) full_path = Rails.root + "../../#{page}.md" + raise PageNotFound.new(page) unless path_exists?(full_path) + + safe_path = filter_unsafe_paths(full_path) + raise PageNotAllowed.new(page) unless safe_path + + text = File.read(safe_path) + new(text) + end + + private + + def path_exists?(full_path) + File.exist?(full_path) + end + + def doc_paths + [ + Dir.glob(Rails.root + "../../**/*.md"), + Dir.glob(Rails.root + "../../*.md"), + ].join + end - if File.exist?(full_path) - text = File.read(full_path) - new(text) - end + def filter_unsafe_paths(full_path) + doc_paths[full_path.to_s] end end diff --git a/spec/models/doc_page_spec.rb b/spec/models/doc_page_spec.rb index ff39898229..a583988e66 100644 --- a/spec/models/doc_page_spec.rb +++ b/spec/models/doc_page_spec.rb @@ -2,10 +2,16 @@ RSpec.describe DocPage do describe ".find" do - it "is nil if the page doesn't exist" do - page = DocPage.find("not_a_page") + it "raises an error if the page doesn't exist" do + expect do + DocPage.find("not_a_page") + end.to raise_error(DocPage::PageNotFound) + end - expect(page).to be_nil + it "raises an error on cheeky paths" do + expect do + DocPage.find("docs/../spec/example_app/README") + end.to raise_error(DocPage::PageNotAllowed) end it "renders pages without metadata" do From 4ffc4359081cabe9137a6a1d632dbe519bbae995 Mon Sep 17 00:00:00 2001 From: Nick Charlton Date: Fri, 12 Aug 2022 18:12:37 +0100 Subject: [PATCH 021/137] Release version 0.18.0 --- CHANGELOG.md | 73 +++++++++++++++++++++++++++++++++++++ Gemfile.lock | 4 +- lib/administrate/version.rb | 2 +- 3 files changed, 76 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 443b11a745..70e777f8e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,79 @@ ## Changes +### 0.18.0 (August 12, 2022) + +This is a general catchup release. We've added `dart-sass` compatibility, +improved a i18n handling, dropped support for Rails 5.x and Ruby 2.6, dropped +`datetime_picker_rails` because now browser support is good enough, plus many +others. + +The following templates have changed since v0.17.0: + + app/views/administrate/application/_collection.html.erb + app/views/administrate/application/_collection_header_actions.html.erb + app/views/administrate/application/_collection_item_actions.html.erb + app/views/administrate/application/_index_header.html.erb + app/views/administrate/application/_navigation.html.erb + app/views/administrate/application/_pagination.html.erb + app/views/administrate/application/edit.html.erb + app/views/administrate/application/index.html.erb + app/views/administrate/application/show.html.erb + app/views/fields/belongs_to/_index.html.erb + app/views/fields/belongs_to/_show.html.erb + app/views/fields/date/_form.html.erb + app/views/fields/date_time/_form.html.erb + app/views/fields/has_many/_index.html.erb + app/views/fields/has_one/_form.html.erb + app/views/fields/has_one/_index.html.erb + app/views/fields/has_one/_show.html.erb + app/views/fields/polymorphic/_index.html.erb + app/views/fields/polymorphic/_show.html.erb + app/views/fields/time/_form.html.erb + app/views/fields/url/_index.html.erb + app/views/fields/url/_show.html.erb + +If your application overrides any of them, make sure to review your +custom templates to ensure that they remain compatible. + +* [DOC] [#2154] Ensure we read from sanitised paths +* [FEATURE] [#2154] Try out GitHub's code scanning tool +* [DOC] [#2243] Add guide on how to scope has_many relations +* [UI] [#2239] Move pagination into partial +* [FEATURE] [#2237] Move bundle-audit to GitHub Actions +* [i18n] [#2200] Fix HasOne association translations +* BUGFIX] [#2235] Guess correct name for namespaced associations +* [BUGFIX] [#2215] Fix typos and formatting in hiding dashboard docs +* [FEATURE] [#1941] Unify Action Checks +* [FEATURE] [#2181] Allow overriding the sample app database config +* [COMPAT] [#2201] Drop support for Rails 5.x +* [DOC] [#2225] Document how to customize Field::Select option labels +* [SECURITY] [#2227] Update Rails out of CVE-2022-32224 +* [FEATURE] [#2216] Move pagination into private method for overriding +* [FEATURE] [#2208] Enable ordering the BelongsTo fields by using `order` option. +* [i18n] [#2219] Add Slovenian translations +* [FEATURE] [#2211] Improve index eager load performance +* [COMPAT] [#2198] Dart-sass compatibility +* [COMPAT] [#2194] Drop support for Ruby 2.6, which reached EOL +* [i18n] [#2186] Correct grammar on German error messages +* [i18n] [#2183] Only include locales when bundling +* [OPTIM] [#2182] Change ApplicationController's routes's class to Set to speed up "valid_action?" +* [DOC] [#2153] How to customise the search +* [BUGFIX] [#2164] Use field.name rather than resource_name for has_one relationships +* [BUGFIX] [#2163] Check the routes before render link in collection.html +* [COMPAT] [#2161] Bump Rails dependencies from 6.1.4.6 to 6.1.5 +* [FEATURE] [#2133] Sort dashboard attributes +* [BUGFIX] [#2152] Fix typos in example view for Adding Controllers +* [UI] [#2146] Add destroy link in the show template +* [BUGFIX] [#2145] Fix table header classes of has_many field +* [COMPAT] [#2141] Fix Pundit >2.2.0 include +* [UI] [#2139] Add HTML options to the URL field +* [COMPAT] [#2144] Update Rails to 6.1.4.6 +* [UI] [#2136] Drop datetime_picker_rails and use browser fields +* [CHANGE] [#2138] Provide a stylelint config that we can tweak +* [CHANGE] [#2096] Make search easier to override and adapt to custom use cases +* [i18n] [#2114] Add i18n support for Field::HasMany + ### 0.17.0 (January 31, 2022) This release incorporates nearly a year of minor changes, starts testing diff --git a/Gemfile.lock b/Gemfile.lock index 111fe75d71..a5fad617cc 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - administrate (0.17.0) + administrate (0.18.0) actionpack (>= 5.0) actionview (>= 5.0) activerecord (>= 5.0) @@ -297,7 +297,7 @@ GEM terminal-table (3.0.2) unicode-display_width (>= 1.1.1, < 3) thor (1.2.1) - tilt (2.0.10) + tilt (2.0.11) timecop (0.9.5) tzinfo (2.0.5) concurrent-ruby (~> 1.0) diff --git a/lib/administrate/version.rb b/lib/administrate/version.rb index ab18a3eaa8..7a3e01c117 100644 --- a/lib/administrate/version.rb +++ b/lib/administrate/version.rb @@ -1,3 +1,3 @@ module Administrate - VERSION = "0.17.0".freeze + VERSION = "0.18.0".freeze end From 895d5707a5f059847300f3647b3a8a57b3891836 Mon Sep 17 00:00:00 2001 From: Monica Mateiu Date: Thu, 11 Aug 2022 08:54:11 +0100 Subject: [PATCH 022/137] remove redundant ARIA roles from elements with implicit role --- app/views/administrate/application/_collection.html.erb | 1 - app/views/administrate/application/_index_header.html.erb | 2 +- app/views/administrate/application/_navigation.html.erb | 2 +- app/views/administrate/application/edit.html.erb | 2 +- app/views/administrate/application/new.html.erb | 2 +- app/views/administrate/application/show.html.erb | 2 +- app/views/layouts/administrate/application.html.erb | 2 +- .../app/views/admin/customers/_index_header.html.erb | 2 +- spec/example_app/spec/features/log_search_spec.rb | 2 +- 9 files changed, 8 insertions(+), 9 deletions(-) diff --git a/app/views/administrate/application/_collection.html.erb b/app/views/administrate/application/_collection.html.erb index 044a53efdd..f455f01754 100644 --- a/app/views/administrate/application/_collection.html.erb +++ b/app/views/administrate/application/_collection.html.erb @@ -27,7 +27,6 @@ to display a collection of resources in an HTML table. cell-label--<%= collection_presenter.ordered_html_class(attr_name) %> cell-label--<%= "#{collection_presenter.resource_name}_#{attr_name}" %>" scope="col" - role="columnheader" aria-sort="<%= sort_order(collection_presenter.ordered_html_class(attr_name)) %>"> <%= link_to(sanitized_order_params(page, collection_field_name).merge( collection_presenter.order_params_for(attr_name, key: collection_field_name) diff --git a/app/views/administrate/application/_index_header.html.erb b/app/views/administrate/application/_index_header.html.erb index ad069cac3a..75ef3d410b 100644 --- a/app/views/administrate/application/_index_header.html.erb +++ b/app/views/administrate/application/_index_header.html.erb @@ -2,7 +2,7 @@ <%= display_resource_name(page.resource_name) %> <% end %> -