addons-server/mkt/inapp_pay/verify.py

166 строки
5.9 KiB
Python
Исходник Обычный вид История

import calendar
from datetime import datetime
import json
import sys
import time
from django.conf import settings
import jwt
from statsd import statsd
import amo
2012-03-28 00:31:00 +04:00
from mkt.inapp_pay.models import InappConfig
class InappPaymentError(Exception):
"""An error occurred while processing an in-app payment."""
def __init__(self, msg, app_id=None):
self.app_id = app_id
if self.app_id:
msg = '%s (app ID=%r)' % (msg, self.app_id)
super(Exception, self).__init__(msg)
class UnknownAppError(InappPaymentError):
"""The application ID is not known."""
class RequestVerificationError(InappPaymentError):
"""The payment request could not be verified."""
class RequestExpired(InappPaymentError):
"""The payment request expired."""
class AppPaymentsDisabled(InappPaymentError):
"""In-app payment functionality for this app has been disabled."""
class AppPaymentsRevoked(InappPaymentError):
"""In-app payment functionality for this app has been revoked."""
class InvalidRequest(InappPaymentError):
"""The payment request has malformed or missing information."""
def _re_raise_as(NewExc, *args, **kw):
"""Raise a new exception using the preserved traceback of the last one."""
etype, val, tb = sys.exc_info()
raise NewExc(*args, **kw), None, tb
def verify_request(signed_request):
"""
Verifies a signed in-app payment request.
Returns the trusted JSON data from the original request.
JWT spec: http://openid.net/specs/draft-jones-json-web-token-07.html
One extra key, _config, is added to the returned JSON.
This is the InappConfig instance.
When there's an error, an exception derived from InappPaymentError
will be raised.
"""
try:
signed_request = str(signed_request) # must be base64 encoded bytes
except UnicodeEncodeError, exc:
_re_raise_as(RequestVerificationError,
'Non-ascii payment JWT: %s' % exc)
try:
app_req = jwt.decode(signed_request, verify=False)
except jwt.DecodeError, exc:
_re_raise_as(RequestVerificationError, 'Invalid payment JWT: %s' % exc)
try:
app_req = json.loads(app_req)
except ValueError, exc:
_re_raise_as(RequestVerificationError,
'Invalid JSON for payment JWT: %s' % exc)
app_id = app_req.get('iss')
# Verify the signature:
try:
cfg = InappConfig.objects.get(public_key=app_id)
except InappConfig.DoesNotExist:
_re_raise_as(UnknownAppError, 'App does not exist', app_id=app_id)
if cfg.status == amo.INAPP_STATUS_REVOKED:
raise AppPaymentsRevoked('Payments revoked', app_id=app_id)
elif cfg.status != amo.INAPP_STATUS_ACTIVE:
raise AppPaymentsDisabled('Payments disabled (status=%s)'
% (cfg.status), app_id=app_id)
app_req['_config'] = cfg
# TODO(Kumar) see bug 736573 for HSM integration
try:
with statsd.timer('inapp_pay.verify'):
jwt.decode(signed_request, cfg.private_key, verify=True)
except jwt.DecodeError, exc:
_re_raise_as(RequestVerificationError,
'Payment verification failed: %s' % exc,
app_id=app_id)
# Check timestamps:
try:
expires = float(str(app_req.get('exp')))
issued = float(str(app_req.get('iat')))
except ValueError:
_re_raise_as(RequestVerificationError,
'Payment JWT had an invalid exp (%r) or iat (%r) '
% (app_req.get('exp'), app_req.get('iat')),
app_id=app_id)
now = calendar.timegm(time.gmtime())
if expires < now:
raise RequestExpired('Payment JWT expired: %s UTC < %s UTC '
'(issued at %s UTC)'
% (datetime.utcfromtimestamp(expires),
datetime.utcfromtimestamp(now),
datetime.utcfromtimestamp(issued)),
app_id=app_id)
if issued < (now - 3600): # issued more than an hour ago
raise RequestExpired('Payment JWT iat expired: %s UTC < %s UTC '
% (datetime.utcfromtimestamp(issued),
datetime.utcfromtimestamp(now)),
app_id=app_id)
try:
not_before = float(str(app_req.get('nbf')))
except ValueError:
app_req['nbf'] = None # this field is optional
else:
about_now = now + 300 # pad 5 minutes for clock skew
if not_before >= about_now:
raise InvalidRequest('Payment JWT cannot be processed before '
'%s UTC (nbf must be < %s UTC)'
% (datetime.utcfromtimestamp(not_before),
datetime.utcfromtimestamp(about_now)),
app_id=app_id)
# Check JWT audience.
audience = app_req.get('aud', None)
if not audience:
raise InvalidRequest('Payment JWT is missing aud (audience)',
app_id=app_id)
if audience != settings.INAPP_PAYMENT_AUD:
raise InvalidRequest('Payment JWT aud (audience) must be set to %r; '
'got: %r' % (settings.INAPP_PAYMENT_AUD,
audience),
app_id=app_id)
# Check payment details.
request = app_req.get('request', None)
if not isinstance(request, dict):
raise InvalidRequest('Payment JWT is missing request dict: %r'
% request, app_id=app_id)
for key in ('price', 'currency', 'name', 'description'):
if key not in request:
raise InvalidRequest('Payment JWT is missing request[%r]'
% key, app_id=app_id)
# TODO(Kumar) Prevent repeat payments with nonce (bug 738776).
return app_req