Как мне подписать тело запроса. Запрос в методе __call__ объекта Auth?

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

  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 ответ

Решение

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

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

  • Request() экземпляр создается со всеми теми же аргументами, что и исходный вызов
  • Запрос передан Session.prepare_request(), который сначала объединяет сохраненные в сеансе базовые значения с аргументами исходного вызова, затем
  • 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
Другие вопросы по тегам