Add SSO auth to Wagtail and Django admins (#14649)

* Add mozilla-django-oidc to the project dependencies

* Add SSO support to Bedrock for accessing Wagtail and Django admins
* Plumbs in mozilla-django-oidc
* Add custom login pages for Wagtail and Django admins that show an SSO button instead of form fields
* Retain support for username + password login (for local development)
* Tests

* Add custom CSRF page to help explain SSO-related session loss, if it occurs

Because a renewed/cycled OIDC/SSO session can zap a CSRF token and block
a user from submitting a CMS edit, we need to provide a bit more information
about what's happened. This changeset adds that, via a new template and a tiny view
to serve it, plugged in as Django's default CSRF view

Logged out users (who are very unlikely to see this anyway) get a simple
version of the message, while logged in users get more detail/context.

* Bump SSO lease time to 18 hours - trying to balance awkward signouts with wanting re-checks

* Update test.env so that Wagtail and Django admins are available by default when urlconf is generated. Oddly the reload trick didn't work here

* Update bedrock/base/templates/403_csrf.html

Co-authored-by: Alex Gibson <alexgibson@users.noreply.github.com>

* Make translation tagging consistent on new login templates

* Move new CSRF view to use a CSS bundle, not inline CSS

* Remove old, redundant CSRF view

It looks like this was no longer in use. It wasn't specified as settings.CSRF_FAILURE_VIEW so wouldn't have been used/found by Django I believe

* Drop translation markup from login templates to simplify

* Don't count the test 404 and 500 views as nonlocaled, because we do localize them

* Update bedrock/admin/templates/wagtailadmin/login.html

* Tweak wording re SSO for login pages

---------

Co-authored-by: Alex Gibson <alexgibson@users.noreply.github.com>
This commit is contained in:
Steve Jalim 2024-06-07 11:47:33 +01:00 коммит произвёл GitHub
Родитель e4c24b84db
Коммит ed764987ed
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
19 изменённых файлов: 565 добавлений и 112 удалений

Просмотреть файл

@ -17,3 +17,12 @@ WAGTAIL_ENABLE_ADMIN=True
# GS_PROJECT_ID="meao-stevejalim-dev-sandbox"
# # export this before starting the django runserver:
# # GOOGLE_APPLICATION_CREDENTIALS="./local-credentials/name-of-credentials-file.json"
# Change to True if you want to use SSO locally, else you'll use username+password auth
USE_SSO_AUTH=False
# If USE_SSO_AUTH is True, you'll be using Mozilla OpenID Connect via Auth0
# Get from IAM creentials from an appropriate person within the org to set here
# in your .env
OIDC_RP_CLIENT_ID=setme
OIDC_RP_CLIENT_SECRET=setme

Просмотреть файл

@ -0,0 +1,63 @@
{% extends "admin/login.html" %}
{% comment %} SKIP LICENSE INSERTION {% endcomment %}
{% comment %}
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at https://mozilla.org/MPL/2.0/.
{% endcomment %}
{% load auth_tags %}
{% block content %}
{% should_use_sso_auth as sso_auth_enabled %}
{% if sso_auth_enabled %}
{% if form.errors and not form.non_field_errors %}
<p class="errornote">
Please correct the error{{form.errors.items|length|pluralize}} below.
</p>
{% endif %}
{% if form.non_field_errors %}
{% for error in form.non_field_errors %}
<p class="errornote">
{{ error }}
</p>
{% endfor %}
{% endif %}
<div id="content-main">
{% if user.is_authenticated %}
<p class="errornote">
You are authenticated as {{ username }}, but are not authorized to
access this page. Would you like to login to a different account?
</p>
{% endif %}
<p class="module">
<a class="button" href="{% url 'oidc_authentication_init' %}">
Sign in with Mozilla SSO
</a>
</p>
<p>
{% url 'admin:index' as admin_url %}
Note that after sign-in, you will be sent back to the CMS admin. Please re-access {{admin_url}} manually.
</p>
<p>
<em>
If you lack SSO access to this site, please ask your manager or in #www.
</em>
</p>
</div>
{% else %}
{{block.super}}
{% endif %}
{% endblock content %}

Просмотреть файл

@ -0,0 +1,39 @@
{% extends "wagtailadmin/login.html" %}
{% comment %} SKIP LICENSE INSERTION {% endcomment %}
{% comment %}
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at https://mozilla.org/MPL/2.0/.
{% endcomment %}
{% load auth_tags %}
{% block login_form %}
{% should_use_sso_auth as sso_auth_enabled %}
{% if sso_auth_enabled %}
<a href="{% url 'oidc_authentication_init' %}" class="button button-longrunning" data-clicked-text="Signing in...">
<span class="icon icon-spinner"></span>
<em>
Sign in with Mozilla SSO
</em>
</a>
<h3>If you lack SSO access to this site, please ask your manager or in #www</h3>
{% else %}
{{block.super}}
{% endif %}
{% endblock login_form %}
{% block submit_buttons %}
{% should_use_sso_auth as sso_auth_enabled %}
{% if sso_auth_enabled %}
{# No need to show the button content if SSO is enabled#}
{% else %}
{{block.super}}
{% endif %}
{% endblock submit_buttons %}

Просмотреть файл

@ -0,0 +1,34 @@
{#
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at https://mozilla.org/MPL/2.0/.
#}
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8" />
<title>CSRF mismatch detected.</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
{{ css_bundle('csrf-failure') }}
</head>
<body>
<h1>Access denied</h1>
<p>
Cross-site request forgery (CSRF) mismatch detected.
</p>
{% if request.user.is_staff %}
<p>
This is most likely because your SSO session expired or had to be renewed.
</p>
<p>
It's likely and regrettable that you have lost work since your last save.
<br>
If this is happening a lot, please <a href="https://github.com/mozilla/bedrock">open a bug report</a>.
</p>
{% endif %}
<p>
Please go back using your browser's back button and try again. Reloading this page will not fix the problem.
</p>
</body>
</html>

Просмотреть файл

@ -0,0 +1,13 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
from django.conf import settings
from django.template import Library
register = Library()
@register.simple_tag
def should_use_sso_auth():
return settings.USE_SSO_AUTH is True

Просмотреть файл

@ -0,0 +1,22 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
from django.test import override_settings
import pytest
from bedrock.base.templatetags.auth_tags import should_use_sso_auth
@pytest.mark.parametrize(
"settings_val, expected",
(
(True, True),
(False, False),
(None, False),
),
)
def test_should_use_simple_auth(settings_val, expected):
with override_settings(USE_SSO_AUTH=settings_val):
assert should_use_sso_auth() == expected

Просмотреть файл

@ -5,6 +5,7 @@
import datetime
from unittest.mock import patch
from django.conf import settings
from django.test import RequestFactory, TestCase
from django.utils.timezone import now as tz_now
@ -73,3 +74,8 @@ def test_get_contentful_sync_info(mock_timeago_format, mock_tz_now):
# Also check the no-data context dict:
ContentfulEntry.objects.all().delete()
assert get_contentful_sync_info() == {}
@pytest.mark.django_db
def test_csrf_view_is_custom_one():
assert settings.CSRF_FAILURE_VIEW == "bedrock.base.views.csrf_failure"

Просмотреть файл

@ -172,3 +172,7 @@ def server_error_view(request, template_name="500.html"):
def page_not_found_view(request, exception=None, template_name="404.html"):
"""404 error handler that runs context processors."""
return l10n_utils.render(request, template_name, ftl_files=["404", "500"], status=404)
def csrf_failure(request, reason="CSRF failure", template_name="403_csrf.html"):
return render(request, template_name, status=403)

Просмотреть файл

@ -0,0 +1,171 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
from importlib import reload
from unittest import mock
from django.contrib.auth.models import User
from django.test import TestCase, override_settings
from django.urls import reverse
class LoginTestBase(TestCase):
TEST_ADMIN_PASSWORD = "admin12345"
def setUp(self):
self.wagtail_login_url = reverse("wagtailadmin_login")
self.django_admin_login_url = reverse("admin:login")
def _create_admin(self):
# create an admin user
admin = User.objects.create_superuser(
username="admin",
email="admin@example.com",
password=self.TEST_ADMIN_PASSWORD,
)
assert admin.is_active is True
assert admin.has_usable_password() is True
assert admin.check_password(self.TEST_ADMIN_PASSWORD) is True
assert admin.is_staff is True
assert admin.is_superuser is True
return admin
class ConventionalLoginDeniedTest(LoginTestBase):
"""Tests to show that the standard way to sign in to Wagtail and the Django
Admin just do not work (which is good, because everyone should use SSO
in production)"""
@override_settings(
WAGTAIL_ENABLE_ADMIN=True,
USE_SSO_AUTH=True,
AUTHENTICATION_BACKENDS=("mozilla_django_oidc.auth.OIDCAuthenticationBackend",),
)
def test_login_page_contains_no_form(self):
for url in (self.wagtail_login_url, self.django_admin_login_url):
with self.subTest(url=url):
response = self.client.get(url)
assert response.status_code == 200
# Check for the form field attrs that would normally be present
self.assertNotContains(response, b'name="username"')
self.assertNotContains(response, b'name="password"')
# No CSRF token == no go, anyway
self.assertNotContains(response, b"csrfmiddlewaretoken")
# Confirm SSO link
self.assertContains(response, b"Sign in with Mozilla SSO")
@override_settings(
WAGTAIL_ENABLE_ADMIN=True,
USE_SSO_AUTH=True,
AUTHENTICATION_BACKENDS=("mozilla_django_oidc.auth.OIDCAuthenticationBackend",),
)
def test_posting_to_login_denied(self):
admin = self._create_admin()
for url, error_message, expected_template in (
(
self.wagtail_login_url,
b"Your username and password didn&#x27;t match.",
"wagtailadmin/login.html",
),
(
self.django_admin_login_url,
b"Please enter the correct username and password for a staff account.",
"admin/login.html",
),
):
payload = {
"username": admin.username,
"password": self.TEST_ADMIN_PASSWORD,
}
with self.subTest(
url=url,
error_message=error_message,
expected_template=expected_template,
):
response = self.client.post(url, data=payload, follow=True)
self.assertEqual(
response.status_code,
200, # 200 is what comes back after the redirect
)
# Show that while we provided valid credentials, we still get
# treated as if they are not the correct ones.
self.assertContains(response, error_message)
self.assertContains(response, b"Sign in with Mozilla SSO")
self.assertTemplateUsed(response, expected_template)
class AuthenticationBackendSelectionTests(TestCase):
# We have to force the USE_SSO_AUTH to True at the environment level
# then import the settings to trigger the appropriate if/else branch
# that sets the right auth backend.
@mock.patch.dict("os.environ", {"USE_SSO_AUTH": "True"})
def test_only_sso_backend_enabled_if_USE_SSO_AUTH_is_True(self):
from bedrock.settings import base as base_settings
reloaded_settings = reload(base_settings)
self.assertEqual(
reloaded_settings.AUTHENTICATION_BACKENDS,
("mozilla_django_oidc.auth.OIDCAuthenticationBackend",),
)
@mock.patch.dict("os.environ", {"USE_SSO_AUTH": "False"})
def test_only_model_backend_enabled_if_USE_SSO_AUTH_is_False(self):
from bedrock.settings import base as base_settings
reloaded_settings = reload(base_settings)
self.assertEqual(
reloaded_settings.AUTHENTICATION_BACKENDS,
("django.contrib.auth.backends.ModelBackend",),
)
class ConventionalLoginAllowedTest(LoginTestBase):
"""If certain settings are set in settings.local, regular
username + password sign-in functionality is restored
"""
@override_settings(WAGTAIL_ENABLE_ADMIN=True, USE_SSO_AUTH=False)
def test_login_page_contains_form(self):
for url in (self.wagtail_login_url, self.django_admin_login_url):
with self.subTest(url=url):
response = self.client.get(url)
assert response.status_code == 200
# Check for the form field attrs that would normally be present
self.assertContains(response, b'name="username"', 1)
self.assertContains(response, b'name="password"', 1)
self.assertContains(response, b"csrfmiddlewaretoken", 1)
self.assertNotContains(response, b"Sign in with Mozilla SSO")
@override_settings(
AUTHENTICATION_BACKENDS=("django.contrib.auth.backends.ModelBackend",),
WAGTAIL_ENABLE_ADMIN=True,
USE_SSO_AUTH=False,
)
def test_posting_to_login_works_if_the_modelbackend_is_configured(self):
# Only relevant to local usage, but good to confirm
admin = self._create_admin()
for url, expected_template in (
(self.wagtail_login_url, "wagtailadmin/home.html"),
(
self.django_admin_login_url,
"wagtailadmin/home.html",
# That expected template is correct. Signing in to Django Admin
# redirects to Wagtail's Admin, because that's what
# LOGIN_REDIRECT_URL points to
),
):
payload = {
"username": admin.username,
"password": self.TEST_ADMIN_PASSWORD,
}
with self.subTest(url=url, expected_template=expected_template):
response = self.client.post(url, data=payload, follow=True)
self.assertEqual(response.status_code, 200)
self.assertNotContains(response, b"Sign in")
self.assertTemplateUsed(response, expected_template)

Просмотреть файл

@ -1,28 +0,0 @@
{#
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at https://mozilla.org/MPL/2.0/.
#}
{% extends "base-protocol-mozilla.html" %}
{% block page_title %}Invalid Request{% endblock %}
{% block page_css %}
{{ css_bundle('csrf-failure') }}
{% endblock %}
{% block body_id %}csrf-failure{% endblock %}
{% block content %}
<main>
<div class="mzp-l-content">
<h1>Invalid Request</h1>
<p>
We could not process your request.
</p>
</div>
</main>
{% endblock %}

Просмотреть файл

@ -32,11 +32,6 @@ credits_file = CreditsFile("credits")
TECH_BLOG_SLUGS = ["hacks", "cd", "futurereleases"]
def csrf_failure(request, reason=""):
template_vars = {"reason": reason}
return l10n_utils.render(request, "mozorg/csrf-failure.html", template_vars, status=403)
@xframe_allow
def hacks_newsletter(request):
return l10n_utils.render(request, "mozorg/newsletter/hacks.mozilla.org.html")

Просмотреть файл

@ -485,7 +485,9 @@ SUPPORTED_NONLOCALES = [
"xbl", # in mozorg urls
"revision.txt", # from root_files
"locales", # in mozorg urls
"csrf_403",
]
# Paths that can exist either with or without a locale code in the URL.
# Matches the whole URL path
SUPPORTED_LOCALE_IGNORE = [
@ -717,6 +719,7 @@ INSTALLED_APPS = [
"django_extensions",
"lib.l10n_utils",
"django_rq",
"mozilla_django_oidc", # needs to be loaded after django.contrib.auth
]
# Must match the list at CloudFlare if the
@ -1083,6 +1086,10 @@ LOGGING = {
"handlers": ["console"],
"propagate": False,
},
"mozilla_django_oidc": {
"handlers": ["console"],
"level": "DEBUG",
},
},
}
@ -1907,6 +1914,67 @@ RELAY_PRODUCT_URL = config(
"RELAY_PRODUCT_URL", default="https://stage.fxprivaterelay.nonprod.cloudops.mozgcp.net/" if DEV else "https://relay.firefox.com/"
)
# Authentication with Mozilla OpenID Connect / Auth0 ============================================
LOGIN_ERROR_URL = "/cms-admin/"
LOGIN_REDIRECT_URL_FAILURE = "/cms-admin/"
LOGIN_REDIRECT_URL = "/cms-admin/"
LOGOUT_REDIRECT_URL = "/cms-admin/"
OIDC_RP_SIGN_ALGO = "RS256"
# How frequently do we check with the provider that the user still exists and is authorised?
OIDC_RENEW_ID_TOKEN_EXPIRY_SECONDS = config(
"OIDC_RENEW_ID_TOKEN_EXPIRY_SECONDS",
default="64800", # 18 hours
parser=int,
)
OIDC_CREATE_USER = False # We don't want drive-by signups
OIDC_RP_CLIENT_ID = config("OIDC_RP_CLIENT_ID", default="", parser=str)
OIDC_RP_CLIENT_SECRET = config("OIDC_RP_CLIENT_SECRET", default="", parser=str)
OIDC_OP_AUTHORIZATION_ENDPOINT = "https://auth.mozilla.auth0.com/authorize"
OIDC_OP_TOKEN_ENDPOINT = "https://auth.mozilla.auth0.com/oauth/token"
OIDC_OP_USER_ENDPOINT = "https://auth.mozilla.auth0.com/userinfo"
OIDC_OP_DOMAIN = "auth.mozilla.auth0.com"
OIDC_OP_JWKS_ENDPOINT = "https://auth.mozilla.auth0.com/.well-known/jwks.json"
# If True (which should only be for local work in your .env), then show
# username and password fields when signing up, not the SSO button
USE_SSO_AUTH = config("USE_SSO_AUTH", default="True", parser=bool)
if USE_SSO_AUTH:
AUTHENTICATION_BACKENDS = (
# Deliberately OIDC only, else no entry by any other means
"mozilla_django_oidc.auth.OIDCAuthenticationBackend",
)
else:
AUTHENTICATION_BACKENDS = (
# Regular username + password auth
"django.contrib.auth.backends.ModelBackend",
)
# Note that AUTHENTICATION_BACKENDS is overridden in tests, so take care
# to check/amend those if you add additional auth backends
# Extra Wagtail config to disable password usage (SSO should be the only route)
# https://docs.wagtail.org/en/v4.2.4/reference/settings.html#wagtail-password-management-enabled
# Don't let users change or reset their password
if USE_SSO_AUTH:
WAGTAIL_PASSWORD_MANAGEMENT_ENABLED = False
WAGTAIL_PASSWORD_RESET_ENABLED = False
# Don't require a password when creating a user,
# and blank password means cannot log in unless via SSO
WAGTAILUSERS_PASSWORD_ENABLED = False
# Custom CSRF failure view to show custom CSRF messaging, which is
# more likely to appear with SSO auth enabled, when sessions expire
CSRF_FAILURE_VIEW = "bedrock.base.views.csrf_failure"
# WAGTAIL =======================================================================================
WAGTAIL_SITE_NAME = config(
@ -1944,6 +2012,7 @@ if WAGTAIL_ENABLE_ADMIN:
]
for midddleware_spec in [
"mozilla_django_oidc.middleware.SessionRefresh", # In case someone has their Auth0 revoked while logged in, revalidate it
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
@ -1955,6 +2024,7 @@ if WAGTAIL_ENABLE_ADMIN:
"cms-admin",
"django-admin",
"django-rq",
"oidc",
]
)

Просмотреть файл

@ -57,9 +57,14 @@ if settings.DEBUG:
path("404/", import_string(handler404)),
path("500/", import_string(handler500)),
)
urlpatterns += (path("csrf_403/", base_views.csrf_failure, {}),)
if settings.WAGTAIL_ENABLE_ADMIN:
# If adding new a new path here, you must also add an entry to
# settings.SUPPORTED_NONLOCALES in the `if WAGTAIL_ENABLE_ADMIN` block so
# that bedrock doesn't try to prepend a locale onto requests for the path
urlpatterns += (
path("oidc/", include("mozilla_django_oidc.urls")),
path("cms-admin/", include(wagtailadmin_urls)),
path("django-admin/", admin.site.urls), # needed to show django-rq UI
path("django-rq/", include("django_rq.urls")), # task queue management

Просмотреть файл

@ -2,3 +2,4 @@ DEBUG=False
DEV=False
ALLOWED_HOSTS=*
ADMINS=["thedude@example.com"]
WAGTAIL_ENABLE_ADMIN=True

Просмотреть файл

@ -115,8 +115,22 @@ we need to.
Infrastructure notes
--------------------
To come
SSO setup
~~~~~~~~~
When the env vars `OIDC_RP_CLIENT_ID` and `OIDC_RP_CLIENT_SECRET` are present and
`USE_SSO_AUTH` is set to True in settings, Bedrock will use Mozilla SSO instead of
username + password to sign in. The deployed sites will have these set, but we
also have credentials available for using SSO locally if you need to develop something
that needs it - see our password vault.
Note that Bedrock in SSO mode will not support 'drive by' user creation even if
they have an @mozilla.com identity. Only users who already exist in the Wagtail
admin as a User will be allowed to log in. You can create new users using Django's
`createsuperuser`_ command, setting both the username and email do be your
``flast@mozilla.com`` LDAP address
.. _wagtail: https://wagtail.org/
.. _Editor Guide: https://guide.wagtail.org/en-latest/
.. _Wagtail Images docs: https://docs.wagtail.org/en/stable/topics/images.html
.. _createsuperuser: https://docs.djangoproject.com/en/5.0/ref/django-admin/#createsuperuser

Просмотреть файл

@ -4,6 +4,10 @@
@import '~@mozilla-protocol/core/protocol/css/includes/lib';
main {
body {
min-height: 350px;
color: #000;
background-color: #fff;
font: 100%/1.5 sans-serif;
padding: 40px;
}

Просмотреть файл

@ -69,9 +69,9 @@ boto3==1.34.101 \
--hash=sha256:1d854b5880e185db546b4c759fcb664bf3326275064d2b44229cc217e8be9d7e \
--hash=sha256:79b93f3370ea96ce838042bc2eac0c996aee204b01e7e6452eb77abcbe697d6a
# via -r requirements/prod.txt
botocore==1.34.118 \
--hash=sha256:0a3d1ec0186f8b516deb39474de3d226d531f77f92a0f56ad79b80219db3ae9e \
--hash=sha256:e3f6c5636a4394768e81e33a16f5c6ae7f364f512415d423f9b9dc67fc638df4
botocore==1.34.119 \
--hash=sha256:4bdf7926a1290b2650d62899ceba65073dd2693e61c35f5cdeb3a286a0aaa27b \
--hash=sha256:b253f15b24b87b070e176af48e8ef146516090429d30a7d8b136a4c079b28008
# via
# -r requirements/prod.txt
# boto3
@ -336,41 +336,43 @@ coverage[toml]==7.5.3 \
--hash=sha256:fcf7d1d6f5da887ca04302db8e0e0cf56ce9a5e05f202720e49b3e8157ddb9a9 \
--hash=sha256:fd27d8b49e574e50caa65196d908f80e4dff64d7e592d0c59788b45aad7e8b35
# via pytest-cov
cryptography==42.0.7 \
--hash=sha256:02c0eee2d7133bdbbc5e24441258d5d2244beb31da5ed19fbb80315f4bbbff55 \
--hash=sha256:0d563795db98b4cd57742a78a288cdbdc9daedac29f2239793071fe114f13785 \
--hash=sha256:16268d46086bb8ad5bf0a2b5544d8a9ed87a0e33f5e77dd3c3301e63d941a83b \
--hash=sha256:1a58839984d9cb34c855197043eaae2c187d930ca6d644612843b4fe8513c886 \
--hash=sha256:2954fccea107026512b15afb4aa664a5640cd0af630e2ee3962f2602693f0c82 \
--hash=sha256:2e47577f9b18723fa294b0ea9a17d5e53a227867a0a4904a1a076d1646d45ca1 \
--hash=sha256:31adb7d06fe4383226c3e963471f6837742889b3c4caa55aac20ad951bc8ffda \
--hash=sha256:3577d029bc3f4827dd5bf8bf7710cac13527b470bbf1820a3f394adb38ed7d5f \
--hash=sha256:36017400817987670037fbb0324d71489b6ead6231c9604f8fc1f7d008087c68 \
--hash=sha256:362e7197754c231797ec45ee081f3088a27a47c6c01eff2ac83f60f85a50fe60 \
--hash=sha256:3de9a45d3b2b7d8088c3fbf1ed4395dfeff79d07842217b38df14ef09ce1d8d7 \
--hash=sha256:4f698edacf9c9e0371112792558d2f705b5645076cc0aaae02f816a0171770fd \
--hash=sha256:5482e789294854c28237bba77c4c83be698be740e31a3ae5e879ee5444166582 \
--hash=sha256:5e44507bf8d14b36b8389b226665d597bc0f18ea035d75b4e53c7b1ea84583cc \
--hash=sha256:779245e13b9a6638df14641d029add5dc17edbef6ec915688f3acb9e720a5858 \
--hash=sha256:789caea816c6704f63f6241a519bfa347f72fbd67ba28d04636b7c6b7da94b0b \
--hash=sha256:7f8b25fa616d8b846aef64b15c606bb0828dbc35faf90566eb139aa9cff67af2 \
--hash=sha256:8cb8ce7c3347fcf9446f201dc30e2d5a3c898d009126010cbd1f443f28b52678 \
--hash=sha256:93a3209f6bb2b33e725ed08ee0991b92976dfdcf4e8b38646540674fc7508e13 \
--hash=sha256:a3a5ac8b56fe37f3125e5b72b61dcde43283e5370827f5233893d461b7360cd4 \
--hash=sha256:a47787a5e3649008a1102d3df55424e86606c9bae6fb77ac59afe06d234605f8 \
--hash=sha256:a79165431551042cc9d1d90e6145d5d0d3ab0f2d66326c201d9b0e7f5bf43604 \
--hash=sha256:a987f840718078212fdf4504d0fd4c6effe34a7e4740378e59d47696e8dfb477 \
--hash=sha256:a9bc127cdc4ecf87a5ea22a2556cab6c7eda2923f84e4f3cc588e8470ce4e42e \
--hash=sha256:bd13b5e9b543532453de08bcdc3cc7cebec6f9883e886fd20a92f26940fd3e7a \
--hash=sha256:c65f96dad14f8528a447414125e1fc8feb2ad5a272b8f68477abbcc1ea7d94b9 \
--hash=sha256:d8e3098721b84392ee45af2dd554c947c32cc52f862b6a3ae982dbb90f577f14 \
--hash=sha256:e6b79d0adb01aae87e8a44c2b64bc3f3fe59515280e00fb6d57a7267a2583cda \
--hash=sha256:e6b8f1881dac458c34778d0a424ae5769de30544fc678eac51c1c8bb2183e9da \
--hash=sha256:e9b2a6309f14c0497f348d08a065d52f3020656f675819fc405fb63bbcd26562 \
--hash=sha256:ecbfbc00bf55888edda9868a4cf927205de8499e7fabe6c050322298382953f2 \
--hash=sha256:efd0bf5205240182e0f13bcaea41be4fdf5c22c5129fc7ced4a0282ac86998c9
cryptography==42.0.8 \
--hash=sha256:013629ae70b40af70c9a7a5db40abe5d9054e6f4380e50ce769947b73bf3caad \
--hash=sha256:2346b911eb349ab547076f47f2e035fc8ff2c02380a7cbbf8d87114fa0f1c583 \
--hash=sha256:2f66d9cd9147ee495a8374a45ca445819f8929a3efcd2e3df6428e46c3cbb10b \
--hash=sha256:2f88d197e66c65be5e42cd72e5c18afbfae3f741742070e3019ac8f4ac57262c \
--hash=sha256:31f721658a29331f895a5a54e7e82075554ccfb8b163a18719d342f5ffe5ecb1 \
--hash=sha256:343728aac38decfdeecf55ecab3264b015be68fc2816ca800db649607aeee648 \
--hash=sha256:5226d5d21ab681f432a9c1cf8b658c0cb02533eece706b155e5fbd8a0cdd3949 \
--hash=sha256:57080dee41209e556a9a4ce60d229244f7a66ef52750f813bfbe18959770cfba \
--hash=sha256:5a94eccb2a81a309806027e1670a358b99b8fe8bfe9f8d329f27d72c094dde8c \
--hash=sha256:6b7c4f03ce01afd3b76cf69a5455caa9cfa3de8c8f493e0d3ab7d20611c8dae9 \
--hash=sha256:7016f837e15b0a1c119d27ecd89b3515f01f90a8615ed5e9427e30d9cdbfed3d \
--hash=sha256:81884c4d096c272f00aeb1f11cf62ccd39763581645b0812e99a91505fa48e0c \
--hash=sha256:81d8a521705787afe7a18d5bfb47ea9d9cc068206270aad0b96a725022e18d2e \
--hash=sha256:8d09d05439ce7baa8e9e95b07ec5b6c886f548deb7e0f69ef25f64b3bce842f2 \
--hash=sha256:961e61cefdcb06e0c6d7e3a1b22ebe8b996eb2bf50614e89384be54c48c6b63d \
--hash=sha256:9c0c1716c8447ee7dbf08d6db2e5c41c688544c61074b54fc4564196f55c25a7 \
--hash=sha256:a0608251135d0e03111152e41f0cc2392d1e74e35703960d4190b2e0f4ca9c70 \
--hash=sha256:a0c5b2b0585b6af82d7e385f55a8bc568abff8923af147ee3c07bd8b42cda8b2 \
--hash=sha256:ad803773e9df0b92e0a817d22fd8a3675493f690b96130a5e24f1b8fabbea9c7 \
--hash=sha256:b297f90c5723d04bcc8265fc2a0f86d4ea2e0f7ab4b6994459548d3a6b992a14 \
--hash=sha256:ba4f0a211697362e89ad822e667d8d340b4d8d55fae72cdd619389fb5912eefe \
--hash=sha256:c4783183f7cb757b73b2ae9aed6599b96338eb957233c58ca8f49a49cc32fd5e \
--hash=sha256:c9bb2ae11bfbab395bdd072985abde58ea9860ed84e59dbc0463a5d0159f5b71 \
--hash=sha256:cafb92b2bc622cd1aa6a1dce4b93307792633f4c5fe1f46c6b97cf67073ec961 \
--hash=sha256:d45b940883a03e19e944456a558b67a41160e367a719833c53de6911cabba2b7 \
--hash=sha256:dc0fdf6787f37b1c6b08e6dfc892d9d068b5bdb671198c72072828b80bd5fe4c \
--hash=sha256:dea567d1b0e8bc5764b9443858b673b734100c2871dc93163f58c46a97a83d28 \
--hash=sha256:dec9b018df185f08483f294cae6ccac29e7a6e0678996587363dc352dc65c842 \
--hash=sha256:e3ec3672626e1b9e55afd0df6d774ff0e953452886e06e0f1eb7eb0c832e8902 \
--hash=sha256:e599b53fd95357d92304510fb7bda8523ed1f79ca98dce2f43c115950aa78801 \
--hash=sha256:fa76fbb7596cc5839320000cdd5d0955313696d9511debab7ee7278fc8b5c84a \
--hash=sha256:fff12c88a672ab9c9c1cf7b0c80e3ad9e2ebd9d828d955c126be4fd3e5578c9e
# via
# -r requirements/prod.txt
# josepy
# mozilla-django-oidc
# pyjwt
# pyopenssl
cssselect==1.2.0 \
@ -475,6 +477,7 @@ django==4.2.11 \
# django-treebeard
# django-watchman
# djangorestframework
# mozilla-django-oidc
# wagtail
# wagtail-localize
django-allow-cidr==0.7.1 \
@ -596,9 +599,9 @@ factory-boy==3.3.0 \
# via
# -r requirements/dev.in
# wagtail-factories
faker==25.4.0 \
--hash=sha256:2baf32ca8a9e6870445f2248c99b210e5d0e5eadaba5936b407f6eb9d63cb96a \
--hash=sha256:c6c2a937c59f12ee9878434c6ad3d6eca5d429d50a9b7c5923d99aa1a7ba5fba
faker==25.5.0 \
--hash=sha256:84d454fc9fef0b73428e00bdf45a36c04568c75f22727e990071580840cfbb84 \
--hash=sha256:edb85040a47ef1b30ccd8c4b6f07ee3cb4bd64aab1483be4efe75816ee2e2e36
# via factory-boy
filetype==1.2.0 \
--hash=sha256:66b56cd6474bf41d8c54660347d37afcc3f7d1970648de365c102ef77548aadb \
@ -888,6 +891,12 @@ jmespath==1.0.1 \
# -r requirements/prod.txt
# boto3
# botocore
josepy==1.14.0 \
--hash=sha256:308b3bf9ce825ad4d4bba76372cf19b5dc1c2ce96a9d298f9642975e64bd13dd \
--hash=sha256:d2b36a30f316269f3242f4c2e45e15890784178af5ec54fa3e49cf9234ee22e0
# via
# -r requirements/prod.txt
# mozilla-django-oidc
jq==1.7.0 \
--hash=sha256:0137445eb67c43eb0eb46933aff7e8afbbd6c5aaf8574efd5df536dc9d177d1d \
--hash=sha256:05ebdaa868f068967d9e7cbf76e59e61fbdafa565dbc3579c387fb1f248592bb \
@ -1215,6 +1224,10 @@ mdurl==0.1.2 \
mdx-outline @ https://github.com/mozmeao/mdx_outline/archive/refs/tags/markdown-3.4-compatibility.tar.gz \
--hash=sha256:a78e112f80628246dd45858fe18404aaa8efb8dc81949bb1fbb87e91f9654afa
# via -r requirements/prod.txt
mozilla-django-oidc==4.0.1 \
--hash=sha256:04ef58759be69f22cdc402d082480aaebf193466cad385dc9e4f8df2a0b187ca \
--hash=sha256:4ff8c64069e3e05c539cecf9345e73225a99641a25e13b7a5f933ec897b58918
# via -r requirements/prod.txt
newrelic==9.10.0 \
--hash=sha256:02db25b0fd2fc835efe4a7f1c92dbc5bbb95125341aba07152041aa6a5666cda \
--hash=sha256:09912303e04bee6aa1fe1c671e87b4e8e55461081a96210895828798f5ba8c3f \
@ -1568,7 +1581,9 @@ pynacl==1.5.0 \
pyopenssl==24.1.0 \
--hash=sha256:17ed5be5936449c5418d1cd269a1a9e9081bc54c17aed272b45856a3d3dc86ad \
--hash=sha256:cabed4bfaa5df9f1a16c0ef64a0cb65318b5cd077a7eda7d6970131ca2f41a6f
# via -r requirements/prod.txt
# via
# -r requirements/prod.txt
# josepy
pypng==0.20220715.0 \
--hash=sha256:4a43e969b8f5aaafb2a415536c1a8ec7e341cd6a3f957fd5b5f32a4cfeed902c \
--hash=sha256:739c433ba96f078315de54c0db975aee537cbc3e1d0ae4ed9aab0ca1e427e2c1
@ -1840,6 +1855,7 @@ requests==2.32.3 \
# django-mozilla-product-details
# google-api-core
# google-cloud-storage
# mozilla-django-oidc
# pygithub
# pytest-base-url
# pytest-selenium

Просмотреть файл

@ -41,6 +41,7 @@ lxml==5.2.2 # Needed as a top-level dep so that it's available for BeautifulSou
Markdown==3.6
markus[datadog]==4.2.0
https://github.com/mozmeao/mdx_outline/archive/refs/tags/markdown-3.4-compatibility.tar.gz#egg=mdx_outline
mozilla-django-oidc==4.0.1
newrelic==9.10.0
Pillow==10.3.0
psycopg2-binary==2.9.9

Просмотреть файл

@ -53,9 +53,9 @@ boto3==1.34.101 \
--hash=sha256:1d854b5880e185db546b4c759fcb664bf3326275064d2b44229cc217e8be9d7e \
--hash=sha256:79b93f3370ea96ce838042bc2eac0c996aee204b01e7e6452eb77abcbe697d6a
# via -r requirements/prod.in
botocore==1.34.118 \
--hash=sha256:0a3d1ec0186f8b516deb39474de3d226d531f77f92a0f56ad79b80219db3ae9e \
--hash=sha256:e3f6c5636a4394768e81e33a16f5c6ae7f364f512415d423f9b9dc67fc638df4
botocore==1.34.119 \
--hash=sha256:4bdf7926a1290b2650d62899ceba65073dd2693e61c35f5cdeb3a286a0aaa27b \
--hash=sha256:b253f15b24b87b070e176af48e8ef146516090429d30a7d8b136a4c079b28008
# via
# boto3
# s3transfer
@ -240,40 +240,42 @@ contextlib2==21.6.0 \
--hash=sha256:3fbdb64466afd23abaf6c977627b75b6139a5a3e8ce38405c5b413aed7a0471f \
--hash=sha256:ab1e2bfe1d01d968e1b7e8d9023bc51ef3509bba217bb730cee3827e1ee82869
# via -r requirements/prod.in
cryptography==42.0.7 \
--hash=sha256:02c0eee2d7133bdbbc5e24441258d5d2244beb31da5ed19fbb80315f4bbbff55 \
--hash=sha256:0d563795db98b4cd57742a78a288cdbdc9daedac29f2239793071fe114f13785 \
--hash=sha256:16268d46086bb8ad5bf0a2b5544d8a9ed87a0e33f5e77dd3c3301e63d941a83b \
--hash=sha256:1a58839984d9cb34c855197043eaae2c187d930ca6d644612843b4fe8513c886 \
--hash=sha256:2954fccea107026512b15afb4aa664a5640cd0af630e2ee3962f2602693f0c82 \
--hash=sha256:2e47577f9b18723fa294b0ea9a17d5e53a227867a0a4904a1a076d1646d45ca1 \
--hash=sha256:31adb7d06fe4383226c3e963471f6837742889b3c4caa55aac20ad951bc8ffda \
--hash=sha256:3577d029bc3f4827dd5bf8bf7710cac13527b470bbf1820a3f394adb38ed7d5f \
--hash=sha256:36017400817987670037fbb0324d71489b6ead6231c9604f8fc1f7d008087c68 \
--hash=sha256:362e7197754c231797ec45ee081f3088a27a47c6c01eff2ac83f60f85a50fe60 \
--hash=sha256:3de9a45d3b2b7d8088c3fbf1ed4395dfeff79d07842217b38df14ef09ce1d8d7 \
--hash=sha256:4f698edacf9c9e0371112792558d2f705b5645076cc0aaae02f816a0171770fd \
--hash=sha256:5482e789294854c28237bba77c4c83be698be740e31a3ae5e879ee5444166582 \
--hash=sha256:5e44507bf8d14b36b8389b226665d597bc0f18ea035d75b4e53c7b1ea84583cc \
--hash=sha256:779245e13b9a6638df14641d029add5dc17edbef6ec915688f3acb9e720a5858 \
--hash=sha256:789caea816c6704f63f6241a519bfa347f72fbd67ba28d04636b7c6b7da94b0b \
--hash=sha256:7f8b25fa616d8b846aef64b15c606bb0828dbc35faf90566eb139aa9cff67af2 \
--hash=sha256:8cb8ce7c3347fcf9446f201dc30e2d5a3c898d009126010cbd1f443f28b52678 \
--hash=sha256:93a3209f6bb2b33e725ed08ee0991b92976dfdcf4e8b38646540674fc7508e13 \
--hash=sha256:a3a5ac8b56fe37f3125e5b72b61dcde43283e5370827f5233893d461b7360cd4 \
--hash=sha256:a47787a5e3649008a1102d3df55424e86606c9bae6fb77ac59afe06d234605f8 \
--hash=sha256:a79165431551042cc9d1d90e6145d5d0d3ab0f2d66326c201d9b0e7f5bf43604 \
--hash=sha256:a987f840718078212fdf4504d0fd4c6effe34a7e4740378e59d47696e8dfb477 \
--hash=sha256:a9bc127cdc4ecf87a5ea22a2556cab6c7eda2923f84e4f3cc588e8470ce4e42e \
--hash=sha256:bd13b5e9b543532453de08bcdc3cc7cebec6f9883e886fd20a92f26940fd3e7a \
--hash=sha256:c65f96dad14f8528a447414125e1fc8feb2ad5a272b8f68477abbcc1ea7d94b9 \
--hash=sha256:d8e3098721b84392ee45af2dd554c947c32cc52f862b6a3ae982dbb90f577f14 \
--hash=sha256:e6b79d0adb01aae87e8a44c2b64bc3f3fe59515280e00fb6d57a7267a2583cda \
--hash=sha256:e6b8f1881dac458c34778d0a424ae5769de30544fc678eac51c1c8bb2183e9da \
--hash=sha256:e9b2a6309f14c0497f348d08a065d52f3020656f675819fc405fb63bbcd26562 \
--hash=sha256:ecbfbc00bf55888edda9868a4cf927205de8499e7fabe6c050322298382953f2 \
--hash=sha256:efd0bf5205240182e0f13bcaea41be4fdf5c22c5129fc7ced4a0282ac86998c9
cryptography==42.0.8 \
--hash=sha256:013629ae70b40af70c9a7a5db40abe5d9054e6f4380e50ce769947b73bf3caad \
--hash=sha256:2346b911eb349ab547076f47f2e035fc8ff2c02380a7cbbf8d87114fa0f1c583 \
--hash=sha256:2f66d9cd9147ee495a8374a45ca445819f8929a3efcd2e3df6428e46c3cbb10b \
--hash=sha256:2f88d197e66c65be5e42cd72e5c18afbfae3f741742070e3019ac8f4ac57262c \
--hash=sha256:31f721658a29331f895a5a54e7e82075554ccfb8b163a18719d342f5ffe5ecb1 \
--hash=sha256:343728aac38decfdeecf55ecab3264b015be68fc2816ca800db649607aeee648 \
--hash=sha256:5226d5d21ab681f432a9c1cf8b658c0cb02533eece706b155e5fbd8a0cdd3949 \
--hash=sha256:57080dee41209e556a9a4ce60d229244f7a66ef52750f813bfbe18959770cfba \
--hash=sha256:5a94eccb2a81a309806027e1670a358b99b8fe8bfe9f8d329f27d72c094dde8c \
--hash=sha256:6b7c4f03ce01afd3b76cf69a5455caa9cfa3de8c8f493e0d3ab7d20611c8dae9 \
--hash=sha256:7016f837e15b0a1c119d27ecd89b3515f01f90a8615ed5e9427e30d9cdbfed3d \
--hash=sha256:81884c4d096c272f00aeb1f11cf62ccd39763581645b0812e99a91505fa48e0c \
--hash=sha256:81d8a521705787afe7a18d5bfb47ea9d9cc068206270aad0b96a725022e18d2e \
--hash=sha256:8d09d05439ce7baa8e9e95b07ec5b6c886f548deb7e0f69ef25f64b3bce842f2 \
--hash=sha256:961e61cefdcb06e0c6d7e3a1b22ebe8b996eb2bf50614e89384be54c48c6b63d \
--hash=sha256:9c0c1716c8447ee7dbf08d6db2e5c41c688544c61074b54fc4564196f55c25a7 \
--hash=sha256:a0608251135d0e03111152e41f0cc2392d1e74e35703960d4190b2e0f4ca9c70 \
--hash=sha256:a0c5b2b0585b6af82d7e385f55a8bc568abff8923af147ee3c07bd8b42cda8b2 \
--hash=sha256:ad803773e9df0b92e0a817d22fd8a3675493f690b96130a5e24f1b8fabbea9c7 \
--hash=sha256:b297f90c5723d04bcc8265fc2a0f86d4ea2e0f7ab4b6994459548d3a6b992a14 \
--hash=sha256:ba4f0a211697362e89ad822e667d8d340b4d8d55fae72cdd619389fb5912eefe \
--hash=sha256:c4783183f7cb757b73b2ae9aed6599b96338eb957233c58ca8f49a49cc32fd5e \
--hash=sha256:c9bb2ae11bfbab395bdd072985abde58ea9860ed84e59dbc0463a5d0159f5b71 \
--hash=sha256:cafb92b2bc622cd1aa6a1dce4b93307792633f4c5fe1f46c6b97cf67073ec961 \
--hash=sha256:d45b940883a03e19e944456a558b67a41160e367a719833c53de6911cabba2b7 \
--hash=sha256:dc0fdf6787f37b1c6b08e6dfc892d9d068b5bdb671198c72072828b80bd5fe4c \
--hash=sha256:dea567d1b0e8bc5764b9443858b673b734100c2871dc93163f58c46a97a83d28 \
--hash=sha256:dec9b018df185f08483f294cae6ccac29e7a6e0678996587363dc352dc65c842 \
--hash=sha256:e3ec3672626e1b9e55afd0df6d774ff0e953452886e06e0f1eb7eb0c832e8902 \
--hash=sha256:e599b53fd95357d92304510fb7bda8523ed1f79ca98dce2f43c115950aa78801 \
--hash=sha256:fa76fbb7596cc5839320000cdd5d0955313696d9511debab7ee7278fc8b5c84a \
--hash=sha256:fff12c88a672ab9c9c1cf7b0c80e3ad9e2ebd9d828d955c126be4fd3e5578c9e
# via
# josepy
# mozilla-django-oidc
# pyjwt
# pyopenssl
datadog==0.49.1 \
@ -322,6 +324,7 @@ django==4.2.11 \
# django-treebeard
# django-watchman
# djangorestframework
# mozilla-django-oidc
# wagtail
# wagtail-localize
django-allow-cidr==0.7.1 \
@ -677,6 +680,10 @@ jmespath==1.0.1 \
# via
# boto3
# botocore
josepy==1.14.0 \
--hash=sha256:308b3bf9ce825ad4d4bba76372cf19b5dc1c2ce96a9d298f9642975e64bd13dd \
--hash=sha256:d2b36a30f316269f3242f4c2e45e15890784178af5ec54fa3e49cf9234ee22e0
# via mozilla-django-oidc
jq==1.7.0 \
--hash=sha256:0137445eb67c43eb0eb46933aff7e8afbbd6c5aaf8574efd5df536dc9d177d1d \
--hash=sha256:05ebdaa868f068967d9e7cbf76e59e61fbdafa565dbc3579c387fb1f248592bb \
@ -986,6 +993,10 @@ markus[datadog]==4.2.0 \
mdx-outline @ https://github.com/mozmeao/mdx_outline/archive/refs/tags/markdown-3.4-compatibility.tar.gz \
--hash=sha256:a78e112f80628246dd45858fe18404aaa8efb8dc81949bb1fbb87e91f9654afa
# via -r requirements/prod.in
mozilla-django-oidc==4.0.1 \
--hash=sha256:04ef58759be69f22cdc402d082480aaebf193466cad385dc9e4f8df2a0b187ca \
--hash=sha256:4ff8c64069e3e05c539cecf9345e73225a99641a25e13b7a5f933ec897b58918
# via -r requirements/prod.in
newrelic==9.10.0 \
--hash=sha256:02db25b0fd2fc835efe4a7f1c92dbc5bbb95125341aba07152041aa6a5666cda \
--hash=sha256:09912303e04bee6aa1fe1c671e87b4e8e55461081a96210895828798f5ba8c3f \
@ -1289,7 +1300,9 @@ pynacl==1.5.0 \
pyopenssl==24.1.0 \
--hash=sha256:17ed5be5936449c5418d1cd269a1a9e9081bc54c17aed272b45856a3d3dc86ad \
--hash=sha256:cabed4bfaa5df9f1a16c0ef64a0cb65318b5cd077a7eda7d6970131ca2f41a6f
# via -r requirements/prod.in
# via
# -r requirements/prod.in
# josepy
pypng==0.20220715.0 \
--hash=sha256:4a43e969b8f5aaafb2a415536c1a8ec7e341cd6a3f957fd5b5f32a4cfeed902c \
--hash=sha256:739c433ba96f078315de54c0db975aee537cbc3e1d0ae4ed9aab0ca1e427e2c1
@ -1393,6 +1406,7 @@ requests==2.32.3 \
# django-mozilla-product-details
# google-api-core
# google-cloud-storage
# mozilla-django-oidc
# pygithub
# wagtail
rich-text-renderer==0.2.8 \