Skip to content

Commit fd1a1e9

Browse files
committed
feat: add templating utils method dynamically; method to set http code
Added new utils.set_http_response(integer) to set the HTML response code from a template. Useful for error handling inside template. Also noted that a real TemplatingUtils (like set_http_response) method gets the TemplatingUtils object instance, but there is no way to do this with registerUtil() from an extension file. Added new instance.registerUtilMethod() method to register a function in an extension as a method passing the client instance in as the first parameter (aka self).
1 parent a9c5fd9 commit fd1a1e9

File tree

6 files changed

+142
-26
lines changed

6 files changed

+142
-26
lines changed

CHANGES.txt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,13 @@ Features:
9595
- issue2551116 - Replace xmlrpclib (xmlrpc.client) with defusedxml.
9696
Added support for defusedxml to better secure the xmlrpc
9797
endpoint. (John Rouillard)
98-
98+
- Added new instance.registerUtilMethod() method to make using complex
99+
templating easier as it provides a default Client instance to the
100+
templating method. (John Rouillard)
101+
- Added new templating utils.set_http_response(integer) method to
102+
allow reporting an error to the user from a template. (John
103+
Rouillard)
104+
99105
2024-07-13 2.4.0
100106

101107
Fixed:

doc/admin_guide.txt

Lines changed: 49 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,8 @@ when invoked.
443443
More secure CSPs can also be created. However because of the ability
444444
to customise the web interface, it is difficult to provide guidance.
445445

446+
.. _dynamic_csp:
447+
446448
Dynamic CSP
447449
-----------
448450

@@ -469,7 +471,7 @@ directory that generates the CSP on the fly. For example::
469471
}
470472

471473

472-
def AddHtmlHeaders(client, header_dict=None):
474+
def AddHtmlHeaders(self, header_dict=None):
473475
''' Generate https headers from dict use default security headers
474476

475477
Setting the header with a value of None will not inject the
@@ -479,34 +481,35 @@ directory that generates the CSP on the fly. For example::
479481
nonce. Use to set a nonce for inline scripts.
480482
'''
481483
try:
482-
if client.client_nonce is None:
484+
if self.client.client_nonce is None:
483485
# logger.warning("client_nonce is None")
484-
client.client_nonce = client.session_api._gen_sid()
486+
self.client.client_nonce = self.client.session_api._gen_sid()
485487
except AttributeError:
486-
# client.client_nonce doesn't exist, create it
488+
# self.client.client_nonce doesn't exist, create it
487489
# logger.warning("client_nonce does not exist, creating")
488-
client.client_nonce = client.session_api._gen_sid()
490+
self.client.client_nonce = client.session_api._gen_sid()
489491

490492
headers = default_security_headers.copy()
491493
if isinstance(header_dict, dict):
492494
headers.update(header_dict)
493495

494-
client_headers = client.additional_headers
496+
client_headers = self.client.additional_headers
495497

496498
for header, value in list(headers.items()):
497499
if value is None:
498500
continue
499501
client_headers[header] = value.format(
500-
nonce=client.client_nonce)
502+
nonce=self.client.client_nonce)
501503

502504
def init(instance):
503-
instance.registerUtil('AddHtmlHeaders', AddHtmlHeaders)
505+
# Note the use of the new (in version 2.5) registerUtilMethod
506+
instance.registerUtilMethod('AddHtmlHeaders', AddHtmlHeaders)
504507

505508

506509
Adding the following to ``page.html`` right after the opening
507510
``<html....`>`` tag::
508511

509-
<tal:code tal:content="python:utils.AddHtmlHeaders(request.client)" />
512+
<tal:code tal:content="python:utils.AddHtmlHeaders()" />
510513

511514
will invoke ``AddHtmlHeaders()`` to add the CSP header with the nonce.
512515

@@ -523,6 +526,43 @@ to::
523526

524527
for each script, object or style tag.
525528

529+
If you are using a version of Roundup before version 2.5, you need to
530+
replace ``instance.registerUtilMethod`` with
531+
``instance.registerUtil``. For example::
532+
533+
def init(instance):
534+
# Note the use of the new (in version 2.5) registerUtilMethod
535+
instance.registerUtil('AddHtmlHeaders', AddHtmlHeaders)
536+
537+
The AddHtmlHeaders function needs to be changed so that ``self.client``
538+
is replaced by ``client``::
539+
540+
# replace self parameter with client
541+
def AddHtmlHeaders(client, header_dict=None):
542+
''' Generate https headers from dict use default security headers
543+
544+
Setting the header with a value of None will not inject the
545+
header and can override the default set.
546+
547+
Header values will be formatted with a dictionary including a
548+
nonce. Use to set a nonce for inline scripts.
549+
'''
550+
551+
### Then change all references to self.client to client
552+
553+
try:
554+
if client.client_nonce is None: # note self.client -> client
555+
# logger.warning("client_nonce is None")
556+
client.client_nonce = self.client.session_api._gen_sid()
557+
558+
...
559+
560+
Lastly the client must be passed explicitly when calling
561+
AddHtmlHeaders. The call looks like::
562+
563+
<tal:code tal:content="python:utils.AddHtmlHeaders(request.client)" />
564+
565+
526566
Remediating ``unsafe-inline``
527567
-----------------------------
528568
.. _remediating unsafe-inline:

doc/reference.txt

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1148,6 +1148,11 @@ mechanism.
11481148
(see `adding a time log to your issues
11491149
<customizing.html#adding-a-time-log-to-your-issues-4>`_ for an example)
11501150

1151+
* ``instance.registerUtilMethod`` is also used for adding `templating
1152+
utilities`_, and provides a client instance by default to the
1153+
function. This makes more complex templating actions easier to
1154+
use. (see :ref:`dynamic_csp` for an example)
1155+
11511156
* ``instance.registerAction`` is used to add more actions to the
11521157
instance and to web interface. See `Defining new web actions`_
11531158
for details. Generic action can be added by inheriting from
@@ -3465,22 +3470,23 @@ be added to the variable by using extensions_.
34653470
.. table::
34663471
:class: valign-top
34673472

3468-
=============== ========================================================
3469-
Method Description
3470-
=============== ========================================================
3471-
Batch return a batch object using the supplied list
3472-
anti_csrf_nonce returns the random nonce generated for this session
3473-
expandfile load a file into a template and expand
3474-
'%(tokenname)s' in the file using
3475-
values from the supplied dictionary.
3476-
html_quote quote some text as safe in HTML (ie. <, >, ...)
3477-
html_calendar renders an HTML calendar used by the
3478-
``_generic.calendar.html`` template (itself invoked by
3479-
the popupCalendar DateHTMLProperty method
3480-
readfile read JavaScript or other content in an external
3481-
file into the template.
3482-
url_quote quote some text as safe for a URL (ie. space, %, ...)
3483-
=============== ========================================================
3473+
================= ========================================================
3474+
Method Description
3475+
================= ========================================================
3476+
Batch return a batch object using the supplied list
3477+
anti_csrf_nonce returns the random nonce generated for this session
3478+
expandfile load a file into a template and expand
3479+
'%(tokenname)s' in the file using
3480+
values from the supplied dictionary.
3481+
html_quote quote some text as safe in HTML (ie. <, >, ...)
3482+
html_calendar renders an HTML calendar used by the
3483+
``_generic.calendar.html`` template (itself invoked by
3484+
the popupCalendar DateHTMLProperty method
3485+
readfile read JavaScript or other content in an external
3486+
file into the template.
3487+
set_http_response sets the HTTP response code for the request.
3488+
url_quote quote some text as safe for a URL (ie. space, %, ...)
3489+
================= ========================================================
34843490

34853491
Additional info can be obtained by starting ``python`` with the
34863492
``roundup`` subdirectory on your PYTHONPATH and using the Python help

doc/upgrading.txt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,28 @@ occurs before any database changes are made. Now, all errors related
253253
to the data format (mime type, API version) will return 406 errors,
254254
where some previously resulted in 400 errors.
255255

256+
New method for registering templating utils (info)
257+
--------------------------------------------------
258+
259+
If you are building a template utility function that needs access
260+
to:
261+
262+
* the database
263+
* the client instance
264+
* the form the user submitted
265+
266+
you had to pass these objects from the template using the ``db``,
267+
``request.client`` or ``request.form`` arguments.
268+
269+
A new method for registering a template utility has been
270+
added. If you use the ``instance`` object's
271+
``registerUtilMethod()`` to register a utility function, you do
272+
not need to pass these arguments. The function is called as a
273+
method and the first argument is a ``client`` instance from which
274+
the database (client.db), form (client.form).
275+
276+
You can find an example in :ref:`dynamic_csp`.
277+
256278
.. index:: Upgrading; 2.3.0 to 2.4.0
257279

258280
Migrating from 2.3.0 to 2.4.0

roundup/cgi/templating.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3799,6 +3799,18 @@ def expandfile(self, name, values=None, optional=False):
37993799
{'fullpath': fullpath, 'issue': e.args[0]})
38003800
return ""
38013801

3802+
def set_http_response(self, code):
3803+
'''Set the HTTP response code to the integer `code`.
3804+
Example::
3805+
3806+
<tal:x
3807+
tal:replace="python:utils.set_response(404);"
3808+
/>
3809+
3810+
3811+
will make the template return code 404 (not found).
3812+
'''
3813+
self.client.response_code = code
38023814

38033815
class MissingValue(object):
38043816
def __init__(self, description, **kwargs):

roundup/instance.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,8 +247,38 @@ def registerAction(self, name, action):
247247
self.cgi_actions[name] = action
248248

249249
def registerUtil(self, name, function):
250+
"""Register a function that can be called using:
251+
`utils.<name>(...)`.
252+
253+
The function is defined as:
254+
255+
def function(...):
256+
257+
If you need access to the client, database, form or other
258+
item, you have to pass it explicitly::
259+
260+
utils.name(request.client, ...)
261+
262+
If you need client access, consider using registerUtilMethod()
263+
instead.
264+
265+
"""
250266
self.templating_utils[name] = function
251267

268+
def registerUtilMethod(self, name, function):
269+
"""Register a method that can be called using:
270+
`utils.<name>(...)`.
271+
272+
Unlike registerUtil, the method is defined as:
273+
274+
def function(self, ...):
275+
276+
`self` is a TemplatingUtils object. You can use self.client
277+
to access the client object for your request.
278+
"""
279+
setattr(self.TemplatingUtils,
280+
name,
281+
function)
252282

253283
class TrackerError(RoundupException):
254284
pass

0 commit comments

Comments
 (0)