hexdb 1.6.0__tar.gz → 2.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hexdb
3
- Version: 1.6.0
3
+ Version: 2.0.0
4
4
  Summary: Client for HexDB, the hexagonal document database.
5
5
  Author: Dream In Hex
6
6
  License-Expression: Apache-2.0
@@ -26,7 +26,7 @@ import urllib.request
26
26
  from typing import Any, BinaryIO, Callable, Dict, Iterator, List, Optional, Sequence, Tuple, Union
27
27
 
28
28
  __all__ = ["HexDB", "HexDBError", "Tessellation", "Stream", "Files"]
29
- __version__ = "1.6.0"
29
+ __version__ = "2.0.0"
30
30
 
31
31
  Json = Any
32
32
  Document = Dict[str, Any]
@@ -46,6 +46,22 @@ class HexDBError(Exception):
46
46
  return f"HexDBError({self.status}, {self.code!r}, {self.message!r})"
47
47
 
48
48
 
49
+ class _SameOriginAuth(urllib.request.HTTPRedirectHandler):
50
+ """Follow redirects, but send the API key only to the server it was given for."""
51
+
52
+ def redirect_request(self, req, fp, code, msg, headers, newurl):
53
+ new = super().redirect_request(req, fp, code, msg, headers, newurl)
54
+ if new is not None and _origin(newurl) != _origin(req.full_url):
55
+ new.headers.pop("Authorization", None)
56
+ new.unredirected_hdrs.pop("Authorization", None)
57
+ return new
58
+
59
+
60
+ def _origin(url: str) -> Tuple[str, str]:
61
+ parts = urllib.parse.urlsplit(url)
62
+ return (parts.scheme.lower(), parts.netloc.lower())
63
+
64
+
49
65
  def _quote(text: str) -> str:
50
66
  return urllib.parse.quote(text, safe="")
51
67
 
@@ -67,6 +83,10 @@ class HexDB:
67
83
  self.retries = retries
68
84
  self.timeout = timeout
69
85
  self._context = ssl.create_default_context(cafile=ca_file) if ca_file else None
86
+ handlers: List[Any] = [_SameOriginAuth()]
87
+ if self._context is not None:
88
+ handlers.append(urllib.request.HTTPSHandler(context=self._context))
89
+ self._opener = urllib.request.build_opener(*handlers)
70
90
 
71
91
  # -- transport ---------------------------------------------------------
72
92
 
@@ -84,7 +104,7 @@ class HexDB:
84
104
  while True:
85
105
  req = urllib.request.Request(self.base + path, data=data, method=method, headers=headers)
86
106
  try:
87
- with urllib.request.urlopen(req, timeout=timeout or self.timeout, context=self._context) as response:
107
+ with self._opener.open(req, timeout=timeout or self.timeout) as response:
88
108
  if response.status == 204:
89
109
  return None
90
110
  text = response.read().decode("utf-8")
@@ -115,7 +135,7 @@ class HexDB:
115
135
  headers["Authorization"] = f"Bearer {self.token}"
116
136
  req = urllib.request.Request(self.base + path, data=data, method=method, headers=headers)
117
137
  try:
118
- response = urllib.request.urlopen(req, timeout=timeout or self.timeout, context=self._context)
138
+ response = self._opener.open(req, timeout=timeout or self.timeout)
119
139
  except urllib.error.HTTPError as e:
120
140
  text = e.read().decode("utf-8", "replace")
121
141
  e.close()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hexdb
3
- Version: 1.6.0
3
+ Version: 2.0.0
4
4
  Summary: Client for HexDB, the hexagonal document database.
5
5
  Author: Dream In Hex
6
6
  License-Expression: Apache-2.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "hexdb"
7
- version = "1.6.0"
7
+ version = "2.0.0"
8
8
  description = "Client for HexDB, the hexagonal document database."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -146,3 +146,64 @@ class ClientTest(unittest.TestCase):
146
146
 
147
147
  if __name__ == "__main__":
148
148
  unittest.main()
149
+
150
+
151
+ class RedirectTests(unittest.TestCase):
152
+ """The API key isn't sent to another server on a redirect."""
153
+
154
+ def test_api_key_stays_with_its_server(self):
155
+ import http.server
156
+ import threading
157
+
158
+ seen = {}
159
+
160
+ def server(handler):
161
+ httpd = http.server.HTTPServer(("127.0.0.1", 0), handler)
162
+ threading.Thread(target=httpd.serve_forever, daemon=True).start()
163
+ return httpd
164
+
165
+ class Other(http.server.BaseHTTPRequestHandler):
166
+ def do_GET(self):
167
+ seen["other"] = self.headers.get("Authorization")
168
+ self.send_response(200)
169
+ self.send_header("Content-Type", "application/json")
170
+ self.end_headers()
171
+ self.wfile.write(b"{}")
172
+
173
+ def log_message(self, *args):
174
+ pass
175
+
176
+ other = server(Other)
177
+ target = "http://127.0.0.1:%d/landing" % other.server_address[1]
178
+
179
+ class First(http.server.BaseHTTPRequestHandler):
180
+ def do_GET(self):
181
+ seen.setdefault("first", self.headers.get("Authorization"))
182
+ if self.path == "/same":
183
+ self.send_response(302)
184
+ self.send_header("Location", "/landing")
185
+ elif self.path == "/landing":
186
+ seen["same"] = self.headers.get("Authorization")
187
+ self.send_response(200)
188
+ self.send_header("Content-Type", "application/json")
189
+ self.end_headers()
190
+ self.wfile.write(b"{}")
191
+ return
192
+ else:
193
+ self.send_response(302)
194
+ self.send_header("Location", target)
195
+ self.end_headers()
196
+
197
+ def log_message(self, *args):
198
+ pass
199
+
200
+ first = server(First)
201
+ client = HexDB("http://127.0.0.1:%d" % first.server_address[1], "hxk_secret")
202
+ client.request("GET", "/same")
203
+ self.assertEqual(seen["same"], "Bearer hxk_secret", "kept on the same server")
204
+ client.request("GET", "/elsewhere")
205
+ self.assertEqual(seen["first"], "Bearer hxk_secret")
206
+ self.assertIsNone(seen["other"], "dropped for another server")
207
+ first.shutdown()
208
+ other.shutdown()
209
+
File without changes
File without changes
File without changes