Deprecate [security_compliance]\password_expires_ignore_user_ids

Deprecate [security_compliance]\password_expires_ignore_user_ids in
favor of using the new user-option 'ignore_password_expiry'.

This allows setting the value for ignoring password expiration on
individual users without needing to restart keystone for each change
to the list.

Partial-Bug: 1659995

Change-Id: Ib4b422ab07f91c312f3268ade926db1638052587
This commit is contained in:
Morgan Fainberg 2017-01-27 12:14:17 -08:00 committed by Steve Martinelli
parent 0b3e59e041
commit 930728a57e
5 changed files with 84 additions and 6 deletions

View File

@ -11,6 +11,7 @@
# under the License.
from oslo_config import cfg
from oslo_log import versionutils
from keystone.conf import utils
@ -77,6 +78,16 @@ the `[identity] driver`.
password_expires_ignore_user_ids = cfg.ListOpt(
'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=[],
help=utils.fmt("""
Comma separated list of user IDs to be ignored when checking if a password

View File

@ -17,6 +17,9 @@ USER_OPTIONS_REGISTRY = resource_options.ResourceOptionRegistry('USER')
IGNORE_CHANGE_PASSWORD_OPT = (
resource_options.ResourceOption('1000',
'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.
@ -24,6 +27,7 @@ IGNORE_CHANGE_PASSWORD_OPT = (
def register_user_options():
for opt in [
IGNORE_CHANGE_PASSWORD_OPT,
IGNORE_PASSWORD_EXPIRY_OPT,
]:
USER_OPTIONS_REGISTRY.register_option(opt)

View File

@ -14,6 +14,7 @@
import datetime
from oslo_log import versionutils
import sqlalchemy
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy import orm
@ -149,9 +150,28 @@ class User(sql.ModelBase, sql.DictBase):
def _get_password_expires_at(self, created_at):
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
if expires_days and (self.id not in ignore_list):
expired_date = (created_at + datetime.timedelta(days=expires_days))
if ignore_list:
versionutils.deprecated(
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

View File

@ -695,6 +695,33 @@ class PasswordExpiresValidationTests(test_backend_sql.SqlTests):
user_id=user['id'],
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):
test_user_dict = {
'id': uuid.uuid4().hex,
@ -706,13 +733,15 @@ class PasswordExpiresValidationTests(test_backend_sql.SqlTests):
return test_user_dict
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:
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.expires_at = (
user_ref._get_password_expires_at(password_created_at))
session.add(user_ref)
return base.filter_user(user_ref.to_dict())

View 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