Blame plugins/module_utils/ansible_freeipa_module.py

Packit Service ee01e6
#!/usr/bin/python
Packit Service 0a38ef
# -*- coding: utf-8 -*-
Packit Service 0a38ef
Packit Service 0a38ef
# Authors:
Packit Service 0a38ef
#   Sergio Oliveira Campos <seocam@redhat.com>
Packit Service 0a38ef
#   Thomas Woerner <twoerner@redhat.com>
Packit Service 0a38ef
#
Packit Service 0a38ef
# Copyright (C) 2019  Red Hat
Packit Service 0a38ef
# see file 'COPYING' for use and warranty information
Packit Service 0a38ef
#
Packit Service 0a38ef
# This program is free software; you can redistribute it and/or modify
Packit Service 0a38ef
# it under the terms of the GNU General Public License as published by
Packit Service 0a38ef
# the Free Software Foundation, either version 3 of the License, or
Packit Service 0a38ef
# (at your option) any later version.
Packit Service 0a38ef
#
Packit Service 0a38ef
# This program is distributed in the hope that it will be useful,
Packit Service 0a38ef
# but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit Service 0a38ef
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Packit Service 0a38ef
# GNU General Public License for more details.
Packit Service 0a38ef
#
Packit Service 0a38ef
# You should have received a copy of the GNU General Public License
Packit Service 0a38ef
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
Packit Service 0a38ef
Packit Service 0a38ef
Packit Service a166ed
import sys
Packit Service a166ed
import operator
Packit Service 0a38ef
import os
Packit Service 0a38ef
import uuid
Packit Service 0a38ef
import tempfile
Packit Service 0a38ef
import shutil
Packit Service 0a38ef
import gssapi
Packit Service 0a38ef
from datetime import datetime
Packit Service 0a38ef
from pprint import pformat
Packit Service a166ed
Packit Service a166ed
try:
Packit Service a166ed
    from packaging import version
Packit Service a166ed
except ImportError:
Packit Service a166ed
    # If `packaging` not found, split version string for creating version
Packit Service a166ed
    # object. Although it is not PEP 440 compliant, it will work for stable
Packit Service a166ed
    # FreeIPA releases.
Packit Service a166ed
    import re
Packit Service a166ed
Packit Service a166ed
    class version:
Packit Service a166ed
        @staticmethod
Packit Service a166ed
        def parse(version_str):
Packit Service a166ed
            """
Packit Service a166ed
            Split a version string A.B.C, into a tuple.
Packit Service a166ed
Packit Service a166ed
            This will not work for `rc`, `dev` or similar version string.
Packit Service a166ed
            """
Packit Service a166ed
            return tuple(re.split("[-_\.]", version_str))  # noqa: W605
Packit Service a166ed
Packit Service 0a38ef
from ipalib import api
Packit Service 0a38ef
from ipalib import errors as ipalib_errors  # noqa
Packit Service 0a38ef
from ipalib.config import Env
Packit Service 0a38ef
from ipalib.constants import DEFAULT_CONFIG, LDAP_GENERALIZED_TIME_FORMAT
Packit Service 0a38ef
Packit Service 0a38ef
try:
Packit Service 0a38ef
    from ipalib.install.kinit import kinit_password, kinit_keytab
Packit Service 0a38ef
except ImportError:
Packit Service 0a38ef
    from ipapython.ipautil import kinit_password, kinit_keytab
Packit Service 0a38ef
from ipapython.ipautil import run
Packit Service 0a38ef
from ipapython.dn import DN
Packit Service a166ed
from ipapython.version import VERSION
Packit Service 0a38ef
from ipaplatform.paths import paths
Packit Service 0a38ef
from ipalib.krb_utils import get_credentials_if_valid
Packit Service 0a38ef
from ansible.module_utils.basic import AnsibleModule
Packit Service 0a38ef
from ansible.module_utils._text import to_text
Packit Service a166ed
from ansible.module_utils.common.text.converters import jsonify
Packit Service 0a38ef
Packit Service 0a38ef
try:
Packit Service 0a38ef
    from ipalib.x509 import Encoding
Packit Service 0a38ef
except ImportError:
Packit Service 0a38ef
    from cryptography.hazmat.primitives.serialization import Encoding
Packit Service 0a38ef
Packit Service 0a38ef
try:
Packit Service 0a38ef
    from ipalib.x509 import load_pem_x509_certificate
Packit Service 0a38ef
except ImportError:
Packit Service 0a38ef
    from ipalib.x509 import load_certificate
Packit Service 0a38ef
    load_pem_x509_certificate = None
Packit Service 0a38ef
Packit Service 0a38ef
import socket
Packit Service 0a38ef
import base64
Packit Service 0a38ef
import six
Packit Service 0a38ef
Packit Service 0a38ef
try:
Packit Service 0a38ef
    from collections.abc import Mapping  # noqa
Packit Service 0a38ef
except ImportError:
Packit Service 0a38ef
    from collections import Mapping  # noqa
Packit Service 0a38ef
Packit Service 0a38ef
Packit Service 0a38ef
if six.PY3:
Packit Service 0a38ef
    unicode = str
Packit Service 0a38ef
Packit Service 0a38ef
Packit Service 0a38ef
def valid_creds(module, principal):  # noqa
Packit Service 0a38ef
    """Get valid credentials matching the princial, try GSSAPI first."""
Packit Service 0a38ef
    if "KRB5CCNAME" in os.environ:
Packit Service 0a38ef
        ccache = os.environ["KRB5CCNAME"]
Packit Service 0a38ef
        module.debug('KRB5CCNAME set to %s' % ccache)
Packit Service 0a38ef
Packit Service 0a38ef
        try:
Packit Service 0a38ef
            cred = gssapi.Credentials(usage='initiate',
Packit Service 0a38ef
                                      store={'ccache': ccache})
Packit Service 0a38ef
        except gssapi.raw.misc.GSSError as e:
Packit Service 0a38ef
            module.fail_json(msg='Failed to find default ccache: %s' % e)
Packit Service 0a38ef
        else:
Packit Service 0a38ef
            module.debug("Using principal %s" % str(cred.name))
Packit Service 0a38ef
            return True
Packit Service 0a38ef
Packit Service 0a38ef
    elif "KRB5_CLIENT_KTNAME" in os.environ:
Packit Service 0a38ef
        keytab = os.environ.get('KRB5_CLIENT_KTNAME', None)
Packit Service 0a38ef
        module.debug('KRB5_CLIENT_KTNAME set to %s' % keytab)
Packit Service 0a38ef
Packit Service 0a38ef
        ccache_name = "MEMORY:%s" % str(uuid.uuid4())
Packit Service 0a38ef
        os.environ["KRB5CCNAME"] = ccache_name
Packit Service 0a38ef
Packit Service 0a38ef
        try:
Packit Service 0a38ef
            cred = kinit_keytab(principal, keytab, ccache_name)
Packit Service 0a38ef
        except gssapi.raw.misc.GSSError as e:
Packit Service 0a38ef
            module.fail_json(msg='Kerberos authentication failed : %s' % e)
Packit Service 0a38ef
        else:
Packit Service 0a38ef
            module.debug("Using principal %s" % str(cred.name))
Packit Service 0a38ef
            return True
Packit Service 0a38ef
Packit Service 0a38ef
    creds = get_credentials_if_valid()
Packit Service 0a38ef
    if creds and \
Packit Service 0a38ef
       creds.lifetime > 0 and \
Packit Service 0a38ef
       "%s@" % principal in creds.name.display_as(creds.name.name_type):
Packit Service 0a38ef
        return True
Packit Service 0a38ef
    return False
Packit Service 0a38ef
Packit Service 0a38ef
Packit Service 0a38ef
def temp_kinit(principal, password):
Packit Service 0a38ef
    """Kinit with password using a temporary ccache."""
Packit Service 0a38ef
    if not password:
Packit Service 0a38ef
        raise RuntimeError("The password is not set")
Packit Service 0a38ef
    if not principal:
Packit Service 0a38ef
        principal = "admin"
Packit Service 0a38ef
Packit Service 0a38ef
    ccache_dir = tempfile.mkdtemp(prefix='krbcc')
Packit Service 0a38ef
    ccache_name = os.path.join(ccache_dir, 'ccache')
Packit Service 0a38ef
Packit Service 0a38ef
    try:
Packit Service 0a38ef
        kinit_password(principal, password, ccache_name)
Packit Service 0a38ef
    except RuntimeError as e:
Packit Service 0a38ef
        raise RuntimeError("Kerberos authentication failed: {}".format(e))
Packit Service 0a38ef
Packit Service 0a38ef
    os.environ["KRB5CCNAME"] = ccache_name
Packit Service 0a38ef
    return ccache_dir, ccache_name
Packit Service 0a38ef
Packit Service 0a38ef
Packit Service 0a38ef
def temp_kdestroy(ccache_dir, ccache_name):
Packit Service 0a38ef
    """Destroy temporary ticket and remove temporary ccache."""
Packit Service 0a38ef
    if ccache_name is not None:
Packit Service 0a38ef
        run([paths.KDESTROY, '-c', ccache_name], raiseonerr=False)
Packit Service 0a38ef
        del os.environ['KRB5CCNAME']
Packit Service 0a38ef
    if ccache_dir is not None:
Packit Service 0a38ef
        shutil.rmtree(ccache_dir, ignore_errors=True)
Packit Service 0a38ef
Packit Service 0a38ef
Packit Service 0a38ef
def api_connect(context=None):
Packit Service 0a38ef
    """
Packit Service 0a38ef
    Initialize IPA API with the provided context.
Packit Service 0a38ef
Packit Service 0a38ef
    `context` can be any of:
Packit Service 0a38ef
        * `server` (default)
Packit Service 0a38ef
        * `ansible-freeipa`
Packit Service 0a38ef
        * `cli_installer`
Packit Service 0a38ef
    """
Packit Service 0a38ef
    env = Env()
Packit Service 0a38ef
    env._bootstrap()
Packit Service 0a38ef
    env._finalize_core(**dict(DEFAULT_CONFIG))
Packit Service 0a38ef
Packit Service 0a38ef
    # available contexts are 'server', 'ansible-freeipa' and 'cli_installer'
Packit Service 0a38ef
    if context is None:
Packit Service 0a38ef
        context = 'server'
Packit Service 0a38ef
Packit Service 0a38ef
    api.bootstrap(context=context, debug=env.debug, log=None)
Packit Service 0a38ef
    api.finalize()
Packit Service 0a38ef
Packit Service 0a38ef
    if api.env.in_server:
Packit Service 0a38ef
        backend = api.Backend.ldap2
Packit Service 0a38ef
    else:
Packit Service 0a38ef
        backend = api.Backend.rpcclient
Packit Service 0a38ef
Packit Service 0a38ef
    if not backend.isconnected():
Packit Service 0a38ef
        backend.connect(ccache=os.environ.get('KRB5CCNAME', None))
Packit Service 0a38ef
Packit Service 0a38ef
Packit Service 0a38ef
def api_command(module, command, name, args):
Packit Service 0a38ef
    """Call ipa.Command."""
Packit Service 0a38ef
    return api.Command[command](name, **args)
Packit Service 0a38ef
Packit Service 0a38ef
Packit Service 0a38ef
def api_command_no_name(module, command, args):
Packit Service 0a38ef
    """Call ipa.Command without a name."""
Packit Service 0a38ef
    return api.Command[command](**args)
Packit Service 0a38ef
Packit Service 0a38ef
Packit Service 0a38ef
def api_check_command(command):
Packit Service 0a38ef
    """Return if command exists in command list."""
Packit Service 0a38ef
    return command in api.Command
Packit Service 0a38ef
Packit Service 0a38ef
Packit Service 0a38ef
def api_check_param(command, name):
Packit Service 0a38ef
    """Check if param exists in command param list."""
Packit Service 0a38ef
    return name in api.Command[command].params
Packit Service 0a38ef
Packit Service 0a38ef
Packit Service a166ed
def api_check_ipa_version(oper, requested_version):
Packit Service a166ed
    """
Packit Service a166ed
    Compare the installed IPA version against a requested version.
Packit Service a166ed
Packit Service a166ed
    The valid operators are: <, <=, >, >=, ==, !=
Packit Service a166ed
    """
Packit Service a166ed
    oper_map = {
Packit Service a166ed
        "<": operator.lt,
Packit Service a166ed
        "<=": operator.le,
Packit Service a166ed
        ">": operator.gt,
Packit Service a166ed
        ">=": operator.ge,
Packit Service a166ed
        "==": operator.eq,
Packit Service a166ed
        "!=": operator.ne,
Packit Service a166ed
    }
Packit Service a166ed
    operation = oper_map.get(oper)
Packit Service a166ed
    if not(operation):
Packit Service a166ed
        raise NotImplementedError("Invalid operator: %s" % oper)
Packit Service a166ed
    return operation(version.parse(VERSION), version.parse(requested_version))
Packit Service a166ed
Packit Service a166ed
Packit Service 0a38ef
def execute_api_command(module, principal, password, command, name, args):
Packit Service 0a38ef
    """
Packit Service 0a38ef
    Execute an API command.
Packit Service 0a38ef
Packit Service 0a38ef
    Get KRB ticket if not already there, initialize api, connect,
Packit Service 0a38ef
    execute command and destroy ticket again if it has been created also.
Packit Service 0a38ef
    """
Packit Service 0a38ef
    ccache_dir = None
Packit Service 0a38ef
    ccache_name = None
Packit Service 0a38ef
    try:
Packit Service 0a38ef
        if not valid_creds(module, principal):
Packit Service 0a38ef
            ccache_dir, ccache_name = temp_kinit(principal, password)
Packit Service 0a38ef
        api_connect()
Packit Service 0a38ef
Packit Service 0a38ef
        return api_command(module, command, name, args)
Packit Service 0a38ef
    except Exception as e:
Packit Service 0a38ef
        module.fail_json(msg=str(e))
Packit Service 0a38ef
Packit Service 0a38ef
    finally:
Packit Service 0a38ef
        temp_kdestroy(ccache_dir, ccache_name)
Packit Service 0a38ef
Packit Service 0a38ef
Packit Service 0a38ef
def date_format(value):
Packit Service 0a38ef
    accepted_date_formats = [
Packit Service 0a38ef
        LDAP_GENERALIZED_TIME_FORMAT,  # generalized time
Packit Service 0a38ef
        '%Y-%m-%dT%H:%M:%SZ',  # ISO 8601, second precision
Packit Service 0a38ef
        '%Y-%m-%dT%H:%MZ',     # ISO 8601, minute precision
Packit Service 0a38ef
        '%Y-%m-%dZ',           # ISO 8601, date only
Packit Service 0a38ef
        '%Y-%m-%d %H:%M:%SZ',  # non-ISO 8601, second precision
Packit Service 0a38ef
        '%Y-%m-%d %H:%MZ',     # non-ISO 8601, minute precision
Packit Service 0a38ef
    ]
Packit Service 0a38ef
Packit Service 0a38ef
    for date_format in accepted_date_formats:
Packit Service 0a38ef
        try:
Packit Service 0a38ef
            return datetime.strptime(value, date_format)
Packit Service 0a38ef
        except ValueError:
Packit Service 0a38ef
            pass
Packit Service 0a38ef
    raise ValueError("Invalid date '%s'" % value)
Packit Service 0a38ef
Packit Service 0a38ef
Packit Service 0a38ef
def compare_args_ipa(module, args, ipa):  # noqa
Packit Service 0a38ef
    """Compare IPA obj attrs with the command args.
Packit Service 0a38ef
Packit Service 0a38ef
    This function compares IPA objects attributes with the args the
Packit Service 0a38ef
    module is intending to use to call a command. This is useful to know
Packit Service 0a38ef
    if call to IPA server will be needed or not.
Packit Service 0a38ef
    In other to compare we have to prepare the perform slight changes in
Packit Service 0a38ef
    data formats.
Packit Service 0a38ef
Packit Service 0a38ef
    Returns True if they are the same and False otherwise.
Packit Service 0a38ef
    """
Packit Service 0a38ef
    base_debug_msg = "Ansible arguments and IPA commands differed. "
Packit Service 0a38ef
Packit Service 0a38ef
    for key in args.keys():
Packit Service 0a38ef
        if key not in ipa:
Packit Service 0a38ef
            module.debug(
Packit Service 0a38ef
                base_debug_msg + "Command key not present in IPA: %s" % key
Packit Service 0a38ef
            )
Packit Service 0a38ef
            return False
Packit Service 0a38ef
        else:
Packit Service 0a38ef
            arg = args[key]
Packit Service 0a38ef
            ipa_arg = ipa[key]
Packit Service 0a38ef
            # If ipa_arg is a list and arg is not, replace arg
Packit Service 0a38ef
            # with list containing arg. Most args in a find result
Packit Service 0a38ef
            # are lists, but not all.
Packit Service 0a38ef
            if isinstance(ipa_arg, tuple):
Packit Service 0a38ef
                ipa_arg = list(ipa_arg)
Packit Service 0a38ef
            if isinstance(ipa_arg, list):
Packit Service 0a38ef
                if not isinstance(arg, list):
Packit Service 0a38ef
                    arg = [arg]
Packit Service 0a38ef
                if len(ipa_arg) != len(arg):
Packit Service 0a38ef
                    module.debug(
Packit Service 0a38ef
                        base_debug_msg
Packit Service 0a38ef
                        + "List length doesn't match for key %s: %d %d"
Packit Service 0a38ef
                        % (key, len(arg), len(ipa_arg),)
Packit Service 0a38ef
                    )
Packit Service 0a38ef
                    return False
Packit Service 0a38ef
                if isinstance(ipa_arg[0], str) and isinstance(arg[0], int):
Packit Service 0a38ef
                    arg = [to_text(_arg) for _arg in arg]
Packit Service 0a38ef
                if isinstance(ipa_arg[0], unicode) and isinstance(arg[0], int):
Packit Service 0a38ef
                    arg = [to_text(_arg) for _arg in arg]
Packit Service 0a38ef
            try:
Packit Service 0a38ef
                arg_set = set(arg)
Packit Service 0a38ef
                ipa_arg_set = set(ipa_arg)
Packit Service 0a38ef
            except TypeError:
Packit Service 0a38ef
                if arg != ipa_arg:
Packit Service 0a38ef
                    module.debug(
Packit Service 0a38ef
                        base_debug_msg
Packit Service 0a38ef
                        + "Different values: %s %s" % (arg, ipa_arg)
Packit Service 0a38ef
                    )
Packit Service 0a38ef
                    return False
Packit Service 0a38ef
            else:
Packit Service 0a38ef
                if arg_set != ipa_arg_set:
Packit Service 0a38ef
                    module.debug(
Packit Service 0a38ef
                        base_debug_msg
Packit Service 0a38ef
                        + "Different set content: %s %s"
Packit Service 0a38ef
                        % (arg_set, ipa_arg_set,)
Packit Service 0a38ef
                    )
Packit Service 0a38ef
                    return False
Packit Service 0a38ef
    return True
Packit Service 0a38ef
Packit Service 0a38ef
Packit Service 0a38ef
def _afm_convert(value):
Packit Service 0a38ef
    if value is not None:
Packit Service 0a38ef
        if isinstance(value, list):
Packit Service 0a38ef
            return [_afm_convert(x) for x in value]
Packit Service 0a38ef
        elif isinstance(value, dict):
Packit Service 0a38ef
            return {_afm_convert(k): _afm_convert(v) for k, v in value.items()}
Packit Service 0a38ef
        elif isinstance(value, str):
Packit Service 0a38ef
            return to_text(value)
Packit Service 0a38ef
        else:
Packit Service 0a38ef
            return value
Packit Service 0a38ef
    else:
Packit Service 0a38ef
        return value
Packit Service 0a38ef
Packit Service 0a38ef
Packit Service 0a38ef
def module_params_get(module, name):
Packit Service 0a38ef
    return _afm_convert(module.params.get(name))
Packit Service 0a38ef
Packit Service 0a38ef
Packit Service 0a38ef
def api_get_realm():
Packit Service 0a38ef
    return api.env.realm
Packit Service 0a38ef
Packit Service 0a38ef
Packit Service 0a38ef
def gen_add_del_lists(user_list, res_list):
Packit Service 0a38ef
    """Generate the lists for the addition and removal of members."""
Packit Service 0a38ef
    # The user list is None, therefore the parameter should not be touched
Packit Service 0a38ef
    if user_list is None:
Packit Service 0a38ef
        return [], []
Packit Service 0a38ef
Packit Service 0a38ef
    add_list = list(set(user_list or []) - set(res_list or []))
Packit Service 0a38ef
    del_list = list(set(res_list or []) - set(user_list or []))
Packit Service 0a38ef
Packit Service 0a38ef
    return add_list, del_list
Packit Service 0a38ef
Packit Service 0a38ef
Packit Service 0a38ef
def encode_certificate(cert):
Packit Service 0a38ef
    """
Packit Service 0a38ef
    Encode a certificate using base64.
Packit Service 0a38ef
Packit Service 0a38ef
    It also takes FreeIPA and Python versions into account.
Packit Service 0a38ef
    """
Packit Service 0a38ef
    if isinstance(cert, (str, unicode, bytes)):
Packit Service 0a38ef
        encoded = base64.b64encode(cert)
Packit Service 0a38ef
    else:
Packit Service 0a38ef
        encoded = base64.b64encode(cert.public_bytes(Encoding.DER))
Packit Service 0a38ef
    if not six.PY2:
Packit Service 0a38ef
        encoded = encoded.decode('ascii')
Packit Service 0a38ef
    return encoded
Packit Service 0a38ef
Packit Service 0a38ef
Packit Service 0a38ef
def load_cert_from_str(cert):
Packit Service 0a38ef
    cert = cert.strip()
Packit Service 0a38ef
    if not cert.startswith("-----BEGIN CERTIFICATE-----"):
Packit Service 0a38ef
        cert = "-----BEGIN CERTIFICATE-----\n" + cert
Packit Service 0a38ef
    if not cert.endswith("-----END CERTIFICATE-----"):
Packit Service 0a38ef
        cert += "\n-----END CERTIFICATE-----"
Packit Service 0a38ef
Packit Service 0a38ef
    if load_pem_x509_certificate is not None:
Packit Service 0a38ef
        cert = load_pem_x509_certificate(cert.encode('utf-8'))
Packit Service 0a38ef
    else:
Packit Service 0a38ef
        cert = load_certificate(cert.encode('utf-8'))
Packit Service 0a38ef
    return cert
Packit Service 0a38ef
Packit Service 0a38ef
Packit Service 0a38ef
def DN_x500_text(text):
Packit Service 0a38ef
    if hasattr(DN, "x500_text"):
Packit Service 0a38ef
        return DN(text).x500_text()
Packit Service 0a38ef
    else:
Packit Service 0a38ef
        # Emulate x500_text
Packit Service 0a38ef
        dn = DN(text)
Packit Service 0a38ef
        dn.rdns = reversed(dn.rdns)
Packit Service 0a38ef
        return str(dn)
Packit Service 0a38ef
Packit Service 0a38ef
Packit Service 0a38ef
def is_valid_port(port):
Packit Service 0a38ef
    if not isinstance(port, int):
Packit Service 0a38ef
        return False
Packit Service 0a38ef
Packit Service 0a38ef
    if 1 <= port <= 65535:
Packit Service 0a38ef
        return True
Packit Service 0a38ef
Packit Service 0a38ef
    return False
Packit Service 0a38ef
Packit Service 0a38ef
Packit Service 0a38ef
def is_ipv4_addr(ipaddr):
Packit Service 0a38ef
    """Test if given IP address is a valid IPv4 address."""
Packit Service 0a38ef
    try:
Packit Service 0a38ef
        socket.inet_pton(socket.AF_INET, ipaddr)
Packit Service 0a38ef
    except socket.error:
Packit Service 0a38ef
        return False
Packit Service 0a38ef
    return True
Packit Service 0a38ef
Packit Service 0a38ef
Packit Service 0a38ef
def is_ipv6_addr(ipaddr):
Packit Service 0a38ef
    """Test if given IP address is a valid IPv6 address."""
Packit Service 0a38ef
    try:
Packit Service 0a38ef
        socket.inet_pton(socket.AF_INET6, ipaddr)
Packit Service 0a38ef
    except socket.error:
Packit Service 0a38ef
        return False
Packit Service 0a38ef
    return True
Packit Service 0a38ef
Packit Service 0a38ef
Packit Service a166ed
def exit_raw_json(module, **kwargs):
Packit Service a166ed
    """
Packit Service a166ed
    Print the raw parameters in JSON format, without masking.
Packit Service a166ed
Packit Service a166ed
    Due to Ansible filtering out values in the output that match values
Packit Service a166ed
    in variables which has `no_log` set, if a module need to return user
Packit Service a166ed
    defined dato to the controller, it cannot rely on
Packit Service a166ed
    AnsibleModule.exit_json, as there is a chance that a partial match may
Packit Service a166ed
    occur, masking the data returned.
Packit Service a166ed
Packit Service a166ed
    This method is a replacement for AnsibleModule.exit_json. It has
Packit Service a166ed
    nearly the same implementation as exit_json, but does not filter
Packit Service a166ed
    data. Beware that this data will be logged by Ansible, and if it
Packit Service a166ed
    contains sensible data, it will be appear in the logs.
Packit Service a166ed
    """
Packit Service a166ed
    module.do_cleanup_files()
Packit Service a166ed
    print(jsonify(kwargs))
Packit Service a166ed
    sys.exit(0)
Packit Service a166ed
Packit Service a166ed
Packit Service 0a38ef
class AnsibleFreeIPAParams(Mapping):
Packit Service 0a38ef
    def __init__(self, ansible_module):
Packit Service 0a38ef
        self.mapping = ansible_module.params
Packit Service 0a38ef
        self.ansible_module = ansible_module
Packit Service 0a38ef
Packit Service 0a38ef
    def __getitem__(self, key):
Packit Service 0a38ef
        param = self.mapping[key]
Packit Service 0a38ef
        if param is not None:
Packit Service 0a38ef
            return _afm_convert(param)
Packit Service 0a38ef
Packit Service 0a38ef
    def __iter__(self):
Packit Service 0a38ef
        return iter(self.mapping)
Packit Service 0a38ef
Packit Service 0a38ef
    def __len__(self):
Packit Service 0a38ef
        return len(self.mapping)
Packit Service 0a38ef
Packit Service 0a38ef
    @property
Packit Service 0a38ef
    def names(self):
Packit Service 0a38ef
        return self.name
Packit Service 0a38ef
Packit Service 0a38ef
    def __getattr__(self, name):
Packit Service 0a38ef
        return self.get(name)
Packit Service 0a38ef
Packit Service 0a38ef
Packit Service 0a38ef
class FreeIPABaseModule(AnsibleModule):
Packit Service 0a38ef
    """
Packit Service 0a38ef
    Base class for FreeIPA Ansible modules.
Packit Service 0a38ef
Packit Service 0a38ef
    Provides methods useful methods to be used by our modules.
Packit Service 0a38ef
Packit Service 0a38ef
    This class should be overriten and instantiated for the module.
Packit Service 0a38ef
    A basic implementation of an Ansible FreeIPA module expects its
Packit Service 0a38ef
    class to:
Packit Service 0a38ef
Packit Service 0a38ef
    1. Define a class attribute ``ipa_param_mapping``
Packit Service 0a38ef
    2. Implement the method ``define_ipa_commands()``
Packit Service 0a38ef
    3. Implement the method ``check_ipa_params()`` (optional)
Packit Service 0a38ef
Packit Service 0a38ef
    After instantiating the class the method ``ipa_run()`` should be called.
Packit Service 0a38ef
Packit Service 0a38ef
    Example (ansible-freeipa/plugins/modules/ipasomemodule.py):
Packit Service 0a38ef
Packit Service 0a38ef
    class SomeIPAModule(FreeIPABaseModule):
Packit Service 0a38ef
        ipa_param_mapping = {
Packit Service 0a38ef
            "arg_to_be_passed_to_ipa_command": "module_param",
Packit Service 0a38ef
            "another_arg": "get_another_module_param",
Packit Service 0a38ef
        }
Packit Service 0a38ef
Packit Service 0a38ef
        def get_another_module_param(self):
Packit Service 0a38ef
            another_module_param = self.ipa_params.another_module_param
Packit Service 0a38ef
            # Validate or modify another_module_param
Packit Service 0a38ef
            # ...
Packit Service 0a38ef
            return another_module_param
Packit Service 0a38ef
Packit Service 0a38ef
        def check_ipa_params(self):
Packit Service 0a38ef
            # Validate your params here
Packit Service 0a38ef
            # Example:
Packit Service 0a38ef
            if not self.ipa_params.module_param in VALID_OPTIONS:
Packit Service 0a38ef
                self.fail_json(msg="Invalid value for argument module_param")
Packit Service 0a38ef
Packit Service 0a38ef
        def define_ipa_commands(self):
Packit Service 0a38ef
            args = self.get_ipa_command_args()
Packit Service 0a38ef
Packit Service 0a38ef
            self.add_ipa_command(
Packit Service 0a38ef
                "some_ipa_command",
Packit Service 0a38ef
                name="obj-name",
Packit Service 0a38ef
                args=args,
Packit Service 0a38ef
            )
Packit Service 0a38ef
Packit Service 0a38ef
    def main():
Packit Service 0a38ef
        ipa_module = SomeIPAModule(argument_spec=dict(
Packit Service 0a38ef
            module_param=dict(
Packit Service 0a38ef
                type="str",
Packit Service 0a38ef
                default=None,
Packit Service 0a38ef
                required=False,
Packit Service 0a38ef
            ),
Packit Service 0a38ef
            another_module_param=dict(
Packit Service 0a38ef
                type="str",
Packit Service 0a38ef
                default=None,
Packit Service 0a38ef
                required=False,
Packit Service 0a38ef
            ),
Packit Service 0a38ef
        ))
Packit Service 0a38ef
        ipa_module.ipa_run()
Packit Service 0a38ef
Packit Service 0a38ef
    if __name__ == "__main__":
Packit Service 0a38ef
        main()
Packit Service 0a38ef
Packit Service 0a38ef
    """
Packit Service 0a38ef
Packit Service 0a38ef
    ipa_param_mapping = None
Packit Service 0a38ef
Packit Service 0a38ef
    def __init__(self, *args, **kwargs):
Packit Service 0a38ef
        super(FreeIPABaseModule, self).__init__(*args, **kwargs)
Packit Service 0a38ef
Packit Service 0a38ef
        # Attributes to store kerberos credentials (if needed)
Packit Service 0a38ef
        self.ccache_dir = None
Packit Service 0a38ef
        self.ccache_name = None
Packit Service 0a38ef
Packit Service 0a38ef
        # Status of an execution. Will be changed to True
Packit Service 0a38ef
        #   if something is actually peformed.
Packit Service 0a38ef
        self.changed = False
Packit Service 0a38ef
Packit Service 0a38ef
        # Status of the connection with the IPA server.
Packit Service 0a38ef
        # We need to know if the connection was actually stablished
Packit Service 0a38ef
        #   before we start sending commands.
Packit Service 0a38ef
        self.ipa_connected = False
Packit Service 0a38ef
Packit Service 0a38ef
        # Commands to be executed
Packit Service 0a38ef
        self.ipa_commands = []
Packit Service 0a38ef
Packit Service 0a38ef
        # Module exit arguments.
Packit Service 0a38ef
        self.exit_args = {}
Packit Service 0a38ef
Packit Service 0a38ef
        # Wrapper around the AnsibleModule.params.
Packit Service 0a38ef
        # Return the actual params but performing transformations
Packit Service 0a38ef
        #   when needed.
Packit Service 0a38ef
        self.ipa_params = AnsibleFreeIPAParams(self)
Packit Service 0a38ef
Packit Service a166ed
    def get_ipa_command_args(self, **kwargs):
Packit Service 0a38ef
        """
Packit Service 0a38ef
        Return a dict to be passed to an IPA command.
Packit Service 0a38ef
Packit Service 0a38ef
        The keys of ``ipa_param_mapping`` are also the keys of the return dict.
Packit Service 0a38ef
Packit Service 0a38ef
        The values of ``ipa_param_mapping`` needs to be either:
Packit Service 0a38ef
            * A str with the name of a defined method; or
Packit Service 0a38ef
            * A key of ``AnsibleModule.param``.
Packit Service 0a38ef
Packit Service 0a38ef
        In case of a method the return of the method will be set as value
Packit Service 0a38ef
        for the return dict.
Packit Service 0a38ef
Packit Service 0a38ef
        In case of a AnsibleModule.param the value of the param will be
Packit Service 0a38ef
        set in the return dict. In addition to that boolean values will be
Packit Service 0a38ef
        automaticaly converted to uppercase strings (as required by FreeIPA
Packit Service 0a38ef
        server).
Packit Service 0a38ef
Packit Service 0a38ef
        """
Packit Service 0a38ef
        args = {}
Packit Service 0a38ef
        for ipa_param_name, param_name in self.ipa_param_mapping.items():
Packit Service 0a38ef
Packit Service 0a38ef
            # Check if param_name is actually a param
Packit Service 0a38ef
            if param_name in self.ipa_params:
Packit Service 0a38ef
                value = self.ipa_params.get(param_name)
Packit Service 0a38ef
                if isinstance(value, bool):
Packit Service 0a38ef
                    value = "TRUE" if value else "FALSE"
Packit Service 0a38ef
Packit Service 0a38ef
            # Since param wasn't a param check if it's a method name
Packit Service 0a38ef
            elif hasattr(self, param_name):
Packit Service 0a38ef
                method = getattr(self, param_name)
Packit Service 0a38ef
                if callable(method):
Packit Service a166ed
                    value = method(**kwargs)
Packit Service 0a38ef
Packit Service 0a38ef
            # We don't have a way to guess the value so fail.
Packit Service 0a38ef
            else:
Packit Service 0a38ef
                self.fail_json(
Packit Service 0a38ef
                    msg=(
Packit Service 0a38ef
                        "Couldn't get a value for '%s'. Option '%s' is not "
Packit Service 0a38ef
                        "a module argument neither a defined method."
Packit Service 0a38ef
                    )
Packit Service 0a38ef
                    % (ipa_param_name, param_name)
Packit Service 0a38ef
                )
Packit Service 0a38ef
Packit Service 0a38ef
            if value is not None:
Packit Service 0a38ef
                args[ipa_param_name] = value
Packit Service 0a38ef
Packit Service 0a38ef
        return args
Packit Service 0a38ef
Packit Service 0a38ef
    def check_ipa_params(self):
Packit Service 0a38ef
        """Validate ipa_params before command is called."""
Packit Service 0a38ef
        pass
Packit Service 0a38ef
Packit Service 0a38ef
    def define_ipa_commands(self):
Packit Service 0a38ef
        """Define commands that will be run in IPA server."""
Packit Service 0a38ef
        raise NotImplementedError
Packit Service 0a38ef
Packit Service 0a38ef
    def api_command(self, command, name=None, args=None):
Packit Service 0a38ef
        """Execute a single command in IPA server."""
Packit Service 0a38ef
        if args is None:
Packit Service 0a38ef
            args = {}
Packit Service 0a38ef
Packit Service 0a38ef
        if name is None:
Packit Service 0a38ef
            return api_command_no_name(self, command, args)
Packit Service 0a38ef
Packit Service 0a38ef
        return api_command(self, command, name, args)
Packit Service 0a38ef
Packit Service 0a38ef
    def __enter__(self):
Packit Service 0a38ef
        """
Packit Service 0a38ef
        Connect to IPA server.
Packit Service 0a38ef
Packit Service 0a38ef
        Check the there are working Kerberos credentials to connect to
Packit Service 0a38ef
        IPA server. If there are not we perform a temporary kinit
Packit Service 0a38ef
        that will be terminated when exiting the context.
Packit Service 0a38ef
Packit Service 0a38ef
        If the connection fails ``ipa_connected`` attribute will be set
Packit Service 0a38ef
        to False.
Packit Service 0a38ef
        """
Packit Service 0a38ef
        principal = self.ipa_params.ipaadmin_principal
Packit Service 0a38ef
        password = self.ipa_params.ipaadmin_password
Packit Service 0a38ef
Packit Service 0a38ef
        try:
Packit Service 0a38ef
            if not valid_creds(self, principal):
Packit Service 0a38ef
                self.ccache_dir, self.ccache_name = temp_kinit(
Packit Service 0a38ef
                    principal, password,
Packit Service 0a38ef
                )
Packit Service 0a38ef
Packit Service 0a38ef
            api_connect()
Packit Service 0a38ef
Packit Service 0a38ef
        except Exception as excpt:
Packit Service 0a38ef
            self.fail_json(msg=str(excpt))
Packit Service 0a38ef
        else:
Packit Service 0a38ef
            self.ipa_connected = True
Packit Service 0a38ef
Packit Service 0a38ef
        return self
Packit Service 0a38ef
Packit Service 0a38ef
    def __exit__(self, exc_type, exc_val, exc_tb):
Packit Service 0a38ef
        """
Packit Service 0a38ef
        Terminate a connection with the IPA server.
Packit Service 0a38ef
Packit Service 0a38ef
        Deal with exceptions, destroy temporary kinit credentials and
Packit Service 0a38ef
        exit the module with proper arguments.
Packit Service 0a38ef
Packit Service 0a38ef
        """
Packit Service 5b5096
        # TODO: shouldn't we also disconnect from api backend?
Packit Service 5b5096
        temp_kdestroy(self.ccache_dir, self.ccache_name)
Packit Service 5b5096
Packit Service a166ed
        if exc_type == SystemExit:
Packit Service a166ed
            raise
Packit Service a166ed
Packit Service a166ed
        if exc_val:
Packit Service a166ed
            self.fail_json(msg=str(exc_val))
Packit Service a166ed
Packit Service a166ed
        self.exit_json(changed=self.changed, **self.exit_args)
Packit Service 0a38ef
Packit Service 0a38ef
    def get_command_errors(self, command, result):
Packit Service 0a38ef
        """Look for erros into command results."""
Packit Service 0a38ef
        # Get all errors
Packit Service 0a38ef
        # All "already a member" and "not a member" failures in the
Packit Service 0a38ef
        # result are ignored. All others are reported.
Packit Service 0a38ef
        errors = []
Packit Service 0a38ef
        for item in result.get("failed", tuple()):
Packit Service 0a38ef
            failed_item = result["failed"][item]
Packit Service 0a38ef
            for member_type in failed_item:
Packit Service 0a38ef
                for member, failure in failed_item[member_type]:
Packit Service 0a38ef
                    if (
Packit Service 0a38ef
                        "already a member" in failure
Packit Service 0a38ef
                        or "not a member" in failure
Packit Service 0a38ef
                    ):
Packit Service 0a38ef
                        continue
Packit Service 0a38ef
                    errors.append(
Packit Service 0a38ef
                        "%s: %s %s: %s"
Packit Service 0a38ef
                        % (command, member_type, member, failure)
Packit Service 0a38ef
                    )
Packit Service 0a38ef
Packit Service 0a38ef
        if len(errors) > 0:
Packit Service 0a38ef
            self.fail_json(", ".join("errors"))
Packit Service 0a38ef
Packit Service 0a38ef
    def add_ipa_command(self, command, name=None, args=None):
Packit Service 0a38ef
        """Add a command to the list of commands to be executed."""
Packit Service 0a38ef
        self.ipa_commands.append((name, command, args or {}))
Packit Service 0a38ef
Packit Service 0a38ef
    def _run_ipa_commands(self):
Packit Service 0a38ef
        """Execute commands in self.ipa_commands."""
Packit Service 0a38ef
        result = None
Packit Service 0a38ef
Packit Service 0a38ef
        for name, command, args in self.ipa_commands:
Packit Service 0a38ef
            try:
Packit Service 0a38ef
                result = self.api_command(command, name, args)
Packit Service 0a38ef
            except Exception as excpt:
Packit Service 0a38ef
                self.fail_json(msg="%s: %s: %s" % (command, name, str(excpt)))
Packit Service 0a38ef
            else:
Packit Service a166ed
                self.process_command_result(name, command, args, result)
Packit Service 5b5096
            self.get_command_errors(command, result)
Packit Service 2939af
Packit Service a166ed
    def process_command_result(self, name, command, args, result):
Packit Service a166ed
        """
Packit Service a166ed
        Process an API command result.
Packit Service a166ed
Packit Service a166ed
        This method can be overriden in subclasses, and change self.exit_values
Packit Service a166ed
        to return data in the result for the controller.
Packit Service a166ed
        """
Packit Service a166ed
        if "completed" in result:
Packit Service a166ed
            if result["completed"] > 0:
Packit Service a166ed
                self.changed = True
Packit Service a166ed
        else:
Packit Service a166ed
            self.changed = True
Packit Service a166ed
Packit Service 0a38ef
    def require_ipa_attrs_change(self, command_args, ipa_attrs):
Packit Service 0a38ef
        """
Packit Service 0a38ef
        Compare given args with current object attributes.
Packit Service 0a38ef
Packit Service 0a38ef
        Returns True in case current IPA object attributes differ from
Packit Service 0a38ef
        args passed to the module.
Packit Service 0a38ef
        """
Packit Service 0a38ef
        equal = compare_args_ipa(self, command_args, ipa_attrs)
Packit Service 0a38ef
        return not equal
Packit Service 0a38ef
Packit Service 0a38ef
    def pdebug(self, value):
Packit Service 0a38ef
        """Debug with pretty formatting."""
Packit Service 0a38ef
        self.debug(pformat(value))
Packit Service 0a38ef
Packit Service 0a38ef
    def ipa_run(self):
Packit Service 0a38ef
        """Execute module actions."""
Packit Service 0a38ef
        with self:
Packit Service 0a38ef
            if not self.ipa_connected:
Packit Service 0a38ef
                return
Packit Service 0a38ef
Packit Service 0a38ef
            self.check_ipa_params()
Packit Service 0a38ef
            self.define_ipa_commands()
Packit Service 0a38ef
            self._run_ipa_commands()