fix(security): gate SLA APIs and automation on the sla feature (#15209)

SLA is a premium feature, but several SLA surfaces never checked the
account's `sla` flag. An account without SLA — or one whose plan was
downgraded and had the flag revoked — could still read SLA breach
reporting, attach an SLA policy to a conversation through the
conversation update endpoint, and keep applying SLA policies from
automation rules. All three paths now require the feature, matching the
SLA policies API which already checked it.

## How to reproduce

1. Disable the `sla` feature for an account that has SLA policies and
applied SLAs.
2. `GET /api/v1/accounts/{id}/applied_slas/metrics` (also `index` and
`download`) — returns the account's SLA breach data instead of denying.
3. `PATCH /api/v1/accounts/{id}/conversations/{display_id}` with
`sla_policy_id` — the policy is attached.
4. Trigger an automation rule with an "Add SLA" action — the SLA is
applied and starts tracking.

## What changed

- `Api::V1::Accounts::AppliedSlasController` gains the same
`ensure_sla_feature_enabled` guard the SLA policies controller uses,
covering `index`, `metrics` and `download`.
-
`Enterprise::Api::V1::Accounts::ConversationsController#permitted_update_params`
only permits `sla_policy_id` when the feature is enabled. When it is off
the parameter is dropped, so an existing SLA association is preserved
rather than cleared.
- `Enterprise::ActionService#add_sla` returns early when the feature is
off, so automation rules stop applying SLAs on revoked accounts.
This commit is contained in:
Tanmay Deep Sharma
2026-08-04 18:43:34 +05:30
committed by GitHub
parent e7ded47753
commit 8448001fdc
16 changed files with 96 additions and 6 deletions

View File

@@ -4,6 +4,7 @@ class Api::V1::Accounts::AppliedSlasController < Api::V1::Accounts::EnterpriseAc
RESULTS_PER_PAGE = 25
before_action :ensure_sla_feature_enabled
before_action :set_applied_slas, only: [:index, :metrics, :download]
before_action :set_current_page, only: [:index]
before_action :check_admin_authorization?
@@ -30,6 +31,10 @@ class Api::V1::Accounts::AppliedSlasController < Api::V1::Accounts::EnterpriseAc
private
def ensure_sla_feature_enabled
raise Pundit::NotAuthorizedError unless Current.account.feature_enabled?('sla')
end
def total_applied_slas
@total_applied_slas ||= @applied_slas.count
end

View File

@@ -1,4 +1,5 @@
class Api::V1::Accounts::SlaPoliciesController < Api::V1::Accounts::EnterpriseAccountsController
before_action :ensure_sla_feature_enabled
before_action :fetch_sla, only: [:show, :update, :destroy]
before_action :check_authorization
@@ -29,4 +30,8 @@ class Api::V1::Accounts::SlaPoliciesController < Api::V1::Accounts::EnterpriseAc
def fetch_sla
@sla_policy = Current.account.sla_policies.find_by(id: params[:id])
end
def ensure_sla_feature_enabled
raise Pundit::NotAuthorizedError unless Current.account.feature_enabled?('sla')
end
end

View File

@@ -16,6 +16,9 @@ module Enterprise::Api::V1::Accounts::ConversationsController
end
def permitted_update_params
# SLA is a premium feature; only accept sla_policy_id assignment when it is enabled for the account.
return super unless Current.account.feature_enabled?('sla')
super.merge(params.permit(:sla_policy_id))
end

View File

@@ -2,6 +2,9 @@ class Sla::ProcessAccountAppliedSlasJob < ApplicationJob
queue_as :medium
def perform(account)
# The scheduler filters on the feature, but this job can already be queued when a plan is downgraded.
return unless account.feature_enabled?('sla')
account.applied_slas.with_sla_applicable_conversation.where(sla_status: %w[active active_with_misses]).each do |applied_sla|
Sla::ProcessAppliedSlaJob.perform_later(applied_sla)
end

View File

@@ -2,6 +2,9 @@ class Sla::ProcessAppliedSlaJob < ApplicationJob
queue_as :medium
def perform(applied_sla)
# This job can already be queued when a plan is downgraded, so re-check before evaluating.
return unless applied_sla.account.feature_enabled?('sla')
Sla::EvaluateAppliedSlaService.new(applied_sla: applied_sla).perform
end
end

View File

@@ -2,7 +2,8 @@ class Sla::TriggerSlasForAccountsJob < ApplicationJob
queue_as :scheduled_jobs
def perform
Account.joins(:sla_policies).distinct.find_each do |account|
# SLA is a premium feature; skip accounts that have policies left over from a downgrade.
Account.feature_sla.joins(:sla_policies).distinct.find_each do |account|
Rails.logger.info "Enqueuing ProcessAccountAppliedSlasJob for account #{account.id}"
Sla::ProcessAccountAppliedSlasJob.perform_later(account)
end

View File

@@ -1,6 +1,8 @@
module Enterprise::ActionService
def add_sla(sla_policy_id)
return if sla_policy_id.blank?
# SLA is a premium feature; automation rules must not keep applying SLAs once it is disabled.
return unless @account.feature_enabled?('sla')
sla_policy = @account.sla_policies.find_by(id: sla_policy_id.first)
return if sla_policy.nil?