diff --git a/README.md b/README.md
index 9c0cb4e6..ad133e0f 100644
--- a/README.md
+++ b/README.md
@@ -36,6 +36,21 @@ Github issues and feature requests welcomed.
### Integrations
+| Category | Library |
+|-----------------|-------------------------------------------------------------|
+| API | flask |
+| Chat | slack |
+| Data Scraping | beautifulsoup
facebook groups
instagram
scrapy |
+| Databases | elasticsearch
neo4j
splunk |
+| Data Store | minio
swift |
+| Devices | snmp |
+| Google Cloud | google auth api
google people api
google sheets api |
+| Logging | sentryio |
+| MacOS | airport
macchanger |
+| Python | logging
requests |
+| SOAR | swimlane
splunk soar |
+| Recon | nmap |
+| Test Automation | selenium |
| Category | Library |
|-------------------|-------------------------------------------------------------|
| API | flask |
diff --git a/automon/integrations/swimlaneWrapper/__init__.py b/automon/integrations/swimlaneWrapper/__init__.py
new file mode 100644
index 00000000..2d1ec20f
--- /dev/null
+++ b/automon/integrations/swimlaneWrapper/__init__.py
@@ -0,0 +1 @@
+from .client import SwimlaneClient, SwimlaneClientRest, SwimlaneConfig
diff --git a/automon/integrations/swimlaneWrapper/api/__init__.py b/automon/integrations/swimlaneWrapper/api/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/automon/integrations/swimlaneWrapper/api/v2.py b/automon/integrations/swimlaneWrapper/api/v2.py
new file mode 100644
index 00000000..124fc085
--- /dev/null
+++ b/automon/integrations/swimlaneWrapper/api/v2.py
@@ -0,0 +1,94 @@
+from enum import Enum
+
+
+class Api(object):
+ api = f'api'
+
+
+class Auth(object):
+ api = f'{Api.api}/auth'
+ user = f'{api}/user'
+ token = f'{api}/token'
+ create = f'{token}/create'
+
+ @classmethod
+ def delete(cls, userId: str):
+ return f'{cls.user}/{userId}/token'
+
+ @classmethod
+ def metadata(cls, userId: str):
+ return f'{cls.user}/{userId}/token'
+
+
+class ApplicationViewModels(Enum):
+ """
+ [
+ {
+ "id": "string",
+ "name": "string",
+ "acronym": "string",
+ "description": "string",
+ "createdDate": "string",
+ "createdByUser": {
+ "id": "string",
+ "name": "string"
+ },
+ "modifiedDate": "string",
+ "modifiedByUser": {
+ "id": "string",
+ "name": "string"
+ }
+ }
+ ]
+ """
+ id: str
+ name: str
+ acronym: str
+ description: str
+ createdDate: str
+ createdByUser: {
+ "id": str,
+ "name": str
+ }
+ modifiedDate: str
+ modifiedByUser: {
+ "id": str,
+ "name": str
+ }
+
+
+class Application(object):
+ api = f'{Api.api}/app'
+ light = f'{api}/light'
+
+
+class Record(object):
+
+ @classmethod
+ def api(cls, appId: str):
+ return f'{Application.api}/{appId}/record'
+
+ @classmethod
+ def get(cls, appId: str, id: str):
+ return f'{cls.api(appId)}/{id}'
+
+
+class User(object):
+ api = f'{Api.api}/user'
+ login = f'{api}/login'
+ authorize = f'{api}/authorize'
+
+
+class Workspace(object):
+ api = f'{Api.api}/workspaces'
+ nav = f'{api}/nav'
+
+ @classmethod
+ def id(cls, id: str):
+ """workspace specified by id"""
+ return f'{cls.api}/{id}'
+
+ @classmethod
+ def app(cls, id: str):
+ """workspaces for application id"""
+ return f'{cls.api}/app/{id}'
diff --git a/automon/integrations/swimlaneWrapper/client.py b/automon/integrations/swimlaneWrapper/client.py
new file mode 100644
index 00000000..67acf3f4
--- /dev/null
+++ b/automon/integrations/swimlaneWrapper/client.py
@@ -0,0 +1,248 @@
+import json
+
+from automon import log
+from automon.integrations.requestsWrapper import RequestsClient
+
+from .config import SwimlaneConfig
+from .api.v2 import *
+
+logger = log.logging.getLogger(__name__)
+logger.setLevel(log.DEBUG)
+
+
+class SwimlaneClient(object):
+ pass
+
+
+class SwimlaneClientRest(object):
+
+ def __init__(self):
+ self.config = SwimlaneConfig()
+ self.requests = RequestsClient()
+
+ self.auth = None
+ self.apps = None
+ self.records = None
+ self.workspaces = None
+
+ async def is_ready(self):
+ if await self.config.is_ready():
+ if self.config.headers:
+ return True
+
+ async def test_connection(self):
+ return
+
+ async def login(self):
+ """tries all types of login"""
+
+ if await self.login_username_password():
+ return True
+
+ if await self.login_token():
+ return True
+
+ return False
+
+ async def login_username_password(self) -> bool:
+ """Login with username and password"""
+ url = f'{self.host}/{User.login}'
+
+ response = await self.requests.post(
+ url=url,
+ json=self.config.credentials,
+ )
+
+ apiKey = dict(json.loads(self.requests.content)).get('token')
+ self.config.apiKey = apiKey
+
+ self.requests.session.headers.update(self.config.headers)
+
+ self.config.userName_model = await self.requests.to_dict()
+
+ return response
+
+ async def login_token(self) -> bool:
+ """Login with username and password"""
+ url = f'{self.host}/{User.authorize}'
+
+ self.requests.session.headers.update(self.config.headers_jwt_token)
+
+ response = await self.requests.get(
+ url=url,
+ )
+
+ self.config.userName_model = await self.requests.to_dict()
+
+ return response
+
+ async def create_auth_token(self):
+ """Creates a new access token for the user making the request"""
+ url = f'{self.host}/{Auth.create}'
+
+ response = await self.requests.post(
+ url=url,
+ )
+
+ return response
+
+ async def app_list(self):
+ url = f'{self.host}/{Application.api}'
+
+ response = await self.requests.get(
+ url=url,
+ )
+
+ self.apps = await self.requests.to_dict()
+
+ return self.apps
+
+ @property
+ def host(self):
+ return self.config.host
+
+ async def record_resolve_fields(self, appId: str):
+ """since swimlane has no documentation on resolving field names"""
+
+ url = f'{self.host}/{Record.api(appId)}'
+
+ response = await self.requests.get(
+ url=url,
+ )
+
+ record_hashmap = {}
+
+ record = await self.requests.to_dict()
+ record_values = dict(record.get('values'))
+
+ for item in record_values.items():
+ key, value = item
+ if '$' in key:
+ continue
+ record_hashmap[key] = key
+
+ logger.debug(record_hashmap)
+
+ record = {
+ "applicationId": appId,
+ "values": {
+ "$type": "System.Collections.Generic.Dictionary`2[[System.String, mscorlib],[System.Object, mscorlib]], mscorlib",
+ }
+ }
+
+ record.get('values').update(record_hashmap)
+
+ record_create = await self.record_create_hard(appId=appId, data=record)
+
+ return record_hashmap
+
+ async def record_schema(self, appId: str):
+ url = f'{self.host}/{Record.api(appId)}'
+
+ response = await self.requests.get(
+ url=url,
+ )
+
+ record = await self.requests.to_dict()
+
+ return record
+
+ async def record_create(self, appId: str, key: str, value: str or int):
+ """create a record"""
+ return await self.record_create_easy(appId=appId, key=key, value=value)
+
+ async def record_create_easy(self, appId: str, key: str, value: str or int):
+ """create a record with boilerplate added
+
+ The bare minimum you need to send is (assuming application id is 5667113fd273a205bc747cf0):
+ {
+ "applicationId": "5667113fd273a205bc747cf0",
+ "values": {
+ "$type": "System.Collections.Generic.Dictionary`2[[System.String, mscorlib],[System.Object, mscorlib]], mscorlib",
+ "56674c5cc6c7dea0aeab4aed": "A new value"
+ }
+ }
+
+ """
+ url = f'{self.host}/{Record.api(appId)}'
+
+ record = {
+ "applicationId": appId,
+ "values": {
+ "$type": "System.Collections.Generic.Dictionary`2[[System.String, mscorlib],[System.Object, mscorlib]], mscorlib",
+ key: value
+ }
+ }
+
+ record_json = json.dumps(record)
+
+ response = await self.requests.post(
+ url=url,
+ json=record
+ )
+
+ record_created = await self.requests.content_to_dict()
+
+ return record_created
+
+ async def record_create_hard(self, appId: str, data: dict):
+ """create a record the hard way
+
+ no handholding. you're on your own"""
+ url = f'{self.host}/{Record.api(appId)}'
+
+ response = await self.requests.post(
+ url=url,
+ json=data
+ )
+
+ record_created = await self.requests.content_to_dict()
+
+ return record_created
+
+ async def record_delete_all(self, appId: str):
+ """delete all records in application"""
+ url = f'{self.host}/{Record.api(appId)}'
+
+ response = await self.requests.delete(
+ url=url
+ )
+
+ return response
+
+ async def record_get(self, appId: str, id: str):
+ """get a record"""
+ url = f'{self.host}/{Record.get(appId=appId, id=id)}'
+
+ response = await self.requests.get(
+ url=url
+ )
+
+ record = await self.requests.to_dict()
+
+ return record
+
+ async def record_get_base(self, appId: str):
+ """get a record"""
+ url = f'{self.host}/{Record.api(appId=appId)}'
+
+ response = await self.requests.get(
+ url=url
+ )
+
+ return response
+
+ @property
+ def userId(self):
+ return self.config.userName
+
+ async def workspace_list(self):
+ url = f'{self.host}/{Workspace.api}'
+
+ response = await self.requests.get(
+ url=url,
+ )
+
+ self.workspaces = await self.requests.to_dict()
+
+ return self.workspaces
diff --git a/automon/integrations/swimlaneWrapper/config.py b/automon/integrations/swimlaneWrapper/config.py
new file mode 100644
index 00000000..06f436f3
--- /dev/null
+++ b/automon/integrations/swimlaneWrapper/config.py
@@ -0,0 +1,92 @@
+from automon import log
+from automon.helpers import environ
+
+logger = log.logging.getLogger(__name__)
+logger.setLevel(log.DEBUG)
+
+
+class SwimlaneConfig(object):
+
+ def __init__(
+ self,
+ host: str = None,
+ userName: str = '',
+ password: str = '',
+ apiKey: str = None,
+ jwt_token: str = None,
+ ):
+ self.host = host or environ('SWIMLANE_HOST')
+ self.userName = userName or environ('SWIMLANE_USERNAME', '')
+ self.password = password or environ('SWIMLANE_PASSWORD', '')
+ self.apiKey = apiKey or environ('SWIMLANE_APIKEY')
+ self.jwt_token = jwt_token or environ('SWIMLANE_JWT_TOKEN', 'missing SWIMLANE_JWT_TOKEN')
+
+ self.userName_model = None
+
+ self.appId = environ('SWIMLANE_APP_ID')
+
+ @property
+ def access_token(self):
+ """alias to private acces token"""
+ return self.jwt_token
+
+ @property
+ def bearer_token(self):
+ """token you get from username / password"""
+ return self.token
+
+ @property
+ def credentials(self):
+ if self.userName and self.password:
+ return {
+ 'userName': self.userName,
+ 'password': self.password,
+ }
+
+ @property
+ def token(self):
+ return self.apiKey
+
+ @property
+ def headers(self):
+ if self.token:
+ return self.headers_api_token
+
+ if self.private_token:
+ return self.headers_jwt_token
+
+ @property
+ def headers_api_token(self):
+ if self.token:
+ return {
+ 'Authorization': f'Bearer {self.apiKey}'
+ }
+
+ @property
+ def headers_jwt_token(self):
+ if self.jwt_token:
+ return {
+ 'Private-Token': self.jwt_token
+ }
+
+ async def is_ready(self) -> bool:
+ if self.host:
+ if self.userName and self.password:
+ return True
+ if self.apiKey:
+ return True
+ if self.jwt_token:
+ return True
+
+ logger.error(str(dict(
+ host=self.host,
+ userName=self.userName,
+ password=self.password,
+ apiKey=self.apiKey,
+ jwt_token=self.jwt_token,
+ )))
+ return False
+
+ @property
+ def private_token(self):
+ return self.jwt_token
diff --git a/automon/integrations/swimlaneWrapper/tests/__init__.py b/automon/integrations/swimlaneWrapper/tests/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/automon/integrations/swimlaneWrapper/tests/test_library_record_create.py b/automon/integrations/swimlaneWrapper/tests/test_library_record_create.py
new file mode 100644
index 00000000..e7e9ccfa
--- /dev/null
+++ b/automon/integrations/swimlaneWrapper/tests/test_library_record_create.py
@@ -0,0 +1,42 @@
+# import os
+# import json
+# import unittest
+# import swimlane
+#
+# from swimlane import Swimlane
+# from automon import environ
+#
+# appId = environ('SWIMLANE_APP_ID')
+# host = environ('SWIMLANE_HOST')
+# user = environ('SWIMLANE_USERNAME')
+# password = environ('SWIMLANE_PASSWORD')
+#
+#
+# class MyTestCase(unittest.TestCase):
+#
+# if host and user and password and appId:
+# def test_login(self):
+# swimlane = Swimlane(host, user, password, verify_ssl=False)
+#
+# app = swimlane.apps.get(id=appId)
+#
+# # records = app.records.get()
+# # records = app.records.search('.')
+#
+# # swimlane.exceptions.UnknownField: " has no field 'test'"
+# record = app.records.create(
+# json=json.dumps(dict(
+# string='string',
+# int=1,
+# list=[1, 2, 3],
+# dict=dict(
+# key='value'
+# )
+# ))
+# )
+#
+# pass
+#
+#
+# if __name__ == '__main__':
+# unittest.main()
diff --git a/automon/integrations/swimlaneWrapper/tests/test_rest_app.py b/automon/integrations/swimlaneWrapper/tests/test_rest_app.py
new file mode 100644
index 00000000..40d8ee23
--- /dev/null
+++ b/automon/integrations/swimlaneWrapper/tests/test_rest_app.py
@@ -0,0 +1,19 @@
+import unittest
+import asyncio
+
+from automon.integrations.swimlaneWrapper.client import SwimlaneClientRest
+
+client = SwimlaneClientRest()
+
+
+class MyTestCase(unittest.TestCase):
+ def test_login(self):
+ if asyncio.run(client.is_ready()):
+ if asyncio.run(client.login()):
+ self.assertTrue(asyncio.run(
+ client.app_list())
+ )
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/automon/integrations/swimlaneWrapper/tests/test_rest_auth.py b/automon/integrations/swimlaneWrapper/tests/test_rest_auth.py
new file mode 100644
index 00000000..83d9c3a5
--- /dev/null
+++ b/automon/integrations/swimlaneWrapper/tests/test_rest_auth.py
@@ -0,0 +1,24 @@
+import unittest
+import asyncio
+
+from automon.integrations.swimlaneWrapper.client import SwimlaneClientRest
+
+client = SwimlaneClientRest()
+
+
+class MyTestCase(unittest.TestCase):
+ def test_login(self):
+ if asyncio.run(client.is_ready()):
+ self.assertTrue(
+ asyncio.run(client.login_username_password())
+ )
+ self.assertFalse(
+ asyncio.run(client.login_token())
+ )
+ self.assertFalse(
+ asyncio.run(client.create_auth_token())
+ )
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/automon/integrations/swimlaneWrapper/tests/test_rest_auth_token.py b/automon/integrations/swimlaneWrapper/tests/test_rest_auth_token.py
new file mode 100644
index 00000000..dc9ab8b3
--- /dev/null
+++ b/automon/integrations/swimlaneWrapper/tests/test_rest_auth_token.py
@@ -0,0 +1,25 @@
+import unittest
+import asyncio
+
+from automon.integrations.swimlaneWrapper.client import SwimlaneClientRest
+
+client = SwimlaneClientRest()
+
+
+class MyTestCase(unittest.TestCase):
+ def test_login(self):
+ if asyncio.run(client.is_ready()):
+ if client.config.jwt_token:
+ self.assertTrue(asyncio.run(
+ client.login_token()
+ ))
+
+ self.assertFalse(asyncio.run(
+ client.create_auth_token()
+ ))
+
+ pass
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/automon/integrations/swimlaneWrapper/tests/test_rest_record.py b/automon/integrations/swimlaneWrapper/tests/test_rest_record.py
new file mode 100644
index 00000000..4df96d10
--- /dev/null
+++ b/automon/integrations/swimlaneWrapper/tests/test_rest_record.py
@@ -0,0 +1,24 @@
+import unittest
+import asyncio
+
+from automon.integrations.swimlaneWrapper.client import SwimlaneClientRest
+
+client = SwimlaneClientRest()
+
+
+class MyTestCase(unittest.TestCase):
+ def test_login(self):
+ if asyncio.run(client.is_ready()):
+ if asyncio.run(client.login()):
+ app_id = client.config.appId
+ record = asyncio.run(
+ client.record_schema(appId=app_id)
+ )
+
+ self.assertTrue(record)
+
+ pass
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/automon/integrations/swimlaneWrapper/tests/test_rest_record_create.py b/automon/integrations/swimlaneWrapper/tests/test_rest_record_create.py
new file mode 100644
index 00000000..684a5e20
--- /dev/null
+++ b/automon/integrations/swimlaneWrapper/tests/test_rest_record_create.py
@@ -0,0 +1,44 @@
+import unittest
+import json
+import asyncio
+
+from automon.integrations.swimlaneWrapper.client import SwimlaneClientRest
+
+client = SwimlaneClientRest()
+
+
+class MyTestCase(unittest.TestCase):
+ def test_login(self):
+ if asyncio.run(client.is_ready()):
+ if asyncio.run(client.login_token()):
+ self.assertTrue(asyncio.run(
+ client.app_list()
+ ))
+
+ key = 'a7m4r' # json
+ value = json.dumps(dict(
+ key='value',
+ key2='value2',
+ ))
+
+ record_new = asyncio.run(
+ client.record_create(
+ appId=client.config.appId,
+ key=key,
+ value=value)
+ )
+
+ self.assertTrue(record_new)
+
+ record_id = record_new.get('id')
+
+ record_get = asyncio.run(
+ client.record_get(appId=client.config.appId, id=record_id))
+
+ self.assertTrue(record_get)
+
+ pass
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/automon/integrations/swimlaneWrapper/tests/test_rest_record_delete_all.py b/automon/integrations/swimlaneWrapper/tests/test_rest_record_delete_all.py
new file mode 100644
index 00000000..64586ddf
--- /dev/null
+++ b/automon/integrations/swimlaneWrapper/tests/test_rest_record_delete_all.py
@@ -0,0 +1,24 @@
+import unittest
+import asyncio
+
+from automon.integrations.swimlaneWrapper.client import SwimlaneClientRest
+
+client = SwimlaneClientRest()
+
+
+class MyTestCase(unittest.TestCase):
+ def test_login(self):
+ if asyncio.run(client.is_ready()):
+ if asyncio.run(client.login()):
+ app_id = client.config.appId
+ delete_all = asyncio.run(
+ client.record_delete_all(appId=app_id)
+ )
+
+ self.assertTrue(delete_all)
+
+ pass
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/automon/integrations/swimlaneWrapper/tests/test_rest_record_resolve_fields.py b/automon/integrations/swimlaneWrapper/tests/test_rest_record_resolve_fields.py
new file mode 100644
index 00000000..d3314503
--- /dev/null
+++ b/automon/integrations/swimlaneWrapper/tests/test_rest_record_resolve_fields.py
@@ -0,0 +1,24 @@
+import unittest
+import asyncio
+
+from automon.integrations.swimlaneWrapper.client import SwimlaneClientRest
+
+client = SwimlaneClientRest()
+
+
+class MyTestCase(unittest.TestCase):
+ def test_login(self):
+ if asyncio.run(client.is_ready()):
+ if asyncio.run(client.login()):
+ app_id = client.config.appId
+ fields = asyncio.run(
+ client.record_resolve_fields(appId=app_id)
+ )
+
+ self.assertTrue(fields)
+
+ pass
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/automon/integrations/swimlaneWrapper/tests/test_rest_workspace.py b/automon/integrations/swimlaneWrapper/tests/test_rest_workspace.py
new file mode 100644
index 00000000..b4c4aab3
--- /dev/null
+++ b/automon/integrations/swimlaneWrapper/tests/test_rest_workspace.py
@@ -0,0 +1,19 @@
+import unittest
+import asyncio
+
+from automon.integrations.swimlaneWrapper.client import SwimlaneClientRest
+
+client = SwimlaneClientRest()
+
+
+class MyTestCase(unittest.TestCase):
+ def test_login(self):
+ if asyncio.run(client.is_ready()):
+ if asyncio.run(client.login_username_password()):
+ self.assertTrue(
+ asyncio.run(client.workspace_list())
+ )
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/requirements.txt b/requirements.txt
index b56c4965..ac57a3c1 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -65,6 +65,9 @@ slackclient>=2.9.3
# splunk
splunk-sdk>=1.6.16
+# swimlane
+#swimlane>=10.14.0
+
# unit testing
pytest>=6.2.4
pytest-cov>=2.12.1