mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
Add ipa_pwpolicy module (#1147)
* Add ipa_pwpolicy module Used for modifying FreeIPA password policies Functions similarly to the existing IPA modules * Add sample return value to ipa_pwpolicy module * Add unit tests for the ipa_pwpolicy module Also moves the `exit_json` call in the main module outside of the try clause because it was stopping the tests from working * Update version added for the ipa_pwpolicy module * Add check_mode note for the ipa_pwpolicy module * Add missing period in ipa_pwpolicy module doc * Fix tense of the ipa_pwpolicy module description * Reword ipa_pwpolicy documentation Improve the wording of the ipa_pwpolicy documentation to make it more clear * Rename ipa_pwpolicy options to use shorter names
This commit is contained in:
parent
e7b16a96b9
commit
74fcb0335e
5 changed files with 869 additions and 0 deletions
255
plugins/modules/identity/ipa/ipa_pwpolicy.py
Normal file
255
plugins/modules/identity/ipa/ipa_pwpolicy.py
Normal file
|
@ -0,0 +1,255 @@
|
||||||
|
#!/usr/bin/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
|
||||||
|
|
||||||
|
DOCUMENTATION = r'''
|
||||||
|
---
|
||||||
|
module: ipa_pwpolicy
|
||||||
|
author: Adralioh (@adralioh)
|
||||||
|
short_description: Manage FreeIPA password policies
|
||||||
|
description:
|
||||||
|
- Add, modify, or delete a password policy using the IPA API.
|
||||||
|
version_added: 2.0.0
|
||||||
|
options:
|
||||||
|
group:
|
||||||
|
description:
|
||||||
|
- Name of the group that the policy applies to.
|
||||||
|
- If omitted, the global policy is used.
|
||||||
|
aliases: ["name"]
|
||||||
|
type: str
|
||||||
|
state:
|
||||||
|
description: State to ensure.
|
||||||
|
default: "present"
|
||||||
|
choices: ["absent", "present"]
|
||||||
|
type: str
|
||||||
|
maxpwdlife:
|
||||||
|
description: Maximum password lifetime (in days).
|
||||||
|
type: str
|
||||||
|
minpwdlife:
|
||||||
|
description: Minimum password lifetime (in hours).
|
||||||
|
type: str
|
||||||
|
historylength:
|
||||||
|
description:
|
||||||
|
- Number of previous passwords that are remembered.
|
||||||
|
- Users cannot reuse remembered passwords.
|
||||||
|
type: str
|
||||||
|
minclasses:
|
||||||
|
description: Minimum number of character classes.
|
||||||
|
type: str
|
||||||
|
minlength:
|
||||||
|
description: Minimum password length.
|
||||||
|
type: str
|
||||||
|
priority:
|
||||||
|
description:
|
||||||
|
- Priority of the policy.
|
||||||
|
- High number means lower priority.
|
||||||
|
- Required when C(cn) is not the global policy.
|
||||||
|
type: str
|
||||||
|
maxfailcount:
|
||||||
|
description: Maximum number of consecutive failures before lockout.
|
||||||
|
type: str
|
||||||
|
failinterval:
|
||||||
|
description: Period (in seconds) after which the number of failed login attempts is reset.
|
||||||
|
type: str
|
||||||
|
lockouttime:
|
||||||
|
description: Period (in seconds) for which users are locked out.
|
||||||
|
type: str
|
||||||
|
extends_documentation_fragment:
|
||||||
|
- community.general.ipa.documentation
|
||||||
|
notes:
|
||||||
|
- Supports C(check_mode).
|
||||||
|
'''
|
||||||
|
|
||||||
|
EXAMPLES = r'''
|
||||||
|
- name: Modify the global password policy
|
||||||
|
community.general.ipa_pwpolicy:
|
||||||
|
maxpwdlife: '90'
|
||||||
|
minpwdlife: '1'
|
||||||
|
historylength: '8'
|
||||||
|
minclasses: '3'
|
||||||
|
minlength: '16'
|
||||||
|
maxfailcount: '6'
|
||||||
|
failinterval: '60'
|
||||||
|
lockouttime: '600'
|
||||||
|
ipa_host: ipa.example.com
|
||||||
|
ipa_user: admin
|
||||||
|
ipa_pass: topsecret
|
||||||
|
|
||||||
|
- name: Ensure the password policy for the group admins is present
|
||||||
|
community.general.ipa_pwpolicy:
|
||||||
|
group: admins
|
||||||
|
state: present
|
||||||
|
maxpwdlife: '60'
|
||||||
|
minpwdlife: '24'
|
||||||
|
historylength: '16'
|
||||||
|
minclasses: '4'
|
||||||
|
priority: '10'
|
||||||
|
maxfailcount: '4'
|
||||||
|
failinterval: '600'
|
||||||
|
lockouttime: '1200'
|
||||||
|
ipa_host: ipa.example.com
|
||||||
|
ipa_user: admin
|
||||||
|
ipa_pass: topsecret
|
||||||
|
|
||||||
|
- name: Ensure that the group sysops does not have a unique password policy
|
||||||
|
community.general.ipa_pwpolicy:
|
||||||
|
group: sysops
|
||||||
|
state: absent
|
||||||
|
ipa_host: ipa.example.com
|
||||||
|
ipa_user: admin
|
||||||
|
ipa_pass: topsecret
|
||||||
|
'''
|
||||||
|
|
||||||
|
RETURN = r'''
|
||||||
|
pwpolicy:
|
||||||
|
description: Password policy as returned by IPA API.
|
||||||
|
returned: always
|
||||||
|
type: dict
|
||||||
|
sample:
|
||||||
|
cn: ['admins']
|
||||||
|
cospriority: ['10']
|
||||||
|
dn: 'cn=admins,cn=EXAMPLE.COM,cn=kerberos,dc=example,dc=com'
|
||||||
|
krbmaxpwdlife: ['60']
|
||||||
|
krbminpwdlife: ['24']
|
||||||
|
krbpwdfailurecountinterval: ['600']
|
||||||
|
krbpwdhistorylength: ['16']
|
||||||
|
krbpwdlockoutduration: ['1200']
|
||||||
|
krbpwdmaxfailure: ['4']
|
||||||
|
krbpwdmindiffchars: ['4']
|
||||||
|
objectclass: ['top', 'nscontainer', 'krbpwdpolicy']
|
||||||
|
'''
|
||||||
|
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
|
from ansible_collections.community.general.plugins.module_utils.ipa import IPAClient, ipa_argument_spec
|
||||||
|
from ansible.module_utils._text import to_native
|
||||||
|
|
||||||
|
|
||||||
|
class PwPolicyIPAClient(IPAClient):
|
||||||
|
'''The global policy will be selected when `name` is `None`'''
|
||||||
|
def __init__(self, module, host, port, protocol):
|
||||||
|
super(PwPolicyIPAClient, self).__init__(module, host, port, protocol)
|
||||||
|
|
||||||
|
def pwpolicy_find(self, name):
|
||||||
|
if name is None:
|
||||||
|
# Manually set the cn to the global policy because pwpolicy_find will return a random
|
||||||
|
# different policy if cn is `None`
|
||||||
|
name = 'global_policy'
|
||||||
|
return self._post_json(method='pwpolicy_find', name=None, item={'all': True, 'cn': name})
|
||||||
|
|
||||||
|
def pwpolicy_add(self, name, item):
|
||||||
|
return self._post_json(method='pwpolicy_add', name=name, item=item)
|
||||||
|
|
||||||
|
def pwpolicy_mod(self, name, item):
|
||||||
|
return self._post_json(method='pwpolicy_mod', name=name, item=item)
|
||||||
|
|
||||||
|
def pwpolicy_del(self, name):
|
||||||
|
return self._post_json(method='pwpolicy_del', name=name)
|
||||||
|
|
||||||
|
|
||||||
|
def get_pwpolicy_dict(maxpwdlife=None, minpwdlife=None, historylength=None, minclasses=None,
|
||||||
|
minlength=None, priority=None, maxfailcount=None, failinterval=None,
|
||||||
|
lockouttime=None):
|
||||||
|
pwpolicy = {}
|
||||||
|
if maxpwdlife is not None:
|
||||||
|
pwpolicy['krbmaxpwdlife'] = maxpwdlife
|
||||||
|
if minpwdlife is not None:
|
||||||
|
pwpolicy['krbminpwdlife'] = minpwdlife
|
||||||
|
if historylength is not None:
|
||||||
|
pwpolicy['krbpwdhistorylength'] = historylength
|
||||||
|
if minclasses is not None:
|
||||||
|
pwpolicy['krbpwdmindiffchars'] = minclasses
|
||||||
|
if minlength is not None:
|
||||||
|
pwpolicy['krbpwdminlength'] = minlength
|
||||||
|
if priority is not None:
|
||||||
|
pwpolicy['cospriority'] = priority
|
||||||
|
if maxfailcount is not None:
|
||||||
|
pwpolicy['krbpwdmaxfailure'] = maxfailcount
|
||||||
|
if failinterval is not None:
|
||||||
|
pwpolicy['krbpwdfailurecountinterval'] = failinterval
|
||||||
|
if lockouttime is not None:
|
||||||
|
pwpolicy['krbpwdlockoutduration'] = lockouttime
|
||||||
|
|
||||||
|
return pwpolicy
|
||||||
|
|
||||||
|
|
||||||
|
def get_pwpolicy_diff(client, ipa_pwpolicy, module_pwpolicy):
|
||||||
|
return client.get_diff(ipa_data=ipa_pwpolicy, module_data=module_pwpolicy)
|
||||||
|
|
||||||
|
|
||||||
|
def ensure(module, client):
|
||||||
|
state = module.params['state']
|
||||||
|
name = module.params['group']
|
||||||
|
|
||||||
|
module_pwpolicy = get_pwpolicy_dict(maxpwdlife=module.params.get('maxpwdlife'),
|
||||||
|
minpwdlife=module.params.get('minpwdlife'),
|
||||||
|
historylength=module.params.get('historylength'),
|
||||||
|
minclasses=module.params.get('minclasses'),
|
||||||
|
minlength=module.params.get('minlength'),
|
||||||
|
priority=module.params.get('priority'),
|
||||||
|
maxfailcount=module.params.get('maxfailcount'),
|
||||||
|
failinterval=module.params.get('failinterval'),
|
||||||
|
lockouttime=module.params.get('lockouttime'))
|
||||||
|
|
||||||
|
ipa_pwpolicy = client.pwpolicy_find(name=name)
|
||||||
|
|
||||||
|
changed = False
|
||||||
|
if state == 'present':
|
||||||
|
if not ipa_pwpolicy:
|
||||||
|
changed = True
|
||||||
|
if not module.check_mode:
|
||||||
|
ipa_pwpolicy = client.pwpolicy_add(name=name, item=module_pwpolicy)
|
||||||
|
else:
|
||||||
|
diff = get_pwpolicy_diff(client, ipa_pwpolicy, module_pwpolicy)
|
||||||
|
if len(diff) > 0:
|
||||||
|
changed = True
|
||||||
|
if not module.check_mode:
|
||||||
|
ipa_pwpolicy = client.pwpolicy_mod(name=name, item=module_pwpolicy)
|
||||||
|
else:
|
||||||
|
if ipa_pwpolicy:
|
||||||
|
changed = True
|
||||||
|
if not module.check_mode:
|
||||||
|
client.pwpolicy_del(name=name)
|
||||||
|
|
||||||
|
return changed, ipa_pwpolicy
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
argument_spec = ipa_argument_spec()
|
||||||
|
argument_spec.update(group=dict(type='str', aliases=['name']),
|
||||||
|
state=dict(type='str', default='present', choices=['present', 'absent']),
|
||||||
|
maxpwdlife=dict(type='str'),
|
||||||
|
minpwdlife=dict(type='str'),
|
||||||
|
historylength=dict(type='str'),
|
||||||
|
minclasses=dict(type='str'),
|
||||||
|
minlength=dict(type='str'),
|
||||||
|
priority=dict(type='str'),
|
||||||
|
maxfailcount=dict(type='str'),
|
||||||
|
failinterval=dict(type='str'),
|
||||||
|
lockouttime=dict(type='str'))
|
||||||
|
|
||||||
|
module = AnsibleModule(argument_spec=argument_spec,
|
||||||
|
supports_check_mode=True)
|
||||||
|
|
||||||
|
client = PwPolicyIPAClient(module=module,
|
||||||
|
host=module.params['ipa_host'],
|
||||||
|
port=module.params['ipa_port'],
|
||||||
|
protocol=module.params['ipa_prot'])
|
||||||
|
|
||||||
|
try:
|
||||||
|
client.login(username=module.params['ipa_user'],
|
||||||
|
password=module.params['ipa_pass'])
|
||||||
|
changed, pwpolicy = ensure(module, client)
|
||||||
|
except Exception as e:
|
||||||
|
module.fail_json(msg=to_native(e), exception=traceback.format_exc())
|
||||||
|
|
||||||
|
module.exit_json(changed=changed, pwpolicy=pwpolicy)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
1
plugins/modules/ipa_pwpolicy.py
Symbolic link
1
plugins/modules/ipa_pwpolicy.py
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
./identity/ipa/ipa_pwpolicy.py
|
0
tests/unit/plugins/modules/identity/__init__.py
Normal file
0
tests/unit/plugins/modules/identity/__init__.py
Normal file
0
tests/unit/plugins/modules/identity/ipa/__init__.py
Normal file
0
tests/unit/plugins/modules/identity/ipa/__init__.py
Normal file
613
tests/unit/plugins/modules/identity/ipa/test_ipa_pwpolicy.py
Normal file
613
tests/unit/plugins/modules/identity/ipa/test_ipa_pwpolicy.py
Normal file
|
@ -0,0 +1,613 @@
|
||||||
|
# -*- 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_pwpolicy
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def patch_ipa(**kwargs):
|
||||||
|
"""Mock context manager for patching the methods in PwPolicyIPAClient 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_pwpolicy.PwPolicyIPAClient
|
||||||
|
with patch.object(obj, 'login') as mock_login:
|
||||||
|
with patch.object(obj, '_post_json', **kwargs) as mock_post:
|
||||||
|
yield mock_login, mock_post
|
||||||
|
|
||||||
|
|
||||||
|
class TestIPAPwPolicy(ModuleTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
super(TestIPAPwPolicy, self).setUp()
|
||||||
|
self.module = ipa_pwpolicy
|
||||||
|
|
||||||
|
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 PwPolicyIPAClient.pwpolicy_find, 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 pwpolicy_find).
|
||||||
|
An empty dict means that the policy doesn't exist.
|
||||||
|
|
||||||
|
mock_calls (list/tuple of dicts):
|
||||||
|
List of calls made to PwPolicyIPAClient._post_json, in order.
|
||||||
|
_post_json is called by all of the pwpolicy_* 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_add(self):
|
||||||
|
"""Add a new policy"""
|
||||||
|
module_args = {
|
||||||
|
'group': 'admins',
|
||||||
|
'state': 'present',
|
||||||
|
'priority': '10',
|
||||||
|
'maxpwdlife': '90',
|
||||||
|
'minpwdlife': '1',
|
||||||
|
'historylength': '8',
|
||||||
|
'minclasses': '3',
|
||||||
|
'minlength': '16',
|
||||||
|
'maxfailcount': '6',
|
||||||
|
'failinterval': '60',
|
||||||
|
'lockouttime': '600'
|
||||||
|
}
|
||||||
|
return_value = {}
|
||||||
|
mock_calls = (
|
||||||
|
{
|
||||||
|
'method': 'pwpolicy_find',
|
||||||
|
'name': None,
|
||||||
|
'item': {
|
||||||
|
'all': True,
|
||||||
|
'cn': 'admins'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'method': 'pwpolicy_add',
|
||||||
|
'name': 'admins',
|
||||||
|
'item': {
|
||||||
|
'cospriority': '10',
|
||||||
|
'krbmaxpwdlife': '90',
|
||||||
|
'krbminpwdlife': '1',
|
||||||
|
'krbpwdhistorylength': '8',
|
||||||
|
'krbpwdmindiffchars': '3',
|
||||||
|
'krbpwdminlength': '16',
|
||||||
|
'krbpwdmaxfailure': '6',
|
||||||
|
'krbpwdfailurecountinterval': '60',
|
||||||
|
'krbpwdlockoutduration': '600'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
changed = True
|
||||||
|
|
||||||
|
self._test_base(module_args, return_value, mock_calls, changed)
|
||||||
|
|
||||||
|
def test_aliases(self):
|
||||||
|
"""Same as test_add, but uses the `name` alias for the `group` option"""
|
||||||
|
module_args = {
|
||||||
|
'name': 'admins',
|
||||||
|
'state': 'present',
|
||||||
|
'priority': '10',
|
||||||
|
'maxpwdlife': '90',
|
||||||
|
'minpwdlife': '1',
|
||||||
|
'historylength': '8',
|
||||||
|
'minclasses': '3',
|
||||||
|
'minlength': '16',
|
||||||
|
'maxfailcount': '6',
|
||||||
|
'failinterval': '60',
|
||||||
|
'lockouttime': '600'
|
||||||
|
}
|
||||||
|
return_value = {}
|
||||||
|
mock_calls = (
|
||||||
|
{
|
||||||
|
'method': 'pwpolicy_find',
|
||||||
|
'name': None,
|
||||||
|
'item': {
|
||||||
|
'all': True,
|
||||||
|
'cn': 'admins'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'method': 'pwpolicy_add',
|
||||||
|
'name': 'admins',
|
||||||
|
'item': {
|
||||||
|
'cospriority': '10',
|
||||||
|
'krbmaxpwdlife': '90',
|
||||||
|
'krbminpwdlife': '1',
|
||||||
|
'krbpwdhistorylength': '8',
|
||||||
|
'krbpwdmindiffchars': '3',
|
||||||
|
'krbpwdminlength': '16',
|
||||||
|
'krbpwdmaxfailure': '6',
|
||||||
|
'krbpwdfailurecountinterval': '60',
|
||||||
|
'krbpwdlockoutduration': '600'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
changed = True
|
||||||
|
|
||||||
|
self._test_base(module_args, return_value, mock_calls, changed)
|
||||||
|
|
||||||
|
def test_mod_different_args(self):
|
||||||
|
"""Policy exists, but some of the args are different and need to be modified"""
|
||||||
|
module_args = {
|
||||||
|
'group': 'sysops',
|
||||||
|
'state': 'present',
|
||||||
|
'priority': '10',
|
||||||
|
'maxpwdlife': '60',
|
||||||
|
'minpwdlife': '24',
|
||||||
|
'historylength': '8',
|
||||||
|
'minclasses': '3',
|
||||||
|
'minlength': '12',
|
||||||
|
'maxfailcount': '8',
|
||||||
|
'failinterval': '60',
|
||||||
|
'lockouttime': '600'
|
||||||
|
}
|
||||||
|
return_value = {
|
||||||
|
'cn': ['sysops'],
|
||||||
|
'cospriority': ['10'],
|
||||||
|
'krbmaxpwdlife': ['90'],
|
||||||
|
'krbminpwdlife': ['1'],
|
||||||
|
'krbpwdhistorylength': ['8'],
|
||||||
|
'krbpwdmindiffchars': ['3'],
|
||||||
|
'krbpwdminlength': ['16'],
|
||||||
|
'krbpwdmaxfailure': ['6'],
|
||||||
|
'krbpwdfailurecountinterval': ['60'],
|
||||||
|
'krbpwdlockoutduration': ['600'],
|
||||||
|
'dn': 'cn=sysops,cn=EXAMPLE.COM,cn=kerberos,dc=example,dc=com',
|
||||||
|
'objectclass': ['top', 'nscontainer', 'krbpwdpolicy']
|
||||||
|
}
|
||||||
|
mock_calls = (
|
||||||
|
{
|
||||||
|
'method': 'pwpolicy_find',
|
||||||
|
'name': None,
|
||||||
|
'item': {
|
||||||
|
'all': True,
|
||||||
|
'cn': 'sysops'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'method': 'pwpolicy_mod',
|
||||||
|
'name': 'sysops',
|
||||||
|
'item': {
|
||||||
|
'cospriority': '10',
|
||||||
|
'krbmaxpwdlife': '60',
|
||||||
|
'krbminpwdlife': '24',
|
||||||
|
'krbpwdhistorylength': '8',
|
||||||
|
'krbpwdmindiffchars': '3',
|
||||||
|
'krbpwdminlength': '12',
|
||||||
|
'krbpwdmaxfailure': '8',
|
||||||
|
'krbpwdfailurecountinterval': '60',
|
||||||
|
'krbpwdlockoutduration': '600'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
changed = True
|
||||||
|
|
||||||
|
self._test_base(module_args, return_value, mock_calls, changed)
|
||||||
|
|
||||||
|
def test_mod_missing_args(self):
|
||||||
|
"""Policy exists, but some of the args aren't set, so need to be added"""
|
||||||
|
module_args = {
|
||||||
|
'group': 'sysops',
|
||||||
|
'state': 'present',
|
||||||
|
'priority': '10',
|
||||||
|
'maxpwdlife': '90',
|
||||||
|
'minpwdlife': '1',
|
||||||
|
'historylength': '8',
|
||||||
|
'minclasses': '3',
|
||||||
|
'minlength': '16',
|
||||||
|
'maxfailcount': '6',
|
||||||
|
'failinterval': '60',
|
||||||
|
'lockouttime': '600'
|
||||||
|
}
|
||||||
|
return_value = {
|
||||||
|
'cn': ['sysops'],
|
||||||
|
'cospriority': ['10'],
|
||||||
|
'krbmaxpwdlife': ['90'],
|
||||||
|
'krbpwdhistorylength': ['8'],
|
||||||
|
'krbpwdminlength': ['16'],
|
||||||
|
'krbpwdmaxfailure': ['6'],
|
||||||
|
'dn': 'cn=sysops,cn=EXAMPLE.COM,cn=kerberos,dc=example,dc=com',
|
||||||
|
'objectclass': ['top', 'nscontainer', 'krbpwdpolicy']
|
||||||
|
}
|
||||||
|
mock_calls = (
|
||||||
|
{
|
||||||
|
'method': 'pwpolicy_find',
|
||||||
|
'name': None,
|
||||||
|
'item': {
|
||||||
|
'all': True,
|
||||||
|
'cn': 'sysops'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'method': 'pwpolicy_mod',
|
||||||
|
'name': 'sysops',
|
||||||
|
'item': {
|
||||||
|
'cospriority': '10',
|
||||||
|
'krbmaxpwdlife': '90',
|
||||||
|
'krbminpwdlife': '1',
|
||||||
|
'krbpwdhistorylength': '8',
|
||||||
|
'krbpwdmindiffchars': '3',
|
||||||
|
'krbpwdminlength': '16',
|
||||||
|
'krbpwdmaxfailure': '6',
|
||||||
|
'krbpwdfailurecountinterval': '60',
|
||||||
|
'krbpwdlockoutduration': '600'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
changed = True
|
||||||
|
|
||||||
|
self._test_base(module_args, return_value, mock_calls, changed)
|
||||||
|
|
||||||
|
def test_del(self):
|
||||||
|
"""Policy exists, and state is absent. Needs to be deleted"""
|
||||||
|
module_args = {
|
||||||
|
'group': 'sysops',
|
||||||
|
'state': 'absent',
|
||||||
|
# other arguments are ignored when state is `absent`
|
||||||
|
'priority': '10',
|
||||||
|
'maxpwdlife': '90',
|
||||||
|
'historylength': '8',
|
||||||
|
'minlength': '16',
|
||||||
|
'maxfailcount': '6'
|
||||||
|
}
|
||||||
|
return_value = {
|
||||||
|
'cn': ['sysops'],
|
||||||
|
'cospriority': ['10'],
|
||||||
|
'krbmaxpwdlife': ['90'],
|
||||||
|
'krbpwdhistorylength': ['8'],
|
||||||
|
'krbpwdminlength': ['16'],
|
||||||
|
'krbpwdmaxfailure': ['6'],
|
||||||
|
'dn': 'cn=sysops,cn=EXAMPLE.COM,cn=kerberos,dc=example,dc=com',
|
||||||
|
'objectclass': ['top', 'nscontainer', 'krbpwdpolicy']
|
||||||
|
}
|
||||||
|
mock_calls = (
|
||||||
|
{
|
||||||
|
'method': 'pwpolicy_find',
|
||||||
|
'name': None,
|
||||||
|
'item': {
|
||||||
|
'all': True,
|
||||||
|
'cn': 'sysops'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'method': 'pwpolicy_del',
|
||||||
|
'name': 'sysops',
|
||||||
|
}
|
||||||
|
)
|
||||||
|
changed = True
|
||||||
|
|
||||||
|
self._test_base(module_args, return_value, mock_calls, changed)
|
||||||
|
|
||||||
|
def test_no_change(self):
|
||||||
|
"""Policy already exists. No changes needed"""
|
||||||
|
module_args = {
|
||||||
|
'group': 'admins',
|
||||||
|
'state': 'present',
|
||||||
|
'priority': '10',
|
||||||
|
'maxpwdlife': '90',
|
||||||
|
'minpwdlife': '1',
|
||||||
|
'historylength': '8',
|
||||||
|
'minclasses': '3',
|
||||||
|
'minlength': '16',
|
||||||
|
'maxfailcount': '6',
|
||||||
|
'failinterval': '60',
|
||||||
|
'lockouttime': '600'
|
||||||
|
}
|
||||||
|
return_value = {
|
||||||
|
'cn': ['admins'],
|
||||||
|
'cospriority': ['10'],
|
||||||
|
'krbmaxpwdlife': ['90'],
|
||||||
|
'krbminpwdlife': ['1'],
|
||||||
|
'krbpwdhistorylength': ['8'],
|
||||||
|
'krbpwdmindiffchars': ['3'],
|
||||||
|
'krbpwdminlength': ['16'],
|
||||||
|
'krbpwdmaxfailure': ['6'],
|
||||||
|
'krbpwdfailurecountinterval': ['60'],
|
||||||
|
'krbpwdlockoutduration': ['600'],
|
||||||
|
'dn': 'cn=admins,cn=EXAMPLE.COM,cn=kerberos,dc=example,dc=com',
|
||||||
|
'objectclass': ['top', 'nscontainer', 'krbpwdpolicy']
|
||||||
|
}
|
||||||
|
mock_calls = [
|
||||||
|
{
|
||||||
|
'method': 'pwpolicy_find',
|
||||||
|
'name': None,
|
||||||
|
'item': {
|
||||||
|
'all': True,
|
||||||
|
'cn': 'admins'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
changed = False
|
||||||
|
|
||||||
|
self._test_base(module_args, return_value, mock_calls, changed)
|
||||||
|
|
||||||
|
def test_del_no_change(self):
|
||||||
|
"""Policy doesn't exist, and state is absent. No change needed"""
|
||||||
|
module_args = {
|
||||||
|
'group': 'sysops',
|
||||||
|
'state': 'absent',
|
||||||
|
# other arguments are ignored when state is `absent`
|
||||||
|
'priority': '10',
|
||||||
|
'maxpwdlife': '90',
|
||||||
|
'historylength': '8',
|
||||||
|
'minlength': '16',
|
||||||
|
'maxfailcount': '6'
|
||||||
|
}
|
||||||
|
return_value = {}
|
||||||
|
mock_calls = [
|
||||||
|
{
|
||||||
|
'method': 'pwpolicy_find',
|
||||||
|
'name': None,
|
||||||
|
'item': {
|
||||||
|
'all': True,
|
||||||
|
'cn': 'sysops'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
changed = False
|
||||||
|
|
||||||
|
self._test_base(module_args, return_value, mock_calls, changed)
|
||||||
|
|
||||||
|
def test_global(self):
|
||||||
|
"""Modify the global policy"""
|
||||||
|
module_args = {
|
||||||
|
'maxpwdlife': '60',
|
||||||
|
'minpwdlife': '24',
|
||||||
|
'historylength': '8',
|
||||||
|
'minclasses': '3',
|
||||||
|
'minlength': '12',
|
||||||
|
'maxfailcount': '8',
|
||||||
|
'failinterval': '60',
|
||||||
|
'lockouttime': '600'
|
||||||
|
}
|
||||||
|
return_value = {
|
||||||
|
'cn': ['global_policy'],
|
||||||
|
'krbmaxpwdlife': ['90'],
|
||||||
|
'krbminpwdlife': ['1'],
|
||||||
|
'krbpwdmindiffchars': ['3'],
|
||||||
|
'krbpwdminlength': ['16'],
|
||||||
|
'krbpwdmaxfailure': ['6'],
|
||||||
|
'krbpwdfailurecountinterval': ['60'],
|
||||||
|
'krbpwdlockoutduration': ['600'],
|
||||||
|
'dn': 'cn=global_policy,cn=EXAMPLE.COM,cn=kerberos,dc=example,dc=com',
|
||||||
|
'objectclass': ['top', 'nscontainer', 'krbpwdpolicy']
|
||||||
|
}
|
||||||
|
mock_calls = (
|
||||||
|
{
|
||||||
|
'method': 'pwpolicy_find',
|
||||||
|
'name': None,
|
||||||
|
'item': {
|
||||||
|
'all': True,
|
||||||
|
'cn': 'global_policy'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'method': 'pwpolicy_mod',
|
||||||
|
'name': None,
|
||||||
|
'item': {
|
||||||
|
'krbmaxpwdlife': '60',
|
||||||
|
'krbminpwdlife': '24',
|
||||||
|
'krbpwdhistorylength': '8',
|
||||||
|
'krbpwdmindiffchars': '3',
|
||||||
|
'krbpwdminlength': '12',
|
||||||
|
'krbpwdmaxfailure': '8',
|
||||||
|
'krbpwdfailurecountinterval': '60',
|
||||||
|
'krbpwdlockoutduration': '600'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
changed = True
|
||||||
|
|
||||||
|
self._test_base(module_args, return_value, mock_calls, changed)
|
||||||
|
|
||||||
|
def test_global_no_change(self):
|
||||||
|
"""Global policy already matches the given arguments. No change needed"""
|
||||||
|
module_args = {
|
||||||
|
'maxpwdlife': '90',
|
||||||
|
'minpwdlife': '1',
|
||||||
|
'historylength': '8',
|
||||||
|
'minclasses': '3',
|
||||||
|
'minlength': '16',
|
||||||
|
'maxfailcount': '6',
|
||||||
|
'failinterval': '60',
|
||||||
|
'lockouttime': '600'
|
||||||
|
}
|
||||||
|
return_value = {
|
||||||
|
'cn': ['global_policy'],
|
||||||
|
'krbmaxpwdlife': ['90'],
|
||||||
|
'krbminpwdlife': ['1'],
|
||||||
|
'krbpwdhistorylength': ['8'],
|
||||||
|
'krbpwdmindiffchars': ['3'],
|
||||||
|
'krbpwdminlength': ['16'],
|
||||||
|
'krbpwdmaxfailure': ['6'],
|
||||||
|
'krbpwdfailurecountinterval': ['60'],
|
||||||
|
'krbpwdlockoutduration': ['600'],
|
||||||
|
'dn': 'cn=global_policy,cn=EXAMPLE.COM,cn=kerberos,dc=example,dc=com',
|
||||||
|
'objectclass': ['top', 'nscontainer', 'krbpwdpolicy']
|
||||||
|
}
|
||||||
|
mock_calls = [
|
||||||
|
{
|
||||||
|
'method': 'pwpolicy_find',
|
||||||
|
'name': None,
|
||||||
|
'item': {
|
||||||
|
'all': True,
|
||||||
|
'cn': 'global_policy'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
changed = False
|
||||||
|
|
||||||
|
self._test_base(module_args, return_value, mock_calls, changed)
|
||||||
|
|
||||||
|
def test_check_add(self):
|
||||||
|
"""Add a new policy in check mode. pwpolicy_add shouldn't be called"""
|
||||||
|
module_args = {
|
||||||
|
'_ansible_check_mode': True,
|
||||||
|
'group': 'admins',
|
||||||
|
'state': 'present',
|
||||||
|
'priority': '10',
|
||||||
|
'maxpwdlife': '90',
|
||||||
|
'minpwdlife': '1',
|
||||||
|
'historylength': '8',
|
||||||
|
'minclasses': '3',
|
||||||
|
'minlength': '16',
|
||||||
|
'maxfailcount': '6',
|
||||||
|
'failinterval': '60',
|
||||||
|
'lockouttime': '600'
|
||||||
|
}
|
||||||
|
return_value = {}
|
||||||
|
mock_calls = [
|
||||||
|
{
|
||||||
|
'method': 'pwpolicy_find',
|
||||||
|
'name': None,
|
||||||
|
'item': {
|
||||||
|
'all': True,
|
||||||
|
'cn': 'admins'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
changed = True
|
||||||
|
|
||||||
|
self._test_base(module_args, return_value, mock_calls, changed)
|
||||||
|
|
||||||
|
def test_check_mod(self):
|
||||||
|
"""Modify a policy in check mode. pwpolicy_mod shouldn't be called"""
|
||||||
|
module_args = {
|
||||||
|
'_ansible_check_mode': True,
|
||||||
|
'group': 'sysops',
|
||||||
|
'state': 'present',
|
||||||
|
'priority': '10',
|
||||||
|
'maxpwdlife': '60',
|
||||||
|
'minpwdlife': '24',
|
||||||
|
'historylength': '8',
|
||||||
|
'minclasses': '3',
|
||||||
|
'minlength': '12',
|
||||||
|
'maxfailcount': '8',
|
||||||
|
'failinterval': '60',
|
||||||
|
'lockouttime': '600'
|
||||||
|
}
|
||||||
|
return_value = {
|
||||||
|
'cn': ['sysops'],
|
||||||
|
'cospriority': ['10'],
|
||||||
|
'krbmaxpwdlife': ['90'],
|
||||||
|
'krbminpwdlife': ['1'],
|
||||||
|
'krbpwdhistorylength': ['8'],
|
||||||
|
'krbpwdmindiffchars': ['3'],
|
||||||
|
'krbpwdminlength': ['16'],
|
||||||
|
'krbpwdmaxfailure': ['6'],
|
||||||
|
'krbpwdfailurecountinterval': ['60'],
|
||||||
|
'krbpwdlockoutduration': ['600'],
|
||||||
|
'dn': 'cn=sysops,cn=EXAMPLE.COM,cn=kerberos,dc=example,dc=com',
|
||||||
|
'objectclass': ['top', 'nscontainer', 'krbpwdpolicy']
|
||||||
|
}
|
||||||
|
mock_calls = [
|
||||||
|
{
|
||||||
|
'method': 'pwpolicy_find',
|
||||||
|
'name': None,
|
||||||
|
'item': {
|
||||||
|
'all': True,
|
||||||
|
'cn': 'sysops'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
changed = True
|
||||||
|
|
||||||
|
self._test_base(module_args, return_value, mock_calls, changed)
|
||||||
|
|
||||||
|
def test_check_del(self):
|
||||||
|
"""Delete a policy in check mode. pwpolicy_del shouldn't be called"""
|
||||||
|
module_args = {
|
||||||
|
'_ansible_check_mode': True,
|
||||||
|
'group': 'sysops',
|
||||||
|
'state': 'absent'
|
||||||
|
}
|
||||||
|
return_value = {
|
||||||
|
'cn': ['sysops'],
|
||||||
|
'cospriority': ['10'],
|
||||||
|
'krbmaxpwdlife': ['90'],
|
||||||
|
'krbpwdhistorylength': ['8'],
|
||||||
|
'krbpwdminlength': ['16'],
|
||||||
|
'krbpwdmaxfailure': ['6'],
|
||||||
|
'dn': 'cn=sysops,cn=EXAMPLE.COM,cn=kerberos,dc=example,dc=com',
|
||||||
|
'objectclass': ['top', 'nscontainer', 'krbpwdpolicy']
|
||||||
|
}
|
||||||
|
mock_calls = [
|
||||||
|
{
|
||||||
|
'method': 'pwpolicy_find',
|
||||||
|
'name': None,
|
||||||
|
'item': {
|
||||||
|
'all': True,
|
||||||
|
'cn': 'sysops'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
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({
|
||||||
|
'group': 'admins',
|
||||||
|
'state': 'absent'
|
||||||
|
})
|
||||||
|
|
||||||
|
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()
|
Loading…
Reference in a new issue