2021-04-14 11:00:10 +02:00
|
|
|
# standard imports
|
|
|
|
import json
|
|
|
|
import logging
|
|
|
|
import os
|
|
|
|
from typing import Dict, Union
|
|
|
|
|
|
|
|
# third-part imports
|
|
|
|
import requests
|
|
|
|
from cic_types.models.person import generate_metadata_pointer, Person
|
|
|
|
|
|
|
|
# local imports
|
|
|
|
from cic_ussd.metadata import make_request
|
|
|
|
from cic_ussd.metadata.signer import Signer
|
|
|
|
from cic_ussd.redis import cache_data
|
|
|
|
from cic_ussd.error import MetadataStoreError
|
|
|
|
|
|
|
|
|
|
|
|
logg = logging.getLogger().getChild(__name__)
|
|
|
|
|
|
|
|
|
2021-04-09 15:00:15 +02:00
|
|
|
class Metadata:
|
|
|
|
"""
|
2021-04-14 11:00:10 +02:00
|
|
|
:cvar base_url: The base url or the metadata server.
|
|
|
|
:type base_url: str
|
2021-04-09 15:00:15 +02:00
|
|
|
"""
|
|
|
|
|
|
|
|
base_url = None
|
2021-04-14 11:00:10 +02:00
|
|
|
|
|
|
|
|
|
|
|
def metadata_http_error_handler(result: requests.Response):
|
|
|
|
""" This function handles and appropriately raises errors from http requests interacting with the metadata server.
|
|
|
|
:param result: The response object from a http request.
|
|
|
|
:type result: requests.Response
|
|
|
|
"""
|
|
|
|
status_code = result.status_code
|
|
|
|
|
|
|
|
if 100 <= status_code < 200:
|
|
|
|
raise MetadataStoreError(f'Informational errors: {status_code}, reason: {result.reason}')
|
|
|
|
|
|
|
|
elif 300 <= status_code < 400:
|
|
|
|
raise MetadataStoreError(f'Redirect Issues: {status_code}, reason: {result.reason}')
|
|
|
|
|
|
|
|
elif 400 <= status_code < 500:
|
|
|
|
raise MetadataStoreError(f'Client Error: {status_code}, reason: {result.reason}')
|
|
|
|
|
|
|
|
elif 500 <= status_code < 600:
|
|
|
|
raise MetadataStoreError(f'Server Error: {status_code}, reason: {result.reason}')
|
|
|
|
|
|
|
|
|
|
|
|
class MetadataRequestsHandler(Metadata):
|
|
|
|
|
|
|
|
def __init__(self, cic_type: str, identifier: bytes, engine: str = 'pgp'):
|
|
|
|
"""
|
|
|
|
:param cic_type: The salt value with which to hash a specific metadata identifier.
|
|
|
|
:type cic_type: str
|
|
|
|
:param engine: Encryption used for sending data to the metadata server.
|
|
|
|
:type engine: str
|
|
|
|
:param identifier: A unique element of data in bytes necessary for creating a metadata pointer.
|
|
|
|
:type identifier: bytes
|
|
|
|
"""
|
|
|
|
self.cic_type = cic_type
|
|
|
|
self.engine = engine
|
|
|
|
self.headers = {
|
|
|
|
'X-CIC-AUTOMERGE': 'server',
|
|
|
|
'Content-Type': 'application/json'
|
|
|
|
}
|
|
|
|
self.identifier = identifier
|
|
|
|
self.metadata_pointer = generate_metadata_pointer(
|
|
|
|
identifier=self.identifier,
|
|
|
|
cic_type=self.cic_type
|
|
|
|
)
|
|
|
|
if self.base_url:
|
|
|
|
self.url = os.path.join(self.base_url, self.metadata_pointer)
|
|
|
|
|
|
|
|
def create(self, data: Union[Dict, str]):
|
|
|
|
""" This function is responsible for posting data to the metadata server with a corresponding metadata pointer
|
|
|
|
for storage.
|
|
|
|
:param data: The data to be stored in the metadata server.
|
|
|
|
:type data: dict|str
|
|
|
|
"""
|
|
|
|
data = json.dumps(data).encode('utf-8')
|
|
|
|
result = make_request(method='POST', url=self.url, data=data, headers=self.headers)
|
|
|
|
metadata_http_error_handler(result=result)
|
|
|
|
metadata = result.content
|
|
|
|
self.edit(data=metadata)
|
|
|
|
|
|
|
|
def edit(self, data: bytes):
|
|
|
|
""" This function is responsible for editing data in the metadata server corresponding to a unique pointer.
|
|
|
|
:param data: The data to be edited in the metadata server.
|
|
|
|
:type data: bytes
|
|
|
|
"""
|
|
|
|
cic_meta_signer = Signer()
|
|
|
|
signature = cic_meta_signer.sign_digest(data=data)
|
|
|
|
algorithm = cic_meta_signer.get_operational_key().get('algo')
|
|
|
|
decoded_data = data.decode('utf-8')
|
|
|
|
formatted_data = {
|
|
|
|
'm': data.decode('utf-8'),
|
|
|
|
's': {
|
|
|
|
'engine': self.engine,
|
|
|
|
'algo': algorithm,
|
|
|
|
'data': signature,
|
|
|
|
'digest': json.loads(data).get('digest'),
|
|
|
|
}
|
|
|
|
}
|
2021-05-19 18:25:10 +02:00
|
|
|
formatted_data = json.dumps(formatted_data)
|
2021-04-14 11:00:10 +02:00
|
|
|
result = make_request(method='PUT', url=self.url, data=formatted_data, headers=self.headers)
|
|
|
|
logg.info(f'signed metadata submission status: {result.status_code}.')
|
|
|
|
metadata_http_error_handler(result=result)
|
|
|
|
try:
|
|
|
|
decoded_identifier = self.identifier.decode("utf-8")
|
|
|
|
except UnicodeDecodeError:
|
|
|
|
decoded_identifier = self.identifier.hex()
|
|
|
|
logg.info(f'identifier: {decoded_identifier}. metadata pointer: {self.metadata_pointer} set to: {decoded_data}.')
|
|
|
|
|
|
|
|
def query(self):
|
|
|
|
"""This function is responsible for querying the metadata server for data corresponding to a unique pointer."""
|
|
|
|
result = make_request(method='GET', url=self.url)
|
|
|
|
metadata_http_error_handler(result=result)
|
2021-05-19 18:25:10 +02:00
|
|
|
response_data = result.json()
|
|
|
|
data = json.loads(response_data)
|
|
|
|
if not isinstance(data, dict):
|
|
|
|
raise ValueError(f'Invalid data object: {data}.')
|
2021-05-01 16:52:54 +02:00
|
|
|
if result.status_code == 200 and self.cic_type == ':cic.person':
|
2021-04-14 11:00:10 +02:00
|
|
|
person = Person()
|
2021-05-15 09:40:34 +02:00
|
|
|
deserialized_person = person.deserialize(person_data=data)
|
2021-04-14 11:00:10 +02:00
|
|
|
data = json.dumps(deserialized_person.serialize())
|
|
|
|
cache_data(self.metadata_pointer, data=data)
|
|
|
|
logg.debug(f'caching: {data} with key: {self.metadata_pointer}')
|