mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
f64ace97af
* Added module for ipa_otpconfig
* Make no_log=False explicit.
* Updated inputs to be int type instead of strings to align to expected inputs. Updated output message
* Add changelog fragment
* Remove changelog fragment as this is a new module
* Update plugins/modules/identity/ipa/ipa_otpconfig.py
Add version_added field to module description.
Co-authored-by: Felix Fontein <felix@fontein.de>
* Updated punctuation in examples
* Add unit test for ipa_otpconfig
* Add ipa_otptoken module with unit test
* Updated documentation in unit test
* Update plugins/modules/identity/ipa/ipa_otpconfig.py
Co-authored-by: Felix Fontein <felix@fontein.de>
* Update plugins/modules/identity/ipa/ipa_otpconfig.py
Co-authored-by: Felix Fontein <felix@fontein.de>
* Update plugins/modules/identity/ipa/ipa_otptoken.py
Co-authored-by: Felix Fontein <felix@fontein.de>
* Update plugins/modules/identity/ipa/ipa_otptoken.py
Co-authored-by: Felix Fontein <felix@fontein.de>
* Update plugins/modules/identity/ipa/ipa_otptoken.py
Co-authored-by: Felix Fontein <felix@fontein.de>
* Update plugins/modules/identity/ipa/ipa_otptoken.py
Co-authored-by: Felix Fontein <felix@fontein.de>
* Update plugins/modules/identity/ipa/ipa_otptoken.py
Co-authored-by: Felix Fontein <felix@fontein.de>
* Update plugins/modules/identity/ipa/ipa_otptoken.py
Co-authored-by: Felix Fontein <felix@fontein.de>
* Added some documentation updates to make it conform to ansible standards
* Update plugins/modules/identity/ipa/ipa_otptoken.py
Co-authored-by: Felix Fontein <felix@fontein.de>
* Address review comments
Co-authored-by: Chris Costa <chris.costa@compellingtech.com>
Co-authored-by: Felix Fontein <felix@fontein.de>
(cherry picked from commit 31645ded11
)
Co-authored-by: justchris1 <30219018+justchris1@users.noreply.github.com>
406 lines
13 KiB
Python
406 lines
13 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
# Copyright: (c) 2020, Ansible Project
|
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
|
|
from __future__ import absolute_import, division, print_function
|
|
__metaclass__ = type
|
|
|
|
from contextlib import contextmanager
|
|
|
|
from ansible_collections.community.general.tests.unit.compat import unittest
|
|
from ansible_collections.community.general.tests.unit.compat.mock import call, patch
|
|
from ansible_collections.community.general.tests.unit.plugins.modules.utils import AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args
|
|
|
|
from ansible_collections.community.general.plugins.modules.identity.ipa import ipa_otpconfig
|
|
|
|
|
|
@contextmanager
|
|
def patch_ipa(**kwargs):
|
|
"""Mock context manager for patching the methods in OTPConfigIPAClient that contact the IPA server
|
|
|
|
Patches the `login` and `_post_json` methods
|
|
|
|
Keyword arguments are passed to the mock object that patches `_post_json`
|
|
|
|
No arguments are passed to the mock object that patches `login` because no tests require it
|
|
|
|
Example::
|
|
|
|
with patch_ipa(return_value={}) as (mock_login, mock_post):
|
|
...
|
|
"""
|
|
obj = ipa_otpconfig.OTPConfigIPAClient
|
|
with patch.object(obj, 'login') as mock_login:
|
|
with patch.object(obj, '_post_json', **kwargs) as mock_post:
|
|
yield mock_login, mock_post
|
|
|
|
|
|
class TestIPAOTPConfig(ModuleTestCase):
|
|
def setUp(self):
|
|
super(TestIPAOTPConfig, self).setUp()
|
|
self.module = ipa_otpconfig
|
|
|
|
def _test_base(self, module_args, return_value, mock_calls, changed):
|
|
"""Base function that's called by all the other test functions
|
|
|
|
module_args (dict):
|
|
Arguments passed to the module
|
|
|
|
return_value (dict):
|
|
Mocked return value of OTPConfigIPAClient.otpconfig_show, as returned by the IPA API.
|
|
This should be set to the current state. It will be changed to the desired state using the above arguments.
|
|
(Technically, this is the return value of _post_json, but it's only checked by otpconfig_show).
|
|
|
|
mock_calls (list/tuple of dicts):
|
|
List of calls made to OTPConfigIPAClient._post_json, in order.
|
|
_post_json is called by all of the otpconfig_* methods of the class.
|
|
Pass an empty list if no calls are expected.
|
|
|
|
changed (bool):
|
|
Whether or not the module is supposed to be marked as changed
|
|
"""
|
|
set_module_args(module_args)
|
|
|
|
# Run the module
|
|
with patch_ipa(return_value=return_value) as (mock_login, mock_post):
|
|
with self.assertRaises(AnsibleExitJson) as exec_info:
|
|
self.module.main()
|
|
|
|
# Verify that the calls to _post_json match what is expected
|
|
expected_call_count = len(mock_calls)
|
|
if expected_call_count > 1:
|
|
# Convert the call dicts to unittest.mock.call instances because `assert_has_calls` only accepts them
|
|
converted_calls = []
|
|
for call_dict in mock_calls:
|
|
converted_calls.append(call(**call_dict))
|
|
|
|
mock_post.assert_has_calls(converted_calls)
|
|
self.assertEqual(len(mock_post.mock_calls), expected_call_count)
|
|
elif expected_call_count == 1:
|
|
mock_post.assert_called_once_with(**mock_calls[0])
|
|
else: # expected_call_count is 0
|
|
mock_post.assert_not_called()
|
|
|
|
# Verify that the module's changed status matches what is expected
|
|
self.assertIs(exec_info.exception.args[0]['changed'], changed)
|
|
|
|
def test_set_all_no_adjustment(self):
|
|
"""Set values requiring no adjustment"""
|
|
module_args = {
|
|
'ipatokentotpauthwindow': 11,
|
|
'ipatokentotpsyncwindow': 12,
|
|
'ipatokenhotpauthwindow': 13,
|
|
'ipatokenhotpsyncwindow': 14
|
|
}
|
|
return_value = {
|
|
'ipatokentotpauthwindow': ['11'],
|
|
'ipatokentotpsyncwindow': ['12'],
|
|
'ipatokenhotpauthwindow': ['13'],
|
|
'ipatokenhotpsyncwindow': ['14']}
|
|
mock_calls = (
|
|
{
|
|
'method': 'otpconfig_show',
|
|
'name': None
|
|
},
|
|
{
|
|
'method': 'otpconfig_show',
|
|
'name': None
|
|
}
|
|
)
|
|
changed = False
|
|
|
|
self._test_base(module_args, return_value, mock_calls, changed)
|
|
|
|
def test_set_all_aliases_no_adjustment(self):
|
|
"""Set values requiring no adjustment on all using aliases values"""
|
|
module_args = {
|
|
'totpauthwindow': 11,
|
|
'totpsyncwindow': 12,
|
|
'hotpauthwindow': 13,
|
|
'hotpsyncwindow': 14
|
|
}
|
|
return_value = {
|
|
'ipatokentotpauthwindow': ['11'],
|
|
'ipatokentotpsyncwindow': ['12'],
|
|
'ipatokenhotpauthwindow': ['13'],
|
|
'ipatokenhotpsyncwindow': ['14']}
|
|
mock_calls = (
|
|
{
|
|
'method': 'otpconfig_show',
|
|
'name': None
|
|
},
|
|
{
|
|
'method': 'otpconfig_show',
|
|
'name': None
|
|
}
|
|
)
|
|
changed = False
|
|
|
|
self._test_base(module_args, return_value, mock_calls, changed)
|
|
|
|
def test_set_totp_auth_window_no_adjustment(self):
|
|
"""Set values requiring no adjustment on totpauthwindow"""
|
|
module_args = {
|
|
'totpauthwindow': 11
|
|
}
|
|
return_value = {
|
|
'ipatokentotpauthwindow': ['11'],
|
|
'ipatokentotpsyncwindow': ['12'],
|
|
'ipatokenhotpauthwindow': ['13'],
|
|
'ipatokenhotpsyncwindow': ['14']}
|
|
mock_calls = (
|
|
{
|
|
'method': 'otpconfig_show',
|
|
'name': None
|
|
},
|
|
{
|
|
'method': 'otpconfig_show',
|
|
'name': None
|
|
}
|
|
)
|
|
changed = False
|
|
|
|
self._test_base(module_args, return_value, mock_calls, changed)
|
|
|
|
def test_set_totp_sync_window_no_adjustment(self):
|
|
"""Set values requiring no adjustment on totpsyncwindow"""
|
|
module_args = {
|
|
'totpsyncwindow': 12
|
|
}
|
|
return_value = {
|
|
'ipatokentotpauthwindow': ['11'],
|
|
'ipatokentotpsyncwindow': ['12'],
|
|
'ipatokenhotpauthwindow': ['13'],
|
|
'ipatokenhotpsyncwindow': ['14']}
|
|
mock_calls = (
|
|
{
|
|
'method': 'otpconfig_show',
|
|
'name': None
|
|
},
|
|
{
|
|
'method': 'otpconfig_show',
|
|
'name': None
|
|
}
|
|
)
|
|
changed = False
|
|
|
|
self._test_base(module_args, return_value, mock_calls, changed)
|
|
|
|
def test_set_hotp_auth_window_no_adjustment(self):
|
|
"""Set values requiring no adjustment on hotpauthwindow"""
|
|
module_args = {
|
|
'hotpauthwindow': 13
|
|
}
|
|
return_value = {
|
|
'ipatokentotpauthwindow': ['11'],
|
|
'ipatokentotpsyncwindow': ['12'],
|
|
'ipatokenhotpauthwindow': ['13'],
|
|
'ipatokenhotpsyncwindow': ['14']}
|
|
mock_calls = (
|
|
{
|
|
'method': 'otpconfig_show',
|
|
'name': None
|
|
},
|
|
{
|
|
'method': 'otpconfig_show',
|
|
'name': None
|
|
}
|
|
)
|
|
changed = False
|
|
|
|
self._test_base(module_args, return_value, mock_calls, changed)
|
|
|
|
def test_set_hotp_sync_window_no_adjustment(self):
|
|
"""Set values requiring no adjustment on hotpsyncwindow"""
|
|
module_args = {
|
|
'hotpsyncwindow': 14
|
|
}
|
|
return_value = {
|
|
'ipatokentotpauthwindow': ['11'],
|
|
'ipatokentotpsyncwindow': ['12'],
|
|
'ipatokenhotpauthwindow': ['13'],
|
|
'ipatokenhotpsyncwindow': ['14']}
|
|
mock_calls = (
|
|
{
|
|
'method': 'otpconfig_show',
|
|
'name': None
|
|
},
|
|
{
|
|
'method': 'otpconfig_show',
|
|
'name': None
|
|
}
|
|
)
|
|
changed = False
|
|
|
|
self._test_base(module_args, return_value, mock_calls, changed)
|
|
|
|
def test_set_totp_auth_window(self):
|
|
"""Set values requiring adjustment on totpauthwindow"""
|
|
module_args = {
|
|
'totpauthwindow': 10
|
|
}
|
|
return_value = {
|
|
'ipatokentotpauthwindow': ['11'],
|
|
'ipatokentotpsyncwindow': ['12'],
|
|
'ipatokenhotpauthwindow': ['13'],
|
|
'ipatokenhotpsyncwindow': ['14']}
|
|
mock_calls = (
|
|
{
|
|
'method': 'otpconfig_show',
|
|
'name': None
|
|
},
|
|
{
|
|
'method': 'otpconfig_mod',
|
|
'name': None,
|
|
'item': {'ipatokentotpauthwindow': '10'}
|
|
},
|
|
{
|
|
'method': 'otpconfig_show',
|
|
'name': None
|
|
}
|
|
)
|
|
changed = True
|
|
|
|
self._test_base(module_args, return_value, mock_calls, changed)
|
|
|
|
def test_set_totp_sync_window(self):
|
|
"""Set values requiring adjustment on totpsyncwindow"""
|
|
module_args = {
|
|
'totpsyncwindow': 10
|
|
}
|
|
return_value = {
|
|
'ipatokentotpauthwindow': ['11'],
|
|
'ipatokentotpsyncwindow': ['12'],
|
|
'ipatokenhotpauthwindow': ['13'],
|
|
'ipatokenhotpsyncwindow': ['14']}
|
|
mock_calls = (
|
|
{
|
|
'method': 'otpconfig_show',
|
|
'name': None
|
|
},
|
|
{
|
|
'method': 'otpconfig_mod',
|
|
'name': None,
|
|
'item': {'ipatokentotpsyncwindow': '10'}
|
|
},
|
|
{
|
|
'method': 'otpconfig_show',
|
|
'name': None
|
|
}
|
|
)
|
|
changed = True
|
|
|
|
self._test_base(module_args, return_value, mock_calls, changed)
|
|
|
|
def test_set_hotp_auth_window(self):
|
|
"""Set values requiring adjustment on hotpauthwindow"""
|
|
module_args = {
|
|
'hotpauthwindow': 10
|
|
}
|
|
return_value = {
|
|
'ipatokentotpauthwindow': ['11'],
|
|
'ipatokentotpsyncwindow': ['12'],
|
|
'ipatokenhotpauthwindow': ['13'],
|
|
'ipatokenhotpsyncwindow': ['14']}
|
|
mock_calls = (
|
|
{
|
|
'method': 'otpconfig_show',
|
|
'name': None
|
|
},
|
|
{
|
|
'method': 'otpconfig_mod',
|
|
'name': None,
|
|
'item': {'ipatokenhotpauthwindow': '10'}
|
|
},
|
|
{
|
|
'method': 'otpconfig_show',
|
|
'name': None
|
|
}
|
|
)
|
|
changed = True
|
|
|
|
self._test_base(module_args, return_value, mock_calls, changed)
|
|
|
|
def test_set_hotp_sync_window(self):
|
|
"""Set values requiring adjustment on hotpsyncwindow"""
|
|
module_args = {
|
|
'hotpsyncwindow': 10
|
|
}
|
|
return_value = {
|
|
'ipatokentotpauthwindow': ['11'],
|
|
'ipatokentotpsyncwindow': ['12'],
|
|
'ipatokenhotpauthwindow': ['13'],
|
|
'ipatokenhotpsyncwindow': ['14']}
|
|
mock_calls = (
|
|
{
|
|
'method': 'otpconfig_show',
|
|
'name': None
|
|
},
|
|
{
|
|
'method': 'otpconfig_mod',
|
|
'name': None,
|
|
'item': {'ipatokenhotpsyncwindow': '10'}
|
|
},
|
|
{
|
|
'method': 'otpconfig_show',
|
|
'name': None
|
|
}
|
|
)
|
|
changed = True
|
|
|
|
self._test_base(module_args, return_value, mock_calls, changed)
|
|
|
|
def test_set_all(self):
|
|
"""Set values requiring adjustment on all"""
|
|
module_args = {
|
|
'ipatokentotpauthwindow': 11,
|
|
'ipatokentotpsyncwindow': 12,
|
|
'ipatokenhotpauthwindow': 13,
|
|
'ipatokenhotpsyncwindow': 14
|
|
}
|
|
return_value = {
|
|
'ipatokentotpauthwindow': ['1'],
|
|
'ipatokentotpsyncwindow': ['2'],
|
|
'ipatokenhotpauthwindow': ['3'],
|
|
'ipatokenhotpsyncwindow': ['4']}
|
|
mock_calls = (
|
|
{
|
|
'method': 'otpconfig_show',
|
|
'name': None
|
|
},
|
|
{
|
|
'method': 'otpconfig_mod',
|
|
'name': None,
|
|
'item': {'ipatokentotpauthwindow': '11',
|
|
'ipatokentotpsyncwindow': '12',
|
|
'ipatokenhotpauthwindow': '13',
|
|
'ipatokenhotpsyncwindow': '14'}
|
|
},
|
|
{
|
|
'method': 'otpconfig_show',
|
|
'name': None
|
|
}
|
|
)
|
|
changed = True
|
|
|
|
self._test_base(module_args, return_value, mock_calls, changed)
|
|
|
|
def test_fail_post(self):
|
|
"""Fail due to an exception raised from _post_json"""
|
|
set_module_args({
|
|
'ipatokentotpauthwindow': 11,
|
|
'ipatokentotpsyncwindow': 12,
|
|
'ipatokenhotpauthwindow': 13,
|
|
'ipatokenhotpsyncwindow': 14
|
|
})
|
|
|
|
with patch_ipa(side_effect=Exception('ERROR MESSAGE')) as (mock_login, mock_post):
|
|
with self.assertRaises(AnsibleFailJson) as exec_info:
|
|
self.module.main()
|
|
|
|
self.assertEqual(exec_info.exception.args[0]['msg'], 'ERROR MESSAGE')
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|