#
"""
     Copyright (c) 2016 World Wide Technology, Inc.
     All rights reserved.

     Revision history:
     28 March 2016  |  1.0 - initial release
     29 March 2016  |  1.1 - comments and style modifications
     30 March 2016  |  1.2 - documentation update
     31 March 2016  |  1.3 - password 'data type' should be password, not string
                             reformatted debug output
     14 June  2016  |  2.0 - cyber5 branch, new F5 icontrol_install_config module

     module: F5_connector.py
     author: Joel W. King, World Wide Technology
     short_description: This Phantom app supports containment actions like 'block ip' or 'unblock ip' on an F5 BIG-IP appliance.

    remarks: The appdev tutorial is at  https://<phantom IP>/docs/appdev/tutorial

             ssh phantom@<phantom IP>
             export PYTHONPATH=/opt/phantom/lib/:/opt/phantom/www/
             export REQUESTS_CA_BUNDLE=/opt/phantom/etc/cacerts.pem
             cd ./app_dev/f5_firewall
             touch __init__.py
             ../compile_app.py -i
             python2.7 ./F5_connector.py ./test_jsons/test.json

"""
#
# Phantom App imports
#
import phantom.app as phantom
from phantom.base_connector import BaseConnector
from phantom.action_result import ActionResult
#
#  system imports
#
import simplejson as json
import time
import httplib
#
#  application imports
#
import icontrol_install_config as iControl                 # https://github.com/joelwking/ansible-f5/blob/master/icontrol_install_config.py
try:
    from F5_connector_consts import *                      # file name would be ./F5_connector_consts.py
except ImportError:
    pass                                                   # this is an optional file, used to bring in constants

# ========================================================
# AppConnector
# ========================================================


class F5_Connector(BaseConnector):
    " "
    BANNER = "F5"
    HEADER = {"Content-Type": "application/json"}

    def initialize(self):
        """
        This is an optional function that can be implemented by the AppConnector derived class. Since the configuration
        dictionary is already validated by the time this function is called, it's a good place to do any extra initialization
        of any internal modules. This function MUST return a value of either phantom.APP_SUCCESS or phantom.APP_ERROR.
        If this function returns phantom.APP_ERROR, then AppConnector::handle_action will not get called.
        """
        self.debug_print("%s INITIALIZE %s" % (F5_Connector.BANNER, time.asctime()))
        return phantom.APP_SUCCESS

    def finalize(self):
        """
        This function gets called once all the param dictionary elements are looped over and no more handle_action calls
        are left to be made. It gives the AppConnector a chance to loop through all the results that were accumulated by
        multiple handle_action function calls and create any summary if required. Another usage is cleanup, disconnect
        from remote devices etc.
        """
        self.debug_print("%s FINALIZE" % F5_Connector.BANNER)
        return

    def handle_exception(self, exception_object):
        """
        All the code within BaseConnector::_handle_action is within a 'try: except:' clause. Thus if an exception occurs
        during the execution of this code it is caught at a single place. The resulting exception object is passed to the
        AppConnector::handle_exception() to do any cleanup of it's own if required. This exception is then added to the
        connector run result and passed back to spawn, which gets displayed in the Phantom UI.
        """
        self.debug_print("%s HANDLE_EXCEPTION %s" % (F5_Connector.BANNER, exception_object))
        return

    def _test_connectivity(self, param):
        """
        Called when the user depresses the test connectivity button on the Phantom UI.
        Use a basic query to determine if the IP address, username and password is correct,
            curl -k -u admin:redacted -X GET https://192.0.2.1/mgmt/tm/ltm/
        """
        self.debug_print("%s TEST_CONNECTIVITY %s" % (F5_Connector.BANNER, param))

        config = self.get_config()
        host = config.get("device")
        F5 = iControl.BIG_IP(host=host,
                    username=config.get("username"),
                    password=config.get("password"),
                    uri="/mgmt/tm/sys/software/image",
                    method="GET")
        msg = "test connectivity to %s status_code: " % host

        if F5.genericGET():
            # True is success
            return self.set_status_save_progress(phantom.APP_SUCCESS, msg + "%s %s" % (F5.status_code, httplib.responses[F5.status_code]))
        else:
            # None or False, is a failure based on incorrect IP address, username, passords
            return self.set_status_save_progress(phantom.APP_ERROR, msg + "%s %s" % (F5.status_code, F5.response))

    def handle_action(self, param):
        """
        This function implements the main functionality of the AppConnector. It gets called for every param dictionary element
        in the parameters array. In it's simplest form it gets the current action identifier and then calls a member function
        of it's own to handle the action. This function is expected to create the results of the action run that get added
        to the connector run. The return value of this function is mostly ignored by the BaseConnector. Instead it will
        just loop over the next param element in the parameters array and call handle_action again.

        We create a case structure in Python to allow for any number of actions to be easily added.
        """

        action_id = self.get_action_identifier()           # action_id determines what function to execute
        self.debug_print("%s HANDLE_ACTION action_id:%s parameters:\n%s" % (F5_Connector.BANNER, action_id, param))

        supported_actions = {"test connectivity": self._test_connectivity,
                            "block ip": self.block_ip,
                            "unblock ip": self.unblock_ip}

        run_action = supported_actions[action_id]

        return run_action(param)

    def unblock_ip(self, param):
        """
        Allow the IP address by deleting the rule which originally blocked the source IP address.

        URL https://10.255.111.100/mgmt/tm/security/firewall/policy/~Common~Phantom_Inbound/rules/sourceIP_8.8.8.8
        """

        config = self.get_config()
        self.debug_print("%s UNBLOCK_IP parameters:\n%s \nconfig:%s" % (F5_Connector.BANNER, param, config))

        action_result = ActionResult(dict(param))          # Add an action result to the App Run
        self.add_action_result(action_result)

        URL = "/mgmt/tm/security/firewall/policy/~%s~%s/rules/%s" % (param["partition"], param["policy"], param["rule name"])
        self.debug_print("%s UNBLOCK_IP URL: %s" % (F5_Connector.BANNER, URL))

        F5 = iControl.BIG_IP(host=config.get("device"),
                             username=config.get("username"),
                             password=config.get("password"),
                             uri=URL,
                             method="DELETE")

        if F5.genericDELETE():
            action_result.set_status(phantom.APP_SUCCESS)
        else:
            action_result.set_status(phantom.APP_ERROR)

        action_result.add_data(F5.response)
        self.debug_print("%s UNBLOCK_IP code: %s \nresponse: %s" % (F5_Connector.BANNER, F5.status_code, F5.response))
        return

    def block_ip(self, param):
        """
        Block a source IP address,  a simple call to update a security policy in place.
        The firewall policy is called "Phantom_Inbound" which currently is tied to an inbound VIP in the "Common" partition.

        POST
        URL https://10.255.111.100/mgmt/tm/security/firewall/policy/~Common~Phantom_Inbound/rules
        body {"name":"DYNAMIC_BLOCK_NAME","action":"reject","place-after":"first","source":{"addresses":[{"name": "8.8.8.8/32"}"

        """
        config = self.get_config()
        self.debug_print("%s BLOCK_IP parameters:\n%s \nconfig:%s" % (F5_Connector.BANNER, param, config))

        action_result = ActionResult(dict(param))          # Add an action result to the App Run
        self.add_action_result(action_result)

        URL = "/mgmt/tm/security/firewall/policy/~%s~%s/rules" % (param["partition"], param["policy"])
        body = '{"name":"%s","action":"%s","place-after":"first","source":{"addresses":[{"name":"%s/32"}]}}' \
               % (param["rule name"], param["action"], param["source"])

        self.debug_print("%s BLOCK_IP URL: %s \nbody:%s" % (F5_Connector.BANNER, URL, body))

        F5 = iControl.BIG_IP(host=config.get("device"),
                             username=config.get("username"),
                             password=config.get("password"),
                             uri=URL,
                             method="POST")

        if F5.genericPOST(body):
            action_result.set_status(phantom.APP_SUCCESS)
        else:
            action_result.set_status(phantom.APP_ERROR)

        action_result.add_data(F5.response)
        self.debug_print("%s BLOCK_IP code: %s \nresponse: %s" % (F5_Connector.BANNER, F5.status_code, F5.response))
        return


# ==========================================================================================
# Logic for testing interactively e.g. python2.7 ./F5_connector.py ./test_jsons/reject.json
# ==========================================================================================

if __name__ == '__main__':

    import sys
    # import pudb                                          # executes a runtime breakpoint and brings up the pudb debugger.
    # pudb.set_trace()

    if (len(sys.argv) < 2):
        print "No test json specified as input"
        exit(0)

    with open(sys.argv[1]) as f:                           # input a json file that contains data like the configuration and action parameters,
        in_json = f.read()
        in_json = json.loads(in_json)
        print ("%s %s" % (sys.argv[1], json.dumps(in_json, indent=4)))

        connector = F5_Connector()
        connector.print_progress_message = True
        ret_val = connector._handle_action(json.dumps(in_json), None)
        print ("%s %s" % (connector.BANNER, json.dumps(json.loads(ret_val), indent=4)))

    exit(0)
