Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Jlc/palm mig/patch generate passwords #130

Merged
merged 1 commit into from
Mar 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Comment on lines +93 to +96

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @johanseto for the fix today.

I believe we should patch the generate password locally inside the importing modules:

from lms.djangoapps.support.views import manage_user
manage_user.generate_password = generate_password

Otherwise the imports won't be updated in manage_user due to the patch being too late in the import order.

What do you think?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Forking edx_django_utils is also an option.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @OmarIthawi the patching locally is managed this PR.
https://github.com/eduNEXT/eox-nelp/pull/208/files
Is doing what you recommend using eox-tenant and seems working. Let us check out the behavior.

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
Loading