├── .circleci └── config.yml ├── .gitignore ├── .rspec ├── .rubocop.yml ├── Appraisals ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── Gemfile ├── Gemfile.lock ├── LICENSE.txt ├── README.md ├── Rakefile ├── bin ├── console └── setup ├── gemfiles ├── .bundle │ └── config ├── rails_4_2.gemfile ├── rails_4_2.gemfile.lock ├── rails_5_0.gemfile ├── rails_5_0.gemfile.lock ├── rails_5_1.gemfile ├── rails_5_1.gemfile.lock ├── rails_5_2.gemfile ├── rails_5_2.gemfile.lock ├── rails_6_0.gemfile └── rails_6_0.gemfile.lock ├── lib ├── generators │ ├── moderate_parameters │ │ └── install_generator.rb │ └── templates │ │ └── moderate_parameters.rb ├── moderate_parameters.rb └── moderate_parameters │ ├── breadcrumbs.rb │ ├── logger.rb │ ├── parameters.rb │ └── version.rb ├── log └── .gitkeep ├── moderate_parameters.gemspec └── spec ├── moderate_parameters_spec.rb └── spec_helper.rb /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Ruby CircleCI 2.0 configuration file 2 | # 3 | # Check https://circleci.com/docs/2.0/language-ruby/ for more details 4 | # 5 | version: 2 6 | jobs: 7 | build: 8 | docker: 9 | # specify the version you desire here 10 | - image: circleci/ruby:2.6.1 11 | environment: 12 | BUNDLE_JOBS: 3 13 | BUNDLE_RETRY: 3 14 | BUNDLE_PATH: vendor/bundle 15 | RAILS_ENV: test 16 | 17 | # Specify service dependencies here if necessary 18 | # CircleCI maintains a library of pre-built images 19 | # documented at https://circleci.com/docs/2.0/circleci-images/ 20 | # - image: circleci/postgres:9.4 21 | 22 | working_directory: ~/repo 23 | 24 | steps: 25 | - checkout 26 | # Download and cache dependencies 27 | - restore_cache: 28 | keys: 29 | - v1-dependencies-{{ checksum "Gemfile.lock" }} 30 | # fallback to using the latest cache if no exact match is found 31 | - v1-dependencies- 32 | 33 | - run: 34 | name: Configure Bundler 35 | command: | 36 | echo 'export BUNDLER_VERSION=$(cat Gemfile.lock | tail -1 | tr -d " ")' >> $BASH_ENV 37 | source $BASH_ENV 38 | gem install bundler 39 | 40 | - run: 41 | name: install dependencies 42 | command: | 43 | bundle install --jobs=4 --retry=3 --path vendor/bundle 44 | 45 | - save_cache: 46 | paths: 47 | - ./vendor/bundle 48 | key: v1-dependencies-{{ checksum "Gemfile.lock" }} 49 | 50 | # run tests! 51 | - run: 52 | name: run tests 53 | command: | 54 | mkdir /tmp/test-results 55 | TEST_FILES="$(circleci tests glob "spec/**/*_spec.rb" | \ 56 | circleci tests split --split-by=timings)" 57 | 58 | bundle exec rspec \ 59 | --format progress \ 60 | --format RspecJunitFormatter \ 61 | --out /tmp/test-results/rspec.xml \ 62 | --format progress \ 63 | $TEST_FILES 64 | 65 | # collect reports 66 | - store_test_results: 67 | path: /tmp/test-results 68 | - store_artifacts: 69 | path: /tmp/test-results 70 | destination: test-results -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /_yardoc/ 4 | /coverage/ 5 | /doc/ 6 | /pkg/ 7 | /spec/reports/ 8 | /tmp/ 9 | 10 | *.gem 11 | # rspec failure tracking 12 | .rspec_status 13 | *.log 14 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --format documentation 2 | --color 3 | --require spec_helper 4 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | Metrics/BlockLength: 2 | Exclude: 3 | - 'spec/**/*' 4 | - 'moderate_parameters.gemspec' -------------------------------------------------------------------------------- /Appraisals: -------------------------------------------------------------------------------- 1 | appraise 'rails-4-2' do 2 | gem 'actionpack', '4.2.11.1' 3 | gem 'activemodel', '4.2.11.1' 4 | gem 'activesupport', '4.2.11.1' 5 | gem 'railties', '4.2.11.1' 6 | end 7 | 8 | appraise 'rails-5-0' do 9 | gem 'actionpack', '5.0.7.2' 10 | gem 'activemodel', '5.0.7.2' 11 | gem 'activesupport', '5.0.7.2' 12 | gem 'railties', '5.0.7.2' 13 | end 14 | 15 | appraise 'rails-5-1' do 16 | gem 'actionpack', '5.1.7' 17 | gem 'activemodel', '5.1.7' 18 | gem 'activesupport', '5.1.7' 19 | gem 'railties', '5.1.7' 20 | end 21 | 22 | appraise 'rails-5-2' do 23 | gem 'actionpack', '5.2.3' 24 | gem 'activemodel', '5.2.3' 25 | gem 'activesupport', '5.2.3' 26 | gem 'railties', '5.2.3' 27 | end 28 | 29 | appraise 'rails-6-0' do 30 | gem 'actionpack', '6.0.0' 31 | gem 'activemodel', '6.0.0' 32 | gem 'activesupport', '6.0.0' 33 | gem 'railties', '6.0.0' 34 | end 35 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### 0.2.1 - 2019-08-25 2 | 3 | * Bugfix deprecation warning with already defined constant 4 | 5 | ### 0.2.0 - 2019-08-25 6 | 7 | * Add Breadcrumbs functionality for mutation logging 8 | * Add generator for Breadcrumbs initializer config template 9 | 10 | ### 0.1.0 - 2019-03-25 11 | 12 | * Init Project 13 | * moderate_parameters.log 14 | * Extend ActionController::Parameters functionality 15 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at mike@hint.io. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: http://contributor-covenant.org 74 | [version]: http://contributor-covenant.org/version/1/4/ 75 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } 4 | 5 | # Specify your gem's dependencies in moderate_parameters.gemspec 6 | gemspec 7 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | moderate_parameters (0.4.1) 5 | actionpack (>= 4.2, < 6.2) 6 | activemodel (>= 4.2, < 6.2) 7 | activesupport (>= 4.2, < 6.2) 8 | nokogiri (>= 1.13.9) 9 | railties (>= 4.2, < 6.2) 10 | 11 | GEM 12 | remote: https://rubygems.org/ 13 | specs: 14 | actionpack (6.1.7) 15 | actionview (= 6.1.7) 16 | activesupport (= 6.1.7) 17 | rack (~> 2.0, >= 2.0.9) 18 | rack-test (>= 0.6.3) 19 | rails-dom-testing (~> 2.0) 20 | rails-html-sanitizer (~> 1.0, >= 1.2.0) 21 | actionview (6.1.7) 22 | activesupport (= 6.1.7) 23 | builder (~> 3.1) 24 | erubi (~> 1.4) 25 | rails-dom-testing (~> 2.0) 26 | rails-html-sanitizer (~> 1.1, >= 1.2.0) 27 | activemodel (6.1.7) 28 | activesupport (= 6.1.7) 29 | activesupport (6.1.7) 30 | concurrent-ruby (~> 1.0, >= 1.0.2) 31 | i18n (>= 1.6, < 2) 32 | minitest (>= 5.1) 33 | tzinfo (~> 2.0) 34 | zeitwerk (~> 2.3) 35 | appraisal (2.2.0) 36 | bundler 37 | rake 38 | thor (>= 0.14.0) 39 | builder (3.2.4) 40 | coderay (1.1.2) 41 | concurrent-ruby (1.1.10) 42 | crass (1.0.6) 43 | diff-lcs (1.3) 44 | erubi (1.11.0) 45 | i18n (1.12.0) 46 | concurrent-ruby (~> 1.0) 47 | loofah (2.19.0) 48 | crass (~> 1.0.2) 49 | nokogiri (>= 1.5.9) 50 | method_source (0.9.2) 51 | mini_portile2 (2.8.0) 52 | minitest (5.16.3) 53 | nokogiri (1.13.9) 54 | mini_portile2 (~> 2.8.0) 55 | racc (~> 1.4) 56 | pry (0.12.2) 57 | coderay (~> 1.1.0) 58 | method_source (~> 0.9.0) 59 | racc (1.6.0) 60 | rack (2.2.4) 61 | rack-test (2.0.2) 62 | rack (>= 1.3) 63 | rails-dom-testing (2.0.3) 64 | activesupport (>= 4.2.0) 65 | nokogiri (>= 1.6) 66 | rails-html-sanitizer (1.4.3) 67 | loofah (~> 2.3) 68 | railties (6.1.7) 69 | actionpack (= 6.1.7) 70 | activesupport (= 6.1.7) 71 | method_source 72 | rake (>= 12.2) 73 | thor (~> 1.0) 74 | rake (13.0.1) 75 | rspec (3.9.0) 76 | rspec-core (~> 3.9.0) 77 | rspec-expectations (~> 3.9.0) 78 | rspec-mocks (~> 3.9.0) 79 | rspec-core (3.9.1) 80 | rspec-support (~> 3.9.1) 81 | rspec-expectations (3.9.0) 82 | diff-lcs (>= 1.2.0, < 2.0) 83 | rspec-support (~> 3.9.0) 84 | rspec-mocks (3.9.1) 85 | diff-lcs (>= 1.2.0, < 2.0) 86 | rspec-support (~> 3.9.0) 87 | rspec-support (3.9.2) 88 | rspec_junit_formatter (0.4.1) 89 | rspec-core (>= 2, < 4, != 2.12.0) 90 | thor (1.0.1) 91 | tzinfo (2.0.5) 92 | concurrent-ruby (~> 1.0) 93 | zeitwerk (2.6.6) 94 | 95 | PLATFORMS 96 | ruby 97 | 98 | DEPENDENCIES 99 | appraisal (= 2.2.0) 100 | bundler (~> 2.0) 101 | moderate_parameters! 102 | pry (~> 0.12.2) 103 | rake (~> 13.0) 104 | rspec (~> 3.0) 105 | rspec_junit_formatter (= 0.4.1) 106 | 107 | BUNDLED WITH 108 | 2.1.4 109 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 Hint Media Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Moderate Parameters 3 |

4 | 5 | 6 | By [Hint.io](https://hint.io) 7 | 8 | [![Gem Version](https://badge.fury.io/rb/moderate_parameters.svg)](https://badge.fury.io/rb/moderate_parameters) ![CI](https://github.com/hintmedia/moderate_parameters/workflows/CI/badge.svg) ![Appraisals](https://github.com/hintmedia/moderate_parameters/workflows/Appraisals/badge.svg) [![Maintainability](https://api.codeclimate.com/v1/badges/4971eb01d5bd98dbac8b/maintainability)](https://codeclimate.com/github/hintmedia/moderate_parameters/maintainability) 9 | 10 | In our experience with [UpgradeRails](https://www.upgraderails.com), the migration from [protected_attributes](https://github.com/rails/protected_attributes) to [strong_parameters](https://api.rubyonrails.org/classes/ActionController/StrongParameters.html) can leave more questions than answers. It can be difficult to determine what data is originating from within the app and what is coming from the internet. 11 | 12 | Moderate Parameters is a set of tools providing logging of data sources in the controller by extending `ActionController::Parameters` functionality. 13 | 14 | ## Installation 15 | 16 | Add this line to your application's Gemfile: 17 | 18 | ```ruby 19 | gem 'moderate_parameters' 20 | ``` 21 | 22 | And then execute: 23 | 24 | $ bundle 25 | 26 | Or install it yourself as: 27 | 28 | $ gem install moderate_parameters 29 | 30 | Then add the initializer by running: 31 | 32 | $ bundle exec rails g moderate_parameters:install 33 | 34 | This will add an initializer to your rails app for turning on/off functionality. 35 | 36 | ## Usage 37 | 38 | Given a form at `/people/new` that submits data to the `PeopleController#create` action like so: 39 | 40 | ```ruby 41 | { person: { name: 'Kyle', age: '26', height: '180' } } 42 | ``` 43 | 44 | With a model that looks like: 45 | 46 | ```ruby 47 | class Person < ActiveRecord::Base 48 | attr_accessible :name, :age, :height 49 | 50 | . . . 51 | 52 | end 53 | ``` 54 | 55 | And a controller looks like this: 56 | 57 | ```ruby 58 | class PeopleController < ActionController::Base 59 | def create 60 | Person.create(params[:person]) 61 | end 62 | 63 | . . . 64 | 65 | end 66 | ``` 67 | 68 | We can add `moderate_parameters` by following the `strong_parameters` implementation method with a couple slight changes. 69 | 70 | Add a private params method for the controller calling `moderate` (with `controller_name` and `action_name` as the first two args) instead of `permit`: 71 | 72 | ```ruby 73 | class PeopleController < ActionController::Base 74 | def create 75 | Person.create(person_params) # Was Person.create(params[:person]) 76 | end 77 | 78 | . . . 79 | 80 | private 81 | 82 | def person_params 83 | params.require(:person).moderate(controller_name, action_name, :name) 84 | end 85 | end 86 | ``` 87 | 88 | This will cause the `person_params` to flow the same way they did before (getting passed to the model without interruption), 89 | but the params that are not included in the argument of `moderate` will be logged to `/log/moderate_params.log` 90 | 91 | Meaning that, after submitting the aforementioned data, our `moderate_parameters.log` will look like so: 92 | 93 | people#create Top Level is missing: age 94 | people#create Top Level is missing: height 95 | 96 | We can fix this by adding `age` and `height` to `person_params` like so: 97 | 98 | ```ruby 99 | class PeopleController < ActionController::Base 100 | def create 101 | Person.create(person_params) 102 | end 103 | 104 | . . . 105 | 106 | private 107 | 108 | def person_params 109 | params.require(:person).moderate(controller_name, action_name, :name, :age, :height) 110 | end 111 | end 112 | ``` 113 | 114 | We can then hit submit data from the form at `/people/new` and see that no new lines are added to the `moderate_parameters.log` file. 115 | 116 | This means that we can remove `moderate_parameters` and move to using `permit` as the final migration step of `strong_parameters`: 117 | 118 | ```ruby 119 | class PeopleController < ActionController::Base 120 | def create 121 | Person.create(person_params) 122 | end 123 | 124 | . . . 125 | 126 | private 127 | 128 | def person_params 129 | params.require(:person).permit(:name, :age, :height) 130 | end 131 | end 132 | ``` 133 | 134 | It is only _**AFTER**_ this final step of the `strong_parameters` migration has been completed that you can safely remove the `protected_attributes` line in the model: 135 | 136 | ```ruby 137 | class Person < ActiveRecord::Base 138 | # attr_accessible :name, :age, :height 139 | 140 | . . . 141 | 142 | end 143 | ``` 144 | 145 | ## Contributing 146 | 147 | Bug reports and pull requests are welcome on GitHub at https://github.com/hintmedia/moderate_parameters. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct. 148 | 149 | ## License 150 | 151 | The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). 152 | 153 | ## Code of Conduct 154 | 155 | Everyone interacting in the moderate_parameters project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/hintmedia/moderate_parameters/blob/master/CODE_OF_CONDUCT.md). 156 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require "rspec/core/rake_task" 3 | 4 | RSpec::Core::RakeTask.new(:spec) 5 | 6 | task :default => :spec 7 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "bundler/setup" 4 | require "moderate_parameters" 5 | 6 | # You can add fixtures and/or initialization code here to make experimenting 7 | # with your gem easier. You can also use a different console, if you like. 8 | 9 | # (If you use this, don't forget to add pry to your Gemfile!) 10 | # require "pry" 11 | # Pry.start 12 | 13 | require "irb" 14 | IRB.start(__FILE__) 15 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | bundle install 7 | 8 | # Do any other automated setup that you need to do here 9 | -------------------------------------------------------------------------------- /gemfiles/.bundle/config: -------------------------------------------------------------------------------- 1 | --- 2 | BUNDLE_RETRY: "1" 3 | -------------------------------------------------------------------------------- /gemfiles/rails_4_2.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "actionpack", "4.2.11.1" 6 | gem "activemodel", "4.2.11.1" 7 | gem "activesupport", "4.2.11.1" 8 | gem "railties", "4.2.11.1" 9 | 10 | gemspec path: "../" 11 | -------------------------------------------------------------------------------- /gemfiles/rails_4_2.gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: .. 3 | specs: 4 | moderate_parameters (0.2.1) 5 | actionpack (>= 4.2, < 6.1) 6 | activemodel (>= 4.2, < 6.1) 7 | activesupport (>= 4.2, < 6.1) 8 | railties (>= 4.2, < 6.1) 9 | 10 | GEM 11 | remote: https://rubygems.org/ 12 | specs: 13 | actionpack (4.2.11.1) 14 | actionview (= 4.2.11.1) 15 | activesupport (= 4.2.11.1) 16 | rack (~> 1.6) 17 | rack-test (~> 0.6.2) 18 | rails-dom-testing (~> 1.0, >= 1.0.5) 19 | rails-html-sanitizer (~> 1.0, >= 1.0.2) 20 | actionview (4.2.11.1) 21 | activesupport (= 4.2.11.1) 22 | builder (~> 3.1) 23 | erubis (~> 2.7.0) 24 | rails-dom-testing (~> 1.0, >= 1.0.5) 25 | rails-html-sanitizer (~> 1.0, >= 1.0.3) 26 | activemodel (4.2.11.1) 27 | activesupport (= 4.2.11.1) 28 | builder (~> 3.1) 29 | activesupport (4.2.11.1) 30 | i18n (~> 0.7) 31 | minitest (~> 5.1) 32 | thread_safe (~> 0.3, >= 0.3.4) 33 | tzinfo (~> 1.1) 34 | appraisal (2.2.0) 35 | bundler 36 | rake 37 | thor (>= 0.14.0) 38 | builder (3.2.3) 39 | coderay (1.1.2) 40 | concurrent-ruby (1.1.5) 41 | crass (1.0.5) 42 | diff-lcs (1.3) 43 | erubis (2.7.0) 44 | i18n (0.9.5) 45 | concurrent-ruby (~> 1.0) 46 | loofah (2.3.1) 47 | crass (~> 1.0.2) 48 | nokogiri (>= 1.5.9) 49 | method_source (0.9.2) 50 | mini_portile2 (2.4.0) 51 | minitest (5.12.2) 52 | nokogiri (1.10.4) 53 | mini_portile2 (~> 2.4.0) 54 | pry (0.12.2) 55 | coderay (~> 1.1.0) 56 | method_source (~> 0.9.0) 57 | rack (1.6.11) 58 | rack-test (0.6.3) 59 | rack (>= 1.0) 60 | rails-deprecated_sanitizer (1.0.3) 61 | activesupport (>= 4.2.0.alpha) 62 | rails-dom-testing (1.0.9) 63 | activesupport (>= 4.2.0, < 5.0) 64 | nokogiri (~> 1.6) 65 | rails-deprecated_sanitizer (>= 1.0.1) 66 | rails-html-sanitizer (1.3.0) 67 | loofah (~> 2.3) 68 | railties (4.2.11.1) 69 | actionpack (= 4.2.11.1) 70 | activesupport (= 4.2.11.1) 71 | rake (>= 0.8.7) 72 | thor (>= 0.18.1, < 2.0) 73 | rake (10.5.0) 74 | rspec (3.9.0) 75 | rspec-core (~> 3.9.0) 76 | rspec-expectations (~> 3.9.0) 77 | rspec-mocks (~> 3.9.0) 78 | rspec-core (3.9.0) 79 | rspec-support (~> 3.9.0) 80 | rspec-expectations (3.9.0) 81 | diff-lcs (>= 1.2.0, < 2.0) 82 | rspec-support (~> 3.9.0) 83 | rspec-mocks (3.9.0) 84 | diff-lcs (>= 1.2.0, < 2.0) 85 | rspec-support (~> 3.9.0) 86 | rspec-support (3.9.0) 87 | rspec_junit_formatter (0.4.1) 88 | rspec-core (>= 2, < 4, != 2.12.0) 89 | thor (0.20.3) 90 | thread_safe (0.3.6) 91 | tzinfo (1.2.5) 92 | thread_safe (~> 0.1) 93 | 94 | PLATFORMS 95 | ruby 96 | 97 | DEPENDENCIES 98 | actionpack (= 4.2.11.1) 99 | activemodel (= 4.2.11.1) 100 | activesupport (= 4.2.11.1) 101 | appraisal (= 2.2.0) 102 | bundler (~> 2.0.1) 103 | moderate_parameters! 104 | pry (~> 0.12.2) 105 | railties (= 4.2.11.1) 106 | rake (~> 10.0) 107 | rspec (~> 3.0) 108 | rspec_junit_formatter (= 0.4.1) 109 | 110 | BUNDLED WITH 111 | 2.0.1 112 | -------------------------------------------------------------------------------- /gemfiles/rails_5_0.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "actionpack", "5.0.7.2" 6 | gem "activemodel", "5.0.7.2" 7 | gem "activesupport", "5.0.7.2" 8 | gem "railties", "5.0.7.2" 9 | 10 | gemspec path: "../" 11 | -------------------------------------------------------------------------------- /gemfiles/rails_5_0.gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: .. 3 | specs: 4 | moderate_parameters (0.2.1) 5 | actionpack (>= 3.0, < 6.1) 6 | activemodel (>= 3.0, < 6.1) 7 | activesupport (>= 3.0, < 6.1) 8 | railties (>= 3.0, < 6.1) 9 | 10 | GEM 11 | remote: https://rubygems.org/ 12 | specs: 13 | actionpack (5.0.7.2) 14 | actionview (= 5.0.7.2) 15 | activesupport (= 5.0.7.2) 16 | rack (~> 2.0) 17 | rack-test (~> 0.6.3) 18 | rails-dom-testing (~> 2.0) 19 | rails-html-sanitizer (~> 1.0, >= 1.0.2) 20 | actionview (5.0.7.2) 21 | activesupport (= 5.0.7.2) 22 | builder (~> 3.1) 23 | erubis (~> 2.7.0) 24 | rails-dom-testing (~> 2.0) 25 | rails-html-sanitizer (~> 1.0, >= 1.0.3) 26 | activemodel (5.0.7.2) 27 | activesupport (= 5.0.7.2) 28 | activesupport (5.0.7.2) 29 | concurrent-ruby (~> 1.0, >= 1.0.2) 30 | i18n (>= 0.7, < 2) 31 | minitest (~> 5.1) 32 | tzinfo (~> 1.1) 33 | appraisal (2.2.0) 34 | bundler 35 | rake 36 | thor (>= 0.14.0) 37 | builder (3.2.3) 38 | coderay (1.1.2) 39 | concurrent-ruby (1.1.5) 40 | crass (1.0.5) 41 | diff-lcs (1.3) 42 | erubis (2.7.0) 43 | i18n (1.7.0) 44 | concurrent-ruby (~> 1.0) 45 | loofah (2.3.1) 46 | crass (~> 1.0.2) 47 | nokogiri (>= 1.5.9) 48 | method_source (0.9.2) 49 | mini_portile2 (2.4.0) 50 | minitest (5.12.2) 51 | nokogiri (1.10.4) 52 | mini_portile2 (~> 2.4.0) 53 | pry (0.12.2) 54 | coderay (~> 1.1.0) 55 | method_source (~> 0.9.0) 56 | rack (2.0.7) 57 | rack-test (0.6.3) 58 | rack (>= 1.0) 59 | rails-dom-testing (2.0.3) 60 | activesupport (>= 4.2.0) 61 | nokogiri (>= 1.6) 62 | rails-html-sanitizer (1.3.0) 63 | loofah (~> 2.3) 64 | railties (5.0.7.2) 65 | actionpack (= 5.0.7.2) 66 | activesupport (= 5.0.7.2) 67 | method_source 68 | rake (>= 0.8.7) 69 | thor (>= 0.18.1, < 2.0) 70 | rake (10.5.0) 71 | rspec (3.9.0) 72 | rspec-core (~> 3.9.0) 73 | rspec-expectations (~> 3.9.0) 74 | rspec-mocks (~> 3.9.0) 75 | rspec-core (3.9.0) 76 | rspec-support (~> 3.9.0) 77 | rspec-expectations (3.9.0) 78 | diff-lcs (>= 1.2.0, < 2.0) 79 | rspec-support (~> 3.9.0) 80 | rspec-mocks (3.9.0) 81 | diff-lcs (>= 1.2.0, < 2.0) 82 | rspec-support (~> 3.9.0) 83 | rspec-support (3.9.0) 84 | rspec_junit_formatter (0.4.1) 85 | rspec-core (>= 2, < 4, != 2.12.0) 86 | thor (0.20.3) 87 | thread_safe (0.3.6) 88 | tzinfo (1.2.5) 89 | thread_safe (~> 0.1) 90 | 91 | PLATFORMS 92 | ruby 93 | 94 | DEPENDENCIES 95 | actionpack (= 5.0.7.2) 96 | activemodel (= 5.0.7.2) 97 | activesupport (= 5.0.7.2) 98 | appraisal (= 2.2.0) 99 | bundler (~> 2.0.1) 100 | moderate_parameters! 101 | pry (~> 0.12.2) 102 | railties (= 5.0.7.2) 103 | rake (~> 10.0) 104 | rspec (~> 3.0) 105 | rspec_junit_formatter (= 0.4.1) 106 | 107 | BUNDLED WITH 108 | 2.0.1 109 | -------------------------------------------------------------------------------- /gemfiles/rails_5_1.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "actionpack", "5.1.7" 6 | gem "activemodel", "5.1.7" 7 | gem "activesupport", "5.1.7" 8 | gem "railties", "5.1.7" 9 | 10 | gemspec path: "../" 11 | -------------------------------------------------------------------------------- /gemfiles/rails_5_1.gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: .. 3 | specs: 4 | moderate_parameters (0.2.1) 5 | actionpack (>= 3.0, < 6.1) 6 | activemodel (>= 3.0, < 6.1) 7 | activesupport (>= 3.0, < 6.1) 8 | railties (>= 3.0, < 6.1) 9 | 10 | GEM 11 | remote: https://rubygems.org/ 12 | specs: 13 | actionpack (5.1.7) 14 | actionview (= 5.1.7) 15 | activesupport (= 5.1.7) 16 | rack (~> 2.0) 17 | rack-test (>= 0.6.3) 18 | rails-dom-testing (~> 2.0) 19 | rails-html-sanitizer (~> 1.0, >= 1.0.2) 20 | actionview (5.1.7) 21 | activesupport (= 5.1.7) 22 | builder (~> 3.1) 23 | erubi (~> 1.4) 24 | rails-dom-testing (~> 2.0) 25 | rails-html-sanitizer (~> 1.0, >= 1.0.3) 26 | activemodel (5.1.7) 27 | activesupport (= 5.1.7) 28 | activesupport (5.1.7) 29 | concurrent-ruby (~> 1.0, >= 1.0.2) 30 | i18n (>= 0.7, < 2) 31 | minitest (~> 5.1) 32 | tzinfo (~> 1.1) 33 | appraisal (2.2.0) 34 | bundler 35 | rake 36 | thor (>= 0.14.0) 37 | builder (3.2.3) 38 | coderay (1.1.2) 39 | concurrent-ruby (1.1.5) 40 | crass (1.0.5) 41 | diff-lcs (1.3) 42 | erubi (1.9.0) 43 | i18n (1.7.0) 44 | concurrent-ruby (~> 1.0) 45 | loofah (2.3.1) 46 | crass (~> 1.0.2) 47 | nokogiri (>= 1.5.9) 48 | method_source (0.9.2) 49 | mini_portile2 (2.4.0) 50 | minitest (5.12.2) 51 | nokogiri (1.10.4) 52 | mini_portile2 (~> 2.4.0) 53 | pry (0.12.2) 54 | coderay (~> 1.1.0) 55 | method_source (~> 0.9.0) 56 | rack (2.0.7) 57 | rack-test (1.1.0) 58 | rack (>= 1.0, < 3) 59 | rails-dom-testing (2.0.3) 60 | activesupport (>= 4.2.0) 61 | nokogiri (>= 1.6) 62 | rails-html-sanitizer (1.3.0) 63 | loofah (~> 2.3) 64 | railties (5.1.7) 65 | actionpack (= 5.1.7) 66 | activesupport (= 5.1.7) 67 | method_source 68 | rake (>= 0.8.7) 69 | thor (>= 0.18.1, < 2.0) 70 | rake (10.5.0) 71 | rspec (3.9.0) 72 | rspec-core (~> 3.9.0) 73 | rspec-expectations (~> 3.9.0) 74 | rspec-mocks (~> 3.9.0) 75 | rspec-core (3.9.0) 76 | rspec-support (~> 3.9.0) 77 | rspec-expectations (3.9.0) 78 | diff-lcs (>= 1.2.0, < 2.0) 79 | rspec-support (~> 3.9.0) 80 | rspec-mocks (3.9.0) 81 | diff-lcs (>= 1.2.0, < 2.0) 82 | rspec-support (~> 3.9.0) 83 | rspec-support (3.9.0) 84 | rspec_junit_formatter (0.4.1) 85 | rspec-core (>= 2, < 4, != 2.12.0) 86 | thor (0.20.3) 87 | thread_safe (0.3.6) 88 | tzinfo (1.2.5) 89 | thread_safe (~> 0.1) 90 | 91 | PLATFORMS 92 | ruby 93 | 94 | DEPENDENCIES 95 | actionpack (= 5.1.7) 96 | activemodel (= 5.1.7) 97 | activesupport (= 5.1.7) 98 | appraisal (= 2.2.0) 99 | bundler (~> 2.0.1) 100 | moderate_parameters! 101 | pry (~> 0.12.2) 102 | railties (= 5.1.7) 103 | rake (~> 10.0) 104 | rspec (~> 3.0) 105 | rspec_junit_formatter (= 0.4.1) 106 | 107 | BUNDLED WITH 108 | 2.0.1 109 | -------------------------------------------------------------------------------- /gemfiles/rails_5_2.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "actionpack", "5.2.3" 6 | gem "activemodel", "5.2.3" 7 | gem "activesupport", "5.2.3" 8 | gem "railties", "5.2.3" 9 | 10 | gemspec path: "../" 11 | -------------------------------------------------------------------------------- /gemfiles/rails_5_2.gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: .. 3 | specs: 4 | moderate_parameters (0.2.1) 5 | actionpack (>= 3.0, < 6.1) 6 | activemodel (>= 3.0, < 6.1) 7 | activesupport (>= 3.0, < 6.1) 8 | railties (>= 3.0, < 6.1) 9 | 10 | GEM 11 | remote: https://rubygems.org/ 12 | specs: 13 | actionpack (5.2.3) 14 | actionview (= 5.2.3) 15 | activesupport (= 5.2.3) 16 | rack (~> 2.0) 17 | rack-test (>= 0.6.3) 18 | rails-dom-testing (~> 2.0) 19 | rails-html-sanitizer (~> 1.0, >= 1.0.2) 20 | actionview (5.2.3) 21 | activesupport (= 5.2.3) 22 | builder (~> 3.1) 23 | erubi (~> 1.4) 24 | rails-dom-testing (~> 2.0) 25 | rails-html-sanitizer (~> 1.0, >= 1.0.3) 26 | activemodel (5.2.3) 27 | activesupport (= 5.2.3) 28 | activesupport (5.2.3) 29 | concurrent-ruby (~> 1.0, >= 1.0.2) 30 | i18n (>= 0.7, < 2) 31 | minitest (~> 5.1) 32 | tzinfo (~> 1.1) 33 | appraisal (2.2.0) 34 | bundler 35 | rake 36 | thor (>= 0.14.0) 37 | builder (3.2.3) 38 | coderay (1.1.2) 39 | concurrent-ruby (1.1.5) 40 | crass (1.0.5) 41 | diff-lcs (1.3) 42 | erubi (1.9.0) 43 | i18n (1.7.0) 44 | concurrent-ruby (~> 1.0) 45 | loofah (2.3.1) 46 | crass (~> 1.0.2) 47 | nokogiri (>= 1.5.9) 48 | method_source (0.9.2) 49 | mini_portile2 (2.4.0) 50 | minitest (5.12.2) 51 | nokogiri (1.10.4) 52 | mini_portile2 (~> 2.4.0) 53 | pry (0.12.2) 54 | coderay (~> 1.1.0) 55 | method_source (~> 0.9.0) 56 | rack (2.0.7) 57 | rack-test (1.1.0) 58 | rack (>= 1.0, < 3) 59 | rails-dom-testing (2.0.3) 60 | activesupport (>= 4.2.0) 61 | nokogiri (>= 1.6) 62 | rails-html-sanitizer (1.3.0) 63 | loofah (~> 2.3) 64 | railties (5.2.3) 65 | actionpack (= 5.2.3) 66 | activesupport (= 5.2.3) 67 | method_source 68 | rake (>= 0.8.7) 69 | thor (>= 0.19.0, < 2.0) 70 | rake (10.5.0) 71 | rspec (3.9.0) 72 | rspec-core (~> 3.9.0) 73 | rspec-expectations (~> 3.9.0) 74 | rspec-mocks (~> 3.9.0) 75 | rspec-core (3.9.0) 76 | rspec-support (~> 3.9.0) 77 | rspec-expectations (3.9.0) 78 | diff-lcs (>= 1.2.0, < 2.0) 79 | rspec-support (~> 3.9.0) 80 | rspec-mocks (3.9.0) 81 | diff-lcs (>= 1.2.0, < 2.0) 82 | rspec-support (~> 3.9.0) 83 | rspec-support (3.9.0) 84 | rspec_junit_formatter (0.4.1) 85 | rspec-core (>= 2, < 4, != 2.12.0) 86 | thor (0.20.3) 87 | thread_safe (0.3.6) 88 | tzinfo (1.2.5) 89 | thread_safe (~> 0.1) 90 | 91 | PLATFORMS 92 | ruby 93 | 94 | DEPENDENCIES 95 | actionpack (= 5.2.3) 96 | activemodel (= 5.2.3) 97 | activesupport (= 5.2.3) 98 | appraisal (= 2.2.0) 99 | bundler (~> 2.0.1) 100 | moderate_parameters! 101 | pry (~> 0.12.2) 102 | railties (= 5.2.3) 103 | rake (~> 10.0) 104 | rspec (~> 3.0) 105 | rspec_junit_formatter (= 0.4.1) 106 | 107 | BUNDLED WITH 108 | 2.0.1 109 | -------------------------------------------------------------------------------- /gemfiles/rails_6_0.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "actionpack", "6.0.0" 6 | gem "activemodel", "6.0.0" 7 | gem "activesupport", "6.0.0" 8 | gem "railties", "6.0.0" 9 | 10 | gemspec path: "../" 11 | -------------------------------------------------------------------------------- /gemfiles/rails_6_0.gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: .. 3 | specs: 4 | moderate_parameters (0.2.1) 5 | actionpack (>= 3.0, < 6.1) 6 | activemodel (>= 3.0, < 6.1) 7 | activesupport (>= 3.0, < 6.1) 8 | railties (>= 3.0, < 6.1) 9 | 10 | GEM 11 | remote: https://rubygems.org/ 12 | specs: 13 | actionpack (6.0.0) 14 | actionview (= 6.0.0) 15 | activesupport (= 6.0.0) 16 | rack (~> 2.0) 17 | rack-test (>= 0.6.3) 18 | rails-dom-testing (~> 2.0) 19 | rails-html-sanitizer (~> 1.0, >= 1.2.0) 20 | actionview (6.0.0) 21 | activesupport (= 6.0.0) 22 | builder (~> 3.1) 23 | erubi (~> 1.4) 24 | rails-dom-testing (~> 2.0) 25 | rails-html-sanitizer (~> 1.1, >= 1.2.0) 26 | activemodel (6.0.0) 27 | activesupport (= 6.0.0) 28 | activesupport (6.0.0) 29 | concurrent-ruby (~> 1.0, >= 1.0.2) 30 | i18n (>= 0.7, < 2) 31 | minitest (~> 5.1) 32 | tzinfo (~> 1.1) 33 | zeitwerk (~> 2.1, >= 2.1.8) 34 | appraisal (2.2.0) 35 | bundler 36 | rake 37 | thor (>= 0.14.0) 38 | builder (3.2.3) 39 | coderay (1.1.2) 40 | concurrent-ruby (1.1.5) 41 | crass (1.0.5) 42 | diff-lcs (1.3) 43 | erubi (1.9.0) 44 | i18n (1.7.0) 45 | concurrent-ruby (~> 1.0) 46 | loofah (2.3.1) 47 | crass (~> 1.0.2) 48 | nokogiri (>= 1.5.9) 49 | method_source (0.9.2) 50 | mini_portile2 (2.4.0) 51 | minitest (5.12.2) 52 | nokogiri (1.10.4) 53 | mini_portile2 (~> 2.4.0) 54 | pry (0.12.2) 55 | coderay (~> 1.1.0) 56 | method_source (~> 0.9.0) 57 | rack (2.0.7) 58 | rack-test (1.1.0) 59 | rack (>= 1.0, < 3) 60 | rails-dom-testing (2.0.3) 61 | activesupport (>= 4.2.0) 62 | nokogiri (>= 1.6) 63 | rails-html-sanitizer (1.3.0) 64 | loofah (~> 2.3) 65 | railties (6.0.0) 66 | actionpack (= 6.0.0) 67 | activesupport (= 6.0.0) 68 | method_source 69 | rake (>= 0.8.7) 70 | thor (>= 0.20.3, < 2.0) 71 | rake (10.5.0) 72 | rspec (3.9.0) 73 | rspec-core (~> 3.9.0) 74 | rspec-expectations (~> 3.9.0) 75 | rspec-mocks (~> 3.9.0) 76 | rspec-core (3.9.0) 77 | rspec-support (~> 3.9.0) 78 | rspec-expectations (3.9.0) 79 | diff-lcs (>= 1.2.0, < 2.0) 80 | rspec-support (~> 3.9.0) 81 | rspec-mocks (3.9.0) 82 | diff-lcs (>= 1.2.0, < 2.0) 83 | rspec-support (~> 3.9.0) 84 | rspec-support (3.9.0) 85 | rspec_junit_formatter (0.4.1) 86 | rspec-core (>= 2, < 4, != 2.12.0) 87 | thor (0.20.3) 88 | thread_safe (0.3.6) 89 | tzinfo (1.2.5) 90 | thread_safe (~> 0.1) 91 | zeitwerk (2.2.0) 92 | 93 | PLATFORMS 94 | ruby 95 | 96 | DEPENDENCIES 97 | actionpack (= 6.0.0) 98 | activemodel (= 6.0.0) 99 | activesupport (= 6.0.0) 100 | appraisal (= 2.2.0) 101 | bundler (~> 2.0.1) 102 | moderate_parameters! 103 | pry (~> 0.12.2) 104 | railties (= 6.0.0) 105 | rake (~> 10.0) 106 | rspec (~> 3.0) 107 | rspec_junit_formatter (= 0.4.1) 108 | 109 | BUNDLED WITH 110 | 2.0.1 111 | -------------------------------------------------------------------------------- /lib/generators/moderate_parameters/install_generator.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rails/generators/base' 4 | 5 | module ModerateParameters 6 | module Generators 7 | class InstallGenerator < Rails::Generators::Base 8 | source_root File.expand_path('../templates', __dir__) 9 | 10 | desc 'Creates a ModerateParameters initializer.' 11 | 12 | def copy_initializer 13 | template 'moderate_parameters.rb', 'config/initializers/moderate_parameters.rb' 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/generators/templates/moderate_parameters.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | ModerateParameters.configure do |config| 4 | # Enables/Disables logging occurrences of 5 | # reading/writing from ActionController::Parameters. 6 | config.breadcrumbs_enabled = false 7 | # Sets where to log the ModerateParameters output 8 | config.logger = ActiveSupport::Logger.new('log/moderate_parameters.log') 9 | end 10 | -------------------------------------------------------------------------------- /lib/moderate_parameters.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'action_controller' 4 | require 'active_support' 5 | require 'moderate_parameters/version' 6 | require 'moderate_parameters/parameters' 7 | require 'moderate_parameters/breadcrumbs' 8 | 9 | module ModerateParameters 10 | mattr_accessor :breadcrumbs_enabled 11 | @@breadcrumbs_enabled = false 12 | 13 | mattr_accessor :logger 14 | @@logger = nil 15 | 16 | def self.configure 17 | yield self 18 | end 19 | end 20 | 21 | module ActionController 22 | class Parameters 23 | prepend ModerateParameters::Breadcrumbs 24 | prepend ModerateParameters::Parameters 25 | end 26 | end 27 | 28 | require 'moderate_parameters/logger' 29 | -------------------------------------------------------------------------------- /lib/moderate_parameters/breadcrumbs.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module ModerateParameters 4 | module Breadcrumbs 5 | def []=(key, _value) 6 | internal_param_logging(key, key?(key) ? 'overwritten' : 'added', caller_locations) 7 | super 8 | end 9 | 10 | def merge!(other_hash) 11 | internal_method_logging('merge!', other_hash.keys, caller_locations) 12 | super 13 | end 14 | 15 | def reverse_merge!(other_hash) 16 | internal_method_logging('reverse_merge!', other_hash.keys, caller_locations) 17 | super 18 | end 19 | 20 | def extract!(*keys) 21 | internal_method_logging('extract!', keys, caller_locations) 22 | super 23 | end 24 | 25 | def slice!(*keys) 26 | internal_method_logging('slice!', keys, caller_locations) 27 | super 28 | end 29 | 30 | def delete(*keys, &block) 31 | internal_method_logging('delete', keys, caller_locations) 32 | super 33 | end 34 | 35 | def reject!(&block) 36 | internal_block_logging('reject!', caller_locations) 37 | super 38 | end 39 | 40 | # Alias for #reject! 41 | def delete_if(&block) 42 | internal_block_logging('delete_if', caller_locations) 43 | super 44 | end 45 | 46 | def select!(&block) 47 | internal_block_logging('select!', caller_locations) 48 | super 49 | end 50 | 51 | # Alias for #select! 52 | def keep_if(&block) 53 | internal_block_logging('keep_if', caller_locations) 54 | super 55 | end 56 | 57 | private 58 | 59 | def needs_logged? 60 | ModerateParameters.breadcrumbs_enabled && 61 | instance_variable_get(:@moderate_params_object_id) && 62 | !permitted? 63 | end 64 | 65 | def internal_param_logging(key, action, stack_array) 66 | return unless needs_logged? 67 | 68 | ActiveSupport::Notifications.instrument('moderate_parameters') do |payload| 69 | payload[:caller_locations] = stack_array 70 | payload[:message] = "#{key} is being #{action} on: #{stack_array.join("\n")}" 71 | end 72 | end 73 | 74 | def internal_method_logging(method, args, stack_array) 75 | return unless needs_logged? 76 | 77 | ActiveSupport::Notifications.instrument('moderate_parameters') do |payload| 78 | payload[:caller_locations] = stack_array 79 | payload[:message] = "#{method} is being called with #{args} on: #{stack_array.join("\n")}" 80 | end 81 | end 82 | 83 | def internal_block_logging(method, stack_array) 84 | return unless needs_logged? 85 | 86 | ActiveSupport::Notifications.instrument('moderate_parameters') do |payload| 87 | payload[:caller_locations] = stack_array 88 | payload[:message] = "#{method} is being called with a block on: #{stack_array.join("\n")}" 89 | end 90 | end 91 | end 92 | end 93 | -------------------------------------------------------------------------------- /lib/moderate_parameters/logger.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | ActiveSupport::Notifications.subscribe('moderate_parameters') do |_, _, _, _, payload| 4 | (ModerateParameters.logger || ActiveSupport::Logger.new('/dev/null')).info( 5 | "#{payload[:controller]}##{payload[:action]} #{payload[:message]}" 6 | ) 7 | end 8 | -------------------------------------------------------------------------------- /lib/moderate_parameters/parameters.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module ModerateParameters 4 | module Parameters 5 | MP_OBJECT_ID = :@moderate_params_object_id 6 | MP_PARENT_KEY = :@moderate_params_parent_key 7 | 8 | def moderate(controller_name, action, *filters) 9 | log_duplicate_moderate_warning( 10 | caller_locations, 11 | instance_variable_get(MP_PARENT_KEY), 12 | controller_name, 13 | action 14 | ) if instance_variable_get(MP_OBJECT_ID) 15 | 16 | params = self.class.new 17 | 18 | filters.each do |filter| 19 | case filter 20 | when Symbol, String 21 | if non_scalar?(self[filter]) 22 | non_scalar_value_filter(params, filter, controller_name, action) 23 | else 24 | permitted_scalar_filter(params, filter) 25 | end 26 | when Hash 27 | cust_hash_filter(params, filter, controller_name, action) 28 | end 29 | end 30 | 31 | incoming_params_logging(params, controller_name, action) 32 | duplicate_params = dup 33 | instance_variable_set(MP_OBJECT_ID, duplicate_params.object_id) 34 | duplicate_params.permit! 35 | end 36 | 37 | def require(key) 38 | return super if key.is_a?(Array) || self[key].blank? 39 | self[key].instance_variable_set(MP_PARENT_KEY, key) 40 | super 41 | end 42 | 43 | private 44 | 45 | def write_to_log(options) 46 | ActiveSupport::Notifications.instrument('moderate_parameters') do |payload| 47 | payload.merge!(options) 48 | end 49 | end 50 | 51 | def incoming_params_logging(params, controller_name, action) 52 | unpermitted_keys(params).each do |k| 53 | write_to_log(message: "#{@context || 'Top Level'} is missing: #{k}", 54 | action: action, 55 | controller: controller_name) 56 | end 57 | end 58 | 59 | def log_duplicate_moderate_warning(stack_array, parent_key, controller_name, action) 60 | write_to_log(message: ".moderate has already been called on params.require(:#{parent_key}): #{stack_array.join("\n")}", 61 | action: action, 62 | controller: controller_name) 63 | end 64 | 65 | def non_scalar_value_filter(params, key, controller_name, action) 66 | if has_key?(key) && !permitted_scalar?(self[key]) 67 | params[key] = self[key].class.new 68 | write_to_log(message: "#{@context || 'Top Level'} is missing: #{params[key]} value for #{key}", 69 | action: action, 70 | controller: controller_name) 71 | end 72 | end 73 | 74 | def array_of_permitted_scalars?(value) 75 | if value.is_a?(Array) && value.all? { |element| permitted_scalar?(element) } 76 | return true unless block_given? 77 | 78 | yield value 79 | end 80 | end 81 | 82 | def non_scalar?(value) 83 | value.is_a?(Array) || value.is_a?(Parameters) 84 | end 85 | 86 | def permit_any_in_array(array) 87 | [].tap do |sanitized| 88 | array.each do |element| 89 | case element 90 | when ->(e) { permitted_scalar?(e) } 91 | sanitized << element 92 | when Parameters 93 | sanitized << permit_any_in_parameters(element) 94 | else 95 | # Log it 96 | end 97 | end 98 | end 99 | end 100 | 101 | def permit_any_in_parameters(params) 102 | self.class.new.tap do |sanitized| 103 | params.each do |key, value| 104 | case value 105 | when ->(v) { permitted_scalar?(v) } 106 | sanitized[key] = value 107 | when Array 108 | sanitized[key] = permit_any_in_array(value) 109 | when Parameters 110 | sanitized[key] = permit_any_in_parameters(value) 111 | else 112 | # Log It 113 | end 114 | end 115 | end 116 | end 117 | 118 | EMPTY_HASH ||= {} 119 | EMPTY_ARRAY ||= [] 120 | def cust_hash_filter(params, filter, controller_name, action) 121 | filter = filter.with_indifferent_access 122 | 123 | # Slicing filters out non-declared keys. 124 | slice(*filter.keys).each do |key, value| 125 | next unless value 126 | next unless has_key? key 127 | 128 | if filter[key] == EMPTY_ARRAY 129 | # Declaration { comment_ids: [] }. 130 | array_of_permitted_scalars?(self[key]) do |val| 131 | params[key] = val 132 | end 133 | elsif filter[key] == EMPTY_HASH 134 | # Declaration { preferences: {} }. 135 | if value.is_a?(Parameters) 136 | params[key] = permit_any_in_parameters(value) 137 | end 138 | elsif non_scalar?(value) 139 | # Declaration { user: :name } or { user: [:name, :age, { address: ... }] }. 140 | params[key] = each_element(value) do |element| 141 | element.instance_variable_set '@context', "Parent #{key}" 142 | element.moderate(controller_name, action, *Array.wrap(filter[key])) 143 | end 144 | end 145 | end 146 | end 147 | end 148 | end 149 | -------------------------------------------------------------------------------- /lib/moderate_parameters/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module ModerateParameters 4 | VERSION = '0.4.1' 5 | end 6 | -------------------------------------------------------------------------------- /log/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testdouble/moderate_parameters/77c8148d2ee726c00ad763f6c3c68a25946ad288/log/.gitkeep -------------------------------------------------------------------------------- /moderate_parameters.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | lib = File.expand_path('lib', __dir__) 4 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 5 | require 'moderate_parameters/version' 6 | 7 | Gem::Specification.new do |spec| 8 | spec.name = 'moderate_parameters' 9 | spec.version = ModerateParameters::VERSION 10 | spec.authors = ['Kyle Boe'] 11 | spec.email = ['kyle@hint.io'] 12 | 13 | spec.summary = 'Protected Attributes to Strong Parameters migration tool' 14 | spec.description = 'A tool for migrating Rails applications from Protected ' \ 15 | 'Attributes to Strong Parameters.' 16 | spec.homepage = 'https://github.com/hintmedia/moderate_parameters' 17 | spec.license = 'MIT' 18 | 19 | if spec.respond_to?(:metadata) 20 | spec.metadata['homepage_uri'] = spec.homepage 21 | spec.metadata['source_code_uri'] = 'https://github.com/hintmedia/moderate_parameters' 22 | spec.metadata['changelog_uri'] = 'https://github.com/hintmedia/moderate_parameters/blob/master/CHANGELOG.md' 23 | else 24 | raise 'RubyGems 2.0 or newer is required to protect against ' \ 25 | 'public gem pushes.' 26 | end 27 | 28 | spec.files = Dir.chdir(File.expand_path(__dir__)) do 29 | `git ls-files -z`.split("\x0").reject do |f| 30 | f.match(%r{^(test|spec|features)/}) 31 | end 32 | end 33 | spec.bindir = 'bin' 34 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } 35 | spec.require_paths = ['lib'] 36 | 37 | spec.required_ruby_version = '>= 2.3.1' 38 | 39 | spec.add_dependency 'actionpack', '>= 4.2', '< 6.2' 40 | spec.add_dependency 'activemodel', '>= 4.2', '< 6.2' 41 | spec.add_dependency 'activesupport', '>= 4.2', '< 6.2' 42 | spec.add_dependency 'railties', '>= 4.2', '< 6.2' 43 | spec.add_dependency 'nokogiri', '>= 1.13.9' 44 | 45 | spec.add_development_dependency 'bundler', '~> 2.0' 46 | spec.add_development_dependency 'pry', '~> 0.12.2' 47 | spec.add_development_dependency 'rake', '~> 13.0' 48 | spec.add_development_dependency 'rspec', '~> 3.0' 49 | spec.add_development_dependency 'rspec_junit_formatter', '0.4.1' 50 | spec.add_development_dependency 'appraisal', '2.2.0' 51 | end 52 | -------------------------------------------------------------------------------- /spec/moderate_parameters_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe ModerateParameters do 4 | let(:params) do 5 | ActionController::Parameters.new( 6 | { 7 | person: { 8 | name: 'Francesco', 9 | age: '25', 10 | sub_array: %i[foo bar], 11 | sub_hash: { baz: :bang } 12 | } 13 | } 14 | ) 15 | end 16 | let(:payload) { notification_payload_for('moderate_parameters') { subject } } 17 | let(:valid_permission_keys) { [:name, :age, { sub_array: [], sub_hash: {} }] } 18 | 19 | it 'has a version number' do 20 | expect(ModerateParameters::VERSION).to be_an_instance_of(String) 21 | end 22 | 23 | describe '::Parameters' do 24 | describe '#moderate' do 25 | let(:permission_keys) { valid_permission_keys } 26 | let(:subject) { params.require(:person).moderate('controller', 'action', *permission_keys) } 27 | it 'sets the moderate_params_object_id instance variable on the original params object' do 28 | params.require(:person).moderate('controller', 'action', *permission_keys) 29 | expect(params[:person].instance_variable_get(:@moderate_params_object_id)).to be_a Integer 30 | expect(payload[:controller]).to eql('controller') 31 | expect(payload[:action]).to eql('action') 32 | expect(payload[:message]).to start_with('.moderate has already been called on params.require(:person):') 33 | end 34 | 35 | context 'with permitted params properly specified' do 36 | it 'does not log to a file' do 37 | expect(payload).to be nil 38 | end 39 | end 40 | 41 | context 'without a top level key' do 42 | let(:permission_keys) { [:name, { sub_array: [], sub_hash: {} }] } 43 | it 'logs to a file' do 44 | expect(payload[:controller]).to eql('controller') 45 | expect(payload[:action]).to eql('action') 46 | expect(payload[:message]).to eql('Top Level is missing: age') 47 | end 48 | end 49 | 50 | context 'key present but missing array value' do 51 | let(:permission_keys) { [:name, :age, :sub_array, { sub_hash: {} }] } 52 | 53 | it 'logs to a file' do 54 | expect(payload[:controller]).to eql('controller') 55 | expect(payload[:action]).to eql('action') 56 | expect(payload[:message]).to eql('Top Level is missing: [] value for sub_array') 57 | end 58 | end 59 | 60 | context 'key present but missing hash value' do 61 | let(:permission_keys) { [:name, :age, { sub_array: [] }, :sub_hash] } 62 | 63 | it 'logs to a file' do 64 | expect(payload[:controller]).to eql('controller') 65 | expect(payload[:action]).to eql('action') 66 | expect(payload[:message]).to eql('Top Level is missing: {} value for sub_hash') 67 | end 68 | end 69 | end 70 | 71 | describe '#require' do 72 | let(:subject) { params.require(:person) } 73 | 74 | it 'sets an instance variable on the child object' do 75 | expect(subject.instance_variable_get(:@moderate_params_parent_key)).to eql(:person) 76 | end 77 | 78 | context 'when the require is passed an array' do 79 | let(:params) { ActionController::Parameters.new({ person: { foo: :bar }, other: { baz: :bang } }) } 80 | let(:subject) { params.require([:person, :other]) } 81 | 82 | it 'sets an instance variable on each child object' do 83 | expect(subject.map { |o| o.instance_variable_get(:@moderate_params_parent_key) }).to eql([:person, :other]) 84 | end 85 | end 86 | 87 | context 'when the require is called on params with a blank value' do 88 | let(:params) { ActionController::Parameters.new(person: nil) } 89 | let(:subject) { params.require(:person) } 90 | 91 | it 'sets an instance variable on each child object' do 92 | expect { subject }.to raise_error(ActionController::ParameterMissing, "param is missing or the value is empty: person") 93 | end 94 | end 95 | end 96 | end 97 | 98 | describe '::Breadcrumbs' do 99 | let(:subject) { a(params[:person]) } 100 | 101 | before(:each) do 102 | ModerateParameters.configure do |c| 103 | c.breadcrumbs_enabled = true 104 | end 105 | params.require(:person).moderate('controller', 'action', *valid_permission_keys) 106 | end 107 | 108 | describe '#[]=' do 109 | let(:relative_line) { __LINE__ + 2 } 110 | def a(test_params) 111 | test_params[:age] = nil 112 | end 113 | 114 | context 'with the key already being set' do 115 | it 'logs to a file' do 116 | expect(payload[:message]).to start_with('age is being overwritten on:') 117 | expect(payload[:caller_locations][0].to_s).to end_with("spec/moderate_parameters_spec.rb:#{relative_line}:in \`a'") 118 | end 119 | end 120 | 121 | context 'without the key already being set' do 122 | let(:params) do 123 | ActionController::Parameters.new( 124 | { 125 | person: { 126 | name: 'Francesco', 127 | sub_array: %i[foo bar], 128 | sub_hash: { baz: :bang } 129 | } 130 | } 131 | ) 132 | end 133 | 134 | it 'logs to a file' do 135 | expect(payload[:message]).to start_with('age is being added on:') 136 | expect(payload[:caller_locations][0].to_s).to end_with("spec/moderate_parameters_spec.rb:#{relative_line}:in \`a'") 137 | end 138 | end 139 | end 140 | 141 | describe '#merge!' do 142 | let(:other_hash) { { name: 'Sophie'} } 143 | let(:relative_line) { __LINE__ + 2 } 144 | def a(test_params) 145 | test_params.merge!(other_hash) 146 | end 147 | 148 | it 'logs to a file' do 149 | expect(payload[:message]).to start_with("merge! is being called with #{other_hash.keys} on:") 150 | expect(payload[:caller_locations][0].to_s).to end_with("spec/moderate_parameters_spec.rb:#{relative_line}:in \`a'") 151 | end 152 | end 153 | 154 | describe '#reverse_merge!' do 155 | let(:other_hash) { { name: 'Alyssa'} } 156 | let(:relative_line) { __LINE__ + 2 } 157 | def a(test_params) 158 | test_params.reverse_merge!(other_hash) 159 | end 160 | 161 | it 'logs to a file' do 162 | expect(payload[:message]).to start_with("reverse_merge! is being called with #{other_hash.keys} on:") 163 | expect(payload[:caller_locations][0].to_s).to end_with("spec/moderate_parameters_spec.rb:#{relative_line}:in \`a'") 164 | end 165 | end 166 | 167 | describe '#extract!' do 168 | let(:relative_line) { __LINE__ + 2 } 169 | def a(test_params) 170 | test_params.extract!(:name) 171 | end 172 | 173 | it 'logs to a file' do 174 | expect(payload[:message]).to start_with('extract! is being called with [:name] on:') 175 | expect(payload[:caller_locations][0].to_s).to end_with("spec/moderate_parameters_spec.rb:#{relative_line}:in \`a'") 176 | end 177 | end 178 | 179 | describe '#slice!' do 180 | let(:relative_line) { __LINE__ + 2 } 181 | def a(test_params) 182 | test_params.slice!(:name) 183 | end 184 | 185 | it 'logs to a file' do 186 | expect(payload[:message]).to start_with('slice! is being called with [:name] on:') 187 | expect(payload[:caller_locations][0].to_s).to end_with("spec/moderate_parameters_spec.rb:#{relative_line}:in \`a'") 188 | end 189 | end 190 | 191 | describe '#delete' do 192 | let(:relative_line) { __LINE__ + 2 } 193 | def a(test_params) 194 | test_params.delete(:name) 195 | end 196 | 197 | it 'logs to a file' do 198 | expect(payload[:message]).to start_with('delete is being called with [:name] on:') 199 | expect(payload[:caller_locations][0].to_s).to end_with("spec/moderate_parameters_spec.rb:#{relative_line}:in \`a'") 200 | end 201 | end 202 | 203 | describe '#reject!' do 204 | let(:relative_line) { __LINE__ + 2 } 205 | def a(test_params) 206 | test_params.reject! { |k, _v| k == :name } 207 | end 208 | 209 | it 'logs to a file' do 210 | expect(payload[:message]).to start_with('reject! is being called with a block on:') 211 | expect(payload[:caller_locations][0].to_s).to end_with("spec/moderate_parameters_spec.rb:#{relative_line}:in \`a'") 212 | end 213 | end 214 | 215 | describe '#delete_if' do 216 | let(:relative_line) { __LINE__ + 2 } 217 | def a(test_params) 218 | test_params.delete_if { |k, _v| k == :name } 219 | end 220 | 221 | it 'logs to a file' do 222 | expect(payload[:message]).to start_with('delete_if is being called with a block on:') 223 | expect(payload[:caller_locations][0].to_s).to end_with("spec/moderate_parameters_spec.rb:#{relative_line}:in \`a'") 224 | end 225 | end 226 | 227 | describe '#select!' do 228 | let(:relative_line) { __LINE__ + 2 } 229 | def a(test_params) 230 | test_params.select! { |k, _v| k == :name } 231 | end 232 | 233 | it 'logs to a file' do 234 | expect(payload[:message]).to start_with('select! is being called with a block on:') 235 | expect(payload[:caller_locations][0].to_s).to end_with("spec/moderate_parameters_spec.rb:#{relative_line}:in \`a'") 236 | end 237 | end 238 | 239 | describe '#keep_if' do 240 | let(:relative_line) { __LINE__ + 2 } 241 | def a(test_params) 242 | test_params.keep_if { |k, _v| k == :name } 243 | end 244 | 245 | it 'logs to a file' do 246 | expect(payload[:message]).to start_with('keep_if is being called with a block on:') 247 | expect(payload[:caller_locations][0].to_s).to end_with("spec/moderate_parameters_spec.rb:#{relative_line}:in \`a'") 248 | end 249 | end 250 | end 251 | end 252 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bundler/setup' 4 | require 'pry' 5 | require 'moderate_parameters' 6 | 7 | RSpec.configure do |config| 8 | # Enable flags like --only-failures and --next-failure 9 | config.example_status_persistence_file_path = '.rspec_status' 10 | 11 | # Disable RSpec exposing methods globally on `Module` and `main` 12 | config.disable_monkey_patching! 13 | 14 | config.expect_with :rspec do |c| 15 | c.syntax = :expect 16 | end 17 | end 18 | 19 | def notification_payload_for(notification) 20 | test_payload = nil 21 | ActiveSupport::Notifications.subscribe(notification) do |_, _, _, _, payload| 22 | test_payload = payload 23 | end 24 | 25 | yield 26 | 27 | ActiveSupport::Notifications.unsubscribe(notification) 28 | 29 | test_payload 30 | end 31 | --------------------------------------------------------------------------------