mirror of
https://github.com/django/django.git
synced 2025-06-11 06:29:13 +00:00
Refs #35959 -- Added render_password_as_hash auth template tag for password rendering.
This commit is contained in:
parent
d469db978e
commit
8a0ad1ebe3
@ -3,7 +3,7 @@ import unicodedata
|
|||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.contrib.auth import authenticate, get_user_model, password_validation
|
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.models import User
|
||||||
from django.contrib.auth.tokens import default_token_generator
|
from django.contrib.auth.tokens import default_token_generator
|
||||||
from django.contrib.sites.shortcuts import get_current_site
|
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.encoding import force_bytes
|
||||||
from django.utils.http import urlsafe_base64_encode
|
from django.utils.http import urlsafe_base64_encode
|
||||||
from django.utils.text import capfirst
|
from django.utils.text import capfirst
|
||||||
from django.utils.translation import gettext
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.views.decorators.debug import sensitive_variables
|
from django.views.decorators.debug import sensitive_variables
|
||||||
|
|
||||||
@ -40,24 +39,6 @@ class ReadOnlyPasswordHashWidget(forms.Widget):
|
|||||||
def get_context(self, name, value, attrs):
|
def get_context(self, name, value, attrs):
|
||||||
context = super().get_context(name, value, attrs)
|
context = super().get_context(name, value, attrs)
|
||||||
usable_password = value and not value.startswith(UNUSABLE_PASSWORD_PREFIX)
|
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"] = (
|
context["button_label"] = (
|
||||||
_("Reset password") if usable_password else _("Set password")
|
_("Reset password") if usable_password else _("Set password")
|
||||||
)
|
)
|
||||||
|
@ -1,8 +1,5 @@
|
|||||||
|
{% load auth %}
|
||||||
<div{% include 'django/forms/widgets/attrs.html' %}>
|
<div{% include 'django/forms/widgets/attrs.html' %}>
|
||||||
<p>
|
{% render_password_as_hash widget.value %}
|
||||||
{% for entry in summary %}
|
|
||||||
<strong>{{ entry.label }}</strong>{% if entry.value %}: <bdi>{{ entry.value }}</bdi>{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
</p>
|
|
||||||
<p><a role="button" class="button" href="{{ password_url|default:"../password/" }}">{{ button_label }}</a></p>
|
<p><a role="button" class="button" href="{{ password_url|default:"../password/" }}">{{ button_label }}</a></p>
|
||||||
</div>
|
</div>
|
||||||
|
0
django/contrib/auth/templatetags/__init__.py
Normal file
0
django/contrib/auth/templatetags/__init__.py
Normal file
25
django/contrib/auth/templatetags/auth.py
Normal file
25
django/contrib/auth/templatetags/auth.py
Normal 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),
|
||||||
|
)
|
@ -1445,6 +1445,29 @@ class ReadOnlyPasswordHashTest(SimpleTestCase):
|
|||||||
"</div>",
|
"</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):
|
def test_readonly_field_has_changed(self):
|
||||||
field = ReadOnlyPasswordHashField()
|
field = ReadOnlyPasswordHashField()
|
||||||
self.assertIs(field.disabled, True)
|
self.assertIs(field.disabled, True)
|
||||||
|
37
tests/auth_tests/test_templatetags.py
Normal file
37
tests/auth_tests/test_templatetags.py
Normal 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)
|
Loading…
x
Reference in New Issue
Block a user