Skip to content

Commit f3d1fea

Browse files
committed
Conflicts: coinbase/__init__.py
2 parents aa4dfa9 + 32eb767 commit f3d1fea

File tree

1 file changed

+100
-92
lines changed

1 file changed

+100
-92
lines changed

coinbase/__init__.py

Lines changed: 100 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,18 @@
4040
except:
4141
oauth2_supported = False
4242

43-
import requests
4443
import httplib2
4544
import json
4645
import os
46+
import hashlib
47+
import hmac
48+
import time
4749
import re
48-
4950
from decimal import Decimal
51+
from warnings import warn
52+
53+
import requests
54+
from requests.auth import AuthBase
5055

5156
from coinbase.config import COINBASE_ENDPOINT
5257
from coinbase.models import *
@@ -69,48 +74,21 @@ def coinbase_url(*args):
6974
return '/'.join([COINBASE_ENDPOINT] + args)
7075

7176

72-
class CoinbaseAccount(object):
73-
"""
74-
Primary object for interacting with a Coinbase account
75-
76-
You may use oauth credentials, a classic API key, or
77-
no auth (for unauthenticated resources only)
78-
"""
79-
80-
def __init__(self, oauth2_credentials=None, api_key=None,
81-
allow_transfers=True):
82-
"""
83-
:param oauth2_credentials: JSON representation of Coinbase oauth2
84-
credentials
85-
:param api_key: Coinbase API key
86-
:param allow_transfers: Whether to allow sending money.
87-
You can set this to False for safety while in a development
88-
environment if you want to more sure you don't actually send
89-
any money around.
90-
"""
91-
92-
self.allow_transfers = allow_transfers
93-
94-
#Set up our requests session
95-
self.session = requests.session()
77+
class CoinbaseAuth(AuthBase):
78+
def __init__(self, oauth2_credentials=None, api_key=None, api_secret=None):
79+
#CA Cert Path
80+
ca_directory = os.path.abspath(__file__).split('/')[0:-1]
9681

97-
#Set our Content-Type
98-
self.session.headers.update({'content-type': 'application/json'})
82+
ca_path = '/'.join(ca_directory) + '/ca_certs.txt'
9983

100-
self.authenticated = bool(oauth2_credentials or api_key)
84+
#Set CA certificates (breaks without them)
85+
self.http = httplib2.Http(ca_certs=ca_path)
10186

102-
if oauth2_credentials:
87+
self.oauth2_credentials = None
10388

89+
if oauth2_credentials is not None:
10490
if not oauth2_supported:
105-
raise Exception('oauth2 is not supported in this environment')
106-
107-
#CA Cert Path
108-
ca_directory = os.path.abspath(__file__).split('/')[0:-1]
109-
110-
ca_path = '/'.join(ca_directory) + '/ca_certs.txt'
111-
112-
#Set CA certificates (breaks without them)
113-
self.http = httplib2.Http(ca_certs=ca_path)
91+
raise RuntimeError('oauth2 is not supported in this environment')
11492

11593
#Create our credentials from the JSON sent
11694
self.oauth2_credentials = \
@@ -123,27 +101,15 @@ def __init__(self, oauth2_credentials=None, api_key=None,
123101
except AccessTokenCredentialsError:
124102
self.token_expired = True
125103

126-
#Apply our oAuth credentials to the session
127-
self.oauth2_credentials.apply(headers=self.session.headers)
128-
129-
#Set our request parameters to be empty
130-
self.auth_params = {}
104+
elif api_key and api_secret:
105+
self.api_key = api_key
106+
self.api_secret = api_secret
131107

132108
elif api_key:
133-
#Set our API Key
109+
warn("API key authentication without a secret has been deprecated"
110+
" by Coinbase- you should use a new key with a secret!")
134111
self.api_key = api_key
135112

136-
#Set our auth_params
137-
self.auth_params = {'api_key': api_key}
138-
139-
def _require_allow_transfers(self):
140-
if not self.allow_transfers:
141-
raise Exception('Transfers are not enabled')
142-
143-
def _require_authentication(self):
144-
if not self.authenticated:
145-
raise Exception('Authentication credentials required')
146-
147113
def _check_oauth_expired(self):
148114
"""
149115
Internal function to check if the oauth2 credentials are expired
@@ -152,9 +118,6 @@ def _check_oauth_expired(self):
152118
#Check if they are expired
153119
if self.oauth2_credentials.access_token_expired:
154120

155-
#Print an notification message if they are
156-
print('oAuth2 Token Expired')
157-
158121
#Raise the appropriate error
159122
raise AccessTokenCredentialsError
160123

@@ -179,19 +142,77 @@ def refresh_oauth(self):
179142
#If the refresh token was invalid
180143
except AccessTokenRefreshError:
181144

182-
#Print a warning
183-
print('Your refresh token is invalid')
145+
warn('Your refresh token is invalid.')
184146

185147
#Raise the appropriate error
186148
raise AccessTokenRefreshError
187149

188-
def _prepare_request(self):
150+
def __call__(self, req):
151+
import pdb; pdb.set_trace()
152+
if self.oauth2_credentials:
153+
#Check if the oauth token is expired and refresh it if necessary
154+
self._check_oauth_expired()
155+
156+
self.oauth2_credentials.apply(headers=req.headers)
157+
elif self.api_key is not None and self.api_secret is not None:
158+
nonce = int(time.time() * 1e6)
159+
message = str(nonce) + req.url + ('' if not req.body else req.body)
160+
signature = hmac.new(self.api_secret, message, hashlib.sha256).hexdigest()
161+
req.headers.update({'ACCESS_KEY': self.api_key,
162+
'ACCESS_SIGNATURE': signature,
163+
'ACCESS_NONCE': nonce})
164+
165+
elif self.api_key is not None:
166+
req.params.update({'api_key': api_key})
167+
return req
168+
169+
170+
class CoinbaseAccount(object):
171+
"""
172+
Primary object for interacting with a Coinbase account
173+
174+
You may use oauth credentials, an API key + secret, a lone API key
175+
(deprecated), or no auth (for unauthenticated resources only).
176+
"""
177+
178+
def __init__(self, oauth2_credentials=None, api_key=None, api_secret=None,
179+
allow_transfers=True):
189180
"""
190-
Prepare our request in various ways
181+
:param oauth2_credentials: JSON representation of Coinbase oauth2
182+
credentials
183+
:param api_key: Coinbase API key
184+
:param api_secret: Coinbase API secret. Typically included with a key,
185+
since key-only auth is deprecated.
186+
:param allow_transfers: Whether to allow sending money.
187+
You can set this to False for safety while in a development
188+
environment if you want to more sure you don't actually send
189+
any money around.
191190
"""
192191

193-
#Check if the oauth token is expired and refresh it if necessary
194-
self._check_oauth_expired()
192+
self.allow_transfers = allow_transfers
193+
194+
self.authenticated = (oauth2_credentials is not None
195+
or api_key is not None)
196+
if self.authenticated:
197+
self.auth = CoinbaseAuth(oauth2_credentials=oauth2_credentials,
198+
api_key=api_key, api_secret=api_secret)
199+
else:
200+
self.auth = {}
201+
202+
#Set up our requests session
203+
self.session = requests.session()
204+
self.session.auth = self.auth
205+
206+
#Set our Content-Type
207+
self.session.headers.update({'content-type': 'application/json'})
208+
209+
def _require_allow_transfers(self):
210+
if not self.allow_transfers:
211+
raise Exception('Transfers are not enabled')
212+
213+
def _require_authentication(self):
214+
if not self.authenticated:
215+
raise Exception('Authentication credentials required')
195216

196217
@property
197218
def balance(self):
@@ -203,7 +224,7 @@ def balance(self):
203224
self._require_authentication()
204225

205226
url = coinbase_url('account', 'balance')
206-
response = self.session.get(url, params=self.auth_params)
227+
response = self.session.get(url)
207228
return CoinbaseAmount.from_coinbase_dict(response.json())
208229

209230
@property
@@ -216,7 +237,7 @@ def receive_address(self):
216237
self._require_authentication()
217238

218239
url = coinbase_url('account', 'receive_address')
219-
response = self.session.get(url, params=self.auth_params)
240+
response = self.session.get(url)
220241
return response.json()['address']
221242

222243
def contacts(self, page=None, limit=None, query=None):
@@ -227,7 +248,6 @@ def contacts(self, page=None, limit=None, query=None):
227248
:param limit: Number of records to return. Maximum is 1000. Default
228249
value is 25.
229250
:param query: Optional partial string match to filter contacts.
230-
231251
:return: list of CoinbaseContact
232252
"""
233253
self._require_authentication()
@@ -241,7 +261,6 @@ def contacts(self, page=None, limit=None, query=None):
241261
params['limit'] = limit
242262
if query is not None:
243263
params['query'] = query
244-
params.update(self.auth_params)
245264

246265
response = self.session.get(url, params=params)
247266
return [CoinbaseContact.from_coinbase_dict(x['contact'])
@@ -256,8 +275,7 @@ def buy_price(self, qty=1):
256275
url = coinbase_url('prices', 'buy')
257276
params = {'qty': qty}
258277
response = self.session.get(url, params=params)
259-
results = response.json()
260-
return CoinbaseAmount.from_coinbase_dict(results)
278+
return CoinbaseAmount.from_coinbase_dict(response.json())
261279

262280
def sell_price(self, qty=1):
263281
"""
@@ -290,8 +308,8 @@ def buy_btc(self, qty, pricevaries=False):
290308
"qty": qty,
291309
"agree_btc_amount_varies": pricevaries
292310
}
293-
response = self.session.post(url=url, data=json.dumps(request_data),
294-
params=self.auth_params)
311+
response = self.session.post(url=url, data=json.dumps(request_data))
312+
295313
response_parsed = response.json()
296314
if not response_parsed.get('success'):
297315
raise CoinbaseError('Failed to buy btc.',
@@ -314,8 +332,7 @@ def sell_btc(self, qty):
314332
request_data = {
315333
"qty": qty,
316334
}
317-
response = self.session.post(url=url, data=json.dumps(request_data),
318-
params=self.auth_params)
335+
response = self.session.post(url=url, data=json.dumps(request_data))
319336
response_parsed = response.json()
320337
if not response_parsed.get('success'):
321338
raise CoinbaseError('Failed to sell btc.',
@@ -351,8 +368,7 @@ def request(self, from_email, amount, notes=''):
351368
request_data['transaction']['amount_string'] = str(amount.amount)
352369
request_data['transaction']['amount_currency_iso'] = amount.currency
353370

354-
response = self.session.post(url=url, data=json.dumps(request_data),
355-
params=self.auth_params)
371+
response = self.session.post(url=url, data=json.dumps(request_data))
356372
response_parsed = response.json()
357373
if not response_parsed.get('success'):
358374
raise CoinbaseError('Failed to request btc.',
@@ -403,8 +419,7 @@ def send(self, to_address, amount, notes='', user_fee=None, idem=None):
403419
if idem is not None:
404420
request_data['transaction']['idem'] = str(idem)
405421

406-
response = self.session.post(url=url, data=json.dumps(request_data),
407-
params=self.auth_params)
422+
response = self.session.post(url=url, data=json.dumps(request_data))
408423
response_parsed = response.json()
409424

410425
if not response_parsed.get('success'):
@@ -432,7 +447,6 @@ def transactions(self, count=30):
432447

433448
if not reached_final_page:
434449
params = {'page': page}
435-
params.update(self.auth_params)
436450
response = self.session.get(url=url, params=params)
437451
parsed_transactions = response.json()
438452

@@ -464,7 +478,6 @@ def transfers(self, count=30):
464478

465479
if not reached_final_page:
466480
params = {'page': page}
467-
params.update(self.auth_params)
468481
response = self.session.get(url=url, params=params)
469482
parsed_transfers = response.json()
470483

@@ -486,7 +499,7 @@ def get_transaction(self, transaction_id):
486499
self._require_authentication()
487500

488501
url = coinbase_url('transactions', transaction_id)
489-
response = self.session.get(url, params=self.auth_params)
502+
response = self.session.get(url)
490503
results = response.json()
491504

492505
if not results.get('success', True):
@@ -504,7 +517,7 @@ def get_user_details(self):
504517
self._require_authentication()
505518

506519
url = coinbase_url('users')
507-
response = self.session.get(url, params=self.auth_params)
520+
response = self.session.get(url)
508521
results = response.json()
509522

510523
return CoinbaseUser.from_coinbase_dict(results['users'][0]['user'])
@@ -523,8 +536,7 @@ def generate_receive_address(self, callback_url=None):
523536
'callback_url': callback_url
524537
}
525538
}
526-
response = self.session.post(url=url, data=json.dumps(request_data),
527-
params=self.auth_params)
539+
response = self.session.post(url=url, data=json.dumps(request_data))
528540
return response.json()['address']
529541

530542
def create_button(self, button, account_id=None):
@@ -551,8 +563,7 @@ def create_button(self, button, account_id=None):
551563
if account_id is not None:
552564
request_data['account_id'] = account_id
553565

554-
response = self.session.post(url=url, data=json.dumps(request_data),
555-
params=self.auth_params)
566+
response = self.session.post(url=url, data=json.dumps(request_data))
556567
resp_data = response.json()
557568
if not resp_data.get('success') or 'button' not in resp_data:
558569
error_msg = 'Error creating button'
@@ -601,7 +612,6 @@ def orders(self, account_id=None, page=None):
601612
params['account_id'] = account_id
602613
if page is not None:
603614
params['page'] = page
604-
params.update(self.auth_params)
605615

606616
response = self.session.get(url=url, params=params)
607617
return list(map(
@@ -617,7 +627,6 @@ def get_order(self, id_or_custom_field, account_id=None):
617627
params = {}
618628
if account_id is not None:
619629
params['account_id'] = account_id
620-
params.update(self.auth_params)
621630

622631
response = self.session.get(url=url, params=params)
623632
return CoinbaseOrder.from_coinbase_dict(response.json())
@@ -631,14 +640,13 @@ def create_button_and_order(self, button):
631640
'button': button.to_coinbase_dict()
632641
}
633642

634-
response = self.session.post(url=url, data=json.dumps(request_data),
635-
params=self.auth_params)
643+
response = self.session.post(url=url, data=json.dumps(request_data))
636644
return CoinbaseOrder.from_coinbase_dict(response.json())
637645

638646
def create_order_from_button(self, button_id):
639647
self._require_authentication()
640648

641649
url = coinbase_url('buttons', button_id, 'create_order')
642650

643-
response = self.session.post(url=url, params=self.auth_params)
651+
response = self.session.post(url=url)
644652
return CoinbaseOrder.from_coinbase_dict(response.json())

0 commit comments

Comments
 (0)