#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright (c) 2020, Ansible Project
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later

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
attributes:
    check_mode:
        support: full
    diff_mode:
        support: none
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
  - community.general.attributes
'''

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.common.text.converters 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()
