Skip to content

Commit e6256e7

Browse files
author
Greg Taylor
committed
Replacing urllib2 with python-requests. Correcting a UniPay bug. Cleanup of the interface initialization. Removed the old SSL cert validation code, as python-requests now does that for us.
1 parent eec1251 commit e6256e7

File tree

6 files changed

+69
-195
lines changed

6 files changed

+69
-195
lines changed

CHANGES

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,18 @@ Changes introduced with each version.
33
1.2.0
44
-----
55

6+
* Replacing urllib2 with python-requests. Greatly simplifies things, and
7+
improves SSL certificate validation.
8+
* SSL certificate validation is now enabled by default. The old behvaior of
9+
being able to specify a private cert to validate against still remains.
610
* Tests are now ran via nose. The old test runner is no more. See the README
711
in the ``tests`` directory for details.
812
* Added the ``bm_create_button`` method, which maps to BMCreateButton.
13+
* UniPay should be working again.
914

1015
1.1.0
1116
-----
17+
1218
This version addresses compatibility with the newer versions of the PayPal API
1319
by backing off on pre-query validation. PayPal's API error messages are
1420
the safest bet, as bad as they can be at times.
@@ -29,15 +35,18 @@ changes are prefixed with 'BIC'.
2935

3036
1.0.3
3137
-----
38+
3239
* Python 2.5 compatibility. (Manik)
3340

3441
1.0.2
3542
-----
43+
3644
* Documentation updates. (gtaylor)
3745
* We now distribute with a copy of the Apache License. (gtaylor)
3846

3947
1.0.1
4048
-----
49+
4150
* Misc. more specific imports to avoid import errors. (grigouze)
4251
* Fixes to country test suite. (grigouze)
4352
* Added a countries module that has all of the countries PayPal supports. (gtaylor)

paypal/__init__.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
# coding=utf-8
2+
#noinspection PyUnresolvedReferences
23
from paypal.interface import PayPalInterface
4+
#noinspection PyUnresolvedReferences
35
from paypal.settings import PayPalConfig
6+
#noinspection PyUnresolvedReferences
47
from paypal.exceptions import PayPalError, PayPalConfigError, PayPalAPIResponseError
8+
#noinspection PyUnresolvedReferences
59
import paypal.countries
10+
import logging
11+
12+
logging.basicConfig()
613

714
VERSION = '1.2.0'

paypal/https_connection.py

Lines changed: 0 additions & 141 deletions
This file was deleted.

paypal/interface.py

Lines changed: 12 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,15 @@
66
"""
77

88
import types
9-
import socket
109
import urllib
11-
import urllib2
1210
import logging
1311
from pprint import pformat
1412

13+
import requests
14+
1515
from paypal.settings import PayPalConfig
1616
from paypal.response import PayPalResponse
1717
from paypal.exceptions import PayPalError, PayPalAPIResponseError
18-
from paypal.https_connection import CertValidatingHTTPSHandler
1918

2019
logger = logging.getLogger('paypal.interface')
2120

@@ -73,21 +72,18 @@ def _call(self, method, **kwargs):
7372
7473
``kwargs`` will be a hash of
7574
"""
76-
# Beware, this is a global setting.
77-
socket.setdefaulttimeout(self.config.HTTP_TIMEOUT)
78-
7975
# This dict holds the key/value pairs to pass to the PayPal API.
8076
url_values = {
8177
'METHOD': method,
8278
'VERSION': self.config.API_VERSION,
8379
}
8480

85-
if(self.config.API_AUTHENTICATION_MODE == "3TOKEN"):
81+
if self.config.API_AUTHENTICATION_MODE == "3TOKEN":
8682
url_values['USER'] = self.config.API_USERNAME
8783
url_values['PWD'] = self.config.API_PASSWORD
8884
url_values['SIGNATURE'] = self.config.API_SIGNATURE
89-
elif(self.config.API_AUTHENTICATION_MODE == "UNIPAY"):
90-
url_values['SUBJECT'] = self.config.SUBJECT
85+
elif self.config.API_AUTHENTICATION_MODE == "UNIPAY":
86+
url_values['SUBJECT'] = self.config.UNIPAY_SUBJECT
9187

9288
# All values passed to PayPal API must be uppercase.
9389
for key, value in kwargs.iteritems():
@@ -97,24 +93,15 @@ def _call(self, method, **kwargs):
9793
if logger.isEnabledFor(logging.DEBUG):
9894
logger.debug('PayPal NVP Query Key/Vals:\n%s' % pformat(url_values))
9995

100-
url = self._encode_utf8(**url_values)
101-
data = urllib.urlencode(url).encode('utf-8')
102-
req = urllib2.Request(self.config.API_ENDPOINT, data)
103-
104-
# If certificate provided build an opener that will validate PayPal server cert
105-
if self.config.API_CA_CERTS:
106-
handler = CertValidatingHTTPSHandler(ca_certs=self.config.API_CA_CERTS)
107-
opener = urllib2.build_opener(handler)
108-
if logger.isEnabledFor(logging.DEBUG):
109-
logger.debug('Validating PayPal server with certificate:\n%s\n' % self.config.API_CA_CERTS)
110-
else:
111-
opener = urllib2.build_opener()
112-
if logger.isEnabledFor(logging.DEBUG):
113-
logger.debug('Skipping PayPal server certificate validation')
96+
req = requests.get(
97+
self.config.API_ENDPOINT,
98+
params=url_values,
99+
timeout=self.config.HTTP_TIMEOUT,
100+
verify=self.config.API_CA_CERTS,
101+
)
114102

115103
# Call paypal API
116-
response = PayPalResponse(opener.open(req).read().decode('utf-8'),
117-
self.config)
104+
response = PayPalResponse(req.text, self.config)
118105

119106
logger.debug('PayPal NVP API Endpoint: %s'% self.config.API_ENDPOINT)
120107

paypal/settings.py

Lines changed: 39 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import os
99
from pprint import pformat
1010

11-
from paypal.exceptions import PayPalConfigError, PayPalError
11+
from paypal.exceptions import PayPalConfigError
1212

1313
logger = logging.getLogger('paypal.settings')
1414

@@ -21,8 +21,8 @@ class PayPalConfig(object):
2121
"""
2222
# Used to validate correct values for certain config directives.
2323
_valid_= {
24-
'API_ENVIRONMENT' : ['sandbox','production'],
25-
'API_AUTHENTICATION_MODE' : ['3TOKEN','CERTIFICATE'],
24+
'API_ENVIRONMENT' : ['sandbox', 'production'],
25+
'API_AUTHENTICATION_MODE' : ['3TOKEN', 'CERTIFICATE'],
2626
}
2727

2828
# Various API servers.
@@ -56,9 +56,11 @@ class PayPalConfig(object):
5656
API_ENDPOINT = None
5757
PAYPAL_URL_BASE = None
5858

59-
# API Endpoint CA certificate chain
60-
# (filename with path e.g. '/etc/ssl/certs/Verisign_Class_3_Public_Primary_Certification_Authority.pem')
61-
API_CA_CERTS = None
59+
# API Endpoint CA certificate chain. If this is True, do a simple SSL
60+
# certificate check on the endpoint. If it's a full path, verify against
61+
# a private cert.
62+
# e.g. '/etc/ssl/certs/Verisign_Class_3_Public_Primary_Certification_Authority.pem'
63+
API_CA_CERTS = True
6264

6365
# UNIPAY credentials
6466
UNIPAY_SUBJECT = None
@@ -67,7 +69,7 @@ class PayPalConfig(object):
6769
ACK_SUCCESS_WITH_WARNING = "SUCCESSWITHWARNING"
6870

6971
# In seconds. Depending on your setup, this may need to be higher.
70-
HTTP_TIMEOUT = 15
72+
HTTP_TIMEOUT = 15.0
7173

7274
def __init__(self, **kwargs):
7375
"""
@@ -79,30 +81,38 @@ def __init__(self, **kwargs):
7981
are applied for certain directives in the absence of
8082
user-provided values.
8183
"""
82-
if 'API_ENVIRONMENT' not in kwargs:
83-
kwargs['API_ENVIRONMENT']= self.API_ENVIRONMENT
84-
# Make sure the environment is one of the acceptable values.
85-
if kwargs['API_ENVIRONMENT'] not in self._valid_['API_ENVIRONMENT']:
86-
raise PayPalConfigError('Invalid API_ENVIRONMENT')
87-
self.API_ENVIRONMENT = kwargs['API_ENVIRONMENT']
88-
89-
if 'API_AUTHENTICATION_MODE' not in kwargs:
90-
kwargs['API_AUTHENTICATION_MODE']= self.API_AUTHENTICATION_MODE
91-
# Make sure the auth mode is one of the known/implemented methods.
92-
if kwargs['API_AUTHENTICATION_MODE'] not in self._valid_['API_AUTHENTICATION_MODE']:
93-
raise PayPalConfigError("Not a supported auth mode. Use one of: %s" % \
94-
", ".join(self._valid_['API_AUTHENTICATION_MODE']))
84+
if kwargs.get('API_ENVIRONMENT'):
85+
api_environment = kwargs['API_ENVIRONMENT'].upper()
86+
# Make sure the environment is one of the acceptable values.
87+
if api_environment not in self._valid_['API_ENVIRONMENT']:
88+
raise PayPalConfigError('Invalid API_ENVIRONMENT')
89+
else:
90+
self.API_ENVIRONMENT = api_environment
91+
92+
if kwargs.get('API_AUTHENTICATION_MODE'):
93+
auth_mode = kwargs['API_AUTHENTICATION_MODE'].upper()
94+
# Make sure the auth mode is one of the known/implemented methods.
95+
if auth_mode not in self._valid_['API_AUTHENTICATION_MODE']:
96+
choices = ", ".join(self._valid_['API_AUTHENTICATION_MODE'])
97+
raise PayPalConfigError(
98+
"Not a supported auth mode. Use one of: %s" % choices
99+
)
100+
else:
101+
self.API_AUTHENTICATION_MODE = auth_mode
95102

96103
# Set the API endpoints, which is a cheesy way of saying API servers.
97104
self.API_ENDPOINT= self._API_ENDPOINTS[self.API_AUTHENTICATION_MODE][self.API_ENVIRONMENT]
98105
self.PAYPAL_URL_BASE= self._PAYPAL_URL_BASE[self.API_ENVIRONMENT]
99106

100-
# Set the CA_CERTS location
101-
if 'API_CA_CERTS' not in kwargs:
102-
kwargs['API_CA_CERTS']= self.API_CA_CERTS
103-
if kwargs['API_CA_CERTS'] and not os.path.exists(kwargs['API_CA_CERTS']):
104-
raise PayPalConfigError('Invalid API_CA_CERTS')
105-
self.API_CA_CERTS = kwargs['API_CA_CERTS']
107+
# Set the CA_CERTS location. This can either be a None, a bool, or a
108+
# string path.
109+
if kwargs.get('API_CA_CERTS'):
110+
self.API_CA_CERTS = kwargs['API_CA_CERTS']
111+
112+
if isinstance(self.API_CA_CERTS, basestring) and \
113+
not os.path.exists(self.API_CA_CERTS):
114+
# A CA Cert path was specified, but it's invalid.
115+
raise PayPalConfigError('Invalid API_CA_CERTS')
106116

107117
# set the 3TOKEN required fields
108118
if self.API_AUTHENTICATION_MODE == '3TOKEN':
@@ -115,5 +125,6 @@ def __init__(self, **kwargs):
115125
if arg in kwargs:
116126
setattr(self, arg, kwargs[arg])
117127

118-
logger.debug('PayPalConfig object instantiated with kwargs: %s' %
119-
pformat(kwargs))
128+
logger.debug(
129+
'PayPalConfig object instantiated with kwargs: %s' % pformat(kwargs)
130+
)

requirements.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
nose
1+
nose
2+
requests

0 commit comments

Comments
 (0)