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 ADDED
@@ -0,0 +1,5 @@
1
+ from .client import FortiSOAR
2
+ from .exceptions import FortiSOARException, AuthenticationError
3
+
4
+ __all__ = ['FortiSOAR', 'FortiSOARException', 'AuthenticationError']
5
+ __version__ = "0.1.0"
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
@@ -0,0 +1,10 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+
4
+ class BaseAuth(ABC):
5
+ """Base authentication class"""
6
+
7
+ @abstractmethod
8
+ def get_auth_headers(self) -> dict:
9
+ """Return authentication headers"""
10
+ pass
@@ -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
@@ -0,0 +1,3 @@
1
+ DEFAULT_API_VERSION = "3"
2
+ API_PATH = f"/api/{DEFAULT_API_VERSION}"
3
+ QUERY_PATH = "/api/query"
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,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.27.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -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.