New Upstream Snapshot - ruby-pundit
Ready changes
Summary
Merged new upstream version: 2.2.0+git20221219.1.dd53b69 (was: 2.1.0).
Resulting package
Built on 2023-02-11T17:38 (took 38m46s)
The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:
apt install -t fresh-snapshots ruby-pundit
Lintian Result
Diff
diff --git a/.gitignore b/.gitignore
deleted file mode 100644
index 1b71c6b..0000000
--- a/.gitignore
+++ /dev/null
@@ -1,18 +0,0 @@
-*.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
diff --git a/.rubocop.yml b/.rubocop.yml
index 6306ca1..249d675 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -1,10 +1,9 @@
AllCops:
- DisplayCopNames: true
- TargetRubyVersion: 2.2
+ TargetRubyVersion: 2.6
Exclude:
- - "gemfiles/**/*"
- - "vendor/**/*"
- - "lib/generators/**/*"
+ - "lib/generators/**/templates/**/*"
+ SuggestExtensions: false
+ NewCops: disable
Metrics/BlockLength:
Exclude:
@@ -18,7 +17,7 @@ Metrics/ModuleLength:
Exclude:
- "**/*_spec.rb"
-Metrics/LineLength:
+Layout/LineLength:
Max: 120
Metrics/AbcSize:
@@ -30,7 +29,10 @@ Metrics/CyclomaticComplexity:
Metrics/PerceivedComplexity:
Enabled: false
-Layout/AlignParameters:
+Gemspec/RequiredRubyVersion:
+ Enabled: false
+
+Layout/ParameterAlignment:
EnforcedStyle: with_fixed_indentation
Layout/CaseIndentation:
@@ -40,15 +42,9 @@ Layout/CaseIndentation:
- end
IndentOneStep: true
-Layout/AccessModifierIndentation:
- EnforcedStyle: outdent
-
Layout/EndAlignment:
EnforcedStyleAlignWith: variable
-Style/FrozenStringLiteralComment:
- Enabled: true
-
Style/PercentLiteralDelimiters:
PreferredDelimiters:
'%w': "[]"
@@ -72,5 +68,5 @@ Style/Not:
Style/DoubleNegation:
Enabled: false
-Documentation:
+Style/Documentation:
Enabled: false # TODO: Enable again once we have more docs
diff --git a/.travis.yml b/.travis.yml
index e2f943e..184a9e6 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,21 +1,26 @@
language: ruby
-before_install:
- - gem install bundler -v 1.17.3
+dist: focal
matrix:
include:
- - rvm: 2.5.1 # Pre-installed Ruby version
+ - name: "RuboCop lint on pre-installed Ruby version"
+ rvm: 2.7.1 # Pre-installed Ruby version
+ before_install:
+ - gem install bundler
script: bundle exec rake rubocop # ONLY lint once, first
- - rvm: 2.1
- - rvm: 2.2
- - rvm: 2.3.5
- - rvm: 2.4.6
- - rvm: 2.5.5
- - rvm: 2.6.3
- - rvm: jruby-9.1.8.0
- env:
- - JRUBY_OPTS="--debug"
- jdk: openjdk8
- - rvm: jruby-9.2.8.0
+ - rvm: 2.6.7
+ before_script:
+ - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
+ - chmod +x ./cc-test-reporter
+ - ./cc-test-reporter before-build
+ after_script:
+ - ./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"
+ - rvm: truffleruby-head
+ allow_failures:
+ - rvm: truffleruby-head
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5217ac1..279fba1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,51 @@
# Pundit
+## 2.3.0 (2022-12-19)
+
+### Added
+
+- add support for rubocop-rspec syntax extensions (#745)
+
+## 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)
+
+Friday 13th-release!
+
+Careful! The bugfix below (#626) could break existing code. If you rely on the
+return value for `authorize` and namespaced policies you might need to do some
+changes.
+
+### Fixed
+
+- `.authorize` and `#authorize` return the instance, even for namespaced
+ policies (#626)
+
+### Changed
+
+- Generate application scope with `protected` attr_readers. (#616)
+
+### Removed
+
+- Dropped support for Ruby end-of-life versions: 2.1 and 2.2. (#604)
+- Dropped support for Ruby end-of-life versions: 2.3 (#633)
+- Dropped support for Ruby end-of-life versions: 2.4, 2.5 and JRuby 9.1 (#676)
+- Dropped support for RSpec 2 (#615)
+
+## 2.1.0 (2019-08-14)
+
### Fixed
- Avoid name clashes with the Error class. (#590)
diff --git a/README.md b/README.md
index 3aa7774..4ce649e 100644
--- a/README.md
+++ b/README.md
@@ -7,11 +7,11 @@
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.
+scalable authorization system.
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,16 +22,17 @@ Sponsored by:
## Installation
-``` ruby
-gem "pundit"
+> **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).
+
+``` sh
+bundle add pundit
```
-Include Pundit in your application controller:
+Include `Pundit::Authorization` in your application controller:
``` ruby
class ApplicationController < ActionController::Base
- include Pundit
- protect_from_forgery
+ include Pundit::Authorization
end
```
@@ -61,7 +62,7 @@ class PostPolicy
end
def update?
- user.admin? or not post.published?
+ user.admin? || !post.published?
end
end
```
@@ -165,13 +166,18 @@ def admin_list
end
```
-`authorize` returns the object passed to it, so you can chain it like this:
+`authorize` returns the instance passed to it, so you can chain it like this:
Controller:
```ruby
def show
@user = authorize User.find(params[:id])
end
+
+# return the record even for namespaced policies
+def show
+ @user = authorize [:admin, User.find(params[:id])]
+end
```
You can easily get a hold of an instance of the policy through the `policy`
@@ -190,8 +196,17 @@ you can retrieve it by passing a symbol.
```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
```
@@ -201,7 +216,10 @@ is what is passed as the record to `authorize` below.
```ruby
# In controllers
-authorize :dashboard, :show?
+def show
+ authorize :dashboard, :show?
+ ...
+end
```
```erb
@@ -220,8 +238,6 @@ 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
@@ -234,6 +250,10 @@ class PostPolicy < ApplicationPolicy
scope.where(published: true)
end
end
+
+ private
+
+ attr_reader :user, :scope
end
def update?
@@ -296,13 +316,11 @@ def index
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:
+In this case it is a shortcut for doing:
``` ruby
def index
- @posts = PostPolicy::Scope.new(current_user, Post).resolve
+ @publications = PublicationPolicy::Scope.new(current_user, Post).resolve
end
```
@@ -330,7 +348,7 @@ that you haven't forgotten to authorize the action. For example:
``` ruby
class ApplicationController < ActionController::Base
- include Pundit
+ include Pundit::Authorization
after_action :verify_authorized
end
```
@@ -343,7 +361,7 @@ authorize individual instances.
``` ruby
class ApplicationController < ActionController::Base
- include Pundit
+ include Pundit::Authorization
after_action :verify_authorized, except: :index
after_action :verify_policy_scoped, only: :index
end
@@ -391,6 +409,16 @@ class Post
end
```
+Alternatively, you can declare an instance method:
+
+``` ruby
+class Post
+ def policy_class
+ PostablePolicy
+ end
+end
+```
+
## Just plain old Ruby
As you can see, Pundit doesn't do anything you couldn't have easily done
@@ -476,8 +504,7 @@ method in every controller.
```ruby
class ApplicationController < ActionController::Base
- protect_from_forgery
- include Pundit
+ include Pundit::Authorization
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
@@ -485,7 +512,7 @@ class ApplicationController < ActionController::Base
def user_not_authorized
flash[:alert] = "You are not authorized to perform this action."
- redirect_to(request.referrer || root_path)
+ redirect_back(fallback_location: root_path)
end
end
```
@@ -514,7 +541,7 @@ class ApplicationController < ActionController::Base
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)
+ redirect_back(fallback_url: root_path)
end
end
```
@@ -597,8 +624,7 @@ class Admin::PostController < AdminController
end
def show
- post = Post.find(params[:id])
- authorize(post)
+ post = authorize Post.find(params[:id])
end
end
```
@@ -631,7 +657,7 @@ class UserContext
end
class ApplicationController
- include Pundit
+ include Pundit::Authorization
def pundit_user
UserContext.new(current_user, request.ip)
@@ -641,9 +667,8 @@ 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
+In Rails,
+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:
@@ -667,7 +692,7 @@ You can now retrieve these attributes from the policy:
class PostsController < ApplicationController
def update
@post = Post.find(params[:id])
- if @post.update_attributes(post_params)
+ if @post.update(post_params)
redirect_to @post
else
render :edit
@@ -689,7 +714,7 @@ However, this is a bit cumbersome, so Pundit provides a convenient helper method
class PostsController < ApplicationController
def update
@post = Post.find(params[:id])
- if @post.update_attributes(permitted_attributes(@post))
+ if @post.update(permitted_attributes(@post))
redirect_to @post
else
render :edit
@@ -777,11 +802,22 @@ An alternative approach to Pundit policy specs is scoping them to a user context
Pundit does not provide a DSL for testing scopes. Just test it like a regular Ruby class!
+### Linting with RuboCop RSpec
+
+When you lint your RSpec spec files with `rubocop-rspec`, it will fail to properly detect RSpec constructs that Pundit defines, `permissions`.
+Make sure to use `rubocop-rspec` 2.0 or newer and add the following to your `.rubocop.yml`:
+
+```yaml
+inherit_gem:
+ pundit: config/rubocop-rspec.yml
+```
+
# 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/)
+- [Testing Pundit with Minitest](https://github.com/varvet/pundit/issues/204#issuecomment-60166450)
- [Using Pundit outside of a Rails controller](https://github.com/varvet/pundit/pull/136)
- [Straightforward Rails Authorization with Pundit](http://www.sitepoint.com/straightforward-rails-authorization-with-pundit/)
diff --git a/config/rubocop-rspec.yml b/config/rubocop-rspec.yml
new file mode 100644
index 0000000..6f4c7e1
--- /dev/null
+++ b/config/rubocop-rspec.yml
@@ -0,0 +1,5 @@
+RSpec:
+ Language:
+ ExampleGroups:
+ Regular:
+ - permissions
diff --git a/debian/changelog b/debian/changelog
index 4a94efe..492bf26 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,9 +1,10 @@
-ruby-pundit (2.1.0-2) UNRELEASED; urgency=medium
+ruby-pundit (2.2.0+git20221219.1.dd53b69-1) UNRELEASED; urgency=medium
* Update standards version to 4.5.1, no changes needed.
* Update standards version to 4.6.2, no changes needed.
+ * New upstream snapshot.
- -- Debian Janitor <janitor@jelmer.uk> Sat, 11 Sep 2021 04:33:55 -0000
+ -- Debian Janitor <janitor@jelmer.uk> Sat, 11 Feb 2023 17:10:16 -0000
ruby-pundit (2.1.0-1) unstable; urgency=medium
diff --git a/lib/generators/pundit/install/install_generator.rb b/lib/generators/pundit/install/install_generator.rb
index 2aa26c3..de87aa9 100644
--- a/lib/generators/pundit/install/install_generator.rb
+++ b/lib/generators/pundit/install/install_generator.rb
@@ -1,10 +1,12 @@
+# frozen_string_literal: true
+
module Pundit
module Generators
class InstallGenerator < ::Rails::Generators::Base
- source_root File.expand_path('templates', __dir__)
+ source_root File.expand_path("templates", __dir__)
def copy_application_policy
- template 'application_policy.rb', 'app/policies/application_policy.rb'
+ template "application_policy.rb", "app/policies/application_policy.rb"
end
end
end
diff --git a/lib/generators/pundit/install/templates/application_policy.rb b/lib/generators/pundit/install/templates/application_policy.rb
index eefe976..e000cba 100644
--- a/lib/generators/pundit/install/templates/application_policy.rb
+++ b/lib/generators/pundit/install/templates/application_policy.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class ApplicationPolicy
attr_reader :user, :record
@@ -35,15 +37,17 @@ class ApplicationPolicy
end
class Scope
- attr_reader :user, :scope
-
def initialize(user, scope)
@user = user
@scope = scope
end
def resolve
- scope.all
+ raise NotImplementedError, "You must define #resolve in #{self.class}"
end
+
+ private
+
+ attr_reader :user, :scope
end
end
diff --git a/lib/generators/pundit/policy/policy_generator.rb b/lib/generators/pundit/policy/policy_generator.rb
index 95bcc28..b4734fc 100644
--- a/lib/generators/pundit/policy/policy_generator.rb
+++ b/lib/generators/pundit/policy/policy_generator.rb
@@ -1,10 +1,12 @@
+# frozen_string_literal: true
+
module Pundit
module Generators
class PolicyGenerator < ::Rails::Generators::NamedBase
- source_root File.expand_path('templates', __dir__)
+ source_root File.expand_path("templates", __dir__)
def create_policy
- template 'policy.rb', File.join('app/policies', class_path, "#{file_name}_policy.rb")
+ template "policy.rb", File.join("app/policies", class_path, "#{file_name}_policy.rb")
end
hook_for :test_framework
diff --git a/lib/generators/pundit/policy/templates/policy.rb b/lib/generators/pundit/policy/templates/policy.rb
index 8eb9def..6798550 100644
--- a/lib/generators/pundit/policy/templates/policy.rb
+++ b/lib/generators/pundit/policy/templates/policy.rb
@@ -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 --git a/lib/generators/rspec/policy_generator.rb b/lib/generators/rspec/policy_generator.rb
index 24db961..026e60d 100644
--- a/lib/generators/rspec/policy_generator.rb
+++ b/lib/generators/rspec/policy_generator.rb
@@ -1,10 +1,12 @@
+# frozen_string_literal: true
+
module Rspec
module Generators
class PolicyGenerator < ::Rails::Generators::NamedBase
- source_root File.expand_path('templates', __dir__)
+ source_root File.expand_path("templates", __dir__)
def create_policy_spec
- template 'policy_spec.rb', File.join('spec/policies', class_path, "#{file_name}_policy_spec.rb")
+ template "policy_spec.rb", File.join("spec/policies", class_path, "#{file_name}_policy_spec.rb")
end
end
end
diff --git a/lib/generators/test_unit/policy_generator.rb b/lib/generators/test_unit/policy_generator.rb
index c22f4fa..89c375f 100644
--- a/lib/generators/test_unit/policy_generator.rb
+++ b/lib/generators/test_unit/policy_generator.rb
@@ -1,10 +1,12 @@
+# frozen_string_literal: true
+
module TestUnit
module Generators
class PolicyGenerator < ::Rails::Generators::NamedBase
- source_root File.expand_path('templates', __dir__)
+ source_root File.expand_path("templates", __dir__)
def create_policy_test
- template 'policy_test.rb', File.join('test/policies', class_path, "#{file_name}_policy_test.rb")
+ template "policy_test.rb", File.join("test/policies", class_path, "#{file_name}_policy_test.rb")
end
end
end
diff --git a/lib/pundit.rb b/lib/pundit.rb
index 0262f96..99b3502 100644
--- a/lib/pundit.rb
+++ b/lib/pundit.rb
@@ -7,6 +7,7 @@ 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"
+require "pundit/authorization"
# @api private
# To avoid name clashes with common Error naming when mixing in Pundit,
@@ -15,7 +16,7 @@ class Pundit::Error < StandardError; end # rubocop:disable Style/ClassAndModuleC
# @api public
module Pundit
- SUFFIX = "Policy".freeze
+ SUFFIX = "Policy"
# @api private
module Generators; end
@@ -53,7 +54,12 @@ module Pundit
# 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
+ '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,13 +67,19 @@ module Pundit
# 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)
@@ -124,7 +136,7 @@ module Pundit
# @return [Object, nil] instance of policy class with query methods
def policy(user, record)
policy = PolicyFinder.new(record).policy
- policy.new(user, pundit_model(record)) if policy
+ policy&.new(user, pundit_model(record))
rescue ArgumentError
raise InvalidConstructorError, "Invalid #<#{policy}> constructor is called"
end
@@ -144,7 +156,7 @@ module Pundit
raise InvalidConstructorError, "Invalid #<#{policy}> constructor is called"
end
- private
+ private
def pundit_model(record)
record.is_a?(Array) ? record.last : record
@@ -157,169 +169,4 @@ module Pundit
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
- 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 --git a/lib/pundit/authorization.rb b/lib/pundit/authorization.rb
new file mode 100644
index 0000000..1231f2a
--- /dev/null
+++ b/lib/pundit/authorization.rb
@@ -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 --git a/lib/pundit/policy_finder.rb b/lib/pundit/policy_finder.rb
index df072d9..40467d9 100644
--- a/lib/pundit/policy_finder.rb
+++ b/lib/pundit/policy_finder.rb
@@ -68,7 +68,7 @@ module Pundit
end
end
- private
+ private
def find(subject)
if subject.is_a?(Array)
diff --git a/lib/pundit/rspec.rb b/lib/pundit/rspec.rb
index 687cf17..243ebec 100644
--- a/lib/pundit/rspec.rb
+++ b/lib/pundit/rspec.rb
@@ -1,7 +1,5 @@
# frozen_string_literal: true
-require "active_support/core_ext/array/conversions"
-
module Pundit
module RSpec
module Matchers
@@ -74,17 +72,9 @@ module Pundit
end
RSpec.configure do |config|
- if RSpec::Core::Version::STRING.split(".").first.to_i >= 3
- config.include(
- Pundit::RSpec::PolicyExampleGroup,
- type: :policy,
- file_path: %r{spec/policies}
- )
- else
- config.include(
- Pundit::RSpec::PolicyExampleGroup,
- type: :policy,
- example_group: { file_path: %r{spec/policies} }
- )
- end
+ config.include(
+ Pundit::RSpec::PolicyExampleGroup,
+ type: :policy,
+ file_path: %r{spec/policies}
+ )
end
diff --git a/lib/pundit/version.rb b/lib/pundit/version.rb
index 7869632..e15152f 100644
--- a/lib/pundit/version.rb
+++ b/lib/pundit/version.rb
@@ -1,5 +1,5 @@
# frozen_string_literal: true
module Pundit
- VERSION = "2.1.0".freeze
+ VERSION = "2.3.0"
end
diff --git a/pundit.gemspec b/pundit.gemspec
index 5117319..44c8e8f 100644
--- a/pundit.gemspec
+++ b/pundit.gemspec
@@ -24,8 +24,10 @@ Gem::Specification.new do |gem|
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", ">= 2.0.0"
- gem.add_development_dependency "rubocop", "0.57.2"
+ gem.add_development_dependency "rspec", ">= 3.0.0"
+ gem.add_development_dependency "rubocop", "1.24.0"
+ gem.add_development_dependency "simplecov", ">= 0.17.0"
gem.add_development_dependency "yard"
end
diff --git a/spec/authorization_spec.rb b/spec/authorization_spec.rb
new file mode 100644
index 0000000..2995bfd
--- /dev/null
+++ b/spec/authorization_spec.rb
@@ -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 --git a/spec/generators_spec.rb b/spec/generators_spec.rb
new file mode 100644
index 0000000..5f15877
--- /dev/null
+++ b/spec/generators_spec.rb
@@ -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 --git a/spec/policies/post_policy_spec.rb b/spec/policies/post_policy_spec.rb
index 1d19ef9..54e301e 100644
--- a/spec/policies/post_policy_spec.rb
+++ b/spec/policies/post_policy_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe PostPolicy do
+RSpec.describe PostPolicy do
let(:user) { double }
let(:own_post) { double(user: user) }
let(:other_post) { double(user: double) }
diff --git a/spec/policy_finder_spec.rb b/spec/policy_finder_spec.rb
index 76533c7..11eb25a 100644
--- a/spec/policy_finder_spec.rb
+++ b/spec/policy_finder_spec.rb
@@ -2,7 +2,8 @@
require "spec_helper"
-describe Pundit::PolicyFinder do
+class Foo; end
+RSpec.describe Pundit::PolicyFinder do
let(:user) { double }
let(:post) { Post.new(user) }
let(:comment) { CommentFourFiveSix.new }
@@ -24,37 +25,99 @@ describe Pundit::PolicyFinder do
end
describe "#policy" do
- subject { described_class.new(post) }
+ context "with an instance" do
+ it "returns the associated policy" do
+ object = described_class.new(post)
+
+ expect(object.policy).to eq PostPolicy
+ end
+ end
+
+ context "with an array of symbols" do
+ it "returns the associated namespaced policy" do
+ object = described_class.new(%i[project post])
+
+ expect(object.policy).to eq Project::PostPolicy
+ end
+ end
+
+ context "with an array of a symbol and an instance" do
+ it "returns the associated namespaced policy" do
+ object = described_class.new([:project, post])
- it "returns a policy" do
- expect(subject.policy).to eq PostPolicy
+ expect(object.policy).to eq Project::PostPolicy
+ end
end
- context "with a string" do
- it "returns a policy" do
- allow(subject).to receive(:find).and_return "PostPolicy"
- expect(subject.policy).to eq PostPolicy
+ context "with an array of a symbol and a class with a specified policy class" do
+ it "returns the associated namespaced policy" do
+ object = described_class.new([:project, Customer::Post])
+
+ expect(object.policy).to eq Project::PostPolicy
+ end
+ end
+
+ context "with an array of a symbol and a class with a specified model name" do
+ it "returns the associated namespaced policy" do
+ object = described_class.new([:project, CommentsRelation])
+
+ expect(object.policy).to eq Project::CommentPolicy
end
end
context "with a class" do
- it "returns a policy" do
- allow(subject).to receive(:find).and_return PostPolicy
- expect(subject.policy).to eq PostPolicy
+ it "returns the associated policy" do
+ object = described_class.new(Post)
+
+ expect(object.policy).to eq PostPolicy
+ end
+ end
+
+ context "with a class which has a specified policy class" do
+ it "returns the associated policy" do
+ object = described_class.new(Customer::Post)
+
+ expect(object.policy).to eq PostPolicy
+ end
+ end
+
+ context "with an instance which has a specified policy class" do
+ it "returns the associated policy" do
+ object = described_class.new(Customer::Post.new(user))
+
+ expect(object.policy).to eq PostPolicy
+ end
+ end
+
+ context "with a class which has a specified model name" do
+ it "returns the associated policy" do
+ object = described_class.new(CommentsRelation)
+
+ expect(object.policy).to eq CommentPolicy
+ end
+ end
+
+ context "with an instance which has a specified policy class" do
+ it "returns the associated policy" do
+ object = described_class.new(CommentsRelation.new)
+
+ expect(object.policy).to eq CommentPolicy
end
end
context "with nil" do
- it "returns nil" do
- allow(subject).to receive(:find).and_return nil
- expect(subject.policy).to eq nil
+ it "returns a NilClassPolicy" do
+ object = described_class.new(nil)
+
+ expect(object.policy).to eq NilClassPolicy
end
end
- context "with a string that can't be constantized" do
+ context "with a class that doesn't have an associated policy" do
it "returns nil" do
- allow(subject).to receive(:find).and_return "FooPolicy"
- expect(subject.policy).to eq nil
+ object = described_class.new(Foo)
+
+ expect(object.policy).to eq nil
end
end
end
diff --git a/spec/pundit_spec.rb b/spec/pundit_spec.rb
index 632f0c0..91aefc3 100644
--- a/spec/pundit_spec.rb
+++ b/spec/pundit_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-describe Pundit do
+RSpec.describe Pundit do
let(:user) { double }
let(:post) { Post.new(user) }
let(:customer_post) { Customer::Post.new(user) }
@@ -10,25 +10,48 @@ describe Pundit do
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
expect(Pundit.authorize(user, post, :update?)).to be_truthy
end
+ it "returns the record on successful authorization" do
+ expect(Pundit.authorize(user, post, :update?)).to eq(post)
+ end
+
+ it "returns the record when passed record with namespace " do
+ expect(Pundit.authorize(user, [:project, comment], :update?)).to eq(comment)
+ end
+
+ it "returns the record when passed record with nested namespace " do
+ expect(Pundit.authorize(user, [:project, :admin, comment], :update?)).to eq(comment)
+ end
+
+ it "returns the policy name symbol when passed record with headless policy" do
+ expect(Pundit.authorize(user, :publication, :create?)).to eq(:publication)
+ end
+
+ it "returns the class when passed record not a particular instance" do
+ expect(Pundit.authorize(user, Post, :show?)).to eq(Post)
+ end
+
it "can be given a different policy class" do
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)
@@ -46,6 +69,18 @@ describe Pundit do
# 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?)
@@ -360,231 +395,26 @@ describe Pundit do
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
+ describe ".included" do
+ it "includes Authorization module" do
+ klass = Class.new
- 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 be(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
+ ActiveSupport::Deprecation.silence do
+ klass.include Pundit
+ end
- it "raises an error with a invalid policy scope constructor" do
- expect { controller.policy_scope(Wiki) }.to raise_error(Pundit::InvalidConstructorError)
+ expect(klass).to include Pundit::Authorization
end
- it "allows policy_scope to be injected" do
- new_scope = OpenStruct.new
- controller.policy_scopes[Post] = new_scope
+ it "warns about deprecation" do
+ klass = Class.new
+ allow(ActiveSupport::Deprecation).to receive(:warn)
- 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
+ ActiveSupport::Deprecation.silence do
+ klass.include Pundit
+ 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")
+ expect(ActiveSupport::Deprecation).to have_received(:warn).with start_with("'include Pundit' is deprecated")
end
end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 3768762..192146f 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -1,5 +1,10 @@
# frozen_string_literal: true
+require "simplecov"
+SimpleCov.start do
+ add_filter "/spec/"
+end
+
require "pundit"
require "pundit/rspec"
@@ -11,22 +16,6 @@ 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)
class Scope < Struct.new(:user, :scope)
def resolve
@@ -86,15 +75,12 @@ module Customer
def self.policy_class
PostPolicy
end
-
- def policy_class
- self.class.policy_class
- end
end
end
class CommentScope
attr_reader :original_object
+
def initialize(original_object)
@original_object = original_object
end
@@ -129,7 +115,7 @@ class Comment
end
class CommentsRelation
- def initialize(empty = false)
+ def initialize(empty: false)
@empty = empty
end
@@ -137,7 +123,7 @@ class CommentsRelation
@empty
end
- def model_name
+ def self.model_name
Comment.model_name
end
end
@@ -174,6 +160,10 @@ class CriteriaPolicy < Struct.new(:user, :criteria); end
module Project
class CommentPolicy < Struct.new(:user, :comment)
+ def update?
+ true
+ end
+
class Scope < Struct.new(:user, :scope)
def resolve
scope
@@ -190,6 +180,18 @@ module Project
end
end
end
+
+ module Admin
+ class CommentPolicy < Struct.new(:user, :comment)
+ def update?
+ true
+ end
+
+ def destroy?
+ false
+ end
+ end
+ end
end
class DenierPolicy < Struct.new(:user, :record)
@@ -199,11 +201,11 @@ class DenierPolicy < Struct.new(:user, :record)
end
class Controller
- include Pundit
+ include Pundit::Authorization
# Mark protected methods public so they may be called in test
- # rubocop:disable Layout/AccessModifierIndentation, Style/AccessModifierDeclarations
- public(*Pundit.protected_instance_methods)
- # rubocop:enable Layout/AccessModifierIndentation, Style/AccessModifierDeclarations
+ # rubocop:disable Style/AccessModifierDeclarations
+ public(*Pundit::Authorization.protected_instance_methods)
+ # rubocop:enable Style/AccessModifierDeclarations
attr_reader :current_user, :action_name, :params
@@ -231,6 +233,7 @@ class NilClassPolicy < Struct.new(:user, :record)
end
class Wiki; end
+
class WikiPolicy
class Scope
# deliberate typo method
@@ -241,6 +244,7 @@ end
class Thread
def self.all; end
end
+
class ThreadPolicy < Struct.new(:user, :thread)
class Scope < Struct.new(:user, :scope)
def resolve
Debdiff
[The following lists of changes regard files as different if they have different names, permissions or owners.]
Files in second set of .debs but not in first
-rw-r--r-- root/root /usr/share/rubygems-integration/all/gems/pundit-2.3.0/config/rubocop-rspec.yml -rw-r--r-- root/root /usr/share/rubygems-integration/all/gems/pundit-2.3.0/lib/generators/pundit/install/USAGE -rw-r--r-- root/root /usr/share/rubygems-integration/all/gems/pundit-2.3.0/lib/generators/pundit/install/install_generator.rb -rw-r--r-- root/root /usr/share/rubygems-integration/all/gems/pundit-2.3.0/lib/generators/pundit/install/templates/application_policy.rb -rw-r--r-- root/root /usr/share/rubygems-integration/all/gems/pundit-2.3.0/lib/generators/pundit/policy/USAGE -rw-r--r-- root/root /usr/share/rubygems-integration/all/gems/pundit-2.3.0/lib/generators/pundit/policy/policy_generator.rb -rw-r--r-- root/root /usr/share/rubygems-integration/all/gems/pundit-2.3.0/lib/generators/pundit/policy/templates/policy.rb -rw-r--r-- root/root /usr/share/rubygems-integration/all/gems/pundit-2.3.0/lib/generators/rspec/policy_generator.rb -rw-r--r-- root/root /usr/share/rubygems-integration/all/gems/pundit-2.3.0/lib/generators/rspec/templates/policy_spec.rb -rw-r--r-- root/root /usr/share/rubygems-integration/all/gems/pundit-2.3.0/lib/generators/test_unit/policy_generator.rb -rw-r--r-- root/root /usr/share/rubygems-integration/all/gems/pundit-2.3.0/lib/generators/test_unit/templates/policy_test.rb -rw-r--r-- root/root /usr/share/rubygems-integration/all/gems/pundit-2.3.0/lib/pundit.rb -rw-r--r-- root/root /usr/share/rubygems-integration/all/gems/pundit-2.3.0/lib/pundit/authorization.rb -rw-r--r-- root/root /usr/share/rubygems-integration/all/gems/pundit-2.3.0/lib/pundit/policy_finder.rb -rw-r--r-- root/root /usr/share/rubygems-integration/all/gems/pundit-2.3.0/lib/pundit/rspec.rb -rw-r--r-- root/root /usr/share/rubygems-integration/all/gems/pundit-2.3.0/lib/pundit/version.rb -rw-r--r-- root/root /usr/share/rubygems-integration/all/specifications/pundit-2.3.0.gemspec
Files in first set of .debs but not in second
-rw-r--r-- root/root /usr/share/rubygems-integration/all/gems/pundit-2.1.0/lib/generators/pundit/install/USAGE -rw-r--r-- root/root /usr/share/rubygems-integration/all/gems/pundit-2.1.0/lib/generators/pundit/install/install_generator.rb -rw-r--r-- root/root /usr/share/rubygems-integration/all/gems/pundit-2.1.0/lib/generators/pundit/install/templates/application_policy.rb -rw-r--r-- root/root /usr/share/rubygems-integration/all/gems/pundit-2.1.0/lib/generators/pundit/policy/USAGE -rw-r--r-- root/root /usr/share/rubygems-integration/all/gems/pundit-2.1.0/lib/generators/pundit/policy/policy_generator.rb -rw-r--r-- root/root /usr/share/rubygems-integration/all/gems/pundit-2.1.0/lib/generators/pundit/policy/templates/policy.rb -rw-r--r-- root/root /usr/share/rubygems-integration/all/gems/pundit-2.1.0/lib/generators/rspec/policy_generator.rb -rw-r--r-- root/root /usr/share/rubygems-integration/all/gems/pundit-2.1.0/lib/generators/rspec/templates/policy_spec.rb -rw-r--r-- root/root /usr/share/rubygems-integration/all/gems/pundit-2.1.0/lib/generators/test_unit/policy_generator.rb -rw-r--r-- root/root /usr/share/rubygems-integration/all/gems/pundit-2.1.0/lib/generators/test_unit/templates/policy_test.rb -rw-r--r-- root/root /usr/share/rubygems-integration/all/gems/pundit-2.1.0/lib/pundit.rb -rw-r--r-- root/root /usr/share/rubygems-integration/all/gems/pundit-2.1.0/lib/pundit/policy_finder.rb -rw-r--r-- root/root /usr/share/rubygems-integration/all/gems/pundit-2.1.0/lib/pundit/rspec.rb -rw-r--r-- root/root /usr/share/rubygems-integration/all/gems/pundit-2.1.0/lib/pundit/version.rb -rw-r--r-- root/root /usr/share/rubygems-integration/all/specifications/pundit-2.1.0.gemspec
No differences were encountered in the control files