cherrypy-foundation 1.0.0a16__py3-none-any.whl → 1.0.0a18__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.
- cherrypy_foundation/components/Datatable.js +2 -2
- cherrypy_foundation/components/__init__.py +2 -2
- cherrypy_foundation/components/tests/test_static.py +1 -1
- cherrypy_foundation/error_page.py +2 -2
- cherrypy_foundation/flash.py +2 -2
- cherrypy_foundation/form.py +2 -2
- cherrypy_foundation/logging.py +2 -2
- cherrypy_foundation/passwd.py +2 -2
- cherrypy_foundation/plugins/db.py +1 -1
- cherrypy_foundation/plugins/ldap.py +3 -1
- cherrypy_foundation/plugins/restapi.py +1 -1
- cherrypy_foundation/plugins/scheduler.py +1 -1
- cherrypy_foundation/plugins/smtp.py +8 -2
- cherrypy_foundation/plugins/tests/test_db.py +2 -2
- cherrypy_foundation/plugins/tests/test_ldap.py +1 -1
- cherrypy_foundation/plugins/tests/test_scheduler.py +1 -1
- cherrypy_foundation/plugins/tests/test_scheduler_db.py +1 -1
- cherrypy_foundation/plugins/tests/test_smtp.py +31 -1
- cherrypy_foundation/sessions.py +82 -0
- cherrypy_foundation/tests/__init__.py +1 -1
- cherrypy_foundation/tests/test_error_page.py +1 -1
- cherrypy_foundation/tests/test_flash.py +3 -3
- cherrypy_foundation/tests/test_form.py +3 -3
- cherrypy_foundation/tests/test_logging.py +78 -0
- cherrypy_foundation/tests/test_passwd.py +2 -2
- cherrypy_foundation/tests/test_url.py +2 -2
- cherrypy_foundation/tools/auth.py +31 -27
- cherrypy_foundation/tools/auth_mfa.py +87 -83
- cherrypy_foundation/tools/i18n.py +1 -1
- cherrypy_foundation/tools/jinja2.py +1 -1
- cherrypy_foundation/tools/ratelimit.py +33 -19
- cherrypy_foundation/tools/secure_headers.py +1 -1
- cherrypy_foundation/tools/sessions_timeout.py +23 -21
- cherrypy_foundation/tools/tests/test_auth.py +20 -3
- cherrypy_foundation/tools/tests/test_auth_mfa.py +6 -4
- cherrypy_foundation/tools/tests/test_i18n.py +1 -1
- cherrypy_foundation/tools/tests/test_jinja2.py +2 -2
- cherrypy_foundation/tools/tests/test_ratelimit.py +2 -2
- cherrypy_foundation/tools/tests/test_secure_headers.py +4 -4
- cherrypy_foundation/url.py +2 -2
- cherrypy_foundation/widgets.py +2 -2
- {cherrypy_foundation-1.0.0a16.dist-info → cherrypy_foundation-1.0.0a18.dist-info}/METADATA +21 -9
- {cherrypy_foundation-1.0.0a16.dist-info → cherrypy_foundation-1.0.0a18.dist-info}/RECORD +46 -44
- {cherrypy_foundation-1.0.0a16.dist-info → cherrypy_foundation-1.0.0a18.dist-info}/WHEEL +1 -1
- {cherrypy_foundation-1.0.0a16.dist-info → cherrypy_foundation-1.0.0a18.dist-info}/licenses/LICENSE.md +0 -0
- {cherrypy_foundation-1.0.0a16.dist-info → cherrypy_foundation-1.0.0a18.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* Copyright (C)
|
|
2
|
+
* Cherrypy-foundation
|
|
3
|
+
* Copyright (C) 2026 IKUS Software
|
|
4
4
|
*
|
|
5
5
|
* This program is free software: you can redistribute it and/or modify
|
|
6
6
|
* it under the terms of the GNU General Public License as published by
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
# Cherrypy
|
|
2
|
-
# Copyright (C)
|
|
1
|
+
# Cherrypy-foundation
|
|
2
|
+
# Copyright (C) 2026 IKUS Software
|
|
3
3
|
#
|
|
4
4
|
# This program is free software: you can redistribute it and/or modify
|
|
5
5
|
# it under the terms of the GNU General Public License as published by
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
#
|
|
2
|
-
# Copyright (C) 2020-
|
|
1
|
+
# Cherrypy-foundation
|
|
2
|
+
# Copyright (C) 2020-2026 IKUS Software
|
|
3
3
|
#
|
|
4
4
|
# This program is free software: you can redistribute it and/or modify
|
|
5
5
|
# it under the terms of the GNU General Public License as published by
|
cherrypy_foundation/flash.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
#
|
|
2
|
-
# Copyright (C) 2020-
|
|
1
|
+
# Cherrypy-foundation
|
|
2
|
+
# Copyright (C) 2020-2026 IKUS Software
|
|
3
3
|
#
|
|
4
4
|
# This program is free software: you can redistribute it and/or modify
|
|
5
5
|
# it under the terms of the GNU General Public License as published by
|
cherrypy_foundation/form.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
#
|
|
2
|
-
# Copyright (C) 2020-
|
|
1
|
+
# Cherrypy-foundation
|
|
2
|
+
# Copyright (C) 2020-2026 IKUS Software
|
|
3
3
|
#
|
|
4
4
|
# This program is free software: you can redistribute it and/or modify
|
|
5
5
|
# it under the terms of the GNU General Public License as published by
|
cherrypy_foundation/logging.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
#
|
|
2
|
-
# Copyright (C)
|
|
1
|
+
# Cherrypy-foundation
|
|
2
|
+
# Copyright (C) 2026 IKUS Software
|
|
3
3
|
#
|
|
4
4
|
# This program is free software: you can redistribute it and/or modify
|
|
5
5
|
# it under the terms of the GNU General Public License as published by
|
cherrypy_foundation/passwd.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
#
|
|
2
|
-
# Copyright (C)
|
|
1
|
+
# Cherrypy-foundation
|
|
2
|
+
# Copyright (C) 2026 IKUS Software
|
|
3
3
|
#
|
|
4
4
|
# This program is free software: you can redistribute it and/or modify
|
|
5
5
|
# it under the terms of the GNU General Public License as published by
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# LDAP Plugins for cherrypy
|
|
2
|
-
# Copyright (C)
|
|
2
|
+
# Copyright (C) 2026 IKUS Software
|
|
3
3
|
#
|
|
4
4
|
# This program is free software: you can redistribute it and/or modify
|
|
5
5
|
# it under the terms of the GNU General Public License as published by
|
|
@@ -101,6 +101,7 @@ class LdapPlugin(SimplePlugin):
|
|
|
101
101
|
firstname_attribute = None
|
|
102
102
|
lastname_attribute = None
|
|
103
103
|
email_attribute = None
|
|
104
|
+
pool_size = 10
|
|
104
105
|
|
|
105
106
|
def start(self):
|
|
106
107
|
# Don't configure this plugin if the ldap URI is not provided.
|
|
@@ -118,6 +119,7 @@ class LdapPlugin(SimplePlugin):
|
|
|
118
119
|
version=self.version,
|
|
119
120
|
raise_exceptions=True,
|
|
120
121
|
client_strategy=ldap3.REUSABLE,
|
|
122
|
+
pool_size=self.pool_size,
|
|
121
123
|
)
|
|
122
124
|
|
|
123
125
|
def stop(self):
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# SMTP Plugins for cherrypy
|
|
2
|
-
# Copyright (C)
|
|
2
|
+
# Copyright (C) 2026 IKUS Software
|
|
3
3
|
#
|
|
4
4
|
# This program is free software: you can redistribute it and/or modify
|
|
5
5
|
# it under the terms of the GNU General Public License as published by
|
|
@@ -127,6 +127,7 @@ class SmtpPlugin(SimplePlugin):
|
|
|
127
127
|
password = None
|
|
128
128
|
encryption = None
|
|
129
129
|
email_from = None
|
|
130
|
+
bcc = None
|
|
130
131
|
|
|
131
132
|
def _create_msg(self, subject: str, message: str, to=None, cc=None, bcc=None, reply_to=None, headers={}):
|
|
132
133
|
assert subject
|
|
@@ -141,8 +142,13 @@ class SmtpPlugin(SimplePlugin):
|
|
|
141
142
|
msg['To'] = _formataddr(to)
|
|
142
143
|
if cc:
|
|
143
144
|
msg['Cc'] = _formataddr(cc)
|
|
145
|
+
bcc_list = []
|
|
146
|
+
if self.bcc:
|
|
147
|
+
bcc_list.append(_formataddr(self.bcc))
|
|
144
148
|
if bcc:
|
|
145
|
-
|
|
149
|
+
bcc_list.append(_formataddr(bcc))
|
|
150
|
+
if bcc_list:
|
|
151
|
+
msg['Bcc'] = ', '.join(bcc_list)
|
|
146
152
|
if reply_to:
|
|
147
153
|
msg['Reply-To'] = _formataddr(reply_to)
|
|
148
154
|
msg['Message-ID'] = email.utils.make_msgid()
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
# Cherrypy
|
|
2
|
-
# Copyright (C) 2022-
|
|
1
|
+
# Cherrypy-foundation
|
|
2
|
+
# Copyright (C) 2022-2026 IKUS Software
|
|
3
3
|
#
|
|
4
4
|
# This program is free software: you can redistribute it and/or modify
|
|
5
5
|
# it under the terms of the GNU General Public License as published by
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# SMTP Plugins for cherrypy
|
|
2
|
-
# Copyright (C) 2022-
|
|
2
|
+
# Copyright (C) 2022-2026 IKUS Software
|
|
3
3
|
#
|
|
4
4
|
# This program is free software: you can redistribute it and/or modify
|
|
5
5
|
# it under the terms of the GNU General Public License as published by
|
|
@@ -18,6 +18,7 @@ from unittest import mock, skipUnless
|
|
|
18
18
|
|
|
19
19
|
import cherrypy
|
|
20
20
|
from cherrypy.test import helper
|
|
21
|
+
from parameterized import parameterized
|
|
21
22
|
|
|
22
23
|
from .. import smtp # noqa
|
|
23
24
|
|
|
@@ -108,3 +109,32 @@ Here is the link [1] you wanted.
|
|
|
108
109
|
self.assertEqual(
|
|
109
110
|
'test2@test.com, TEST3 <test3@test.com>', smtp._formataddr(['test2@test.com', ('TEST3', 'test3@test.com')])
|
|
110
111
|
)
|
|
112
|
+
|
|
113
|
+
@parameterized.expand(
|
|
114
|
+
[
|
|
115
|
+
(None, None, None),
|
|
116
|
+
(None, 'test2@test.com', 'test2@test.com'),
|
|
117
|
+
('test2@test.com', None, 'test2@test.com'),
|
|
118
|
+
('test1@test.com', 'test2@test.com', 'test1@test.com, test2@test.com'),
|
|
119
|
+
]
|
|
120
|
+
)
|
|
121
|
+
def test_with_bcc(self, smtp_bcc, bcc, expected_bcc):
|
|
122
|
+
# Given a valid smtp server
|
|
123
|
+
with mock.patch(smtp.__name__ + '.smtplib') as smtplib:
|
|
124
|
+
# Given bcc is defined at plugin level.
|
|
125
|
+
cherrypy.smtp.bcc = smtp_bcc
|
|
126
|
+
# When publishing a send_mail with Bcc
|
|
127
|
+
cherrypy.engine.publish(
|
|
128
|
+
'send_mail',
|
|
129
|
+
to=('A name', 'target@test.com'),
|
|
130
|
+
subject='subjet',
|
|
131
|
+
message='body',
|
|
132
|
+
bcc=bcc,
|
|
133
|
+
)
|
|
134
|
+
# Then message is sent
|
|
135
|
+
smtplib.SMTP.assert_called_once_with('__default__', 25)
|
|
136
|
+
smtplib.SMTP.return_value.send_message.assert_called_once_with(mock.ANY)
|
|
137
|
+
msg = smtplib.SMTP.return_value.send_message.call_args.args[0]
|
|
138
|
+
smtplib.SMTP.return_value.quit.assert_called_once_with()
|
|
139
|
+
# Message include our expected Bcc
|
|
140
|
+
self.assertEqual(expected_bcc, msg['Bcc'])
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Session timeout tool for cherrypy
|
|
2
|
+
# Copyright (C) 2012-2026 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
|
+
# BFH Science Self-Service Portal
|
|
18
|
+
# Copyright (C) 2025-2026 IKUS Software
|
|
19
|
+
#
|
|
20
|
+
# This program is free software: you can redistribute it and/or modify
|
|
21
|
+
# it under the terms of the GNU General Public License as published by
|
|
22
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
23
|
+
# (at your option) any later version.
|
|
24
|
+
#
|
|
25
|
+
# This program is distributed in the hope that it will be useful,
|
|
26
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
27
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
28
|
+
# GNU General Public License for more details.
|
|
29
|
+
#
|
|
30
|
+
# You should have received a copy of the GNU General Public License
|
|
31
|
+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
32
|
+
import time
|
|
33
|
+
from contextlib import contextmanager
|
|
34
|
+
|
|
35
|
+
import cherrypy
|
|
36
|
+
import zc.lockfile
|
|
37
|
+
from cherrypy.lib import locking
|
|
38
|
+
from cherrypy.lib.sessions import FileSession as CPFileSession
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# Issue in cherrypy: https://github.com/cherrypy/cherrypy/issues/2065
|
|
42
|
+
class FileSession(CPFileSession):
|
|
43
|
+
"""
|
|
44
|
+
Override implementation of cherrpy file session to improve file locking.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
def acquire_lock(self, path=None):
|
|
48
|
+
"""Acquire an exclusive lock on the currently-loaded session data."""
|
|
49
|
+
if path is None:
|
|
50
|
+
path = self._get_file_path()
|
|
51
|
+
path += self.LOCK_SUFFIX
|
|
52
|
+
checker = locking.LockChecker(self.id, self.lock_timeout)
|
|
53
|
+
while not checker.expired():
|
|
54
|
+
try:
|
|
55
|
+
self.lock = zc.lockfile.LockFile(path)
|
|
56
|
+
except zc.lockfile.LockError:
|
|
57
|
+
# Sleep for 1ms only.
|
|
58
|
+
time.sleep(0.001)
|
|
59
|
+
else:
|
|
60
|
+
break
|
|
61
|
+
self.locked = True
|
|
62
|
+
if self.debug:
|
|
63
|
+
cherrypy.log('Lock acquired.', 'TOOLS.SESSIONS')
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@contextmanager
|
|
67
|
+
def session_lock():
|
|
68
|
+
"""
|
|
69
|
+
Acquire session lock as required. Support re-intrant lock.
|
|
70
|
+
"""
|
|
71
|
+
s = cherrypy.serving.session
|
|
72
|
+
if s.locking == 'explicit' and not s.locked:
|
|
73
|
+
s.acquire_lock()
|
|
74
|
+
try:
|
|
75
|
+
yield s
|
|
76
|
+
finally:
|
|
77
|
+
# When explicit, we want to save the session (with also release the lock.)
|
|
78
|
+
if s.locking == 'explicit':
|
|
79
|
+
s.save()
|
|
80
|
+
cherrypy.serving.request._sessionsaved = True
|
|
81
|
+
else:
|
|
82
|
+
yield s
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
#
|
|
2
|
-
# Copyright (C)
|
|
1
|
+
# Cherrypy-foundation
|
|
2
|
+
# Copyright (C) 2026 IKUS Software
|
|
3
3
|
#
|
|
4
4
|
# This program is free software: you can redistribute it and/or modify
|
|
5
5
|
# it under the terms of the GNU General Public License as published by
|
|
@@ -30,7 +30,7 @@ env = cherrypy.tools.jinja2.create_env(
|
|
|
30
30
|
)
|
|
31
31
|
|
|
32
32
|
|
|
33
|
-
@cherrypy.tools.sessions()
|
|
33
|
+
@cherrypy.tools.sessions(locking='explicit')
|
|
34
34
|
@cherrypy.tools.jinja2(env=env)
|
|
35
35
|
class Root:
|
|
36
36
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
#
|
|
2
|
-
# Copyright (C)
|
|
1
|
+
# Cherrypy-foundation
|
|
2
|
+
# Copyright (C) 2026 IKUS Software
|
|
3
3
|
#
|
|
4
4
|
# This program is free software: you can redistribute it and/or modify
|
|
5
5
|
# it under the terms of the GNU General Public License as published by
|
|
@@ -69,7 +69,7 @@ class LoginForm(CherryForm):
|
|
|
69
69
|
)
|
|
70
70
|
|
|
71
71
|
|
|
72
|
-
@cherrypy.tools.sessions()
|
|
72
|
+
@cherrypy.tools.sessions(locking='explicit')
|
|
73
73
|
@cherrypy.tools.jinja2(env=env)
|
|
74
74
|
class Root:
|
|
75
75
|
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# CherryPy
|
|
2
|
+
# Copyright (C) 2026 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 tempfile
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
|
|
20
|
+
import cherrypy
|
|
21
|
+
from cherrypy.test import helper
|
|
22
|
+
|
|
23
|
+
from ..logging import setup_logging
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Root:
|
|
27
|
+
|
|
28
|
+
@cherrypy.expose
|
|
29
|
+
def index(self):
|
|
30
|
+
return "OK"
|
|
31
|
+
|
|
32
|
+
@cherrypy.expose
|
|
33
|
+
def error(self):
|
|
34
|
+
cherrypy.log('error messages to be logged')
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class LoggingTest(helper.CPWebCase):
|
|
38
|
+
interactive = False
|
|
39
|
+
|
|
40
|
+
@classmethod
|
|
41
|
+
def setup_server(cls):
|
|
42
|
+
cls.tempdir = tempfile.TemporaryDirectory(prefix='cherrypy-foundation-', suffix='-logging')
|
|
43
|
+
cls.log_access_file = f'{cls.tempdir.name}/access.log'
|
|
44
|
+
cls.log_file = f'{cls.tempdir.name}/error.log'
|
|
45
|
+
setup_logging(log_file=cls.log_file, log_access_file=cls.log_access_file, level='DEBUG')
|
|
46
|
+
cherrypy.tree.mount(Root(), '/')
|
|
47
|
+
|
|
48
|
+
@classmethod
|
|
49
|
+
def teardown_class(cls):
|
|
50
|
+
# Delete temp folder
|
|
51
|
+
cls.tempdir.cleanup()
|
|
52
|
+
# Stop server
|
|
53
|
+
super().teardown_class()
|
|
54
|
+
# Reset logging to default.
|
|
55
|
+
# Re-enable screen logging
|
|
56
|
+
cherrypy.config.update({'log.screen': True, 'log.error_file': '', 'log.access_file': ''})
|
|
57
|
+
# Reset internal logger references
|
|
58
|
+
cherrypy.log.error_file = None
|
|
59
|
+
cherrypy.log.access_file = None
|
|
60
|
+
|
|
61
|
+
def test_logging_access(self):
|
|
62
|
+
mtime = Path(self.log_access_file).stat().st_mtime
|
|
63
|
+
# When page get queried
|
|
64
|
+
self.getPage('/')
|
|
65
|
+
# Then access files get updated
|
|
66
|
+
mtime2 = Path(self.log_access_file).stat().st_mtime
|
|
67
|
+
self.assertNotEqual(mtime, mtime2)
|
|
68
|
+
|
|
69
|
+
def test_logging_error(self):
|
|
70
|
+
data = Path(self.log_file).read_text()
|
|
71
|
+
self.assertNotIn('error messages to be logged', data)
|
|
72
|
+
# When page get queried
|
|
73
|
+
self.getPage('/error')
|
|
74
|
+
self.assertStatus(200)
|
|
75
|
+
# Then access files get updated
|
|
76
|
+
data2 = Path(self.log_file).read_text()
|
|
77
|
+
self.assertNotEqual(data, data2)
|
|
78
|
+
self.assertIn('error messages to be logged', data2)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
#
|
|
2
|
-
# Copyright (C)
|
|
1
|
+
# Cherrypy-foundation
|
|
2
|
+
# Copyright (C) 2026 IKUS Software
|
|
3
3
|
#
|
|
4
4
|
# This program is free software: you can redistribute it and/or modify
|
|
5
5
|
# it under the terms of the GNU General Public License as published by
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
#
|
|
2
|
-
# Copyright (C)
|
|
1
|
+
# Cherrypy-foundation
|
|
2
|
+
# Copyright (C) 2026 IKUS Software
|
|
3
3
|
#
|
|
4
4
|
# This program is free software: you can redistribute it and/or modify
|
|
5
5
|
# it under the terms of the GNU General Public License as published by
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# Authentication tools for cherrypy
|
|
2
|
-
# Copyright (C)
|
|
2
|
+
# Copyright (C) 2026 IKUS Software
|
|
3
3
|
#
|
|
4
4
|
# This program is free software: you can redistribute it and/or modify
|
|
5
5
|
# it under the terms of the GNU General Public License as published by
|
|
@@ -20,6 +20,8 @@ import urllib.parse
|
|
|
20
20
|
|
|
21
21
|
import cherrypy
|
|
22
22
|
|
|
23
|
+
from cherrypy_foundation.sessions import session_lock
|
|
24
|
+
|
|
23
25
|
AUTH_LAST_PASSWORD_AT = '_auth_last_password_at'
|
|
24
26
|
AUTH_METHOD = '_auth_method'
|
|
25
27
|
AUTH_ORIGINAL_URL = '_auth_original_url'
|
|
@@ -71,17 +73,17 @@ class AuthManager(cherrypy.Tool):
|
|
|
71
73
|
if not hasattr(cherrypy.serving, 'session'):
|
|
72
74
|
return
|
|
73
75
|
# Check if a user_key is stored in session.
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
76
|
+
with session_lock() as session:
|
|
77
|
+
user_key = session.get(self._session_user_key())
|
|
78
|
+
if not user_key:
|
|
79
|
+
return
|
|
78
80
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
81
|
+
last = session.get(AUTH_LAST_PASSWORD_AT)
|
|
82
|
+
if last is None:
|
|
83
|
+
return # never had a password login in this session
|
|
84
|
+
timeout = self._reauth_timeout_minutes()
|
|
85
|
+
if (last + datetime.timedelta(minutes=timeout)) < session.now():
|
|
86
|
+
return
|
|
85
87
|
|
|
86
88
|
# Mark request as authenticated by key; user object will be resolved later.
|
|
87
89
|
cherrypy.serving.request.login = user_key
|
|
@@ -154,7 +156,8 @@ class AuthManager(cherrypy.Tool):
|
|
|
154
156
|
)
|
|
155
157
|
# If we reach here, authentication failed
|
|
156
158
|
if hasattr(cherrypy.serving, 'session'):
|
|
157
|
-
|
|
159
|
+
with session_lock() as session:
|
|
160
|
+
session.regenerate() # Prevent session analysis
|
|
158
161
|
|
|
159
162
|
remote_ip = cherrypy.serving.request.remote.ip
|
|
160
163
|
cherrypy.log(
|
|
@@ -195,13 +198,13 @@ class AuthManager(cherrypy.Tool):
|
|
|
195
198
|
|
|
196
199
|
# Store in session
|
|
197
200
|
if hasattr(cherrypy.serving, 'session'):
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
201
|
+
with session_lock() as session:
|
|
202
|
+
session_user_key = self._session_user_key()
|
|
203
|
+
session[session_user_key] = user_key
|
|
204
|
+
session[AUTH_METHOD] = auth_method
|
|
205
|
+
session[AUTH_LAST_PASSWORD_AT] = session.now()
|
|
206
|
+
# Generate a new session id
|
|
207
|
+
session.regenerate()
|
|
205
208
|
|
|
206
209
|
# When authenticated, store user_key in request.
|
|
207
210
|
cherrypy.serving.request.login = user_key
|
|
@@ -213,9 +216,9 @@ class AuthManager(cherrypy.Tool):
|
|
|
213
216
|
"""
|
|
214
217
|
Clear session data and generate a new session id.
|
|
215
218
|
"""
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
+
with session_lock() as session:
|
|
220
|
+
session.clear()
|
|
221
|
+
session.regenerate()
|
|
219
222
|
|
|
220
223
|
def get_original_url(self):
|
|
221
224
|
"""
|
|
@@ -223,15 +226,16 @@ class AuthManager(cherrypy.Tool):
|
|
|
223
226
|
"""
|
|
224
227
|
if not hasattr(cherrypy.serving, 'session'):
|
|
225
228
|
return None
|
|
226
|
-
|
|
229
|
+
with session_lock() as session:
|
|
230
|
+
return session.get(AUTH_ORIGINAL_URL)
|
|
227
231
|
|
|
228
232
|
def get_user_key(self):
|
|
229
233
|
"""Return the last username."""
|
|
230
234
|
if not hasattr(cherrypy.serving, 'session'):
|
|
231
235
|
return False
|
|
232
|
-
session = cherrypy.serving.session
|
|
233
236
|
session_user_key = self._session_user_key()
|
|
234
|
-
|
|
237
|
+
with session_lock() as session:
|
|
238
|
+
return session.get(session_user_key)
|
|
235
239
|
|
|
236
240
|
def redirect_to_original_url(self):
|
|
237
241
|
# Redirect user to original URL
|
|
@@ -252,8 +256,8 @@ class AuthManager(cherrypy.Tool):
|
|
|
252
256
|
query_string = request.query_string
|
|
253
257
|
|
|
254
258
|
# Store value in session
|
|
255
|
-
|
|
256
|
-
|
|
259
|
+
with session_lock() as session:
|
|
260
|
+
session[AUTH_ORIGINAL_URL] = cherrypy.url(original_url, qs=query_string, base='')
|
|
257
261
|
|
|
258
262
|
|
|
259
263
|
cherrypy.tools.auth = AuthManager()
|