mirror of
				https://github.com/django/django.git
				synced 2025-10-31 01:25:32 +00:00 
			
		
		
		
	Refs #35959 -- Added render_password_as_hash auth template tag for password rendering.
This commit is contained in:
		| @@ -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") | ||||
|         ) | ||||
|   | ||||
| @@ -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> | ||||
|   | ||||
							
								
								
									
										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>", | ||||
|         ) | ||||
|  | ||||
|     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) | ||||
|   | ||||
							
								
								
									
										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) | ||||
		Reference in New Issue
	
	Block a user