Mercurial > p > roundup > code
changeset 8262:2a7c3eeaf167
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).
| author | John Rouillard <rouilj@ieee.org> |
|---|---|
| date | Tue, 07 Jan 2025 20:22:33 -0500 |
| parents | 28c5030757d3 |
| children | 78b13272d41b |
| files | CHANGES.txt doc/admin_guide.txt doc/reference.txt doc/upgrading.txt roundup/cgi/templating.py roundup/instance.py |
| diffstat | 6 files changed, 142 insertions(+), 26 deletions(-) [+] |
line wrap: on
line diff
--- a/CHANGES.txt Sun Jan 05 00:59:12 2025 -0500 +++ b/CHANGES.txt Tue Jan 07 20:22:33 2025 -0500 @@ -95,7 +95,13 @@ - issue2551116 - Replace xmlrpclib (xmlrpc.client) with defusedxml. Added support for defusedxml to better secure the xmlrpc endpoint. (John Rouillard) - +- Added new instance.registerUtilMethod() method to make using complex + templating easier as it provides a default Client instance to the + templating method. (John Rouillard) +- Added new templating utils.set_http_response(integer) method to + allow reporting an error to the user from a template. (John + Rouillard) + 2024-07-13 2.4.0 Fixed:
--- a/doc/admin_guide.txt Sun Jan 05 00:59:12 2025 -0500 +++ b/doc/admin_guide.txt Tue Jan 07 20:22:33 2025 -0500 @@ -443,6 +443,8 @@ More secure CSPs can also be created. However because of the ability to customise the web interface, it is difficult to provide guidance. +.. _dynamic_csp: + Dynamic CSP ----------- @@ -469,7 +471,7 @@ } - def AddHtmlHeaders(client, header_dict=None): + def AddHtmlHeaders(self, header_dict=None): ''' Generate https headers from dict use default security headers Setting the header with a value of None will not inject the @@ -479,34 +481,35 @@ nonce. Use to set a nonce for inline scripts. ''' try: - if client.client_nonce is None: + if self.client.client_nonce is None: # logger.warning("client_nonce is None") - client.client_nonce = client.session_api._gen_sid() + self.client.client_nonce = self.client.session_api._gen_sid() except AttributeError: - # client.client_nonce doesn't exist, create it + # self.client.client_nonce doesn't exist, create it # logger.warning("client_nonce does not exist, creating") - client.client_nonce = client.session_api._gen_sid() + self.client.client_nonce = client.session_api._gen_sid() headers = default_security_headers.copy() if isinstance(header_dict, dict): headers.update(header_dict) - client_headers = client.additional_headers + client_headers = self.client.additional_headers for header, value in list(headers.items()): if value is None: continue client_headers[header] = value.format( - nonce=client.client_nonce) + nonce=self.client.client_nonce) def init(instance): - instance.registerUtil('AddHtmlHeaders', AddHtmlHeaders) + # Note the use of the new (in version 2.5) registerUtilMethod + instance.registerUtilMethod('AddHtmlHeaders', AddHtmlHeaders) Adding the following to ``page.html`` right after the opening ``<html....`>`` tag:: - <tal:code tal:content="python:utils.AddHtmlHeaders(request.client)" /> + <tal:code tal:content="python:utils.AddHtmlHeaders()" /> will invoke ``AddHtmlHeaders()`` to add the CSP header with the nonce. @@ -523,6 +526,43 @@ for each script, object or style tag. +If you are using a version of Roundup before version 2.5, you need to +replace ``instance.registerUtilMethod`` with +``instance.registerUtil``. For example:: + + def init(instance): + # Note the use of the new (in version 2.5) registerUtilMethod + instance.registerUtil('AddHtmlHeaders', AddHtmlHeaders) + +The AddHtmlHeaders function needs to be changed so that ``self.client`` +is replaced by ``client``:: + + # replace self parameter with client + def AddHtmlHeaders(client, header_dict=None): + ''' Generate https headers from dict use default security headers + + Setting the header with a value of None will not inject the + header and can override the default set. + + Header values will be formatted with a dictionary including a + nonce. Use to set a nonce for inline scripts. + ''' + + ### Then change all references to self.client to client + + try: + if client.client_nonce is None: # note self.client -> client + # logger.warning("client_nonce is None") + client.client_nonce = self.client.session_api._gen_sid() + + ... + +Lastly the client must be passed explicitly when calling +AddHtmlHeaders. The call looks like:: + + <tal:code tal:content="python:utils.AddHtmlHeaders(request.client)" /> + + Remediating ``unsafe-inline`` ----------------------------- .. _remediating unsafe-inline:
--- a/doc/reference.txt Sun Jan 05 00:59:12 2025 -0500 +++ b/doc/reference.txt Tue Jan 07 20:22:33 2025 -0500 @@ -1148,6 +1148,11 @@ (see `adding a time log to your issues <customizing.html#adding-a-time-log-to-your-issues-4>`_ for an example) +* ``instance.registerUtilMethod`` is also used for adding `templating + utilities`_, and provides a client instance by default to the + function. This makes more complex templating actions easier to + use. (see :ref:`dynamic_csp` for an example) + * ``instance.registerAction`` is used to add more actions to the instance and to web interface. See `Defining new web actions`_ for details. Generic action can be added by inheriting from @@ -3465,22 +3470,23 @@ .. table:: :class: valign-top - =============== ======================================================== - Method Description - =============== ======================================================== - Batch return a batch object using the supplied list - anti_csrf_nonce returns the random nonce generated for this session - expandfile load a file into a template and expand - '%(tokenname)s' in the file using - values from the supplied dictionary. - html_quote quote some text as safe in HTML (ie. <, >, ...) - html_calendar renders an HTML calendar used by the - ``_generic.calendar.html`` template (itself invoked by - the popupCalendar DateHTMLProperty method - readfile read JavaScript or other content in an external - file into the template. - url_quote quote some text as safe for a URL (ie. space, %, ...) - =============== ======================================================== + ================= ======================================================== + Method Description + ================= ======================================================== + Batch return a batch object using the supplied list + anti_csrf_nonce returns the random nonce generated for this session + expandfile load a file into a template and expand + '%(tokenname)s' in the file using + values from the supplied dictionary. + html_quote quote some text as safe in HTML (ie. <, >, ...) + html_calendar renders an HTML calendar used by the + ``_generic.calendar.html`` template (itself invoked by + the popupCalendar DateHTMLProperty method + readfile read JavaScript or other content in an external + file into the template. + set_http_response sets the HTTP response code for the request. + url_quote quote some text as safe for a URL (ie. space, %, ...) + ================= ======================================================== Additional info can be obtained by starting ``python`` with the ``roundup`` subdirectory on your PYTHONPATH and using the Python help
--- a/doc/upgrading.txt Sun Jan 05 00:59:12 2025 -0500 +++ b/doc/upgrading.txt Tue Jan 07 20:22:33 2025 -0500 @@ -253,6 +253,28 @@ to the data format (mime type, API version) will return 406 errors, where some previously resulted in 400 errors. +New method for registering templating utils (info) +-------------------------------------------------- + +If you are building a template utility function that needs access +to: + + * the database + * the client instance + * the form the user submitted + +you had to pass these objects from the template using the ``db``, +``request.client`` or ``request.form`` arguments. + +A new method for registering a template utility has been +added. If you use the ``instance`` object's +``registerUtilMethod()`` to register a utility function, you do +not need to pass these arguments. The function is called as a +method and the first argument is a ``client`` instance from which +the database (client.db), form (client.form). + +You can find an example in :ref:`dynamic_csp`. + .. index:: Upgrading; 2.3.0 to 2.4.0 Migrating from 2.3.0 to 2.4.0
--- a/roundup/cgi/templating.py Sun Jan 05 00:59:12 2025 -0500 +++ b/roundup/cgi/templating.py Tue Jan 07 20:22:33 2025 -0500 @@ -3799,6 +3799,18 @@ {'fullpath': fullpath, 'issue': e.args[0]}) return "" + def set_http_response(self, code): + '''Set the HTTP response code to the integer `code`. + Example:: + + <tal:x + tal:replace="python:utils.set_response(404);" + /> + + + will make the template return code 404 (not found). + ''' + self.client.response_code = code class MissingValue(object): def __init__(self, description, **kwargs):
--- a/roundup/instance.py Sun Jan 05 00:59:12 2025 -0500 +++ b/roundup/instance.py Tue Jan 07 20:22:33 2025 -0500 @@ -247,8 +247,38 @@ self.cgi_actions[name] = action def registerUtil(self, name, function): + """Register a function that can be called using: + `utils.<name>(...)`. + + The function is defined as: + + def function(...): + + If you need access to the client, database, form or other + item, you have to pass it explicitly:: + + utils.name(request.client, ...) + + If you need client access, consider using registerUtilMethod() + instead. + + """ self.templating_utils[name] = function + def registerUtilMethod(self, name, function): + """Register a method that can be called using: + `utils.<name>(...)`. + + Unlike registerUtil, the method is defined as: + + def function(self, ...): + + `self` is a TemplatingUtils object. You can use self.client + to access the client object for your request. + """ + setattr(self.TemplatingUtils, + name, + function) class TrackerError(RoundupException): pass
