mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-04-06 07:29:16 +00:00
When a guest places an order and tries to cancel it from the order confirmation page, the cancellation silently failed and redirected to the home page. The guest was left unsure whether the order was cancelled, and the hub received no cancellation notification. Root cause: two missing pieces for guest (token-based) authorization: 1. The `:cancel` ability in Ability#add_shopping_abilities only checked `order.user == user`, ignoring the guest token. The `:read` and `:update` abilities already support `order.token && token == order.token` as a fallback — `:cancel` now does the same. 2. The `cancel` action called `authorize! :cancel, @order` without passing `session[:access_token]`, so even with the corrected ability the token was never evaluated. Fixes #13817 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
485 lines
18 KiB
Ruby
485 lines
18 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require 'cancan'
|
|
|
|
module Spree
|
|
class Ability
|
|
include CanCan::Ability
|
|
|
|
REPORTS_SEARCH_ACTIONS = [
|
|
:search_enterprise_fees, :search_enterprise_fee_owners, :search_distributors,
|
|
:search_suppliers, :search_order_cycles, :search_order_customers
|
|
].freeze
|
|
|
|
def initialize(user)
|
|
clear_aliased_actions
|
|
|
|
# override cancan default aliasing (we don't want to differentiate between read and index)
|
|
alias_action :delete, to: :destroy
|
|
alias_action :edit, to: :update
|
|
alias_action :new, to: :create
|
|
alias_action :new_action, to: :create
|
|
alias_action :show, to: :read
|
|
|
|
user ||= Spree::User.new
|
|
|
|
if user.try(:admin?)
|
|
can :manage, :all
|
|
|
|
# this action was needed for restrictions for distributors and suppliers
|
|
# however, admins don't need to be restricted, so, bypassing it for admins
|
|
cannot :edit_as_producer_only, Spree::Order
|
|
else
|
|
can [:index, :read], Country
|
|
can :create, Order
|
|
can :read, Order do |order, token|
|
|
order.user == user || (order.token && token == order.token)
|
|
end
|
|
can :update, Order do |order, token|
|
|
order.user == user || (order.token && token == order.token)
|
|
end
|
|
can [:index, :read], ProductProperty
|
|
can [:index, :read], Property
|
|
can :create, Spree::User
|
|
can [:read, :update, :destroy], Spree::User, id: user.id
|
|
can [:index, :read], State
|
|
can [:index, :read], StockItem
|
|
can [:index, :read], Taxon
|
|
can [:index, :read], Variant
|
|
can [:index, :read], Zone
|
|
end
|
|
|
|
add_shopping_abilities user
|
|
add_base_abilities user if is_new_user? user
|
|
add_enterprise_management_abilities user if can_manage_enterprises? user
|
|
add_group_management_abilities user if can_manage_groups? user
|
|
add_product_management_abilities user if can_manage_products? user
|
|
add_order_cycle_management_abilities user if can_manage_order_cycles? user
|
|
if can_manage_orders? user
|
|
add_order_management_abilities user
|
|
elsif can_manage_line_items_in_orders? user
|
|
add_manage_line_items_abilities user
|
|
end
|
|
add_relationship_management_abilities user if can_manage_relationships? user
|
|
add_customer_account_transaction_abilities user if can_manage_enterprises? user
|
|
end
|
|
|
|
# New users have no enterprises.
|
|
def is_new_user?(user)
|
|
user.enterprises.blank?
|
|
end
|
|
|
|
# Users can manage an enterprise if they have one.
|
|
def can_manage_enterprises?(user)
|
|
user.enterprises.present?
|
|
end
|
|
|
|
# Users can manage a group if they have one.
|
|
def can_manage_groups?(user)
|
|
user.owned_groups.present?
|
|
end
|
|
|
|
# Users can manage products if they have an enterprise that is not a profile.
|
|
def can_manage_products?(user)
|
|
can_manage_enterprises?(user) &&
|
|
user.enterprises.any? { |e| e.category != :hub_profile && e.producer_profile_only != true }
|
|
end
|
|
|
|
# Users can manage order cycles if they manage a sells own/any enterprise
|
|
# OR if they manage a producer which is included in any order cycles
|
|
def can_manage_order_cycles?(user)
|
|
can_manage_orders?(user) ||
|
|
OrderCycle.visible_by(user).any?
|
|
end
|
|
|
|
# Users can manage orders if they have a sells own/any enterprise.
|
|
def can_manage_orders?(user)
|
|
user.can_manage_orders?
|
|
end
|
|
|
|
# Users can manage line items in orders if they have producer enterprise and
|
|
# any of order distributors allow them to edit their orders.
|
|
def can_manage_line_items_in_orders?(user)
|
|
user.can_manage_line_items_in_orders?
|
|
end
|
|
|
|
def can_manage_relationships?(user)
|
|
can_manage_enterprises? user
|
|
end
|
|
|
|
def add_shopping_abilities(user)
|
|
can [:destroy], Spree::LineItem do |item|
|
|
user == item.order.user &&
|
|
item.order.changes_allowed?
|
|
end
|
|
|
|
can :cancel, Spree::Order do |order, token|
|
|
order.user == user || (order.token && token == order.token)
|
|
end
|
|
|
|
can :bulk_cancel, Spree::Order do |order|
|
|
order.user == user
|
|
end
|
|
|
|
can [:update, :destroy], Spree::CreditCard do |credit_card|
|
|
credit_card.user == user
|
|
end
|
|
|
|
can [:update], Customer do |customer|
|
|
customer.user == user
|
|
end
|
|
end
|
|
|
|
# New users can create an enterprise, and gain other permissions from doing this.
|
|
def add_base_abilities(_user)
|
|
can [:create], Enterprise
|
|
end
|
|
|
|
def add_group_management_abilities(user)
|
|
can [:admin, :index], :overview
|
|
can [:admin, :index], EnterpriseGroup
|
|
can [:read, :edit, :update], EnterpriseGroup do |group|
|
|
user.owned_groups.include? group
|
|
end
|
|
end
|
|
|
|
def add_enterprise_management_abilities(user)
|
|
# We perform authorize! on (:create, nil) when creating a new order from admin,
|
|
# and also (:search, nil) when searching for variants to add to the order
|
|
can [:create, :search], nil
|
|
|
|
can [:admin, :index], :overview
|
|
|
|
can [:admin, :index, :read, :create, :edit, :update_positions, :destroy], ProducerProperty
|
|
|
|
can :new, TagRule
|
|
can [:admin, :map_by_tag, :destroy, :variant_tag_rules], TagRule do |tag_rule|
|
|
user.enterprises.include? tag_rule.enterprise
|
|
end
|
|
|
|
can [:admin, :index, :create], Enterprise
|
|
can [:read, :edit, :update,
|
|
:remove_logo, :remove_promo_image, :remove_terms_and_conditions,
|
|
:bulk_update, :resend_confirmation, :new_tag_rule_group], Enterprise do |enterprise|
|
|
OpenFoodNetwork::Permissions.new(user).editable_enterprises.include? enterprise
|
|
end
|
|
can [:welcome, :register], Enterprise do |enterprise|
|
|
enterprise.owner == user
|
|
end
|
|
can [:manage_payment_methods,
|
|
:manage_shipping_methods,
|
|
:manage_enterprise_fees,
|
|
:manage_connected_apps], Enterprise do |enterprise|
|
|
user.enterprises.include? enterprise
|
|
end
|
|
|
|
# All enterprises can have fees, though possibly suppliers don't need them?
|
|
can [:index, :create], EnterpriseFee
|
|
can [:admin, :read, :edit, :bulk_update, :destroy], EnterpriseFee do |enterprise_fee|
|
|
user.enterprises.include? enterprise_fee.enterprise
|
|
end
|
|
|
|
can [:admin, :known_users, :customers], :search
|
|
|
|
can [:admin, :show], :account
|
|
|
|
# For printing own account invoice orders
|
|
can [:print], Spree::Order do |order|
|
|
order.user == user
|
|
end
|
|
|
|
can [:admin, :bulk_update], ColumnPreference do |column_preference|
|
|
column_preference.user == user
|
|
end
|
|
|
|
can [:admin, :connect, :status, :destroy], StripeAccount do |stripe_account|
|
|
user.enterprises.include? stripe_account.enterprise
|
|
end
|
|
|
|
can [:admin, :create], UserInvitation
|
|
|
|
can [:admin, :index, :destroy], :oidc_setting
|
|
|
|
can [:admin, :create], Voucher
|
|
|
|
can [:admin, :destroy], EnterpriseRole do |enterprise_role|
|
|
enterprise_role.enterprise.owner_id == user.id
|
|
end
|
|
end
|
|
|
|
def add_product_management_abilities(user)
|
|
# Enterprise User can only access products that they are a supplier for
|
|
can [:create], Spree::Product
|
|
# An enterprise user can change a product if they are supplier of at least
|
|
# one of the product's associated variants
|
|
can [:admin, :read, :index, :update,
|
|
:seo, :group_buy_options,
|
|
:bulk_update, :clone, :delete,
|
|
:destroy], Spree::Product do |product|
|
|
variant_suppliers = product.variants.map(&:supplier)
|
|
OpenFoodNetwork::Permissions.new(user).managed_product_enterprises.intersect?(
|
|
variant_suppliers
|
|
)
|
|
end
|
|
|
|
# An enterprise user can clone if they have been granted permission to the source variant.
|
|
# Technically I'd call this permission clone_linked_variant, but it would be less confusing to
|
|
# use the same name as everywhere else.
|
|
can [:create_linked_variant], Spree::Variant do |variant|
|
|
OpenFoodNetwork::Permissions.new(user).
|
|
enterprises_granting_linked_variants.include? variant.supplier
|
|
end
|
|
|
|
can [:admin, :index, :bulk_update, :destroy, :destroy_variant, :clone,
|
|
:create_linked_variant], :products_v3
|
|
|
|
can [:create], Spree::Variant
|
|
can [:admin, :index, :read, :edit,
|
|
:update, :search, :delete, :destroy], Spree::Variant do |variant|
|
|
OpenFoodNetwork::Permissions.new(user).
|
|
managed_product_enterprises.include? variant.supplier
|
|
end
|
|
|
|
if OpenFoodNetwork::FeatureToggle.enabled?(:inventory, *user.enterprises)
|
|
can [:admin, :index, :read, :update, :bulk_update, :bulk_reset], VariantOverride do |vo|
|
|
next false unless vo.hub.present? && vo.variant&.supplier.present?
|
|
|
|
hub_auth = OpenFoodNetwork::Permissions.new(user).
|
|
variant_override_hubs.
|
|
include? vo.hub
|
|
|
|
producer_auth = OpenFoodNetwork::Permissions.new(user).
|
|
variant_override_producers.
|
|
include? vo.variant.supplier
|
|
|
|
hub_auth && producer_auth
|
|
end
|
|
end
|
|
|
|
can [:admin, :create, :update], InventoryItem do |ii|
|
|
next false unless ii.enterprise.present? &&
|
|
ii.variant&.supplier.present?
|
|
|
|
hub_auth = OpenFoodNetwork::Permissions.new(user).
|
|
variant_override_hubs.
|
|
include? ii.enterprise
|
|
|
|
producer_auth = OpenFoodNetwork::Permissions.new(user).
|
|
variant_override_producers.
|
|
include? ii.variant.supplier
|
|
|
|
hub_auth && producer_auth
|
|
end
|
|
|
|
can [:admin, :index, :read, :create,
|
|
:edit, :update_positions, :destroy], Spree::ProductProperty
|
|
can [:admin, :index, :read, :create, :edit, :update, :destroy], Spree::Image
|
|
|
|
can [:admin, :index, :read, :search], Spree::Taxon
|
|
|
|
can [:admin, :index, :guide, :import, :save, :save_data,
|
|
:validate_data, :reset_absent_products], ProductImport::ProductImporter
|
|
|
|
can [:admin, :index, :import], ::Admin::DfcProductImportsController
|
|
|
|
# Reports page
|
|
can [:admin, :index, :show, :create, *REPORTS_SEARCH_ACTIONS],
|
|
::Admin::ReportsController
|
|
can [:admin, :show, :create, :customers, :orders_and_distributors, :group_buys, :payments,
|
|
:orders_and_fulfillment, :products_and_inventory, :order_cycle_management,
|
|
:packing, :enterprise_fee_summary, :bulk_coop, :suppliers], :report
|
|
end
|
|
|
|
def add_order_cycle_management_abilities(user)
|
|
can [:admin, :index], OrderCycle do |order_cycle|
|
|
OrderCycle.visible_by(user).include?(order_cycle) ||
|
|
order_cycle.orders.editable_by_producers(user.enterprises).exists?
|
|
end
|
|
|
|
can [
|
|
:read, :edit, :update, :incoming, :outgoing, :checkout_options
|
|
], OrderCycle do |order_cycle|
|
|
OrderCycle.visible_by(user).include? order_cycle
|
|
end
|
|
can [:admin, :index, :create], Schedule
|
|
can [:admin, :update, :destroy], Schedule do |schedule|
|
|
OpenFoodNetwork::Permissions.new(user).editable_schedules.include? schedule
|
|
end
|
|
can [:bulk_update, :clone, :destroy, :notify_producers], OrderCycle do |order_cycle|
|
|
user.enterprises.include? order_cycle.coordinator
|
|
end
|
|
can [:for_order_cycle], Enterprise
|
|
can [:for_order_cycle], EnterpriseFee
|
|
end
|
|
|
|
def add_order_management_abilities(user)
|
|
can [:manage_order_sections], Spree::Order do |order|
|
|
user.admin? ||
|
|
order.distributor.nil? ||
|
|
user.enterprises.include?(order.distributor) ||
|
|
order.order_cycle&.coordinated_by?(user)
|
|
end
|
|
|
|
can [:edit_as_producer_only], Spree::Order do |order|
|
|
cannot?(:manage_order_sections, order) && can_edit_as_producer(order, user)
|
|
end
|
|
|
|
can [:index], Spree::Order do
|
|
user.admin? ||
|
|
user.enterprises.any?(&:is_distributor) ||
|
|
user.enterprises.distributors.where(enable_producers_to_edit_orders: true).exist?
|
|
end
|
|
|
|
can [:create], Spree::Order
|
|
|
|
# Spree::Admin::PaymentController need to load the order to credit_customer
|
|
can [:read, :update, :credit_customer], Spree::Order do |order|
|
|
# We allow editing orders with a nil distributor as this state occurs
|
|
# during the order creation process from the admin backend
|
|
order.distributor.nil? ||
|
|
# Enterprise User can access orders that they are a distributor for
|
|
user.enterprises.include?(order.distributor) ||
|
|
# Enterprise User can access orders that are placed inside a OC they coordinate
|
|
order.order_cycle&.coordinated_by?(user) ||
|
|
can_edit_as_producer(order, user)
|
|
end
|
|
|
|
can [:fire, :resend, :invoice, :print], Spree::Order do |order|
|
|
# We allow editing orders with a nil distributor as this state occurs
|
|
# during the order creation process from the admin backend
|
|
order.distributor.nil? ||
|
|
# Enterprise User can access orders that they are a distributor for
|
|
user.enterprises.include?(order.distributor) ||
|
|
# Enterprise User can access orders that are placed inside a OC they coordinate
|
|
order.order_cycle&.coordinated_by?(user)
|
|
end
|
|
|
|
can [:admin, :bulk_management], Spree::Order do |order|
|
|
user.admin? ||
|
|
user.enterprises.any?(&:is_distributor) ||
|
|
can_edit_as_producer(order, user)
|
|
end
|
|
|
|
can [:managed, :distribution], Spree::Order do
|
|
user.admin? || user.enterprises.any?(&:is_distributor)
|
|
end
|
|
can [:admin, :index, :create, :show, :poll, :generate], :invoice
|
|
can [:admin, :visible], Enterprise
|
|
can [:admin, :index, :create, :update, :destroy], :line_item
|
|
can [:admin, :index, :create], Spree::LineItem do |item|
|
|
user.admin? ||
|
|
user.enterprises.any?(&:is_distributor) ||
|
|
can_edit_as_producer(item.order, user)
|
|
end
|
|
can [:destroy, :update], Spree::LineItem do |item|
|
|
order = item.order
|
|
user.admin? ||
|
|
user.enterprises.include?(order.distributor) ||
|
|
order.order_cycle&.coordinated_by?(user) ||
|
|
can_edit_as_producer(order, user)
|
|
end
|
|
|
|
can [:admin, :index, :read, :create, :edit, :update, :fire], Spree::Shipment do |shipment|
|
|
user.admin? ||
|
|
user.enterprises.any?(&:is_distributor) ||
|
|
can_edit_as_producer(shipment.order, user)
|
|
end
|
|
|
|
can [:admin, :index, :read, :create, :edit, :update, :fire, :credit_customer], Spree::Payment
|
|
can [:admin, :index, :read, :create, :edit, :update, :fire], Spree::Adjustment
|
|
can [:admin, :index, :read, :create, :edit, :update, :fire], Spree::ReturnAuthorization
|
|
can [:destroy], Spree::Adjustment do |adjustment|
|
|
if user.admin?
|
|
true
|
|
else
|
|
order = adjustment.order
|
|
user.enterprises.include?(order.distributor) ||
|
|
order.order_cycle&.coordinated_by?(user)
|
|
end
|
|
end
|
|
|
|
can [:create], OrderCycle
|
|
|
|
can [:admin, :index, :read, :create, :edit, :update], ExchangeVariant
|
|
can [:admin, :index, :read, :create, :edit, :update], Exchange
|
|
can [:admin, :index, :read, :create, :edit, :update], ExchangeFee
|
|
|
|
# Enterprise user can only access payment and shipping methods for their distributors
|
|
can [:index, :create], Spree::PaymentMethod
|
|
can [:admin, :read, :update, :fire, :resend,
|
|
:destroy, :show_provider_preferences], Spree::PaymentMethod do |payment_method|
|
|
(user.enterprises & payment_method.distributors).any?
|
|
end
|
|
|
|
can [:index, :create], Spree::ShippingMethod
|
|
can [:admin, :read, :update, :destroy], Spree::ShippingMethod do |shipping_method|
|
|
(user.enterprises & shipping_method.distributors).any?
|
|
end
|
|
|
|
# Reports page
|
|
can [:admin, :index, :show, :create, *REPORTS_SEARCH_ACTIONS], ::Admin::ReportsController
|
|
can [:admin, :customers, :group_buys, :sales_tax, :payments,
|
|
:orders_and_distributors, :orders_and_fulfillment, :products_and_inventory,
|
|
:order_cycle_management, :xero_invoices, :enterprise_fee_summary, :bulk_coop], :report
|
|
|
|
can [:create], Customer
|
|
can [:admin, :index, :update,
|
|
:destroy, :show], Customer, enterprise_id: Enterprise.managed_by(user).pluck(:id)
|
|
can [:admin, :new, :index], Subscription
|
|
can [:create, :edit, :update, :cancel, :pause, :unpause], Subscription do |subscription|
|
|
user.enterprises.include?(subscription.shop)
|
|
end
|
|
can [:admin, :build], SubscriptionLineItem
|
|
can [:destroy], SubscriptionLineItem do |subscription_line_item|
|
|
user.enterprises.include?(subscription_line_item.subscription.shop)
|
|
end
|
|
can [:admin, :edit, :cancel, :resume], ProxyOrder do |proxy_order|
|
|
user.enterprises.include?(proxy_order.subscription.shop)
|
|
end
|
|
can [:visible], Enterprise
|
|
end
|
|
|
|
def can_edit_as_producer(order, user)
|
|
return unless order.distributor&.enable_producers_to_edit_orders
|
|
|
|
order.variants.any? { |variant| user.enterprises.ids.include?(variant.supplier_id) }
|
|
end
|
|
|
|
def add_manage_line_items_abilities(user)
|
|
can [
|
|
:admin,
|
|
:read,
|
|
:index,
|
|
:edit,
|
|
:update,
|
|
:bulk_management,
|
|
:edit_as_producer_only
|
|
], Spree::Order do |order|
|
|
can_edit_as_producer(order, user)
|
|
end
|
|
can [:admin, :index, :create, :destroy, :update], Spree::LineItem do |item|
|
|
can_edit_as_producer(item.order, user)
|
|
end
|
|
can [:index, :create, :add, :read, :edit, :update], Spree::Shipment do |shipment|
|
|
can_edit_as_producer(shipment.order, user)
|
|
end
|
|
can [:admin, :index], OrderCycle do |order_cycle|
|
|
can_edit_as_producer(order_cycle.order, user)
|
|
end
|
|
can [:visible], Enterprise
|
|
end
|
|
|
|
def add_relationship_management_abilities(user)
|
|
can [:admin, :index, :create], EnterpriseRelationship
|
|
can [:destroy], EnterpriseRelationship do |enterprise_relationship|
|
|
user.enterprises.include?(enterprise_relationship.parent) ||
|
|
user.enterprises.include?(enterprise_relationship.child)
|
|
end
|
|
end
|
|
|
|
def add_customer_account_transaction_abilities(_user)
|
|
can [:admin, :create, :index], CustomerAccountTransaction
|
|
end
|
|
end
|
|
end
|