cherrypy-foundation 1.0.0a21__py3-none-any.whl → 1.0.1__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/plugins/ldap.py +20 -22
- cherrypy_foundation/tools/jinja2.py +59 -9
- cherrypy_foundation/tools/tests/test_jinja2.py +1 -0
- {cherrypy_foundation-1.0.0a21.dist-info → cherrypy_foundation-1.0.1.dist-info}/METADATA +1 -1
- {cherrypy_foundation-1.0.0a21.dist-info → cherrypy_foundation-1.0.1.dist-info}/RECORD +8 -8
- {cherrypy_foundation-1.0.0a21.dist-info → cherrypy_foundation-1.0.1.dist-info}/WHEEL +0 -0
- {cherrypy_foundation-1.0.0a21.dist-info → cherrypy_foundation-1.0.1.dist-info}/licenses/LICENSE.md +0 -0
- {cherrypy_foundation-1.0.0a21.dist-info → cherrypy_foundation-1.0.1.dist-info}/top_level.txt +0 -0
|
@@ -77,8 +77,8 @@ class LdapPlugin(SimplePlugin):
|
|
|
77
77
|
"""
|
|
78
78
|
Used this plugin to authenticate user against an LDAP server.
|
|
79
79
|
|
|
80
|
-
`authenticate(
|
|
81
|
-
valid. Otherwise it return a tuple or
|
|
80
|
+
`authenticate(login, password)` return None if the credentials are not
|
|
81
|
+
valid. Otherwise it return a tuple or login and extra attributes.
|
|
82
82
|
The extra attribute may contains `_fullname` and `_email`.
|
|
83
83
|
"""
|
|
84
84
|
|
|
@@ -89,7 +89,7 @@ class LdapPlugin(SimplePlugin):
|
|
|
89
89
|
scope = 'subtree'
|
|
90
90
|
tls = False
|
|
91
91
|
user_filter = '(objectClass=*)'
|
|
92
|
-
|
|
92
|
+
login_attribute = ['uid']
|
|
93
93
|
required_group = None
|
|
94
94
|
group_attribute = 'member'
|
|
95
95
|
group_attribute_is_dn = False
|
|
@@ -133,14 +133,14 @@ class LdapPlugin(SimplePlugin):
|
|
|
133
133
|
self.stop()
|
|
134
134
|
self.start()
|
|
135
135
|
|
|
136
|
-
def authenticate(self,
|
|
136
|
+
def authenticate(self, login, password):
|
|
137
137
|
"""
|
|
138
138
|
Check if the given credential as valid according to LDAP.
|
|
139
139
|
Return False if invalid.
|
|
140
140
|
Return None if the plugin is unavailable to validate credentials or if the plugin is disabled.
|
|
141
|
-
Return tuple (<
|
|
141
|
+
Return tuple (<login>, <attributes>) if the credentials are valid.
|
|
142
142
|
"""
|
|
143
|
-
assert isinstance(
|
|
143
|
+
assert isinstance(login, str)
|
|
144
144
|
assert isinstance(password, str)
|
|
145
145
|
|
|
146
146
|
if not hasattr(self, '_pool'):
|
|
@@ -150,14 +150,14 @@ class LdapPlugin(SimplePlugin):
|
|
|
150
150
|
with self._pool as conn:
|
|
151
151
|
try:
|
|
152
152
|
# Search the LDAP server for user's DN.
|
|
153
|
-
|
|
154
|
-
attr_filter = ''.join([f'({_safe(attr)}={
|
|
153
|
+
safe_login = _safe(login)
|
|
154
|
+
attr_filter = ''.join([f'({_safe(attr)}={safe_login})' for attr in self.login_attribute])
|
|
155
155
|
search_filter = f"(&{self.user_filter}(|{attr_filter}))"
|
|
156
156
|
response = self._search(conn, search_filter)
|
|
157
157
|
if not response:
|
|
158
|
-
cherrypy.log(f"lookup failed
|
|
158
|
+
cherrypy.log(f"lookup failed login={login} reason=not_found", context='LDAP')
|
|
159
159
|
return False
|
|
160
|
-
cherrypy.log(f"lookup successful
|
|
160
|
+
cherrypy.log(f"lookup successful login={login}", context='LDAP')
|
|
161
161
|
user_dn = response[0]['dn']
|
|
162
162
|
|
|
163
163
|
# Use a separate connection to validate credentials
|
|
@@ -171,18 +171,18 @@ class LdapPlugin(SimplePlugin):
|
|
|
171
171
|
)
|
|
172
172
|
if not login_conn.bind():
|
|
173
173
|
cherrypy.log(
|
|
174
|
-
f'ldap authentication failed
|
|
174
|
+
f'ldap authentication failed login={login} reason=wrong_password',
|
|
175
175
|
context='LDAP',
|
|
176
176
|
severity=logging.WARNING,
|
|
177
177
|
)
|
|
178
178
|
return False
|
|
179
179
|
|
|
180
|
-
# Get
|
|
180
|
+
# Get user's login
|
|
181
181
|
attrs = response[0]['attributes']
|
|
182
|
-
|
|
183
|
-
if not
|
|
182
|
+
new_login = first_attribute(attrs, self.login_attribute[0])
|
|
183
|
+
if not new_login:
|
|
184
184
|
cherrypy.log(
|
|
185
|
-
f"object missing
|
|
185
|
+
f"object missing login attribute user_dn={user_dn} attribute={self.login_attribute[0]}",
|
|
186
186
|
context='LDAP',
|
|
187
187
|
severity=logging.WARNING,
|
|
188
188
|
)
|
|
@@ -192,7 +192,7 @@ class LdapPlugin(SimplePlugin):
|
|
|
192
192
|
if self.required_group:
|
|
193
193
|
if not isinstance(self.required_group, list):
|
|
194
194
|
self.required_group = [self.required_group]
|
|
195
|
-
user_value = user_dn if self.group_attribute_is_dn else
|
|
195
|
+
user_value = user_dn if self.group_attribute_is_dn else new_login
|
|
196
196
|
group_filter = '(&(%s=%s)(|%s)%s)' % (
|
|
197
197
|
_safe(self.group_attribute),
|
|
198
198
|
_safe(user_value),
|
|
@@ -201,13 +201,13 @@ class LdapPlugin(SimplePlugin):
|
|
|
201
201
|
)
|
|
202
202
|
# Search LDAP Server for matching groups.
|
|
203
203
|
cherrypy.log(
|
|
204
|
-
f"group check start
|
|
204
|
+
f"group check start login={user_value} required_groups={' '.join(self.required_group)}",
|
|
205
205
|
context='LDAP',
|
|
206
206
|
)
|
|
207
207
|
response = self._search(conn, group_filter, attributes=['cn'])
|
|
208
208
|
if not response:
|
|
209
209
|
cherrypy.log(
|
|
210
|
-
f"group check failed
|
|
210
|
+
f"group check failed login={user_value} required_groups={' '.join(self.required_group)}",
|
|
211
211
|
context='LDAP',
|
|
212
212
|
)
|
|
213
213
|
return False
|
|
@@ -220,13 +220,11 @@ class LdapPlugin(SimplePlugin):
|
|
|
220
220
|
firstname = first_attribute(attrs, self.firstname_attribute, '')
|
|
221
221
|
lastname = first_attribute(attrs, self.lastname_attribute, '')
|
|
222
222
|
attrs['fullname'] = ' '.join([name for name in [firstname, lastname] if name])
|
|
223
|
-
return (
|
|
223
|
+
return (new_login, attrs)
|
|
224
224
|
except LDAPInvalidCredentialsResult:
|
|
225
225
|
return False
|
|
226
226
|
except LDAPException:
|
|
227
|
-
cherrypy.log(
|
|
228
|
-
f"unexpected error username={username}", context='LDAP', severity=logging.ERROR, traceback=True
|
|
229
|
-
)
|
|
227
|
+
cherrypy.log(f"unexpected error login={login}", context='LDAP', severity=logging.ERROR, traceback=True)
|
|
230
228
|
return None
|
|
231
229
|
|
|
232
230
|
def search(self, filter, attributes=ldap3.ALL_ATTRIBUTES, search_base=None, paged_size=None):
|
|
@@ -20,6 +20,7 @@ import time
|
|
|
20
20
|
|
|
21
21
|
import cherrypy
|
|
22
22
|
import jinja2
|
|
23
|
+
from markupsafe import Markup
|
|
23
24
|
|
|
24
25
|
# Sentinel value
|
|
25
26
|
_UNDEFINED = object()
|
|
@@ -27,6 +28,8 @@ _UNDEFINED = object()
|
|
|
27
28
|
# Capture epoch time to invalidate cache of static file.
|
|
28
29
|
_cache_invalidate = int(time.time())
|
|
29
30
|
|
|
31
|
+
_JINJAX_TOKEN = '@@JINJAX-CATALOG-TOKEN@@'
|
|
32
|
+
|
|
30
33
|
|
|
31
34
|
class Jinja2Tool(cherrypy.Tool):
|
|
32
35
|
"""
|
|
@@ -41,12 +44,47 @@ class Jinja2Tool(cherrypy.Tool):
|
|
|
41
44
|
Replace the placeholder token in the rendered HTML with the fully
|
|
42
45
|
formatted asset tags, then reset asset state for the next render.
|
|
43
46
|
"""
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
# Skip this step if render_assets() has not been called.
|
|
48
|
+
if hasattr(catalog, '_emit_assets_later') and getattr(catalog, '_emit_assets_later') is False:
|
|
49
|
+
return html
|
|
50
|
+
|
|
51
|
+
# Render Js and Css collected assets
|
|
52
|
+
if hasattr(catalog, '_format_collected_assets'):
|
|
53
|
+
# JinjaX > 0.60
|
|
54
|
+
catalog._emit_assets_later = False
|
|
55
|
+
assets_html = catalog._format_collected_assets()
|
|
56
|
+
assets_html = str(assets_html).replace('<script type="module" ', '<script ')
|
|
57
|
+
return str(html).replace(catalog._assets_placeholder, assets_html)
|
|
58
|
+
else:
|
|
59
|
+
# JinjaX<=0.60
|
|
60
|
+
html_css = []
|
|
61
|
+
rendered_urls = set()
|
|
62
|
+
|
|
63
|
+
for url in catalog.collected_css:
|
|
64
|
+
if url == _JINJAX_TOKEN:
|
|
65
|
+
continue
|
|
66
|
+
if not url.startswith(("http://", "https://")):
|
|
67
|
+
full_url = f"{catalog.root_url}{url}"
|
|
68
|
+
else:
|
|
69
|
+
full_url = url
|
|
70
|
+
|
|
71
|
+
if full_url not in rendered_urls:
|
|
72
|
+
html_css.append(f'<link rel="stylesheet" href="{full_url}">')
|
|
73
|
+
rendered_urls.add(full_url)
|
|
74
|
+
|
|
75
|
+
html_js = []
|
|
76
|
+
for url in catalog.collected_js:
|
|
77
|
+
if not url.startswith(("http://", "https://")):
|
|
78
|
+
full_url = f"{catalog.root_url}{url}"
|
|
79
|
+
else:
|
|
80
|
+
full_url = url
|
|
81
|
+
|
|
82
|
+
if full_url not in rendered_urls:
|
|
83
|
+
html_js.append(f'<script src="{full_url}"></script>')
|
|
84
|
+
rendered_urls.add(full_url)
|
|
85
|
+
|
|
86
|
+
assets_html = Markup("\n".join(html_css + html_js))
|
|
87
|
+
return str(html).replace(f'<link rel="stylesheet" href="/static/components/{_JINJAX_TOKEN}">', assets_html)
|
|
50
88
|
|
|
51
89
|
def _wrap_handler(self, env, template, extra_processor=None, debug=False):
|
|
52
90
|
|
|
@@ -147,11 +185,23 @@ class Jinja2Tool(cherrypy.Tool):
|
|
|
147
185
|
tmpl = env.select_template(names)
|
|
148
186
|
else:
|
|
149
187
|
tmpl = env.get_template(template)
|
|
188
|
+
|
|
189
|
+
# Monkey patch JinjaX catalog.render_assets() for JinjaX <= 0.60
|
|
190
|
+
catalog = env.globals.get('catalog')
|
|
191
|
+
if catalog:
|
|
192
|
+
catalog.collected_css = []
|
|
193
|
+
catalog.collected_js = []
|
|
194
|
+
if not hasattr(catalog, '_assets_placeholder'):
|
|
195
|
+
# JinjaX <= 0.60
|
|
196
|
+
# Leave a token to be replace.
|
|
197
|
+
catalog.collected_css = [_JINJAX_TOKEN]
|
|
198
|
+
|
|
199
|
+
# Render template
|
|
150
200
|
out = tmpl.render(context)
|
|
151
201
|
|
|
152
|
-
# With JinjaX
|
|
153
|
-
if
|
|
154
|
-
return self._finalize_assets(catalog=
|
|
202
|
+
# With JinjaX finalise render_assets().
|
|
203
|
+
if catalog:
|
|
204
|
+
return self._finalize_assets(catalog=catalog, html=out)
|
|
155
205
|
return out
|
|
156
206
|
|
|
157
207
|
|
|
@@ -124,6 +124,7 @@ class Jinja2Test(helper.CPWebCase, SeleniumUnitTest):
|
|
|
124
124
|
# Then the page return without error
|
|
125
125
|
self.assertStatus(200)
|
|
126
126
|
# Then the page is render dynamically using page context
|
|
127
|
+
self.assertInBody('<script src="/static/components/LocaleSelection.js"></script>')
|
|
127
128
|
if expected_lang == 'fr':
|
|
128
129
|
self.assertInBody('lang="fr"')
|
|
129
130
|
self.assertInBody('français')
|
|
@@ -84,7 +84,7 @@ cherrypy_foundation/components/vendor/typeahead/jquery.typeahead.min.css,sha256=
|
|
|
84
84
|
cherrypy_foundation/components/vendor/typeahead/jquery.typeahead.min.js,sha256=eFBtgouYdW587kYINWsjkN-UR8GVWAG_fQe1fpJfjOw,47828
|
|
85
85
|
cherrypy_foundation/plugins/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
86
86
|
cherrypy_foundation/plugins/db.py,sha256=S2OqujZ-D_TxUM6NbQW7XGDt8eGDup1Wbhf0XCcYSbs,10108
|
|
87
|
-
cherrypy_foundation/plugins/ldap.py,sha256=
|
|
87
|
+
cherrypy_foundation/plugins/ldap.py,sha256=Y0gUD5CcFaUAI7SSPIlrlr4IkQ1QePX2--qfZrIBlbo,9861
|
|
88
88
|
cherrypy_foundation/plugins/restapi.py,sha256=1W1mmVh-rIzg_mcFaNQZZXpqF7eN7VhRL9oTEiQOr-w,2843
|
|
89
89
|
cherrypy_foundation/plugins/scheduler.py,sha256=m2XLM7euRjC5bkU4NILvwnRo0w6NWRwmCI19iEiQlE0,10777
|
|
90
90
|
cherrypy_foundation/plugins/smtp.py,sha256=7qZv0VrQxi7gNoVdRds0F6Ya9pyo3LlZ12uFaVpAsms,7658
|
|
@@ -109,7 +109,7 @@ cherrypy_foundation/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJW
|
|
|
109
109
|
cherrypy_foundation/tools/auth.py,sha256=WRXiZdRXJcd4uZFDb8DAGX-ySqiYytdWyjiQiLZb9Vo,9935
|
|
110
110
|
cherrypy_foundation/tools/auth_mfa.py,sha256=hDwfKc2zFY3bxOm1JRLLFQRXVPL9atBQ-PEwQTEQUww,9579
|
|
111
111
|
cherrypy_foundation/tools/i18n.py,sha256=f4m9fc2FKtcdUpXgnLXzhoipn4oLDuHY94Vv3KPSfp0,17126
|
|
112
|
-
cherrypy_foundation/tools/jinja2.py,sha256=
|
|
112
|
+
cherrypy_foundation/tools/jinja2.py,sha256=StEKv7nWtnCwGoOGYzIKtcPkEsRGPg1TtsjdyK_VW0Q,7834
|
|
113
113
|
cherrypy_foundation/tools/ratelimit.py,sha256=C_bGzkx9waIJlpK-X6_SH8r1rP0I_xr0wLnGZhSEGEo,8742
|
|
114
114
|
cherrypy_foundation/tools/secure_headers.py,sha256=XqmjU5MCK8fTacQ2uTUiODYN4uaa9cCGfQJZVnp4J4g,3921
|
|
115
115
|
cherrypy_foundation/tools/sessions_timeout.py,sha256=M54IpRc3c4rUH0Cbri363O6GhD89EmzrjqvWeul8khc,7484
|
|
@@ -117,7 +117,7 @@ cherrypy_foundation/tools/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm
|
|
|
117
117
|
cherrypy_foundation/tools/tests/test_auth.py,sha256=qQxCs8uCgu3C2Of9DRWplpCLLB7zSiJkrV4F9muLkJg,3414
|
|
118
118
|
cherrypy_foundation/tools/tests/test_auth_mfa.py,sha256=qYIlw4tDQUi9R7nPwph6UpMsThWDN1EYW-FrrOF_grQ,15111
|
|
119
119
|
cherrypy_foundation/tools/tests/test_i18n.py,sha256=6lHV3kxriRI5jo-YMnxzKKDFtLhogUrqK7D4X2-m42U,10758
|
|
120
|
-
cherrypy_foundation/tools/tests/test_jinja2.py,sha256=
|
|
120
|
+
cherrypy_foundation/tools/tests/test_jinja2.py,sha256=8BsxpwLQkoSJCS1cQi5a_wZMu9Zdry4JDKBAUxy7pz0,5636
|
|
121
121
|
cherrypy_foundation/tools/tests/test_ratelimit.py,sha256=BZn2lhbuaVeIPq80eUn-SFu0mMN3Mb4ttwVoXbozWUA,3431
|
|
122
122
|
cherrypy_foundation/tools/tests/test_secure_headers.py,sha256=Iu3X7u6VJpWOmuNMv1qZFh0oPWdLZAdoJppRyuTV148,7769
|
|
123
123
|
cherrypy_foundation/tools/tests/components/Button.jinja,sha256=uSLp1GpEIgZNXK_GWglu0E_a1c3jHpDLI66MRfMqGhE,95
|
|
@@ -129,8 +129,8 @@ cherrypy_foundation/tools/tests/locales/fr/LC_MESSAGES/messages.po,sha256=6_Sk9I
|
|
|
129
129
|
cherrypy_foundation/tools/tests/templates/test_jinja2.html,sha256=s1bHmy-lyf0YW0t-LOx3ugILV2kqFoBNYxziWgrZbo0,216
|
|
130
130
|
cherrypy_foundation/tools/tests/templates/test_jinjax.html,sha256=NImzIW0mUHxilFd61PSoxFC-yu1nayEVwv-5zlgD9yo,179
|
|
131
131
|
cherrypy_foundation/tools/tests/templates/test_jinjax_i18n.html,sha256=yre8j7HBjpTQZHpM0PuB3ASGD3O4vKkJ-y72Fm6STgY,771
|
|
132
|
-
cherrypy_foundation-1.0.
|
|
133
|
-
cherrypy_foundation-1.0.
|
|
134
|
-
cherrypy_foundation-1.0.
|
|
135
|
-
cherrypy_foundation-1.0.
|
|
136
|
-
cherrypy_foundation-1.0.
|
|
132
|
+
cherrypy_foundation-1.0.1.dist-info/licenses/LICENSE.md,sha256=trSLYs5qlaow_bBwsLTRKpmTXsXzFksM_YUCMqrgAJQ,35149
|
|
133
|
+
cherrypy_foundation-1.0.1.dist-info/METADATA,sha256=pNbkEH_cQBF1jXYE3ejQPDcXTJ6HGPiKRcqcmk7ndVw,3548
|
|
134
|
+
cherrypy_foundation-1.0.1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
135
|
+
cherrypy_foundation-1.0.1.dist-info/top_level.txt,sha256=B1vQPTLYhpKJ6W0JkRCWyAf8RPcnwJWdYxixv75-4ew,20
|
|
136
|
+
cherrypy_foundation-1.0.1.dist-info/RECORD,,
|
|
File without changes
|
{cherrypy_foundation-1.0.0a21.dist-info → cherrypy_foundation-1.0.1.dist-info}/licenses/LICENSE.md
RENAMED
|
File without changes
|
{cherrypy_foundation-1.0.0a21.dist-info → cherrypy_foundation-1.0.1.dist-info}/top_level.txt
RENAMED
|
File without changes
|