txhttputil 1.3.0__tar.gz → 1.4.8__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.
- txhttputil-1.4.8/PKG-INFO +47 -0
- txhttputil-1.4.8/pyproject.toml +40 -0
- txhttputil-1.4.8/setup.cfg +4 -0
- txhttputil-1.4.8/txhttputil/__init__.py +3 -0
- {txhttputil-1.3.0 → txhttputil-1.4.8}/txhttputil/site/AuthSessionWrapper.py +26 -7
- {txhttputil-1.3.0 → txhttputil-1.4.8}/txhttputil/site/BasicResource.py +9 -1
- txhttputil-1.4.8/txhttputil/site/BasicResourceTest.py +104 -0
- {txhttputil-1.3.0 → txhttputil-1.4.8}/txhttputil/site/FileUnderlayResource.py +23 -16
- {txhttputil-1.3.0 → txhttputil-1.4.8}/txhttputil/site/FileUnderlayResourceTest.py +12 -2
- {txhttputil-1.3.0 → txhttputil-1.4.8}/txhttputil/site/RootResource.py +6 -4
- {txhttputil-1.3.0 → txhttputil-1.4.8}/txhttputil/site/SiteUtil.py +2 -3
- {txhttputil-1.3.0 → txhttputil-1.4.8}/txhttputil/site/StaticFileResource.py +2 -2
- txhttputil-1.4.8/txhttputil.egg-info/PKG-INFO +47 -0
- {txhttputil-1.3.0 → txhttputil-1.4.8}/txhttputil.egg-info/SOURCES.txt +1 -1
- txhttputil-1.3.0/PKG-INFO +0 -18
- txhttputil-1.3.0/pyproject.toml +0 -41
- txhttputil-1.3.0/setup.cfg +0 -7
- txhttputil-1.3.0/txhttputil/__init__.py +0 -3
- txhttputil-1.3.0/txhttputil.egg-info/PKG-INFO +0 -18
- {txhttputil-1.3.0 → txhttputil-1.4.8}/LICENSE +0 -0
- {txhttputil-1.3.0 → txhttputil-1.4.8}/README.md +0 -0
- {txhttputil-1.3.0 → txhttputil-1.4.8}/txhttputil/downloader/HttpFileDownloader.py +0 -0
- {txhttputil-1.3.0 → txhttputil-1.4.8}/txhttputil/downloader/HttpResourceProxy.py +0 -0
- {txhttputil-1.3.0 → txhttputil-1.4.8}/txhttputil/downloader/__init__.py +0 -0
- {txhttputil-1.3.0 → txhttputil-1.4.8}/txhttputil/login_page/LoginElement.py +0 -0
- {txhttputil-1.3.0 → txhttputil-1.4.8}/txhttputil/login_page/LoginTemplate.xml +0 -0
- {txhttputil-1.3.0 → txhttputil-1.4.8}/txhttputil/login_page/__init__.py +0 -0
- {txhttputil-1.3.0 → txhttputil-1.4.8}/txhttputil/site/AuthCredentials.py +0 -0
- {txhttputil-1.3.0 → txhttputil-1.4.8}/txhttputil/site/AuthResource.py +0 -0
- {txhttputil-1.3.0 → txhttputil-1.4.8}/txhttputil/site/AuthUserDetails.py +0 -0
- {txhttputil-1.3.0 → txhttputil-1.4.8}/txhttputil/site/FileUploadRequest.py +1 -1
- {txhttputil-1.3.0 → txhttputil-1.4.8}/txhttputil/site/RedirectToHttpsResource.py +0 -0
- {txhttputil-1.3.0 → txhttputil-1.4.8}/txhttputil/site/RedirectionRule.py +0 -0
- {txhttputil-1.3.0 → txhttputil-1.4.8}/txhttputil/site/ResourceCreator.py +0 -0
- {txhttputil-1.3.0 → txhttputil-1.4.8}/txhttputil/site/RootSiteTest.py +0 -0
- {txhttputil-1.3.0 → txhttputil-1.4.8}/txhttputil/site/__init__.py +0 -0
- {txhttputil-1.3.0 → txhttputil-1.4.8}/txhttputil/util/DeferUtil.py +0 -0
- {txhttputil-1.3.0 → txhttputil-1.4.8}/txhttputil/util/ModuleUtil.py +0 -0
- {txhttputil-1.3.0 → txhttputil-1.4.8}/txhttputil/util/PemUtil.py +0 -0
- {txhttputil-1.3.0 → txhttputil-1.4.8}/txhttputil/util/SslUtil.py +0 -0
- {txhttputil-1.3.0 → txhttputil-1.4.8}/txhttputil/util/__init__.py +0 -0
- {txhttputil-1.3.0 → txhttputil-1.4.8}/txhttputil.egg-info/dependency_links.txt +0 -0
- {txhttputil-1.3.0 → txhttputil-1.4.8}/txhttputil.egg-info/requires.txt +0 -0
- {txhttputil-1.3.0 → txhttputil-1.4.8}/txhttputil.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: txhttputil
|
|
3
|
+
Version: 1.4.8
|
|
4
|
+
Summary: Synerty utility classes for serving a static site with twisted.web with user permissions.
|
|
5
|
+
Author-email: Synerty <contact@synerty.com>
|
|
6
|
+
Project-URL: Homepage, https://gitlab.synerty.com/peek/util/txhttputil
|
|
7
|
+
Keywords: twisted,resource,file,download,synerty
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Requires-Python: >=3.9
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Requires-Dist: pytz
|
|
16
|
+
Requires-Dist: txwebsocket>=1.0.1
|
|
17
|
+
Requires-Dist: filetype
|
|
18
|
+
Requires-Dist: pem
|
|
19
|
+
Requires-Dist: pytmpdir
|
|
20
|
+
Requires-Dist: treelib>=1.6.4
|
|
21
|
+
Requires-Dist: cryptography
|
|
22
|
+
Requires-Dist: pyOpenSSL
|
|
23
|
+
|
|
24
|
+
# TxHttpUtil
|
|
25
|
+
Synerty utility classes for serving a static site with twisted.web with user permissions.
|
|
26
|
+
|
|
27
|
+
Whats of interest?
|
|
28
|
+
|
|
29
|
+
* Protected resource with simple form login - Not suitable for public networks at this
|
|
30
|
+
stage
|
|
31
|
+
* A resource that underlays a multi level file system search under a standard twisted
|
|
32
|
+
Resource.putChild type resource tree.
|
|
33
|
+
* Twisted HTTP File Downloader
|
|
34
|
+
* Consistent request data, providing a Bytes IO like oboject and switching to a
|
|
35
|
+
NamedTemporaryFile if fileno, or name is called, or the data exceeds 5mb.
|
|
36
|
+
* Support for single page applications
|
|
37
|
+
|
|
38
|
+
# TODO
|
|
39
|
+
|
|
40
|
+
Unit tests for :
|
|
41
|
+
* Creating resource trees
|
|
42
|
+
* Getting resources from the resource tree
|
|
43
|
+
* Getting files from an underlay resource with two underlays
|
|
44
|
+
* Downloading files with the
|
|
45
|
+
* Logging into the test site (Using Seleneium/Chrome WebDriver)
|
|
46
|
+
* Test uploading data under 5mb and over 5mb to and auto switching to NamedTemporaryFile
|
|
47
|
+
from BytesIO
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools<70.0.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta:__legacy__"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "txhttputil"
|
|
7
|
+
version = "1.4.8"
|
|
8
|
+
description = "Synerty utility classes for serving a static site with twisted.web with user permissions."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
keywords = ["twisted", "resource", "file", "download", "synerty"]
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Programming Language :: Python :: 3.9",
|
|
14
|
+
"Programming Language :: Python :: 3.10",
|
|
15
|
+
"Programming Language :: Python :: 3.11",
|
|
16
|
+
"Programming Language :: Python :: 3.12"
|
|
17
|
+
]
|
|
18
|
+
dependencies = [
|
|
19
|
+
"pytz",
|
|
20
|
+
"txwebsocket>=1.0.1",
|
|
21
|
+
"filetype",
|
|
22
|
+
"pem",
|
|
23
|
+
"pytmpdir",
|
|
24
|
+
"treelib>=1.6.4",
|
|
25
|
+
"cryptography",
|
|
26
|
+
"pyOpenSSL",
|
|
27
|
+
]
|
|
28
|
+
authors = [
|
|
29
|
+
{name = "Synerty", email = "contact@synerty.com"}
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
[project.urls]
|
|
33
|
+
Homepage = "https://gitlab.synerty.com/peek/util/txhttputil"
|
|
34
|
+
|
|
35
|
+
[tool.setuptools.packages.find]
|
|
36
|
+
where = ["."]
|
|
37
|
+
include = ["txhttputil*"]
|
|
38
|
+
|
|
39
|
+
[tool.setuptools.package-data]
|
|
40
|
+
"txhttputil.login_page" = ["*.xml"]
|
|
@@ -4,16 +4,16 @@ from datetime import datetime
|
|
|
4
4
|
import pytz
|
|
5
5
|
from twisted.web.resource import IResource, ErrorPage
|
|
6
6
|
from twisted.web.util import DeferredResource
|
|
7
|
-
from zope.interface import implementer
|
|
8
|
-
|
|
9
7
|
from txhttputil.site.AuthCredentials import AuthCredentials
|
|
10
8
|
from txhttputil.site.AuthUserDetails import IUserSession
|
|
11
9
|
from txhttputil.site.BasicResource import BasicResource
|
|
10
|
+
from zope.interface import implementer
|
|
11
|
+
|
|
12
12
|
from .AuthResource import LoginResource, LoginSucceededResource
|
|
13
13
|
|
|
14
14
|
logger = logging.getLogger(name=__name__)
|
|
15
15
|
|
|
16
|
-
__author__ =
|
|
16
|
+
__author__ = "synerty"
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
@implementer(IResource)
|
|
@@ -24,11 +24,15 @@ class FormBasedAuthSessionWrapper(object):
|
|
|
24
24
|
|
|
25
25
|
isLeaf = False
|
|
26
26
|
|
|
27
|
-
def __init__(
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
def __init__(
|
|
28
|
+
self,
|
|
29
|
+
siteRootResource: BasicResource,
|
|
30
|
+
credentialChecker: AuthCredentials,
|
|
31
|
+
allowedIpsList=None,
|
|
32
|
+
):
|
|
30
33
|
self._siteRootResource = siteRootResource
|
|
31
34
|
self._credentialChecker = credentialChecker
|
|
35
|
+
self._allowedIpsList = allowedIpsList
|
|
32
36
|
|
|
33
37
|
def _authorizedResource(self, request):
|
|
34
38
|
"""
|
|
@@ -67,7 +71,7 @@ class FormBasedAuthSessionWrapper(object):
|
|
|
67
71
|
|
|
68
72
|
a = request.args
|
|
69
73
|
if b"username" in a and b"password" in a:
|
|
70
|
-
user = a[b
|
|
74
|
+
user = a[b"username"][0]
|
|
71
75
|
pass_ = a[b"password"][0]
|
|
72
76
|
|
|
73
77
|
deferred = self._credentialChecker.check(user, pass_)
|
|
@@ -103,6 +107,13 @@ class FormBasedAuthSessionWrapper(object):
|
|
|
103
107
|
possible, and render it. Otherwise, perhaps render an error page
|
|
104
108
|
requiring authorization or describing an internal server failure.
|
|
105
109
|
"""
|
|
110
|
+
# Check IP filtering if configured
|
|
111
|
+
if self._allowedIpsList is not None:
|
|
112
|
+
clientIp = request.getClientAddress().host
|
|
113
|
+
if clientIp not in self._allowedIpsList:
|
|
114
|
+
request.setResponseCode(403)
|
|
115
|
+
return b"Forbidden: IP address not allowed"
|
|
116
|
+
|
|
106
117
|
return self._authorizedResource(request).render(request)
|
|
107
118
|
|
|
108
119
|
def getChildWithDefault(self, path, request):
|
|
@@ -113,6 +124,14 @@ class FormBasedAuthSessionWrapper(object):
|
|
|
113
124
|
be returned, essentially halting further dispatch on the wrapped
|
|
114
125
|
resource and all children
|
|
115
126
|
"""
|
|
127
|
+
# Check IP filtering if configured
|
|
128
|
+
if self._allowedIpsList is not None:
|
|
129
|
+
clientIp = request.getClientAddress().host
|
|
130
|
+
if clientIp not in self._allowedIpsList:
|
|
131
|
+
from twisted.web.resource import ErrorPage
|
|
132
|
+
|
|
133
|
+
return ErrorPage(403, b"Forbidden", b"IP address not allowed")
|
|
134
|
+
|
|
116
135
|
# Don't consume any segments of the request - this class should be
|
|
117
136
|
# transparent!
|
|
118
137
|
request.postpath.insert(0, request.prepath.pop())
|
|
@@ -37,11 +37,12 @@ class BasicResource:
|
|
|
37
37
|
server = None
|
|
38
38
|
isLeaf = False
|
|
39
39
|
|
|
40
|
-
def __init__(self):
|
|
40
|
+
def __init__(self, allowedIpsList=None):
|
|
41
41
|
"""
|
|
42
42
|
Initialize.
|
|
43
43
|
"""
|
|
44
44
|
self._children = {}
|
|
45
|
+
self._allowedIpsList = allowedIpsList
|
|
45
46
|
|
|
46
47
|
def getChildWithDefault(self, path, request):
|
|
47
48
|
""" Get Child With Default
|
|
@@ -120,6 +121,13 @@ class BasicResource:
|
|
|
120
121
|
del self._children[path]
|
|
121
122
|
|
|
122
123
|
def render(self, request):
|
|
124
|
+
# Check IP filtering if configured
|
|
125
|
+
if self._allowedIpsList is not None:
|
|
126
|
+
clientIp = request.getClientAddress().host
|
|
127
|
+
if clientIp not in self._allowedIpsList:
|
|
128
|
+
request.setResponseCode(403)
|
|
129
|
+
return b'Forbidden: IP address not allowed'
|
|
130
|
+
|
|
123
131
|
# Optionally, Do some checking with userSession.userDetails.group
|
|
124
132
|
# userSession = IUserSession(request.getSession())
|
|
125
133
|
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from twisted.trial import unittest
|
|
4
|
+
from twisted.web.resource import NoResource
|
|
5
|
+
from twisted.web.test.requesthelper import DummyRequest
|
|
6
|
+
from txhttputil.site.BasicResource import BasicResource
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class MockAddress:
|
|
12
|
+
def __init__(self, host):
|
|
13
|
+
self.host = host
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TestBasicResource(BasicResource):
|
|
17
|
+
def render_GET(self, request):
|
|
18
|
+
return b"GET response"
|
|
19
|
+
|
|
20
|
+
def render_POST(self, request):
|
|
21
|
+
return b"POST response"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class BasicResourceTest(unittest.TestCase):
|
|
25
|
+
|
|
26
|
+
def setUp(self):
|
|
27
|
+
self.resource = TestBasicResource()
|
|
28
|
+
|
|
29
|
+
def testBasicGetChild(self):
|
|
30
|
+
child = self.resource.getChild(b"nonexistent", None)
|
|
31
|
+
self.assertIsInstance(child, NoResource)
|
|
32
|
+
|
|
33
|
+
def testPutAndGetChild(self):
|
|
34
|
+
childResource = TestBasicResource()
|
|
35
|
+
self.resource.putChild(b"child", childResource)
|
|
36
|
+
|
|
37
|
+
retrievedChild = self.resource.getChild(b"child", None)
|
|
38
|
+
self.assertEqual(retrievedChild, childResource)
|
|
39
|
+
|
|
40
|
+
def testDeleteChild(self):
|
|
41
|
+
childResource = TestBasicResource()
|
|
42
|
+
self.resource.putChild(b"child", childResource)
|
|
43
|
+
|
|
44
|
+
self.resource.deleteChild(b"child")
|
|
45
|
+
|
|
46
|
+
retrievedChild = self.resource.getChild(b"child", None)
|
|
47
|
+
self.assertIsInstance(retrievedChild, NoResource)
|
|
48
|
+
|
|
49
|
+
def testRenderGetMethod(self):
|
|
50
|
+
request = DummyRequest([])
|
|
51
|
+
request.method = b"GET"
|
|
52
|
+
|
|
53
|
+
response = self.resource.render(request)
|
|
54
|
+
self.assertEqual(response, b"GET response")
|
|
55
|
+
|
|
56
|
+
def testRenderPostMethod(self):
|
|
57
|
+
request = DummyRequest([])
|
|
58
|
+
request.method = b"POST"
|
|
59
|
+
|
|
60
|
+
response = self.resource.render(request)
|
|
61
|
+
self.assertEqual(response, b"POST response")
|
|
62
|
+
|
|
63
|
+
def testIpFilteringAllowed(self):
|
|
64
|
+
allowedIps = ["127.0.0.1", "192.168.1.100"]
|
|
65
|
+
resource = TestBasicResource(allowedIpsList=allowedIps)
|
|
66
|
+
|
|
67
|
+
request = DummyRequest([])
|
|
68
|
+
request.method = b"GET"
|
|
69
|
+
request.getClientAddress = lambda: MockAddress("127.0.0.1")
|
|
70
|
+
|
|
71
|
+
response = resource.render(request)
|
|
72
|
+
self.assertEqual(response, b"GET response")
|
|
73
|
+
|
|
74
|
+
def testIpFilteringBlocked(self):
|
|
75
|
+
allowedIps = ["127.0.0.1", "192.168.1.100"]
|
|
76
|
+
resource = TestBasicResource(allowedIpsList=allowedIps)
|
|
77
|
+
|
|
78
|
+
request = DummyRequest([])
|
|
79
|
+
request.method = b"GET"
|
|
80
|
+
request.getClientAddress = lambda: MockAddress("10.0.0.1")
|
|
81
|
+
|
|
82
|
+
response = resource.render(request)
|
|
83
|
+
self.assertEqual(response, b"Forbidden: IP address not allowed")
|
|
84
|
+
self.assertEqual(request.responseCode, 403)
|
|
85
|
+
|
|
86
|
+
def testIpFilteringDisabled(self):
|
|
87
|
+
resource = TestBasicResource(allowedIpsList=None)
|
|
88
|
+
|
|
89
|
+
request = DummyRequest([])
|
|
90
|
+
request.method = b"GET"
|
|
91
|
+
request.getClientAddress = lambda: MockAddress("10.0.0.1")
|
|
92
|
+
|
|
93
|
+
response = resource.render(request)
|
|
94
|
+
self.assertEqual(response, b"GET response")
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
if __name__ == "__main__":
|
|
98
|
+
from twisted.trial import runner, reporter
|
|
99
|
+
import sys
|
|
100
|
+
|
|
101
|
+
trialRunner = runner.TrialRunner(reporter.VerboseTextReporter)
|
|
102
|
+
suite = runner.TestLoader().loadClass(BasicResourceTest)
|
|
103
|
+
result = trialRunner.run(suite)
|
|
104
|
+
sys.exit(not result.wasSuccessful())
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
from typing import Union, List
|
|
3
|
-
|
|
4
2
|
import os
|
|
3
|
+
from typing import Union
|
|
4
|
+
|
|
5
5
|
from twisted.web.resource import NoResource
|
|
6
6
|
from twisted.web.util import Redirect
|
|
7
|
-
|
|
8
7
|
from txhttputil.site.BasicResource import BasicResource
|
|
9
8
|
from txhttputil.site.RedirectionRule import RedirectionRule
|
|
10
9
|
|
|
@@ -17,12 +16,12 @@ mimetypes.init()
|
|
|
17
16
|
|
|
18
17
|
def get_extensions_for_type(general_type):
|
|
19
18
|
for ext in mimetypes.types_map:
|
|
20
|
-
if mimetypes.types_map[ext].split(
|
|
19
|
+
if mimetypes.types_map[ext].split("/")[0] == general_type:
|
|
21
20
|
yield ext
|
|
22
21
|
|
|
23
22
|
|
|
24
|
-
IMAGE_EXTENSIONS = list(get_extensions_for_type(
|
|
25
|
-
FONT_EXTENSIONS = list(get_extensions_for_type(
|
|
23
|
+
IMAGE_EXTENSIONS = list(get_extensions_for_type("image"))
|
|
24
|
+
FONT_EXTENSIONS = list(get_extensions_for_type("font"))
|
|
26
25
|
|
|
27
26
|
|
|
28
27
|
class SinglePageAppConfig:
|
|
@@ -31,7 +30,9 @@ class SinglePageAppConfig:
|
|
|
31
30
|
|
|
32
31
|
for kw, arg in kwargs.items():
|
|
33
32
|
if not hasattr(self, kw):
|
|
34
|
-
raise KeyError(
|
|
33
|
+
raise KeyError(
|
|
34
|
+
"%s is not an argument for %s" % (kw, self.__class__)
|
|
35
|
+
)
|
|
35
36
|
setattr(self, kw, arg)
|
|
36
37
|
|
|
37
38
|
|
|
@@ -46,14 +47,14 @@ class FileUnderlayResource(BasicResource):
|
|
|
46
47
|
|
|
47
48
|
"""
|
|
48
49
|
|
|
49
|
-
acceptedExtensions = [
|
|
50
|
+
acceptedExtensions = [".js", ".css", ".html", ".xml"]
|
|
50
51
|
acceptedExtensions += FONT_EXTENSIONS
|
|
51
52
|
acceptedExtensions += IMAGE_EXTENSIONS
|
|
52
53
|
|
|
53
54
|
# TODO Implement accepted extensions
|
|
54
55
|
|
|
55
|
-
def __init__(self):
|
|
56
|
-
BasicResource.__init__(self)
|
|
56
|
+
def __init__(self, allowedIpsList=None):
|
|
57
|
+
BasicResource.__init__(self, allowedIpsList)
|
|
57
58
|
|
|
58
59
|
self._fileSystemRoots = []
|
|
59
60
|
self._singlePageAppConfig = None
|
|
@@ -72,7 +73,7 @@ class FileUnderlayResource(BasicResource):
|
|
|
72
73
|
if filePath:
|
|
73
74
|
return self._gzipIfRequired(StaticFileResource(filePath))
|
|
74
75
|
|
|
75
|
-
if not resourcePath or not
|
|
76
|
+
if not resourcePath or not "." in os.path.basename(resourcePath):
|
|
76
77
|
singlePageResource = self._getSinglePageAppResource()
|
|
77
78
|
if singlePageResource:
|
|
78
79
|
return singlePageResource
|
|
@@ -86,7 +87,9 @@ class FileUnderlayResource(BasicResource):
|
|
|
86
87
|
self._fileSystemRoots.append(fileSystemRoot)
|
|
87
88
|
|
|
88
89
|
def enableSinglePageApplication(self, indexHtmlPath="index.html"):
|
|
89
|
-
self._singlePageAppConfig = SinglePageAppConfig(
|
|
90
|
+
self._singlePageAppConfig = SinglePageAppConfig(
|
|
91
|
+
indexHtmlPath=indexHtmlPath
|
|
92
|
+
)
|
|
90
93
|
|
|
91
94
|
def getRealFilePath(self, resourcePath: str) -> Union[str, None]:
|
|
92
95
|
if not resourcePath:
|
|
@@ -96,8 +99,11 @@ class FileUnderlayResource(BasicResource):
|
|
|
96
99
|
realFilePath = os.path.join(rootDir, resourcePath)
|
|
97
100
|
|
|
98
101
|
if os.path.isdir(realFilePath):
|
|
99
|
-
logger.debug(
|
|
100
|
-
|
|
102
|
+
logger.debug(
|
|
103
|
+
"Resource path %s is a directory %s",
|
|
104
|
+
resourcePath,
|
|
105
|
+
realFilePath,
|
|
106
|
+
)
|
|
101
107
|
continue
|
|
102
108
|
|
|
103
109
|
if os.path.isfile(realFilePath):
|
|
@@ -111,10 +117,11 @@ class FileUnderlayResource(BasicResource):
|
|
|
111
117
|
filePath = self.getRealFilePath(self._singlePageAppConfig.indexHtmlPath)
|
|
112
118
|
if filePath:
|
|
113
119
|
from txhttputil.site.StaticFileResource import StaticFileResource
|
|
120
|
+
|
|
114
121
|
return self._gzipIfRequired(StaticFileResource(filePath))
|
|
115
122
|
|
|
116
123
|
def render_GET(self, request):
|
|
117
|
-
"""
|
|
124
|
+
"""Render Get
|
|
118
125
|
|
|
119
126
|
If we're being redered then that means we've exactly matched the resurce path.
|
|
120
127
|
IE, The path is looking for a directory, not a file.
|
|
@@ -131,4 +138,4 @@ class FileUnderlayResource(BasicResource):
|
|
|
131
138
|
def getChild(self, path, request):
|
|
132
139
|
if request.uri in self._redirections.keys():
|
|
133
140
|
return Redirect(self._redirections[request.uri])
|
|
134
|
-
return super().getChild(path, request)
|
|
141
|
+
return super().getChild(path, request)
|
|
@@ -5,11 +5,11 @@ from twisted.internet import reactor
|
|
|
5
5
|
from twisted.trial import unittest
|
|
6
6
|
from twisted.web import server
|
|
7
7
|
from twisted.web.resource import ErrorPage
|
|
8
|
-
|
|
9
|
-
import txhttputil
|
|
10
8
|
from txhttputil.downloader.HttpFileDownloader import HttpFileDownloader
|
|
11
9
|
from txhttputil.site.FileUnderlayResource import FileUnderlayResource
|
|
12
10
|
|
|
11
|
+
import txhttputil
|
|
12
|
+
|
|
13
13
|
logger = logging.getLogger(__name__)
|
|
14
14
|
|
|
15
15
|
|
|
@@ -72,3 +72,13 @@ class FileUnderlayResourceTest(unittest.TestCase):
|
|
|
72
72
|
d = HttpFileDownloader(self.sitePath + fileName).run()
|
|
73
73
|
d.addCallback(self._checkDownloadedResource, fileName=realPath)
|
|
74
74
|
return d
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
if __name__ == "__main__":
|
|
78
|
+
from twisted.trial import runner, reporter
|
|
79
|
+
import sys
|
|
80
|
+
|
|
81
|
+
trialRunner = runner.TrialRunner(reporter.VerboseTextReporter)
|
|
82
|
+
suite = runner.TestLoader().loadClass(FileUnderlayResourceTest)
|
|
83
|
+
result = trialRunner.run(suite)
|
|
84
|
+
sys.exit(not result.wasSuccessful())
|
|
@@ -7,21 +7,23 @@
|
|
|
7
7
|
* Support : support@synerty.com
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
|
+
from txhttputil.site.AuthUserDetails import AuthUserDetails
|
|
10
11
|
from txhttputil.site.ResourceUtil import RESOURCES
|
|
11
|
-
from txhttputil.site.StaticFileResource import StaticFileResource
|
|
12
12
|
from txhttputil.site.StaticFileMultiPath import StaticFileMultiPath
|
|
13
|
-
from txhttputil.site.
|
|
13
|
+
from txhttputil.site.StaticFileResource import StaticFileResource
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
class RootResource(StaticFileResource):
|
|
17
|
-
"""
|
|
17
|
+
"""Root Resource
|
|
18
18
|
|
|
19
19
|
This resource is the root or subroot of a resource tree.
|
|
20
20
|
It first looks for resources created with "createRoot
|
|
21
21
|
"""
|
|
22
22
|
|
|
23
23
|
|
|
24
|
-
def createRootResource(
|
|
24
|
+
def createRootResource(
|
|
25
|
+
userAccess: AuthUserDetails, staticFileRoot: StaticFileMultiPath
|
|
26
|
+
):
|
|
25
27
|
rootResource = RootResource(userAccess, staticFileRoot=staticFileRoot)
|
|
26
28
|
callResourceCreators(RESOURCES, rootResource, userAccess)
|
|
27
29
|
|
|
@@ -14,7 +14,6 @@ from typing import Optional
|
|
|
14
14
|
from twisted.internet import reactor
|
|
15
15
|
from twisted.internet._sslverify import trustRootFromCertificates
|
|
16
16
|
from twisted.web import server
|
|
17
|
-
|
|
18
17
|
from txhttputil.login_page.LoginElement import LoginElement
|
|
19
18
|
from txhttputil.site.AuthCredentials import (
|
|
20
19
|
AllowAllAuthCredentials,
|
|
@@ -23,8 +22,6 @@ from txhttputil.site.AuthCredentials import (
|
|
|
23
22
|
from txhttputil.site.AuthSessionWrapper import FormBasedAuthSessionWrapper
|
|
24
23
|
from txhttputil.site.FileUploadRequest import FileUploadRequest
|
|
25
24
|
from txhttputil.site.RedirectToHttpsResource import RedirectToHttpsResource
|
|
26
|
-
from txwebsocket.txws import WebSocketUpgradeHTTPChannel
|
|
27
|
-
|
|
28
25
|
from txhttputil.util.PemUtil import (
|
|
29
26
|
PEM_CHECK_TYPE,
|
|
30
27
|
parseTrustRootFromBundle,
|
|
@@ -36,6 +33,8 @@ from txhttputil.util.SslUtil import (
|
|
|
36
33
|
buildCertificateOptionsForTwisted,
|
|
37
34
|
)
|
|
38
35
|
|
|
36
|
+
from txwebsocket.txws import WebSocketUpgradeHTTPChannel
|
|
37
|
+
|
|
39
38
|
logger = logging.getLogger(__name__)
|
|
40
39
|
|
|
41
40
|
|
|
@@ -39,9 +39,9 @@ class StaticFileResource(BasicResource):
|
|
|
39
39
|
]
|
|
40
40
|
|
|
41
41
|
def __init__(
|
|
42
|
-
self, filePath: str, expireMinutes: int = 30, chunkSize: int = 128000
|
|
42
|
+
self, filePath: str, expireMinutes: int = 30, chunkSize: int = 128000, allowedIpsList=None
|
|
43
43
|
):
|
|
44
|
-
BasicResource.__init__(self)
|
|
44
|
+
BasicResource.__init__(self, allowedIpsList)
|
|
45
45
|
self._filePath = filePath
|
|
46
46
|
self.cancelDownload = False
|
|
47
47
|
self.expireMinutes = expireMinutes
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: txhttputil
|
|
3
|
+
Version: 1.4.8
|
|
4
|
+
Summary: Synerty utility classes for serving a static site with twisted.web with user permissions.
|
|
5
|
+
Author-email: Synerty <contact@synerty.com>
|
|
6
|
+
Project-URL: Homepage, https://gitlab.synerty.com/peek/util/txhttputil
|
|
7
|
+
Keywords: twisted,resource,file,download,synerty
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Requires-Python: >=3.9
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Requires-Dist: pytz
|
|
16
|
+
Requires-Dist: txwebsocket>=1.0.1
|
|
17
|
+
Requires-Dist: filetype
|
|
18
|
+
Requires-Dist: pem
|
|
19
|
+
Requires-Dist: pytmpdir
|
|
20
|
+
Requires-Dist: treelib>=1.6.4
|
|
21
|
+
Requires-Dist: cryptography
|
|
22
|
+
Requires-Dist: pyOpenSSL
|
|
23
|
+
|
|
24
|
+
# TxHttpUtil
|
|
25
|
+
Synerty utility classes for serving a static site with twisted.web with user permissions.
|
|
26
|
+
|
|
27
|
+
Whats of interest?
|
|
28
|
+
|
|
29
|
+
* Protected resource with simple form login - Not suitable for public networks at this
|
|
30
|
+
stage
|
|
31
|
+
* A resource that underlays a multi level file system search under a standard twisted
|
|
32
|
+
Resource.putChild type resource tree.
|
|
33
|
+
* Twisted HTTP File Downloader
|
|
34
|
+
* Consistent request data, providing a Bytes IO like oboject and switching to a
|
|
35
|
+
NamedTemporaryFile if fileno, or name is called, or the data exceeds 5mb.
|
|
36
|
+
* Support for single page applications
|
|
37
|
+
|
|
38
|
+
# TODO
|
|
39
|
+
|
|
40
|
+
Unit tests for :
|
|
41
|
+
* Creating resource trees
|
|
42
|
+
* Getting resources from the resource tree
|
|
43
|
+
* Getting files from an underlay resource with two underlays
|
|
44
|
+
* Downloading files with the
|
|
45
|
+
* Logging into the test site (Using Seleneium/Chrome WebDriver)
|
|
46
|
+
* Test uploading data under 5mb and over 5mb to and auto switching to NamedTemporaryFile
|
|
47
|
+
from BytesIO
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
LICENSE
|
|
2
2
|
README.md
|
|
3
3
|
pyproject.toml
|
|
4
|
-
setup.cfg
|
|
5
4
|
txhttputil/__init__.py
|
|
6
5
|
txhttputil.egg-info/PKG-INFO
|
|
7
6
|
txhttputil.egg-info/SOURCES.txt
|
|
@@ -19,6 +18,7 @@ txhttputil/site/AuthResource.py
|
|
|
19
18
|
txhttputil/site/AuthSessionWrapper.py
|
|
20
19
|
txhttputil/site/AuthUserDetails.py
|
|
21
20
|
txhttputil/site/BasicResource.py
|
|
21
|
+
txhttputil/site/BasicResourceTest.py
|
|
22
22
|
txhttputil/site/FileUnderlayResource.py
|
|
23
23
|
txhttputil/site/FileUnderlayResourceTest.py
|
|
24
24
|
txhttputil/site/FileUploadRequest.py
|
txhttputil-1.3.0/PKG-INFO
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: txhttputil
|
|
3
|
-
Version: 1.3.0
|
|
4
|
-
Summary: Synerty utility classes for serving a static site with twisted.web with user permissions.
|
|
5
|
-
Author-email: Synerty <contact@synerty.com>
|
|
6
|
-
Project-URL: Homepage, https://github.com/Synerty/txhttputil
|
|
7
|
-
Project-URL: Download, https://github.com/Synerty/txhttputil/tarball/1.2.11
|
|
8
|
-
Keywords: twisted,resource,file,download,synerty
|
|
9
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
10
|
-
License-File: LICENSE
|
|
11
|
-
Requires-Dist: pytz
|
|
12
|
-
Requires-Dist: txwebsocket>=1.0.1
|
|
13
|
-
Requires-Dist: filetype
|
|
14
|
-
Requires-Dist: pem
|
|
15
|
-
Requires-Dist: pytmpdir
|
|
16
|
-
Requires-Dist: treelib>=1.6.4
|
|
17
|
-
Requires-Dist: cryptography
|
|
18
|
-
Requires-Dist: pyOpenSSL
|
txhttputil-1.3.0/pyproject.toml
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
[build-system]
|
|
2
|
-
requires = ["setuptools>=61.2"]
|
|
3
|
-
build-backend = "setuptools.build_meta"
|
|
4
|
-
|
|
5
|
-
[project]
|
|
6
|
-
name = "txhttputil"
|
|
7
|
-
version = "1.3.0"
|
|
8
|
-
authors = [{name = "Synerty", email = "contact@synerty.com"}]
|
|
9
|
-
description = "Synerty utility classes for serving a static site with twisted.web with user permissions."
|
|
10
|
-
keywords = [
|
|
11
|
-
"twisted",
|
|
12
|
-
"resource",
|
|
13
|
-
"file",
|
|
14
|
-
"download",
|
|
15
|
-
"synerty",
|
|
16
|
-
]
|
|
17
|
-
classifiers = ["Programming Language :: Python :: 3.9"]
|
|
18
|
-
dependencies = [
|
|
19
|
-
"pytz",
|
|
20
|
-
"txwebsocket>=1.0.1",
|
|
21
|
-
"filetype",
|
|
22
|
-
"pem",
|
|
23
|
-
"pytmpdir",
|
|
24
|
-
"treelib>=1.6.4",
|
|
25
|
-
"cryptography",
|
|
26
|
-
"pyOpenSSL",
|
|
27
|
-
]
|
|
28
|
-
|
|
29
|
-
[project.urls]
|
|
30
|
-
Homepage = "https://github.com/Synerty/txhttputil"
|
|
31
|
-
Download = "https://github.com/Synerty/txhttputil/tarball/1.2.11"
|
|
32
|
-
|
|
33
|
-
[tool.setuptools]
|
|
34
|
-
packages = [
|
|
35
|
-
"txhttputil",
|
|
36
|
-
"txhttputil.downloader",
|
|
37
|
-
"txhttputil.login_page",
|
|
38
|
-
"txhttputil.site",
|
|
39
|
-
"txhttputil.util",
|
|
40
|
-
]
|
|
41
|
-
include-package-data = false
|
txhttputil-1.3.0/setup.cfg
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: txhttputil
|
|
3
|
-
Version: 1.3.0
|
|
4
|
-
Summary: Synerty utility classes for serving a static site with twisted.web with user permissions.
|
|
5
|
-
Author-email: Synerty <contact@synerty.com>
|
|
6
|
-
Project-URL: Homepage, https://github.com/Synerty/txhttputil
|
|
7
|
-
Project-URL: Download, https://github.com/Synerty/txhttputil/tarball/1.2.11
|
|
8
|
-
Keywords: twisted,resource,file,download,synerty
|
|
9
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
10
|
-
License-File: LICENSE
|
|
11
|
-
Requires-Dist: pytz
|
|
12
|
-
Requires-Dist: txwebsocket>=1.0.1
|
|
13
|
-
Requires-Dist: filetype
|
|
14
|
-
Requires-Dist: pem
|
|
15
|
-
Requires-Dist: pytmpdir
|
|
16
|
-
Requires-Dist: treelib>=1.6.4
|
|
17
|
-
Requires-Dist: cryptography
|
|
18
|
-
Requires-Dist: pyOpenSSL
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|