зеркало из
https://github.com/glebtv/yookassa.git
synced 2026-08-28 15:16:18 +03:00
fix webhook auth diagnostics and token length handling
Этот коммит содержится в:
@@ -24,21 +24,42 @@ module Yookassa
|
||||
end
|
||||
|
||||
def authentic_webhook?(payload)
|
||||
token_valid? && source_ip_allowed? && payload_matches_api_object?(payload)
|
||||
token_ok = token_valid?
|
||||
ip_ok = source_ip_allowed?
|
||||
api_ok = payload_matches_api_object?(payload)
|
||||
|
||||
unless token_ok && ip_ok && api_ok
|
||||
Rails.logger.info("[Yookassa Webhook] Auth failed: token=#{token_ok}, ip=#{ip_ok}, api=#{api_ok}, remote_ip=#{request.remote_ip}")
|
||||
end
|
||||
|
||||
token_ok && ip_ok && api_ok
|
||||
end
|
||||
|
||||
def token_valid?
|
||||
token = params[:token].to_s
|
||||
configured_token = Yookassa.config.webhook_token.to_s
|
||||
return false if token.empty? || configured_token.empty?
|
||||
if token.empty? || configured_token.empty?
|
||||
Rails.logger.info("[Yookassa Webhook] Token validation failed: param_token=#{token.empty? ? "empty" : "present"}, configured_token=#{configured_token.empty? ? "empty" : "present"}")
|
||||
return false
|
||||
end
|
||||
|
||||
ActiveSupport::SecurityUtils.secure_compare(token, configured_token)
|
||||
if token.bytesize != configured_token.bytesize
|
||||
Rails.logger.info("[Yookassa Webhook] Token comparison: false (length mismatch)")
|
||||
return false
|
||||
end
|
||||
|
||||
result = ActiveSupport::SecurityUtils.secure_compare(token, configured_token)
|
||||
Rails.logger.info("[Yookassa Webhook] Token comparison: #{result}")
|
||||
result
|
||||
end
|
||||
|
||||
def source_ip_allowed?
|
||||
source_ip = request.remote_ip
|
||||
allowed_cidrs.any? { |cidr| IPAddr.new(cidr).include?(source_ip) }
|
||||
rescue IPAddr::InvalidAddressError
|
||||
allowed = allowed_cidrs.any? { |cidr| IPAddr.new(cidr).include?(source_ip) }
|
||||
Rails.logger.info("[Yookassa Webhook] IP check: remote_ip=#{source_ip}, allowed=#{allowed}, cidrs=#{allowed_cidrs}")
|
||||
allowed
|
||||
rescue IPAddr::InvalidAddressError => e
|
||||
Rails.logger.info("[Yookassa Webhook] IP check failed: #{e.message}")
|
||||
false
|
||||
end
|
||||
|
||||
@@ -55,17 +76,29 @@ module Yookassa
|
||||
|
||||
def payload_matches_api_object?(payload)
|
||||
object = extract_object(payload)
|
||||
return false unless object.is_a?(Hash)
|
||||
unless object.is_a?(Hash)
|
||||
Rails.logger.info("[Yookassa Webhook] API object check failed: object is not a Hash")
|
||||
return false
|
||||
end
|
||||
|
||||
object_id = object["id"].to_s
|
||||
object_status = object["status"].to_s
|
||||
return false if object_id.empty? || object_status.empty?
|
||||
if object_id.empty? || object_status.empty?
|
||||
Rails.logger.info("[Yookassa Webhook] API object check failed: object_id=#{object_id}, object_status=#{object_status}")
|
||||
return false
|
||||
end
|
||||
|
||||
fetched_object = fetch_object_from_api(payload, object_id)
|
||||
return false if fetched_object.nil?
|
||||
if fetched_object.nil?
|
||||
Rails.logger.info("[Yookassa Webhook] API object check failed: could not fetch object #{object_id}")
|
||||
return false
|
||||
end
|
||||
|
||||
fetched_object.id == object_id && fetched_object.status == object_status
|
||||
rescue StandardError
|
||||
result = fetched_object.id == object_id && fetched_object.status == object_status
|
||||
Rails.logger.info("[Yookassa Webhook] API object check: fetched_id=#{fetched_object.id}, fetched_status=#{fetched_object.status}, matches=#{result}")
|
||||
result
|
||||
rescue StandardError => e
|
||||
Rails.logger.info("[Yookassa Webhook] API object check failed: #{e.message}")
|
||||
false
|
||||
end
|
||||
|
||||
|
||||
@@ -36,6 +36,11 @@ RSpec.describe Yookassa::WebhooksController do
|
||||
end
|
||||
|
||||
it "rejects webhook when token is invalid" do
|
||||
payments_client = instance_double(Yookassa::Payments)
|
||||
allow(Yookassa).to receive(:payments).and_return(payments_client)
|
||||
allow(payments_client).to receive(:find).with(payment_id: "payment-1")
|
||||
.and_return(instance_double(Yookassa::Entity::Payment, id: "payment-1", status: "succeeded"))
|
||||
|
||||
post "/yookassa/webhooks/wrong-token", payload, headers
|
||||
|
||||
expect(last_response.status).to eq(401)
|
||||
@@ -64,6 +69,21 @@ RSpec.describe Yookassa::WebhooksController do
|
||||
end
|
||||
end
|
||||
|
||||
describe "logging", :rails do
|
||||
it "logs auth result when webhook authentication fails" do
|
||||
controller = described_class.new
|
||||
|
||||
allow(controller).to receive(:token_valid?).and_return(true)
|
||||
allow(controller).to receive(:source_ip_allowed?).and_return(false)
|
||||
allow(controller).to receive(:payload_matches_api_object?).and_return(true)
|
||||
allow(controller).to receive(:request).and_return(instance_double(ActionDispatch::Request, remote_ip: "192.168.0.10"))
|
||||
|
||||
expect(Rails.logger).to receive(:info).with(include("Auth failed: token=true, ip=false, api=true"))
|
||||
|
||||
expect(controller.send(:authentic_webhook?, {})).to eq(false)
|
||||
end
|
||||
end
|
||||
|
||||
describe "IP source check", :rails do
|
||||
it "uses request.remote_ip" do
|
||||
controller = described_class.new
|
||||
|
||||
Ссылка в новой задаче
Block a user