[PR #8653/80f48cce backport][stable-9] Redfish: Added steps to allow a user to change their password when their account requires a password change (#8881)
Redfish: Added steps to allow a user to change their password when their account requires a password change (#8653)
* Redfish: Added steps to allow a user to change their password when their account requires a password change
Signed-off-by: Mike Raineri <michael.raineri@dell.com>
* Bug fix
Signed-off-by: Mike Raineri <michael.raineri@dell.com>
* Bug fix
Signed-off-by: Mike Raineri <michael.raineri@dell.com>
* Bug fixes with return data handling
Signed-off-by: Mike Raineri <michael.raineri@dell.com>
* Added changelog fragment
Signed-off-by: Mike Raineri <michael.raineri@dell.com>
* Update changelogs/fragments/8652-Redfish-Password-Change-Required.yml
Co-authored-by: Felix Fontein <felix@fontein.de>
---------
Signed-off-by: Mike Raineri <michael.raineri@dell.com>
Co-authored-by: Felix Fontein <felix@fontein.de>
(cherry picked from commit 80f48cceb4
)
Co-authored-by: Mike Raineri <mraineri@gmail.com>
pull/8890/head
parent
5550ba1946
commit
e6edf9cdea
|
@ -0,0 +1,2 @@
|
|||
minor_changes:
|
||||
- redfish_command - add handling of the ``PasswordChangeRequired`` message from services in the ``UpdateUserPassword`` command to directly modify the user's password if the requested user is the one invoking the operation (https://github.com/ansible-collections/community.general/issues/8652, https://github.com/ansible-collections/community.general/pull/8653).
|
|
@ -165,11 +165,11 @@ class RedfishUtils(object):
|
|||
if not allow_no_resp:
|
||||
raise
|
||||
except HTTPError as e:
|
||||
msg = self._get_extended_message(e)
|
||||
msg, data = self._get_extended_message(e)
|
||||
return {'ret': False,
|
||||
'msg': "HTTP Error %s on GET request to '%s', extended message: '%s'"
|
||||
% (e.code, uri, msg),
|
||||
'status': e.code}
|
||||
'status': e.code, 'data': data}
|
||||
except URLError as e:
|
||||
return {'ret': False, 'msg': "URL Error on GET request to '%s': '%s'"
|
||||
% (uri, e.reason)}
|
||||
|
@ -208,11 +208,11 @@ class RedfishUtils(object):
|
|||
data = None
|
||||
headers = {k.lower(): v for (k, v) in resp.info().items()}
|
||||
except HTTPError as e:
|
||||
msg = self._get_extended_message(e)
|
||||
msg, data = self._get_extended_message(e)
|
||||
return {'ret': False,
|
||||
'msg': "HTTP Error %s on POST request to '%s', extended message: '%s'"
|
||||
% (e.code, uri, msg),
|
||||
'status': e.code}
|
||||
'status': e.code, 'data': data}
|
||||
except URLError as e:
|
||||
return {'ret': False, 'msg': "URL Error on POST request to '%s': '%s'"
|
||||
% (uri, e.reason)}
|
||||
|
@ -256,11 +256,11 @@ class RedfishUtils(object):
|
|||
follow_redirects='all',
|
||||
use_proxy=True, timeout=self.timeout, ciphers=self.ciphers)
|
||||
except HTTPError as e:
|
||||
msg = self._get_extended_message(e)
|
||||
msg, data = self._get_extended_message(e)
|
||||
return {'ret': False, 'changed': False,
|
||||
'msg': "HTTP Error %s on PATCH request to '%s', extended message: '%s'"
|
||||
% (e.code, uri, msg),
|
||||
'status': e.code}
|
||||
'status': e.code, 'data': data}
|
||||
except URLError as e:
|
||||
return {'ret': False, 'changed': False,
|
||||
'msg': "URL Error on PATCH request to '%s': '%s'" % (uri, e.reason)}
|
||||
|
@ -291,11 +291,11 @@ class RedfishUtils(object):
|
|||
follow_redirects='all',
|
||||
use_proxy=True, timeout=self.timeout, ciphers=self.ciphers)
|
||||
except HTTPError as e:
|
||||
msg = self._get_extended_message(e)
|
||||
msg, data = self._get_extended_message(e)
|
||||
return {'ret': False,
|
||||
'msg': "HTTP Error %s on PUT request to '%s', extended message: '%s'"
|
||||
% (e.code, uri, msg),
|
||||
'status': e.code}
|
||||
'status': e.code, 'data': data}
|
||||
except URLError as e:
|
||||
return {'ret': False, 'msg': "URL Error on PUT request to '%s': '%s'"
|
||||
% (uri, e.reason)}
|
||||
|
@ -317,11 +317,11 @@ class RedfishUtils(object):
|
|||
follow_redirects='all',
|
||||
use_proxy=True, timeout=self.timeout, ciphers=self.ciphers)
|
||||
except HTTPError as e:
|
||||
msg = self._get_extended_message(e)
|
||||
msg, data = self._get_extended_message(e)
|
||||
return {'ret': False,
|
||||
'msg': "HTTP Error %s on DELETE request to '%s', extended message: '%s'"
|
||||
% (e.code, uri, msg),
|
||||
'status': e.code}
|
||||
'status': e.code, 'data': data}
|
||||
except URLError as e:
|
||||
return {'ret': False, 'msg': "URL Error on DELETE request to '%s': '%s'"
|
||||
% (uri, e.reason)}
|
||||
|
@ -391,8 +391,10 @@ class RedfishUtils(object):
|
|||
:param error: an HTTPError exception
|
||||
:type error: HTTPError
|
||||
:return: the ExtendedInfo message if present, else standard HTTP error
|
||||
:return: the JSON data of the response if present
|
||||
"""
|
||||
msg = http_client.responses.get(error.code, '')
|
||||
data = None
|
||||
if error.code >= 400:
|
||||
try:
|
||||
body = error.read().decode('utf-8')
|
||||
|
@ -406,7 +408,7 @@ class RedfishUtils(object):
|
|||
msg = str(data['error']['@Message.ExtendedInfo'])
|
||||
except Exception:
|
||||
pass
|
||||
return msg
|
||||
return msg, data
|
||||
|
||||
def _init_session(self):
|
||||
pass
|
||||
|
@ -1245,32 +1247,49 @@ class RedfishUtils(object):
|
|||
return response
|
||||
return {'ret': True, 'changed': True}
|
||||
|
||||
def _find_account_uri(self, username=None, acct_id=None):
|
||||
def _find_account_uri(self, username=None, acct_id=None, password_change_uri=None):
|
||||
if not any((username, acct_id)):
|
||||
return {'ret': False, 'msg':
|
||||
'Must provide either account_id or account_username'}
|
||||
|
||||
response = self.get_request(self.root_uri + self.accounts_uri)
|
||||
if response['ret'] is False:
|
||||
return response
|
||||
data = response['data']
|
||||
|
||||
uris = [a.get('@odata.id') for a in data.get('Members', []) if
|
||||
a.get('@odata.id')]
|
||||
for uri in uris:
|
||||
response = self.get_request(self.root_uri + uri)
|
||||
if password_change_uri:
|
||||
# Password change required; go directly to the specified URI
|
||||
response = self.get_request(self.root_uri + password_change_uri)
|
||||
if response['ret'] is False:
|
||||
continue
|
||||
return response
|
||||
data = response['data']
|
||||
headers = response['headers']
|
||||
if username:
|
||||
if username == data.get('UserName'):
|
||||
return {'ret': True, 'data': data,
|
||||
'headers': headers, 'uri': uri}
|
||||
'headers': headers, 'uri': password_change_uri}
|
||||
if acct_id:
|
||||
if acct_id == data.get('Id'):
|
||||
return {'ret': True, 'data': data,
|
||||
'headers': headers, 'uri': uri}
|
||||
'headers': headers, 'uri': password_change_uri}
|
||||
else:
|
||||
# Walk the accounts collection to find the desired user
|
||||
response = self.get_request(self.root_uri + self.accounts_uri)
|
||||
if response['ret'] is False:
|
||||
return response
|
||||
data = response['data']
|
||||
|
||||
uris = [a.get('@odata.id') for a in data.get('Members', []) if
|
||||
a.get('@odata.id')]
|
||||
for uri in uris:
|
||||
response = self.get_request(self.root_uri + uri)
|
||||
if response['ret'] is False:
|
||||
continue
|
||||
data = response['data']
|
||||
headers = response['headers']
|
||||
if username:
|
||||
if username == data.get('UserName'):
|
||||
return {'ret': True, 'data': data,
|
||||
'headers': headers, 'uri': uri}
|
||||
if acct_id:
|
||||
if acct_id == data.get('Id'):
|
||||
return {'ret': True, 'data': data,
|
||||
'headers': headers, 'uri': uri}
|
||||
|
||||
return {'ret': False, 'no_match': True, 'msg':
|
||||
'No account with the given account_id or account_username found'}
|
||||
|
@ -1491,7 +1510,8 @@ class RedfishUtils(object):
|
|||
'Must provide account_password for UpdateUserPassword command'}
|
||||
|
||||
response = self._find_account_uri(username=user.get('account_username'),
|
||||
acct_id=user.get('account_id'))
|
||||
acct_id=user.get('account_id'),
|
||||
password_change_uri=user.get('account_passwordchangerequired'))
|
||||
if not response['ret']:
|
||||
return response
|
||||
|
||||
|
@ -1534,6 +1554,31 @@ class RedfishUtils(object):
|
|||
resp['msg'] = 'Modified account service'
|
||||
return resp
|
||||
|
||||
def check_password_change_required(self, return_data):
|
||||
"""
|
||||
Checks a response if a user needs to change their password
|
||||
|
||||
:param return_data: The return data for a failed request
|
||||
:return: None or the URI of the account to update
|
||||
"""
|
||||
uri = None
|
||||
if 'data' in return_data:
|
||||
# Find the extended messages in the response payload
|
||||
extended_messages = return_data['data'].get('error', {}).get('@Message.ExtendedInfo', [])
|
||||
if len(extended_messages) == 0:
|
||||
extended_messages = return_data['data'].get('@Message.ExtendedInfo', [])
|
||||
# Go through each message and look for Base.1.X.PasswordChangeRequired
|
||||
for message in extended_messages:
|
||||
message_id = message.get('MessageId')
|
||||
if message_id is None:
|
||||
# While this is invalid, treat the lack of a MessageId as "no message"
|
||||
continue
|
||||
if message_id.startswith('Base.1.') and message_id.endswith('.PasswordChangeRequired'):
|
||||
# Password change required; get the URI of the user account
|
||||
uri = message['MessageArgs'][0]
|
||||
break
|
||||
return uri
|
||||
|
||||
def get_sessions(self):
|
||||
result = {}
|
||||
# listing all users has always been slower than other operations, why?
|
||||
|
|
|
@ -911,6 +911,7 @@ def main():
|
|||
'account_oemaccounttypes': module.params['oem_account_types'],
|
||||
'account_updatename': module.params['update_username'],
|
||||
'account_properties': module.params['account_properties'],
|
||||
'account_passwordchangerequired': None,
|
||||
}
|
||||
|
||||
# timeout
|
||||
|
@ -983,10 +984,16 @@ def main():
|
|||
# execute only if we find an Account service resource
|
||||
result = rf_utils._find_accountservice_resource()
|
||||
if result['ret'] is False:
|
||||
module.fail_json(msg=to_native(result['msg']))
|
||||
|
||||
for command in command_list:
|
||||
result = ACCOUNTS_COMMANDS[command](user)
|
||||
# If a password change is required and the user is attempting to
|
||||
# modify their password, try to proceed.
|
||||
user['account_passwordchangerequired'] = rf_utils.check_password_change_required(result)
|
||||
if len(command_list) == 1 and command_list[0] == "UpdateUserPassword" and user['account_passwordchangerequired']:
|
||||
result = rf_utils.update_user_password(user)
|
||||
else:
|
||||
module.fail_json(msg=to_native(result['msg']))
|
||||
else:
|
||||
for command in command_list:
|
||||
result = ACCOUNTS_COMMANDS[command](user)
|
||||
|
||||
elif category == "Systems":
|
||||
# execute only if we find a System resource
|
||||
|
|
Loading…
Reference in New Issue