txhttputil 1.4.9__py3-none-any.whl
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.
- txhttputil/__init__.py +9 -0
- txhttputil/downloader/HttpFileDownloader.py +102 -0
- txhttputil/downloader/HttpResourceProxy.py +151 -0
- txhttputil/downloader/__init__.py +8 -0
- txhttputil/login_page/LoginElement.py +45 -0
- txhttputil/login_page/LoginTemplate.xml +63 -0
- txhttputil/login_page/__init__.py +8 -0
- txhttputil/site/AuthCredentials.py +38 -0
- txhttputil/site/AuthResource.py +58 -0
- txhttputil/site/AuthSessionWrapper.py +224 -0
- txhttputil/site/AuthUserDetails.py +51 -0
- txhttputil/site/BasicResource.py +155 -0
- txhttputil/site/BasicResourceTest.py +112 -0
- txhttputil/site/FileUnderlayResource.py +188 -0
- txhttputil/site/FileUnderlayResourceTest.py +108 -0
- txhttputil/site/FileUploadRequest.py +250 -0
- txhttputil/site/RedirectToHttpsResource.py +43 -0
- txhttputil/site/RedirectionRule.py +24 -0
- txhttputil/site/ResourceCreator.py +12 -0
- txhttputil/site/RootResource.py +29 -0
- txhttputil/site/RootSiteTest.py +8 -0
- txhttputil/site/SiteUtil.py +157 -0
- txhttputil/site/StaticFileResource.py +163 -0
- txhttputil/site/__init__.py +8 -0
- txhttputil/util/DeferUtil.py +54 -0
- txhttputil/util/ModuleUtil.py +23 -0
- txhttputil/util/PemUtil.py +750 -0
- txhttputil/util/SslUtil.py +396 -0
- txhttputil/util/__init__.py +8 -0
- txhttputil-1.4.9.dist-info/LICENSE +22 -0
- txhttputil-1.4.9.dist-info/METADATA +82 -0
- txhttputil-1.4.9.dist-info/RECORD +34 -0
- txhttputil-1.4.9.dist-info/WHEEL +5 -0
- txhttputil-1.4.9.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
# Copyright (c) 2016-2026 Flying Ion Pty Ltd
|
|
2
|
+
#
|
|
3
|
+
# This software is open source; the MIT license applies.
|
|
4
|
+
# See LICENSE for the full license text.
|
|
5
|
+
#
|
|
6
|
+
# Website : https://flyingion.com
|
|
7
|
+
# Support : contact@flyingion.com
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
from binascii import hexlify
|
|
11
|
+
from typing import List, Set, Optional
|
|
12
|
+
|
|
13
|
+
import cryptography
|
|
14
|
+
|
|
15
|
+
import OpenSSL
|
|
16
|
+
from OpenSSL import crypto
|
|
17
|
+
from OpenSSL import SSL
|
|
18
|
+
from OpenSSL.crypto import FILETYPE_PEM
|
|
19
|
+
from twisted.internet._sslverify import (
|
|
20
|
+
PrivateCertificate,
|
|
21
|
+
KeyPair,
|
|
22
|
+
Certificate,
|
|
23
|
+
_setAcceptableProtocols,
|
|
24
|
+
ClientTLSOptions,
|
|
25
|
+
OpenSSLCertificateOptions,
|
|
26
|
+
IOpenSSLTrustRoot,
|
|
27
|
+
)
|
|
28
|
+
from twisted.internet.interfaces import IOpenSSLContextFactory
|
|
29
|
+
from twisted.internet.ssl import CertificateOptions, TLSVersion
|
|
30
|
+
from twisted.python.randbytes import secureRandom
|
|
31
|
+
|
|
32
|
+
from twisted.web.client import _requireSSL
|
|
33
|
+
from twisted.internet._sslverify import Certificate as TxCertificate
|
|
34
|
+
from twisted.web.iweb import IPolicyForHTTPS
|
|
35
|
+
from zope.interface import implementer
|
|
36
|
+
|
|
37
|
+
from txhttputil.util.PemUtil import (
|
|
38
|
+
parseTrustRootFromBundle,
|
|
39
|
+
parsePemBundleForClient,
|
|
40
|
+
PrivateKeyWithFullChain,
|
|
41
|
+
parsePemBundleForTrustedPeers,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
logger = logging.getLogger(__name__)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@implementer(IOpenSSLContextFactory)
|
|
48
|
+
class _CertificateOptions(CertificateOptions):
|
|
49
|
+
trustedPeers: List[Certificate] = None
|
|
50
|
+
trustedPeersASN1Bytes: Set[bytes] = None
|
|
51
|
+
|
|
52
|
+
def _makeContext(self):
|
|
53
|
+
ctx = self._contextFactory(self.method)
|
|
54
|
+
ctx.set_options(self._options)
|
|
55
|
+
ctx.set_mode(self._mode)
|
|
56
|
+
|
|
57
|
+
if self.certificate is not None and self.privateKey is not None:
|
|
58
|
+
ctx.use_certificate(self.certificate)
|
|
59
|
+
ctx.use_privatekey(self.privateKey)
|
|
60
|
+
for extraCert in self.extraCertChain:
|
|
61
|
+
ctx.add_extra_chain_cert(extraCert)
|
|
62
|
+
# Sanity check
|
|
63
|
+
ctx.check_privatekey()
|
|
64
|
+
|
|
65
|
+
verifyFlags = SSL.VERIFY_NONE
|
|
66
|
+
if self.verify:
|
|
67
|
+
verifyFlags = SSL.VERIFY_PEER
|
|
68
|
+
if self.requireCertificate:
|
|
69
|
+
verifyFlags |= SSL.VERIFY_FAIL_IF_NO_PEER_CERT
|
|
70
|
+
if self.verifyOnce:
|
|
71
|
+
verifyFlags |= SSL.VERIFY_CLIENT_ONCE
|
|
72
|
+
self.trustRoot._addCACertsToContext(ctx)
|
|
73
|
+
|
|
74
|
+
def verifyCallback(
|
|
75
|
+
conn: SSL.Connection,
|
|
76
|
+
cert: crypto.X509,
|
|
77
|
+
errno: int,
|
|
78
|
+
depth: int,
|
|
79
|
+
preverify_ok: int,
|
|
80
|
+
):
|
|
81
|
+
# https://www.openssl.org/docs/man1.1.1/man3/X509_STORE_CTX_verify_cb.html
|
|
82
|
+
# The ok parameter to the callback indicates the value
|
|
83
|
+
# the callback should return to retain the default behaviour.
|
|
84
|
+
# If it is zero then an error condition is indicated.
|
|
85
|
+
# If it is 1 then no error occurred.
|
|
86
|
+
# If the flag X509_V_FLAG_NOTIFY_POLICY is set
|
|
87
|
+
# then ok is set to 2 to indicate the policy checking is complete.
|
|
88
|
+
|
|
89
|
+
FAIL = 0
|
|
90
|
+
|
|
91
|
+
# only check peer's cert - not other certs in trust chain
|
|
92
|
+
# e.g. cert -> intermediate A -> intermediate B -> root CA
|
|
93
|
+
# depth 0 1 2 3
|
|
94
|
+
if depth != 0:
|
|
95
|
+
# pass through the result
|
|
96
|
+
return preverify_ok
|
|
97
|
+
|
|
98
|
+
# if peer verify is disabled
|
|
99
|
+
if self.trustedPeersASN1Bytes is None:
|
|
100
|
+
# pass through the result
|
|
101
|
+
return preverify_ok
|
|
102
|
+
|
|
103
|
+
# if verify failed
|
|
104
|
+
if preverify_ok == FAIL:
|
|
105
|
+
# pass through the result
|
|
106
|
+
return preverify_ok
|
|
107
|
+
|
|
108
|
+
# get PEM from public key
|
|
109
|
+
sessionPeerPublickeyBytes = crypto.dump_publickey(
|
|
110
|
+
crypto.FILETYPE_ASN1, cert.get_pubkey()
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
# check if peer is in trust list
|
|
114
|
+
if sessionPeerPublickeyBytes not in self.trustedPeersASN1Bytes:
|
|
115
|
+
logger.error(
|
|
116
|
+
f"mTLS peer verify failed at depth '{depth}', presenting "
|
|
117
|
+
f"certificate '{cert.get_subject()}'"
|
|
118
|
+
)
|
|
119
|
+
return FAIL
|
|
120
|
+
|
|
121
|
+
# peer verified, pass through the result
|
|
122
|
+
logger.debug(
|
|
123
|
+
f"mTLS peer verify success at depth '{depth}', presenting "
|
|
124
|
+
f"certificate '{cert.get_subject()}'"
|
|
125
|
+
)
|
|
126
|
+
return preverify_ok
|
|
127
|
+
|
|
128
|
+
ctx.set_verify(verifyFlags, verifyCallback)
|
|
129
|
+
if self.verifyDepth is not None:
|
|
130
|
+
ctx.set_verify_depth(self.verifyDepth)
|
|
131
|
+
|
|
132
|
+
# Until we know what's going on with
|
|
133
|
+
# https://twistedmatrix.com/trac/ticket/9764 let's be conservative
|
|
134
|
+
# in naming this; ASCII-only, short, as the recommended value (a
|
|
135
|
+
# hostname) might be:
|
|
136
|
+
sessionIDContext = hexlify(secureRandom(7))
|
|
137
|
+
# Note that this doesn't actually set the session ID (which had
|
|
138
|
+
# better be per-connection anyway!):
|
|
139
|
+
# https://github.com/pyca/pyopenssl/issues/845
|
|
140
|
+
|
|
141
|
+
# This is set unconditionally because it's apparently required for
|
|
142
|
+
# client certificates to work:
|
|
143
|
+
# https://www.openssl.org/docs/man1.1.1/man3/SSL_CTX_set_session_id_context.html
|
|
144
|
+
ctx.set_session_id(sessionIDContext)
|
|
145
|
+
|
|
146
|
+
if self.enableSessions:
|
|
147
|
+
ctx.set_session_cache_mode(SSL.SESS_CACHE_SERVER)
|
|
148
|
+
else:
|
|
149
|
+
ctx.set_session_cache_mode(SSL.SESS_CACHE_OFF)
|
|
150
|
+
|
|
151
|
+
if self.dhParameters:
|
|
152
|
+
ctx.load_tmp_dh(self.dhParameters._dhFile.path)
|
|
153
|
+
ctx.set_cipher_list(self._cipherString.encode("ascii"))
|
|
154
|
+
|
|
155
|
+
self._ecChooser.configureECDHCurve(ctx)
|
|
156
|
+
|
|
157
|
+
if self._acceptableProtocols:
|
|
158
|
+
# Try to set NPN and ALPN. _acceptableProtocols cannot be set by
|
|
159
|
+
# the constructor unless at least one mechanism is supported.
|
|
160
|
+
_setAcceptableProtocols(ctx, self._acceptableProtocols)
|
|
161
|
+
|
|
162
|
+
return ctx
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
@implementer(IOpenSSLTrustRoot)
|
|
166
|
+
class _OpenSSLCertificateAuthorities:
|
|
167
|
+
"""
|
|
168
|
+
Trust an explicitly specified set of certificates, represented by a list of
|
|
169
|
+
L{OpenSSL.crypto.X509} objects.
|
|
170
|
+
"""
|
|
171
|
+
|
|
172
|
+
def __init__(self, caCerts: List[TxCertificate]):
|
|
173
|
+
"""
|
|
174
|
+
@param caCerts: The certificate authorities to trust when using this
|
|
175
|
+
object as a C{trustRoot} for L{OpenSSLCertificateOptions}.
|
|
176
|
+
@type caCerts: L{list} of L{OpenSSL.crypto.X509}
|
|
177
|
+
"""
|
|
178
|
+
self._caCerts = caCerts
|
|
179
|
+
|
|
180
|
+
def _addCACertsToContext(self, context):
|
|
181
|
+
store = context.get_cert_store()
|
|
182
|
+
for cert in self._caCerts:
|
|
183
|
+
# convert twisted Certificate objects
|
|
184
|
+
# to OpenSSL.crypto.X509 objects
|
|
185
|
+
# via `cryptography` package
|
|
186
|
+
opensslCert = OpenSSL.crypto.X509.from_cryptography(
|
|
187
|
+
cryptography.x509.load_pem_x509_certificate(cert.dumpPEM())
|
|
188
|
+
)
|
|
189
|
+
store.add_cert(opensslCert)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def buildCertificateOptionsForTwisted(
|
|
193
|
+
privateKeyWithFullChain: PrivateKeyWithFullChain,
|
|
194
|
+
trustRoot=None,
|
|
195
|
+
trustedPeerCertificates: List[Certificate] = None,
|
|
196
|
+
raiseMinimumTo=TLSVersion.TLSv1_2,
|
|
197
|
+
lowerMaximumSecurityTo=TLSVersion.TLSv1_3,
|
|
198
|
+
acceptableProtocols=None,
|
|
199
|
+
dhParameters=None,
|
|
200
|
+
) -> CertificateOptions:
|
|
201
|
+
twistedLoadedChain = PrivateKeyWithFullChain(
|
|
202
|
+
privateKey=KeyPair.load(
|
|
203
|
+
privateKeyWithFullChain.privateKey.as_bytes(), FILETYPE_PEM
|
|
204
|
+
).original,
|
|
205
|
+
certificate=Certificate.loadPEM(
|
|
206
|
+
privateKeyWithFullChain.certificate.as_bytes()
|
|
207
|
+
).original,
|
|
208
|
+
intermediateCAs=[
|
|
209
|
+
Certificate.loadPEM(intermediateCA.as_bytes()).original
|
|
210
|
+
for intermediateCA in privateKeyWithFullChain.intermediateCAs
|
|
211
|
+
],
|
|
212
|
+
rootCA=Certificate.loadPEM(
|
|
213
|
+
privateKeyWithFullChain.rootCA.as_bytes()
|
|
214
|
+
).original,
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
if acceptableProtocols is None:
|
|
218
|
+
acceptableProtocols = [b"http/1.1"]
|
|
219
|
+
|
|
220
|
+
if trustedPeerCertificates is not None:
|
|
221
|
+
# load PEMs to ASN1 bytes
|
|
222
|
+
_CertificateOptions.trustedPeers = trustedPeerCertificates
|
|
223
|
+
_CertificateOptions.trustedPeersASN1Bytes = set([])
|
|
224
|
+
for trustedPeer in _CertificateOptions.trustedPeers:
|
|
225
|
+
# load as a pyopenssl cert
|
|
226
|
+
certificate: crypto.X509 = crypto.load_certificate(
|
|
227
|
+
crypto.FILETYPE_PEM, trustedPeer.as_bytes()
|
|
228
|
+
)
|
|
229
|
+
# get publickey in ASN1 bytes
|
|
230
|
+
publicKey: bytes = crypto.dump_publickey(
|
|
231
|
+
crypto.FILETYPE_ASN1, certificate.get_pubkey()
|
|
232
|
+
)
|
|
233
|
+
# add publickey to trusted peers
|
|
234
|
+
_CertificateOptions.trustedPeersASN1Bytes.add(publicKey)
|
|
235
|
+
|
|
236
|
+
return _CertificateOptions(
|
|
237
|
+
privateKey=twistedLoadedChain.privateKey,
|
|
238
|
+
certificate=twistedLoadedChain.certificate,
|
|
239
|
+
extraCertChain=twistedLoadedChain.intermediateCAs,
|
|
240
|
+
trustRoot=trustRoot,
|
|
241
|
+
raiseMinimumTo=raiseMinimumTo,
|
|
242
|
+
lowerMaximumSecurityTo=lowerMaximumSecurityTo,
|
|
243
|
+
acceptableProtocols=acceptableProtocols,
|
|
244
|
+
dhParameters=dhParameters,
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
@implementer(IPolicyForHTTPS)
|
|
249
|
+
class MutualAuthenticationPolicyForHTTPS:
|
|
250
|
+
def __init__(
|
|
251
|
+
self,
|
|
252
|
+
clientCertificate: PrivateCertificate = None,
|
|
253
|
+
trustRoot=None,
|
|
254
|
+
trustedPeers=None,
|
|
255
|
+
):
|
|
256
|
+
self._extraCertificateOptions = {}
|
|
257
|
+
|
|
258
|
+
if clientCertificate is not None:
|
|
259
|
+
self._clientCertificate = clientCertificate
|
|
260
|
+
self._extraCertificateOptions.update(
|
|
261
|
+
privateKey=clientCertificate.privateKey.original,
|
|
262
|
+
certificate=clientCertificate.original,
|
|
263
|
+
)
|
|
264
|
+
self._trustRoot = trustRoot
|
|
265
|
+
self._trustedPeers = trustedPeers
|
|
266
|
+
self._trustedPeersASN1Bytes = None
|
|
267
|
+
|
|
268
|
+
if trustedPeers is not None:
|
|
269
|
+
self._trustedPeersASN1Bytes = set([])
|
|
270
|
+
for trustedPeer in self._trustedPeers:
|
|
271
|
+
# load as a pyopenssl cert
|
|
272
|
+
certificate: crypto.X509 = crypto.load_certificate(
|
|
273
|
+
crypto.FILETYPE_PEM, trustedPeer.as_bytes()
|
|
274
|
+
)
|
|
275
|
+
# get publickey in ASN1 bytes
|
|
276
|
+
publicKey: bytes = crypto.dump_publickey(
|
|
277
|
+
crypto.FILETYPE_ASN1, certificate.get_pubkey()
|
|
278
|
+
)
|
|
279
|
+
# add publickey to trusted peers
|
|
280
|
+
self._trustedPeersASN1Bytes.add(publicKey)
|
|
281
|
+
|
|
282
|
+
# verify enabled
|
|
283
|
+
self._verifyFlags = SSL.VERIFY_PEER
|
|
284
|
+
# verifyOnce:
|
|
285
|
+
self._verifyFlags |= SSL.VERIFY_CLIENT_ONCE
|
|
286
|
+
if self._trustRoot or self._trustedPeers:
|
|
287
|
+
# verify mTLS peer
|
|
288
|
+
self._verifyFlags |= SSL.VERIFY_FAIL_IF_NO_PEER_CERT
|
|
289
|
+
|
|
290
|
+
@_requireSSL
|
|
291
|
+
def creatorForNetloc(self, hostname, port):
|
|
292
|
+
certificateOptions = OpenSSLCertificateOptions(
|
|
293
|
+
trustRoot=self._trustRoot,
|
|
294
|
+
acceptableProtocols=[b"http/1.1"],
|
|
295
|
+
raiseMinimumTo=TLSVersion.TLSv1_2,
|
|
296
|
+
lowerMaximumSecurityTo=TLSVersion.TLSv1_3,
|
|
297
|
+
**self._extraCertificateOptions,
|
|
298
|
+
)
|
|
299
|
+
context = certificateOptions.getContext()
|
|
300
|
+
|
|
301
|
+
def verifyCallback(
|
|
302
|
+
conn: SSL.Connection,
|
|
303
|
+
cert: crypto.X509,
|
|
304
|
+
errno: int,
|
|
305
|
+
depth: int,
|
|
306
|
+
preverify_ok: int,
|
|
307
|
+
):
|
|
308
|
+
# same as `_CertificateOptions._makeContext.verifyCallback`
|
|
309
|
+
|
|
310
|
+
# https://www.openssl.org/docs/man1.1.1/man3/X509_STORE_CTX_verify_cb.html
|
|
311
|
+
# The ok parameter to the callback indicates the value
|
|
312
|
+
# the callback should return to retain the default behaviour.
|
|
313
|
+
# If it is zero then an error condition is indicated.
|
|
314
|
+
# If it is 1 then no error occurred.
|
|
315
|
+
# If the flag X509_V_FLAG_NOTIFY_POLICY is set
|
|
316
|
+
# then ok is set to 2 to indicate the policy checking is complete.
|
|
317
|
+
|
|
318
|
+
FAIL = 0
|
|
319
|
+
|
|
320
|
+
# only check peer's cert - not other certs in trust chain
|
|
321
|
+
# e.g. cert -> intermediate A -> intermediate B -> root CA
|
|
322
|
+
# depth 0 1 2 3
|
|
323
|
+
if depth != 0:
|
|
324
|
+
# pass through the result
|
|
325
|
+
return preverify_ok
|
|
326
|
+
|
|
327
|
+
# if peer verify is disabled
|
|
328
|
+
if self._trustedPeersASN1Bytes is None:
|
|
329
|
+
# pass through the result
|
|
330
|
+
return preverify_ok
|
|
331
|
+
|
|
332
|
+
# if verify failed
|
|
333
|
+
if preverify_ok == FAIL:
|
|
334
|
+
# pass through the result
|
|
335
|
+
return preverify_ok
|
|
336
|
+
|
|
337
|
+
# get PEM from public key
|
|
338
|
+
sessionPeerPublickeyBytes = crypto.dump_publickey(
|
|
339
|
+
crypto.FILETYPE_ASN1, cert.get_pubkey()
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
# check if peer is in trust list
|
|
343
|
+
if sessionPeerPublickeyBytes not in self._trustedPeersASN1Bytes:
|
|
344
|
+
logger.error(
|
|
345
|
+
f"mTLS peer verify failed at depth '{depth}', presenting "
|
|
346
|
+
f"certificate '{cert.get_subject()}'"
|
|
347
|
+
)
|
|
348
|
+
return FAIL
|
|
349
|
+
|
|
350
|
+
# peer verified, pass through the result
|
|
351
|
+
logger.debug(
|
|
352
|
+
f"mTLS peer verify success at depth '{depth}', presenting "
|
|
353
|
+
f"certificate '{cert.get_subject()}'"
|
|
354
|
+
)
|
|
355
|
+
return preverify_ok
|
|
356
|
+
|
|
357
|
+
context.set_verify(self._verifyFlags, verifyCallback)
|
|
358
|
+
return ClientTLSOptions(hostname, context)
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def buildSSLContextFactoryForMutualTLS(
|
|
362
|
+
sslClientCertificateBundleFilePath: Optional[str],
|
|
363
|
+
sslTrustedPeerCertificateAuthorityBundleFilePath: Optional[str],
|
|
364
|
+
sslMutualTLSTrustedPeerCertificateBundleFilePath: Optional[str],
|
|
365
|
+
) -> MutualAuthenticationPolicyForHTTPS:
|
|
366
|
+
trustRoot = None
|
|
367
|
+
if sslTrustedPeerCertificateAuthorityBundleFilePath:
|
|
368
|
+
rootCAsAndIntermediateCAs = parseTrustRootFromBundle(
|
|
369
|
+
sslTrustedPeerCertificateAuthorityBundleFilePath
|
|
370
|
+
)
|
|
371
|
+
trustRoot = _OpenSSLCertificateAuthorities(
|
|
372
|
+
caCerts=rootCAsAndIntermediateCAs
|
|
373
|
+
)
|
|
374
|
+
clientCertificate = None
|
|
375
|
+
if sslClientCertificateBundleFilePath is not None:
|
|
376
|
+
bank: PrivateKeyWithFullChain = parsePemBundleForClient(
|
|
377
|
+
sslClientCertificateBundleFilePath
|
|
378
|
+
)
|
|
379
|
+
clientKeyCertificateBytes: bytes = bank.privateKey.as_bytes()
|
|
380
|
+
clientKeyCertificateBytes += b"\n"
|
|
381
|
+
clientKeyCertificateBytes += bank.certificate.as_bytes()
|
|
382
|
+
clientCertificate = PrivateCertificate.loadPEM(
|
|
383
|
+
clientKeyCertificateBytes
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
trustedPeerCertificates = None
|
|
387
|
+
if sslMutualTLSTrustedPeerCertificateBundleFilePath is not None:
|
|
388
|
+
trustedPeerCertificates = parsePemBundleForTrustedPeers(
|
|
389
|
+
sslMutualTLSTrustedPeerCertificateBundleFilePath
|
|
390
|
+
)
|
|
391
|
+
|
|
392
|
+
return MutualAuthenticationPolicyForHTTPS(
|
|
393
|
+
clientCertificate=clientCertificate,
|
|
394
|
+
trustRoot=trustRoot,
|
|
395
|
+
trustedPeers=trustedPeerCertificates,
|
|
396
|
+
)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2016-2026 Flying Ion Pty Ltd
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: txhttputil
|
|
3
|
+
Version: 1.4.9
|
|
4
|
+
Summary: Flying Ion utility classes for serving a static site with twisted.web with user permissions.
|
|
5
|
+
Author-email: Flying Ion Pty Ltd <contact@flyingion.com>
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2016-2026 Flying Ion Pty Ltd
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
Project-URL: Homepage, https://flyingion.com
|
|
30
|
+
Project-URL: Repository, https://gitlab.com/flyingion/txhttputil
|
|
31
|
+
Project-URL: Issues, https://gitlab.com/flyingion/txhttputil/-/issues
|
|
32
|
+
Keywords: twisted,resource,file,download,flying-ion
|
|
33
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
34
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
35
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
36
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
38
|
+
Requires-Python: >=3.9
|
|
39
|
+
Description-Content-Type: text/markdown
|
|
40
|
+
License-File: LICENSE
|
|
41
|
+
Requires-Dist: pytz
|
|
42
|
+
Requires-Dist: txwebsocket>=1.0.1
|
|
43
|
+
Requires-Dist: filetype
|
|
44
|
+
Requires-Dist: pem
|
|
45
|
+
Requires-Dist: pytmpdir
|
|
46
|
+
Requires-Dist: treelib>=1.6.4
|
|
47
|
+
Requires-Dist: cryptography
|
|
48
|
+
Requires-Dist: pyOpenSSL
|
|
49
|
+
Provides-Extra: dev
|
|
50
|
+
Requires-Dist: mypy>=1.0; extra == "dev"
|
|
51
|
+
Requires-Dist: ruff>=0.15; extra == "dev"
|
|
52
|
+
Requires-Dist: vulture>=2.14; extra == "dev"
|
|
53
|
+
Requires-Dist: basedpyright>=1.20; extra == "dev"
|
|
54
|
+
|
|
55
|
+
# TxHttpUtil
|
|
56
|
+
Flying Ion utility classes for serving a static site with twisted.web with user permissions.
|
|
57
|
+
|
|
58
|
+
Whats of interest?
|
|
59
|
+
|
|
60
|
+
* Protected resource with simple form login - Not suitable for public networks at this
|
|
61
|
+
stage
|
|
62
|
+
* A resource that underlays a multi level file system search under a standard twisted
|
|
63
|
+
Resource.putChild type resource tree.
|
|
64
|
+
* Twisted HTTP File Downloader
|
|
65
|
+
* Consistent request data, providing a Bytes IO like oboject and switching to a
|
|
66
|
+
NamedTemporaryFile if fileno, or name is called, or the data exceeds 5mb.
|
|
67
|
+
* Support for single page applications
|
|
68
|
+
|
|
69
|
+
# TODO
|
|
70
|
+
|
|
71
|
+
Unit tests for :
|
|
72
|
+
* Creating resource trees
|
|
73
|
+
* Getting resources from the resource tree
|
|
74
|
+
* Getting files from an underlay resource with two underlays
|
|
75
|
+
* Downloading files with the
|
|
76
|
+
* Logging into the test site (Using Seleneium/Chrome WebDriver)
|
|
77
|
+
* Test uploading data under 5mb and over 5mb to and auto switching to NamedTemporaryFile
|
|
78
|
+
from BytesIO
|
|
79
|
+
|
|
80
|
+
## License
|
|
81
|
+
|
|
82
|
+
Copyright (c) 2016-2026 [Flying Ion Pty Ltd](https://flyingion.com). Released under the [MIT License](LICENSE).
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
txhttputil/__init__.py,sha256=Y63nztT4icW-9PSSAFU6rEdaE25EK-ljXNMrWpRe01k,238
|
|
2
|
+
txhttputil/downloader/HttpFileDownloader.py,sha256=ugIg78QxOfMj3dPmxKdIoILX3vw35llCdvQ0H38TMQc,2867
|
|
3
|
+
txhttputil/downloader/HttpResourceProxy.py,sha256=XXFyTtcd9_aW0BdDxwTQVy-I73Moc0YYVFHUMW2woUQ,5033
|
|
4
|
+
txhttputil/downloader/__init__.py,sha256=2e3E5dYf1w9b3nV1Etbx_yI_U-VMBCxNbaZ3BiXu24o,216
|
|
5
|
+
txhttputil/login_page/LoginElement.py,sha256=N7veqpubgNpkg4p_FolAoiqb-0HZnttqrZmREDr9OXs,1074
|
|
6
|
+
txhttputil/login_page/LoginTemplate.xml,sha256=9D47ny5p_zNlr69jgWcmuQZXRrtix7fCaIgoWURQpVw,71705
|
|
7
|
+
txhttputil/login_page/__init__.py,sha256=2e3E5dYf1w9b3nV1Etbx_yI_U-VMBCxNbaZ3BiXu24o,216
|
|
8
|
+
txhttputil/site/AuthCredentials.py,sha256=3dl8NB_VIATMs3Yh1nLdAubj3G_ABKLBffledeDfZMI,880
|
|
9
|
+
txhttputil/site/AuthResource.py,sha256=ZOhAHInud4lnZ_3dIX4l33Ier2w76TmP0TWjxtMGYBI,1581
|
|
10
|
+
txhttputil/site/AuthSessionWrapper.py,sha256=apa46xcgTPQvo2g0-UtucKOjCxmlp05MCYL-5zf8c54,9035
|
|
11
|
+
txhttputil/site/AuthUserDetails.py,sha256=_DyZQ8FYoGPSlxE5LLgezM4thSJSmJUG_iKcSxI_U-o,1303
|
|
12
|
+
txhttputil/site/BasicResource.py,sha256=E8ffgKu_o08JA0nm1JHn3q41hslkDg3y__9zERioX7w,5083
|
|
13
|
+
txhttputil/site/BasicResourceTest.py,sha256=VQLYrlK-ZpEKK9DrfH4q9mqwbWfwxEf-18H7p5gKnFE,3414
|
|
14
|
+
txhttputil/site/FileUnderlayResource.py,sha256=ls9TQsIKxdcWs3ZV6Pr010o1JYfNUNe-4by-pCNV-Jw,5890
|
|
15
|
+
txhttputil/site/FileUnderlayResourceTest.py,sha256=EbdaZLPw702uW4sn2s8U-hz6rYrr_WTz5BRrxoNSIAE,3348
|
|
16
|
+
txhttputil/site/FileUploadRequest.py,sha256=oVs5kH3qaT_hBKfuX3dBRj7Y3Fe6X9_RRSgwoWYbaZE,9237
|
|
17
|
+
txhttputil/site/RedirectToHttpsResource.py,sha256=b1RgAS5qt3lfaiM1dt61InGbJG14jAbWJlQ2AwyGcrY,1301
|
|
18
|
+
txhttputil/site/RedirectionRule.py,sha256=Emv8xu63kp7arqmV47QgBrWWZxEM_Xw42SEN--oou7Y,736
|
|
19
|
+
txhttputil/site/ResourceCreator.py,sha256=ikkustGexoQ03mI0sYCCllpMlPt8cHFpv1PvhQF22kQ,270
|
|
20
|
+
txhttputil/site/RootResource.py,sha256=DrdOlsSM6wq7-B-thVzB6IVl1bpJx6UIpJ7hoESZT4g,912
|
|
21
|
+
txhttputil/site/RootSiteTest.py,sha256=2e3E5dYf1w9b3nV1Etbx_yI_U-VMBCxNbaZ3BiXu24o,216
|
|
22
|
+
txhttputil/site/SiteUtil.py,sha256=kkuC_uY_6dYLQnX5z5XXmUySB0MmXN8ABektI7Nxogk,5144
|
|
23
|
+
txhttputil/site/StaticFileResource.py,sha256=zGcf7F_1UP89Q2pO6BMKtZ2Sk89w5iig1Ymbg72zam8,5207
|
|
24
|
+
txhttputil/site/__init__.py,sha256=2e3E5dYf1w9b3nV1Etbx_yI_U-VMBCxNbaZ3BiXu24o,216
|
|
25
|
+
txhttputil/util/DeferUtil.py,sha256=RSVpZUTsvaxWOZll4fR5kyx_rZ4SaN4PpIirKwNIZOk,1318
|
|
26
|
+
txhttputil/util/ModuleUtil.py,sha256=RijQzsS7Bo_m-8s-3zMq-lYnrpUE7TDMmyvJO65Jm-8,659
|
|
27
|
+
txhttputil/util/PemUtil.py,sha256=lyz-NExGHRVQd4KsC51DIU92JTREzOd4wjg2W7ki6eQ,25050
|
|
28
|
+
txhttputil/util/SslUtil.py,sha256=0QTK5WUOCIO3FHGs5woyLHDFmngSKdJ0ui5u_GozWD0,14688
|
|
29
|
+
txhttputil/util/__init__.py,sha256=2e3E5dYf1w9b3nV1Etbx_yI_U-VMBCxNbaZ3BiXu24o,216
|
|
30
|
+
txhttputil-1.4.9.dist-info/LICENSE,sha256=ai2ZpUtxh0uJtP82roTLJogpfhiGVcjNpMEjQ0ZvxRQ,1081
|
|
31
|
+
txhttputil-1.4.9.dist-info/METADATA,sha256=viQe-aVUurKwBzRZ-2M7v31FPUSJG_RHqbq9WxQfrmU,3499
|
|
32
|
+
txhttputil-1.4.9.dist-info/WHEEL,sha256=51RkbunBAw4BWsgaQWTpPhg4Diwp3c9P5iaLk67Hdtg,92
|
|
33
|
+
txhttputil-1.4.9.dist-info/top_level.txt,sha256=qCwwcG18r9jJUyy8StOVOET-ICSDsvdhhzW3Ue01GO0,11
|
|
34
|
+
txhttputil-1.4.9.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
txhttputil
|