Skip to content

Commit

Permalink
Merge pull request #4596 from jmcrawford45/PS-4716-4542
Browse files Browse the repository at this point in the history
Strong password policy for user accounts (fixes #4542)
  • Loading branch information
jmcrawford45 committed Sep 26, 2023
2 parents d2f3257 + fb36453 commit 40b54aa
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 17 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.rst
Expand Up @@ -3,6 +3,12 @@ Changelog

Unreleased
~~~~~~~~~~~~~~~~~~~~
Added password complexity requirements:
- At least 12 characters (required for your Muhlenberg password)—the more characters, the better
- A mixture of both uppercase and lowercase letters
- A mixture of letters and numbers
- Inclusion of at least one special character, e.g., ! @ # ? ]
If you don't want password complexity requirements, you can set CHECK_PASSWORD_STRENGTH to False.
Added ability to limit authority creation to admins only using config option `ADMIN_ONLY_AUTHORITY_CREATION`.
User passwords can now be updated by admins with the update user endpoint.
Route53 find_zone_dns now selects the maximum suffix match for zone id (previously we selected the first match).
Expand Down
19 changes: 8 additions & 11 deletions lemur/auth/views.py
Expand Up @@ -6,31 +6,28 @@
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
"""

import jwt
import base64
import requests
import time

import jwt
import requests
from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes, hmac

from flask import Blueprint, current_app

from flask_restful import reqparse, Resource, Api
from flask_principal import Identity, identity_changed
from flask_restful import reqparse, Resource, Api

from lemur.auth import ldap
from lemur.auth.service import create_token, fetch_token_header, get_rsa_public_key
from lemur.common.utils import get_psuedo_random_string, get_state_token_secret
from lemur.constants import SUCCESS_METRIC_STATUS, FAILURE_METRIC_STATUS
from lemur.exceptions import TokenExchangeFailed
from lemur.extensions import metrics
from lemur.common.utils import get_psuedo_random_string, get_state_token_secret

from lemur.users import service as user_service
from lemur.roles import service as role_service
from lemur.logs import service as log_service
from lemur.auth.service import create_token, fetch_token_header, get_rsa_public_key
from lemur.auth import ldap
from lemur.plugins.base import plugins
from lemur.roles import service as role_service
from lemur.users import service as user_service

mod = Blueprint("auth", __name__)
api = Api(mod)
Expand Down
28 changes: 26 additions & 2 deletions lemur/tests/test_users.py
@@ -1,8 +1,10 @@
import json

import pytest
from marshmallow import ValidationError

from lemur.tests.factories import UserFactory, RoleFactory
from lemur.users.schemas import UserInputSchema, UserCreateInputSchema
from lemur.users.views import * # noqa
from .vectors import (
VALID_ADMIN_API_TOKEN,
Expand All @@ -12,8 +14,6 @@


def test_user_input_schema(client):
from lemur.users.schemas import UserInputSchema

input_data = {
"username": "example",
"password": "1233432",
Expand All @@ -25,6 +25,30 @@ def test_user_input_schema(client):
assert not errors


def test_valid_password():
schema = UserCreateInputSchema()
good_password = "ABcdefg123456@#]"
# This password should not raise an exception
schema.validate_password(good_password)


@pytest.mark.parametrize(
"bad_password",
[
"ABCD1234!#]", # No lowercase
"abcd1234@#]", # No uppercase
"!@#]Abcdefg", # No digit
"ABCDabcd1234", # No special character
"Ab1!@#]", # less than 12 characters
],
)
def test_invalid_password(bad_password):
schema = UserCreateInputSchema()
# All these passwords should raise an exception
with pytest.raises(ValidationError):
schema.validate_password(bad_password)


@pytest.mark.parametrize(
"token,status",
[
Expand Down
29 changes: 27 additions & 2 deletions lemur/users/schemas.py
Expand Up @@ -5,7 +5,10 @@
:license: Apache, see LICENSE for more details.
.. moduleauthor:: Kevin Glisson <kglisson@netflix.com>
"""
from marshmallow import fields
import re

from flask import current_app
from marshmallow import fields, validates, ValidationError

from lemur.common.schema import LemurInputSchema, LemurOutputSchema
from lemur.schemas import (
Expand All @@ -19,13 +22,34 @@ class UserInputSchema(LemurInputSchema):
id = fields.Integer()
username = fields.String(required=True)
email = fields.Email(required=True)
password = fields.String() # TODO add complexity requirements
password = fields.String()
active = fields.Boolean()
roles = fields.Nested(AssociatedRoleSchema, many=True, missing=[])
certificates = fields.Nested(AssociatedCertificateSchema, many=True, missing=[])
authorities = fields.Nested(AssociatedAuthoritySchema, many=True, missing=[])


class UserCreateInputSchema(UserInputSchema):
@validates('password')
def validate_password(self, value):
if current_app.config.get('CHECK_PASSWORD_STRENGTH', True):
# At least 12 characters
if len(value) < 12:
raise ValidationError('Password must be at least 12 characters long.')

# A mixture of both uppercase and lowercase letters
if not any(map(str.isupper, value)) or not any(map(str.islower, value)):
raise ValidationError('Password must contain both uppercase and lowercase characters.')

# A mixture of letters and numbers
if not any(map(str.isdigit, value)):
raise ValidationError('Password must contain at least one digit.')

# Inclusion of at least one special character
if not re.findall(r'[!@#?\]]', value):
raise ValidationError('Password must contain at least one special character (!@#?]).')


class UserOutputSchema(LemurOutputSchema):
id = fields.Integer()
username = fields.String()
Expand All @@ -36,6 +60,7 @@ class UserOutputSchema(LemurOutputSchema):


user_input_schema = UserInputSchema()
user_create_input_schema = UserCreateInputSchema()
user_output_schema = UserOutputSchema()
users_output_schema = UserOutputSchema(many=True)

Expand Down
4 changes: 2 additions & 2 deletions lemur/users/views.py
Expand Up @@ -18,7 +18,7 @@
from lemur.users.schemas import (
user_input_schema,
user_output_schema,
users_output_schema,
users_output_schema, user_create_input_schema,
)

mod = Blueprint("users", __name__)
Expand Down Expand Up @@ -89,7 +89,7 @@ def get(self):
args = parser.parse_args()
return service.render(args)

@validate_schema(user_input_schema, user_output_schema)
@validate_schema(user_create_input_schema, user_output_schema)
@admin_permission.require(http_exception=403)
def post(self, data=None):
"""
Expand Down

0 comments on commit 40b54aa

Please sign in to comment.