У меня есть код из функции единого входа

from urllib.parse import unquote
import base64

payload = unquote(payload)
print(payload)
print(type(payload))
decoded = base64.decodestring(payload)

Decodestring жалуется, что я дал ему строку вместо байтов ...

  File "/Users/Jeff/Development/langalang/proj/discourse/views.py", line 38, in sso
    decoded = base64.decodestring(payload)
  File "/Users/Jeff/.virtualenvs/proj/lib/python3.6/base64.py", line 559, in decodestring
    return decodebytes(s)
  File "/Users/Jeff/.virtualenvs/proj/lib/python3.6/base64.py", line 551, in decodebytes
    _input_type_check(s)
  File "/Users/Jeff/.virtualenvs/proj/lib/python3.6/base64.py", line 520, in _input_type_check
    raise TypeError(msg) from err
TypeError: expected bytes-like object, not str

Это нормально, но когда я смотрю на то, что мои операторы печати печатали на терминал, я вижу это ...

b'bm9uY2U9NDI5NDg5OTU0NjU4MjAzODkyNTI=\n'
<class 'str'>

Кажется, что это строка байтов, но потом говорится, что это строка.

Что здесь происходит?

Если я добавлю encode() в конец объявления полезной нагрузки, я вижу это ...

payload = unquote(payload).encode()

b"b'bm9uY2U9NDQxMTQ4MzIyNDMwNjU3MjcyMDM=\\n'"
<class 'bytes'>

РЕДАКТИРОВАТЬ: добавив метод, который делает полезную нагрузку

@patch("discourse.views.HttpResponseRedirect")
def test_sso_success(self, mock_redirect):
    """Test for the sso view"""

    # Generating a random number, encoding for url, signing it with a hash
    nonce = "".join([str(random.randint(0, 9)) for i in range(20)])
    # The sso payload needs to be a dict of params
    params = {"nonce": nonce}
    payload = base64.encodestring(urlencode(params).encode())
    print(payload.decode() + " tests")

    key = settings.SSO_SECRET
    h = hmac.new(key.encode(), payload, digestmod=hashlib.sha256)
    signature = h.hexdigest()

    url = reverse("discourse:sso") + "?sso=%s&sig=%s" % (payload, signature)
    req = self.rf.get(url)
    req.user = self.user
    response = sso(req)
    self.assertTrue(mock_redirect.called)
0
Joff 10 Янв 2017 в 09:41

3 ответа

Лучший ответ

Поскольку вы payload сгенерированы этим base64.encodestring(s), который по документации:

Кодировать байтовоподобный объект s, который может содержать произвольные двоичные данные, и возвращать байты, содержащие данные в кодировке base64, с символами новой строки (b '\ n'), вставляемыми после каждых 76 байтов выходных данных, и гарантируя наличие завершающей строки согласно RFC 2045 (MIME).

Затем вы делаете urllib.parse.unquote для последовательности байтов, которая состоит из символов ASCII. В этот момент вы получили префикс b' к вашей строке, так как расстановка кавычек запускает конструктор str над массивом полезных данных. В качестве запроса вы получаете str вместо байтов, который, кроме того, недопустим в кодировке base64.

1
Alexey Smirnov 10 Янв 2017 в 07:20

Исходная ошибка позволяла думать об этом, и отображение закодированной строки подтверждает это: ваша payload строка является строкой Юникода, которая начинается с префикса "b'" и заканчивается одним "'".

Такая строка обычно создается с помощью repr вызова:

>>> b = b'abc'   # b is a byte string
>>> r = repr(b)  # by construction r is a unicode string
>>> print(r)     # will look like a byte string
b'abc'
>>> print(b)     # what is printed for a true byte string
abc

Вы можете вернуться к истинной байтовой строке с помощью literal_eval:

>>> b2 = ast.literal_eval(r)
>>> type(b2)
<class 'bytes'>
>>> b == b2
True

Но возврат - это только обходной путь, и вы должны отслеживать в своем коде, где вы строите представление байтовой строки.

0
Serge Ballesta 10 Янв 2017 в 07:16

кажется, что это строка байтов, но потом говорится, что это строка.

Похоже, у вас здесь строка выглядит следующим образом: "b'bm9uY2U9NDQxMTQ4MzIyNDMwNjU3MjcyMDM=\\n'", так что ведущий b - это не байтовый литерал, а просто часть значения строки.

Поэтому вам нужно удалить эти символы перед тем, как передать их в декодер base64:

from urllib.parse import unquote, quote_from_bytes
import base64

payload = unquote(payload)
print(payload[2:-1])
enc = base64.decodebytes(payload[2:-1].encode())
print(enc)
1
neverwalkaloner 10 Янв 2017 в 07:14