mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-17 04:32:51 -06:00
* Closes #16964: Validate password when creating a new user or updating password for an existing user * Add serializer validation & tests --------- Co-authored-by: Nishant Gaglani <nishantgaglani@gmail.com>
This commit is contained in:
parent
93cebae55c
commit
cb59f6e6f7
@ -1,4 +1,4 @@
|
|||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model, password_validation
|
||||||
from drf_spectacular.types import OpenApiTypes
|
from drf_spectacular.types import OpenApiTypes
|
||||||
from drf_spectacular.utils import extend_schema_field
|
from drf_spectacular.utils import extend_schema_field
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
@ -61,6 +61,14 @@ class UserSerializer(ValidatedModelSerializer):
|
|||||||
'password': {'write_only': True}
|
'password': {'write_only': True}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def validate(self, data):
|
||||||
|
|
||||||
|
# Enforce password validation rules (if configured)
|
||||||
|
if not self.nested and data.get('password'):
|
||||||
|
password_validation.validate_password(data['password'], self.instance)
|
||||||
|
|
||||||
|
return super().validate(data)
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
"""
|
"""
|
||||||
Extract the password from validated data and set it separately to ensure proper hash generation.
|
Extract the password from validated data and set it separately to ensure proper hash generation.
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model, password_validation
|
||||||
from django.contrib.postgres.forms import SimpleArrayField
|
from django.contrib.postgres.forms import SimpleArrayField
|
||||||
from django.core.exceptions import FieldError
|
from django.core.exceptions import FieldError
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
@ -227,6 +227,10 @@ class UserForm(forms.ModelForm):
|
|||||||
if self.cleaned_data['password'] and self.cleaned_data['password'] != self.cleaned_data['confirm_password']:
|
if self.cleaned_data['password'] and self.cleaned_data['password'] != self.cleaned_data['confirm_password']:
|
||||||
raise forms.ValidationError(_("Passwords do not match! Please check your input and try again."))
|
raise forms.ValidationError(_("Passwords do not match! Please check your input and try again."))
|
||||||
|
|
||||||
|
# Enforce password validation rules (if configured)
|
||||||
|
if self.cleaned_data['password']:
|
||||||
|
password_validation.validate_password(self.cleaned_data['password'], self.instance)
|
||||||
|
|
||||||
|
|
||||||
class GroupForm(forms.ModelForm):
|
class GroupForm(forms.ModelForm):
|
||||||
users = DynamicModelMultipleChoiceField(
|
users = DynamicModelMultipleChoiceField(
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
|
from django.test import override_settings
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
from core.models import ObjectType
|
from core.models import ObjectType
|
||||||
@ -93,6 +94,31 @@ class UserTest(APIViewTestCases.APIViewTestCase):
|
|||||||
user.refresh_from_db()
|
user.refresh_from_db()
|
||||||
self.assertTrue(user.check_password(data['password']))
|
self.assertTrue(user.check_password(data['password']))
|
||||||
|
|
||||||
|
@override_settings(AUTH_PASSWORD_VALIDATORS=[{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
||||||
|
'OPTIONS': {'min_length': 8}
|
||||||
|
}])
|
||||||
|
def test_password_validation_enforced(self):
|
||||||
|
"""
|
||||||
|
Test that any configured password validation rules (AUTH_PASSWORD_VALIDATORS) are enforced.
|
||||||
|
"""
|
||||||
|
self.add_permissions('users.add_user')
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'username': 'new_user',
|
||||||
|
'password': 'foo',
|
||||||
|
}
|
||||||
|
url = reverse('users-api:user-list')
|
||||||
|
|
||||||
|
# Password too short
|
||||||
|
response = self.client.post(url, data, format='json', **self.header)
|
||||||
|
self.assertEqual(response.status_code, 400)
|
||||||
|
|
||||||
|
# Password long enough
|
||||||
|
data['password'] = 'foobar123'
|
||||||
|
response = self.client.post(url, data, format='json', **self.header)
|
||||||
|
self.assertEqual(response.status_code, 201)
|
||||||
|
|
||||||
|
|
||||||
class GroupTest(APIViewTestCases.APIViewTestCase):
|
class GroupTest(APIViewTestCases.APIViewTestCase):
|
||||||
model = Group
|
model = Group
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
from django.test import override_settings
|
||||||
|
|
||||||
from core.models import ObjectType
|
from core.models import ObjectType
|
||||||
from users.models import *
|
from users.models import *
|
||||||
from utilities.testing import ViewTestCases, create_test_user
|
from utilities.testing import ViewTestCases, create_test_user, extract_form_failures
|
||||||
|
|
||||||
|
|
||||||
class UserTestCase(
|
class UserTestCase(
|
||||||
@ -58,6 +60,34 @@ class UserTestCase(
|
|||||||
'last_name': 'newlastname',
|
'last_name': 'newlastname',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override_settings(AUTH_PASSWORD_VALIDATORS=[{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
||||||
|
'OPTIONS': {'min_length': 8}
|
||||||
|
}])
|
||||||
|
def test_password_validation_enforced(self):
|
||||||
|
"""
|
||||||
|
Test that any configured password validation rules (AUTH_PASSWORD_VALIDATORS) are enforced.
|
||||||
|
"""
|
||||||
|
self.add_permissions('users.add_user')
|
||||||
|
data = {
|
||||||
|
'username': 'new_user',
|
||||||
|
'password': 'foo',
|
||||||
|
'confirm_password': 'foo',
|
||||||
|
}
|
||||||
|
|
||||||
|
# Password too short
|
||||||
|
request = {
|
||||||
|
'path': self._get_url('add'),
|
||||||
|
'data': data,
|
||||||
|
}
|
||||||
|
response = self.client.post(**request)
|
||||||
|
self.assertHttpStatus(response, 200)
|
||||||
|
|
||||||
|
# Password long enough
|
||||||
|
data['password'] = 'foobar123'
|
||||||
|
data['confirm_password'] = 'foobar123'
|
||||||
|
self.assertHttpStatus(self.client.post(**request), 302)
|
||||||
|
|
||||||
|
|
||||||
class GroupTestCase(
|
class GroupTestCase(
|
||||||
ViewTestCases.GetObjectViewTestCase,
|
ViewTestCases.GetObjectViewTestCase,
|
||||||
|
Loading…
Reference in New Issue
Block a user