Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package rubygem-pundit for openSUSE:Factory checked in at 2022-02-24 18:20:19 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/rubygem-pundit (Old) and /work/SRC/openSUSE:Factory/.rubygem-pundit.new.1958 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "rubygem-pundit" Thu Feb 24 18:20:19 2022 rev:5 rq:956120 version:2.2.0 Changes: -------- --- /work/SRC/openSUSE:Factory/rubygem-pundit/rubygem-pundit.changes 2021-08-25 20:59:42.925042469 +0200 +++ /work/SRC/openSUSE:Factory/.rubygem-pundit.new.1958/rubygem-pundit.changes 2022-02-24 18:23:34.650656535 +0100 @@ -1,0 +2,7 @@ +Tue Feb 15 07:34:50 UTC 2022 - Stephan Kulow <co...@suse.com> + +updated to version 2.2.0 + see installed CHANGELOG.md + + +------------------------------------------------------------------- Old: ---- pundit-2.1.1.gem New: ---- pundit-2.2.0.gem ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ rubygem-pundit.spec ++++++ --- /var/tmp/diff_new_pack.Q4K83V/_old 2022-02-24 18:23:35.054656430 +0100 +++ /var/tmp/diff_new_pack.Q4K83V/_new 2022-02-24 18:23:35.058656429 +0100 @@ -1,7 +1,7 @@ # # spec file for package rubygem-pundit # -# Copyright (c) 2021 SUSE LLC +# Copyright (c) 2022 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -24,7 +24,7 @@ # Name: rubygem-pundit -Version: 2.1.1 +Version: 2.2.0 Release: 0 %define mod_name pundit %define mod_full_name %{mod_name}-%{version} ++++++ pundit-2.1.1.gem -> pundit-2.2.0.gem ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/.rubocop.yml new/.rubocop.yml --- old/.rubocop.yml 2021-08-13 11:10:28.000000000 +0200 +++ new/.rubocop.yml 2022-02-11 13:14:56.000000000 +0100 @@ -2,6 +2,8 @@ TargetRubyVersion: 2.6 Exclude: - "lib/generators/**/templates/**/*" + SuggestExtensions: false + NewCops: disable Metrics/BlockLength: Exclude: @@ -15,7 +17,7 @@ Exclude: - "**/*_spec.rb" -Metrics/LineLength: +Layout/LineLength: Max: 120 Metrics/AbcSize: @@ -27,7 +29,10 @@ Metrics/PerceivedComplexity: Enabled: false -Layout/AlignParameters: +Gemspec/RequiredRubyVersion: + Enabled: false + +Layout/ParameterAlignment: EnforcedStyle: with_fixed_indentation Layout/CaseIndentation: @@ -63,5 +68,5 @@ Style/DoubleNegation: Enabled: false -Documentation: +Style/Documentation: Enabled: false # TODO: Enable again once we have more docs diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/.travis.yml new/.travis.yml --- old/.travis.yml 2021-08-13 11:10:28.000000000 +0200 +++ new/.travis.yml 2022-02-11 13:14:56.000000000 +0100 @@ -17,6 +17,7 @@ - ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT - rvm: 2.7.3 - rvm: 3.0.1 + - rvm: 3.1.0 - rvm: jruby-9.2.17.0 env: - JRUBY_OPTS="--debug" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/CHANGELOG.md new/CHANGELOG.md --- old/CHANGELOG.md 2021-08-13 11:10:28.000000000 +0200 +++ new/CHANGELOG.md 2022-02-11 13:14:56.000000000 +0100 @@ -1,6 +1,18 @@ # Pundit -## Unreleased +## 2.2.0 (2022-02-11) + +### Fixed + +- Using `policy_class` and a namespaced record now passes only the record when instantiating the policy. (#697, #689, #694, #666) + +### Changed + +- Require users to explicitly define Scope#resolve in generated policies (#711, #722) + +### Deprecated + +- Deprecate `include Pundit` in favor of `include Pundit::Authorization` (#621) ## 2.1.1 (2021-08-13) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/README.md new/README.md --- old/README.md 2021-08-13 11:10:28.000000000 +0200 +++ new/README.md 2022-02-11 13:14:56.000000000 +0100 @@ -11,7 +11,7 @@ Links: -- [API documentation](http://www.rubydoc.info/gems/pundit) +- [API documentation for the most recent version](http://www.rubydoc.info/gems/pundit) - [Source Code](https://github.com/varvet/pundit) - [Contributing](https://github.com/varvet/pundit/blob/master/CONTRIBUTING.md) - [Code of Conduct](https://github.com/varvet/pundit/blob/master/CODE_OF_CONDUCT.md) @@ -22,15 +22,17 @@ ## Installation +> **Please note** that the README on GitHub is accurate with the _latest code on GitHub_. You are most likely using a released version of Pundit, so please refer to the [documentation for the latest released version of Pundit](https://www.rubydoc.info/gems/pundit). + ``` ruby gem "pundit" ``` -Include Pundit in your application controller: +Include `Pundit::Authorization` in your application controller: ``` ruby class ApplicationController < ActionController::Base - include Pundit + include Pundit::Authorization end ``` @@ -194,8 +196,17 @@ ```ruby # app/policies/dashboard_policy.rb -class DashboardPolicy < Struct.new(:user, :dashboard) - # ... +class DashboardPolicy + attr_reader :user + + # _record in this example will just be :dashboard + def initialize(user, _record) + @user = user + end + + def show? + user.admin? + end end ``` @@ -205,7 +216,10 @@ ```ruby # In controllers -authorize :dashboard, :show? +def show + authorize :dashboard, :show? + ... +end ``` ```erb @@ -334,7 +348,7 @@ ``` ruby class ApplicationController < ActionController::Base - include Pundit + include Pundit::Authorization after_action :verify_authorized end ``` @@ -347,7 +361,7 @@ ``` ruby class ApplicationController < ActionController::Base - include Pundit + include Pundit::Authorization after_action :verify_authorized, except: :index after_action :verify_policy_scoped, only: :index end @@ -490,7 +504,7 @@ ```ruby class ApplicationController < ActionController::Base - include Pundit + include Pundit::Authorization rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized @@ -643,7 +657,7 @@ end class ApplicationController - include Pundit + include Pundit::Authorization def pundit_user UserContext.new(current_user, request.ip) Binary files old/checksums.yaml.gz and new/checksums.yaml.gz differ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/generators/pundit/install/templates/application_policy.rb new/lib/generators/pundit/install/templates/application_policy.rb --- old/lib/generators/pundit/install/templates/application_policy.rb 2021-08-13 11:10:28.000000000 +0200 +++ new/lib/generators/pundit/install/templates/application_policy.rb 2022-02-11 13:14:56.000000000 +0100 @@ -43,7 +43,7 @@ end def resolve - scope.all + raise NotImplementedError, "You must define #resolve in #{self.class}" end private diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/generators/pundit/policy/templates/policy.rb new/lib/generators/pundit/policy/templates/policy.rb --- old/lib/generators/pundit/policy/templates/policy.rb 2021-08-13 11:10:28.000000000 +0200 +++ new/lib/generators/pundit/policy/templates/policy.rb 2022-02-11 13:14:56.000000000 +0100 @@ -1,9 +1,10 @@ <% module_namespacing do -%> class <%= class_name %>Policy < ApplicationPolicy class Scope < Scope - def resolve - scope.all - end + # NOTE: Be explicit about which records you allow access to! + # def resolve + # scope.all + # end end end <% end -%> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/pundit/authorization.rb new/lib/pundit/authorization.rb --- old/lib/pundit/authorization.rb 1970-01-01 01:00:00.000000000 +0100 +++ new/lib/pundit/authorization.rb 2022-02-11 13:14:56.000000000 +0100 @@ -0,0 +1,168 @@ +# frozen_string_literal: true + +module Pundit + module Authorization + extend ActiveSupport::Concern + + 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 + end + + protected + + # @return [Boolean] whether authorization has been performed, i.e. whether + # one {#authorize} or {#skip_authorization} has been called + def pundit_policy_authorized? + !!@_pundit_policy_authorized + end + + # @return [Boolean] whether policy scoping has been performed, i.e. whether + # one {#policy_scope} or {#skip_policy_scope} has been called + def pundit_policy_scoped? + !!@_pundit_policy_scoped + end + + # Raises an error if authorization has not been performed, usually used as an + # `after_action` filter to prevent programmer error in forgetting to call + # {#authorize} or {#skip_authorization}. + # + # @see https://github.com/varvet/pundit#ensuring-policies-and-scopes-are-used + # @raise [AuthorizationNotPerformedError] if authorization has not been performed + # @return [void] + def verify_authorized + raise AuthorizationNotPerformedError, self.class unless pundit_policy_authorized? + end + + # Raises an error if policy scoping has not been performed, usually used as an + # `after_action` filter to prevent programmer error in forgetting to call + # {#policy_scope} or {#skip_policy_scope} in index actions. + # + # @see https://github.com/varvet/pundit#ensuring-policies-and-scopes-are-used + # @raise [AuthorizationNotPerformedError] if policy scoping has not been performed + # @return [void] + def verify_policy_scoped + raise PolicyScopingNotPerformedError, self.class unless pundit_policy_scoped? + end + + # Retrieves the policy for the given record, initializing it with the record + # and current user and finally throwing an error if the user is not + # authorized to perform the given action. + # + # @param record [Object, Array] the object we're checking permissions of + # @param query [Symbol, String] the predicate method to check on the policy (e.g. `:show?`). + # If omitted then this defaults to the Rails controller action name. + # @param policy_class [Class] the policy class we want to force use of + # @raise [NotAuthorizedError] if the given query method returned false + # @return [Object] Always returns the passed object record + def authorize(record, query = nil, policy_class: nil) + query ||= "#{action_name}?" + + @_pundit_policy_authorized = true + + Pundit.authorize(pundit_user, record, query, policy_class: policy_class, cache: policies) + end + + # Allow this action not to perform authorization. + # + # @see https://github.com/varvet/pundit#ensuring-policies-and-scopes-are-used + # @return [void] + def skip_authorization + @_pundit_policy_authorized = :skipped + end + + # Allow this action not to perform policy scoping. + # + # @see https://github.com/varvet/pundit#ensuring-policies-and-scopes-are-used + # @return [void] + def skip_policy_scope + @_pundit_policy_scoped = :skipped + end + + # Retrieves the policy scope for the given record. + # + # @see https://github.com/varvet/pundit#scopes + # @param scope [Object] the object we're retrieving the policy scope for + # @param policy_scope_class [Class] the policy scope class we want to force use of + # @return [Scope{#resolve}, nil] instance of scope class which can resolve to a scope + def policy_scope(scope, policy_scope_class: nil) + @_pundit_policy_scoped = true + policy_scope_class ? policy_scope_class.new(pundit_user, scope).resolve : pundit_policy_scope(scope) + end + + # Retrieves the policy for the given record. + # + # @see https://github.com/varvet/pundit#policies + # @param record [Object] the object we're retrieving the policy for + # @return [Object, nil] instance of policy class with query methods + def policy(record) + policies[record] ||= Pundit.policy!(pundit_user, record) + end + + # Retrieves a set of permitted attributes from the policy by instantiating + # the policy class for the given record and calling `permitted_attributes` on + # it, or `permitted_attributes_for_{action}` if `action` is defined. It then infers + # what key the record should have in the params hash and retrieves the + # permitted attributes from the params hash under that key. + # + # @see https://github.com/varvet/pundit#strong-parameters + # @param record [Object] the object we're retrieving permitted attributes for + # @param action [Symbol, String] the name of the action being performed on the record (e.g. `:update`). + # If omitted then this defaults to the Rails controller action name. + # @return [Hash{String => Object}] the permitted attributes + def permitted_attributes(record, action = action_name) + policy = policy(record) + method_name = if policy.respond_to?("permitted_attributes_for_#{action}") + "permitted_attributes_for_#{action}" + else + "permitted_attributes" + end + pundit_params_for(record).permit(*policy.public_send(method_name)) + end + + # Retrieves the params for the given record. + # + # @param record [Object] the object we're retrieving params for + # @return [ActionController::Parameters] the params + def pundit_params_for(record) + params.require(PolicyFinder.new(record).param_key) + end + + # Cache of policies. You should not rely on this method. + # + # @api private + # rubocop:disable Naming/MemoizedInstanceVariableName + def policies + @_pundit_policies ||= {} + end + # rubocop:enable Naming/MemoizedInstanceVariableName + + # Cache of policy scope. You should not rely on this method. + # + # @api private + # rubocop:disable Naming/MemoizedInstanceVariableName + def policy_scopes + @_pundit_policy_scopes ||= {} + end + # rubocop:enable Naming/MemoizedInstanceVariableName + + # Hook method which allows customizing which user is passed to policies and + # scopes initialized by {#authorize}, {#policy} and {#policy_scope}. + # + # @see https://github.com/varvet/pundit#customize-pundit-user + # @return [Object] the user object to be used with pundit + def pundit_user + current_user + end + + private + + def pundit_policy_scope(scope) + policy_scopes[scope] ||= Pundit.policy_scope!(pundit_user, scope) + end + end +end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/pundit/version.rb new/lib/pundit/version.rb --- old/lib/pundit/version.rb 2021-08-13 11:10:28.000000000 +0200 +++ new/lib/pundit/version.rb 2022-02-11 13:14:56.000000000 +0100 @@ -1,5 +1,5 @@ # frozen_string_literal: true module Pundit - VERSION = "2.1.1" + VERSION = "2.2.0" end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lib/pundit.rb new/lib/pundit.rb --- old/lib/pundit.rb 2021-08-13 11:10:28.000000000 +0200 +++ new/lib/pundit.rb 2022-02-11 13:14:56.000000000 +0100 @@ -7,6 +7,7 @@ require "active_support/core_ext/object/blank" require "active_support/core_ext/module/introspection" require "active_support/dependencies/autoload" +require "pundit/authorization" # @api private # To avoid name clashes with common Error naming when mixing in Pundit, @@ -53,7 +54,12 @@ # Error that will be raised if a policy or policy scope is not defined. class NotDefinedError < Error; end - extend ActiveSupport::Concern + def self.included(base) + ActiveSupport::Deprecation.warn <<~WARNING.strip_heredoc + 'include Pundit' is deprecated. Please use 'include Pundit::Authorization' instead. + WARNING + base.include Authorization + end class << self # Retrieves the policy for the given record, initializing it with the @@ -61,17 +67,23 @@ # authorized to perform the given action. # # @param user [Object] the user that initiated the action - # @param record [Object] the object we're checking permissions of + # @param possibly_namespaced_record [Object, Array] the object we're checking permissions of # @param query [Symbol, String] the predicate method to check on the policy (e.g. `:show?`) # @param policy_class [Class] the policy class we want to force use of + # @param cache [#[], #[]=] a Hash-like object to cache the found policy instance in # @raise [NotAuthorizedError] if the given query method returned false # @return [Object] Always returns the passed object record - def authorize(user, record, query, policy_class: nil) - policy = policy_class ? policy_class.new(user, record) : policy!(user, record) + def authorize(user, possibly_namespaced_record, query, policy_class: nil, cache: {}) + record = pundit_model(possibly_namespaced_record) + policy = if policy_class + policy_class.new(user, record) + else + cache[possibly_namespaced_record] ||= policy!(user, possibly_namespaced_record) + end raise NotAuthorizedError, query: query, record: record, policy: policy unless policy.public_send(query) - record.is_a?(Array) ? record.last : record + record end # Retrieves the policy scope for the given record. @@ -157,169 +169,4 @@ 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 - end - - protected - - # @return [Boolean] whether authorization has been performed, i.e. whether - # one {#authorize} or {#skip_authorization} has been called - def pundit_policy_authorized? - !!@_pundit_policy_authorized - end - - # @return [Boolean] whether policy scoping has been performed, i.e. whether - # one {#policy_scope} or {#skip_policy_scope} has been called - def pundit_policy_scoped? - !!@_pundit_policy_scoped - end - - # Raises an error if authorization has not been performed, usually used as an - # `after_action` filter to prevent programmer error in forgetting to call - # {#authorize} or {#skip_authorization}. - # - # @see https://github.com/varvet/pundit#ensuring-policies-and-scopes-are-used - # @raise [AuthorizationNotPerformedError] if authorization has not been performed - # @return [void] - def verify_authorized - raise AuthorizationNotPerformedError, self.class unless pundit_policy_authorized? - end - - # Raises an error if policy scoping has not been performed, usually used as an - # `after_action` filter to prevent programmer error in forgetting to call - # {#policy_scope} or {#skip_policy_scope} in index actions. - # - # @see https://github.com/varvet/pundit#ensuring-policies-and-scopes-are-used - # @raise [AuthorizationNotPerformedError] if policy scoping has not been performed - # @return [void] - def verify_policy_scoped - raise PolicyScopingNotPerformedError, self.class unless pundit_policy_scoped? - end - - # Retrieves the policy for the given record, initializing it with the record - # and current user and finally throwing an error if the user is not - # authorized to perform the given action. - # - # @param record [Object] the object we're checking permissions of - # @param query [Symbol, String] the predicate method to check on the policy (e.g. `:show?`). - # If omitted then this defaults to the Rails controller action name. - # @param policy_class [Class] the policy class we want to force use of - # @raise [NotAuthorizedError] if the given query method returned false - # @return [Object] Always returns the passed object record - def authorize(record, query = nil, policy_class: nil) - query ||= "#{action_name}?" - - @_pundit_policy_authorized = true - - policy = policy_class ? policy_class.new(pundit_user, record) : policy(record) - - raise NotAuthorizedError, query: query, record: record, policy: policy unless policy.public_send(query) - - record.is_a?(Array) ? record.last : record - end - - # Allow this action not to perform authorization. - # - # @see https://github.com/varvet/pundit#ensuring-policies-and-scopes-are-used - # @return [void] - def skip_authorization - @_pundit_policy_authorized = true - end - - # Allow this action not to perform policy scoping. - # - # @see https://github.com/varvet/pundit#ensuring-policies-and-scopes-are-used - # @return [void] - def skip_policy_scope - @_pundit_policy_scoped = true - end - - # Retrieves the policy scope for the given record. - # - # @see https://github.com/varvet/pundit#scopes - # @param scope [Object] the object we're retrieving the policy scope for - # @param policy_scope_class [Class] the policy scope class we want to force use of - # @return [Scope{#resolve}, nil] instance of scope class which can resolve to a scope - def policy_scope(scope, policy_scope_class: nil) - @_pundit_policy_scoped = true - policy_scope_class ? policy_scope_class.new(pundit_user, scope).resolve : pundit_policy_scope(scope) - end - - # Retrieves the policy for the given record. - # - # @see https://github.com/varvet/pundit#policies - # @param record [Object] the object we're retrieving the policy for - # @return [Object, nil] instance of policy class with query methods - def policy(record) - policies[record] ||= Pundit.policy!(pundit_user, record) - end - - # Retrieves a set of permitted attributes from the policy by instantiating - # the policy class for the given record and calling `permitted_attributes` on - # it, or `permitted_attributes_for_{action}` if `action` is defined. It then infers - # what key the record should have in the params hash and retrieves the - # permitted attributes from the params hash under that key. - # - # @see https://github.com/varvet/pundit#strong-parameters - # @param record [Object] the object we're retrieving permitted attributes for - # @param action [Symbol, String] the name of the action being performed on the record (e.g. `:update`). - # If omitted then this defaults to the Rails controller action name. - # @return [Hash{String => Object}] the permitted attributes - def permitted_attributes(record, action = action_name) - policy = policy(record) - method_name = if policy.respond_to?("permitted_attributes_for_#{action}") - "permitted_attributes_for_#{action}" - else - "permitted_attributes" - end - pundit_params_for(record).permit(*policy.public_send(method_name)) - end - - # Retrieves the params for the given record. - # - # @param record [Object] the object we're retrieving params for - # @return [ActionController::Parameters] the params - def pundit_params_for(record) - params.require(PolicyFinder.new(record).param_key) - end - - # Cache of policies. You should not rely on this method. - # - # @api private - # rubocop:disable Naming/MemoizedInstanceVariableName - def policies - @_pundit_policies ||= {} - end - # rubocop:enable Naming/MemoizedInstanceVariableName - - # Cache of policy scope. You should not rely on this method. - # - # @api private - # rubocop:disable Naming/MemoizedInstanceVariableName - def policy_scopes - @_pundit_policy_scopes ||= {} - end - # rubocop:enable Naming/MemoizedInstanceVariableName - - # Hook method which allows customizing which user is passed to policies and - # scopes initialized by {#authorize}, {#policy} and {#policy_scope}. - # - # @see https://github.com/varvet/pundit#customize-pundit-user - # @return [Object] the user object to be used with pundit - def pundit_user - current_user - end - - private - - def pundit_policy_scope(scope) - policy_scopes[scope] ||= Pundit.policy_scope!(pundit_user, scope) - end end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/metadata new/metadata --- old/metadata 2021-08-13 11:10:28.000000000 +0200 +++ new/metadata 2022-02-11 13:14:56.000000000 +0100 @@ -1,15 +1,15 @@ --- !ruby/object:Gem::Specification name: pundit version: !ruby/object:Gem::Version - version: 2.1.1 + version: 2.2.0 platform: ruby authors: - Jonas Nicklas - Varvet AB -autorequire: +autorequire: bindir: bin cert_chain: [] -date: 2021-08-13 00:00:00.000000000 Z +date: 2022-02-11 00:00:00.000000000 Z dependencies: - !ruby/object:Gem::Dependency name: activesupport @@ -82,6 +82,20 @@ - !ruby/object:Gem::Version version: '0' - !ruby/object:Gem::Dependency + name: railties + 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: rake requirement: !ruby/object:Gem::Requirement requirements: @@ -115,14 +129,14 @@ requirements: - - '=' - !ruby/object:Gem::Version - version: 0.74.0 + version: 1.24.0 type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - '=' - !ruby/object:Gem::Version - version: 0.74.0 + version: 1.24.0 - !ruby/object:Gem::Dependency name: simplecov requirement: !ruby/object:Gem::Requirement @@ -181,10 +195,13 @@ - lib/generators/test_unit/policy_generator.rb - lib/generators/test_unit/templates/policy_test.rb - lib/pundit.rb +- lib/pundit/authorization.rb - lib/pundit/policy_finder.rb - lib/pundit/rspec.rb - lib/pundit/version.rb - pundit.gemspec +- spec/authorization_spec.rb +- spec/generators_spec.rb - spec/policies/post_policy_spec.rb - spec/policy_finder_spec.rb - spec/pundit_spec.rb @@ -193,7 +210,7 @@ licenses: - MIT metadata: {} -post_install_message: +post_install_message: rdoc_options: [] require_paths: - lib @@ -208,11 +225,13 @@ - !ruby/object:Gem::Version version: '0' requirements: [] -rubygems_version: 3.2.25 -signing_key: +rubygems_version: 3.2.32 +signing_key: specification_version: 4 summary: OO authorization for Rails test_files: +- spec/authorization_spec.rb +- spec/generators_spec.rb - spec/policies/post_policy_spec.rb - spec/policy_finder_spec.rb - spec/pundit_spec.rb diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pundit.gemspec new/pundit.gemspec --- old/pundit.gemspec 2021-08-13 11:10:28.000000000 +0200 +++ new/pundit.gemspec 2022-02-11 13:14:56.000000000 +0100 @@ -24,9 +24,10 @@ gem.add_development_dependency "activemodel", ">= 3.0.0" gem.add_development_dependency "bundler" gem.add_development_dependency "pry" + gem.add_development_dependency "railties", ">= 3.0.0" gem.add_development_dependency "rake" gem.add_development_dependency "rspec", ">= 3.0.0" - gem.add_development_dependency "rubocop", "0.74.0" + gem.add_development_dependency "rubocop", "1.24.0" gem.add_development_dependency "simplecov", ">= 0.17.0" gem.add_development_dependency "yard" end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/spec/authorization_spec.rb new/spec/authorization_spec.rb --- old/spec/authorization_spec.rb 1970-01-01 01:00:00.000000000 +0100 +++ new/spec/authorization_spec.rb 2022-02-11 13:14:56.000000000 +0100 @@ -0,0 +1,258 @@ +# frozen_string_literal: true + +require "spec_helper" + +describe Pundit::Authorization do + let(:controller) { Controller.new(user, "update", {}) } + let(:user) { double } + let(:post) { Post.new(user) } + let(:customer_post) { Customer::Post.new(user) } + let(:comment) { Comment.new } + let(:article) { Article.new } + let(:article_tag) { ArticleTag.new } + let(:wiki) { Wiki.new } + + 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 "returns the record on successful authorization" do + expect(controller.authorize(post)).to eq(post) + end + + it "returns the record when passed record with namespace " do + expect(controller.authorize([:project, comment], :update?)).to eq(comment) + end + + it "returns the record when passed record with nested namespace " do + expect(controller.authorize([:project, :admin, comment], :update?)).to eq(comment) + end + + it "returns the policy name symbol when passed record with headless policy" do + expect(controller.authorize(:publication, :create?)).to eq(:publication) + end + + it "returns the class when passed record not a particular instance" do + expect(controller.authorize(Post, :show?)).to eq(Post) + 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 "can be given a different policy class" do + expect(controller.authorize(post, :create?, policy_class: PublicationPolicy)).to be_truthy + 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::NotAuthorizedError) + end + + it "raises an error with a invalid policy constructor" do + expect { controller.authorize(wiki, :destroy?) }.to raise_error(Pundit::InvalidConstructorError) + 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 "raises an error with a invalid policy constructor" do + expect { controller.policy(wiki) }.to raise_error(Pundit::InvalidConstructorError) + 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 "allows policy scope class to be overriden" do + expect(controller.policy_scope(Post, policy_scope_class: PublicationPolicy::Scope)).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 "raises an error with a invalid policy scope constructor" do + expect { controller.policy_scope(Wiki) }.to raise_error(Pundit::InvalidConstructorError) + 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( + post: { + title: "Hello", + votes: 5, + admin: true + } + ) + + action = "update" + + expect(Controller.new(user, action, params).permitted_attributes(post).to_h).to eq( + "title" => "Hello", + "votes" => 5 + ) + expect(Controller.new(double, action, params).permitted_attributes(post).to_h).to eq("votes" => 5) + end + + it "checks policy for permitted attributes for record of a ActiveModel type" do + params = ActionController::Parameters.new( + customer_post: { + title: "Hello", + votes: 5, + admin: true + } + ) + + action = "update" + + expect(Controller.new(user, action, params).permitted_attributes(customer_post).to_h).to eq( + "title" => "Hello", + "votes" => 5 + ) + expect(Controller.new(double, action, params).permitted_attributes(customer_post).to_h).to eq( + "votes" => 5 + ) + end + end + + describe "#permitted_attributes_for_action" do + it "is checked if it is defined in the policy" do + params = ActionController::Parameters.new( + post: { + title: "Hello", + body: "blah", + votes: 5, + admin: true + } + ) + + action = "revise" + expect(Controller.new(user, action, params).permitted_attributes(post).to_h).to eq("body" => "blah") + end + + it "can be explicitly set" do + params = ActionController::Parameters.new( + post: { + title: "Hello", + body: "blah", + votes: 5, + admin: true + } + ) + + action = "update" + expect(Controller.new(user, action, params).permitted_attributes(post, :revise).to_h).to eq("body" => "blah") + end + end +end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/spec/generators_spec.rb new/spec/generators_spec.rb --- old/spec/generators_spec.rb 1970-01-01 01:00:00.000000000 +0100 +++ new/spec/generators_spec.rb 2022-02-11 13:14:56.000000000 +0100 @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require "spec_helper" +require "tmpdir" + +require "rails/generators" +require "generators/pundit/install/install_generator" +require "generators/pundit/policy/policy_generator" + +RSpec.describe "generators" do + before(:all) do + @tmpdir = Dir.mktmpdir + + Dir.chdir(@tmpdir) do + Pundit::Generators::InstallGenerator.new([], { quiet: true }).invoke_all + Pundit::Generators::PolicyGenerator.new(%w[Widget], { quiet: true }).invoke_all + + require "./app/policies/application_policy" + require "./app/policies/widget_policy" + end + end + + after(:all) do + FileUtils.remove_entry(@tmpdir) + end + + describe "WidgetPolicy", type: :policy do + permissions :index?, :show?, :create?, :new?, :update?, :edit?, :destroy? do + it "has safe defaults" do + expect(WidgetPolicy).not_to permit(double("User"), double("Widget")) + end + end + + describe "WidgetPolicy::Scope" do + describe "#resolve" do + it "raises a descriptive error" do + scope = WidgetPolicy::Scope.new(double("User"), double("User.all")) + expect { scope.resolve }.to raise_error(NotImplementedError, /WidgetPolicy::Scope/) + end + end + end + end +end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/spec/policy_finder_spec.rb new/spec/policy_finder_spec.rb --- old/spec/policy_finder_spec.rb 2021-08-13 11:10:28.000000000 +0200 +++ new/spec/policy_finder_spec.rb 2022-02-11 13:14:56.000000000 +0100 @@ -2,6 +2,7 @@ require "spec_helper" +class Foo; end RSpec.describe Pundit::PolicyFinder do let(:user) { double } let(:post) { Post.new(user) } @@ -114,7 +115,6 @@ context "with a class that doesn't have an associated policy" do it "returns nil" do - class Foo; end object = described_class.new(Foo) expect(object.policy).to eq nil diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/spec/pundit_spec.rb new/spec/pundit_spec.rb --- old/spec/pundit_spec.rb 2021-08-13 11:10:28.000000000 +0200 +++ new/spec/pundit_spec.rb 2022-02-11 13:14:56.000000000 +0100 @@ -10,15 +10,13 @@ let(:comment) { Comment.new } let(:comment_four_five_six) { CommentFourFiveSix.new } let(:article) { Article.new } - let(:controller) { Controller.new(user, "update", {}) } let(:artificial_blog) { ArtificialBlog.new } let(:article_tag) { ArticleTag.new } - let(:comments_relation) { CommentsRelation.new } - let(:empty_comments_relation) { CommentsRelation.new(true) } + let(:comments_relation) { CommentsRelation.new(empty: false) } + let(:empty_comments_relation) { CommentsRelation.new(empty: true) } let(:tag_four_five_six) { ProjectOneTwoThree::TagFourFiveSix.new(user) } let(:avatar_four_five_six) { ProjectOneTwoThree::AvatarFourFiveSix.new } let(:wiki) { Wiki.new } - let(:thread) { Thread.new } describe ".authorize" do it "infers the policy and authorizes based on it" do @@ -49,6 +47,11 @@ expect(Pundit.authorize(user, post, :create?, policy_class: PublicationPolicy)).to be_truthy end + it "can be given a different policy class using namespaces" do + expect(PublicationPolicy).to receive(:new).with(user, comment).and_call_original + expect(Pundit.authorize(user, [:project, comment], :create?, policy_class: PublicationPolicy)).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) @@ -66,6 +69,18 @@ # rubocop:enable Style/MultilineBlockChain end + it "raises an error with a the record, query and action when the record is namespaced" do + # rubocop:disable Style/MultilineBlockChain + expect do + Pundit.authorize(user, [:project, :admin, comment], :destroy?) + end.to raise_error(Pundit::NotAuthorizedError, "not allowed to destroy? this Comment") do |error| + expect(error.query).to eq :destroy? + expect(error.record).to eq comment + expect(error.policy).to eq Pundit.policy(user, [:project, :admin, comment]) + end + # rubocop:enable Style/MultilineBlockChain + end + it "raises an error with a invalid policy constructor" do expect do Pundit.authorize(user, wiki, :update?) @@ -380,247 +395,26 @@ 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 "returns the record on successful authorization" do - expect(controller.authorize(post)).to eq(post) - end - - it "returns the record when passed record with namespace " do - expect(controller.authorize([:project, comment], :update?)).to eq(comment) - end - - it "returns the record when passed record with nested namespace " do - expect(controller.authorize([:project, :admin, comment], :update?)).to eq(comment) - end - - it "returns the policy name symbol when passed record with headless policy" do - expect(controller.authorize(:publication, :create?)).to eq(:publication) - end + describe ".included" do + it "includes Authorization module" do + klass = Class.new - it "returns the class when passed record not a particular instance" do - expect(controller.authorize(Post, :show?)).to eq(Post) - 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 "can be given a different policy class" do - expect(controller.authorize(post, :create?, policy_class: PublicationPolicy)).to be_truthy - 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::NotAuthorizedError) - end - - it "raises an error with a invalid policy constructor" do - expect { controller.authorize(wiki, :destroy?) }.to raise_error(Pundit::InvalidConstructorError) - 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 "raises an error with a invalid policy constructor" do - expect { controller.policy(wiki) }.to raise_error(Pundit::InvalidConstructorError) - 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 "allows policy scope class to be overriden" do - expect(controller.policy_scope(Post, policy_scope_class: PublicationPolicy::Scope)).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 "raises an error with a invalid policy scope constructor" do - expect { controller.policy_scope(Wiki) }.to raise_error(Pundit::InvalidConstructorError) - end - - it "allows policy_scope to be injected" do - new_scope = OpenStruct.new - controller.policy_scopes[Post] = new_scope + ActiveSupport::Deprecation.silence do + klass.include Pundit + end - expect(controller.policy_scope(Post)).to eq new_scope + expect(klass).to include Pundit::Authorization end - end - describe "#permitted_attributes" do - it "checks policy for permitted attributes" do - params = ActionController::Parameters.new( - post: { - title: "Hello", - votes: 5, - admin: true - } - ) - - action = "update" - - expect(Controller.new(user, action, params).permitted_attributes(post).to_h).to eq( - "title" => "Hello", - "votes" => 5 - ) - expect(Controller.new(double, action, params).permitted_attributes(post).to_h).to eq("votes" => 5) - end - - it "checks policy for permitted attributes for record of a ActiveModel type" do - params = ActionController::Parameters.new( - customer_post: { - title: "Hello", - votes: 5, - admin: true - } - ) - - action = "update" - - expect(Controller.new(user, action, params).permitted_attributes(customer_post).to_h).to eq( - "title" => "Hello", - "votes" => 5 - ) - expect(Controller.new(double, action, params).permitted_attributes(customer_post).to_h).to eq( - "votes" => 5 - ) - end - end + it "warns about deprecation" do + klass = Class.new + allow(ActiveSupport::Deprecation).to receive(:warn) - describe "#permitted_attributes_for_action" do - it "is checked if it is defined in the policy" do - params = ActionController::Parameters.new( - post: { - title: "Hello", - body: "blah", - votes: 5, - admin: true - } - ) - - action = "revise" - expect(Controller.new(user, action, params).permitted_attributes(post).to_h).to eq("body" => "blah") - end - - it "can be explicitly set" do - params = ActionController::Parameters.new( - post: { - title: "Hello", - body: "blah", - votes: 5, - admin: true - } - ) + ActiveSupport::Deprecation.silence do + klass.include Pundit + end - action = "update" - expect(Controller.new(user, action, params).permitted_attributes(post, :revise).to_h).to eq("body" => "blah") + expect(ActiveSupport::Deprecation).to have_received(:warn).with start_with("'include Pundit' is deprecated") end end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/spec/spec_helper.rb new/spec/spec_helper.rb --- old/spec/spec_helper.rb 2021-08-13 11:10:28.000000000 +0200 +++ new/spec/spec_helper.rb 2022-02-11 13:14:56.000000000 +0100 @@ -80,6 +80,7 @@ class CommentScope attr_reader :original_object + def initialize(original_object) @original_object = original_object end @@ -114,7 +115,7 @@ end class CommentsRelation - def initialize(empty = false) + def initialize(empty: false) @empty = empty end @@ -185,6 +186,10 @@ def update? true end + + def destroy? + false + end end end end @@ -196,10 +201,10 @@ end class Controller - include Pundit + include Pundit::Authorization # Mark protected methods public so they may be called in test # rubocop:disable Style/AccessModifierDeclarations - public(*Pundit.protected_instance_methods) + public(*Pundit::Authorization.protected_instance_methods) # rubocop:enable Style/AccessModifierDeclarations attr_reader :current_user, :action_name, :params @@ -228,6 +233,7 @@ end class Wiki; end + class WikiPolicy class Scope # deliberate typo method @@ -238,6 +244,7 @@ class Thread def self.all; end end + class ThreadPolicy < Struct.new(:user, :thread) class Scope < Struct.new(:user, :scope) def resolve