Merge "Deprecate [security_compliance]\password_expires_ignore_user_ids"
This commit is contained in:
commit
781db8e67a
@ -11,6 +11,7 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
|
from oslo_log import versionutils
|
||||||
|
|
||||||
from keystone.conf import utils
|
from keystone.conf import utils
|
||||||
|
|
||||||
@ -77,6 +78,16 @@ the `[identity] driver`.
|
|||||||
|
|
||||||
password_expires_ignore_user_ids = cfg.ListOpt(
|
password_expires_ignore_user_ids = cfg.ListOpt(
|
||||||
'password_expires_ignore_user_ids',
|
'password_expires_ignore_user_ids',
|
||||||
|
deprecated_for_removal=True,
|
||||||
|
deprecated_reason=utils.fmt("""
|
||||||
|
Functionality added as a per-user option "ignore_password_expiry" in Ocata.
|
||||||
|
Each user that should ignore password expiry should have the value set to
|
||||||
|
"true" in the user's `options` attribute (e.g.
|
||||||
|
`user['options']['ignore_password_expiry'] = True`) with an "update_user" call.
|
||||||
|
This avoids the need to restart keystone to adjust the users that ignore
|
||||||
|
password expiry. This option will be removed in the Pike release.
|
||||||
|
"""),
|
||||||
|
deprecated_since=versionutils.deprecated.OCATA,
|
||||||
default=[],
|
default=[],
|
||||||
help=utils.fmt("""
|
help=utils.fmt("""
|
||||||
Comma separated list of user IDs to be ignored when checking if a password
|
Comma separated list of user IDs to be ignored when checking if a password
|
||||||
|
@ -17,6 +17,9 @@ USER_OPTIONS_REGISTRY = resource_options.ResourceOptionRegistry('USER')
|
|||||||
IGNORE_CHANGE_PASSWORD_OPT = (
|
IGNORE_CHANGE_PASSWORD_OPT = (
|
||||||
resource_options.ResourceOption('1000',
|
resource_options.ResourceOption('1000',
|
||||||
'ignore_change_password_upon_first_use'))
|
'ignore_change_password_upon_first_use'))
|
||||||
|
IGNORE_PASSWORD_EXPIRY_OPT = (
|
||||||
|
resource_options.ResourceOption('1001',
|
||||||
|
'ignore_password_expiry'))
|
||||||
|
|
||||||
|
|
||||||
# NOTE(notmorgan): wrap this in a function for testing purposes.
|
# NOTE(notmorgan): wrap this in a function for testing purposes.
|
||||||
@ -24,6 +27,7 @@ IGNORE_CHANGE_PASSWORD_OPT = (
|
|||||||
def register_user_options():
|
def register_user_options():
|
||||||
for opt in [
|
for opt in [
|
||||||
IGNORE_CHANGE_PASSWORD_OPT,
|
IGNORE_CHANGE_PASSWORD_OPT,
|
||||||
|
IGNORE_PASSWORD_EXPIRY_OPT,
|
||||||
]:
|
]:
|
||||||
USER_OPTIONS_REGISTRY.register_option(opt)
|
USER_OPTIONS_REGISTRY.register_option(opt)
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
|
from oslo_log import versionutils
|
||||||
import sqlalchemy
|
import sqlalchemy
|
||||||
from sqlalchemy.ext.hybrid import hybrid_property
|
from sqlalchemy.ext.hybrid import hybrid_property
|
||||||
from sqlalchemy import orm
|
from sqlalchemy import orm
|
||||||
@ -149,10 +150,29 @@ class User(sql.ModelBase, sql.DictBase):
|
|||||||
|
|
||||||
def _get_password_expires_at(self, created_at):
|
def _get_password_expires_at(self, created_at):
|
||||||
expires_days = CONF.security_compliance.password_expires_days
|
expires_days = CONF.security_compliance.password_expires_days
|
||||||
|
# NOTE(notmorgan): This option is deprecated and subject to removal
|
||||||
|
# in a future release.
|
||||||
ignore_list = CONF.security_compliance.password_expires_ignore_user_ids
|
ignore_list = CONF.security_compliance.password_expires_ignore_user_ids
|
||||||
if expires_days and (self.id not in ignore_list):
|
if ignore_list:
|
||||||
expired_date = (created_at + datetime.timedelta(days=expires_days))
|
versionutils.deprecated(
|
||||||
return expired_date.replace(microsecond=0)
|
what='[security_compliance]\password_expires_ignore_user_ids',
|
||||||
|
as_of=versionutils.deprecated.OCATA,
|
||||||
|
remove_in=+1,
|
||||||
|
in_favor_of=('Using the `ignore_password_expiry` value set to '
|
||||||
|
'`True` in the `user["options"]` dictionary on '
|
||||||
|
'User creation or update (via API call).'))
|
||||||
|
# Get the IGNORE_PASSWORD_EXPIRY_OPT value from the user's
|
||||||
|
# option_mapper.
|
||||||
|
|
||||||
|
ignore_pw_expiry = getattr(
|
||||||
|
self.get_resource_option(iro.IGNORE_PASSWORD_EXPIRY_OPT.option_id),
|
||||||
|
'option_value',
|
||||||
|
False)
|
||||||
|
if (self.id not in ignore_list) and not ignore_pw_expiry:
|
||||||
|
if expires_days:
|
||||||
|
expired_date = (created_at +
|
||||||
|
datetime.timedelta(days=expires_days))
|
||||||
|
return expired_date.replace(microsecond=0)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@password.expression
|
@password.expression
|
||||||
|
@ -695,6 +695,33 @@ class PasswordExpiresValidationTests(test_backend_sql.SqlTests):
|
|||||||
user_id=user['id'],
|
user_id=user['id'],
|
||||||
password=self.password)
|
password=self.password)
|
||||||
|
|
||||||
|
def test_authenticate_with_expired_password_for_ignore_user_option(self):
|
||||||
|
# set user to have the 'ignore_password_expiry' option set to False
|
||||||
|
self.user_dict.setdefault('options', {})[
|
||||||
|
iro.IGNORE_PASSWORD_EXPIRY_OPT.option_name] = False
|
||||||
|
# set password created_at so that the password will expire
|
||||||
|
password_created_at = (
|
||||||
|
datetime.datetime.utcnow() -
|
||||||
|
datetime.timedelta(
|
||||||
|
days=CONF.security_compliance.password_expires_days + 1)
|
||||||
|
)
|
||||||
|
user = self._create_user(self.user_dict, password_created_at)
|
||||||
|
self.assertRaises(exception.PasswordExpired,
|
||||||
|
self.identity_api.authenticate,
|
||||||
|
self.make_request(),
|
||||||
|
user_id=user['id'],
|
||||||
|
password=self.password)
|
||||||
|
|
||||||
|
# update user to explicitly have the expiry option to True
|
||||||
|
user['options'][
|
||||||
|
iro.IGNORE_PASSWORD_EXPIRY_OPT.option_name] = True
|
||||||
|
user = self.identity_api.update_user(user['id'],
|
||||||
|
user)
|
||||||
|
# test password is not expired due to ignore option
|
||||||
|
self.identity_api.authenticate(self.make_request(),
|
||||||
|
user_id=user['id'],
|
||||||
|
password=self.password)
|
||||||
|
|
||||||
def _get_test_user_dict(self, password):
|
def _get_test_user_dict(self, password):
|
||||||
test_user_dict = {
|
test_user_dict = {
|
||||||
'id': uuid.uuid4().hex,
|
'id': uuid.uuid4().hex,
|
||||||
@ -706,13 +733,15 @@ class PasswordExpiresValidationTests(test_backend_sql.SqlTests):
|
|||||||
return test_user_dict
|
return test_user_dict
|
||||||
|
|
||||||
def _create_user(self, user_dict, password_created_at):
|
def _create_user(self, user_dict, password_created_at):
|
||||||
user_dict = utils.hash_user_password(user_dict)
|
# Bypass business logic and go straight for the identity driver
|
||||||
|
# (SQL in this case)
|
||||||
|
driver = self.identity_api.driver
|
||||||
|
driver.create_user(user_dict['id'], user_dict)
|
||||||
with sql.session_for_write() as session:
|
with sql.session_for_write() as session:
|
||||||
user_ref = model.User.from_dict(user_dict)
|
user_ref = session.query(model.User).get(user_dict['id'])
|
||||||
user_ref.password_ref.created_at = password_created_at
|
user_ref.password_ref.created_at = password_created_at
|
||||||
user_ref.password_ref.expires_at = (
|
user_ref.password_ref.expires_at = (
|
||||||
user_ref._get_password_expires_at(password_created_at))
|
user_ref._get_password_expires_at(password_created_at))
|
||||||
session.add(user_ref)
|
|
||||||
return base.filter_user(user_ref.to_dict())
|
return base.filter_user(user_ref.to_dict())
|
||||||
|
|
||||||
|
|
||||||
|
14
releasenotes/notes/bug-1659995-f3e716de743b7291.yaml
Normal file
14
releasenotes/notes/bug-1659995-f3e716de743b7291.yaml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
---
|
||||||
|
fixes:
|
||||||
|
- |
|
||||||
|
[`bug 1659995 <https://bugs.launchpad.net/keystone/+bug/1659995>`_]
|
||||||
|
A new option has been made available via the user create and update API
|
||||||
|
(``POST/PATCH /v3/users) call, the option will allow an admin to
|
||||||
|
mark users as exempt from the PCI password expiry policy via an API.
|
||||||
|
This can be done like so: ``user['options']['ignore_password_expiry']``.
|
||||||
|
deprecations:
|
||||||
|
- |
|
||||||
|
[`bug 1659995 <https://bugs.launchpad.net/keystone/+bug/1659995>`_]
|
||||||
|
The config option ``[security_compliance] ignore_password_expires_user_ids``
|
||||||
|
has been deprecated in favor of using the option value set, available via
|
||||||
|
the user create and update API call
|
Loading…
Reference in New Issue
Block a user