cherrypy-foundation 1.0.0a15__py3-none-any.whl → 1.0.0a16__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.
@@ -1,9 +1,14 @@
1
1
  <!DOCTYPE html>
2
- <html >
2
+ <html lang="en">
3
3
  <head>
4
4
  <title>test-form</title>
5
5
  </head>
6
6
  <body>
7
+ {% if form.error_message %}
8
+ <p>
9
+ {{ form.error_message }}
10
+ </p>
11
+ {% endif %}
7
12
  <form method="post" action="/">
8
13
  <Fields form={{ form }} />
9
14
  </form>
@@ -1,3 +1,4 @@
1
+ <!DOCTYPE html>
1
2
  <html lang="en">
2
3
  <head>
3
4
  <title>test-url</title>
@@ -15,9 +15,11 @@
15
15
  # along with this program. If not, see <https://www.gnu.org/licenses/>.
16
16
  import importlib
17
17
  from unittest import skipUnless
18
+ from urllib.parse import urlencode
18
19
 
19
20
  import cherrypy
20
21
  from cherrypy.test import helper
22
+ from parameterized import parameterized
21
23
  from wtforms.fields import BooleanField, PasswordField, StringField, SubmitField
22
24
  from wtforms.validators import InputRequired, Length
23
25
 
@@ -67,13 +69,24 @@ class LoginForm(CherryForm):
67
69
  )
68
70
 
69
71
 
72
+ @cherrypy.tools.sessions()
70
73
  @cherrypy.tools.jinja2(env=env)
71
74
  class Root:
72
75
 
76
+ @cherrypy.expose
77
+ def index(self, **kwargs):
78
+ if 'login' not in cherrypy.session:
79
+ raise cherrypy.HTTPRedirect('/login')
80
+ return 'OK'
81
+
73
82
  @cherrypy.expose
74
83
  @cherrypy.tools.jinja2(template='test_form.html')
75
- def index(self):
84
+ def login(self, **kwargs):
76
85
  form = LoginForm()
86
+ if form.validate_on_submit():
87
+ # login user with cherrypy.tools.auth
88
+ cherrypy.session['login'] = True
89
+ raise cherrypy.HTTPRedirect('/')
77
90
  return {'form': form}
78
91
 
79
92
 
@@ -89,7 +102,7 @@ class FormTest(helper.CPWebCase):
89
102
  def test_get_form(self):
90
103
  # Given a form
91
104
  # When querying the page that include this form
92
- self.getPage("/")
105
+ self.getPage("/login")
93
106
  self.assertStatus(200)
94
107
  # Then each field is render properly.
95
108
  # 1. Check title
@@ -114,3 +127,22 @@ class FormTest(helper.CPWebCase):
114
127
  self.assertMatchesBody(
115
128
  '<input class="(btn-primary ?|float-end ?|btn ?){3}" container-attr="BAR" container-class="col-sm-6" id="submit" name="submit" type="submit" value="Login">'
116
129
  )
130
+
131
+ @parameterized.expand(
132
+ [
133
+ ('myuser', 'mypassword', 0, 303, False),
134
+ ('myuser', '', 0, 200, 'Password: This field is required.'),
135
+ ('', 'mypassword', 0, 200, 'User: This field is required.'),
136
+ ]
137
+ )
138
+ def test_post_form(self, login, password, persistent, expect_status, expect_error):
139
+ # Given a page with a form.
140
+ # When data is sent to the form.
141
+ self.getPage(
142
+ "/login", method='POST', body=urlencode({'login': login, 'password': password, 'persistent': persistent})
143
+ )
144
+ # Then page return a status
145
+ self.assertStatus(expect_status)
146
+ # Then page may return an error
147
+ if expect_error:
148
+ self.assertInBody(expect_error)
@@ -0,0 +1,200 @@
1
+ # CherryPy Foundation
2
+ # Copyright (C) 2025 IKUS Software
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
16
+
17
+ import cherrypy
18
+ from cherrypy.test import helper
19
+
20
+ from cherrypy_foundation.tools import secure_headers # noqa
21
+
22
+
23
+ @cherrypy.tools.secure_headers(
24
+ csp={
25
+ "default-src": "'self'",
26
+ "style-src": ("'self'", "'unsafe-inline'"),
27
+ "script-src": ("'self'", "'unsafe-inline'"),
28
+ "img-src": ("'self'", "data:"),
29
+ }
30
+ )
31
+ @cherrypy.tools.proxy(local=None, remote='X-Real-IP')
32
+ class Root:
33
+
34
+ @cherrypy.expose
35
+ @cherrypy.tools.sessions()
36
+ def index(self):
37
+ return {'var1': 'test-jinja2'}
38
+
39
+ @cherrypy.expose
40
+ @cherrypy.tools.sessions()
41
+ def logout(self, **kwargs):
42
+ raise cherrypy.HTTPRedirect('/')
43
+
44
+ @cherrypy.expose
45
+ @cherrypy.tools.secure_headers(on=False)
46
+ @cherrypy.tools.sessions(on=False)
47
+ def static(self, value=None):
48
+ return 'OK'
49
+
50
+
51
+ class SecureHeaderTest(helper.CPWebCase):
52
+ interactive = False
53
+
54
+ @classmethod
55
+ def setup_server(cls):
56
+ cherrypy.tree.mount(Root(), '/')
57
+
58
+ def test_cookie_samesite_lax(self):
59
+ # Given a request made to rdiffweb
60
+ # When receiving the response
61
+ self.getPage('/')
62
+ # Then the header contains Set-Cookie with SameSite=Lax
63
+ cookie = self.assertHeader('Set-Cookie')
64
+ self.assertIn('SameSite=Lax', cookie)
65
+
66
+ def test_cookie_samesite_lax_without_session(self):
67
+ # Given not a client sending no cookie
68
+ self.cookies = None
69
+ # When a query is made to a static path (without session)
70
+ self.getPage('/static/blue.css')
71
+ # Then Set-Cookie is not defined.
72
+ self.assertNoHeader('Set-Cookie')
73
+
74
+ def test_cookie_with_https(self):
75
+ # Given an https request made to rdiffweb
76
+ self.getPage('/', headers=[('X-Forwarded-Proto', 'https')])
77
+ # When receiving the response
78
+ self.assertStatus(200)
79
+ # Then the header contains Set-Cookie with Secure
80
+ cookie = self.assertHeader('Set-Cookie')
81
+ self.assertIn('Secure', cookie)
82
+
83
+ def test_cookie_with_http(self):
84
+ # Given an https request made to rdiffweb
85
+ self.getPage('/')
86
+ # When receiving the response
87
+ # Then the header contains Set-Cookie with Secure
88
+ cookie = self.assertHeader('Set-Cookie')
89
+ self.assertNotIn('Secure', cookie)
90
+
91
+ def test_get_with_wrong_origin(self):
92
+ # Given a GET request made to rdiffweb
93
+ # When the request is made using a different origin
94
+ self.getPage('/', headers=[('Origin', 'http://www.examples.com')])
95
+ # Then the response status it 200 OK.
96
+ self.assertStatus(200)
97
+
98
+ def test_post_with_wrong_origin(self):
99
+ # Given a POST request made to rdiffweb
100
+ # When the request is made using a different origin
101
+ self.getPage('/logout', headers=[('Origin', 'http://www.examples.com')], method='POST')
102
+ # Then the request is refused with 403 Forbiden
103
+ self.assertStatus(403)
104
+ self.assertInBody('Unexpected Origin header')
105
+
106
+ def test_post_with_prefixed_origin(self):
107
+ # Given a POST request made to rdiffweb
108
+ # When the request is made using a different origin
109
+ base = 'http://%s:%s' % (self.HOST + 'anything.com', self.PORT)
110
+ self.getPage('/logout', headers=[('Origin', base)], method='POST')
111
+ # Then the request is accepted with 200 OK
112
+ self.assertStatus(403)
113
+ self.assertInBody('Unexpected Origin header')
114
+
115
+ def test_post_with_valid_origin(self):
116
+ # Given a POST request made to rdiffweb
117
+ # When the request is made using a different origin
118
+ base = 'http://%s:%s' % (self.HOST, self.PORT)
119
+ self.getPage('/logout', headers=[('Origin', base)], method='POST')
120
+ # Then the request is accepted with 200 OK
121
+ self.assertStatus(303)
122
+
123
+ def test_post_without_origin(self):
124
+ # Given a POST request made to rdiffweb
125
+ # When the request is made without an origin
126
+ self.getPage('/logout', method='POST')
127
+ # Then the request is accepted with 200 OK
128
+ self.assertStatus(303)
129
+
130
+ def test_clickjacking_defense(self):
131
+ # Given a POST request made to rdiffweb
132
+ # When the request is made without an origin
133
+ self.getPage('/')
134
+ # Then the request is accepted with 200 OK
135
+ self.assertStatus(200)
136
+ self.assertHeaderItemValue('X-Frame-Options', 'DENY')
137
+
138
+ def test_no_cache(self):
139
+ # Given a POST request made to rdiffweb
140
+ # When the request is made without an origin
141
+ self.getPage('/')
142
+ # Then the request is accepted with 200 OK
143
+ self.assertStatus(200)
144
+ self.assertHeaderItemValue('Cache-control', 'no-cache')
145
+ self.assertHeaderItemValue('Cache-control', 'no-store')
146
+ self.assertHeaderItemValue('Cache-control', 'must-revalidate')
147
+ self.assertHeaderItemValue('Cache-control', 'max-age=0')
148
+ self.assertHeaderItemValue('Pragma', 'no-cache')
149
+ self.assertHeaderItemValue('Expires', '0')
150
+
151
+ def test_no_cache_with_static(self):
152
+ self.getPage('/static')
153
+ # Then the request is accepted with 200 OK
154
+ self.assertStatus(200)
155
+ self.assertNoHeader('Cache-control')
156
+ self.assertNoHeader('Pragma')
157
+ self.assertNoHeader('Expires')
158
+
159
+ def test_referrer_policy(self):
160
+ # Given a POST request made to rdiffweb
161
+ # When the request is made without an origin
162
+ self.getPage('/')
163
+ # Then the request is accepted with 200 OK
164
+ self.assertStatus(200)
165
+ self.assertHeaderItemValue('Referrer-Policy', 'same-origin')
166
+
167
+ def test_nosniff(self):
168
+ # Given a POST request made to rdiffweb
169
+ # When the request is made without an origin
170
+ self.getPage('/')
171
+ # Then the request is accepted with 200 OK
172
+ self.assertStatus(200)
173
+ self.assertHeaderItemValue('X-Content-Type-Options', 'nosniff')
174
+
175
+ def test_xss_protection(self):
176
+ # Given a POST request made to rdiffweb
177
+ # When the request is made without an origin
178
+ self.getPage('/')
179
+ # Then the request is accepted with 200 OK
180
+ self.assertStatus(200)
181
+ self.assertHeaderItemValue('X-XSS-Protection', '1; mode=block')
182
+
183
+ def test_content_security_policy(self):
184
+ # Given a POST request made to rdiffweb
185
+ # When the request is made without an origin
186
+ self.getPage('/')
187
+ # Then the request is accepted with 200 OK
188
+ self.assertStatus(200)
189
+ self.assertHeaderItemValue(
190
+ 'Content-Security-Policy',
191
+ "default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline'; img-src 'self' data:",
192
+ )
193
+
194
+ def test_strict_transport_security(self):
195
+ # Given a POST request made to rdiffweb
196
+ # When the request is made without an origin
197
+ self.getPage('/', headers=[('X-Forwarded-Proto', 'https')])
198
+ # Then the request is accepted with 200 OK
199
+ self.assertStatus(200)
200
+ self.assertHeaderItemValue('Strict-Transport-Security', 'max-age=31536000; includeSubDomains')
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cherrypy-foundation
3
- Version: 1.0.0a15
3
+ Version: 1.0.0a16
4
4
  Summary: CherryPy Foundation
5
5
  Author-email: Patrik Dufresne <patrik@ikus-soft.com>
6
6
  License: GPLv3
@@ -8,7 +8,6 @@ Project-URL: Homepage, https://gitlab.com/ikus-soft/cherrypy-foundation
8
8
  Classifier: Development Status :: 4 - Beta
9
9
  Classifier: Framework :: CherryPy
10
10
  Classifier: Intended Audience :: System Administrators
11
- Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
12
11
  Classifier: Programming Language :: Python :: 3.11
13
12
  Classifier: Programming Language :: Python :: 3.12
14
13
  Classifier: Programming Language :: Python :: 3.13
@@ -22,12 +21,22 @@ Requires-Dist: jinjax
22
21
  Requires-Dist: pytz
23
22
  Requires-Dist: WTForms>=3.2.1
24
23
  Provides-Extra: test
25
- Requires-Dist: ldap3; extra == "test"
26
24
  Requires-Dist: apscheduler; extra == "test"
27
- Requires-Dist: sqlalchemy; extra == "test"
25
+ Requires-Dist: argon2-cffi; extra == "test"
26
+ Requires-Dist: ldap3; extra == "test"
28
27
  Requires-Dist: parameterized; extra == "test"
29
28
  Requires-Dist: pytest; extra == "test"
29
+ Requires-Dist: requests_oauthlib; extra == "test"
30
30
  Requires-Dist: selenium; extra == "test"
31
+ Requires-Dist: sqlalchemy; extra == "test"
32
+ Provides-Extra: ldap
33
+ Requires-Dist: ldap3; extra == "ldap"
34
+ Provides-Extra: oauth
35
+ Requires-Dist: requests_oauthlib; extra == "oauth"
36
+ Provides-Extra: passwd
37
+ Requires-Dist: argon2-cffi; extra == "passwd"
38
+ Provides-Extra: scheduler
39
+ Requires-Dist: apscheduler; extra == "scheduler"
31
40
  Dynamic: license-file
32
41
 
33
42
  # Cherrypy-foundation
@@ -39,4 +48,12 @@ Dynamic: license-file
39
48
  <a href="https://sonar.ikus-soft.com/dashboard?id=cherrypy-foundation"><img alt="Coverage" src="https://sonar.ikus-soft.com/api/project_badges/measure?project=cherrypy-foundation&metric=coverage"></a>
40
49
  </p>
41
50
 
42
- This project is a collection of common utilities for creating web applications with CherryPy. It provides integrations with SQLAlchemy, Jinja2, WTForms, and Bootstrap.
51
+ This project provides a collection of reusable utilities that simplify
52
+ building web applications with CherryPy. It offers built-in integrations
53
+ with SQLAlchemy for database access, Jinja2 for templating, WTForms for
54
+ form handling, and Bootstrap for UI components, helping streamline
55
+ application development.
56
+
57
+ # Usage
58
+
59
+ TODO
@@ -96,16 +96,15 @@ cherrypy_foundation/plugins/tests/test_smtp.py,sha256=qs5yezIpSXkBmLmFlqckfPW7Nm
96
96
  cherrypy_foundation/tests/__init__.py,sha256=VIBhEMPDIaffF7lYzA5KevcVNzALz4kykBBaO-gdKHY,2818
97
97
  cherrypy_foundation/tests/test_error_page.py,sha256=8yLK8OGbJIdUjilFIHMNBZadLKHrXnD6KSmQ3Da4LaQ,2399
98
98
  cherrypy_foundation/tests/test_flash.py,sha256=JqZDAgazlNnP3HcPFmFOWbPeDDMzc6z5fHNe-pBTin0,1976
99
- cherrypy_foundation/tests/test_form.py,sha256=sWsPWyXwAVkCeP5t0qIHc0Oi32Zi3kztoQ_wlDR9STc,4326
99
+ cherrypy_foundation/tests/test_form.py,sha256=lBoGonAHeiTrNRxWaQXfiiGsHCrAPwid-5_IHIPJUhg,5542
100
100
  cherrypy_foundation/tests/test_passwd.py,sha256=gC5O4yhHyU1YRYuDc0pG0T_5zvrG2qrr6P822iyK3Rg,1956
101
101
  cherrypy_foundation/tests/test_url.py,sha256=W-RTKQuxYS2KXxCYTTtnKcxfdP9F6Fp3QKY_sBTnBmE,6434
102
102
  cherrypy_foundation/tests/templates/test_flash.html,sha256=b1S4I9v0n-Y1yoTUh2ZKNysR1NMrqv8ldvqONtmInzw,213
103
- cherrypy_foundation/tests/templates/test_form.html,sha256=ywaLKgWqvBdbdfnBgYmi1ihA3xK8X5YtjomA6atnJUg,206
104
- cherrypy_foundation/tests/templates/test_url.html,sha256=427G6AnA6zUfjPoLxsVHy3U2e_XxG4ntpZX3DIjO18Q,512
103
+ cherrypy_foundation/tests/templates/test_form.html,sha256=liubTm2q74-3hqQb4weaGJU3sq4vdq868GdVahBafSQ,333
104
+ cherrypy_foundation/tests/templates/test_url.html,sha256=xoyRIkIzzM8Q-M39i80P4j7Wo8Lkzh1rIzRq-40Szak,528
105
105
  cherrypy_foundation/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
106
106
  cherrypy_foundation/tools/auth.py,sha256=lTSajxCiReMzm-Fl-xhTByi4yFnInEWOoNsmUMnHQhs,9761
107
107
  cherrypy_foundation/tools/auth_mfa.py,sha256=VaLvBz9wo6jTx-2mCGqFXPxl-z14f8UMWvd6_xeXd40,9212
108
- cherrypy_foundation/tools/errors.py,sha256=ELpAj0N9kIxC22QW5xDQJz60zMpCwgm-Twu2WpELM1A,1005
109
108
  cherrypy_foundation/tools/i18n.py,sha256=pP3QrGCHiNHVDpjdKNFKErOCm3eRYZL6W_nlz9j8eD8,17128
110
109
  cherrypy_foundation/tools/jinja2.py,sha256=xrHfk814GjlWhfqJcXTA6I2PQ3YqsSz4eHcCh9sG05U,5969
111
110
  cherrypy_foundation/tools/ratelimit.py,sha256=pT7vZRmjltNeuiQpdyXOmnpG9BcXjLaj-AXJ0e2x_zw,8300
@@ -117,6 +116,7 @@ cherrypy_foundation/tools/tests/test_auth_mfa.py,sha256=911hnBbdg5CKb613uIBrlggo
117
116
  cherrypy_foundation/tools/tests/test_i18n.py,sha256=9rK4_1HVTk2dtTh9egIymx4SP8EO8_igBQkCD4YiIys,10758
118
117
  cherrypy_foundation/tools/tests/test_jinja2.py,sha256=gLwaGeQn1Wt7Ximc_5ESHSm499vCeyqhEhPYUPJnamk,5545
119
118
  cherrypy_foundation/tools/tests/test_ratelimit.py,sha256=rrqybwMbh1GFlF2-Ut57zHPAc1uqX88aqea6VS_6p5E,3449
119
+ cherrypy_foundation/tools/tests/test_secure_headers.py,sha256=-qry-HHINpRzFru22j9jT3Wy1seURN_hd6yQTj608Mc,7733
120
120
  cherrypy_foundation/tools/tests/components/Button.jinja,sha256=uSLp1GpEIgZNXK_GWglu0E_a1c3jHpDLI66MRfMqGhE,95
121
121
  cherrypy_foundation/tools/tests/locales/messages.pot,sha256=5K9piTRL7H5MxDXFIWJsCccSJRA0HwfCQQU8b8VYo30,40
122
122
  cherrypy_foundation/tools/tests/locales/de/LC_MESSAGES/messages.mo,sha256=bsJTVL4OefevkxeHDS3VcW3egP6Yq18LFXwjSyoqIng,336
@@ -126,8 +126,8 @@ cherrypy_foundation/tools/tests/locales/fr/LC_MESSAGES/messages.po,sha256=6_Sk9I
126
126
  cherrypy_foundation/tools/tests/templates/test_jinja2.html,sha256=s1bHmy-lyf0YW0t-LOx3ugILV2kqFoBNYxziWgrZbo0,216
127
127
  cherrypy_foundation/tools/tests/templates/test_jinjax.html,sha256=NImzIW0mUHxilFd61PSoxFC-yu1nayEVwv-5zlgD9yo,179
128
128
  cherrypy_foundation/tools/tests/templates/test_jinjax_i18n.html,sha256=yre8j7HBjpTQZHpM0PuB3ASGD3O4vKkJ-y72Fm6STgY,771
129
- cherrypy_foundation-1.0.0a15.dist-info/licenses/LICENSE.md,sha256=trSLYs5qlaow_bBwsLTRKpmTXsXzFksM_YUCMqrgAJQ,35149
130
- cherrypy_foundation-1.0.0a15.dist-info/METADATA,sha256=yFHspuRqMzwk-zpZtT_j7H7WAR4KTX38sX__MvuwPdI,2023
131
- cherrypy_foundation-1.0.0a15.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
132
- cherrypy_foundation-1.0.0a15.dist-info/top_level.txt,sha256=B1vQPTLYhpKJ6W0JkRCWyAf8RPcnwJWdYxixv75-4ew,20
133
- cherrypy_foundation-1.0.0a15.dist-info/RECORD,,
129
+ cherrypy_foundation-1.0.0a16.dist-info/licenses/LICENSE.md,sha256=trSLYs5qlaow_bBwsLTRKpmTXsXzFksM_YUCMqrgAJQ,35149
130
+ cherrypy_foundation-1.0.0a16.dist-info/METADATA,sha256=xItVw-n6g3DFDtgSjtnDimz9OLCj36Lzsx9HO2QJpt0,2471
131
+ cherrypy_foundation-1.0.0a16.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
132
+ cherrypy_foundation-1.0.0a16.dist-info/top_level.txt,sha256=B1vQPTLYhpKJ6W0JkRCWyAf8RPcnwJWdYxixv75-4ew,20
133
+ cherrypy_foundation-1.0.0a16.dist-info/RECORD,,
@@ -1,27 +0,0 @@
1
- # Cherrypy Foundation
2
- # Copyright (C) 2025 IKUS Software inc.
3
- #
4
- # This program is free software: you can redistribute it and/or modify
5
- # it under the terms of the GNU General Public License as published by
6
- # the Free Software Foundation, either version 3 of the License, or
7
- # (at your option) any later version.
8
- #
9
- # This program is distributed in the hope that it will be useful,
10
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
- # GNU General Public License for more details.
13
- #
14
- # You should have received a copy of the GNU General Public License
15
- # along with this program. If not, see <https://www.gnu.org/licenses/>.
16
- import sys
17
-
18
- import cherrypy
19
-
20
-
21
- def handle_exception(error_table):
22
- t = sys.exc_info()[0]
23
- code = error_table.get(t, 500)
24
- cherrypy.serving.request.error_response = cherrypy.HTTPError(code).set_response
25
-
26
-
27
- cherrypy.tools.errors = cherrypy.Tool('before_error_response', handle_exception, priority=90)