text: #
  """
       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)
  