Commit 1a6c9cef authored by Trishna Saeharaseelan's avatar Trishna Saeharaseelan
Browse files

Merge branch 'release-v1.0.0' into 'master'

Release v1.0.0

See merge request !18
1 merge request!18Release v1.0.0
Pipeline #267183 passed with stages
in 47 seconds
......@@ -7,6 +7,25 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
## [Unreleased]
## [v1.0.0] - 2025-03-11
## Fixed
- Fixed race condition in authentication flow for fast polling rates
- Install runtime dependencies on package install
- Remove all unimported dependencies from requirements.txt
- Wrap HTTP requests in a `requests.Session` with a default timeout set
of 30 seconds
- `run-tests.sh`: Fixed script exiting with a success state on test failure
- Added missing stub `Adapter.authInProgress` method
### Changed
- Add hyphen to regex for schema version to enable issue branches
- Bumped versions of python packages to latest stable in `requirements.txt`:
Needed for packages to continue working with Python 3.12+
## [v0.1.0] - 2023-03-24
### Added
......@@ -32,5 +51,6 @@ Create an example implmentation of the adapter
- store sent files in sent by date and hour
- store received files in received by date and hour
[v0.1.0]: https://git.noc.ac.uk/communications-backbone-system/backbone-adapter-python/compare/ad1285b9...v0.1.0
[unreleased]: https://git.noc.ac.uk/communications-backbone-system/backbone-adapter-python/compare/v0.1.0...dev
[unreleased]: https://git.noc.ac.uk/communications-backbone-system/backbone-adapter-python/compare/v1.0.0...dev
[v1.0.0]: https://git.noc.ac.uk/communications-backbone-system/backbone-adapter-python/compare/v0.1.0...v1.0.0
[v0.1.0]: https://git.noc.ac.uk/communications-backbone-system/backbone-adapter-python/compare/v0.1.0
\ No newline at end of file
......@@ -13,7 +13,7 @@ Implements:
```
pip install -r requirements.txt
copy_tests
python -m testsuite.copy_tests
```
## Test
......
aniso8601==9.0.1
attrs==22.2.0
backbone-adapter-python @ git+https://git.noc.ac.uk/communications-backbone-system/backbone-adapter-python.git@dev#egg=backbone-adapter-python
behave==1.2.6
certifi==2022.12.7
charset-normalizer==3.0.1
click==8.1.3
exceptiongroup==1.1.0
backbone-adapter-python @ git+https://git.noc.ac.uk/communications-backbone-system/backbone-adapter-python.git@a31f7c02ba4498fd842bab5c2ae271dc45fc46ec#egg=backbone-adapter-python
Flask==2.2.2
Flask-RESTful==0.3.9
idna==3.4
importlib-metadata==6.0.0
importlib-resources==5.10.2
iniconfig==2.0.0
itsdangerous==2.1.2
Jinja2==3.1.2
jsonschema==4.17.3
jsonschema-spec==0.1.2
lazy-object-proxy==1.9.0
MarkupSafe==2.1.2
marshmallow==3.19.0
openapi-schema-validator==0.4.1
openapi-spec-validator==0.5.2
packaging==23.0
parse==1.19.0
parse-type==0.6.0
pathable==0.4.3
pkgutil_resolve_name==1.3.10
pluggy==1.0.0
pyrsistent==0.19.3
pytest==7.2.1
pytz==2022.7.1
PyYAML==6.0
requests==2.28.2
responses==0.22.0
six==1.16.0
toml==0.10.2
tomli==2.0.1
types-toml==0.10.8.1
typing_extensions==4.4.0
urllib3==1.26.14
watchdog==2.2.1
Werkzeug==2.2.2
zipp==3.12.0
-r requirements.txt
behave==1.2.6
black==23.1.0
requests-mock==1.10.0
behave>=1.2.6
pytest>=8.3.4
black>=23.1.0
requests-mock>=1.10.0
git+https://git.noc.ac.uk/communications-backbone-system/backbone-adapter-testsuite.git@dev#egg=backbone_adapter_testsuite
-i https://pypi.org/simple
attrs==22.2.0 ; python_version >= '3.6'
certifi==2022.12.7 ; python_version >= '3.6'
charset-normalizer==3.0.1
exceptiongroup==1.1.0 ; python_version < '3.11'
idna==3.4 ; python_version >= '3.5'
importlib-resources==5.10.2 ; python_version < '3.9'
iniconfig==2.0.0 ; python_version >= '3.7'
jsonschema==4.17.3 ; python_version >= '3.7'
jsonschema-spec==0.1.3 ; python_full_version >= '3.7.0' and python_full_version < '4.0.0'
lazy-object-proxy==1.9.0 ; python_version >= '3.7'
openapi-schema-validator==0.4.2
openapi-spec-validator==0.5.4
packaging==23.0 ; python_version >= '3.7'
parse==1.19.0
parse-type==0.6.0 ; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
pathable==0.4.3 ; python_full_version >= '3.7.0' and python_full_version < '4.0.0'
pkgutil-resolve-name==1.3.10 ; python_version < '3.9'
pluggy==1.0.0 ; python_version >= '3.6'
pyrsistent==0.19.3 ; python_version >= '3.7'
pytest==7.2.1
pyyaml==6.0 ; python_version >= '3.6'
requests==2.28.2
responses==0.22.0
six==1.16.0 ; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
toml==0.10.2 ; python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'
tomli==2.0.1 ; python_version < '3.11'
types-toml==0.10.8.2
typing-extensions==4.4.0 ; python_version >= '3.7'
urllib3==1.26.14 ; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'
zipp==3.12.0 ; python_version < '3.10'
openapi-schema-validator==0.6.3
openapi-spec-validator==0.7.1
requests>=2.28.2
#!/usr/bin/env bash
set -e
pip install -r requirements-dev.txt
if [ ! -d "$(pwd)/test/features" ]; then
mkdir $(pwd)/test/features
......@@ -7,7 +9,7 @@ if [ ! -d "$(pwd)/test/fixtures" ]; then
mkdir $(pwd)/test/fixtures
fi
copy_tests
python -m testsuite.copy_tests
behave
rm -rf test/fixtures test/features src/__pycache__
\ No newline at end of file
rm -rf test/fixtures test/features src/__pycache__
from setuptools import setup
setup(
version="0.1.0",
name="backbone_adapter_python",
python_requires=">=3.8",
packages=["backbone_adapter"],
package_dir={
"backbone_adapter": "src",
},
include_package_data=True,
)
with open("requirements.txt") as f:
required = f.read().splitlines()
setup(
version="1.0.0",
name="backbone_adapter_python",
python_requires=">=3.8",
packages=["backbone_adapter"],
package_dir={
"backbone_adapter": "src",
},
include_package_data=True,
install_requires=required,
)
......@@ -3,12 +3,43 @@ import json
import requests
from .exceptions import AuthInProgressError
DEFAULT_REQUEST_TIMEOUT = 30.0
"""
Default HTTP request timeout in seconds
"""
class HTTPSession(requests.Session):
"""
Session with a default timeout set and ability to disable HTTPS
certificate validation with `verify=False`
"""
def __init__(self, *args, timeout=None, verify=None, **kwargs):
super().__init__(*args, **kwargs)
self.timeout = timeout
self.verify = verify
def request(self, *args, **kwargs):
if self.timeout is not None:
kwargs.setdefault("timeout", self.timeout)
return super().request(*args, **kwargs)
class Adapter:
def __init__(self, protocol, config):
def __init__(
self, protocol, config, requests_timeout=DEFAULT_REQUEST_TIMEOUT, verify=True
):
self.protocol = protocol
self.config = config
self.credentials = None
self.authenticating = False
self.requests_timeout = requests_timeout
self.verify = verify
self.session = HTTPSession(timeout=requests_timeout, verify=verify)
def validate(self, message):
return self.protocol.validate(message)
......@@ -22,12 +53,15 @@ class Adapter:
return False
def getAuthorizationHeader(self):
if self.authenticating:
raise AuthInProgressError()
if not self.tokenValid():
self.auth()
return {"Authorization": "Bearer " + self.credentials["token"]}
def auth(self):
self.authenticating = True
adapter_config = self.config
api_root = self.config.get("api")
parameters = {
......@@ -35,19 +69,41 @@ class Adapter:
"secret": adapter_config["secret"],
}
response = requests.get(url=f"{api_root}/token", params=parameters)
response = self.session.get(url=f"{api_root}/token", params=parameters)
if response.status_code == 200:
self.credentials = response.json()
self.authenticating = False
return response
else:
self.authenticating = False
raise requests.exceptions.RequestException(response=response)
def onError(self, type):
"""
Stub method to be overridden by user subclass if required
"""
pass
def authInProgress(self, type):
"""
Stub method to be overridden by user subclass if required
"""
pass
def poll(self, is_retry=False):
api_root = self.config.get("api")
headers = self.getAuthorizationHeader()
response = requests.get(url=f"{api_root}/receive", headers=headers)
# If rapid polling then the first few poll requests
# return an empty response until the auth completes
headers = {}
try:
headers = self.getAuthorizationHeader()
except AuthInProgressError as e:
self.authInProgress(e.type)
return []
response = self.session.get(url=f"{api_root}/receive", headers=headers)
if response.status_code == 200:
for message in response.json():
......@@ -78,7 +134,7 @@ class Adapter:
parameters = {"topic": topic, "body": json.dumps(body)}
response = requests.post(
response = self.session.post(
url=f"{api_root}/send", headers=headers, json=parameters
)
......@@ -102,7 +158,7 @@ class Adapter:
print(str(headers))
parameters = {"body": json.dumps(body)}
response = requests.post(
response = self.session.post(
url=f"{api_root}/notify", headers=headers, json=parameters
)
......
......@@ -11,3 +11,12 @@ class SchemaError(Exception):
self.type = exception_type
self.message = message
super().__init__(self.message)
class AuthInProgressError(Exception):
"""Waiting on response of authentication process"""
def __init__(self):
self.type = "AuthInProgress"
self.message = "Cannot re-authenticate while an existing request is outstanding"
super().__init__(self.message)
......@@ -48,7 +48,7 @@ class GenericSoarProtocol(GenericProtocol):
def __init__(self, schema, services):
# If schema is a string assume it is a branch/tag/commitref
# for the soar protocol swagger docs
if type(schema) == str and re.search("^[\w\.]+$", schema):
if type(schema) == str and re.search("^[\w\.\-]+$", schema):
self.schema = self.get_schema(schema)
else:
self.schema = schema
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment