Create user option ignore_lockout_failure_attempts
Rather than an option list in keystone.conf, leverage the per-user options available via REST APIs, to define whether a user is exempt from being disabled upon too many failed password attempts. Closes-Bug: 1659995 Co-Authored-By: "Steve Martinelli <s.martinelli@gmail.com>" Change-Id: I6999343314a9e11a82664cd0db6e56047084fa76
This commit is contained in:
parent
47cd72911c
commit
9844fa1e26
@ -54,17 +54,6 @@ non-zero value. This feature depends on the `sql` backend for the `[identity]
|
||||
driver`.
|
||||
"""))
|
||||
|
||||
lockout_ignored_user_ids = cfg.ListOpt(
|
||||
'lockout_ignored_user_ids',
|
||||
default=[],
|
||||
help=utils.fmt("""
|
||||
Comma separated list of user IDs to be ignored when checking if a user should
|
||||
be locked out based on failed authentication attempts. Thus, users in this list
|
||||
can fail to authenticate for an unlimited amount of times and will never be
|
||||
locked out. This feature will only be enabled if `[security_compliance]
|
||||
lockout_failure_attempts` is set.
|
||||
"""))
|
||||
|
||||
password_expires_days = cfg.IntOpt(
|
||||
'password_expires_days',
|
||||
min=1,
|
||||
@ -160,7 +149,6 @@ ALL_OPTS = [
|
||||
disable_user_account_days_inactive,
|
||||
lockout_failure_attempts,
|
||||
lockout_duration,
|
||||
lockout_ignored_user_ids,
|
||||
password_expires_days,
|
||||
password_expires_ignore_user_ids,
|
||||
unique_last_password_count,
|
||||
|
@ -27,6 +27,12 @@ IGNORE_PASSWORD_EXPIRY_OPT = (
|
||||
option_name='ignore_password_expiry',
|
||||
validator=resource_options.boolean_validator,
|
||||
json_schema_validation=parameter_types.boolean))
|
||||
IGNORE_LOCKOUT_ATTEMPT_OPT = (
|
||||
resource_options.ResourceOption(
|
||||
option_id='1002',
|
||||
option_name='ignore_lockout_failure_attempts',
|
||||
validator=resource_options.boolean_validator,
|
||||
json_schema_validation=parameter_types.boolean))
|
||||
|
||||
|
||||
# NOTE(notmorgan): wrap this in a function for testing purposes.
|
||||
@ -35,6 +41,7 @@ def register_user_options():
|
||||
for opt in [
|
||||
IGNORE_CHANGE_PASSWORD_OPT,
|
||||
IGNORE_PASSWORD_EXPIRY_OPT,
|
||||
IGNORE_LOCKOUT_ATTEMPT_OPT
|
||||
]:
|
||||
USER_OPTIONS_REGISTRY.register_option(opt)
|
||||
|
||||
|
@ -87,8 +87,11 @@ class Identity(base.IdentityDriverBase):
|
||||
:returns Boolean: True if the account is locked; False otherwise
|
||||
|
||||
"""
|
||||
if user_id in CONF.security_compliance.lockout_ignored_user_ids:
|
||||
ignore_option = user_ref.get_resource_option(
|
||||
options.IGNORE_LOCKOUT_ATTEMPT_OPT.option_id)
|
||||
if ignore_option and ignore_option.option_value is True:
|
||||
return False
|
||||
|
||||
attempts = user_ref.local_user.failed_auth_count or 0
|
||||
max_attempts = CONF.security_compliance.lockout_failure_attempts
|
||||
lockout_duration = CONF.security_compliance.lockout_duration
|
||||
|
@ -533,10 +533,11 @@ class LockingOutUserTests(test_backend_sql.SqlTests):
|
||||
password=uuid.uuid4().hex)
|
||||
|
||||
def test_lock_out_for_ignored_user(self):
|
||||
# add the user id to the ignore list
|
||||
self.config_fixture.config(
|
||||
group='security_compliance',
|
||||
lockout_ignored_user_ids=[self.user['id']])
|
||||
# mark the user as exempt from failed password attempts
|
||||
# ignore user and reset password, password not expired
|
||||
self.user['options'][iro.IGNORE_LOCKOUT_ATTEMPT_OPT.option_name] = True
|
||||
self.identity_api.update_user(self.user['id'], self.user)
|
||||
|
||||
# fail authentication repeatedly the max number of times
|
||||
self._fail_auth_repeatedly(self.user['id'])
|
||||
# authenticate with wrong password, account should not be locked
|
||||
|
@ -1037,6 +1037,29 @@ class UserSelfServiceChangingPasswordsTestCase(ChangePasswordTestCase):
|
||||
self.token = self.get_request_token(reset_password,
|
||||
http_client.CREATED)
|
||||
|
||||
def test_lockout_exempt(self):
|
||||
self.config_fixture.config(group='security_compliance',
|
||||
lockout_failure_attempts=1)
|
||||
|
||||
# create user
|
||||
self.user_ref = unit.create_user(self.identity_api,
|
||||
domain_id=self.domain['id'])
|
||||
|
||||
# update the user, mark her as exempt from lockout
|
||||
ignore_opt_name = options.IGNORE_LOCKOUT_ATTEMPT_OPT.option_name
|
||||
self.user_ref['options'][ignore_opt_name] = True
|
||||
self.identity_api.update_user(self.user_ref['id'], self.user_ref)
|
||||
|
||||
# fail to auth, this should lockout the user, since we're allowed
|
||||
# one failure, but we're exempt from lockout!
|
||||
bad_password = uuid.uuid4().hex
|
||||
self.token = self.get_request_token(bad_password,
|
||||
http_client.UNAUTHORIZED)
|
||||
|
||||
# attempt to authenticate with correct password
|
||||
self.get_request_token(self.user_ref['password'],
|
||||
expected_status=http_client.CREATED)
|
||||
|
||||
|
||||
class PasswordValidationTestCase(ChangePasswordTestCase):
|
||||
|
||||
|
@ -1839,6 +1839,42 @@ class UserValidationTestCase(unit.BaseTestCase):
|
||||
}
|
||||
self.update_user_validator.validate(request_to_validate)
|
||||
|
||||
def test_user_create_with_options_lockout_password(self):
|
||||
request_to_validate = {
|
||||
'name': self.user_name,
|
||||
'options': {
|
||||
ro.IGNORE_LOCKOUT_ATTEMPT_OPT.option_name: True
|
||||
}
|
||||
}
|
||||
self.create_user_validator.validate(request_to_validate)
|
||||
|
||||
def test_user_update_with_options_lockout_password(self):
|
||||
request_to_validate = {
|
||||
'options': {
|
||||
ro.IGNORE_LOCKOUT_ATTEMPT_OPT.option_name: False
|
||||
}
|
||||
}
|
||||
self.update_user_validator.validate(request_to_validate)
|
||||
|
||||
def test_user_update_with_two_options(self):
|
||||
request_to_validate = {
|
||||
'options': {
|
||||
ro.IGNORE_CHANGE_PASSWORD_OPT.option_name: True,
|
||||
ro.IGNORE_LOCKOUT_ATTEMPT_OPT.option_name: True
|
||||
}
|
||||
}
|
||||
self.update_user_validator.validate(request_to_validate)
|
||||
|
||||
def test_user_create_with_two_options(self):
|
||||
request_to_validate = {
|
||||
'name': self.user_name,
|
||||
'options': {
|
||||
ro.IGNORE_CHANGE_PASSWORD_OPT.option_name: False,
|
||||
ro.IGNORE_LOCKOUT_ATTEMPT_OPT.option_name: True
|
||||
}
|
||||
}
|
||||
self.create_user_validator.validate(request_to_validate)
|
||||
|
||||
|
||||
class GroupValidationTestCase(unit.BaseTestCase):
|
||||
"""Test for V3 Group API validation."""
|
||||
|
Loading…
Reference in New Issue
Block a user