#!/usr/bin/python
# Copyright: (c) 2017, 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

ANSIBLE_METADATA = {'metadata_version': '1.1',
                    'status': ['preview'],
                    'supported_by': 'community'}

DOCUMENTATION = '''
---
module: cyberark_authentication
short_description: Module for CyberArk Vault Authentication using PAS Web Services SDK
author: Edward Nunez @ CyberArk BizDev (@enunez-cyberark, @cyberark-bizdev, @erasmix)
version_added: 2.4
description:
    - Authenticates to CyberArk Vault using Privileged Account Security Web Services SDK and
      creates a session fact that can be used by other modules. It returns an Ansible fact
      called I(cyberark_session). Every module can use this fact as C(cyberark_session) parameter.


options:
    state:
        default: present
        choices: [present, absent]
        description:
            - Specifies if an authentication logon/logoff and a cyberark_session should be added/removed.
    username:
        description:
            - The name of the user who will logon to the Vault.
    password:
        description:
            - The password of the user.
    new_password:
        description:
            - The new password of the user. This parameter is optional, and enables you to change a password.
    api_base_url:
        description:
            - A string containing the base URL of the server hosting CyberArk's Privileged Account Security Web Services SDK.
    validate_certs:
        type: bool
        default: 'yes'
        description:
            - If C(false), SSL certificates will not be validated.  This should only
              set to C(false) used on personally controlled sites using self-signed
              certificates.
    use_shared_logon_authentication:
        type: bool
        default: 'no'
        description:
            - Whether or not Shared Logon Authentication will be used.
    use_radius_authentication:
        type: bool
        default: 'no'
        description:
            - Whether or not users will be authenticated via a RADIUS server. Valid values are true/false.
    cyberark_session:
        description:
            - Dictionary set by a CyberArk authentication containing the different values to perform actions on a logged-on CyberArk session.
'''

EXAMPLES = '''
- name: Logon to CyberArk Vault using PAS Web Services SDK - use_shared_logon_authentication
  cyberark_authentication:
    api_base_url: "{{ web_services_base_url }}"
    use_shared_logon_authentication: yes

- name: Logon to CyberArk Vault using PAS Web Services SDK - Not use_shared_logon_authentication
  cyberark_authentication:
    api_base_url: "{{ web_services_base_url }}"
    username: "{{ password_object.password }}"
    password: "{{ password_object.passprops.username }}"
    use_shared_logon_authentication: no

- name: Logoff from CyberArk Vault
  cyberark_authentication:
    state: absent
    cyberark_session: "{{ cyberark_session }}"
'''

RETURN = '''
cyberark_session:
    description: Authentication facts.
    returned: success
    type: dict
    sample:
        api_base_url:
            description: Base URL for API calls. Returned in the cyberark_session, so it can be used in subsequent calls.
            type: string
            returned: always
        token:
            description: The token that identifies the session, encoded in BASE 64.
            type: string
            returned: always
        use_shared_logon_authentication:
            description: Whether or not Shared Logon Authentication was used to establish the session.
            type: bool
            returned: always
        validate_certs:
            description: Whether or not SSL certificates should be validated.
            type: bool
            returned: always
'''

from ansible.module_utils._text import to_text
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.urls import open_url
from ansible.module_utils.six.moves.urllib.error import HTTPError
import json
try:
    import httplib
except ImportError:
    # Python 3
    import http.client as httplib


def processAuthentication(module):

    # Getting parameters from module

    api_base_url = module.params["api_base_url"]
    validate_certs = module.params["validate_certs"]
    username = module.params["username"]
    password = module.params["password"]
    new_password = module.params["new_password"]
    use_shared_logon_authentication = module.params[
        "use_shared_logon_authentication"]
    use_radius_authentication = module.params["use_radius_authentication"]
    state = module.params["state"]
    cyberark_session = module.params["cyberark_session"]

    # if in check mode it will not perform password changes
    if module.check_mode and new_password is not None:
        new_password = None

    # Defining initial values for open_url call
    headers = {'Content-Type': 'application/json'}
    payload = ""

    if state == "present":  # Logon Action

        # Different end_points based on the use of shared logon authentication
        if use_shared_logon_authentication:

            end_point = "/PasswordVault/WebServices/auth/Shared/RestfulAuthenticationService.svc/Logon"

        else:

            end_point = "/PasswordVault/WebServices/auth/Cyberark/CyberArkAuthenticationService.svc/Logon"

            # The payload will contain username, password
            # and optionally use_radius_authentication and new_password
            payload_dict = {"username": username, "password": password}

            if use_radius_authentication:
                payload_dict["useRadiusAuthentication"] = use_radius_authentication

            if new_password is not None:
                payload_dict["newPassword"] = new_password

            payload = json.dumps(payload_dict)

    else:  # Logoff Action

        # Get values from cyberark_session already established
        api_base_url = cyberark_session["api_base_url"]
        validate_certs = cyberark_session["validate_certs"]
        use_shared_logon_authentication = cyberark_session[
            "use_shared_logon_authentication"]
        headers["Authorization"] = cyberark_session["token"]

        # Different end_points based on the use of shared logon authentication
        if use_shared_logon_authentication:
            end_point = "/PasswordVault/WebServices/auth/Shared/RestfulAuthenticationService.svc/Logoff"
        else:
            end_point = "/PasswordVault/WebServices/auth/Cyberark/CyberArkAuthenticationService.svc/Logoff"

    result = None
    changed = False
    response = None

    try:

        response = open_url(
            api_base_url + end_point,
            method="POST",
            headers=headers,
            data=payload,
            validate_certs=validate_certs)

    except (HTTPError, httplib.HTTPException) as http_exception:

        module.fail_json(
            msg=("Error while performing authentication."
                 "Please validate parameters provided, and ability to logon to CyberArk."
                 "\n*** end_point=%s%s\n ==> %s" % (api_base_url, end_point, to_text(http_exception))),
            payload=payload,
            headers=headers,
            status_code=http_exception.code)

    except Exception as unknown_exception:

        module.fail_json(
            msg=("Unknown error while performing authentication."
                 "\n*** end_point=%s%s\n%s" % (api_base_url, end_point, to_text(unknown_exception))),
            payload=payload,
            headers=headers,
            status_code=-1)

    if response.getcode() == 200:  # Success

        if state == "present":  # Logon Action

            # Result token from REST Api uses a different key based
            # the use of shared logon authentication
            token = None
            try:
                if use_shared_logon_authentication:
                    token = json.loads(response.read())["LogonResult"]
                else:
                    token = json.loads(response.read())["CyberArkLogonResult"]
            except Exception as e:
                module.fail_json(
                    msg="Error obtaining token\n%s" % (to_text(e)),
                    payload=payload,
                    headers=headers,
                    status_code=-1)

            # Preparing result of the module
            result = {
                "cyberark_session": {
                    "token": token, "api_base_url": api_base_url, "validate_certs": validate_certs,
                    "use_shared_logon_authentication": use_shared_logon_authentication},
            }

            if new_password is not None:
                # Only marks change if new_password was received resulting
                # in a password change
                changed = True

        else:  # Logoff Action clears cyberark_session

            result = {
                "cyberark_session": {}
            }

        return (changed, result, response.getcode())

    else:
        module.fail_json(
            msg="error in end_point=>" +
            end_point,
            headers=headers)


def main():

    fields = {
        "api_base_url": {"type": "str"},
        "validate_certs": {"type": "bool",
                           "default": "true"},
        "username": {"type": "str"},
        "password": {"type": "str", "no_log": True},
        "new_password": {"type": "str", "no_log": True},
        "use_shared_logon_authentication": {"default": False, "type": "bool"},
        "use_radius_authentication": {"default": False, "type": "bool"},
        "state": {"type": "str",
                  "choices": ["present", "absent"],
                  "default": "present"},
        "cyberark_session": {"type": "dict"},
    }

    mutually_exclusive = [
        ["use_shared_logon_authentication", "use_radius_authentication"],
        ["use_shared_logon_authentication", "new_password"],
        ["api_base_url", "cyberark_session"],
        ["cyberark_session", "username", "use_shared_logon_authentication"]
    ]

    required_if = [
        ("state", "present", ["api_base_url"]),
        ("state", "absent", ["cyberark_session"])
    ]

    required_together = [
        ["username", "password"]
    ]

    module = AnsibleModule(
        argument_spec=fields,
        mutually_exclusive=mutually_exclusive,
        required_if=required_if,
        required_together=required_together,
        supports_check_mode=True)

    (changed, result, status_code) = processAuthentication(module)

    module.exit_json(
        changed=changed,
        ansible_facts=result,
        status_code=status_code)


if __name__ == '__main__':
    main()
