Repository: wagtail/wagtail

Issue:
Rename settings `PASSWORD_REQUIRED_TEMPLATE` & `DOCUMENT_PASSWORD_REQUIRED_TEMPLATE`
### Is your proposal related to a problem?

We have two Wagtail specific settings that are not prefixed with Wagtail, it would be good for these to be prefixed to avoid potential naming conflicts with other Django packages.

Originally I suggested we rename the setting to avoid the term 'password', however, that was agreed by the core team to be too confusing and not give us much benefits for security communication.

This is what the new config names would be:

| **Current** | **Proposed** |
|-|-|
| `PASSWORD_REQUIRED_TEMPLATE` | `WAGTAIL_PASSWORD_REQUIRED_TEMPLATE` |
| `PASSWORD_DOCUMENT_PASSWORD_REQUIRED_TEMPLATE` | `WAGTAILDOCS_PASSWORD_REQUIRED_TEMPLATE` |

### Describe the solution you'd like

1. Rename these two settings as per above.
  * `WAGTAIL_PASSWORD_REQUIRED_TEMPLATE` in https://github.com/wagtail/wagtail/blob/main/wagtail/urls.py#L20
  * `WAGTAILDOCS_PASSWORD_REQUIRED_TEMPLATE` in https://github.com/wagtail/wagtail/blob/main/wagtail/documents/wagtail_hooks.py#L191 & https://github.com/wagtail/wagtail/blob/main/wagtail/documents/views/serve.py#L140
2. Ensure there is backwards compatible support provided for the current Settings with a warning triggered.
4. Ensure that any example code, documentation and templates are updated.

```py
# example of warning for settings (just POC)
# ... other imports
from wagtail.utils.deprecation import RemovedInWagtail70Warning
from warnings import warn

if hasattr(settings, "WAGTAIL_PASSWORD_REQUIRED_TEMPLATE"):
    warn(
        "The `PASSWORD_REQUIRED_TEMPLATE` setting is deprecated - use `WAGTAIL_PASSWORD_REQUIRED_TEMPLATE` instead.",
        category=RemovedInWagtail70Warning,
    )


# then we can check both settings by first using the new name, then the old name, then the default
PASSWORD_REQUIRED_TEMPLATE = getattr(
    settings,
    "WAGTAIL_PASSWORD_REQUIRED_TEMPLATE
    getattr(settings, "PASSWORD_REQUIRED_TEMPLATE", "wagtailcore/login.html")
)

```

### Describe alternatives you've considered

* Leave as is, this is not causing too many issues, it's mostly for consistency.

### Additional context

* https://github.com/wagtail/wagtail/issues/10588 - issue & discussion about other private pages / shared password usage.



Discussion:
@RealOrangeOne  - just wanted to get your thoughts on this naming proposal.

I know this may create a bit of a disconnect with the Page attribute but we call this 'private' pages everywhere and I think that moving away from the term Password in both the UI and the settings will help to communicate that this is not a password but rather a unique access key.
Which setting are you. renaming? I think `WAGTAIL_FRONTEND_LOGIN_TEMPLATE` is a good name for `wagtailcore/login.html`. The one that could probably use renaming to cover the discussion in #10588 would be `PASSWORD_REQUIRED_TEMPLATE` which defaults to `wagtailcore/password_required.html`. What about `WAGTAIL_SHARED_SECRET_TEMPLATE` and `WAGTAILDOCS_SHARED_SECRET_TEMPLATE`?  

While we are renaming, should we [update the docs](https://docs.wagtail.org/en/latest/advanced_topics/privacy.html#private-pages) to refer to "shared secret" rather than "password"? 
@cnk this renaming only covers the two settings that are for the password required templates https://docs.wagtail.org/en/stable/reference/settings.html#frontend-authentication

These are only used for the Private pages and private document features and not for general log in for the admin.

I think this is part of the confusion, the password required template is not used for logging into the admin with a password, it's only used to enter the 'secret key' set by private pages. It does cause a bit of ongoing questions in Slack.

I think there is a wider discussion about adopting a 'secret key' or 'access code' type naming for this feature, I strongly agree with the sentiments on https://github.com/wagtail/wagtail/issues/10588 & also the push back on https://github.com/wagtail/wagtail/pull/10729 where I think the direction is to avoid calling this a 'password' at all.

All that to say, if we renaming these two settings, we probably want to only rename it once, but we need to consider that this is a Front facing template and hence `FRONTEND` in there makes sense. soo... what about..

* `PASSWORD_REQUIRED_TEMPLATE` -> `WAGTAIL_FRONTEND_SHARED_SECRET_TEMPLATE`
* `DOCUMENT_PASSWORD_REQUIRED_TEMPLATE` -> `WAGTAILDOCS_FRONTEND_SHARED_SECRET_TEMPLATE`

> * `PASSWORD_REQUIRED_TEMPLATE` -> `WAGTAIL_FRONTEND_SHARED_SECRET_TEMPLATE`
> * `DOCUMENT_PASSWORD_REQUIRED_TEMPLATE` -> `WAGTAILDOCS_FRONTEND_SHARED_SECRET_TEMPLATE`

That seems good to me. I like that they share `WAGTAIL_FRONTEND_` with the front end login settings used for the other 2 privacy options. 
The only potential issue I see with "private access" is that one of the modes requires Wagtail's login mechanism, which might be confusing to new users that it gets used over the template in `WAGTAIL_FRONTEND_PRIVATE_ACCESS_TEMPLATE`.

Naming things is also hard, so a proper discussion on what we should be calling "pages protected by a shared secret" is one worth having as part of this, and potentially as a dependency of it.
@RealOrangeOne that's a good point, this is a tricky one but I do think there is a consensus on https://github.com/wagtail/wagtail/issues/10588#issuecomment-1602051703 (that comment from @allcaps ) that we should move to calling the 'password' a 'shared secret'. 

I understand there is a bit of blurry lines between this and the actual log-in required level of private pages access, from a developer perspective.

I'll label this as needs design decision though to ensure this and #10588 get another discussion at the next core team.

Update. Also I may need to double check my understanding of how these templates are used. 
so whats the update on this @lb- ?

@vinfinity7 we will try to get a bit of consensus here at the next core team meeting. No progress yet. 

Feel free to add any thoughts on this. 
Discussed this at the core team meeting today.

We will keep the current naming but simply add a WAGTAIL_ prefix with backwards compatibility / deprecation warnings.

The reasoning is that adding a different name will likely cause confusion as to what this is and on the UX side we will keep the term 'password' (or 'shared password').

See https://github.com/wagtail/wagtail/issues/10588#issuecomment-1907806674 for context.

LB to update this issue's description accordingly.
Ohk assign me I'm on it 

Resolving patch:
diff --git a/wagtail/documents/views/serve.py b/wagtail/documents/views/serve.py
--- a/wagtail/documents/views/serve.py
+++ b/wagtail/documents/views/serve.py
@@ -1,3 +1,5 @@
+from warnings import warn
+
 from django.conf import settings
 from django.http import FileResponse, Http404, HttpResponse
 from django.shortcuts import get_object_or_404, redirect
@@ -12,6 +14,7 @@
 from wagtail.forms import PasswordViewRestrictionForm
 from wagtail.models import CollectionViewRestriction
 from wagtail.utils import sendfile_streaming_backend
+from wagtail.utils.deprecation import RemovedInWagtail70Warning
 from wagtail.utils.sendfile import sendfile
 
 
@@ -135,9 +138,21 @@ def authenticate_with_password(request, restriction_id):
 
     password_required_template = getattr(
         settings,
-        "DOCUMENT_PASSWORD_REQUIRED_TEMPLATE",
+        "WAGTAILDOCS_PASSWORD_REQUIRED_TEMPLATE",
         "wagtaildocs/password_required.html",
     )
 
+    if hasattr(settings, "DOCUMENT_PASSWORD_REQUIRED_TEMPLATE"):
+        warn(
+            "The `DOCUMENT_PASSWORD_REQUIRED_TEMPLATE` setting is deprecated - use `WAGTAILDOCS_PASSWORD_REQUIRED_TEMPLATE` instead.",
+            category=RemovedInWagtail70Warning,
+        )
+
+        password_required_template = getattr(
+            settings,
+            "DOCUMENT_PASSWORD_REQUIRED_TEMPLATE",
+            password_required_template,
+        )
+
     context = {"form": form, "action_url": action_url}
     return TemplateResponse(request, password_required_template, context)
diff --git a/wagtail/documents/wagtail_hooks.py b/wagtail/documents/wagtail_hooks.py
--- a/wagtail/documents/wagtail_hooks.py
+++ b/wagtail/documents/wagtail_hooks.py
@@ -1,3 +1,5 @@
+from warnings import warn
+
 from django.conf import settings
 from django.template.response import TemplateResponse
 from django.urls import include, path, reverse, reverse_lazy
@@ -30,6 +32,7 @@
 )
 from wagtail.documents.views.chooser import viewset as chooser_viewset
 from wagtail.models import BaseViewRestriction
+from wagtail.utils.deprecation import RemovedInWagtail70Warning
 from wagtail.wagtail_hooks import require_wagtail_login
 
 
@@ -180,10 +183,22 @@ def check_view_restrictions(document, request):
 
                 password_required_template = getattr(
                     settings,
-                    "DOCUMENT_PASSWORD_REQUIRED_TEMPLATE",
+                    "WAGTAILDOCS_PASSWORD_REQUIRED_TEMPLATE",
                     "wagtaildocs/password_required.html",
                 )
 
+                if hasattr(settings, "DOCUMENT_PASSWORD_REQUIRED_TEMPLATE"):
+                    warn(
+                        "The `DOCUMENT_PASSWORD_REQUIRED_TEMPLATE` setting is deprecated - use `WAGTAILDOCS_PASSWORD_REQUIRED_TEMPLATE` instead.",
+                        category=RemovedInWagtail70Warning,
+                    )
+
+                    password_required_template = getattr(
+                        settings,
+                        "DOCUMENT_PASSWORD_REQUIRED_TEMPLATE",
+                        password_required_template,
+                    )
+
                 context = {"form": form, "action_url": action_url}
                 return TemplateResponse(request, password_required_template, context)
 
diff --git a/wagtail/models/__init__.py b/wagtail/models/__init__.py
--- a/wagtail/models/__init__.py
+++ b/wagtail/models/__init__.py
@@ -18,6 +18,7 @@
 from io import StringIO
 from typing import TYPE_CHECKING
 from urllib.parse import urlparse
+from warnings import warn
 
 from django import forms
 from django.conf import settings
@@ -95,6 +96,7 @@
     workflow_submitted,
 )
 from wagtail.url_routing import RouteResult
+from wagtail.utils.deprecation import RemovedInWagtail70Warning
 from wagtail.utils.timestamps import ensure_utc
 
 from .audit_log import (  # noqa: F401
@@ -2592,9 +2594,7 @@ def add_page_to_check_list(page):
 
         return PageViewRestriction.objects.filter(page_id__in=page_ids_to_check)
 
-    password_required_template = getattr(
-        settings, "PASSWORD_REQUIRED_TEMPLATE", "wagtailcore/password_required.html"
-    )
+    password_required_template = None
 
     def serve_password_required_response(self, request, form, action_url):
         """
@@ -2604,10 +2604,32 @@ def serve_password_required_response(self, request, form, action_url):
             (and zero or more hidden fields that also need to be output on the template)
         action_url = URL that this form should be POSTed to
         """
+
+        password_required_template = self.password_required_template
+
+        if not password_required_template:
+            password_required_template = getattr(
+                settings,
+                "WAGTAIL_PASSWORD_REQUIRED_TEMPLATE",
+                "wagtailcore/password_required.html",
+            )
+
+            if hasattr(settings, "PASSWORD_REQUIRED_TEMPLATE"):
+                warn(
+                    "The `PASSWORD_REQUIRED_TEMPLATE` setting is deprecated - use `WAGTAIL_PASSWORD_REQUIRED_TEMPLATE` instead.",
+                    category=RemovedInWagtail70Warning,
+                )
+
+                password_required_template = getattr(
+                    settings,
+                    "PASSWORD_REQUIRED_TEMPLATE",
+                    password_required_template,
+                )
+
         context = self.get_context(request)
         context["form"] = form
         context["action_url"] = action_url
-        return TemplateResponse(request, self.password_required_template, context)
+        return TemplateResponse(request, password_required_template, context)
 
     def with_content_json(self, content):
         """


Test patch:
diff --git a/wagtail/documents/tests/test_collection_privacy.py b/wagtail/documents/tests/test_collection_privacy.py
--- a/wagtail/documents/tests/test_collection_privacy.py
+++ b/wagtail/documents/tests/test_collection_privacy.py
@@ -107,6 +107,28 @@ def test_anonymous_user_must_authenticate(self):
             )
             self.assertRedirects(response, "/")
 
+    @override_settings(
+        WAGTAILDOCS_PASSWORD_REQUIRED_TEMPLATE="tests/custom_docs_password_required.html"
+    )
+    def test_anonymous_user_must_authenticate_with_custom_password_required_template(
+        self
+    ):
+        secret_document = Document.objects.create(
+            title="Test document",
+            file=self.fake_file,
+            collection=self.password_collection,
+        )
+        doc_url = reverse(
+            "wagtaildocs_serve", args=(secret_document.id, secret_document.filename)
+        )
+        response = self.client.get(doc_url)
+        self.assertNotEqual(
+            response.templates[0].name, "wagtaildocs/password_required.html"
+        )
+        self.assertEqual(
+            response.templates[0].name, "tests/custom_docs_password_required.html"
+        )
+
     def test_group_restriction_with_anonymous_user(self):
         response, url = self.get_document(self.group_collection)
         self.assertRedirects(response, f"/_util/login/?next={url}")
diff --git a/wagtail/test/testapp/templates/tests/custom_docs_password_required.html b/wagtail/test/testapp/templates/tests/custom_docs_password_required.html
new file mode 100644
--- /dev/null
+++ b/wagtail/test/testapp/templates/tests/custom_docs_password_required.html
@@ -0,0 +1,10 @@
+{% extends "tests/base.html" %}
+
+{% block content %}
+    <p>Test only.</p>
+    <form action="{{ action_url }}" method="POST">
+        {% csrf_token %}
+        {{ form.as_p }}
+        <input type="submit" value="Continue" class="button" />
+    </form>
+{% endblock %}
diff --git a/wagtail/test/testapp/templates/tests/custom_page_password_required.html b/wagtail/test/testapp/templates/tests/custom_page_password_required.html
new file mode 100644
--- /dev/null
+++ b/wagtail/test/testapp/templates/tests/custom_page_password_required.html
@@ -0,0 +1,10 @@
+{% extends "tests/base.html" %}
+
+{% block content %}
+    <p>Test only.</p>
+    <form action="{{ action_url }}" method="POST">
+        {% csrf_token %}
+        {{ form.as_p }}
+        <input type="submit" value="Continue" class="button" />
+    </form>
+{% endblock %}
diff --git a/wagtail/tests/test_page_privacy.py b/wagtail/tests/test_page_privacy.py
--- a/wagtail/tests/test_page_privacy.py
+++ b/wagtail/tests/test_page_privacy.py
@@ -1,5 +1,5 @@
 from django.contrib.auth.models import Group
-from django.test import TestCase
+from django.test import TestCase, override_settings
 
 from wagtail.models import Page, PageViewRestriction
 from wagtail.test.utils import WagtailTestUtils
@@ -79,6 +79,23 @@ def test_anonymous_user_must_authenticate(self):
             )
             self.assertRedirects(response, "/")
 
+    @override_settings(
+        WAGTAIL_PASSWORD_REQUIRED_TEMPLATE="tests/custom_page_password_required.html"
+    )
+    def test_anonymous_user_must_authenticate_with_custom_password_required_template(
+        self
+    ):
+        response = self.client.get("/secret-plans/")
+
+        self.assertNotEqual(
+            "wagtailcore/password_required.html",
+            response.templates[0].name,
+        )
+        self.assertEqual(
+            "tests/custom_page_password_required.html",
+            response.templates[0].name,
+        )
+
     def test_view_restrictions_apply_to_subpages(self):
         underpants_page = Page.objects.get(
             url_path="/home/secret-plans/steal-underpants/"