132 lines
3.9 KiB
Python
132 lines
3.9 KiB
Python
# -*- coding: utf-8 -*-
|
|
from base64 import urlsafe_b64decode, urlsafe_b64encode
|
|
from collections import OrderedDict
|
|
|
|
import six
|
|
|
|
|
|
class Base64(object):
|
|
"""
|
|
Helper class for base64 encoding/decoding
|
|
"""
|
|
|
|
@classmethod
|
|
def encode(cls, s):
|
|
"""
|
|
Encode binary string as base64. Remaining "=" characters are removed.
|
|
|
|
Parameters
|
|
----------
|
|
s: bytes
|
|
Bytes string to be encoded as base64
|
|
"""
|
|
assert isinstance(s, (six.binary_type, bytearray))
|
|
return urlsafe_b64encode(s).decode('ascii').rstrip('=')
|
|
|
|
@classmethod
|
|
def decode(cls, s):
|
|
"""
|
|
Decode unicode string from base64 where remaining "=" characters were stripped.
|
|
|
|
Parameters
|
|
----------
|
|
s: str
|
|
Unicode string to be decoded from base64
|
|
"""
|
|
assert isinstance(s, six.text_type)
|
|
return urlsafe_b64decode((s + '=' * (4 - len(s) % 4)).encode('ascii'))
|
|
|
|
|
|
class Encoder(object):
|
|
"""
|
|
Helper class for encoding/decoding SQRL response data.
|
|
"""
|
|
|
|
@classmethod
|
|
def base64_dumps(cls, data):
|
|
"""
|
|
Dumps given data into a single Base64 string.
|
|
|
|
Practically this is the same as :meth:`dumps` except :meth:`dumps`
|
|
can return multiline string for ``dict``. This method normalizes that
|
|
further by converting that multiline string to a single base64 encoded value.
|
|
|
|
Returns
|
|
-------
|
|
binary
|
|
Base64 encoded binary data of input ``data``
|
|
"""
|
|
if data and isinstance(data, dict):
|
|
return Base64.encode(cls.dumps(data).encode('ascii'))
|
|
return cls.dumps(data)
|
|
|
|
@classmethod
|
|
def dumps(cls, data):
|
|
"""
|
|
Recursively dumps given data to SQRL response format.
|
|
|
|
Before data is dumped out, it is normalized by using :meth:`.normalize`.
|
|
|
|
This dumps each data type as follows:
|
|
|
|
:``dict``: returns an ``\\r\\n`` multiline string. Each line is for a single key-pair
|
|
of format ``<key>=<dumped value>``.
|
|
:``list``: tilde (``~``) joined dumped list of values
|
|
:other: no operation
|
|
"""
|
|
data = cls.normalize(data)
|
|
|
|
if isinstance(data, dict):
|
|
return '\r\n'.join(
|
|
'{}={}'.format(k, cls.dumps(v))
|
|
for k, v in data.items()
|
|
) + '\r\n'
|
|
elif isinstance(data, (list, tuple)):
|
|
return '~'.join(cls.dumps(i) for i in data)
|
|
else:
|
|
return data
|
|
|
|
@classmethod
|
|
def normalize(cls, data):
|
|
"""
|
|
Recursively normalize data for encoding.
|
|
|
|
This encodes each data type as follows:
|
|
|
|
:``dict``: returns an ``OrderedDict`` where all values are recursively normalized.
|
|
Empty dict is normalized to empty string
|
|
:``list``: each value is recursively normalized
|
|
:``binary``: Base64 encode data
|
|
:``str``: no operation
|
|
:other: data is casted to string using ``__str__`` (or ``__unicode__``)
|
|
"""
|
|
if isinstance(data, dict):
|
|
if data:
|
|
return OrderedDict((
|
|
(k, cls.normalize(v))
|
|
for k, v in data.items()
|
|
))
|
|
else:
|
|
return ''
|
|
elif isinstance(data, (list, tuple)):
|
|
return [cls.dumps(i) for i in data]
|
|
elif isinstance(data, six.binary_type):
|
|
return Base64.encode(data)
|
|
elif isinstance(data, six.text_type):
|
|
return data
|
|
else:
|
|
return six.text_type(data)
|
|
|
|
def get_user_ip(request):
|
|
"""
|
|
Utility function for getting user's IP from request address.
|
|
|
|
This either returns the IP address from the ``request.REMOTE_ADDR``
|
|
or ``request.META'HTTP_X_REAL_IP']`` when request might of
|
|
been reverse proxied.
|
|
"""
|
|
return (
|
|
request.META.get('HTTP_X_REAL_IP')
|
|
or request.META['REMOTE_ADDR']
|
|
)
|