1
0
mirror of https://github.com/django/django.git synced 2025-06-04 02:59:13 +00:00

Refs #35959 -- Added render_password_as_hash auth template tag for password rendering.

This commit is contained in:
Sarah Boyce 2025-04-16 15:44:00 -03:00 committed by nessita
parent d469db978e
commit 8a0ad1ebe3
6 changed files with 88 additions and 25 deletions

View File

@ -3,7 +3,7 @@ import unicodedata
from django import forms
from django.contrib.auth import authenticate, get_user_model, password_validation
from django.contrib.auth.hashers import UNUSABLE_PASSWORD_PREFIX, identify_hasher
from django.contrib.auth.hashers import UNUSABLE_PASSWORD_PREFIX
from django.contrib.auth.models import User
from django.contrib.auth.tokens import default_token_generator
from django.contrib.sites.shortcuts import get_current_site
@ -13,7 +13,6 @@ from django.template import loader
from django.utils.encoding import force_bytes
from django.utils.http import urlsafe_base64_encode
from django.utils.text import capfirst
from django.utils.translation import gettext
from django.utils.translation import gettext_lazy as _
from django.views.decorators.debug import sensitive_variables
@ -40,24 +39,6 @@ class ReadOnlyPasswordHashWidget(forms.Widget):
def get_context(self, name, value, attrs):
context = super().get_context(name, value, attrs)
usable_password = value and not value.startswith(UNUSABLE_PASSWORD_PREFIX)
summary = []
if usable_password:
try:
hasher = identify_hasher(value)
except ValueError:
summary.append(
{
"label": gettext(
"Invalid password format or unknown hashing algorithm."
)
}
)
else:
for key, value_ in hasher.safe_summary(value).items():
summary.append({"label": gettext(key), "value": value_})
else:
summary.append({"label": gettext("No password set.")})
context["summary"] = summary
context["button_label"] = (
_("Reset password") if usable_password else _("Set password")
)

View File

@ -1,8 +1,5 @@
{% load auth %}
<div{% include 'django/forms/widgets/attrs.html' %}>
<p>
{% for entry in summary %}
<strong>{{ entry.label }}</strong>{% if entry.value %}: <bdi>{{ entry.value }}</bdi>{% endif %}
{% endfor %}
</p>
{% render_password_as_hash widget.value %}
<p><a role="button" class="button" href="{{ password_url|default:"../password/" }}">{{ button_label }}</a></p>
</div>

View File

@ -0,0 +1,25 @@
from django.contrib.auth.hashers import UNUSABLE_PASSWORD_PREFIX, identify_hasher
from django.template import Library
from django.utils.html import format_html, format_html_join
from django.utils.translation import gettext
register = Library()
@register.simple_tag
def render_password_as_hash(value):
if not value or value.startswith(UNUSABLE_PASSWORD_PREFIX):
return format_html("<p><strong>{}</strong></p>", gettext("No password set."))
try:
hasher = identify_hasher(value)
hashed_summary = hasher.safe_summary(value)
except ValueError:
return format_html(
"<p><strong>{}</strong></p>",
gettext("Invalid password format or unknown hashing algorithm."),
)
items = [(gettext(key), val) for key, val in hashed_summary.items()]
return format_html(
"<p>{}</p>",
format_html_join(" ", "<strong>{}</strong>: <bdi>{}</bdi>", items),
)

View File

@ -1445,6 +1445,29 @@ class ReadOnlyPasswordHashTest(SimpleTestCase):
"</div>",
)
def test_render_no_password(self):
widget = ReadOnlyPasswordHashWidget()
self.assertHTMLEqual(
widget.render("name", None, {}),
"<div><p><strong>No password set.</p><p>"
'<a role="button" class="button" href="../password/">Set password</a>'
"</p></div>",
)
@override_settings(
PASSWORD_HASHERS=["django.contrib.auth.hashers.PBKDF2PasswordHasher"]
)
def test_render_invalid_password_format(self):
widget = ReadOnlyPasswordHashWidget()
value = "pbkdf2_sh"
self.assertHTMLEqual(
widget.render("name", value, {}),
"<div><p>"
"<strong>Invalid password format or unknown hashing algorithm.</strong>"
'</p><p><a role="button" class="button" href="../password/">Reset password'
"</a></p></div>",
)
def test_readonly_field_has_changed(self):
field = ReadOnlyPasswordHashField()
self.assertIs(field.disabled, True)

View File

@ -0,0 +1,37 @@
from django.contrib.auth.hashers import make_password
from django.contrib.auth.templatetags.auth import render_password_as_hash
from django.test import SimpleTestCase, override_settings
class RenderPasswordAsHashTests(SimpleTestCase):
@override_settings(
PASSWORD_HASHERS=["django.contrib.auth.hashers.PBKDF2PasswordHasher"]
)
def test_valid_password(self):
value = (
"pbkdf2_sha256$100000$a6Pucb1qSFcD$WmCkn9Hqidj48NVe5x0FEM6A9YiOqQcl/83m2Z5u"
"dm0="
)
hashed_html = (
"<p><strong>algorithm</strong>: <bdi>pbkdf2_sha256</bdi> "
"<strong>iterations</strong>: <bdi>100000</bdi> "
"<strong>salt</strong>: <bdi>a6Pucb******</bdi> "
"<strong>hash</strong>: <bdi>WmCkn9**************************************"
"</bdi></p>"
)
self.assertEqual(render_password_as_hash(value), hashed_html)
def test_invalid_password(self):
expected = (
"<p><strong>Invalid password format or unknown hashing algorithm.</strong>"
"</p>"
)
for value in ["pbkdf2_sh", "md5$password", "invalid", "testhash$password"]:
with self.subTest(value=value):
self.assertEqual(render_password_as_hash(value), expected)
def test_no_password(self):
expected = "<p><strong>No password set.</strong></p>"
for value in ["", None, make_password(None)]:
with self.subTest(value=value):
self.assertEqual(render_password_as_hash(value), expected)