Skip to content

release/verawood.2

@kdmccormick kdmccormick tagged this 29 Oct 15:12
`cert_html_view_overrides` is a course Advanced Setting, so any course
author can set it, and it is merged last into the certificate web view
context, where it can replace any key.  Two sinks rendered those values
without escaping: the footer printed `copyright_text` with `| n`, and
custom certificate templates (`CUSTOM_CERTIFICATE_TEMPLATES_ENABLED`)
were rendered with no default HTML filter at all.  That let a course
author store script that runs on the LMS origin for anyone who opens one
of the course's certificates.

This escapes at the sinks rather than at the source:

- Custom certificate templates now render with
  `default_filters=['decode.utf8', 'h']`, so every `${...}` is
  HTML-escaped while the template's own markup is left alone.
- The footer renders `${copyright_text}` rather than
  `${copyright_text | n, decode.utf8}`.
- The default copyright text uses a literal `©` rather than `©`, so
  it still renders correctly once escaped.

Escaping cannot stop a `javascript:` URL from running when it is used as
a link, because such a URL contains no characters that escaping touches,
and no template filter in Django or Mako validates a URL scheme.  So
overrides whose value parses as a script-executing URL are dropped in
the view and the configured value is used instead.

Overrides are otherwise passed through to the context untouched.
Escaping them there would corrupt URLs rendered into JavaScript strings,
and would coerce non-string overrides into strings, breaking templates
that read them as booleans or mappings.

This also removes a stray `"` from `certificate_verify_description`,
which ended with `"detached signature""".`, a leftover from the
JSON defaults the string was converted from.  The value is HTML-escaped
when rendered, so the entity appeared literally on every certificate.
This changes a translatable string, so its existing translations will
need to be refreshed.

BREAKING CHANGE: certificate context values are now HTML-escaped
wherever they are rendered.

- Custom certificate templates no longer render markup that comes from a
  context value.  An operator who deliberately renders markup must opt
  out per expression with Mako's `n` filter, e.g. `${value | n}`.
- Custom certificate templates must quote any attribute that renders a
  context value, e.g. `<a href="${company_tos_url}">` rather than
  `<a href=${company_tos_url}>`.  Escaping does not touch spaces, so in
  an unquoted attribute a value can still add an event handler.
- `copyright_text` is no longer exempt from escaping.  An operator or
  course author who set markup or HTML entities in it, via
  `CertificateHtmlViewConfiguration` or via a course's
  `cert_html_view_overrides`, will now see them rendered literally.  Use
  the characters themselves, for example `©` rather than `&copy;`.
- A certificate override whose value is a `javascript:` or `vbscript:`
  URL is now ignored and logged, and the configured value is used.
- The `org.openedx.learning.certificate.render.started.v1` filter
  (`CertificateRenderStarted`) is affected in two ways: a context value
  that a filter implementation sets or changes is now escaped wherever it
  is rendered, and a `custom_template` that a filter returns is now
  rendered with escaping.  A filter that deliberately injects markup
  should wrap the value in `openedx.core.djangolib.markup.HTML()`, which
  passes through escaping untouched, or the template should opt out with
  `${value | n}`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 4bea3ebfca742ee7b13f3a0b50ae611068098397)
Assets 2
Loading