diff --git a/__init__.py b/__init__.py
new file mode 100644
index 0000000..a21cc51
--- /dev/null
+++ b/__init__.py
@@ -0,0 +1,14 @@
+# File: __init__.py
+#
+# Copyright (c) 2019-2024 Splunk Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software distributed under
+# the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+# either express or implied. See the License for the specific language governing permissions
+# and limitations under the License.
diff --git a/ciscosma.json b/ciscosma.json
new file mode 100644
index 0000000..a1ddec4
--- /dev/null
+++ b/ciscosma.json
@@ -0,0 +1,65 @@
+
+
+{
+ "appid": "44a028fe-e612-4bed-8486-1e136ab5b5c2",
+ "name": "Cisco SMA",
+ "description": "App integration for Cisco SMA, Secure Email and Web Manager",
+ "type": "information",
+ "product_vendor": "Cisco",
+ "logo": "logo_cisco.svg",
+ "logo_dark": "logo_cisco_dark.svg",
+ "product_name": "Cisco SMA",
+ "product_version_regex": ".*",
+ "publisher": "Splunk",
+ "contributors": [
+ {
+ "name": "Gary Ian Rokas"
+ }
+ ],
+ "license": "Copyright (c) 2024 Splunk Inc.",
+ "app_version": "1.0.0",
+ "latest_tested_versions": [
+ "On-prem, Version 15.5.1, API v2.0 (/sma/api/v2.0), December 2024"
+ ],
+ "utctime_updated": "2024-12-10T00:00:00.000000Z",
+ "package_name": "phantom_ciscosma",
+ "main_module": "ciscosma_connector.py",
+ "app_config_render": "default",
+ "min_phantom_version": "6.3.0",
+ "python_version": "3",
+ "fips_compliant": true,
+ "app_wizard_version": "1.0.0",
+ "configuration": {
+ "host": {
+ "data_type": "string",
+ "order": 0,
+ "description": "Hostname or IP address of Cisco SMA",
+ "required": true
+ },
+ "username": {
+ "data_type": "string",
+ "order": 1,
+ "description": "Username for host",
+ "required": true
+ },
+ "password": {
+ "data_type": "password",
+ "order": 2,
+ "description": "Password for host",
+ "required": true
+ }
+ },
+ "actions": [
+ {
+ "action": "test connectivity",
+ "identifier": "test_connectivity",
+ "description": "Validate the asset configuration for connectivity using supplied configuration",
+ "verbose": "",
+ "type": "test",
+ "read_only": true,
+ "parameters": {},
+ "output": [],
+ "versions": "EQ(*)"
+ }
+ ]
+}
diff --git a/ciscosma_connector.py b/ciscosma_connector.py
new file mode 100644
index 0000000..5d3194d
--- /dev/null
+++ b/ciscosma_connector.py
@@ -0,0 +1,177 @@
+# File: ciscosma_connector.py
+#
+# Copyright (c) 2019-2024 Splunk Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software distributed under
+# the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+# either express or implied. See the License for the specific language governing permissions
+# and limitations under the License.
+
+# Cisco SMA Connector
+
+import base64
+
+import phantom.app as phantom
+import requests
+from phantom.action_result import ActionResult
+from phantom.base_connector import BaseConnector
+
+from ciscosma_consts import CISCOSMA_GET_TOKEN_ENDPOINT
+
+
+class CiscoSmaConnector(BaseConnector):
+ def __init__(self):
+ super(CiscoSmaConnector, self).__init__()
+ self._base_url = None
+ self._username = None
+ self._password = None
+ self._verify = False
+ self._jwt_token = None
+
+ def _make_rest_call(self, endpoint, action_result, headers=None, params=None, data=None, json=None, method="get"):
+ """Function that makes the REST call to the app.
+
+ :param endpoint: REST endpoint that needs to be appended to the service address
+ :param action_result: object of ActionResult class
+ :param headers: request headers
+ :param params: request parameters
+ :param data: request body
+ :param json: JSON object
+ :param method: GET/POST/PUT/DELETE/PATCH (Default will be GET)
+ :return: status phantom.APP_ERROR/phantom.APP_SUCCESS(along with appropriate message),
+ response obtained by making an API call
+ """
+ resp_json = None
+
+ try:
+ kwargs = {"json": json, "data": data, "headers": headers, "params": params, "verify": self._verify}
+
+ response = requests.request(method, endpoint, **kwargs)
+
+ try:
+ resp_json = response.json()
+ except ValueError:
+ return action_result.set_status(phantom.APP_ERROR, f"Invalid JSON response from server: {response.text}"), None
+
+ if response.status_code != 200:
+ return (
+ action_result.set_status(
+ phantom.APP_ERROR, f"API call failed. Status code: {response.status_code}. Response: {response.text}"
+ ),
+ None,
+ )
+
+ return phantom.APP_SUCCESS, resp_json
+
+ except requests.exceptions.RequestException as e:
+ return action_result.set_status(phantom.APP_ERROR, f"Error connecting to server: {str(e)}"), None
+ except Exception as e:
+ return action_result.set_status(phantom.APP_ERROR, f"Error making REST call: {str(e)}"), None
+
+ def _make_authenticated_request(self, action_result, endpoint, headers=None, params=None, data=None, json_data=None, method="get"):
+ """Function that makes authenticated REST calls to the app with automatic token refresh.
+
+ :param endpoint: REST endpoint that needs to be appended to the service address
+ :param action_result: object of ActionResult class
+ :param headers: request headers
+ :param params: request parameters
+ :param data: request body
+ :param json_data: JSON object
+ :param method: GET/POST/PUT/DELETE/PATCH (Default will be GET)
+ :return: status phantom.APP_ERROR/phantom.APP_SUCCESS(along with appropriate message),
+ response obtained by making an API call
+ """
+ url = f"{self._base_url}{endpoint}"
+ if headers is None:
+ headers = {"Content-Type": "application/json", "Accept": "application/json"}
+
+ # Get Token
+ if not self._jwt_token:
+ ret_val, token = self._get_jwt_token(action_result)
+ if phantom.is_fail(ret_val):
+ return phantom.APP_ERROR, None
+ self._jwt_token = token
+
+ headers.update({"Authorization": f"Bearer {self._jwt_token}"})
+
+ ret_val, resp_json = self._make_rest_call(url, action_result, headers, params, data, json_data, method)
+
+ # Generate new token if expired
+ if ret_val == phantom.APP_ERROR and "token" in action_result.get_message().lower():
+ ret_val, token = self._get_jwt_token(action_result)
+ if phantom.is_fail(ret_val):
+ return action_result.get_status(), None
+
+ self._jwt_token = token
+ headers.update({"Authorization": f"Bearer {self._jwt_token}"})
+
+ ret_val, resp_json = self._make_rest_call(url, action_result, headers, params, data, json_data, method)
+
+ if phantom.is_fail(ret_val):
+ return action_result.get_status(), None
+
+ return phantom.APP_SUCCESS, resp_json
+
+ def _get_jwt_token(self, action_result):
+ payload = {
+ "data": {
+ "userName": base64.b64encode(self._username.encode()).decode(),
+ "passphrase": base64.b64encode(self._password.encode()).decode(),
+ }
+ }
+
+ ret_val, resp_json = self._make_rest_call(f"{self._base_url}{CISCOSMA_GET_TOKEN_ENDPOINT}", action_result, json=payload, method="post")
+
+ if phantom.is_fail(ret_val):
+ return ret_val, None
+
+ if not (jwt_token := resp_json.get("data", {}).get("jwtToken")):
+ return action_result.set_status(phantom.APP_ERROR, "JWT token not found in response"), None
+
+ return phantom.APP_SUCCESS, jwt_token
+
+ def _handle_test_connectivity(self, param):
+ action_result = self.add_action_result(ActionResult(dict(param)))
+
+ self.save_progress("Connecting to Cisco SMA...")
+ ret_val, _ = self._make_authenticated_request(action_result, CISCOSMA_GET_TOKEN_ENDPOINT, method="post")
+
+ if phantom.is_fail(ret_val):
+ self.save_progress("Test Connectivity Failed")
+ return action_result.get_status()
+
+ self.save_progress("Test Connectivity Passed")
+ return action_result.set_status(phantom.APP_SUCCESS)
+
+ def initialize(self):
+ config = self.get_config()
+ self._base_url = config["host"].rstrip("/")
+ self._username = config["username"]
+ self._password = config["password"]
+ self._verify = config.get("verify_server_cert", False)
+ return phantom.APP_SUCCESS
+
+ def handle_action(self, param):
+ action = self.get_action_identifier()
+ if action == "test_connectivity":
+ return self._handle_test_connectivity(param)
+ return phantom.APP_ERROR
+
+
+if __name__ == "__main__":
+ import sys
+
+ import pudb
+
+ pudb.set_trace()
+
+ connector = CiscoSmaConnector()
+ connector.print_progress_message = True
+
+ sys.exit(0)
diff --git a/ciscosma_consts.py b/ciscosma_consts.py
new file mode 100644
index 0000000..0943352
--- /dev/null
+++ b/ciscosma_consts.py
@@ -0,0 +1,18 @@
+# File: ciscosma_consts.py
+#
+# Copyright (c) 2019-2024 Splunk Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software distributed under
+# the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+# either express or implied. See the License for the specific language governing permissions
+# and limitations under the License.
+
+# Cisco SMA Constants
+
+CISCOSMA_GET_TOKEN_ENDPOINT = "/sma/api/v2.0/login"
diff --git a/logo_cisco.svg b/logo_cisco.svg
new file mode 100644
index 0000000..8c55b03
--- /dev/null
+++ b/logo_cisco.svg
@@ -0,0 +1,804 @@
+
+
+
diff --git a/logo_cisco_dark.svg b/logo_cisco_dark.svg
new file mode 100644
index 0000000..a9158b8
--- /dev/null
+++ b/logo_cisco_dark.svg
@@ -0,0 +1,944 @@
+
+
+