policies/password: Add Password Policy tests, update password policy for flows
This commit is contained in:
		| @ -12,6 +12,7 @@ class PasswordPolicySerializer(ModelSerializer): | |||||||
|     class Meta: |     class Meta: | ||||||
|         model = PasswordPolicy |         model = PasswordPolicy | ||||||
|         fields = GENERAL_SERIALIZER_FIELDS + [ |         fields = GENERAL_SERIALIZER_FIELDS + [ | ||||||
|  |             "password_field", | ||||||
|             "amount_uppercase", |             "amount_uppercase", | ||||||
|             "amount_lowercase", |             "amount_lowercase", | ||||||
|             "amount_symbols", |             "amount_symbols", | ||||||
|  | |||||||
| @ -14,6 +14,7 @@ class PasswordPolicyForm(forms.ModelForm): | |||||||
|  |  | ||||||
|         model = PasswordPolicy |         model = PasswordPolicy | ||||||
|         fields = GENERAL_FIELDS + [ |         fields = GENERAL_FIELDS + [ | ||||||
|  |             "password_field", | ||||||
|             "amount_uppercase", |             "amount_uppercase", | ||||||
|             "amount_lowercase", |             "amount_lowercase", | ||||||
|             "amount_symbols", |             "amount_symbols", | ||||||
| @ -23,6 +24,7 @@ class PasswordPolicyForm(forms.ModelForm): | |||||||
|         ] |         ] | ||||||
|         widgets = { |         widgets = { | ||||||
|             "name": forms.TextInput(), |             "name": forms.TextInput(), | ||||||
|  |             "password_field": forms.TextInput(), | ||||||
|             "symbol_charset": forms.TextInput(), |             "symbol_charset": forms.TextInput(), | ||||||
|             "error_message": forms.TextInput(), |             "error_message": forms.TextInput(), | ||||||
|         } |         } | ||||||
|  | |||||||
| @ -0,0 +1,21 @@ | |||||||
|  | # Generated by Django 3.0.8 on 2020-07-10 18:29 | ||||||
|  |  | ||||||
|  | from django.db import migrations, models | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  |  | ||||||
|  |     dependencies = [ | ||||||
|  |         ("passbook_policies_password", "0001_initial"), | ||||||
|  |     ] | ||||||
|  |  | ||||||
|  |     operations = [ | ||||||
|  |         migrations.AddField( | ||||||
|  |             model_name="passwordpolicy", | ||||||
|  |             name="password_field", | ||||||
|  |             field=models.TextField( | ||||||
|  |                 default="password", | ||||||
|  |                 help_text="Field key to check, field keys defined in Prompt stages are available.", | ||||||
|  |             ), | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
| @ -14,6 +14,13 @@ LOGGER = get_logger() | |||||||
| class PasswordPolicy(Policy): | class PasswordPolicy(Policy): | ||||||
|     """Policy to make sure passwords have certain properties""" |     """Policy to make sure passwords have certain properties""" | ||||||
|  |  | ||||||
|  |     password_field = models.TextField( | ||||||
|  |         default="password", | ||||||
|  |         help_text=_( | ||||||
|  |             "Field key to check, field keys defined in Prompt stages are available." | ||||||
|  |         ), | ||||||
|  |     ) | ||||||
|  |  | ||||||
|     amount_uppercase = models.IntegerField(default=0) |     amount_uppercase = models.IntegerField(default=0) | ||||||
|     amount_lowercase = models.IntegerField(default=0) |     amount_lowercase = models.IntegerField(default=0) | ||||||
|     amount_symbols = models.IntegerField(default=0) |     amount_symbols = models.IntegerField(default=0) | ||||||
| @ -24,19 +31,29 @@ class PasswordPolicy(Policy): | |||||||
|     form = "passbook.policies.password.forms.PasswordPolicyForm" |     form = "passbook.policies.password.forms.PasswordPolicyForm" | ||||||
|  |  | ||||||
|     def passes(self, request: PolicyRequest) -> PolicyResult: |     def passes(self, request: PolicyRequest) -> PolicyResult: | ||||||
|         # Only check if password is being set |         if self.password_field not in request.context: | ||||||
|         if not hasattr(request.user, "__password__"): |             LOGGER.warning( | ||||||
|             return PolicyResult(True) |                 "Password field not set in Policy Request", | ||||||
|         password = getattr(request.user, "__password__") |                 field=self.password_field, | ||||||
|  |                 fields=request.context.keys(), | ||||||
|  |             ) | ||||||
|  |         password = request.context[self.password_field] | ||||||
|  |  | ||||||
|         filter_regex = r"" |         filter_regex = [] | ||||||
|         if self.amount_lowercase > 0: |         if self.amount_lowercase > 0: | ||||||
|             filter_regex += r"[a-z]{%d,}" % self.amount_lowercase |             filter_regex.append(r"[a-z]{%d,}" % self.amount_lowercase) | ||||||
|         if self.amount_uppercase > 0: |         if self.amount_uppercase > 0: | ||||||
|             filter_regex += r"[A-Z]{%d,}" % self.amount_uppercase |             filter_regex.append(r"[A-Z]{%d,}" % self.amount_uppercase) | ||||||
|         if self.amount_symbols > 0: |         if self.amount_symbols > 0: | ||||||
|             filter_regex += r"[%s]{%d,}" % (self.symbol_charset, self.amount_symbols) |             filter_regex.append( | ||||||
|         result = bool(re.compile(filter_regex).match(password)) |                 r"[%s]{%d,}" % (self.symbol_charset, self.amount_symbols) | ||||||
|  |             ) | ||||||
|  |         full_regex = "|".join(filter_regex) | ||||||
|  |         LOGGER.debug("Built regex", regexp=full_regex) | ||||||
|  |         result = bool(re.compile(full_regex).match(password)) | ||||||
|  |  | ||||||
|  |         result = result and len(password) >= self.length_min | ||||||
|  |  | ||||||
|         if not result: |         if not result: | ||||||
|             return PolicyResult(result, self.error_message) |             return PolicyResult(result, self.error_message) | ||||||
|         return PolicyResult(result) |         return PolicyResult(result) | ||||||
|  | |||||||
							
								
								
									
										42
									
								
								passbook/policies/password/tests.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								passbook/policies/password/tests.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,42 @@ | |||||||
|  | """Password Policy tests""" | ||||||
|  | from django.test import TestCase | ||||||
|  | from guardian.shortcuts import get_anonymous_user | ||||||
|  |  | ||||||
|  | from passbook.policies.password.models import PasswordPolicy | ||||||
|  | from passbook.policies.types import PolicyRequest, PolicyResult | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class TestPasswordPolicy(TestCase): | ||||||
|  |     """Test Password Policy""" | ||||||
|  |  | ||||||
|  |     def test_false(self): | ||||||
|  |         """Failing password case""" | ||||||
|  |         policy = PasswordPolicy.objects.create( | ||||||
|  |             name="test_false", | ||||||
|  |             amount_uppercase=1, | ||||||
|  |             amount_lowercase=2, | ||||||
|  |             amount_symbols=3, | ||||||
|  |             length_min=24, | ||||||
|  |             error_message="test message", | ||||||
|  |         ) | ||||||
|  |         request = PolicyRequest(get_anonymous_user()) | ||||||
|  |         request.context["password"] = "test" | ||||||
|  |         result: PolicyResult = policy.passes(request) | ||||||
|  |         self.assertFalse(result.passing) | ||||||
|  |         self.assertEqual(result.messages, ("test message",)) | ||||||
|  |  | ||||||
|  |     def test_true(self): | ||||||
|  |         """Positive password case""" | ||||||
|  |         policy = PasswordPolicy.objects.create( | ||||||
|  |             name="test_true", | ||||||
|  |             amount_uppercase=1, | ||||||
|  |             amount_lowercase=2, | ||||||
|  |             amount_symbols=3, | ||||||
|  |             length_min=3, | ||||||
|  |             error_message="test message", | ||||||
|  |         ) | ||||||
|  |         request = PolicyRequest(get_anonymous_user()) | ||||||
|  |         request.context["password"] = "Test()!" | ||||||
|  |         result: PolicyResult = policy.passes(request) | ||||||
|  |         self.assertTrue(result.passing) | ||||||
|  |         self.assertEqual(result.messages, tuple()) | ||||||
| @ -5838,6 +5838,11 @@ definitions: | |||||||
|         title: Name |         title: Name | ||||||
|         type: string |         type: string | ||||||
|         x-nullable: true |         x-nullable: true | ||||||
|  |       password_field: | ||||||
|  |         title: Password field | ||||||
|  |         description: Field key to check, field keys defined in Prompt stages are available. | ||||||
|  |         type: string | ||||||
|  |         minLength: 1 | ||||||
|       amount_uppercase: |       amount_uppercase: | ||||||
|         title: Amount uppercase |         title: Amount uppercase | ||||||
|         type: integer |         type: integer | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user
	 Jens Langhammer
					Jens Langhammer