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 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) | ||||||
		Reference in New Issue
	
	Block a user