pundit-1.0.1/0000755000004100000410000000000012576036765013065 5ustar www-datawww-datapundit-1.0.1/Rakefile0000644000004100000410000000056512576036765014540 0ustar www-datawww-datarequire 'rubygems' require 'bundler/gem_tasks' require 'rspec/core/rake_task' require 'yard' desc "Run all examples" RSpec::Core::RakeTask.new(:spec) do |t| #t.rspec_path = 'bin/rspec' t.rspec_opts = %w[--color] end YARD::Rake::YardocTask.new do |t| t.files = ['lib/**/*.rb'] #t.options = ['--any', '--extra', '--opts'] # optional end task :default => [:spec] pundit-1.0.1/Gemfile0000644000004100000410000000024512576036765014361 0ustar www-datawww-datasource 'https://rubygems.org' # Specify your gem's dependencies in pundit.gemspec gem "rspec", ENV["RSPEC_VERSION"] unless ENV["RSPEC_VERSION"].to_s.empty? gemspec pundit-1.0.1/CODE_OF_CONDUCT.md0000644000004100000410000000261512576036765015670 0ustar www-datawww-data# Contributor Code of Conduct As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, age, or religion. Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team. Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers. This Code of Conduct is adapted from the [Contributor Covenant](http:contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/) pundit-1.0.1/LICENSE.txt0000644000004100000410000000207012576036765014707 0ustar www-datawww-dataCopyright (c) 2012 Jonas Nicklas, Elabs AB MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. pundit-1.0.1/spec/0000755000004100000410000000000012576036765014017 5ustar www-datawww-datapundit-1.0.1/spec/pundit_spec.rb0000644000004100000410000003005712576036765016666 0ustar www-datawww-datarequire "spec_helper" describe Pundit do let(:user) { double } let(:post) { Post.new(user) } let(:comment) { Comment.new } let(:article) { Article.new } let(:controller) { Controller.new(user, { :action => 'update' }) } let(:artificial_blog) { ArtificialBlog.new } let(:article_tag) { ArticleTag.new } let(:comments_relation) { CommentsRelation.new } let(:empty_comments_relation) { CommentsRelation.new(true) } describe ".authorize" do it "infers the policy and authorizes based on it" do expect(Pundit.authorize(user, post, :update?)).to be_truthy end it "works with anonymous class policies" do expect(Pundit.authorize(user, article_tag, :show?)).to be_truthy expect { Pundit.authorize(user, article_tag, :destroy?) }.to raise_error(Pundit::NotAuthorizedError) end it "raises an error with a query and action" do expect { Pundit.authorize(user, post, :destroy?) }.to raise_error(Pundit::NotAuthorizedError, "not allowed to destroy? this #") do |error| expect(error.query).to eq :destroy? expect(error.record).to eq post expect(error.policy).to eq Pundit.policy(user, post) end end end describe ".policy_scope" do it "returns an instantiated policy scope given a plain model class" do expect(Pundit.policy_scope(user, Post)).to eq :published end it "returns an instantiated policy scope given an active model class" do expect(Pundit.policy_scope(user, Comment)).to eq Comment end it "returns an instantiated policy scope given an active record relation" do expect(Pundit.policy_scope(user, comments_relation)).to eq comments_relation end it "returns an instantiated policy scope given an empty active record relation" do expect(Pundit.policy_scope(user, empty_comments_relation)).to eq empty_comments_relation end it "returns nil if the given policy scope can't be found" do expect(Pundit.policy_scope(user, Article)).to be_nil end it "returns nil if blank object given" do expect(Pundit.policy_scope(user, nil)).to be_nil end end describe ".policy_scope!" do it "returns an instantiated policy scope given a plain model class" do expect(Pundit.policy_scope!(user, Post)).to eq :published end it "returns an instantiated policy scope given an active model class" do expect(Pundit.policy_scope!(user, Comment)).to eq Comment end it "throws an exception if the given policy scope can't be found" do expect { Pundit.policy_scope!(user, Article) }.to raise_error(Pundit::NotDefinedError) end it "throws an exception if the given policy scope can't be found" do expect { Pundit.policy_scope!(user, ArticleTag) }.to raise_error(Pundit::NotDefinedError) end it "throws an exception if the given policy scope is nil" do expect { Pundit.policy_scope!(user, nil) }.to raise_error(Pundit::NotDefinedError, "unable to find policy scope of nil") end end describe ".policy" do it "returns an instantiated policy given a plain model instance" do policy = Pundit.policy(user, post) expect(policy.user).to eq user expect(policy.post).to eq post end it "returns an instantiated policy given an active model instance" do policy = Pundit.policy(user, comment) expect(policy.user).to eq user expect(policy.comment).to eq comment end it "returns an instantiated policy given a plain model class" do policy = Pundit.policy(user, Post) expect(policy.user).to eq user expect(policy.post).to eq Post end it "returns an instantiated policy given an active model class" do policy = Pundit.policy(user, Comment) expect(policy.user).to eq user expect(policy.comment).to eq Comment end it "returns nil if the given policy can't be found" do expect(Pundit.policy(user, article)).to be_nil expect(Pundit.policy(user, Article)).to be_nil end it "returns nil if the given policy is nil" do expect(Pundit.policy(user, nil)).to be_nil end describe "with .policy_class set on the model" do it "returns an instantiated policy given a plain model instance" do policy = Pundit.policy(user, artificial_blog) expect(policy.user).to eq user expect(policy.blog).to eq artificial_blog end it "returns an instantiated policy given a plain model class" do policy = Pundit.policy(user, ArtificialBlog) expect(policy.user).to eq user expect(policy.blog).to eq ArtificialBlog end it "returns an instantiated policy given a plain model instance providing an anonymous class" do policy = Pundit.policy(user, article_tag) expect(policy.user).to eq user expect(policy.tag).to eq article_tag end it "returns an instantiated policy given a plain model class providing an anonymous class" do policy = Pundit.policy(user, ArticleTag) expect(policy.user).to eq user expect(policy.tag).to eq ArticleTag end it "returns an instantiated policy given a symbol" do policy = Pundit.policy(user, :criteria) expect(policy.class).to eq CriteriaPolicy expect(policy.user).to eq user expect(policy.criteria).to eq :criteria end it "returns an instantiated policy given an array" do policy = Pundit.policy(user, [:project, :criteria]) expect(policy.class).to eq Project::CriteriaPolicy expect(policy.user).to eq user expect(policy.criteria).to eq [:project, :criteria] end end end describe ".policy!" do it "returns an instantiated policy given a plain model instance" do policy = Pundit.policy!(user, post) expect(policy.user).to eq user expect(policy.post).to eq post end it "returns an instantiated policy given an active model instance" do policy = Pundit.policy!(user, comment) expect(policy.user).to eq user expect(policy.comment).to eq comment end it "returns an instantiated policy given a plain model class" do policy = Pundit.policy!(user, Post) expect(policy.user).to eq user expect(policy.post).to eq Post end it "returns an instantiated policy given an active model class" do policy = Pundit.policy!(user, Comment) expect(policy.user).to eq user expect(policy.comment).to eq Comment end it "returns an instantiated policy given a symbol" do policy = Pundit.policy!(user, :criteria) expect(policy.class).to eq CriteriaPolicy expect(policy.user).to eq user expect(policy.criteria).to eq :criteria end it "returns an instantiated policy given an array" do policy = Pundit.policy!(user, [:project, :criteria]) expect(policy.class).to eq Project::CriteriaPolicy expect(policy.user).to eq user expect(policy.criteria).to eq [:project, :criteria] end it "throws an exception if the given policy can't be found" do expect { Pundit.policy!(user, article) }.to raise_error(Pundit::NotDefinedError) expect { Pundit.policy!(user, Article) }.to raise_error(Pundit::NotDefinedError) end it "throws an exception if the given policy is nil" do expect { Pundit.policy!(user, nil) }.to raise_error(Pundit::NotDefinedError, "unable to find policy of nil") end end describe "#verify_authorized" do it "does nothing when authorized" do controller.authorize(post) controller.verify_authorized end it "raises an exception when not authorized" do expect { controller.verify_authorized }.to raise_error(Pundit::AuthorizationNotPerformedError) end end describe "#verify_policy_scoped" do it "does nothing when policy_scope is used" do controller.policy_scope(Post) controller.verify_policy_scoped end it "raises an exception when policy_scope is not used" do expect { controller.verify_policy_scoped }.to raise_error(Pundit::PolicyScopingNotPerformedError) end end describe "#pundit_policy_authorized?" do it "is true when authorized" do controller.authorize(post) expect(controller.pundit_policy_authorized?).to be true end it "is false when not authorized" do expect(controller.pundit_policy_authorized?).to be false end end describe "#pundit_policy_scoped?" do it "is true when policy_scope is used" do controller.policy_scope(Post) expect(controller.pundit_policy_scoped?).to be true end it "is false when policy scope is not used" do expect(controller.pundit_policy_scoped?).to be false end end describe "#authorize" do it "infers the policy name and authorizes based on it" do expect(controller.authorize(post)).to be_truthy end it "can be given a different permission to check" do expect(controller.authorize(post, :show?)).to be_truthy expect { controller.authorize(post, :destroy?) }.to raise_error(Pundit::NotAuthorizedError) end it "works with anonymous class policies" do expect(controller.authorize(article_tag, :show?)).to be_truthy expect { controller.authorize(article_tag, :destroy?) }.to raise_error(Pundit::NotAuthorizedError) end it "throws an exception when the permission check fails" do expect { controller.authorize(Post.new) }.to raise_error(Pundit::NotAuthorizedError) end it "throws an exception when a policy cannot be found" do expect { controller.authorize(Article) }.to raise_error(Pundit::NotDefinedError) end it "caches the policy" do expect(controller.policies[post]).to be_nil controller.authorize(post) expect(controller.policies[post]).not_to be_nil end it "raises an error when the given record is nil" do expect { controller.authorize(nil, :destroy?) }.to raise_error(Pundit::NotDefinedError) end end describe "#skip_authorization" do it "disables authorization verification" do controller.skip_authorization expect { controller.verify_authorized }.not_to raise_error end end describe "#skip_policy_scope" do it "disables policy scope verification" do controller.skip_policy_scope expect { controller.verify_policy_scoped }.not_to raise_error end end describe "#pundit_user" do it 'returns the same thing as current_user' do expect(controller.pundit_user).to eq controller.current_user end end describe "#policy" do it "returns an instantiated policy" do policy = controller.policy(post) expect(policy.user).to eq user expect(policy.post).to eq post end it "throws an exception if the given policy can't be found" do expect { controller.policy(article) }.to raise_error(Pundit::NotDefinedError) end it "allows policy to be injected" do new_policy = OpenStruct.new controller.policies[post] = new_policy expect(controller.policy(post)).to eq new_policy end end describe "#policy_scope" do it "returns an instantiated policy scope" do expect(controller.policy_scope(Post)).to eq :published end it "throws an exception if the given policy can't be found" do expect { controller.policy_scope(Article) }.to raise_error(Pundit::NotDefinedError) end it "allows policy_scope to be injected" do new_scope = OpenStruct.new controller.policy_scopes[Post] = new_scope 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 describe "Pundit::NotAuthorizedError" do it "can be initialized with a string as message" do error = Pundit::NotAuthorizedError.new("must be logged in") expect(error.message).to eq "must be logged in" end end end pundit-1.0.1/spec/spec_helper.rb0000644000004100000410000000435112576036765016640 0ustar www-datawww-datarequire "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 module PunditSpecHelper extend RSpec::Matchers::DSL matcher :be_truthy do match do |actual| actual end end end RSpec.configure do |config| config.include PunditSpecHelper end class PostPolicy < Struct.new(:user, :post) def update? post.user == user end def destroy? false end 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 scope.published end end class Post < Struct.new(:user) def self.published :published end def to_s; "Post"; end def inspect; "#"; end end class CommentPolicy < Struct.new(:user, :comment); end class CommentPolicy::Scope < Struct.new(:user, :scope) def resolve scope end end class Comment; extend ActiveModel::Naming; end # minimum mock for an ActiveRecord Relation returning comments class CommentsRelation def initialize(empty=false); @empty=empty; end def blank?; @empty; end def model_name; Comment.model_name; end end class Article; end class BlogPolicy < Struct.new(:user, :blog); end class Blog; end class ArtificialBlog < Blog def self.policy_class BlogPolicy end end class ArticleTag def self.policy_class Struct.new(:user, :tag) do def show? true end def destroy? false end end end end class CriteriaPolicy < Struct.new(:user, :criteria); end module Project class CriteriaPolicy < Struct.new(:user, :criteria); end end class DenierPolicy < Struct.new(:user, :record) def update? false end end class Controller include Pundit attr_reader :current_user, :params def initialize(current_user, params) @current_user = current_user @params = params end end class NilClassPolicy class Scope def initialize(*) raise "I'm only here to be annoying!" end end def initialize(*) raise "I'm only here to be annoying!" end end pundit-1.0.1/spec/policies/0000755000004100000410000000000012576036765015626 5ustar www-datawww-datapundit-1.0.1/spec/policies/post_policy_spec.rb0000644000004100000410000000100112576036765021521 0ustar www-datawww-datarequire "spec_helper" describe PostPolicy do let(:user) { double } let(:own_post) { double(user: user) } let(:other_post) { double(user: double) } subject { described_class } permissions :update?, :show? do it "is successful when all permissions match" do should permit(user, own_post) end it "fails when any permissions do not match" do expect do should permit(user, other_post) end.to raise_error(RSpec::Expectations::ExpectationNotMetError) end end end pundit-1.0.1/.travis.yml0000644000004100000410000000022112576036765015171 0ustar www-datawww-datalanguage: ruby sudo: false rvm: - 2.0.0 - 2.1.5 - 2.2.0 - jruby-19mode - rbx-2 env: - RSPEC_VERSION="<2.99" - RSPEC_VERSION="~>3.0 pundit-1.0.1/lib/0000755000004100000410000000000012576036765013633 5ustar www-datawww-datapundit-1.0.1/lib/pundit.rb0000644000004100000410000000735712576036765015477 0ustar www-datawww-datarequire "pundit/version" require "pundit/policy_finder" require "active_support/concern" require "active_support/core_ext/string/inflections" require "active_support/core_ext/object/blank" require "active_support/core_ext/module/introspection" require "active_support/dependencies/autoload" module Pundit SUFFIX = "Policy" class Error < StandardError; end class NotAuthorizedError < Error attr_reader :query, :record, :policy def initialize(options = {}) if options.is_a? String message = options else @query = options[:query] @record = options[:record] @policy = options[:policy] message = options.fetch(:message) { "not allowed to #{query} this #{record.inspect}" } end super(message) end end class AuthorizationNotPerformedError < Error; end class PolicyScopingNotPerformedError < AuthorizationNotPerformedError; end class NotDefinedError < Error; end extend ActiveSupport::Concern class << self def authorize(user, record, query) policy = policy!(user, record) unless policy.public_send(query) raise NotAuthorizedError.new(query: query, record: record, policy: policy) end true end def policy_scope(user, scope) policy_scope = PolicyFinder.new(scope).scope policy_scope.new(user, scope).resolve if policy_scope end def policy_scope!(user, scope) PolicyFinder.new(scope).scope!.new(user, scope).resolve end def policy(user, record) policy = PolicyFinder.new(record).policy policy.new(user, record) if policy end def policy!(user, record) PolicyFinder.new(record).policy!.new(user, record) end end module Helper def policy_scope(scope) pundit_policy_scope(scope) end end included do helper Helper if respond_to?(:helper) if respond_to?(:helper_method) helper_method :policy helper_method :pundit_policy_scope helper_method :pundit_user end if respond_to?(:hide_action) hide_action :policy hide_action :policy_scope hide_action :policies hide_action :policy_scopes hide_action :authorize hide_action :verify_authorized hide_action :verify_policy_scoped hide_action :permitted_attributes hide_action :pundit_user hide_action :skip_authorization hide_action :skip_policy_scope end end def pundit_policy_authorized? !!@_pundit_policy_authorized end def pundit_policy_scoped? !!@_pundit_policy_scoped end def verify_authorized raise AuthorizationNotPerformedError unless pundit_policy_authorized? end def verify_policy_scoped raise PolicyScopingNotPerformedError unless pundit_policy_scoped? end def authorize(record, query=nil) query ||= params[:action].to_s + "?" @_pundit_policy_authorized = true policy = policy(record) unless policy.public_send(query) raise NotAuthorizedError.new(query: query, record: record, policy: policy) end true end def skip_authorization @_pundit_policy_authorized = true end def skip_policy_scope @_pundit_policy_scoped = true end def policy_scope(scope) @_pundit_policy_scoped = true pundit_policy_scope(scope) end def policy(record) 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 def policy_scopes @_pundit_policy_scopes ||= {} end def pundit_user current_user end private def pundit_policy_scope(scope) policy_scopes[scope] ||= Pundit.policy_scope!(pundit_user, scope) end end pundit-1.0.1/lib/pundit/0000755000004100000410000000000012576036765015136 5ustar www-datawww-datapundit-1.0.1/lib/pundit/policy_finder.rb0000644000004100000410000000263012576036765020312 0ustar www-datawww-datamodule Pundit class PolicyFinder attr_reader :object def initialize(object) @object = object end def scope policy::Scope if policy rescue NameError nil end def policy klass = find klass = klass.constantize if klass.is_a?(String) klass rescue NameError nil end def scope! raise NotDefinedError, "unable to find policy scope of nil" if object.nil? scope or raise NotDefinedError, "unable to find scope `#{find}::Scope` for `#{object.inspect}`" end def policy! raise NotDefinedError, "unable to find policy of nil" if object.nil? policy or raise NotDefinedError, "unable to find policy `#{find}` for `#{object.inspect}`" end private def find if object.nil? nil elsif object.respond_to?(:policy_class) object.policy_class elsif object.class.respond_to?(:policy_class) object.class.policy_class else klass = if object.respond_to?(:model_name) object.model_name elsif object.class.respond_to?(:model_name) object.class.model_name elsif object.is_a?(Class) object elsif object.is_a?(Symbol) object.to_s.camelize elsif object.is_a?(Array) object.join('/').camelize else object.class end "#{klass}#{SUFFIX}" end end end end pundit-1.0.1/lib/pundit/version.rb0000644000004100000410000000004612576036765017150 0ustar www-datawww-datamodule Pundit VERSION = "1.0.1" end pundit-1.0.1/lib/pundit/rspec.rb0000644000004100000410000000517712576036765016611 0ustar www-datawww-datarequire "active_support/core_ext/array/conversions" module Pundit module RSpec module Matchers extend ::RSpec::Matchers::DSL matcher :permit do |user, record| match_proc = lambda do |policy| @violating_permissions = permissions.find_all { |permission| not policy.new(user, record).public_send(permission) } @violating_permissions.empty? end match_when_negated_proc = lambda do |policy| @violating_permissions = permissions.find_all { |permission| policy.new(user, record).public_send(permission) } @violating_permissions.empty? end failure_message_proc = lambda do |policy| was_were = @violating_permissions.count > 1 ? "were" : "was" "Expected #{policy} to grant #{permissions.to_sentence} on #{record} but #{@violating_permissions.to_sentence} #{was_were} not granted" end failure_message_when_negated_proc = lambda do |policy| was_were = @violating_permissions.count > 1 ? "were" : "was" "Expected #{policy} not to grant #{permissions.to_sentence} on #{record} but #{@violating_permissions.to_sentence} #{was_were} granted" end if respond_to?(:match_when_negated) match(&match_proc) match_when_negated(&match_when_negated_proc) failure_message(&failure_message_proc) failure_message_when_negated(&failure_message_when_negated_proc) else match_for_should(&match_proc) match_for_should_not(&match_when_negated_proc) failure_message_for_should(&failure_message_proc) failure_message_for_should_not(&failure_message_when_negated_proc) end def permissions current_example = ::RSpec.respond_to?(:current_example) ? ::RSpec.current_example : example current_example.metadata[:permissions] end end end module DSL def permissions(*list, &block) describe(list.to_sentence, :permissions => list, :caller => caller) { instance_eval(&block) } end end module PolicyExampleGroup include Pundit::RSpec::Matchers def self.included(base) base.metadata[:type] = :policy base.extend Pundit::RSpec::DSL super end end end end RSpec.configure do |config| if RSpec::Core::Version::STRING.split(".").first.to_i >= 3 config.include(Pundit::RSpec::PolicyExampleGroup, { :type => :policy, :file_path => /spec\/policies/, }) else config.include(Pundit::RSpec::PolicyExampleGroup, { :type => :policy, :example_group => { :file_path => /spec\/policies/ } }) end end pundit-1.0.1/lib/generators/0000755000004100000410000000000012576036765016004 5ustar www-datawww-datapundit-1.0.1/lib/generators/pundit/0000755000004100000410000000000012576036765017307 5ustar www-datawww-datapundit-1.0.1/lib/generators/pundit/policy/0000755000004100000410000000000012576036765020606 5ustar www-datawww-datapundit-1.0.1/lib/generators/pundit/policy/policy_generator.rb0000644000004100000410000000054312576036765024502 0ustar www-datawww-datamodule Pundit module Generators class PolicyGenerator < ::Rails::Generators::NamedBase source_root File.expand_path(File.join(File.dirname(__FILE__), 'templates')) def create_policy template 'policy.rb', File.join('app/policies', class_path, "#{file_name}_policy.rb") end hook_for :test_framework end end end pundit-1.0.1/lib/generators/pundit/policy/templates/0000755000004100000410000000000012576036765022604 5ustar www-datawww-datapundit-1.0.1/lib/generators/pundit/policy/templates/policy.rb0000755000004100000410000000023612576036765024434 0ustar www-datawww-data<% module_namespacing do -%> class <%= class_name %>Policy < ApplicationPolicy class Scope < Scope def resolve scope end end end <% end -%> pundit-1.0.1/lib/generators/pundit/policy/USAGE0000644000004100000410000000026012576036765021373 0ustar www-datawww-dataDescription: Generates a policy for a model with the given name. Example: rails generate pundit:policy user This will create: app/policies/user_policy.rb pundit-1.0.1/lib/generators/pundit/install/0000755000004100000410000000000012576036765020755 5ustar www-datawww-datapundit-1.0.1/lib/generators/pundit/install/templates/0000755000004100000410000000000012576036765022753 5ustar www-datawww-datapundit-1.0.1/lib/generators/pundit/install/templates/application_policy.rb0000644000004100000410000000114412576036765027162 0ustar www-datawww-dataclass ApplicationPolicy attr_reader :user, :record def initialize(user, record) @user = user @record = record end def index? false end def show? scope.where(:id => record.id).exists? end def create? false end def new? create? end def update? false end def edit? update? end def destroy? false end def scope Pundit.policy_scope!(user, record.class) end class Scope attr_reader :user, :scope def initialize(user, scope) @user = user @scope = scope end def resolve scope end end end pundit-1.0.1/lib/generators/pundit/install/USAGE0000644000004100000410000000013312576036765021541 0ustar www-datawww-dataDescription: Generates an application policy as a starting point for your application. pundit-1.0.1/lib/generators/pundit/install/install_generator.rb0000644000004100000410000000047212576036765025021 0ustar www-datawww-datamodule Pundit module Generators class InstallGenerator < ::Rails::Generators::Base source_root File.expand_path(File.join(File.dirname(__FILE__), 'templates')) def copy_application_policy template 'application_policy.rb', 'app/policies/application_policy.rb' end end end end pundit-1.0.1/lib/generators/test_unit/0000755000004100000410000000000012576036765020022 5ustar www-datawww-datapundit-1.0.1/lib/generators/test_unit/policy_generator.rb0000644000004100000410000000052512576036765023716 0ustar www-datawww-datamodule TestUnit module Generators class PolicyGenerator < ::Rails::Generators::NamedBase source_root File.expand_path(File.join(File.dirname(__FILE__), 'templates')) def create_policy_test template 'policy_test.rb', File.join('test/policies', class_path, "#{file_name}_policy_test.rb") end end end end pundit-1.0.1/lib/generators/test_unit/templates/0000755000004100000410000000000012576036765022020 5ustar www-datawww-datapundit-1.0.1/lib/generators/test_unit/templates/policy_test.rb0000644000004100000410000000032212576036765024700 0ustar www-datawww-datarequire 'test_helper' class <%= class_name %>PolicyTest < ActiveSupport::TestCase def test_scope end def test_show end def test_create end def test_update end def test_destroy end end pundit-1.0.1/lib/generators/rspec/0000755000004100000410000000000012576036765017120 5ustar www-datawww-datapundit-1.0.1/lib/generators/rspec/policy_generator.rb0000644000004100000410000000052212576036765023011 0ustar www-datawww-datamodule Rspec module Generators class PolicyGenerator < ::Rails::Generators::NamedBase source_root File.expand_path(File.join(File.dirname(__FILE__), 'templates')) def create_policy_spec template 'policy_spec.rb', File.join('spec/policies', class_path, "#{file_name}_policy_spec.rb") end end end end pundit-1.0.1/lib/generators/rspec/templates/0000755000004100000410000000000012576036765021116 5ustar www-datawww-datapundit-1.0.1/lib/generators/rspec/templates/policy_spec.rb0000644000004100000410000000120512576036765023752 0ustar www-datawww-datarequire '<%= File.exists?('spec/rails_helper.rb') ? 'rails_helper' : 'spec_helper' %>' describe <%= class_name %>Policy do let(:user) { User.new } subject { described_class } permissions ".scope" do pending "add some examples to (or delete) #{__FILE__}" end permissions :show? do pending "add some examples to (or delete) #{__FILE__}" end permissions :create? do pending "add some examples to (or delete) #{__FILE__}" end permissions :update? do pending "add some examples to (or delete) #{__FILE__}" end permissions :destroy? do pending "add some examples to (or delete) #{__FILE__}" end end pundit-1.0.1/metadata.yml0000644000004100000410000001121512576036765015370 0ustar www-datawww-data--- !ruby/object:Gem::Specification name: pundit version: !ruby/object:Gem::Version version: 1.0.1 platform: ruby authors: - Jonas Nicklas - Elabs AB autorequire: bindir: bin cert_chain: [] date: 2015-05-27 00:00:00.000000000 Z dependencies: - !ruby/object:Gem::Dependency name: activesupport requirement: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: 3.0.0 type: :runtime prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: 3.0.0 - !ruby/object:Gem::Dependency name: activemodel requirement: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: 3.0.0 type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: 3.0.0 - !ruby/object:Gem::Dependency name: actionpack requirement: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: 3.0.0 type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: 3.0.0 - !ruby/object:Gem::Dependency name: bundler requirement: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '1.3' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '1.3' - !ruby/object:Gem::Dependency name: rspec requirement: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: 2.0.0 type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: 2.0.0 - !ruby/object:Gem::Dependency name: pry requirement: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' - !ruby/object:Gem::Dependency name: rake requirement: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' - !ruby/object:Gem::Dependency name: yard requirement: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' description: Object oriented authorization for Rails applications email: - jonas.nicklas@gmail.com - dev@elabs.se executables: [] extensions: [] extra_rdoc_files: [] files: - ".gitignore" - ".travis.yml" - CHANGELOG.md - CODE_OF_CONDUCT.md - CONTRIBUTING.md - Gemfile - LICENSE.txt - README.md - Rakefile - lib/generators/pundit/install/USAGE - lib/generators/pundit/install/install_generator.rb - lib/generators/pundit/install/templates/application_policy.rb - lib/generators/pundit/policy/USAGE - lib/generators/pundit/policy/policy_generator.rb - lib/generators/pundit/policy/templates/policy.rb - lib/generators/rspec/policy_generator.rb - lib/generators/rspec/templates/policy_spec.rb - lib/generators/test_unit/policy_generator.rb - lib/generators/test_unit/templates/policy_test.rb - lib/pundit.rb - lib/pundit/policy_finder.rb - lib/pundit/rspec.rb - lib/pundit/version.rb - pundit.gemspec - spec/policies/post_policy_spec.rb - spec/pundit_spec.rb - spec/spec_helper.rb homepage: https://github.com/elabs/pundit licenses: - MIT metadata: {} post_install_message: rdoc_options: [] require_paths: - lib required_ruby_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' required_rubygems_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' requirements: [] rubyforge_project: rubygems_version: 2.4.6 signing_key: specification_version: 4 summary: OO authorization for Rails test_files: - spec/policies/post_policy_spec.rb - spec/pundit_spec.rb - spec/spec_helper.rb has_rdoc: pundit-1.0.1/.gitignore0000644000004100000410000000023612576036765015056 0ustar www-datawww-data*.gem *.rbc .bundle .config .yardoc Gemfile.lock InstalledFiles _yardoc coverage doc/ lib/bundler/man pkg rdoc spec/reports test/tmp test/version_tmp tmp bin pundit-1.0.1/pundit.gemspec0000644000004100000410000000226012576036765015735 0ustar www-datawww-data# -*- encoding: utf-8 -*- lib = File.expand_path('../lib', __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'pundit/version' Gem::Specification.new do |gem| gem.name = "pundit" gem.version = Pundit::VERSION gem.authors = ["Jonas Nicklas", "Elabs AB"] gem.email = ["jonas.nicklas@gmail.com", "dev@elabs.se"] gem.description = %q{Object oriented authorization for Rails applications} gem.summary = %q{OO authorization for Rails} gem.homepage = "https://github.com/elabs/pundit" gem.license = "MIT" gem.files = `git ls-files`.split($/) gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) } gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) gem.require_paths = ["lib"] 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" gem.add_development_dependency "rake" gem.add_development_dependency "yard" end pundit-1.0.1/CONTRIBUTING.md0000644000004100000410000000224212576036765015316 0ustar www-datawww-data## Security issues If you have found a security related issue, please do not file an issue on GitHub or send a PR addressing the issue. Contact [Jonas](mailto:jonas.nicklas@gmail.com) directly. You will be given public credit for your disclosure. ## Reporting issues Please try to answer the following questions in your bug report: - What did you do? - What did you expect to happen? - What happened instead? Make sure to include as much relevant information as possible. Ruby version, Pundit version, OS version and any stack traces you have are very valuable. ## Pull Requests - **Add tests!** Your patch won't be accepted if it doesn't have tests. - **Document any change in behaviour**. Make sure the README and any other relevant documentation are kept up-to-date. - **Create topic branches**. Please don't ask us to pull from your master branch. - **One pull request per feature**. If you want to do more than one thing, send multiple pull requests. - **Send coherent history**. Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please squash them before sending them to us. pundit-1.0.1/CHANGELOG.md0000644000004100000410000000335612576036765014705 0ustar www-datawww-data# Pundit ## 1.0.1 (2015-05-27) - Fixed a regression where NotAuthorizedError could not be ininitialized with a string. - Use `camelize` instead of `classify` for symbol policies to prevent weird pluralizations. ## 1.0.0 (2015-04-19) - Caches policy scopes and policies. - Explicitly setting the policy for the controller via `controller.policy = foo` has been removed. Instead use `controller.policies[record] = foo`. - Explicitly setting the policy scope for the controller via `controller.policy_policy = foo` has been removed. Instead use `controller.policy_scopes[scope] = foo`. - Add `permitted_attributes` helper to fetch attributes from policy. - Add `pundit_policy_authorized?` and `pundit_policy_scoped?` methods. - Instance variables are prefixed to avoid collisions. - Add `Pundit.authorize` method. - Add `skip_authorization` and `skip_policy_scope` helpers. - Better errors when checking multiple permissions in RSpec tests. - Better errors in case `nil` is passed to `policy` or `policy_scope`. - Use `inpect` when printing object for better errors. - Dropped official support for Ruby 1.9.3 ## 0.3.0 (2014-08-22) - Extend the default `ApplicationPolicy` with an `ApplicationPolicy::Scope` (#120) - Fix RSpec 3 deprecation warnings for built-in matchers (#162) - Generate blank policy spec/test files for Rspec/MiniTest/Test::Unit in Rails (#138) ## 0.2.3 (2014-04-06) - Customizable error messages: `#query`, `#record` and `#policy` methods on `Pundit::NotAuthorizedError` (#114) - Raise a different `Pundit::AuthorizationNotPerformedError` when `authorize` call is expected in controller action but missing (#109) - Update Rspec matchers for Rspec 3 (#124) ## 0.2.2 (2014-02-07) - Customize the user to be passed into policies: `pundit_user` (#42) pundit-1.0.1/README.md0000644000004100000410000004110012576036765014340 0ustar www-datawww-data# Pundit [![Build Status](https://secure.travis-ci.org/elabs/pundit.png?branch=master)](https://travis-ci.org/elabs/pundit) [![Code Climate](https://codeclimate.com/github/elabs/pundit.png)](https://codeclimate.com/github/elabs/pundit) [![Inline docs](http://inch-ci.org/github/elabs/pundit.png)](http://inch-ci.org/github/elabs/pundit) Pundit provides a set of helpers which guide you in leveraging regular Ruby classes and object oriented design patterns to build a simple, robust and scaleable authorization system. Links: - [API documentation](http://www.rubydoc.info/gems/pundit) - [Source Code](https://github.com/elabs/pundit) - [Contributing](https://github.com/elabs/pundit/blob/master/CONTRIBUTING.md) - [Code of Conduct](https://github.com/elabs/pundit/blob/master/CODE_OF_CONDUCT.md) Sponsored by: [Elabs](http://elabs.se) ## Installation ``` ruby gem "pundit" ``` Include Pundit in your application controller: ``` ruby class ApplicationController < ActionController::Base include Pundit protect_from_forgery end ``` Optionally, you can run the generator, which will set up an application policy with some useful defaults for you: ``` sh rails g pundit:install ``` After generating your application policy, restart the Rails server so that Rails can pick up any classes in the new `app/policies/` directory. ## Policies Pundit is focused around the notion of policy classes. We suggest that you put these classes in `app/policies`. This is a simple example that allows updating a post if the user is an admin, or if the post is unpublished: ``` ruby class PostPolicy attr_reader :user, :post def initialize(user, post) @user = user @post = post end def update? user.admin? or not post.published? end end ``` As you can see, this is just a plain Ruby class. Pundit makes the following assumptions about this class: - The class has the same name as some kind of model class, only suffixed with the word "Policy". - The first argument is a user. In your controller, Pundit will call the `current_user` method to retrieve what to send into this argument - The second argument is some kind of model object, whose authorization you want to check. This does not need to be an ActiveRecord or even an ActiveModel object, it can be anything really. - The class implements some kind of query method, in this case `update?`. Usually, this will map to the name of a particular controller action. That's it really. Usually you'll want to inherit from the application policy created by the generator, or set up your own base class to inherit from: ``` ruby class PostPolicy < ApplicationPolicy def update? user.admin? or not record.published? end end ``` In the generated `ApplicationPolicy`, the model object is called `record`. Supposing that you have an instance of class `Post`, Pundit now lets you do this in your controller: ``` ruby def update @post = Post.find(params[:id]) authorize @post if @post.update(post_params) redirect_to @post else render :edit end end ``` The authorize method automatically infers that `Post` will have a matching `PostPolicy` class, and instantiates this class, handing in the current user and the given record. It then infers from the action name, that it should call `update?` on this instance of the policy. In this case, you can imagine that `authorize` would have done something like this: ``` ruby raise "not authorized" unless PostPolicy.new(current_user, @post).update? ``` You can pass a second argument to `authorize` if the name of the permission you want to check doesn't match the action name. For example: ``` ruby def publish @post = Post.find(params[:id]) authorize @post, :update? @post.publish! redirect_to @post end ``` You can easily get a hold of an instance of the policy through the `policy` method in both the view and controller. This is especially useful for conditionally showing links or buttons in the view: ``` erb <% if policy(@post).update? %> <%= link_to "Edit post", edit_post_path(@post) %> <% end %> ``` ## Headless policies Given there is a policy without a corresponding model / ruby class, you can retrieve it by passing a symbol. ```ruby # app/policies/dashboard_policy.rb class DashboardPolicy < Struct.new(:user, :dashboard) # ... end ``` ```ruby # In controllers authorize :dashboard, :show? ``` ```erb # In views <% if policy(:dashboard).show? %> <%= link_to 'Dashboard', dashboard_path %> <% end %> ``` ## Ensuring policies are used Pundit adds a method called `verify_authorized` to your controllers. This method will raise an exception if `authorize` has not yet been called. You should run this method in an `after_action` to ensure that you haven't forgotten to authorize the action. For example: ``` ruby class ApplicationController < ActionController::Base after_action :verify_authorized, :except => :index end ``` Likewise, Pundit also adds `verify_policy_scoped` to your controller. This will raise an exception in the vein of `verify_authorized`. However, it tracks if `policy_scope` is used instead of `authorize`. This is mostly useful for controller actions like `index` which find collections with a scope and don't authorize individual instances. ``` ruby class ApplicationController < ActionController::Base after_action :verify_policy_scoped, :only => :index end ``` If you're using `verify_authorized` in your controllers but need to conditionally bypass verification, you can use `skip_authorization`. For bypassing `verify_policy_scoped`, use `skip_policy_scope`. These are useful in circumstances where you don't want to disable verification for the entire action, but have some cases where you intend to not authorize. ```ruby def show record = Record.find_by(attribute: "value") if record.present? authorize record else skip_authorization end end ``` If you need to perform some more sophisticated logic or you want to raise a custom exception you can use the two lower level methods `pundit_policy_authorized?` and `pundit_policy_scoped?` which return `true` or `false` depending on whether `authorize` or `policy_scope` have been called, respectively. ## Scopes Often, you will want to have some kind of view listing records which a particular user has access to. When using Pundit, you are expected to define a class called a policy scope. It can look something like this: ``` ruby class PostPolicy < ApplicationPolicy class Scope attr_reader :user, :scope def initialize(user, scope) @user = user @scope = scope end def resolve if user.admin? scope.all else scope.where(:published => true) end end end def update? user.admin? or not post.published? end end ``` Pundit makes the following assumptions about this class: - The class has the name `Scope` and is nested under the policy class. - The first argument is a user. In your controller, Pundit will call the `current_user` method to retrieve what to send into this argument. - The second argument is a scope of some kind on which to perform some kind of query. It will usually be an ActiveRecord class or a `ActiveRecord::Relation`, but it could be something else entirely. - Instances of this class respond to the method `resolve`, which should return some kind of result which can be iterated over. For ActiveRecord classes, this would usually be an `ActiveRecord::Relation`. You'll probably want to inherit from the application policy scope generated by the generator, or create your own base class to inherit from: ``` ruby class PostPolicy < ApplicationPolicy class Scope < Scope def resolve if user.admin? scope.all else scope.where(:published => true) end end end def update? user.admin? or not post.published? end end ``` You can now use this class from your controller via the `policy_scope` method: ``` ruby def index @posts = policy_scope(Post) end ``` Just as with your policy, this will automatically infer that you want to use the `PostPolicy::Scope` class, it will instantiate this class and call `resolve` on the instance. In this case it is a shortcut for doing: ``` ruby def index @posts = PostPolicy::Scope.new(current_user, Post).resolve end ``` You can, and are encouraged to, use this method in views: ``` erb <% policy_scope(@user.posts).each do |post| %>

<%= link_to post.title, post_path(post) %>

<% end %> ``` ## Manually specifying policy classes Sometimes you might want to explicitly declare which policy to use for a given class, instead of letting Pundit infer it. This can be done like so: ``` ruby class Post def self.policy_class PostablePolicy end end ``` ## Just plain old Ruby As you can see, Pundit doesn't do anything you couldn't have easily done yourself. It's a very small library, it just provides a few neat helpers. Together these give you the power of building a well structured, fully working authorization system without using any special DSLs or funky syntax or anything. Remember that all of the policy and scope classes are just plain Ruby classes, which means you can use the same mechanisms you always use to DRY things up. Encapsulate a set of permissions into a module and include them in multiple policies. Use `alias_method` to make some permissions behave the same as others. Inherit from a base set of permissions. Use metaprogramming if you really have to. ## Generator Use the supplied generator to generate policies: ``` sh rails g pundit:policy post ``` ## Closed systems In many applications, only logged in users are really able to do anything. If you're building such a system, it can be kind of cumbersome to check that the user in a policy isn't `nil` for every single permission. We suggest that you define a filter that redirects unauthenticated users to the login page. As a secondary defence, if you've defined an ApplicationPolicy, it might be a good idea to raise an exception if somehow an unauthenticated user got through. This way you can fail more gracefully. ``` ruby class ApplicationPolicy def initialize(user, record) raise Pundit::NotAuthorizedError, "must be logged in" unless user @user = user @record = record end end ``` ## Rescuing a denied Authorization in Rails Pundit raises a `Pundit::NotAuthorizedError` you can [rescue_from](http://guides.rubyonrails.org/action_controller_overview.html#rescue-from) in your `ApplicationController`. You can customize the `user_not_authorized` method in every controller. ```ruby class ApplicationController < ActionController::Base protect_from_forgery include Pundit rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized private def user_not_authorized flash[:alert] = "You are not authorized to perform this action." redirect_to(request.referrer || root_path) end end ``` ## Creating custom error messages `NotAuthorizedError`s provide information on what query (e.g. `:create?`), what record (e.g. an instance of `Post`), and what policy (e.g. an instance of `PostPolicy`) caused the error to be raised. One way to use these `query`, `record`, and `policy` properties is to connect them with `I18n` to generate error messages. Here's how you might go about doing that. ```ruby class ApplicationController < ActionController::Base rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized private def user_not_authorized(exception) policy_name = exception.policy.class.to_s.underscore flash[:error] = t "#{policy_name}.#{exception.query}", scope: "pundit", default: :default redirect_to(request.referrer || root_path) end end ``` ```yaml en: pundit: default: 'You cannot perform this action.' post_policy: update?: 'You cannot edit this post!' create?: 'You cannot create posts!' ``` Of course, this is just an example. Pundit is agnostic as to how you implement your error messaging. ## Manually retrieving policies and scopes Sometimes you want to retrieve a policy for a record outside the controller or view. For example when you delegate permissions from one policy to another. You can easily retrieve policies and scopes like this: ``` ruby Pundit.policy!(user, post) Pundit.policy(user, post) Pundit.policy_scope!(user, Post) Pundit.policy_scope(user, Post) ``` The bang methods will raise an exception if the policy does not exist, whereas those without the bang will return nil. ## Customize Pundit user In some cases your controller might not have access to `current_user`, or your `current_user` is not the method that should be invoked by Pundit. Simply define a method in your controller called `pundit_user`. ```ruby def pundit_user User.find_by_other_means end ``` ## Additional context Pundit strongly encourages you to model your application in such a way that the only context you need for authorization is a user object and a domain model that you want to check authorization for. If you find yourself needing more context than that, consider whether you are authorizing the right domain model, maybe another domain model (or a wrapper around multiple domain models) can provide the context you need. Pundit does not allow you to pass additional arguments to policies for precisely this reason. However, in very rare cases, you might need to authorize based on more context than just the currently authenticated user. Suppose for example that authorization is dependent on IP address in addition to the authenticated user. In that case, one option is to create a special class which wraps up both user and IP and passes it to the policy. ``` ruby class UserContext attr_reader :user, :ip def initialize(user, ip) @user = user @ip = ip end end class ApplicationController include Pundit def pundit_user UserContext.new(current_user, request.ip) end end ``` ## Strong parameters 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. 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 class PostPolicy < ApplicationPolicy def permitted_attributes if user.admin? || user.owner_of?(post) [:title, :body, :tag_list] else [:tag_list] 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_attributes(post_params) redirect_to @post else render :edit end end private def post_params 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 ``` ## RSpec ### Policy Specs Pundit includes a mini-DSL for writing expressive tests for your policies in RSpec. Require `pundit/rspec` in your `spec_helper.rb`: ``` ruby require "pundit/rspec" ``` Then put your policy specs in `spec/policies`, and make them look somewhat like this: ``` ruby describe PostPolicy do subject { described_class } permissions :update? do it "denies access if post is published" do expect(subject).not_to permit(User.new(:admin => false), Post.new(:published => true)) end it "grants access if post is published and user is an admin" do expect(subject).to permit(User.new(:admin => true), Post.new(:published => true)) end it "grants access if post is unpublished" do expect(subject).to permit(User.new(:admin => false), Post.new(:published => false)) end end end ``` An alternative approach to Pundit policy specs is scoping them to a user context as outlined in this [excellent post](http://thunderboltlabs.com/blog/2013/03/27/testing-pundit-policies-with-rspec/). # External Resources - [RailsApps Example Application: Pundit and Devise](https://github.com/RailsApps/rails-devise-pundit) - [Migrating to Pundit from CanCan](http://blog.carbonfive.com/2013/10/21/migrating-to-pundit-from-cancan/) - [Testing Pundit Policies with RSpec](http://thunderboltlabs.com/blog/2013/03/27/testing-pundit-policies-with-rspec/) - [Using Pundit outside of a Rails controller](https://github.com/elabs/pundit/pull/136) # License Licensed under the MIT license, see the separate LICENSE.txt file.