summaryrefslogtreecommitdiff
path: root/macaroonbakery/_utils/__init__.py
blob: 977cdbed2778c39cb86bafebff9b6ee5c43a0c9e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
# Copyright 2017 Canonical Ltd.
# Licensed under the LGPLv3, see LICENCE file for details.
import base64
import binascii
import ipaddress
import json
import webbrowser
from datetime import datetime

import six
from pymacaroons import Macaroon
from pymacaroons.serializers import json_serializer

import six.moves.http_cookiejar as http_cookiejar
from six.moves.urllib.parse import urlparse


def to_bytes(s):
    '''Return s as a bytes type, using utf-8 encoding if necessary.
    @param s string or bytes
    @return bytes
    '''
    if isinstance(s, six.binary_type):
        return s
    if isinstance(s, six.string_types):
        return s.encode('utf-8')
    raise TypeError('want string or bytes, got {}', type(s))


def macaroon_from_dict(json_macaroon):
    '''Return a pymacaroons.Macaroon object from the given
    JSON-deserialized dict.

    @param JSON-encoded macaroon as dict
    @return the deserialized macaroon object.
    '''
    return Macaroon.deserialize(json.dumps(json_macaroon),
                                json_serializer.JsonSerializer())


def macaroon_to_dict(macaroon):
    '''Turn macaroon into JSON-serializable dict object
    @param pymacaroons.Macaroon.
    '''
    return json.loads(macaroon.serialize(json_serializer.JsonSerializer()))


def macaroon_to_json_string(macaroon):
    '''Serialize macaroon object to a JSON-encoded string.

    @param macaroon object to be serialized.
    @return a string serialization form of the macaroon.
    '''
    return macaroon.serialize(json_serializer.JsonSerializer())


def _add_base64_padding(b):
    '''Add padding to base64 encoded bytes.

    pymacaroons does not give padded base64 bytes from serialization.

    @param bytes b to be padded.
    @return a padded bytes.
    '''
    return b + b'=' * (-len(b) % 4)


def _remove_base64_padding(b):
    '''Remove padding from base64 encoded bytes.

    pymacaroons does not give padded base64 bytes from serialization.

    @param bytes b to be padded.
    @return a padded bytes.
    '''
    return b.rstrip(b'=')


def b64decode(s):
    '''Base64 decodes a base64-encoded string in URL-safe
    or normal format, with or without padding.
    The argument may be string or bytes.

    @param s bytes decode
    @return bytes decoded
    @raises ValueError on failure
    '''
    # add padding if necessary.
    s = to_bytes(s)
    if not s.endswith(b'='):
        s = s + b'=' * (-len(s) % 4)
    try:
        if '_' or '-' in s:
            return base64.urlsafe_b64decode(s)
        else:
            return base64.b64decode(s)
    except (TypeError, binascii.Error) as e:
        raise ValueError(str(e))


def raw_urlsafe_b64encode(b):
    '''Base64 encode using URL-safe encoding with padding removed.

    @param b bytes to decode
    @return bytes decoded
    '''
    b = to_bytes(b)
    b = base64.urlsafe_b64encode(b)
    b = b.rstrip(b'=')  # strip padding
    return b


def visit_page_with_browser(visit_url):
    '''Open a browser so the user can validate its identity.

    @param visit_url: where to prove your identity.
    '''
    webbrowser.open(visit_url, new=1)
    print('Opening an authorization web page in your browser.')
    print('If it does not open, please open this URL:\n', visit_url, '\n')


def cookie(
        url,
        name,
        value,
        expires=None):
    '''Return a new Cookie using a slightly more
    friendly API than that provided by six.moves.http_cookiejar

    @param name The cookie name {str}
    @param value The cookie value {str}
    @param url The URL path of the cookie {str}
    @param expires The expiry time of the cookie {datetime}. If provided,
        it must be a naive timestamp in UTC.
    '''
    u = urlparse(url)
    domain = u.hostname
    if '.' not in domain and not _is_ip_addr(domain):
        domain += ".local"
    port = str(u.port) if u.port is not None else None
    secure = u.scheme == 'https'
    if expires is not None:
        if expires.tzinfo is not None:
            raise ValueError('Cookie expiration must be a naive datetime')
        expires = (expires - datetime(1970, 1, 1)).total_seconds()
    return http_cookiejar.Cookie(
        version=0,
        name=name,
        value=value,
        port=port,
        port_specified=port is not None,
        domain=domain,
        domain_specified=True,
        domain_initial_dot=False,
        path=u.path,
        path_specified=True,
        secure=secure,
        expires=expires,
        discard=False,
        comment=None,
        comment_url=None,
        rest=None,
        rfc2109=False,
    )


def _is_ip_addr(h):
    if six.PY2:
        # the python2.7 backport of ipaddr needs a bytestring passed in
        try:
            h = h.decode('ascii')
        except UnicodeDecodeError:
            # If there are non-ascii chars it's not an address anyway
            return False
    try:
        ipaddress.ip_address(h)
    except ValueError:
        return False
    return True