class AmazonPay::IpnHandler

AmazonPay 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
MSG_CERTIFICATE
MSG_HEADER
MSG_KEY
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, log_enabled: false, log_file_name: nil, log_level: :DEBUG ) 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/amazon_pay/ipn_handler.rb, line 54
def initialize(
  headers,
  body,
  proxy_addr: :ENV,
  proxy_port: nil,
  proxy_user: nil,
  proxy_pass: nil,
  log_enabled: false,
  log_file_name: nil,
  log_level: :DEBUG
)

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

  @log_enabled = log_enabled
  @logger = AmazonPay::LogInitializer.new(log_file_name, log_level).create_logger if @log_enabled
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/amazon_pay/ipn_handler.rb, line 81
def authentic?
  decoded_from_base64 = Base64.decode64(signature)
  validate_header
  validate_subject(certificate.subject)
  public_key = public_key_from(certificate)
  verify_public_key(public_key, decoded_from_base64, canonical_string)

  return true
rescue IpnWasNotAuthenticError => e
  raise e.message
end
environment() click to toggle source
# File lib/amazon_pay/ipn_handler.rb, line 137
def environment
  parse_from(@raw['Message'])['ReleaseEnvironment']
end
message() click to toggle source
# File lib/amazon_pay/ipn_handler.rb, line 105
def message
  @raw['Message']
end
message_id() click to toggle source
# File lib/amazon_pay/ipn_handler.rb, line 97
def message_id
  @raw['MessageId']
end
message_timestamp() click to toggle source
# File lib/amazon_pay/ipn_handler.rb, line 149
def message_timestamp
  parse_from(@raw['Message'])['Timestamp']
end
notification_data() click to toggle source
# File lib/amazon_pay/ipn_handler.rb, line 145
def notification_data
  parse_from(@raw['Message'])['NotificationData']
end
notification_type() click to toggle source
# File lib/amazon_pay/ipn_handler.rb, line 129
def notification_type
  parse_from(@raw['Message'])['NotificationType']
end
parse_from(json) click to toggle source
# File lib/amazon_pay/ipn_handler.rb, line 153
def parse_from(json)
  JSON.parse(json)
end
seller_id() click to toggle source
# File lib/amazon_pay/ipn_handler.rb, line 133
def seller_id
  parse_from(@raw['Message'])['SellerId']
end
signature() click to toggle source
# File lib/amazon_pay/ipn_handler.rb, line 113
def signature
  @raw['Signature']
end
signature_version() click to toggle source
# File lib/amazon_pay/ipn_handler.rb, line 117
def signature_version
  @raw['SignatureVersion']
end
signing_cert_url() click to toggle source
# File lib/amazon_pay/ipn_handler.rb, line 121
def signing_cert_url
  @raw['SigningCertURL']
end
timestamp() click to toggle source
# File lib/amazon_pay/ipn_handler.rb, line 109
def timestamp
  @raw['Timestamp']
end
topic_arn() click to toggle source
# File lib/amazon_pay/ipn_handler.rb, line 101
def topic_arn
  @raw['TopicArn']
end
type() click to toggle source
# File lib/amazon_pay/ipn_handler.rb, line 93
def type
  @raw['Type']
end
unsubscribe_url() click to toggle source
# File lib/amazon_pay/ipn_handler.rb, line 125
def unsubscribe_url
  @raw['UnsubscribeURL']
end
version() click to toggle source
# File lib/amazon_pay/ipn_handler.rb, line 141
def version
  parse_from(@raw['Message'])['Version']
end

Protected Instance Methods

canonical_string() click to toggle source
# File lib/amazon_pay/ipn_handler.rb, line 168
def canonical_string
  text = ''
  SIGNABLE_KEYS.each do |key|
    value = @raw[key]
    next if value.nil? || value.empty?
    text << key << "\n"
    text << value << "\n"
  end
  text
end
certificate() click to toggle source
# File lib/amazon_pay/ipn_handler.rb, line 159
def certificate
  cert_pem = download_cert(signing_cert_url)
  OpenSSL::X509::Certificate.new(cert_pem)
end
download_cert(url) click to toggle source
# File lib/amazon_pay/ipn_handler.rb, line 179
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'
    msg = "Error - certificate is not hosted at AWS URL (https): #{url}"
    raise IpnWasNotAuthenticError, msg
  end
  tries = 0
  begin
    resp = https_get(url)
    if @log_enabled
      data = AmazonPay::Sanitize.new(resp.body)
      @logger.debug(data.sanitize_response_data)
    end
    resp.body
  rescue StandardError => error
    tries += 1
    retry if tries < 3
    raise error
  end
end
https_get(url) click to toggle source
# File lib/amazon_pay/ipn_handler.rb, line 202
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
public_key_from(certificate) click to toggle source
# File lib/amazon_pay/ipn_handler.rb, line 164
def public_key_from(certificate)
  OpenSSL::PKey::RSA.new(certificate.public_key)
end
validate_header() click to toggle source
# File lib/amazon_pay/ipn_handler.rb, line 213
def validate_header
  raise IpnWasNotAuthenticError, MSG_HEADER unless @headers['x-amz-sns-message-type'] == 'Notification'
end
validate_subject(certificate_subject) click to toggle source
# File lib/amazon_pay/ipn_handler.rb, line 217
def validate_subject(certificate_subject)
  subject = certificate_subject.to_a
  raise IpnWasNotAuthenticError, MSG_CERTIFICATE unless subject.rassoc(COMMON_NAME)
end
verify_public_key(public_key, decoded_signature, signed_string) click to toggle source
# File lib/amazon_pay/ipn_handler.rb, line 222
def verify_public_key(public_key, decoded_signature, signed_string)
  raise IpnWasNotAuthenticError, MSG_KEY unless public_key.verify(OpenSSL::Digest::SHA1.new, decoded_signature, signed_string)
end