pyfsr 0.1.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyfsr/__init__.py +5 -0
- pyfsr/api/__init__.py +0 -0
- pyfsr/api/alerts.py +31 -0
- pyfsr/api/base.py +17 -0
- pyfsr/auth/__init__.py +0 -0
- pyfsr/auth/api_key.py +12 -0
- pyfsr/auth/base.py +10 -0
- pyfsr/auth/user_pass.py +31 -0
- pyfsr/client.py +147 -0
- pyfsr/constants.py +3 -0
- pyfsr/exceptions.py +11 -0
- pyfsr-0.1.4.dist-info/METADATA +39 -0
- pyfsr-0.1.4.dist-info/RECORD +15 -0
- pyfsr-0.1.4.dist-info/WHEEL +4 -0
- pyfsr-0.1.4.dist-info/licenses/LICENSE +21 -0
pyfsr/__init__.py
ADDED
pyfsr/api/__init__.py
ADDED
|
File without changes
|
pyfsr/api/alerts.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from typing import Dict
|
|
2
|
+
|
|
3
|
+
from .base import BaseAPI
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class AlertsAPI(BaseAPI):
|
|
7
|
+
"""Alerts API endpoints"""
|
|
8
|
+
|
|
9
|
+
def __init__(self, client):
|
|
10
|
+
super().__init__(client)
|
|
11
|
+
self.module = 'alerts'
|
|
12
|
+
|
|
13
|
+
def create(self, **data: Dict) -> Dict:
|
|
14
|
+
"""Create a new alert"""
|
|
15
|
+
return self._make_request('POST', f'/{self.module}', json=data)
|
|
16
|
+
|
|
17
|
+
def get(self, alert_id: str) -> Dict:
|
|
18
|
+
"""Get alert by ID"""
|
|
19
|
+
return self._make_request('GET', f'/{self.module}/{alert_id}')
|
|
20
|
+
|
|
21
|
+
def get_all(self) -> Dict:
|
|
22
|
+
"""Get All alerts"""
|
|
23
|
+
return self._make_request('GET', f'/{self.module}')
|
|
24
|
+
|
|
25
|
+
def update(self, alert_id: str, data: Dict) -> Dict:
|
|
26
|
+
"""Update an alert"""
|
|
27
|
+
return self._make_request('PUT', f'/{self.module}/{alert_id}', json=data)
|
|
28
|
+
|
|
29
|
+
def delete(self, alert_id: str) -> None:
|
|
30
|
+
"""Delete an alert"""
|
|
31
|
+
return self._make_request('DELETE', f'/{self.module}/{alert_id}')
|
pyfsr/api/base.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from ..exceptions import APIError
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class BaseAPI:
|
|
5
|
+
"""Base API class for all module-specific APIs"""
|
|
6
|
+
|
|
7
|
+
def __init__(self, client):
|
|
8
|
+
self.client = client
|
|
9
|
+
|
|
10
|
+
def _make_request(self, method: str, endpoint: str, **kwargs):
|
|
11
|
+
"""Make API request with error handling"""
|
|
12
|
+
try:
|
|
13
|
+
response = self.client.request(method, endpoint, **kwargs)
|
|
14
|
+
response.raise_for_status()
|
|
15
|
+
return response.json()
|
|
16
|
+
except Exception as e:
|
|
17
|
+
raise APIError(f"API request failed: {str(e)}")
|
pyfsr/auth/__init__.py
ADDED
|
File without changes
|
pyfsr/auth/api_key.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from .base import BaseAuth
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class APIKeyAuth(BaseAuth):
|
|
5
|
+
def __init__(self, api_key: str):
|
|
6
|
+
self.api_key = api_key
|
|
7
|
+
|
|
8
|
+
def get_auth_headers(self) -> dict:
|
|
9
|
+
return {
|
|
10
|
+
'Authorization': f'API-KEY {self.api_key}',
|
|
11
|
+
'Content-Type': 'application/json'
|
|
12
|
+
}
|
pyfsr/auth/base.py
ADDED
pyfsr/auth/user_pass.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
|
|
3
|
+
from .base import BaseAuth
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class UserPasswordAuth(BaseAuth):
|
|
7
|
+
def __init__(self, username: str, password: str, base_url: str, verify_ssl: bool = True,
|
|
8
|
+
):
|
|
9
|
+
self.username = username
|
|
10
|
+
self.password = password
|
|
11
|
+
self.base_url = base_url
|
|
12
|
+
self.verify_ssl = verify_ssl
|
|
13
|
+
self.token = self._authenticate()
|
|
14
|
+
|
|
15
|
+
def _authenticate(self) -> str:
|
|
16
|
+
auth_url = f"{self.base_url}/auth/authenticate"
|
|
17
|
+
payload = {
|
|
18
|
+
"credentials": {
|
|
19
|
+
"loginid": self.username,
|
|
20
|
+
"password": self.password
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
response = requests.post(auth_url, json=payload, verify=self.verify_ssl)
|
|
24
|
+
response.raise_for_status()
|
|
25
|
+
return response.json()['token']
|
|
26
|
+
|
|
27
|
+
def get_auth_headers(self) -> dict:
|
|
28
|
+
return {
|
|
29
|
+
'Authorization': f'Bearer {self.token}',
|
|
30
|
+
'Content-Type': 'application/json'
|
|
31
|
+
}
|
pyfsr/client.py
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
from typing import Union, Optional, Dict, Any
|
|
2
|
+
from urllib.parse import urljoin
|
|
3
|
+
|
|
4
|
+
import requests
|
|
5
|
+
|
|
6
|
+
from .api.alerts import AlertsAPI
|
|
7
|
+
from .auth.api_key import APIKeyAuth
|
|
8
|
+
from .auth.user_pass import UserPasswordAuth
|
|
9
|
+
from .constants import API_PATH
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class FortiSOAR:
|
|
13
|
+
"""
|
|
14
|
+
Main client class for FortiSOAR API
|
|
15
|
+
|
|
16
|
+
Attributes:
|
|
17
|
+
base_url (str): The base URL for the FortiSOAR API.
|
|
18
|
+
session (requests.Session): The session object for making HTTP requests.
|
|
19
|
+
verify_ssl (bool): Whether to verify SSL certificates.
|
|
20
|
+
auth (Union[APIKeyAuth, UserPasswordAuth]): The authentication method.
|
|
21
|
+
alerts (AlertsAPI): The Alerts API interface.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(
|
|
25
|
+
self,
|
|
26
|
+
base_url: str,
|
|
27
|
+
auth: Union[str, tuple],
|
|
28
|
+
verify_ssl: bool = True,
|
|
29
|
+
supress_insecure_warnings: bool = False
|
|
30
|
+
):
|
|
31
|
+
|
|
32
|
+
"""
|
|
33
|
+
Initialize the FortiSOAR client.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
base_url (str): The base URL for the FortiSOAR API.
|
|
37
|
+
auth (Union[str, tuple]): The authentication method, either an API key (str) or a tuple of (username, password).
|
|
38
|
+
verify_ssl (bool, optional): Whether to verify SSL certificates. Defaults to True.
|
|
39
|
+
supress_insecure_warnings (bool, optional): Whether to suppress insecure request warnings. Defaults to False.
|
|
40
|
+
|
|
41
|
+
Raises:
|
|
42
|
+
ValueError: If the provided authentication method is invalid.
|
|
43
|
+
"""
|
|
44
|
+
self.base_url = base_url.rstrip('/')
|
|
45
|
+
self.session = requests.Session()
|
|
46
|
+
self.session.verify = verify_ssl
|
|
47
|
+
self.verify_ssl = verify_ssl
|
|
48
|
+
if supress_insecure_warnings:
|
|
49
|
+
requests.packages.urllib3.disable_warnings(requests.packages.urllib3.exceptions.InsecureRequestWarning)
|
|
50
|
+
|
|
51
|
+
# Setup authentication
|
|
52
|
+
if isinstance(auth, str):
|
|
53
|
+
self.auth = APIKeyAuth(auth)
|
|
54
|
+
elif isinstance(auth, tuple) and len(auth) == 2:
|
|
55
|
+
username, password = auth
|
|
56
|
+
self.auth = UserPasswordAuth(username, password, self.base_url, self.verify_ssl)
|
|
57
|
+
else:
|
|
58
|
+
raise ValueError("Invalid authentication provided")
|
|
59
|
+
|
|
60
|
+
# Apply authentication headers
|
|
61
|
+
self.session.headers.update(self.auth.get_auth_headers())
|
|
62
|
+
|
|
63
|
+
# Initialize API interfaces
|
|
64
|
+
self.alerts = AlertsAPI(self)
|
|
65
|
+
|
|
66
|
+
def request(
|
|
67
|
+
self,
|
|
68
|
+
method: str,
|
|
69
|
+
endpoint: str,
|
|
70
|
+
params: Optional[Dict] = None,
|
|
71
|
+
data: Optional[Dict] = None,
|
|
72
|
+
**kwargs
|
|
73
|
+
) -> requests.Response:
|
|
74
|
+
"""
|
|
75
|
+
Make HTTP request to FortiSOAR API
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
method: HTTP method (GET, POST, PUT, DELETE)
|
|
79
|
+
endpoint: API endpoint path
|
|
80
|
+
params: Query parameters
|
|
81
|
+
data: Request body data
|
|
82
|
+
**kwargs: Additional arguments to pass to requests
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
Response from the API
|
|
86
|
+
|
|
87
|
+
Raises:
|
|
88
|
+
requests.exceptions.RequestException: If the request fails
|
|
89
|
+
"""
|
|
90
|
+
# Ensure endpoint starts with slash for proper URL joining
|
|
91
|
+
if not endpoint.startswith('/'):
|
|
92
|
+
endpoint = f'/{endpoint}'
|
|
93
|
+
|
|
94
|
+
# For API v3 endpoints, prepend the API path if not already present
|
|
95
|
+
if not endpoint.startswith(API_PATH) and not endpoint.startswith('/auth'):
|
|
96
|
+
endpoint = f"{API_PATH}{endpoint}"
|
|
97
|
+
|
|
98
|
+
url = urljoin(self.base_url, endpoint)
|
|
99
|
+
|
|
100
|
+
try:
|
|
101
|
+
response = self.session.request(
|
|
102
|
+
method=method.upper(),
|
|
103
|
+
url=url,
|
|
104
|
+
params=params,
|
|
105
|
+
json=data,
|
|
106
|
+
**kwargs
|
|
107
|
+
)
|
|
108
|
+
response.raise_for_status()
|
|
109
|
+
return response
|
|
110
|
+
|
|
111
|
+
except requests.exceptions.RequestException as e:
|
|
112
|
+
error_msg = f"API request failed: {str(e)}"
|
|
113
|
+
if hasattr(e.response, 'text'):
|
|
114
|
+
error_msg += f"\nResponse: {e.response.text}"
|
|
115
|
+
raise requests.exceptions.RequestException(error_msg)
|
|
116
|
+
|
|
117
|
+
def get(self, endpoint: str, params: Optional[Dict] = None, **kwargs) -> Dict[str, Any]:
|
|
118
|
+
"""Perform GET request and return JSON response"""
|
|
119
|
+
response = self.request('GET', endpoint, params=params, **kwargs)
|
|
120
|
+
return response.json()
|
|
121
|
+
|
|
122
|
+
def post(self, endpoint: str, data: Dict, params: Optional[Dict] = None, **kwargs) -> Dict[str, Any]:
|
|
123
|
+
"""Perform POST request and return JSON response"""
|
|
124
|
+
response = self.request('POST', endpoint, params=params, data=data, **kwargs)
|
|
125
|
+
return response.json()
|
|
126
|
+
|
|
127
|
+
def put(self, endpoint: str, data: Dict, params: Optional[Dict] = None, **kwargs) -> Dict[str, Any]:
|
|
128
|
+
"""Perform PUT request and return JSON response"""
|
|
129
|
+
response = self.request('PUT', endpoint, params=params, data=data, **kwargs)
|
|
130
|
+
return response.json()
|
|
131
|
+
|
|
132
|
+
def delete(self, endpoint: str, params: Optional[Dict] = None, **kwargs) -> None:
|
|
133
|
+
"""Perform DELETE request"""
|
|
134
|
+
self.request('DELETE', endpoint, params=params, **kwargs)
|
|
135
|
+
|
|
136
|
+
def query(self, module: str, query_data: Dict) -> Dict[str, Any]:
|
|
137
|
+
"""
|
|
138
|
+
Execute a query against a module
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
module: Name of the module to query
|
|
142
|
+
query_data: Query parameters and filters
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
Query results
|
|
146
|
+
"""
|
|
147
|
+
return self.post(f'/query/{module}', data=query_data)
|
pyfsr/constants.py
ADDED
pyfsr/exceptions.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
class FortiSOARException(Exception):
|
|
2
|
+
"""Base exception for FortiSOAR client"""
|
|
3
|
+
pass
|
|
4
|
+
|
|
5
|
+
class AuthenticationError(FortiSOARException):
|
|
6
|
+
"""Authentication related errors"""
|
|
7
|
+
pass
|
|
8
|
+
|
|
9
|
+
class APIError(FortiSOARException):
|
|
10
|
+
"""API related errors"""
|
|
11
|
+
pass
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyfsr
|
|
3
|
+
Version: 0.1.4
|
|
4
|
+
Summary: Python implementation of the FortiSOAR REST API
|
|
5
|
+
Project-URL: Homepage, https://github.com/ftnt-dspille/pyFSR
|
|
6
|
+
Project-URL: Bug Tracker, https://github.com/ftnt-dspille/pyFSR/issues
|
|
7
|
+
Project-URL: Documentation, https://github.com/ftnt-dspille/pyFSR#readme
|
|
8
|
+
Project-URL: Source Code, https://github.com/ftnt-dspille/pyFSR
|
|
9
|
+
Author-email: Dylan Spille <dspille@fortinet.com>
|
|
10
|
+
License: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: api,fortinet,fortisoar,rest
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Requires-Dist: requests>=2.31.0
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
24
|
+
Requires-Dist: pytest-cov; extra == 'dev'
|
|
25
|
+
Requires-Dist: pytest-mock; extra == 'dev'
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
# pyfsr
|
|
29
|
+
|
|
30
|
+
Goal of the library is be a Python Client for the FortiSOAR Rest API.
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pip install pyfsr
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
This library is a work in progress and is not yet ready for production use.
|
|
39
|
+
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
pyfsr/__init__.py,sha256=4D6Vkba5JllMVrnEMaRXSUcI8MC0BFBImdJquoNiO14,186
|
|
2
|
+
pyfsr/client.py,sha256=j1SD3a3OhB4-h7lAbO2PTH8LSzuATkmYLonGdMnYamw,5400
|
|
3
|
+
pyfsr/constants.py,sha256=AXz7LqxM2ctGT4H7xRqQw4mMmvjiYB2_-BDltdzhtIo,92
|
|
4
|
+
pyfsr/exceptions.py,sha256=Kb8t7FLrUMa6Fz9tM5QBgl-ZlPq-360S8Kuaj8lRBFU,264
|
|
5
|
+
pyfsr/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
pyfsr/api/alerts.py,sha256=9ORh66j3LC1DG-EE0mjfe7BaLEp9tfUtj8QMrKuJlWc,942
|
|
7
|
+
pyfsr/api/base.py,sha256=gBnGeEMFJRdi1Ph9IhOzHL9hKeXHBNVVjfz25p4qlzc,536
|
|
8
|
+
pyfsr/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
+
pyfsr/auth/api_key.py,sha256=pGDbqj_FSCdKYpKRWlCEStbUAQtl5FwcwWSlX0R_rEg,297
|
|
10
|
+
pyfsr/auth/base.py,sha256=JSBOZc_v-WbTgmmm3k5oRwCHO2XUK40Pz12-6mn-jO8,212
|
|
11
|
+
pyfsr/auth/user_pass.py,sha256=I94a2S601swfuXd_B1pZ3BYIQNZOPVKcFN3K08JiQ80,946
|
|
12
|
+
pyfsr-0.1.4.dist-info/METADATA,sha256=He66j1zXW-sAl5pN1XpNyxNjVlIw1ejcJjz_Zqzrr2g,1305
|
|
13
|
+
pyfsr-0.1.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
14
|
+
pyfsr-0.1.4.dist-info/licenses/LICENSE,sha256=jnmypcXoJPIsS0b_Al7_KKxfzLszI4hXqvP9Zy0Pyt8,1069
|
|
15
|
+
pyfsr-0.1.4.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Dylan Spille
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|