Как мне подписать тело запроса. Запрос в методе __call__ объекта Auth? - PullRequest
0 голосов
/ 08 октября 2018

Я пытаюсь написать хороший помощник для кракена.Я хочу, чтобы он был как можно более автоматическим, поэтому необходимо:

  1. добавить одноразовый номер (time.time()*1000) в тело POST
  2. вычислить сигнатуру в теле POST
  3. поставить подпись в заголовках

Я написал очевидный код, основанный на этот ответ:

class KrakenAuth(AuthBase):                                                                                                                                         
    """a requests-module-compatible auth module for kraken.com"""                                                                                                                  
    def __init__(self, key, secret):                                                                                                                                
        self.api_key    = key                                                                                                                                       
        self.secret_key = secret                                                                                                                                    

    def __call__(self, request):                                                                                                                                    
        #print("Auth got a %r" % type(request))                                                                                                                      
        nonce = int(1000*time.time())                                                                                                                               
        request.data = getattr(request, 'data', {})                                                                                                                 
        request.data['nonce'] = nonce                                                                                                                               
        request.prepare()                                                                                                                                           

        message = request.path_url + hashlib.sha256(str(nonce) + request.body).digest()                                                                             
        hmac_key = base64.b64decode(self.secret_key)                                                                                                                
        signature = hmac.new(hmac_key, message, hashlib.sha512).digest()                                                                                            
        signature = base64.b64encode(signature)                                                                                                                     

        request.headers.update({                                                                                                                                    
            'API-Key': self.api_key,                                                                                                                                
            'API-Sign': signature                                                                                                                                   
        })                                                                                                                                                          
        return request                                         

и их явызов его (из метода-обертки для другого объекта), например:

def _request(self, method, url, **kwargs):
    if not self._auth:
        self._auth = KrakenAuth(key, secret)
    if 'auth' not in kwargs:
        kwargs['auth'] = self._auth
    return self._session.request(method, URL + url, **kwargs)                                                                                             

... но это не работает.Закомментированный оператор print() показывает, что он получает объект PreparedRequest, а не Request, и, следовательно, вызов request.prepare() является вызовом PreparedRequest.prepare, не делает ничего полезного , потому что естьнет request.data, потому что он уже был преобразован в атрибут body.

1 Ответ

0 голосов
/ 08 октября 2018

Вы не можете получить доступ к атрибуту data запроса, поскольку объект аутентификации применяется к requests.PreparedRequest() экземпляру , который не имеет атрибута .data .

Обычный поток вызовов Session.request() (используется всеми вызовами request.<method> и session.<method>) выглядит следующим образом:

  • ARequest() экземпляр создается со всеми теми же аргументами, что и исходный вызов
  • Запрос передается в Session.prepare_request(), который объединяет сохраненные в сеансе базовые значения с аргументами исходногосначала вызывается, затем
  • A PreparedRequest() экземпляр создается
  • Метод PreparedRequest.prepare() вызывается для этого подготовленного экземпляра запроса, передаваяобъединенные данные из экземпляра Request и сеанса.
  • Метод prepare() вызывает различные методы self.prepare_*, включая PreparedRequest.prepare_auth().
  • PreparedRequest.prepare_auth() вызывает auth(self), чтобы дать объекту аутентификации возможность прикрепить информацию к запросу.

К сожалению дляВы, ни в коем случае во время этого потока, исходное отображение data не будет доступно никому другому, кроме PreparedRequest.prepare() и PreparedRequest.prepare_body(), и в этих методах отображение является локальной переменной.Вы не можете получить к нему доступ из объекта аутентификации.

В этом случае вы можете выбрать:

  • К декодировать тело снова и вызвать prepare_body() с обновленным отображением.

  • Чтобы не использовать объект аутентификации, но использовать другой путь из моего ответа;явно создать подготовленный запрос и манипулировать сначала data.

  • Чтобы играть в веселый ад с помощью стека Python и извлекать локальные данные из метода prepare(), который на два кадра вверх.Я действительно не могу рекомендовать этот путь.

Чтобы метод аутентификации был инкапсулирован, я бы пошел с декодированием / перекодированием;последний достаточно прост, если повторно использовать PreparedRequest.prepare_body():

import base64
import hashlib
import hmac
import time
try:
    # Python 3
    from urllib.parse import parse_qs
except ImportError:
    # Python 2
    from urlparse import parse_qs

from requests import AuthBase

URL_ENCODED = 'application/x-www-form-urlencoded'


class KrakenAuth(AuthBase):
    """a requests-module-compatible auth module for kraken.com"""
    def __init__(self, key, secret):
        self.api_key    = key
        self.secret_key = secret

    def __call__(self, request):
        ctheader = request.headers.get('Content-Type')
        assert (
            request.method == 'POST' and (
                ctheader == URL_ENCODED or
                requests.headers.get('Content-Length') == '0'
            )
        ), "Must be a POST request using form data, or empty"

        # insert the nonce in the encoded body
        data = parse_qs(request.body)
        data['nonce'] = nonce
        request.prepare_body(data, None, None)

        body = request.body
        if not isinstance(body, bytes):   # Python 3
            body = body.encode('latin1')  # standard encoding for HTTP

        message = request.path_url + hashlib.sha256(b'%s%s' % (nonce, body)).digest()
        hmac_key = base64.b64decode(self.secret_key)
        signature = hmac.new(hmac_key, message, hashlib.sha512).digest()
        signature = base64.b64encode(signature)

        request.headers.update({
            'API-Key': self.api_key,
            'API-Sign': signature
        })
        return request
...