How to verify a webhook with Ruby? (In Rails)

余生长醉 提交于 2019-12-22 08:59:42

问题


I'm looking to learn how to verify a Paddle webhook with Ruby? Their example has an option on how to do it with PHP, Python and JavaScript, but no Ruby. Any ideas on how to do it?

This following old example doesn't work:

require 'base64'
require 'php_serialize'
require 'openssl'


public_key = '-----BEGIN PUBLIC KEY-----
MIICIjANBgkqh...'

# 'data' represents all of the POST fields sent with the request.
# Get the p_signature parameter & base64 decode it.
signature = Base64.decode64(data['p_signature'])

# Remove the p_signature parameter
data.delete('p_signature')

# Ensure all the data fields are strings
data.each {|key, value|data[key] = String(value)}

# Sort the data
data_sorted = data.sort_by{|key, value| key}

# and serialize the fields
# serialization library is available here: https://github.com/jqr/php-serialize
data_serialized = PHP.serialize(data_sorted, true)

# verify the data
digest    = OpenSSL::Digest::SHA1.new
pub_key   = OpenSSL::PKey::RSA.new(public_key).public_key
verified  = pub_key.verify(digest, signature, data_serialized)

if verified
    puts "Yay! Signature is valid!"
else
    puts "The signature is invalid!"
end

Here is their example in JS:

// Node.js & Express implementation
const express = require('express');
const querystring = require('querystring');
const crypto = require('crypto');
const Serialize = require('php-serialize');

const router = express.Router();
const pubKey = `-----BEGIN PUBLIC KEY-----
...
-----END PUBLIC KEY-----`

function ksort(obj){
    let keys = Object.keys(obj).sort();
    let sortedObj = {};

    for (var i in keys) {
      sortedObj[keys[i]] = obj[keys[i]];
    }

    return sortedObj;
  }

function validateWebhook(jsonObj) {
    const mySig = Buffer.from(jsonObj.p_signature, 'base64');
    delete jsonObj.p_signature;
    // Need to serialize array and assign to data object
    jsonObj = ksort(jsonObj);
    for (var property in jsonObj) {
        if (jsonObj.hasOwnProperty(property) && (typeof jsonObj[property]) !== "string") {
            if (Array.isArray(jsonObj[property])) { // is it an array
                jsonObj[property] = jsonObj[property].toString();
            } else { //if its not an array and not a string, then it is a JSON obj
                jsonObj[property] = JSON.stringify(jsonObj[property]);
            }
        }
    }
    const serialized = Serialize.serialize(jsonObj);
    // End serialize data object
    const verifier = crypto.createVerify('sha1');
    verifier.update(serialized);
    verifier.end();

    let verification = verifier.verify(pubKey, mySig);

    if (verification) {
        return 'Yay! Signature is valid!';
    } else {
        return 'The signature is invalid!';
    }
}

/* Validate a Paddle webhook to this endpoint, or wherever in your app you are listening for Paddle webhooks */
router.post('/', function(req, res, next) {
    res.send(validateWebhook(req.body));
});

module.exports = router;

How could I verify the webhook with Ruby? Is there an alternative way to verify the webhook?

Here is an example webhook request:

(
    [alert_name] => subscription_created
    [cancel_url] => https://checkout.paddle.com/subscription/cancel?user=4&subscription=8&hash=b0bd354fexamplec39b0ff93c917804acf
    [checkout_id] => 1-61ff5b400-756ea301a9
    [currency] => USD
    [email] => wleffler@example.net
    [event_time] => 2019-08-10 18:33:58
    [marketing_consent] => 
    [next_bill_date] => 2019-08-18
    [passthrough] => 1132
    [quantity] => 67
    [status] => active
    [subscription_id] => 4
    [subscription_plan_id] => 5
    [unit_price] => unit_price
    [update_url] => https://checkout.paddle.com/subscription/update?user=5&subscription=4&hash=e937ed03f1637e45d912f4f4d293a
    [user_id] => 6
    [p_signature] => HM2Isn1k6Sy1cKySQGoFH...
)

EDIT:

I'm using Ruby 2.5.5 and Ruby on Rails 5. Currently still getting always "false" in the end. I'll go through it on my console:

Here is the (fake) data, that I get in Rails:

data = {
"alert_id"=>"1", 
"alert_name"=>"alert_created", 
"cancel_url"=>"https://...", 
"checkout_id"=>"1", 
"user_id"=>"1", 
"p_signature"=>"fwWXqR9C..."
} 

public_key = '-----BEGIN PUBLIC KEY-----sDFKJSD2332FKJLWJF......'

Then I do the following:

signature = Base64.decode64(data['p_signature'])

data.delete('p_signature')

data.each {|key, value|data[key] = String(value)}

data_sorted = data.sort_by{|key, value| key}

data_serialized = data_sorted.to_json

digest    = OpenSSL::Digest::SHA1.new

pub_key   = OpenSSL::PKey::RSA.new(public_key)

verified  = pub_key.verify(digest, signature, data_serialized)

In the end verified is always false. What am I doing wrong?


回答1:


The Ruby example that you mentioned doesn't work because you need get the data variable. This must be sent from controller to some class that process the request.

Try this:

in routes.rb

get 'check', to: 'test#check'

in controller

class TestController < ApplicationController

  def check
    verification = SignatureVerifier.new(check_params.as_json)
    if verification
      #... do something
    end
  end

  private

  def check_params
    params.permit.all
  end
end

in verifier class

require 'base64'
require 'json'
require 'openssl'

class SignatureVerifier

  def initialize(data)
    @data = data
    @public_key_path = '/path/to/file'
  end

  #data = {
  #  "alert_name": "payment_succeeded",
  #  "balance_currency": "USD",
  #  "balance_earnings": 355.05,
  #  "balance_fee": 177.36,
  #  "balance_gross": 180.85,
  #  "balance_tax": 433.43,
  #  "checkout_id": "4-601ee0e3d793922-ab8910b010",
  #  "currency": "USD",
  #  "customer_name": "customer_name",
  #  "earnings": 292.87,
  #  "product_name": "Example",
  #  "quantity": 12,
  #  "p_signature": "dl8PN7OrxiYHSJzT3CLUDlElodOE2j8puZkDNPHX9rZnTgig123f4KhtUXZT/HjbU5D7g/PZggxSCt9YrMcWrbSkfINJROTb+YrlhYKAVyTbmMWJV8u+YU6VcGNkhcGK7tIZNBJuaKMBrByrYA14gR3TvMjgXbQWNSFJ8LgJKMWoovbpuOkQwzkKze4vavt3WhElW0izPZwpiqVWTVXAlIvDxHTNT+sS1jXqAHdoli6sVblQQtAujSxdGm2OXB92yifcV0oHhrsqt8rCk1TzJOqsVrhQz1lqSYsbdhlM40QPHM7nHPGe5RITly4t8BjsuCB+V1aeof3N5A0ZDk+2M2Cox6S+vEahEdbW8QdecIKN12SMAYI5kx9zMMiUZ9XZqqC6orXE3uVAcTvMwiTRDDmEVr1HtsBZRo/Ykg7+fMYPc/o7rDpA16/EIOcce1zp+vgilL6rSxIuMFfWlP9qxzrV1MtcmQa86NxEU0GJtebkhehXZfh/eDCAjysmrrBM5xkqE19M+Ye4jZCRTzQTHyDJxjdNYefk7bVfivwRI606JJCGYUMTD6NIsn4rinw2SxKkZquqjTykcob5gn3HH+0AxyjuDj7fsLyqEl3gE9tgo/oMKRBy+zsYzQk4v291sh2PbUfH36W4aL4YYztlsarfMIBWqJshc8rf0RL3pAM="
  #}

  def verify
    data = @data
    signature = Base64.decode64(data[:p_signature])

    # Remove the p_signature parameter
    data.delete(:p_signature)

    # Ensure all the data fields are strings
    data.each {|key, value|data[key] = String(value)}

    # Sort the data
    data_sorted = data.sort_by{|key, value| key}

    # Serialized with JSON library
    data_serialized = data_sorted.to_json

    # verify the data
    digest    = OpenSSL::Digest::SHA1.new
    pub_key   = OpenSSL::PKey::RSA.new(File.read(@public_key_path))
    verified  = pub_key.verify(digest, signature, data_serialized)

    verified
  end
end


来源:https://stackoverflow.com/questions/57444778/how-to-verify-a-webhook-with-ruby-in-rails

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!