* 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>
245 lines
9 KiB
Ruby
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
|