docbrown/app/policies/article_policy.rb
Rajat Talesra 992ced1908
Tag moderation UI changes (#19906)
* Basic add remove functionality with few tests

* Updated cypress test and added more unit tests

* Rename display ads vars in the views (#19877)

* Renamed display_ads vars to billboards

* Rename local vars in the feed view

* Renamed display-ad in the comment

Co-authored-by: Rajat Talesra <rajattalesra4914@gmail.com>

* Renamed variables in the views

---------

Co-authored-by: Rajat Talesra <rajattalesra4914@gmail.com>

* Update js-routes to version 2.2.7 (#19883)

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>

* add field for billboard target geolocations, with unit and e2e tests (#19855)

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
Co-authored-by: Duke Greene <dukegreene@gmail.com>
Co-authored-by: PJ <pj@forem.com>
Co-authored-by: Anna Buianova <lightallloy@gmail.com>
Co-authored-by: Mac Siri <mac@forem.com>
Co-authored-by: zhangted <tedcbook@gmail.com>
Co-authored-by: Josh Klar <jklar@forem.com>

* Added autocomplete value off (#19882)

* Bust cache of organization page when new member is added (#19861)

* Remove unused freezeScrolling (#19808)

* Remove unused freezeScrolling

* chore: try see if this helps to skip the flakey test that wasn't being skipped

* chore: try see if this helps to skip the flakey test that wasn't being skipped

* oops: revert the gemfile changes

---------

Co-authored-by: Mai Irie <mai@forem.com>
Co-authored-by: Mac Siri <mac@forem.com>
Co-authored-by: Ridhwana <ridhwana.khan16@gmail.com>

* ci: Allow workflow_dispatch runs to log in to GHCR. (#19885)

* ci: Fix base image builder... the right way... (#19889)

* Update postcss to version 8.4.27 (#19888)

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>

* Replace uses of toBeDefined with toExist  (#19862)

* Replaced toBeDefined with toExist

* Revert db changes

* Rename display ads to billboards: css and js code (#19887)

* Renamed js vars/css names

* Renamed display_ads to billboards in js functions, variables, components

* [ruby] Update sidekiq-unique-jobs 7.1.29 → 7.1.30 (patch) (#19902)

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>

* Update oj to version 3.15.1 (#19901)

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>

* Updated failing cypress test

* Removed commented code

* Attempt at fixing tests

* Fixed failing tests

* Added more tests

* Basic color and design changes part -1

* Disable/Enable buttons correctly

* Various design updates in Tag adjustement mod UI

* I18N and documentation

* Updated mod panel

* Fixed mod related cypress tests

* Minor design fixes

* Dark mode designs

* Test update

* Mod UI with some bugs

* Updated/simplified code fully-working

* Updated tests

* Added more tests

* Accessibility improvements

* hr line code fix

* Close mod icon bug fix

* Nit fixes

* Design fixes as suggested by Anuj.

* Design fixes

* Dark mode file usage

* Design fixes

* Nit fixes

---------

Co-authored-by: Anna Buianova <lightallloy@gmail.com>
Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
Co-authored-by: Duke Greene <dukegreene@gmail.com>
Co-authored-by: PJ <pj@forem.com>
Co-authored-by: Mac Siri <mac@forem.com>
Co-authored-by: zhangted <tedcbook@gmail.com>
Co-authored-by: Josh Klar <jklar@forem.com>
Co-authored-by: Joshua Wehner <joshua@forem.com>
Co-authored-by: Mai Irie <mai@forem.com>
Co-authored-by: Ridhwana <ridhwana.khan16@gmail.com>
2023-08-24 00:31:21 +05:30

245 lines
9 KiB
Ruby

class ArticlePolicy < ApplicationPolicy
MAX_TAG_LIST_SIZE = 4
# @return [TrueClass] when only Forem admins can post an Article.
# @return [FalseClass] when most any Forem user can post an Article.
#
# @note This is for Authorization System: use case 1-1. At present, this is the quickest way to
# refactor our code to deliver on that feature.
#
# @see https://github.com/forem/forem/pull/16437 for pattern of adding a predicate method to the
# "most relevant" class.
# @see https://github.com/orgs/forem/projects/46 for project details
def self.limit_post_creation_to_admins?
FeatureFlag.enabled?(:limit_post_creation_to_admins)
end
# @param query [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 [TrueClass] if this query should default to hidden
# @return [FalseClass] if this query should not be hidden in the UI.
#
# @note The symmetry of the case statement structure with .scope_users_authorized_to_action
def self.include_hidden_dom_class_for?(query:)
case query.to_sym
when :create?, :new?, :create, :new
limit_post_creation_to_admins?
else
false
end
end
# Helps filter a `:users_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 users_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(users_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.
#
# @note The symmetry of the case statement structure with .include_hidden_dom_class_for?
def self.scope_users_authorized_to_action(users_scope:, action:)
case action.to_sym
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.
users_scope = users_scope.without_role(:suspended)
return users_scope unless limit_post_creation_to_admins?
# NOTE: Not a fan of reaching over to the constant of another class, but I digress.
users_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.
#
# @note as a reminder, if you attempt to authorize this policy in a controller with that calls
# {CachingHeaders#set_cache_control_headers} you may encounter some headaches. What do
# those headaches look like? When you call {CachingHeaders#set_cache_control_headers}, you
# are likely disallowing checks on current_user (via {EdgeCacheSafetyCheck#current_user}).
#
# @todo [@jeremyf] I don't like altering the initializer and its core assumption. But the other
# option to get Articles working for https://github.com/forem/forem/issues/16529 is to
# address the at present fundamental assumption regarding "Policies are for authorizing when
# you have a user, otherwise let the controller decide."
#
#
# @see even Rubocop thinks this is a bad idea. But the short-cut gets me unstuck. I hope there's
# enough breadcrumbs to undo this short-cut.
def initialize(user, record)
@user = user
@record = record
end
def feed?
true
end
# Does the user already have existing articles? Can they create an article?
#
# @return [TrueClass] They have existing published articles OR can create new ones.
# @return [FalseClass] They do not have published articles NOR can they create new ones.
#
# @note As part of our aspirations to only show users what is relevant to them and "hiding" what
# is not, this method will help us with the edge case of "should we show the user a
# dashboard listing of posts?"
#
# @note This handles the case in which a user has lost the ability to create posts (e.g. we've
# toggled on the feature limiting posts to admins only) but they have at least one published
# post. In that case we want to show them things like "their posts's analytics" or a
# dashboard of their published posts.
#
# @note This policy method is a bit different. It is strictly meant to return true or false.
# Other policies might raise exceptions, but the purpose of this method is for conditional
# rendering.
def has_existing_articles_or_can_create_new_ones?
require_user!
return true if user.articles.published.exists?
create?
rescue ApplicationPolicy::NotAuthorizedError
false
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?
user_any_admin?
end
def update?
require_user_in_good_standing!
user_author? || user_super_admin? || user_org_admin? || user_any_admin?
end
def manage?
update? && record.published? && !record.scheduled?
end
def stats?
require_user!
user_author? || user_super_admin? || user_org_admin?
end
def subscriptions?
require_user!
user_author? || user_super_admin?
end
def elevated_user?
user_any_admin? || user_super_moderator?
end
# this method performs the same checks that determine:
# if the record can be featured
# if user can adjust any tag
# if user can perform moderator actions
def revoke_publication?
require_user!
return false unless @record.published?
elevated_user?
end
def allow_tag_adjustment?
require_user!
elevated_user? || tag_moderator_eligible?
end
def tag_moderator_eligible?
tag_ids_moderated_by_user = Tag.with_role(:tag_moderator, @user).ids
tag_ids_moderated_by_user.size.positive?
end
def destroy?
require_user!
user_author? || user_super_admin? || user_org_admin? || user_any_admin?
end
def moderate?
# Technically, we could check the limit_post_creation_to_admins? first, but [@jeremyf]'s
# operating on a "trying to maintain consistency" approach.
require_user_in_good_standing!
return false if self.class.limit_post_creation_to_admins?
# <2022-05-09 Mon> Don't let a user moderate their own article; though this may not be the desired behavior.
return false if user_author?
# Beware a trusted user does not guarantee that they are an admin. And more specifically, being
# an admin does not guarantee being trusted.
return true if user.trusted?
elevated_user?
end
alias admin_featured_toggle? revoke_publication?
alias toggle_featured_status? revoke_publication?
alias can_adjust_any_tag? revoke_publication?
alias can_perform_moderator_actions? revoke_publication?
# Due to the associated controller method "admin_unpublish", we
# alias "admin_ubpublish" to the "revoke_publication" method.
alias admin_unpublish? revoke_publication?
alias new? create?
alias delete_confirm? destroy?
alias discussion_lock_confirm? destroy?
alias discussion_unlock_confirm? destroy?
alias edit? update?
# The ArticlesController#preview method is very complicated but aspirationally, we want to ensure
# that someone can preview an article of their if they already have a published article or they
# can create new ones.
alias preview? has_existing_articles_or_can_create_new_ones?
def permitted_attributes
%i[title body_html body_markdown main_image published canonical_url
description tag_list publish_under_org
video video_code video_source_url video_thumbnail_url receive_notifications
archived]
end
private
def user_author?
# We might have the Article class (instead of the Article instance), so let's short circuit
return false unless record.respond_to?(:user_id)
record.user_id == user.id
end
def user_org_admin?
user.org_admin?(record.organization_id)
end
end