4040except :
4141 oauth2_supported = False
4242
43- import requests
4443import httplib2
4544import json
4645import os
46+ import hashlib
47+ import hmac
48+ import time
4749import re
48-
4950from decimal import Decimal
51+ from warnings import warn
52+
53+ import requests
54+ from requests .auth import AuthBase
5055
5156from coinbase .config import COINBASE_ENDPOINT
5257from 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