Merge "Add keystone-manage reset_last_active command"

This commit is contained in:
Zuul 2024-08-07 16:36:46 +00:00 committed by Gerrit Code Review
commit 831d9098a2
8 changed files with 101 additions and 6 deletions

View File

@ -502,6 +502,47 @@ class DbVersion(BaseApp):
print(upgrades.get_db_version())
class ResetLastActive(BaseApp):
"""Reset null values for all users to current time."""
name = "reset_last_active"
@classmethod
def add_argument_parser(cls, subparsers):
parser = super().add_argument_parser(subparsers)
parser.add_argument(
'--force',
action='store_true',
help='Write to the database without asking for confirmation',
)
return parser
@staticmethod
def main():
if not CONF.command.force:
confirm = input(
"Security Warning: reset_last_active will update all users\n"
"in the database with a NULL value for last_active_at to be\n"
"last active at the current time. This includes users that\n"
"have never logged in. If your Keystone deployment is\n"
"configured to use disable_user_account_days_inactive, these\n"
"users will still be enabled and won't be disabled until the\n"
"configured amount of time has passed after this command is\n"
"run.\n"
"Are you sure you want to continue [y/N]? "
)
if confirm.lower() not in ('y', 'yes'):
raise SystemExit('reset_last_active aborted.')
LOG.debug(
"Resetting null values to current time %s",
datetime.datetime.utcnow,
)
drivers = backends.load_backends()
identity_api = drivers['identity_api']
identity_api.reset_last_active()
class BasePermissionsSetup(BaseApp):
"""Common user/group setup for file permissions."""
@ -1624,6 +1665,7 @@ CMDS = [
ProjectSetup,
ReceiptRotate,
ReceiptSetup,
ResetLastActive,
SamlIdentityProviderMetadata,
TokenRotate,
TokenSetup,

View File

@ -361,6 +361,16 @@ class IdentityDriverBase(metaclass=abc.ABCMeta):
"""
raise exception.NotImplemented() # pragma: no cover
@abc.abstractmethod
def reset_last_active(self):
"""Resets null last_active_at values.
This method looks for all users in the database that have a null
value for last_updated_at and resets that value to the current
time.
"""
raise exception.NotImplemented() # pragma: no cover
# group crud
@abc.abstractmethod

View File

@ -170,6 +170,9 @@ class Identity(base.IdentityDriverBase):
def delete_user(self, user_id):
raise exception.Forbidden(READ_ONLY_LDAP_ERROR_MESSAGE)
def reset_last_active(self):
raise exception.Forbidden(READ_ONLY_LDAP_ERROR_MESSAGE)
def change_password(self, user_id, new_password):
raise exception.Forbidden(READ_ONLY_LDAP_ERROR_MESSAGE)

View File

@ -420,6 +420,14 @@ class Identity(base.IdentityDriverBase):
session.delete(ref)
def reset_last_active(self):
with sql.session_for_write() as session:
session.query(model.User).filter(
model.User.last_active_at.is_(None).update(
{'last_active_at': datetime.datetime.utcnow()}
)
)
# group crud
@sql.handle_conflicts(conflict_type='group')

View File

@ -163,11 +163,10 @@ class ShadowUsers(base.ShadowUsersDriverBase):
return user_ref
def set_last_active_at(self, user_id):
if CONF.security_compliance.disable_user_account_days_inactive:
with sql.session_for_write() as session:
user_ref = session.get(model.User, user_id)
if user_ref:
user_ref.last_active_at = datetime.datetime.utcnow().date()
with sql.session_for_write() as session:
user_ref = session.get(model.User, user_id)
if user_ref:
user_ref.last_active_at = datetime.datetime.utcnow().date()
@sql.handle_conflicts(conflict_type='federated_user')
def update_federated_user_display_name(

View File

@ -72,6 +72,9 @@ class FooDriver(base.IdentityDriverBase):
def get_user_by_name(self, user_name, domain_id):
raise exception.NotImplemented() # pragma: no cover
def reset_last_active(self):
raise exception.NotImplemented() # pragma: no cover
def create_group(self, group_id, group):
raise exception.NotImplemented() # pragma: no cover

View File

@ -176,6 +176,7 @@ class ShadowUsersBackendTests:
group='security_compliance',
disable_user_account_days_inactive=None,
)
now = datetime.datetime.utcnow().date()
password = uuid.uuid4().hex
user = self._create_user(password)
with self.make_request():
@ -183,7 +184,7 @@ class ShadowUsersBackendTests:
user_id=user['id'], password=password
)
user_ref = self._get_user_ref(user_auth['id'])
self.assertIsNone(user_ref.last_active_at)
self.assertGreaterEqual(now, user_ref.last_active_at)
def _add_nonlocal_user(self, nonlocal_user):
with sql.session_for_write() as session:

View File

@ -0,0 +1,29 @@
---
features:
- |
Added a new command to the admin cli tool:
`keystone-manage reset_last_active`. This new command updates the database
to overwritet any NULL values in `last_active_at` in the user table to the
current time. This is a necessary step to fix Bug #2074018. See launchpad
for details.
fixes:
- |
Fixed Bug #2074018: Changed the user model to always save the date of the
last user activity in `last_active_at`. Previous to this change, the
`last_active_at` field was only updated when the option for
`[security_compliance] disable_user_account_days_inactive` was set.
If your deployment is affected by this bug, you must run
`keystone-manage reset_last_active` before setting the
`disable_user_account_days_inactive` option.
security:
- |
The new `keystone-manage rest_last_active` command resets all NULL values
in `last_active_at` in the user table to help fix Bug #2074018. Running
this command may be necessary in environments that have been deployed for
a long time and later decide to adopt the
`[security_compliance disable_user_account_days_inactive = X` option.
See Bug #2074018 for details.
A side-effect of this command is that it resets the amount of time that an
unused account is active for. Unused accounts will remain active until the
configured days have elapsed since the day the command is run.