cherrypy-foundation 1.0.0__py3-none-any.whl → 1.0.0a1__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/ColorModes.jinja +4 -5
- cherrypy_foundation/components/Datatable.jinja +2 -2
- cherrypy_foundation/components/Datatable.js +2 -2
- cherrypy_foundation/components/Field.jinja +6 -16
- cherrypy_foundation/components/Fields.jinja +2 -0
- cherrypy_foundation/components/Typeahead.css +1 -6
- cherrypy_foundation/components/Typeahead.jinja +2 -2
- cherrypy_foundation/components/__init__.py +2 -2
- cherrypy_foundation/components/tests/test_static.py +1 -1
- cherrypy_foundation/error_page.py +17 -20
- cherrypy_foundation/flash.py +15 -17
- cherrypy_foundation/form.py +2 -2
- cherrypy_foundation/logging.py +2 -2
- cherrypy_foundation/passwd.py +2 -2
- cherrypy_foundation/plugins/db.py +9 -35
- cherrypy_foundation/plugins/ldap.py +38 -46
- cherrypy_foundation/plugins/restapi.py +1 -1
- cherrypy_foundation/plugins/scheduler.py +84 -208
- cherrypy_foundation/plugins/smtp.py +46 -78
- cherrypy_foundation/plugins/tests/test_db.py +4 -4
- cherrypy_foundation/plugins/tests/test_ldap.py +3 -76
- cherrypy_foundation/plugins/tests/test_scheduler.py +50 -58
- cherrypy_foundation/plugins/tests/test_smtp.py +7 -40
- cherrypy_foundation/tests/__init__.py +0 -72
- cherrypy_foundation/tests/test_error_page.py +1 -7
- cherrypy_foundation/tests/test_passwd.py +2 -2
- cherrypy_foundation/tools/auth.py +38 -59
- cherrypy_foundation/tools/auth_mfa.py +88 -89
- cherrypy_foundation/tools/errors.py +27 -0
- cherrypy_foundation/tools/i18n.py +153 -246
- cherrypy_foundation/tools/jinja2.py +13 -29
- cherrypy_foundation/tools/ratelimit.py +27 -37
- cherrypy_foundation/tools/secure_headers.py +5 -1
- cherrypy_foundation/tools/sessions_timeout.py +21 -23
- cherrypy_foundation/tools/tests/locales/en/LC_MESSAGES/messages.mo +0 -0
- cherrypy_foundation/tools/tests/locales/{de → en}/LC_MESSAGES/messages.po +2 -2
- cherrypy_foundation/tools/tests/test_auth.py +4 -21
- cherrypy_foundation/tools/tests/test_i18n.py +6 -81
- cherrypy_foundation/tools/tests/test_ratelimit.py +2 -2
- cherrypy_foundation/url.py +25 -25
- cherrypy_foundation/widgets.py +2 -2
- cherrypy_foundation-1.0.0a1.dist-info/METADATA +42 -0
- {cherrypy_foundation-1.0.0.dist-info → cherrypy_foundation-1.0.0a1.dist-info}/RECORD +46 -65
- {cherrypy_foundation-1.0.0.dist-info → cherrypy_foundation-1.0.0a1.dist-info}/WHEEL +1 -1
- cherrypy_foundation/components/Flash.jinja +0 -13
- cherrypy_foundation/components/LocaleSelection.jinja +0 -13
- cherrypy_foundation/components/LocaleSelection.js +0 -26
- cherrypy_foundation/plugins/tests/test_scheduler_db.py +0 -107
- cherrypy_foundation/sessions.py +0 -93
- cherrypy_foundation/tests/templates/test_flash.html +0 -9
- cherrypy_foundation/tests/templates/test_form.html +0 -16
- cherrypy_foundation/tests/templates/test_url.html +0 -15
- cherrypy_foundation/tests/test_flash.py +0 -61
- cherrypy_foundation/tests/test_form.py +0 -148
- cherrypy_foundation/tests/test_logging.py +0 -78
- cherrypy_foundation/tests/test_sessions.py +0 -89
- cherrypy_foundation/tests/test_url.py +0 -161
- cherrypy_foundation/tools/tests/components/Button.jinja +0 -2
- cherrypy_foundation/tools/tests/locales/de/LC_MESSAGES/messages.mo +0 -0
- cherrypy_foundation/tools/tests/templates/test_jinja2.html +0 -11
- cherrypy_foundation/tools/tests/templates/test_jinjax.html +0 -9
- cherrypy_foundation/tools/tests/templates/test_jinjax_i18n.html +0 -22
- cherrypy_foundation/tools/tests/test_auth_mfa.py +0 -369
- cherrypy_foundation/tools/tests/test_jinja2.py +0 -153
- cherrypy_foundation/tools/tests/test_secure_headers.py +0 -200
- cherrypy_foundation-1.0.0.dist-info/METADATA +0 -71
- {cherrypy_foundation-1.0.0.dist-info → cherrypy_foundation-1.0.0a1.dist-info}/licenses/LICENSE.md +0 -0
- {cherrypy_foundation-1.0.0.dist-info → cherrypy_foundation-1.0.0a1.dist-info}/top_level.txt +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# Scheduler plugins for Cherrypy
|
|
2
|
-
# Copyright (C)
|
|
2
|
+
# Copyright (C) 2022-2025 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
|
|
@@ -14,87 +14,79 @@
|
|
|
14
14
|
# You should have received a copy of the GNU General Public License
|
|
15
15
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
"""
|
|
18
|
+
Created on Oct 17, 2015
|
|
19
|
+
|
|
20
|
+
@author: Patrik Dufresne <patrik@ikus-soft.com>
|
|
21
|
+
"""
|
|
22
|
+
import importlib.util
|
|
23
|
+
import unittest
|
|
24
|
+
from time import sleep
|
|
19
25
|
|
|
20
26
|
import cherrypy
|
|
21
27
|
from cherrypy.test import helper
|
|
22
28
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
done = Event()
|
|
29
|
+
HAS_APSCHEDULER = importlib.util.find_spec("apscheduler") is not None
|
|
26
30
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
done.set()
|
|
31
|
+
if HAS_APSCHEDULER:
|
|
32
|
+
from .. import scheduler # noqa
|
|
30
33
|
|
|
31
34
|
|
|
35
|
+
@unittest.skipUnless(HAS_APSCHEDULER, "apscheduler not installed")
|
|
32
36
|
class SchedulerPluginTest(helper.CPWebCase):
|
|
33
37
|
def setUp(self) -> None:
|
|
34
|
-
|
|
35
|
-
cherrypy.scheduler.remove_all_jobs()
|
|
38
|
+
self.called = False
|
|
36
39
|
return super().setUp()
|
|
37
40
|
|
|
38
|
-
def tearDown(self):
|
|
39
|
-
return super().tearDown()
|
|
40
|
-
|
|
41
41
|
@classmethod
|
|
42
42
|
def setup_server(cls):
|
|
43
43
|
pass
|
|
44
44
|
|
|
45
|
-
def
|
|
46
|
-
# Given a scheduled job
|
|
47
|
-
scheduled = cherrypy.engine.publish(
|
|
48
|
-
'scheduler:add_job',
|
|
49
|
-
a_task,
|
|
50
|
-
name='custom_name',
|
|
51
|
-
args=(1, 2, 3),
|
|
52
|
-
kwargs={'foo': 'bar'},
|
|
53
|
-
next_run_time=datetime.now(timezone.utc),
|
|
54
|
-
misfire_grace_time=None,
|
|
55
|
-
)
|
|
56
|
-
self.assertTrue(scheduled)
|
|
57
|
-
self.assertEqual('custom_name', scheduled[0].name)
|
|
58
|
-
# When waiting for all jobs
|
|
59
|
-
cherrypy.scheduler.wait_for_jobs()
|
|
60
|
-
# Then the job is done
|
|
61
|
-
self.assertTrue(done.is_set())
|
|
62
|
-
|
|
63
|
-
def test_add_job_daily(self):
|
|
45
|
+
def test_schedule_job(self):
|
|
64
46
|
# Given a scheduler with a specific number of jobs
|
|
65
|
-
count = len(cherrypy.scheduler.
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
47
|
+
count = len(cherrypy.scheduler.list_jobs())
|
|
48
|
+
|
|
49
|
+
# Given a job schedule every seconds
|
|
50
|
+
def a_job(*args, **kwargs):
|
|
51
|
+
self.called = True
|
|
52
|
+
|
|
53
|
+
scheduled = cherrypy.engine.publish('schedule_job', '23:00', a_job, 1, 2, 3, foo=1, bar=2)
|
|
69
54
|
self.assertTrue(scheduled)
|
|
70
|
-
self.assertEqual(
|
|
71
|
-
# Then the number of jobs increase.
|
|
72
|
-
self.assertEqual(count + 1, len(cherrypy.scheduler.get_jobs()))
|
|
55
|
+
self.assertEqual(count + 1, len(cherrypy.scheduler.list_jobs()))
|
|
73
56
|
|
|
74
|
-
def
|
|
57
|
+
def test_scheduler_task(self):
|
|
75
58
|
# Given a task
|
|
59
|
+
|
|
60
|
+
def a_task(*args, **kwargs):
|
|
61
|
+
self.called = True
|
|
62
|
+
|
|
76
63
|
# When scheduling that task
|
|
77
|
-
scheduled = cherrypy.engine.publish('
|
|
64
|
+
scheduled = cherrypy.engine.publish('schedule_task', a_task, 1, 2, 3, foo=1, bar=2)
|
|
78
65
|
self.assertTrue(scheduled)
|
|
79
|
-
|
|
80
|
-
cherrypy.scheduler.
|
|
66
|
+
sleep(1)
|
|
67
|
+
while len(cherrypy.scheduler.list_tasks()) >= 1:
|
|
68
|
+
sleep(1)
|
|
81
69
|
# Then the task get called
|
|
82
|
-
self.assertTrue(
|
|
70
|
+
self.assertTrue(self.called)
|
|
83
71
|
|
|
84
|
-
def
|
|
72
|
+
def test_unschedule_job(self):
|
|
85
73
|
# Given a scheduler with a specific number of jobs
|
|
86
|
-
count = len(cherrypy.scheduler.
|
|
74
|
+
count = len(cherrypy.scheduler.list_jobs())
|
|
75
|
+
|
|
87
76
|
# Given a job schedule every seconds
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
cherrypy.
|
|
93
|
-
|
|
94
|
-
self.assertEqual(count, len(cherrypy.scheduler.
|
|
95
|
-
|
|
96
|
-
def
|
|
77
|
+
def a_job(*args, **kwargs):
|
|
78
|
+
self.called = True
|
|
79
|
+
|
|
80
|
+
cherrypy.engine.publish('schedule_job', '23:00', a_job, 1, 2, 3, foo=1, bar=2)
|
|
81
|
+
self.assertEqual(count + 1, len(cherrypy.scheduler.list_jobs()))
|
|
82
|
+
cherrypy.engine.publish('unschedule_job', a_job)
|
|
83
|
+
self.assertEqual(count, len(cherrypy.scheduler.list_jobs()))
|
|
84
|
+
|
|
85
|
+
def test_unschedule_job_with_invalid_job(self):
|
|
97
86
|
# Given an unschedule job
|
|
87
|
+
def a_job(*args, **kwargs):
|
|
88
|
+
self.called = True
|
|
89
|
+
|
|
98
90
|
# When unscheduling an invalid job
|
|
99
|
-
cherrypy.engine.publish('
|
|
100
|
-
# Then
|
|
91
|
+
cherrypy.engine.publish('unschedule_job', a_job)
|
|
92
|
+
# Then no error are raised
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# SMTP Plugins for cherrypy
|
|
2
|
-
# Copyright (C) 2022-
|
|
2
|
+
# Copyright (C) 2022-2025 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,7 +18,6 @@ from unittest import mock, skipUnless
|
|
|
18
18
|
|
|
19
19
|
import cherrypy
|
|
20
20
|
from cherrypy.test import helper
|
|
21
|
-
from parameterized import parameterized
|
|
22
21
|
|
|
23
22
|
from .. import smtp # noqa
|
|
24
23
|
|
|
@@ -64,15 +63,12 @@ class SmtpPluginTest(helper.CPWebCase):
|
|
|
64
63
|
|
|
65
64
|
@skipUnless(hasattr(cherrypy, 'scheduler'), reason='Required scheduler')
|
|
66
65
|
def test_queue_mail(self):
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
smtplib.SMTP.assert_called_once_with('__default__', 25)
|
|
74
|
-
smtplib.SMTP.return_value.send_message.assert_called_once_with(mock.ANY)
|
|
75
|
-
smtplib.SMTP.return_value.quit.assert_called_once_with()
|
|
66
|
+
# Given a paused scheduler plugin
|
|
67
|
+
cherrypy.scheduler._scheduler.pause()
|
|
68
|
+
# When queueing a email
|
|
69
|
+
cherrypy.engine.publish('queue_mail', to='target@test.com', subject='subjet', message='body')
|
|
70
|
+
# Then a new job get schedule
|
|
71
|
+
self.assertEqual(1, len(cherrypy.scheduler.list_tasks()))
|
|
76
72
|
|
|
77
73
|
def test_html2plaintext(self):
|
|
78
74
|
"""
|
|
@@ -109,32 +105,3 @@ Here is the link [1] you wanted.
|
|
|
109
105
|
self.assertEqual(
|
|
110
106
|
'test2@test.com, TEST3 <test3@test.com>', smtp._formataddr(['test2@test.com', ('TEST3', 'test3@test.com')])
|
|
111
107
|
)
|
|
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'])
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
# Cherrypy-foundation
|
|
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 os
|
|
18
|
-
import tempfile
|
|
19
|
-
import unittest
|
|
20
|
-
from contextlib import contextmanager
|
|
21
|
-
|
|
22
|
-
from selenium import webdriver
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
class SeleniumUnitTest:
|
|
26
|
-
|
|
27
|
-
@property
|
|
28
|
-
def _session_id(self):
|
|
29
|
-
if hasattr(self, 'cookies') and self.cookies:
|
|
30
|
-
for unused, value in self.cookies:
|
|
31
|
-
for part in value.split(';'):
|
|
32
|
-
key, unused, value = part.partition('=')
|
|
33
|
-
if key == 'session_id':
|
|
34
|
-
return value
|
|
35
|
-
|
|
36
|
-
@contextmanager
|
|
37
|
-
def selenium(self, headless=True, implicitly_wait=3):
|
|
38
|
-
"""
|
|
39
|
-
Decorator to load selenium for a test.
|
|
40
|
-
"""
|
|
41
|
-
# Skip selenium test is display is not available.
|
|
42
|
-
if not os.environ.get('DISPLAY', False):
|
|
43
|
-
raise unittest.SkipTest("selenium require a display")
|
|
44
|
-
# Start selenium driver
|
|
45
|
-
options = webdriver.ChromeOptions()
|
|
46
|
-
if headless:
|
|
47
|
-
options.add_argument('--headless')
|
|
48
|
-
options.add_argument('--disable-gpu')
|
|
49
|
-
options.add_argument('--window-size=1280,800')
|
|
50
|
-
options.add_argument('--no-sandbox')
|
|
51
|
-
options.add_argument('--disable-dev-shm-usage')
|
|
52
|
-
options.add_argument('--lang=en-US')
|
|
53
|
-
driver = webdriver.Chrome(options=options)
|
|
54
|
-
try:
|
|
55
|
-
# If logged in, reuse the same session id.
|
|
56
|
-
if self._session_id:
|
|
57
|
-
driver.get(f'{self.baseurl}/login/')
|
|
58
|
-
driver.add_cookie({"name": "session_id", "value": self.session_id})
|
|
59
|
-
# Configure download folder
|
|
60
|
-
download = os.path.join(os.path.expanduser('~'), 'Downloads')
|
|
61
|
-
os.makedirs(download, exist_ok=True)
|
|
62
|
-
self._selenium_download_dir = tempfile.mkdtemp(dir=download, prefix='selenium-download-')
|
|
63
|
-
driver.execute_cdp_cmd(
|
|
64
|
-
'Page.setDownloadBehavior', {'behavior': 'allow', 'downloadPath': self._selenium_download_dir}
|
|
65
|
-
)
|
|
66
|
-
# Set default wait.
|
|
67
|
-
driver.implicitly_wait(implicitly_wait)
|
|
68
|
-
yield driver
|
|
69
|
-
finally:
|
|
70
|
-
# Code to release resource, e.g.:
|
|
71
|
-
driver.close()
|
|
72
|
-
driver = None
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# CherryPy
|
|
2
|
-
# Copyright (C)
|
|
2
|
+
# Copyright (C) 2025 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
|
|
@@ -29,10 +29,6 @@ class Root:
|
|
|
29
29
|
|
|
30
30
|
@cherrypy.expose
|
|
31
31
|
def not_found(self):
|
|
32
|
-
raise cherrypy.NotFound()
|
|
33
|
-
|
|
34
|
-
@cherrypy.expose
|
|
35
|
-
def not_found_custom(self):
|
|
36
32
|
raise cherrypy.HTTPError(404, message='My error message')
|
|
37
33
|
|
|
38
34
|
@cherrypy.expose
|
|
@@ -64,8 +60,6 @@ class ErrorPageTest(helper.CPWebCase):
|
|
|
64
60
|
|
|
65
61
|
@parameterized.expand(
|
|
66
62
|
[
|
|
67
|
-
('/not_found', '<p>Nothing matches the given URI</p>'),
|
|
68
|
-
('/not_found_custom', '<p>My error message</p>'),
|
|
69
63
|
('/html_error', '<p>My error message</p>'),
|
|
70
64
|
('/json_error', '{"message": "json error message", "status": "400 Bad Request"}'),
|
|
71
65
|
('/text_error', 'text error message'),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
#
|
|
2
|
-
# Copyright (C)
|
|
1
|
+
# CherryPy Foundation
|
|
2
|
+
# Copyright (C) 2025 IKUS Software inc.
|
|
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) 2025 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,8 +20,6 @@ import urllib.parse
|
|
|
20
20
|
|
|
21
21
|
import cherrypy
|
|
22
22
|
|
|
23
|
-
from cherrypy_foundation.sessions import session_lock
|
|
24
|
-
|
|
25
23
|
AUTH_LAST_PASSWORD_AT = '_auth_last_password_at'
|
|
26
24
|
AUTH_METHOD = '_auth_method'
|
|
27
25
|
AUTH_ORIGINAL_URL = '_auth_original_url'
|
|
@@ -31,6 +29,9 @@ AUTH_DEFAULT_SESSION_KEY = "_auth_session_key"
|
|
|
31
29
|
AUTH_DEFAULT_REAUTH_TIMEOUT = 60 # minutes
|
|
32
30
|
|
|
33
31
|
|
|
32
|
+
logger = logging.getLogger(__name__)
|
|
33
|
+
|
|
34
|
+
|
|
34
35
|
class AuthManager(cherrypy.Tool):
|
|
35
36
|
"""
|
|
36
37
|
CherryPy tool handling authentication.
|
|
@@ -73,17 +74,17 @@ class AuthManager(cherrypy.Tool):
|
|
|
73
74
|
if not hasattr(cherrypy.serving, 'session'):
|
|
74
75
|
return
|
|
75
76
|
# Check if a user_key is stored in session.
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
77
|
+
session = cherrypy.serving.session
|
|
78
|
+
user_key = session.get(self._session_user_key())
|
|
79
|
+
if not user_key:
|
|
80
|
+
return
|
|
80
81
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
82
|
+
last = session.get(AUTH_LAST_PASSWORD_AT)
|
|
83
|
+
if last is None:
|
|
84
|
+
return # never had a password login in this session
|
|
85
|
+
timeout = self._reauth_timeout_minutes()
|
|
86
|
+
if (last + datetime.timedelta(minutes=timeout)) < session.now():
|
|
87
|
+
return
|
|
87
88
|
|
|
88
89
|
# Mark request as authenticated by key; user object will be resolved later.
|
|
89
90
|
cherrypy.serving.request.login = user_key
|
|
@@ -101,12 +102,7 @@ class AuthManager(cherrypy.Tool):
|
|
|
101
102
|
try:
|
|
102
103
|
currentuser = user_from_key_func(user_key)
|
|
103
104
|
except Exception:
|
|
104
|
-
|
|
105
|
-
f'unexpected error searching for user_key={user_key}',
|
|
106
|
-
context='AUTH',
|
|
107
|
-
severity=logging.ERROR,
|
|
108
|
-
traceback=True,
|
|
109
|
-
)
|
|
105
|
+
logger.exception('error while resolving user from key')
|
|
110
106
|
currentuser = None
|
|
111
107
|
|
|
112
108
|
if currentuser:
|
|
@@ -126,7 +122,7 @@ class AuthManager(cherrypy.Tool):
|
|
|
126
122
|
Validate credentials with configured checkers; on success, call login_with_result.
|
|
127
123
|
"""
|
|
128
124
|
if not login or not password:
|
|
129
|
-
|
|
125
|
+
logger.warning('empty login or password provided')
|
|
130
126
|
return None
|
|
131
127
|
# Validate credentials using checkpassword function(s).
|
|
132
128
|
conf = self._merged_args()
|
|
@@ -147,24 +143,15 @@ class AuthManager(cherrypy.Tool):
|
|
|
147
143
|
login, user_info = login, None
|
|
148
144
|
# If authentication is successful, initiate login process.
|
|
149
145
|
return self.login_with_result(login=login, user_info=user_info)
|
|
150
|
-
except Exception:
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
context='AUTH',
|
|
154
|
-
severity=logging.ERROR,
|
|
155
|
-
traceback=True,
|
|
146
|
+
except Exception as e:
|
|
147
|
+
logger.exception(
|
|
148
|
+
'unexpected error during authentication for [%s] with [%s]: %s', login, func.__qualname__, e
|
|
156
149
|
)
|
|
157
150
|
# If we reach here, authentication failed
|
|
158
151
|
if hasattr(cherrypy.serving, 'session'):
|
|
159
|
-
|
|
160
|
-
session.regenerate() # Prevent session analysis
|
|
152
|
+
cherrypy.serving.session.regenerate() # Prevent session analysis
|
|
161
153
|
|
|
162
|
-
|
|
163
|
-
cherrypy.log(
|
|
164
|
-
f'authentication failed login={login} ip={remote_ip} reason=wrong_credentials',
|
|
165
|
-
context='AUTH',
|
|
166
|
-
severity=logging.WARNING,
|
|
167
|
-
)
|
|
154
|
+
logger.warning('Failed login attempt for user=%s from ip=%s', login, cherrypy.serving.request.remote.ip)
|
|
168
155
|
|
|
169
156
|
return None
|
|
170
157
|
|
|
@@ -179,18 +166,11 @@ class AuthManager(cherrypy.Tool):
|
|
|
179
166
|
try:
|
|
180
167
|
user_key, userobj = user_lookup_func(login=login, user_info=user_info or {})
|
|
181
168
|
except Exception:
|
|
182
|
-
|
|
183
|
-
f"unexpected error searching user login={login} user_info={user_info}",
|
|
184
|
-
context='AUTH',
|
|
185
|
-
severity=logging.ERROR,
|
|
186
|
-
traceback=True,
|
|
187
|
-
)
|
|
169
|
+
logger.exception('failed to lookup user object for login=%s', login)
|
|
188
170
|
return None
|
|
189
171
|
|
|
190
172
|
if not userobj:
|
|
191
|
-
|
|
192
|
-
f"authentication failed login={login} reason=not_found", context='AUTH', severity=logging.WARNING
|
|
193
|
-
)
|
|
173
|
+
logger.warning('failed to lookup user object for login=%s', login)
|
|
194
174
|
return None
|
|
195
175
|
|
|
196
176
|
# Notify plugins about user login
|
|
@@ -198,13 +178,13 @@ class AuthManager(cherrypy.Tool):
|
|
|
198
178
|
|
|
199
179
|
# Store in session
|
|
200
180
|
if hasattr(cherrypy.serving, 'session'):
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
181
|
+
session = cherrypy.serving.session
|
|
182
|
+
session_user_key = self._session_user_key()
|
|
183
|
+
session[session_user_key] = user_key
|
|
184
|
+
session[AUTH_METHOD] = auth_method
|
|
185
|
+
session[AUTH_LAST_PASSWORD_AT] = session.now()
|
|
186
|
+
# Generate a new session id
|
|
187
|
+
session.regenerate()
|
|
208
188
|
|
|
209
189
|
# When authenticated, store user_key in request.
|
|
210
190
|
cherrypy.serving.request.login = user_key
|
|
@@ -216,9 +196,9 @@ class AuthManager(cherrypy.Tool):
|
|
|
216
196
|
"""
|
|
217
197
|
Clear session data and generate a new session id.
|
|
218
198
|
"""
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
199
|
+
session = cherrypy.serving.session
|
|
200
|
+
session.clear()
|
|
201
|
+
session.regenerate()
|
|
222
202
|
|
|
223
203
|
def get_original_url(self):
|
|
224
204
|
"""
|
|
@@ -226,16 +206,15 @@ class AuthManager(cherrypy.Tool):
|
|
|
226
206
|
"""
|
|
227
207
|
if not hasattr(cherrypy.serving, 'session'):
|
|
228
208
|
return None
|
|
229
|
-
|
|
230
|
-
return session.get(AUTH_ORIGINAL_URL)
|
|
209
|
+
return cherrypy.serving.session.get(AUTH_ORIGINAL_URL)
|
|
231
210
|
|
|
232
211
|
def get_user_key(self):
|
|
233
212
|
"""Return the last username."""
|
|
234
213
|
if not hasattr(cherrypy.serving, 'session'):
|
|
235
214
|
return False
|
|
215
|
+
session = cherrypy.serving.session
|
|
236
216
|
session_user_key = self._session_user_key()
|
|
237
|
-
|
|
238
|
-
return session.get(session_user_key)
|
|
217
|
+
return session.get(session_user_key)
|
|
239
218
|
|
|
240
219
|
def redirect_to_original_url(self):
|
|
241
220
|
# Redirect user to original URL
|
|
@@ -256,8 +235,8 @@ class AuthManager(cherrypy.Tool):
|
|
|
256
235
|
query_string = request.query_string
|
|
257
236
|
|
|
258
237
|
# Store value in session
|
|
259
|
-
|
|
260
|
-
|
|
238
|
+
session = cherrypy.serving.session
|
|
239
|
+
session[AUTH_ORIGINAL_URL] = cherrypy.url(original_url, qs=query_string, base='')
|
|
261
240
|
|
|
262
241
|
|
|
263
242
|
cherrypy.tools.auth = AuthManager()
|