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

feat: password complexity option on DB Auth #1687

Merged
merged 14 commits into from
Sep 7, 2021

Conversation

dpgaspar
Copy link
Owner

@dpgaspar dpgaspar commented Sep 6, 2021

Description

New feature to support password complexity validation to AUTH database users.

Adds the following new optional config keys:

  • FAB_PASSWORD_COMPLEXITY_VALIDATOR: Hook for a custom function to validate password complexity
  • FAB_PASSWORD_COMPLEXITY_ENABLED: Enables new FAB default password complexity validator

This PR also adds partial mypy type checking and a small test refactor

ADDITIONAL INFORMATION

@codecov
Copy link

codecov bot commented Sep 6, 2021

Codecov Report

Merging #1687 (642b9da) into master (4d7dad0) will increase coverage by 0.12%.
The diff coverage is 96.77%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #1687      +/-   ##
==========================================
+ Coverage   76.43%   76.55%   +0.12%     
==========================================
  Files          55       55              
  Lines        8032     8061      +29     
==========================================
+ Hits         6139     6171      +32     
+ Misses       1893     1890       -3     
Flag Coverage Δ
python 76.55% <96.77%> (+0.12%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted Files Coverage Δ
flask_appbuilder/security/views.py 62.02% <91.30%> (+0.27%) ⬆️
flask_appbuilder/exceptions.py 100.00% <100.00%> (ø)
flask_appbuilder/models/base.py 74.03% <100.00%> (+0.29%) ⬆️
flask_appbuilder/security/forms.py 100.00% <100.00%> (ø)
flask_appbuilder/validators.py 100.00% <100.00%> (ø)
flask_appbuilder/views.py 65.68% <0.00%> (+0.42%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 4d7dad0...642b9da. Read the comment docs.

@dpgaspar dpgaspar marked this pull request as ready for review September 6, 2021 17:02
@dpgaspar
Copy link
Owner Author

dpgaspar commented Sep 7, 2021

@ashb @potiuk

- At least 3 Lowercase letters
- At least 1 special character
- At least 2 numeric digits
- At least 10 total digits

Choose a reason for hiding this comment

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

It should be 'At least 10 total characters'.

Copy link
Owner Author

Choose a reason for hiding this comment

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

fixed, thks!

Choose a reason for hiding this comment

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

LGTM, it has all the options I was looking for.

Copy link
Contributor

@villebro villebro left a comment

Choose a reason for hiding this comment

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

One personal opinion + one minor improvement proposal

@@ -5,6 +5,7 @@

from ..fieldwidgets import BS3PasswordFieldWidget, BS3TextFieldWidget
from ..forms import DynamicForm
from ..validators import PasswordComplexityValidator
Copy link
Contributor

Choose a reason for hiding this comment

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

Just an opinion, but while we're on the topic of improving typing/readability, I somehow always find fully qualified imports more pleasant to read:

from flask_appbuilder.validators import PasswordComplexityValidator

over

from ..validators import PasswordComplexityValidator

Copy link
Owner Author

Choose a reason for hiding this comment

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

Currently I prefer them also, but I'm following the current code base "convention", I plan to change them on a different PR

Copy link
Contributor

Choose a reason for hiding this comment

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

Agree. Relative imports are evil.

Copy link
Contributor

Choose a reason for hiding this comment

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

password_complexity_regex = re.compile(
r"""(
^(?=.*[A-Z].*[A-Z]) # at least two capital letters
(?=.*[!@#$&*]) # at least one of these special characters
Copy link
Contributor

Choose a reason for hiding this comment

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

To not "force" users to use a highly restricted set of special characters, would it be better to do a negative match for non-special characters here? Something like

(?!([a-zA-Z0-9])).

Copy link
Owner Author

Choose a reason for hiding this comment

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

good point

Copy link
Contributor

@villebro villebro left a comment

Choose a reason for hiding this comment

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

LGTM

@potiuk
Copy link
Contributor

potiuk commented Sep 7, 2021

Oh cool. did not have time to follow up on this one. let me take a look.

@@ -47,6 +47,13 @@ class BaseInterface(object):
def __init__(self, obj: Type[Any]):
self.obj = obj

def __getattr__(self, name: str) -> Any:
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice. I did not know about it !

from ..base import FABTestCase


class PasswordComplexityTestCase(FABTestCase):
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggestion: Maybe you could use https://pypi.org/project/parameterized/ here?

Copy link
Contributor

Choose a reason for hiding this comment

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

And few other places.

Copy link
Owner Author

Choose a reason for hiding this comment

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

Copy link
Owner Author

@dpgaspar dpgaspar Sep 7, 2021

Choose a reason for hiding this comment

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

added, looks better and it encourages to write more test cases (which I did) :)

password_complexity_validator(field.data)
except PasswordComplexityValidationError as exc:
raise ValidationError(str(exc))
if current_app.config.get("FAB_PASSWORD_COMPLEXITY_ENABLED", False):
Copy link
Contributor

@potiuk potiuk Sep 7, 2021

Choose a reason for hiding this comment

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

I think the current set of configuration variables is a bit confusing.

When I see FAB_PASSWORD_COMPLEXITY_ENABLED - I think this parameter configures password complexity check in general, so I expect that if FAB_PASSWORD_COMPLEXITY_ENABLED is "false" then no validation is done (even if I have my own custom validator configured in FAB_PASSWORD_COMPLEXITY_VALIDATOR).

Currently, in order to enable ONLY custom validator you have to have effectively this combination:

FAB_PASSWORD_COMPLEXITY_ENABLED = false
FAB_PASSWORD_COMPLEXITY_VALIDATOR = your_validator

This is pretty confusing (because I'd expect FAB_PASSWORD_COMPLEXITY_ENABLED = false disables validation of password completely.

I think two one of the two approaches would be better:

a) FAB_PASSWORD_COMPLEXITY_ENABLED - enables validation in general, and FAB_PASSWORD_COMPLEXITY_VALIDATOR replaces the default validator with the custom one. In this case you cannot run both custom and default validation at the same time

b) FAB_PASSWORD_COMPLEXITY_ENABLED - should be renamed to FAB_PASSWORD_DEFAULT_COMPLEXITY_VALIDATOR - to not make impression that whole feature is disabled by setting it to false

I have slight preference for option a) (I think it's better to always run one validator - either default or custom, you can always derive your custom validator from the default one and use it internally) - but I think both a) and b) are better than current proposal.

Copy link
Owner Author

Choose a reason for hiding this comment

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

Very good point, went for option a)

Copy link
Contributor

Choose a reason for hiding this comment

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

Cool. Looks great now. Then I have one more NIT comment :)

@potiuk
Copy link
Contributor

potiuk commented Sep 7, 2021

Thanks for that @dpgaspar - I had some other pressing issues in the meantime, so I did not pick it up as promised, sorry for that. I hoe you find my comments useful

@dpgaspar
Copy link
Owner Author

dpgaspar commented Sep 7, 2021

Thanks for that @dpgaspar - I had some other pressing issues in the meantime, so I did not pick it up as promised, sorry for that. I hoe you find my comments useful

No worries at all, I had this one on my TODO list also. Thank you for the review and yes the comments were great, looks much better now!

Copy link
Contributor

@potiuk potiuk left a comment

Choose a reason for hiding this comment

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

Small NIT after choosing option a)

password_complexity_validator(field.data)
except PasswordComplexityValidationError as exc:
raise ValidationError(str(exc))
if current_app.config.get("FAB_PASSWORD_COMPLEXITY_ENABLED", False):
Copy link
Contributor

Choose a reason for hiding this comment

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

Cool. Looks great now. Then I have one more NIT comment :)

Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
@potiuk
Copy link
Contributor

potiuk commented Sep 7, 2021

Fantastic!

@dpgaspar dpgaspar merged commit c46cac3 into master Sep 7, 2021
@dpgaspar dpgaspar deleted the feat/password-complexity-hook branch September 7, 2021 22:50
@potiuk
Copy link
Contributor

potiuk commented Sep 7, 2021

🎉 🎉 🎉 🎉 🎉

@dpgaspar dpgaspar mentioned this pull request Sep 14, 2021
6 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants