Adding ArticlePolicy.scope_users_authorized_to_action (#16732)

As I'm looking at limiting feed fetching only for users who are able to
create articles, I needed a means of querying "Who are the users who can
create articles?"  This bit of work was the smallest chunk that I could
think of.

These kinds of questsions are going to propogate (e.g. give me a list of
all the people who can comment? who can create articles in this space?)
For now, this pattern should suffice.

Yes, there's duplication of knowledge between the policy's instance
method and class method.  And there's a bit of knowledge bleed between a
user's method (e.g. `user.any_admin?` and
`*Authorizer::RoleBasedQueries::ANY_ADMIN`) but that's a small "evil".

Related to #16486
This commit is contained in:
Jeremy Friesen 2022-03-02 11:02:04 -05:00 committed by GitHub
parent c5a90173bd
commit eff161ba58
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 63 additions and 0 deletions

View file

@ -12,6 +12,43 @@ class ArticlePolicy < ApplicationPolicy
FeatureFlag.enabled?(:limit_post_creation_to_admins)
end
# Helps filter a `:user_scope` to those authorized to the `:action`. I want a list of all users
# who can create an Article. This policy method can help with that.
#
# @param user_scope [ActiveRecord::Relation] a scope for querying user objects
# @param action [Symbol] the name of one of the ArticlePolicy action predicates (e.g. :create?,
# :new?) though as a convenience, we will also accept :new, and :create.
#
# @return [ActiveRecord::Relation]
#
# @see https://api.rubyonrails.org/classes/ActiveRecord/Scoping/Named/ClassMethods.html#method-i-scope
#
# @note With this duplication it would be feasible to alter the instance method logics to use the
# class method (e.g. `ArticlePolicy.scope_authorized(user_scope: User, action:
# :create?).find_by(user.id)`) but that's a future consideration.
#
# @note This is not a Pundit scope (see https://github.com/varvet/pundit#scopes), as those methods
# are for answering "What articles can I see?" This method is for answering "Who all can
# <action> on Articles?"
#
# @note Why isn't this a User.scope method? Because the logic of who can take an action on the
# resource is the problem domain of the policy.
def self.scope_users_authorized_to_action(user_scope:, action:)
case action
when :create?, :new?, :create, :new
# Note the delicate dance to duplicate logic in a general sense. [I hope that] this is a
# stop-gap solution.
user_scope = user_scope.without_role(:suspended)
return user_scope unless limit_post_creation_to_admins?
# NOTE: Not a fan of reaching over to the constant of another class, but I digress.
user_scope.with_any_role(*Authorizer::RoleBasedQueries::ANY_ADMIN_ROLES)
else
# Not going to implement all of the use cases.
raise "Unhandled predicate: #{action} for #{self}.#{__method__}"
end
end
# @note [@jeremyf] I am re-implemnenting the initialize method, but removing the Pundit
# authorization. There's an assumption that all policy questions will require a user,
# unless you know specifically that they don't.
@ -40,6 +77,7 @@ class ArticlePolicy < ApplicationPolicy
true
end
# @see {ArticlePolicy.scope_users_authorized_to_action} for "mirrored" details.
def create?
require_user_in_good_standing!
return true unless self.class.limit_post_creation_to_admins?

View file

@ -84,6 +84,31 @@ RSpec.describe ArticlePolicy do
let(:resource) { build(:article, user: author, organization: organization) }
let(:policy) { described_class.new(user, resource) }
describe ".scope_users_authorized_to_action" do
let!(:regular_user) { create(:user) }
let!(:super_admin_user) { create(:user, :super_admin) }
before { create(:user, :suspended) }
context "when limit_post_creation_to_admins is true" do
before { allow(described_class).to receive(:limit_post_creation_to_admins?).and_return(true) }
it "omits suspended and regular users" do
results = described_class.scope_users_authorized_to_action(user_scope: User, action: :create?).to_a
expect(results).to match_array([super_admin_user])
end
end
context "when limit_post_creation_to_admins is false" do
before { allow(described_class).to receive(:limit_post_creation_to_admins?).and_return(false) }
it "omits only suspended users" do
results = described_class.scope_users_authorized_to_action(user_scope: User, action: :create?).to_a
expect(results).to match_array([regular_user, super_admin_user])
end
end
end
describe "#feed?" do
let(:policy_method) { :feed? }