Skip to content

Commit

Permalink
feat: django password validators monkey patch (#130)
Browse files Browse the repository at this point in the history
feat: first version of monkey patching

feat: add django-password-validators==1.7.3

This is the latest (now), feb5-2024

feat: add edx-django-utils pck

feat: add generate password to init pipeline

chore: add natural docstring

style: pylint and isort

feat: add logging msg of generation password

feat: add edx-django-utils

style: pass flake8 long line error

chore: remove duplicated req
  • Loading branch information
johanseto authored Mar 1, 2024
1 parent 9086711 commit ce851f3
Show file tree
Hide file tree
Showing 8 changed files with 167 additions and 38 deletions.
12 changes: 12 additions & 0 deletions eox_nelp/init_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ def run_init_pipeline():
set_mako_templates()
register_xapi_transformers()
update_permissions()
patch_generate_password()


def patch_user_gender_choices():
Expand Down Expand Up @@ -82,3 +83,14 @@ def update_permissions():
| rules.HasAccessRule("staff")
| rules.HasAccessRule("instructor")
)


def patch_generate_password():
"""This method patch `generate_password` of edx_django_util package,
with custom nelp `generate_password`.
"""
# pylint: disable=import-outside-toplevel, unused-import
from edx_django_utils import user

from eox_nelp.user_authn.utils import generate_password
user.generate_password = generate_password
94 changes: 94 additions & 0 deletions eox_nelp/user_authn/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import logging
import random
import string

from django.conf import settings

LOGGER = logging.getLogger(__name__)


def password_rules():
"""
Inspect the validators defined in AUTH_PASSWORD_VALIDATORS and define
a rule list with the set of available characters and their minimum
for a specific charset category (alphabetic, digits, uppercase, etc).
This is based on the validators defined in
common.djangoapps.util.password_policy_validators and
django_password_validators.password_character_requirements.password_validation.PasswordCharacterValidator
"""
password_validators = settings.AUTH_PASSWORD_VALIDATORS
rules = {
"alpha": [string.ascii_letters, 0],
"digit": [string.digits, 0],
"upper": [string.ascii_uppercase, 0],
"lower": [string.ascii_lowercase, 0],
"punctuation": [string.punctuation, 0],
"symbol": ["£¥€©®™†§¶πμ'±", 0],
"min_length": ["", 0],
}
options_mapping = {
"min_alphabetic": "alpha",
"min_length_alpha": "alpha",
"min_length_digit": "digit",
"min_length_upper": "upper",
"min_length_lower": "lower",
"min_lower": "lower",
"min_upper": "upper",
"min_numeric": "digit",
"min_symbol": "symbol",
"min_punctuation": "punctuation",
}

for validator in password_validators:
for option, mapping in options_mapping.items():
if not validator.get("OPTIONS"):
continue
rules[mapping][1] = max(rules[mapping][1], validator["OPTIONS"].get(option, 0))
# We handle PasswordCharacterValidator separately because it can define
# its own set of special characters.
if validator["NAME"] == (
"django_password_validators.password_character_requirements.password_validation.PasswordCharacterValidator"
):
min_special = validator["OPTIONS"].get("min_length_special", 0)
special_chars = validator["OPTIONS"].get(
"special_characters", "~!@#$%^&*()_+{}\":;'[]"
)
rules["special"] = [special_chars, min_special]

return rules


def generate_password(length=12, chars=string.ascii_letters + string.digits):
"""Generate a valid random password.
The original `generate_password` doesn't account for extra validators
This picks the minimum amount of characters for each charset category.
"""
if length < 8:
raise ValueError("password must be at least 8 characters")

password = ""
password_length = length
choice = random.SystemRandom().choice
rules = password_rules()
min_length = rules.pop("min_length")[1]
password_length = max(min_length, length)
LOGGER.info("generating valid random password using eox-nelp...")

for elems in rules.values():
choices = elems[0]
needed = elems[1]
for _ in range(needed):
next_char = choice(choices)
password += next_char

# fill the password to reach password_length
if len(password) < password_length:
password += "".join(
[choice(chars) for _ in range(password_length - len(password))]
)

password_list = list(password)
random.shuffle(password_list)

password = "".join(password_list)
return password
2 changes: 2 additions & 0 deletions requirements/base.in
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ Django
django-filter
djangorestframework
djangorestframework-jsonapi==5.0.0
django-password-validators==1.7.3
edx-django-utils
edx-drf-extensions
edx-i18n-tools
edx-opaque-keys
Expand Down
37 changes: 23 additions & 14 deletions requirements/base.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ aniso8601==9.0.1
appdirs==1.4.4
# via fs
asgiref==3.7.2
# via django
# via
# django
# django-simple-history
attrs==23.2.0
# via openedx-events
beautifulsoup4==4.12.3
Expand Down Expand Up @@ -51,7 +53,7 @@ click-repl==0.3.0
# via celery
code-annotations==1.6.0
# via edx-toggles
cryptography==42.0.2
cryptography==42.0.5
# via
# jwcrypto
# pyjwt
Expand All @@ -68,6 +70,7 @@ django==3.2.24
# django-filter
# django-model-utils
# django-oauth-toolkit
# django-password-validators
# django-waffle
# djangorestframework
# djangorestframework-jsonapi
Expand Down Expand Up @@ -107,7 +110,11 @@ django-oauth-toolkit==2.3.0
# via eox-core
django-oauth2-provider==0.2.6.1
# via eox-core
django-simple-history==3.4.0
django-password-validators==1.7.3
# via
# -c requirements/constraints.txt
# -r requirements/base.in
django-simple-history==3.5.0
# via edx-proctoring
django-waffle==4.1.0
# via
Expand Down Expand Up @@ -139,8 +146,10 @@ drf-yasg==1.21.7
# via edx-api-doc-tools
edx-api-doc-tools==1.7.0
# via eox-core
edx-django-utils==5.10.1
edx-django-utils==5.2.0
# via
# -c requirements/constraints.txt
# -r requirements/base.in
# edx-drf-extensions
# edx-rest-api-client
# edx-toggles
Expand Down Expand Up @@ -174,7 +183,7 @@ edx-when==2.4.0
# via edx-proctoring
eox-core==10.0.0
# via -r requirements/base.in
eox-tenant==10.0.0
eox-tenant==11.0.1
# via
# -r requirements/base.in
# eox-theming
Expand All @@ -188,7 +197,7 @@ fastavro==1.9.4
# via openedx-events
fs==2.4.16
# via xblock
future==0.18.3
future==1.0.0
# via pyjwkest
idna==3.6
# via requests
Expand Down Expand Up @@ -217,14 +226,14 @@ markupsafe==2.1.5
# jinja2
# mako
# xblock
newrelic==9.6.0
newrelic==9.7.0
# via edx-django-utils
oauthlib==3.2.2
# via
# django-oauth-toolkit
# requests-oauthlib
# social-auth-core
openedx-events==9.5.1
openedx-events==9.5.2
# via
# -r requirements/base.in
# eox-core
Expand Down Expand Up @@ -270,7 +279,7 @@ python-dateutil==2.8.2
# edx-drf-extensions
# edx-proctoring
# xblock
python-ipware==2.0.1
python-ipware==2.0.2
# via django-ipware
python-slugify==8.0.4
# via code-annotations
Expand Down Expand Up @@ -307,7 +316,7 @@ rules==3.3
# via edx-proctoring
semantic-version==2.10.0
# via edx-drf-extensions
shortuuid==1.0.11
shortuuid==1.0.12
# via django-oauth2-provider
simplejson==3.19.2
# via xblock
Expand All @@ -332,7 +341,7 @@ soupsieve==2.5
# via beautifulsoup4
sqlparse==0.4.4
# via django
stevedore==5.1.0
stevedore==5.2.0
# via
# code-annotations
# edx-django-utils
Expand All @@ -341,7 +350,7 @@ text-unidecode==1.3
# via python-slugify
tincan==1.0.0
# via -r requirements/base.in
typing-extensions==4.9.0
typing-extensions==4.10.0
# via
# asgiref
# edx-opaque-keys
Expand All @@ -350,7 +359,7 @@ tzdata==2024.1
# via celery
uritemplate==4.1.1
# via drf-yasg
urllib3==2.2.0
urllib3==2.2.1
# via requests
vine==5.1.0
# via
Expand All @@ -363,7 +372,7 @@ web-fragments==2.1.0
# via xblock
webob==1.8.7
# via xblock
xblock==1.10.0
xblock==2.0.0
# via edx-when

# The following packages are considered to be unsafe in a requirements file:
Expand Down
3 changes: 3 additions & 0 deletions requirements/constraints.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,6 @@ djangorestframework==3.12.4
edx-drf-extensions==8.0.0
# maple django filter version
django-filter==21.1
# django password validators for generate_password monkey patch
django-password-validators==1.7.3
edx-django-utils==5.2.0
6 changes: 4 additions & 2 deletions requirements/pip-tools.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@ click==8.1.7
# via pip-tools
packaging==23.2
# via build
pip-tools==7.3.0
pip-tools==7.4.0
# via -r requirements/pip-tools.in
pyproject-hooks==1.0.0
# via build
# via
# build
# pip-tools
tomli==2.0.1
# via
# build
Expand Down
Loading

0 comments on commit ce851f3

Please sign in to comment.