From c9c655a1e1e865a1f4e274c0aaea1749acc8a53b Mon Sep 17 00:00:00 2001 From: Pavlo Shchelokovskyy Date: Thu, 2 May 2019 16:50:56 -0600 Subject: [PATCH] Add ignore_user_inactivity user option this option allows to override the [security_compliance]disable_user_account_days_inactive setting from config on per-user basis. Co-Authored-By: Vishakha Agarwal Change-Id: Ida360e215426184195687bee2a800877af33af04 Closes-Bug: #1827431 --- api-ref/source/v3/parameters.yaml | 6 ++- doc/source/admin/resource-options.rst | 30 +++++++++++++++ .../identity/backends/resource_options.py | 7 ++++ keystone/identity/backends/sql_model.py | 7 +++- .../tests/unit/identity/test_backend_sql.py | 37 +++++++++++++++++++ .../notes/bug-1827431-2f078c13dfc9a02a.yaml | 9 +++++ 6 files changed, 93 insertions(+), 3 deletions(-) create mode 100644 releasenotes/notes/bug-1827431-2f078c13dfc9a02a.yaml diff --git a/api-ref/source/v3/parameters.yaml b/api-ref/source/v3/parameters.yaml index 683b41ef9b..fefd069e70 100644 --- a/api-ref/source/v3/parameters.yaml +++ b/api-ref/source/v3/parameters.yaml @@ -1945,7 +1945,8 @@ response_user_options_body_required: The resource options for the user. Available resource options are ``ignore_change_password_upon_first_use``, ``ignore_password_expiry``, ``ignore_lockout_failure_attempts``, ``lock_password``, - ``multi_factor_auth_enabled``, and ``multi_factor_auth_rules``. + ``multi_factor_auth_enabled``, and ``multi_factor_auth_rules`` + ``ignore_user_inactivity``. in: body required: true type: object @@ -2240,7 +2241,8 @@ user_options_request_body: The resource options for the user. Available resource options are ``ignore_change_password_upon_first_use``, ``ignore_password_expiry``, ``ignore_lockout_failure_attempts``, ``lock_password``, - ``multi_factor_auth_enabled``, and ``multi_factor_auth_rules``. + ``multi_factor_auth_enabled``, and ``multi_factor_auth_rules`` + ``ignore_user_inactivity``. in: body required: false type: object diff --git a/doc/source/admin/resource-options.rst b/doc/source/admin/resource-options.rst index 9ce1936e39..0fa3ec2375 100644 --- a/doc/source/admin/resource-options.rst +++ b/doc/source/admin/resource-options.rst @@ -42,6 +42,36 @@ or by updating an existing user to include new options ``None``; if the option is set to ``None``, it is removed from the user's data structure. +.. _ignore_user_inactivity: + +ignore_user_inactivity +---------------------- + +Type: ``Boolean`` + +Opt into ignoring global inactivity lock settings defined in +``keystone.conf [security_compliance]`` on a per-user basis. Setting this +option to ``True`` will make users not set as disabled even after the +globally configured inactivity period is reached. + +.. code-block:: json + + { + "user": { + "options": { + "ignore_user_inactivity": true + } + } + } + +.. note:: + Setting this option for users which are already disabled will not + make them automatically enabled. Such users must be enabled manually + after setting this option to True for them. + +See the `security compliance documentation +`_ for more details. + .. _ignore_change_password_upon_first_use: ignore_change_password_upon_first_use diff --git a/keystone/identity/backends/resource_options.py b/keystone/identity/backends/resource_options.py index 2520366055..91ecb43a18 100644 --- a/keystone/identity/backends/resource_options.py +++ b/keystone/identity/backends/resource_options.py @@ -80,6 +80,12 @@ LOCK_PASSWORD_OPT = ( option_name='lock_password', validator=resource_options.boolean_validator, json_schema_validation=parameter_types.boolean)) +IGNORE_USER_INACTIVITY_OPT = ( + resource_options.ResourceOption( + option_id='1004', + option_name='ignore_user_inactivity', + validator=resource_options.boolean_validator, + json_schema_validation=parameter_types.boolean)) MFA_RULES_OPT = ( resource_options.ResourceOption( option_id='MFAR', @@ -117,6 +123,7 @@ def register_user_options(): IGNORE_PASSWORD_EXPIRY_OPT, IGNORE_LOCKOUT_ATTEMPT_OPT, LOCK_PASSWORD_OPT, + IGNORE_USER_INACTIVITY_OPT, MFA_RULES_OPT, MFA_ENABLED_OPT, ]: diff --git a/keystone/identity/backends/sql_model.py b/keystone/identity/backends/sql_model.py index d463529e30..e9407ea1aa 100644 --- a/keystone/identity/backends/sql_model.py +++ b/keystone/identity/backends/sql_model.py @@ -199,13 +199,18 @@ class User(sql.ModelBase, sql.ModelDictMixinWithExtras): if self._enabled: max_days = ( CONF.security_compliance.disable_user_account_days_inactive) + inactivity_exempt = getattr( + self.get_resource_option( + iro.IGNORE_USER_INACTIVITY_OPT.option_id), + 'option_value', + False) last_active = self.last_active_at if not last_active and self.created_at: last_active = self.created_at.date() if max_days and last_active: now = datetime.datetime.utcnow().date() days_inactive = (now - last_active).days - if days_inactive >= max_days: + if days_inactive >= max_days and not inactivity_exempt: self._enabled = False return self._enabled diff --git a/keystone/tests/unit/identity/test_backend_sql.py b/keystone/tests/unit/identity/test_backend_sql.py index 97cd9572e2..8c7fb3103f 100644 --- a/keystone/tests/unit/identity/test_backend_sql.py +++ b/keystone/tests/unit/identity/test_backend_sql.py @@ -342,6 +342,43 @@ class DisableInactiveUserTests(test_backend_sql.SqlTests): user_ref = self._get_user_ref(user['id']) self.assertTrue(user_ref.enabled) + def test_ignore_user_inactivity(self): + self.user_dict['options'] = {'ignore_user_inactivity': True} + user = PROVIDERS.identity_api.create_user( + self.user_dict) + # set last_active_at just beyond the max + last_active_at = ( + datetime.datetime.utcnow() - + datetime.timedelta(self.max_inactive_days + 1)).date() + self._update_user_last_active_at(user['id'], last_active_at) + # get user and verify that the user is not disabled + user = PROVIDERS.identity_api.get_user(user['id']) + self.assertTrue(user['enabled']) + + def test_ignore_user_inactivity_with_user_disabled(self): + user = PROVIDERS.identity_api.create_user( + self.user_dict) + # set last_active_at just beyond the max + last_active_at = ( + datetime.datetime.utcnow() - + datetime.timedelta(self.max_inactive_days + 1)).date() + self._update_user_last_active_at(user['id'], last_active_at) + # get user and verify that the user is disabled + user = PROVIDERS.identity_api.get_user(user['id']) + self.assertFalse(user['enabled']) + # update disabled user with ignore_user_inactivity to true + user['options'] = {'ignore_user_inactivity': True} + user = PROVIDERS.identity_api.update_user( + user['id'], user) + # user is not enabled + user = PROVIDERS.identity_api.get_user(user['id']) + self.assertFalse(user['enabled']) + # Manually set enabled and test + user['enabled'] = True + PROVIDERS.identity_api.update_user(user['id'], user) + user = PROVIDERS.identity_api.get_user(user['id']) + self.assertTrue(user['enabled']) + def _get_user_dict(self, password): user = { 'name': uuid.uuid4().hex, diff --git a/releasenotes/notes/bug-1827431-2f078c13dfc9a02a.yaml b/releasenotes/notes/bug-1827431-2f078c13dfc9a02a.yaml new file mode 100644 index 0000000000..81f8111ba7 --- /dev/null +++ b/releasenotes/notes/bug-1827431-2f078c13dfc9a02a.yaml @@ -0,0 +1,9 @@ +--- +features: + - | + [`bug 1827431 `_] + Added a new user option 'ignore_user_inactivity' (defaults to False). + When set to True, it overrides disabling the user after being inactive + for certain time as set in + ``[security_compliance]disable_user_account_days_inactive`` option + in Keystone configuration file.