removed authentication methods and tests for data service
This commit is contained in:
parent
d09d6f0ec8
commit
7199143257
6 changed files with 8 additions and 158 deletions
11
README.md
11
README.md
|
|
@ -352,16 +352,7 @@ $ oc create secret generic \
|
||||||
|
|
||||||
The CERN data service collects data from various sources and expose them via a REST API endpoint.
|
The CERN data service collects data from various sources and expose them via a REST API endpoint.
|
||||||
|
|
||||||
Create secret:
|
To enable the service set the environment variable `DATA_SERVICE_ENABLED` as `True`.
|
||||||
|
|
||||||
```console
|
|
||||||
$ read DATA_SERVICE_CLIENT_EMAIL
|
|
||||||
$ read DATA_SERVICE_CLIENT_PASSWORD
|
|
||||||
$ oc create secret generic \
|
|
||||||
--from-literal="DATA_SERVICE_CLIENT_EMAIL=$DATA_SERVICE_CLIENT_EMAIL" \
|
|
||||||
--from-literal="DATA_SERVICE_CLIENT_PASSWORD=$DATA_SERVICE_CLIENT_PASSWORD" \
|
|
||||||
data-service-api
|
|
||||||
```
|
|
||||||
|
|
||||||
## Update configuration
|
## Update configuration
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,8 +24,6 @@ if [[ "$APP_NAME" == "calculator-app" ]]; then
|
||||||
export "EXTRA_PAGES"="$EXTRA_PAGES"
|
export "EXTRA_PAGES"="$EXTRA_PAGES"
|
||||||
|
|
||||||
export "DATA_SERVICE_ENABLED"="${DATA_SERVICE_ENABLED:=False}"
|
export "DATA_SERVICE_ENABLED"="${DATA_SERVICE_ENABLED:=False}"
|
||||||
export "DATA_SERVICE_CLIENT_EMAIL"="$DATA_SERVICE_CLIENT_EMAIL"
|
|
||||||
export "DATA_SERVICE_CLIENT_PASSWORD"="$DATA_SERVICE_CLIENT_PASSWORD"
|
|
||||||
|
|
||||||
echo "Starting the caimira webservice with: python -m caimira.apps.calculator ${args[@]}"
|
echo "Starting the caimira webservice with: python -m caimira.apps.calculator ${args[@]}"
|
||||||
python -m caimira.apps.calculator "${args[@]}"
|
python -m caimira.apps.calculator "${args[@]}"
|
||||||
|
|
|
||||||
|
|
@ -285,16 +285,6 @@
|
||||||
name: arve-api
|
name: arve-api
|
||||||
- name: DATA_SERVICE_ENABLED
|
- name: DATA_SERVICE_ENABLED
|
||||||
value: 'False'
|
value: 'False'
|
||||||
- name: DATA_SERVICE_CLIENT_EMAIL
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
key: DATA_SERVICE_CLIENT_EMAIL
|
|
||||||
name: data-service-api
|
|
||||||
- name: DATA_SERVICE_CLIENT_PASSWORD
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
key: DATA_SERVICE_CLIENT_PASSWORD
|
|
||||||
name: data-service-api
|
|
||||||
image: '${PROJECT_NAME}/calculator-app'
|
image: '${PROJECT_NAME}/calculator-app'
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 8080
|
- containerPort: 8080
|
||||||
|
|
|
||||||
|
|
@ -494,12 +494,7 @@ def make_app(
|
||||||
data_service = None
|
data_service = None
|
||||||
data_service_enabled = os.environ.get("DATA_SERVICE_ENABLED", "False")
|
data_service_enabled = os.environ.get("DATA_SERVICE_ENABLED", "False")
|
||||||
is_enabled = data_service_enabled.lower() == "true"
|
is_enabled = data_service_enabled.lower() == "true"
|
||||||
if is_enabled:
|
if is_enabled: data_service = DataService.create()
|
||||||
credentials = {
|
|
||||||
"email": os.environ.get("DATA_SERVICE_CLIENT_EMAIL", None),
|
|
||||||
"password": os.environ.get("DATA_SERVICE_CLIENT_PASSWORD", None),
|
|
||||||
}
|
|
||||||
data_service = DataService.create(credentials)
|
|
||||||
|
|
||||||
return Application(
|
return Application(
|
||||||
urls,
|
urls,
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,5 @@
|
||||||
import logging
|
import logging
|
||||||
import typing
|
import typing
|
||||||
from datetime import datetime, timedelta, timezone
|
|
||||||
|
|
||||||
import jwt
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from caimira.store.data_registry import DataRegistry
|
from caimira.store.data_registry import DataRegistry
|
||||||
|
|
@ -18,78 +15,18 @@ class DataService:
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
credentials: typing.Dict[str, typing.Optional[str]],
|
|
||||||
host: str,
|
host: str,
|
||||||
):
|
):
|
||||||
self._credentials = credentials
|
|
||||||
self._host = host
|
self._host = host
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create(cls, credentials: typing.Dict[str, typing.Optional[str]], host: str = "https://caimira-data-api.app.cern.ch"):
|
def create(cls, host: str = "https://caimira-data-api-qa.app.cern.ch"): # UPDATE QA TO PROD ONCE ALL IS WORKING
|
||||||
"""Factory."""
|
"""Factory."""
|
||||||
return cls(credentials, host)
|
return cls(host)
|
||||||
|
|
||||||
def _is_valid(self, access_token):
|
|
||||||
"""Return True if the expiration token is still valid."""
|
|
||||||
try:
|
|
||||||
decoded = jwt.decode(
|
|
||||||
access_token, algorithms=["HS256"], options={"verify_signature": False}
|
|
||||||
)
|
|
||||||
expiration_timestamp = decoded["exp"]
|
|
||||||
expiration = datetime.utcfromtimestamp(expiration_timestamp).replace(
|
|
||||||
tzinfo=timezone.utc
|
|
||||||
)
|
|
||||||
now = datetime.now(timezone.utc)
|
|
||||||
is_valid = now < expiration - timedelta(
|
|
||||||
seconds=5
|
|
||||||
) # 5 seconds time delta to avoid timing issues
|
|
||||||
|
|
||||||
logger.debug(f"Access token expiration: {expiration_timestamp}. Is valid? {is_valid}")
|
|
||||||
|
|
||||||
return is_valid
|
|
||||||
except jwt.ExpiredSignatureError:
|
|
||||||
logger.warning("JWT token expired.")
|
|
||||||
except jwt.InvalidTokenError:
|
|
||||||
logger.warning("JWT token invalid.")
|
|
||||||
return False
|
|
||||||
|
|
||||||
def _login(self):
|
|
||||||
logger.debug(f"Access token: {self._access_token}")
|
|
||||||
|
|
||||||
if self._access_token and self._is_valid(self._access_token):
|
|
||||||
return self._access_token
|
|
||||||
|
|
||||||
# invalid access_token, fetch it again
|
|
||||||
client_email = self._credentials["email"]
|
|
||||||
client_password = self._credentials["password"]
|
|
||||||
|
|
||||||
if client_email == None or client_password == None:
|
|
||||||
# If the credentials are not defined, an exception is raised.
|
|
||||||
raise Exception("DataService credentials not set")
|
|
||||||
|
|
||||||
url = f"{self._host}/login"
|
|
||||||
headers = {"Content-Type": "application/json"}
|
|
||||||
json_body = dict(email=client_email, password=client_password)
|
|
||||||
|
|
||||||
try:
|
|
||||||
response = requests.post(url, json=json_body, headers=headers)
|
|
||||||
response.raise_for_status()
|
|
||||||
if response.status_code == 200:
|
|
||||||
self._access_token = response.json()["access_token"]
|
|
||||||
logger.debug(f"Obtained new access token: {self._access_token}")
|
|
||||||
return self._access_token
|
|
||||||
else:
|
|
||||||
logger.error(
|
|
||||||
f"Unexpected error on login. Response status code: {response.status_code}, body: f{response.text}"
|
|
||||||
)
|
|
||||||
except requests.exceptions.RequestException as e:
|
|
||||||
logger.exception(e)
|
|
||||||
|
|
||||||
def _fetch(self):
|
def _fetch(self):
|
||||||
access_token = self._login()
|
|
||||||
|
|
||||||
headers = {
|
headers = {
|
||||||
"Authorization": f"Bearer {access_token}",
|
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
}
|
}
|
||||||
url = f"{self._host}/data"
|
url = f"{self._host}/data"
|
||||||
|
|
|
||||||
|
|
@ -1,78 +1,20 @@
|
||||||
import time
|
|
||||||
import unittest
|
import unittest
|
||||||
from unittest.mock import Mock, patch
|
from unittest.mock import Mock, patch
|
||||||
|
|
||||||
import jwt
|
|
||||||
|
|
||||||
from caimira.store.data_service import DataService
|
from caimira.store.data_service import DataService
|
||||||
|
|
||||||
|
|
||||||
class DataServiceTests(unittest.TestCase):
|
class DataServiceTests(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
# Set up any necessary test data or configurations
|
# Set up any necessary test data or configurations
|
||||||
self.credentials = {"email": "test@example.com", "password": "password123"}
|
self.data_service = DataService.create(host="https://dataservice.example.com")
|
||||||
self.data_service = DataService.create(self.credentials, host="https://dataservice.example.com")
|
|
||||||
|
|
||||||
def test_jwt_expiration(self):
|
|
||||||
is_valid = self.data_service._is_valid(None)
|
|
||||||
self.assertFalse(is_valid)
|
|
||||||
|
|
||||||
now = time.time()
|
|
||||||
|
|
||||||
encoded = jwt.encode({"exp": now - 10}, "very secret", algorithm="HS256")
|
|
||||||
is_valid = self.data_service._is_valid(encoded)
|
|
||||||
self.assertFalse(is_valid)
|
|
||||||
|
|
||||||
encoded = jwt.encode({"exp": now}, "very secret", algorithm="HS256")
|
|
||||||
is_valid = self.data_service._is_valid(encoded)
|
|
||||||
self.assertFalse(is_valid)
|
|
||||||
|
|
||||||
encoded = jwt.encode({"exp": now + 10}, "very secret", algorithm="HS256")
|
|
||||||
is_valid = self.data_service._is_valid(encoded)
|
|
||||||
self.assertTrue(is_valid)
|
|
||||||
|
|
||||||
@patch("requests.post")
|
|
||||||
def test_login_successful(self, mock_post):
|
|
||||||
# Mock successful login response
|
|
||||||
mock_response = Mock()
|
|
||||||
mock_response.status_code = 200
|
|
||||||
mock_response.json.return_value = {"access_token": "dummy_token"}
|
|
||||||
mock_post.return_value = mock_response
|
|
||||||
|
|
||||||
# Call the login method
|
|
||||||
access_token = self.data_service._login()
|
|
||||||
|
|
||||||
# Assert that the access token is returned correctly
|
|
||||||
self.assertEqual(access_token, "dummy_token")
|
|
||||||
|
|
||||||
# Verify that the fetch method was called with the expected arguments
|
|
||||||
mock_post.assert_called_once_with(
|
|
||||||
"https://dataservice.example.com/login",
|
|
||||||
json=dict(email="test@example.com", password="password123"),
|
|
||||||
headers={"Content-Type": "application/json"},
|
|
||||||
)
|
|
||||||
|
|
||||||
@patch("requests.post")
|
|
||||||
def test_login_error(self, mock_post):
|
|
||||||
# Mock login error response
|
|
||||||
mock_post.return_value = Mock()
|
|
||||||
mock_post.return_value.status_code = 500
|
|
||||||
|
|
||||||
# Call the login method
|
|
||||||
access_token = self.data_service._login()
|
|
||||||
|
|
||||||
# Assert that the login method returns None in case of an error
|
|
||||||
self.assertIsNone(access_token)
|
|
||||||
|
|
||||||
@patch("requests.get")
|
@patch("requests.get")
|
||||||
@patch.object(DataService, "_login")
|
def test_fetch_successful(self, mock_get):
|
||||||
def test_fetch_successful(self, mock_login, mock_get):
|
|
||||||
# Mock successful fetch response
|
# Mock successful fetch response
|
||||||
mock_get.return_value = Mock()
|
mock_get.return_value = Mock()
|
||||||
mock_get.return_value.status_code = 200
|
mock_get.return_value.status_code = 200
|
||||||
mock_get.return_value.json.return_value = {"data": "dummy_data"}
|
mock_get.return_value.json.return_value = {"data": "dummy_data"}
|
||||||
# Call the fetch method with a mock access token
|
|
||||||
mock_login.return_value = "dummy_token"
|
|
||||||
data = self.data_service._fetch()
|
data = self.data_service._fetch()
|
||||||
|
|
||||||
# Assert that the data is returned correctly
|
# Assert that the data is returned correctly
|
||||||
|
|
@ -82,20 +24,17 @@ class DataServiceTests(unittest.TestCase):
|
||||||
mock_get.assert_called_once_with(
|
mock_get.assert_called_once_with(
|
||||||
"https://dataservice.example.com/data",
|
"https://dataservice.example.com/data",
|
||||||
headers={
|
headers={
|
||||||
"Authorization": "Bearer dummy_token",
|
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@patch("requests.get")
|
@patch("requests.get")
|
||||||
@patch.object(DataService, "_login")
|
def test_fetch_error(self, mock_get):
|
||||||
def test_fetch_error(self, mock_login, mock_get):
|
|
||||||
# Mock fetch error response
|
# Mock fetch error response
|
||||||
mock_get.return_value = Mock()
|
mock_get.return_value = Mock()
|
||||||
mock_get.return_value.status_code = 500
|
mock_get.return_value.status_code = 500
|
||||||
|
|
||||||
# Call the fetch method with a mock access token
|
# Call the fetch method
|
||||||
mock_login.return_value = "dummy_token"
|
|
||||||
data = self.data_service._fetch()
|
data = self.data_service._fetch()
|
||||||
|
|
||||||
# Assert that the fetch method returns None in case of an error
|
# Assert that the fetch method returns None in case of an error
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue