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