class PayWithAmazon::IpnHandler

Pay with Amazon Ipn Handler

This class authenticates an sns message sent from Amazon. It will validate the header, subject, and certificate. After validation there are many helper methods in place to extract information received from the ipn notification.

Constants

COMMON_NAME
SIGNABLE_KEYS

Attributes

body[R]
headers[R]
proxy_addr[RW]
proxy_pass[RW]
proxy_port[RW]
proxy_user[RW]

Public Class Methods

new( headers, body, proxy_addr: :ENV, proxy_port: nil, proxy_user: nil, proxy_pass: nil) click to toggle source

@param headers [request.headers] @param body [request.body.read] @optional proxy_addr [String] @optional proxy_port [String] @optional proxy_user [String] @optional proxy_pass [String]

# File lib/pay_with_amazon/ipn_handler.rb, line 40
def initialize(
        headers,
        body,
        proxy_addr: :ENV,
        proxy_port: nil,
        proxy_user: nil,
        proxy_pass: nil)

  @body = body
  @raw = parse_from(@body)
  @headers = headers
  @proxy_addr = proxy_addr
  @proxy_port = proxy_port
  @proxy_user = proxy_user
  @proxy_pass = proxy_pass
end

Public Instance Methods

authentic?() click to toggle source

This method will authenticate the ipn message sent from Amazon. It will return true if everything is verified. It will raise an error message if verification fails.

# File lib/pay_with_amazon/ipn_handler.rb, line 60
def authentic?
  begin
    decoded_from_base64 = Base64.decode64(signature)
    validate_header
    validate_subject(get_certificate.subject)
    public_key = get_public_key_from(get_certificate)
    verify_public_key(public_key, decoded_from_base64, canonical_string)

    return true
  rescue IpnWasNotAuthenticError => e
    raise e.message
  end
end
environment() click to toggle source
# File lib/pay_with_amazon/ipn_handler.rb, line 118
def environment
  parse_from(@raw['Message'])["ReleaseEnvironment"]
end
message() click to toggle source
# File lib/pay_with_amazon/ipn_handler.rb, line 86
def message
  @raw['Message']
end
message_id() click to toggle source
# File lib/pay_with_amazon/ipn_handler.rb, line 78
def message_id
  @raw['MessageId']
end
message_timestamp() click to toggle source
# File lib/pay_with_amazon/ipn_handler.rb, line 130
def message_timestamp
  parse_from(@raw['Message'])["Timestamp"]
end
notification_data() click to toggle source
# File lib/pay_with_amazon/ipn_handler.rb, line 126
def notification_data
  parse_from(@raw['Message'])["NotificationData"]
end
notification_type() click to toggle source
# File lib/pay_with_amazon/ipn_handler.rb, line 110
def notification_type
  parse_from(@raw['Message'])["NotificationType"]
end
parse_from(json) click to toggle source
# File lib/pay_with_amazon/ipn_handler.rb, line 134
def parse_from(json)
  JSON.parse(json)
end
seller_id() click to toggle source
# File lib/pay_with_amazon/ipn_handler.rb, line 114
def seller_id
  parse_from(@raw['Message'])["SellerId"]
end
signature() click to toggle source
# File lib/pay_with_amazon/ipn_handler.rb, line 94
def signature
  @raw['Signature']
end
signature_version() click to toggle source
# File lib/pay_with_amazon/ipn_handler.rb, line 98
def signature_version
  @raw['SignatureVersion']
end
signing_cert_url() click to toggle source
# File lib/pay_with_amazon/ipn_handler.rb, line 102
def signing_cert_url
  @raw['SigningCertURL']
end
timestamp() click to toggle source
# File lib/pay_with_amazon/ipn_handler.rb, line 90
def timestamp
  @raw['Timestamp']
end
topic_arn() click to toggle source
# File lib/pay_with_amazon/ipn_handler.rb, line 82
def topic_arn
  @raw['TopicArn']
end
type() click to toggle source
# File lib/pay_with_amazon/ipn_handler.rb, line 74
def type
  @raw['Type']
end
unsubscribe_url() click to toggle source
# File lib/pay_with_amazon/ipn_handler.rb, line 106
def unsubscribe_url
  @raw['UnsubscribeURL']
end
version() click to toggle source
# File lib/pay_with_amazon/ipn_handler.rb, line 122
def version
  parse_from(@raw['Message'])["Version"]
end

Protected Instance Methods

canonical_string() click to toggle source
# File lib/pay_with_amazon/ipn_handler.rb, line 149
def canonical_string
  text = ''
  SIGNABLE_KEYS.each do |key|
    value = @raw[key]
    next if value.nil? or value.empty?
    text << key << "\n"
    text << value << "\n"
  end
  text
end
download_cert(url) click to toggle source
# File lib/pay_with_amazon/ipn_handler.rb, line 160
def download_cert(url)
  uri = URI.parse(url)
  unless
    uri.scheme == 'https' &&
    uri.host.match(/^sns\.[a-zA-Z0-9\-]{3,}\.amazonaws\.com(\.cn)?$/) &&
    File.extname(uri.path) == '.pem'
  then
    msg = "Error - certificate is not hosted at AWS URL (https): #{url}"
    raise IpnWasNotAuthenticError, msg
  end
  tries = 0
  begin
    resp = https_get(url)
    resp.body
  rescue => error
    tries += 1
    retry if tries < 3
    raise error
  end
end
get_certificate() click to toggle source
# File lib/pay_with_amazon/ipn_handler.rb, line 140
def get_certificate
  cert_pem = download_cert(signing_cert_url)
  OpenSSL::X509::Certificate.new(cert_pem)
end
get_public_key_from(certificate) click to toggle source
# File lib/pay_with_amazon/ipn_handler.rb, line 145
def get_public_key_from(certificate)
  OpenSSL::PKey::RSA.new(certificate.public_key)
end
https_get(url) click to toggle source
# File lib/pay_with_amazon/ipn_handler.rb, line 181
def https_get(url)
  uri = URI.parse(url)
  http = Net::HTTP.new(uri.host, uri.port, @proxy_addr, @proxy_port, @proxy_user, @proxy_pass)
  http.use_ssl = true
  http.verify_mode = OpenSSL::SSL::VERIFY_PEER
  http.start
  resp = http.request(Net::HTTP::Get.new(uri.request_uri))
  http.finish
  resp
end
validate_header() click to toggle source
# File lib/pay_with_amazon/ipn_handler.rb, line 192
def validate_header
  unless
    @headers['x-amz-sns-message-type'] == 'Notification'
  then
    msg = "Error - Header does not contain x-amz-sns-message-type header"
    raise IpnWasNotAuthenticError, msg
  end
end
validate_subject(certificate_subject) click to toggle source
# File lib/pay_with_amazon/ipn_handler.rb, line 201
def validate_subject(certificate_subject)
  subject = certificate_subject.to_a
  unless
    subject[4][1] == COMMON_NAME
  then
    msg = "Error - Unable to verify certificate subject issued by Amazon"
    raise IpnWasNotAuthenticError, msg
  end
end
verify_public_key(public_key, decoded_signature, signed_string) click to toggle source
# File lib/pay_with_amazon/ipn_handler.rb, line 211
def verify_public_key(public_key, decoded_signature, signed_string)
  unless
    public_key.verify(OpenSSL::Digest::SHA1.new, decoded_signature, signed_string)
  then
    msg = "Error - Unable to verify public key with signature and signed string"
    raise IpnWasNotAuthenticError, msg
  end
end