diff --git a/README.md b/README.md index 6ade19f..c178cbe 100644 --- a/README.md +++ b/README.md @@ -481,10 +481,9 @@ end In Rails 4 (or Rails 3.2 with the [strong_parameters](https://github.com/rails/strong_parameters) gem), -mass-assignment protection is handled in the controller. -Pundit helps you permit different users to set different attributes. Don't -forget to provide your policy an instance of object or a class so correct -permissions could be loaded. +mass-assignment protection is handled in the controller. With Pundit you can +control which attributes a user has access to update via your policies. You can +set up a `permitted_attributes` method in your policy like this: ```ruby # app/policies/post_policy.rb @@ -497,12 +496,16 @@ class PostPolicy < ApplicationPolicy end end end +``` +You can now retrieve these attributes from the policy: + +```ruby # app/controllers/posts_controller.rb class PostsController < ApplicationController def update @post = Post.find(params[:id]) - if @post.update(post_params) + if @post.update_attributes(post_params) redirect_to @post else render :edit @@ -512,7 +515,23 @@ class PostsController < ApplicationController private def post_params - params.require(:post).permit(*policy(@post || Post).permitted_attributes) + params.require(:post).permit(policy(@post).permitted_attributes) + end +end +``` + +However, this is a bit cumbersome, so Pundit provides a convenient helper method: + +```ruby +# app/controllers/posts_controller.rb +class PostsController < ApplicationController + def update + @post = Post.find(params[:id]) + if @post.update_attributes(permitted_attributes(@post)) + redirect_to @post + else + render :edit + end end end ``` diff --git a/lib/pundit.rb b/lib/pundit.rb index d21f203..63c5437 100644 --- a/lib/pundit.rb +++ b/lib/pundit.rb @@ -77,6 +77,7 @@ module Pundit hide_action :authorize hide_action :verify_authorized hide_action :verify_policy_scoped + hide_action :permitted_attributes hide_action :pundit_user end end @@ -115,6 +116,11 @@ module Pundit policies[record] ||= Pundit.policy!(pundit_user, record) end + def permitted_attributes(record) + name = record.class.to_s.demodulize.underscore + params.require(name).permit(policy(record).permitted_attributes) + end + def policies @_pundit_policies ||= {} end diff --git a/pundit.gemspec b/pundit.gemspec index 035a2ec..93e512a 100644 --- a/pundit.gemspec +++ b/pundit.gemspec @@ -20,6 +20,7 @@ Gem::Specification.new do |gem| gem.add_dependency "activesupport", ">= 3.0.0" gem.add_development_dependency "activemodel", ">= 3.0.0" + gem.add_development_dependency "actionpack", ">= 3.0.0" gem.add_development_dependency "bundler", "~> 1.3" gem.add_development_dependency "rspec", ">=2.0.0" gem.add_development_dependency "pry" diff --git a/spec/pundit_spec.rb b/spec/pundit_spec.rb index 5f39728..04cecca 100644 --- a/spec/pundit_spec.rb +++ b/spec/pundit_spec.rb @@ -271,7 +271,7 @@ describe Pundit do end end - describe ".policy" do + describe "#policy" do it "returns an instantiated policy" do policy = controller.policy(post) expect(policy.user).to eq user @@ -290,7 +290,7 @@ describe Pundit do end end - describe ".policy_scope" do + describe "#policy_scope" do it "returns an instantiated policy scope" do expect(controller.policy_scope(Post)).to eq :published end @@ -306,4 +306,13 @@ describe Pundit do expect(controller.policy_scope(Post)).to eq new_scope end end + + describe "#permitted_attributes" do + it "checks policy for permitted attributes" do + params = ActionController::Parameters.new({ action: 'update', post: { title: 'Hello', votes: 5, admin: true } }) + + expect(Controller.new(user, params).permitted_attributes(post)).to eq({ 'title' => 'Hello', 'votes' => 5 }) + expect(Controller.new(double, params).permitted_attributes(post)).to eq({ 'votes' => 5 }) + end + end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index ac757a5..0d4d6d3 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,10 +1,13 @@ require "pundit" require "pundit/rspec" +require "rack" +require "rack/test" require "pry" require "active_support" require "active_support/core_ext" require "active_model/naming" +require "action_controller/metal/strong_parameters" I18n.enforce_available_locales = false @@ -32,6 +35,13 @@ class PostPolicy < Struct.new(:user, :post) def show? true end + def permitted_attributes + if post.user == user + [:title, :votes] + else + [:votes] + end + end end class PostPolicy::Scope < Struct.new(:user, :scope) def resolve