blob: dc6dd4ac1dfc076b042fd635d886cf0782ce403f [file] [log] [blame]
Jane Zadorozhna9c938c62015-07-01 17:06:16 +03001# Copyright 2015 OpenStack Foundation
2# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
15
Lance Bragstada2c4ebc2015-10-05 20:34:39 +000016import time
Jane Zadorozhna9c938c62015-07-01 17:06:16 +030017
Rodrigo Duarte Sousa2d78e8e2016-09-28 10:38:08 -030018import testtools
19
Jane Zadorozhna9c938c62015-07-01 17:06:16 +030020from tempest.api.identity import base
Rodrigo Duarte Sousa2d78e8e2016-09-28 10:38:08 -030021from tempest import config
Andrea Frittoli (andreaf)db9672e2016-02-23 14:07:24 -050022from tempest.lib.common.utils import data_utils
Ken'ichi Ohmichi44f01272017-01-27 18:44:14 -080023from tempest.lib import decorators
Andrea Frittoli (andreaf)db9672e2016-02-23 14:07:24 -050024from tempest.lib import exceptions
Jane Zadorozhna9c938c62015-07-01 17:06:16 +030025
26
Rodrigo Duarte Sousa2d78e8e2016-09-28 10:38:08 -030027CONF = config.CONF
28
29
Jane Zadorozhna9c938c62015-07-01 17:06:16 +030030class IdentityV3UsersTest(base.BaseIdentityV3Test):
zhufl8e3aacd2020-04-27 14:46:46 +080031 """Test identity user password"""
Jane Zadorozhna9c938c62015-07-01 17:06:16 +030032
33 @classmethod
34 def resource_setup(cls):
35 super(IdentityV3UsersTest, cls).resource_setup()
Jordan Pittier8160d312017-04-18 11:52:23 +020036 cls.creds = cls.os_primary.credentials
Jane Zadorozhna9c938c62015-07-01 17:06:16 +030037 cls.user_id = cls.creds.user_id
Jane Zadorozhna9c938c62015-07-01 17:06:16 +030038
Rodrigo Duarte Sousa2d78e8e2016-09-28 10:38:08 -030039 def _update_password(self, original_password, password):
Daniel Mellado7aea5342016-02-09 09:10:12 +000040 self.non_admin_users_client.update_user_password(
Rodrigo Duarte Sousa2d78e8e2016-09-28 10:38:08 -030041 self.user_id,
42 password=password,
43 original_password=original_password)
Lance Bragstad144c2f42015-11-19 16:42:37 +000044
Morgan Fainberg5b2c7452016-02-02 20:15:47 -080045 # NOTE(morganfainberg): Fernet tokens are not subsecond aware and
46 # Keystone should only be precise to the second. Sleep to ensure
Yaroslav Lobankovcbcb6112016-03-08 12:30:01 -060047 # we are passing the second boundary.
Lance Bragstada2c4ebc2015-10-05 20:34:39 +000048 time.sleep(1)
49
Jane Zadorozhna9c938c62015-07-01 17:06:16 +030050 # check authorization with new password
Rodrigo Duarte Sousa2d78e8e2016-09-28 10:38:08 -030051 self.non_admin_token.auth(user_id=self.user_id, password=password)
52
53 # Reset auth to get a new token with the new password
54 self.non_admin_users_client.auth_provider.clear_auth()
55 self.non_admin_users_client.auth_provider.credentials.password = (
56 password)
57
58 def _restore_password(self, old_pass, new_pass):
59 if CONF.identity_feature_enabled.security_compliance:
60 # First we need to clear the password history
61 unique_count = CONF.identity.user_unique_last_password_count
zhufl8e9a0732017-01-26 16:15:21 +080062 for _ in range(unique_count):
Rodrigo Duarte Sousa2d78e8e2016-09-28 10:38:08 -030063 random_pass = data_utils.rand_password()
64 self._update_password(
65 original_password=new_pass, password=random_pass)
66 new_pass = random_pass
67
68 self._update_password(original_password=new_pass, password=old_pass)
69 # Reset auth again to verify the password restore does work.
70 # Clear auth restores the original credentials and deletes
71 # cached auth data
72 self.non_admin_users_client.auth_provider.clear_auth()
73 # NOTE(lbragstad): Fernet tokens are not subsecond aware and
74 # Keystone should only be precise to the second. Sleep to ensure we
75 # are passing the second boundary before attempting to
76 # authenticate.
77 time.sleep(1)
78 self.non_admin_users_client.auth_provider.set_auth()
79
Roman Popelkaba62b112021-08-09 08:44:02 +020080 @testtools.skipUnless(CONF.identity_feature_enabled.security_compliance,
81 'Security compliance not available.')
Ken'ichi Ohmichi44f01272017-01-27 18:44:14 -080082 @decorators.idempotent_id('ad71bd23-12ad-426b-bb8b-195d2b635f27')
nicolasc98a3252018-12-17 13:06:02 -080083 @testtools.skipIf(CONF.identity_feature_enabled.immutable_user_source,
84 'Skipped because environment has an '
85 'immutable user source and solely '
86 'provides read-only access to users.')
Rodrigo Duarte Sousa2d78e8e2016-09-28 10:38:08 -030087 def test_user_update_own_password(self):
zhufl8e3aacd2020-04-27 14:46:46 +080088 """Test updating user's own password"""
Rodrigo Duarte Sousa2d78e8e2016-09-28 10:38:08 -030089 old_pass = self.creds.password
90 old_token = self.non_admin_client.token
91 new_pass = data_utils.rand_password()
92
Ken'ichi Ohmichi553d7cb2018-07-13 22:53:03 +000093 # to change password back. important for use_dynamic_credentials=false
Rodrigo Duarte Sousa2d78e8e2016-09-28 10:38:08 -030094 self.addCleanup(self._restore_password, old_pass, new_pass)
95
96 # user updates own password
97 self._update_password(original_password=old_pass, password=new_pass)
Jane Zadorozhna9c938c62015-07-01 17:06:16 +030098
99 # authorize with old token should lead to IdentityError (404 code)
100 self.assertRaises(exceptions.IdentityError,
101 self.non_admin_token.auth,
Rodrigo Duarte Sousa2d78e8e2016-09-28 10:38:08 -0300102 token=old_token)
Jane Zadorozhna9c938c62015-07-01 17:06:16 +0300103
104 # authorize with old password should lead to Unauthorized
105 self.assertRaises(exceptions.Unauthorized,
106 self.non_admin_token.auth,
107 user_id=self.user_id,
108 password=old_pass)
Rodrigo Duarte Sousa2d78e8e2016-09-28 10:38:08 -0300109
110 @testtools.skipUnless(CONF.identity_feature_enabled.security_compliance,
111 'Security compliance not available.')
Ken'ichi Ohmichi44f01272017-01-27 18:44:14 -0800112 @decorators.idempotent_id('941784ee-5342-4571-959b-b80dd2cea516')
nicolasc98a3252018-12-17 13:06:02 -0800113 @testtools.skipIf(CONF.identity_feature_enabled.immutable_user_source,
114 'Skipped because environment has an '
115 'immutable user source and solely '
116 'provides read-only access to users.')
Rodrigo Duarte Sousa2d78e8e2016-09-28 10:38:08 -0300117 def test_password_history_check_self_service_api(self):
zhufl8e3aacd2020-04-27 14:46:46 +0800118 """Test checking password changing history"""
Rodrigo Duarte Sousa2d78e8e2016-09-28 10:38:08 -0300119 old_pass = self.creds.password
120 new_pass1 = data_utils.rand_password()
121 new_pass2 = data_utils.rand_password()
122
123 self.addCleanup(self._restore_password, old_pass, new_pass2)
124
125 # Update password
126 self._update_password(original_password=old_pass, password=new_pass1)
127
128 if CONF.identity.user_unique_last_password_count > 1:
129 # Can not reuse a previously set password
130 self.assertRaises(exceptions.BadRequest,
131 self.non_admin_users_client.update_user_password,
132 self.user_id,
133 password=new_pass1,
134 original_password=new_pass1)
135
136 self.assertRaises(exceptions.BadRequest,
137 self.non_admin_users_client.update_user_password,
138 self.user_id,
139 password=old_pass,
140 original_password=new_pass1)
141
142 # A different password can be set
143 self._update_password(original_password=new_pass1, password=new_pass2)
144
145 @testtools.skipUnless(CONF.identity_feature_enabled.security_compliance,
146 'Security compliance not available.')
Ken'ichi Ohmichi44f01272017-01-27 18:44:14 -0800147 @decorators.idempotent_id('a7ad8bbf-2cff-4520-8c1d-96332e151658')
Rodrigo Duarte Sousa2d78e8e2016-09-28 10:38:08 -0300148 def test_user_account_lockout(self):
zhufl8e3aacd2020-04-27 14:46:46 +0800149 """Test locking out user account after failure attempts"""
zhufl270c25a2018-10-18 14:56:16 +0800150 if (CONF.identity.user_lockout_failure_attempts <= 0 or
151 CONF.identity.user_lockout_duration <= 0):
152 raise self.skipException(
153 "Both CONF.identity.user_lockout_failure_attempts and "
154 "CONF.identity.user_lockout_duration should be greater than "
155 "zero to test this feature")
156
Rodrigo Duarte Sousa2d78e8e2016-09-28 10:38:08 -0300157 password = self.creds.password
158
159 # First, we login using the correct credentials
160 self.non_admin_token.auth(user_id=self.user_id, password=password)
161
162 # Lock user account by using the wrong password to login
163 bad_password = data_utils.rand_password()
zhufl8e9a0732017-01-26 16:15:21 +0800164 for _ in range(CONF.identity.user_lockout_failure_attempts):
Rodrigo Duarte Sousa2d78e8e2016-09-28 10:38:08 -0300165 self.assertRaises(exceptions.Unauthorized,
166 self.non_admin_token.auth,
167 user_id=self.user_id,
168 password=bad_password)
169
170 # The user account must be locked, so now it is not possible to login
171 # even using the correct password
172 self.assertRaises(exceptions.Unauthorized,
173 self.non_admin_token.auth,
174 user_id=self.user_id,
175 password=password)
176
177 # If we wait the required time, the user account will be unlocked
178 time.sleep(CONF.identity.user_lockout_duration + 1)
Rodrigo Duarte Sousa36f68822017-01-05 16:05:00 -0300179 self.non_admin_token.auth(user_id=self.user_id, password=password)