зеркало из
https://github.com/glebtv/yookassa.git
synced 2026-09-08 11:55:50 +03:00
fix webhook auth diagnostics and token length handling
Этот коммит содержится в:
@@ -24,21 +24,42 @@ module Yookassa
|
|||||||
end
|
end
|
||||||
|
|
||||||
def authentic_webhook?(payload)
|
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
|
end
|
||||||
|
|
||||||
def token_valid?
|
def token_valid?
|
||||||
token = params[:token].to_s
|
token = params[:token].to_s
|
||||||
configured_token = Yookassa.config.webhook_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
|
end
|
||||||
|
|
||||||
def source_ip_allowed?
|
def source_ip_allowed?
|
||||||
source_ip = request.remote_ip
|
source_ip = request.remote_ip
|
||||||
allowed_cidrs.any? { |cidr| IPAddr.new(cidr).include?(source_ip) }
|
allowed = allowed_cidrs.any? { |cidr| IPAddr.new(cidr).include?(source_ip) }
|
||||||
rescue IPAddr::InvalidAddressError
|
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
|
false
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -55,17 +76,29 @@ module Yookassa
|
|||||||
|
|
||||||
def payload_matches_api_object?(payload)
|
def payload_matches_api_object?(payload)
|
||||||
object = extract_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_id = object["id"].to_s
|
||||||
object_status = object["status"].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)
|
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
|
result = fetched_object.id == object_id && fetched_object.status == object_status
|
||||||
rescue StandardError
|
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
|
false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,11 @@ RSpec.describe Yookassa::WebhooksController do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it "rejects webhook when token is invalid" do
|
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
|
post "/yookassa/webhooks/wrong-token", payload, headers
|
||||||
|
|
||||||
expect(last_response.status).to eq(401)
|
expect(last_response.status).to eq(401)
|
||||||
@@ -64,6 +69,21 @@ RSpec.describe Yookassa::WebhooksController do
|
|||||||
end
|
end
|
||||||
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
|
describe "IP source check", :rails do
|
||||||
it "uses request.remote_ip" do
|
it "uses request.remote_ip" do
|
||||||
controller = described_class.new
|
controller = described_class.new
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user