Latest updates.

This commit is contained in:
Daniele Maglie 2024-02-07 14:53:48 +01:00
parent 579ed1de04
commit 9d6a79bb2c
32 changed files with 1006 additions and 300 deletions

View file

@ -1,12 +1,5 @@
[build-system] [build-system]
requires = [ requires = [
"setuptools>=54", "setuptools>=54",
"setuptools-rust"
] ]
build-backend = "setuptools.build_meta" build-backend = "setuptools.build_meta"
[[tool.setuptools-rust.ext-modules]]
target = "rs_cidr_verifier"
path = "src/piracyshield_service/whitelist/cidr/Cargo.toml"
binding = "PyO3"
py-limited-api = "auto"

View file

@ -29,7 +29,7 @@ class AccountExistsByIdentifierService(BaseService):
def execute(self, account_id: str) -> bool | Exception: def execute(self, account_id: str) -> bool | Exception:
try: try:
response = self.data_storage.exists_by_identifier( response = self.data_storage.exists_by_identifier(
account_id = account_id identifier = account_id
) )
batch = response.batch() batch = response.batch()

View file

@ -0,0 +1,57 @@
from __future__ import annotations
from piracyshield_service.base import BaseService
from piracyshield_component.exception import ApplicationException
from piracyshield_data_storage.account.storage import AccountStorageGetException
from piracyshield_service.account.errors import AccountErrorCode, AccountErrorMessage
class AccountGetActiveService(BaseService):
"""
Fetches all active accounts.
"""
data_storage = None
def __init__(self, data_storage: AccountStorage):
"""
Inizialize logger and required modules.
"""
# child data storage class
self.data_storage = data_storage()
super().__init__()
def execute(self) -> list | Exception:
try:
response = self.data_storage.get_active()
batch = response.batch()
if not len(batch):
self.logger.debug(f'No account found')
raise ApplicationException(AccountErrorCode.ACCOUNT_NOT_FOUND, AccountErrorMessage.ACCOUNT_NOT_FOUND)
return list(batch)
except AccountStorageGetException as e:
self.logger.error(f'Could not retrieve any account')
raise ApplicationException(AccountErrorCode.GENERIC, AccountErrorMessage.GENERIC, e)
def _schedule_task(self):
pass
def _validate_parameters(self):
pass
def _prepare_configs(self):
pass
def _prepare_modules(self):
pass

View file

@ -11,17 +11,17 @@ class AuthenticationErrorCode:
USER_NON_ACTIVE = '2001' USER_NON_ACTIVE = '2001'
PASSWORD_NON_VALID = '2002' PASSWORD_NON_VALID = '2001'
PASSWORD_MISMATCH = '2002' PASSWORD_MISMATCH = '2001'
TOKEN_REFRESH_USER_NON_ACTIVE = '2003' TOKEN_REFRESH_USER_NON_ACTIVE = '2002'
TOKEN_MISMATCH = '2003' TOKEN_MISMATCH = '2002'
TOKEN_EXPIRED = '2003' TOKEN_EXPIRED = '2002'
MAX_LOGIN_ATTEMPTS = '2004' MAX_LOGIN_ATTEMPTS = '2003'
class AuthenticationErrorMessage: class AuthenticationErrorMessage:

View file

@ -17,13 +17,15 @@ class DDAErrorCode:
NON_VALID_ACCOUNT_ROLE = '7007' NON_VALID_ACCOUNT_ROLE = '7007'
INSTANCE_EXISTS = '7008' UNKNOWN_DDA_IDENTIFIER = '7008'
CANNOT_REMOVE = '7009' INSTANCE_EXISTS = '7009'
INSTANCE_USED = '7010' CANNOT_REMOVE = '7010'
CANNOT_SET_STATUS = '7011' INSTANCE_USED = '7011'
CANNOT_SET_STATUS = '7012'
class DDAErrorMessage: class DDAErrorMessage:
@ -43,6 +45,8 @@ class DDAErrorMessage:
NON_VALID_ACCOUNT_ROLE = 'Account with wrong role. Only Reporter accounts are allowed to obtain and use a DDA instance.' NON_VALID_ACCOUNT_ROLE = 'Account with wrong role. Only Reporter accounts are allowed to obtain and use a DDA instance.'
UNKNOWN_DDA_IDENTIFIER = 'DDA identifier not found.'
INSTANCE_EXISTS = 'This item has been already created.' INSTANCE_EXISTS = 'This item has been already created.'
CANNOT_REMOVE = 'The item could not be removed. Ensure you have proper permissions or to specify a valid item.' CANNOT_REMOVE = 'The item could not be removed. Ensure you have proper permissions or to specify a valid item.'

View file

@ -0,0 +1,58 @@
from __future__ import annotations
from piracyshield_service.base import BaseService
from piracyshield_component.exception import ApplicationException
from piracyshield_data_storage.dda.storage import DDAStorage, DDAStorageGetException
from piracyshield_service.dda.errors import DDAErrorCode, DDAErrorMessage
class DDAGetByIdentifierService(BaseService):
"""
Get a single DDA by its identifier.
"""
data_storage = None
def __init__(self):
"""
Inizialize logger and required modules.
"""
super().__init__()
self._prepare_modules()
def execute(self, dda_id: str) -> dict | Exception:
try:
response = self.data_storage.get_by_identifier(
dda_id = dda_id
)
if response.empty():
self.logger.debug(f'No DDA found for this identifier `{dda_id}`')
raise ApplicationException(DDAErrorCode.UNKNOWN_DDA_IDENTIFIER, DDAErrorMessage.UNKNOWN_DDA_IDENTIFIER)
document = next(response, None)
return document
except DDAStorageGetException as e:
self.logger.error(f'Cannot get the DDA')
raise ApplicationException(DDAErrorCode.GENERIC, DDAErrorMessage.GENERIC, e)
def _schedule_task(self):
pass
def _validate_parameters(self):
pass
def _prepare_configs(self):
pass
def _prepare_modules(self):
self.data_storage = DDAStorage()

View file

@ -0,0 +1,59 @@
from __future__ import annotations
from piracyshield_service.base import BaseService
from piracyshield_component.exception import ApplicationException
from piracyshield_data_storage.dda.storage import DDAStorage, DDAStorageGetException
from piracyshield_service.dda.errors import DDAErrorCode, DDAErrorMessage
class DDAGetByIdentifierForReporterService(BaseService):
"""
Get a single DDA by its identifier.
"""
data_storage = None
def __init__(self):
"""
Inizialize logger and required modules.
"""
super().__init__()
self._prepare_modules()
def execute(self, dda_id: str, reporter_id: str) -> dict | Exception:
try:
response = self.data_storage.get_by_identifier_for_reporter(
dda_id = dda_id,
reporter_id = reporter_id
)
if response.empty():
self.logger.debug(f'No DDA found for this identifier `{dda_id}`')
raise ApplicationException(DDAErrorCode.UNKNOWN_DDA_IDENTIFIER, DDAErrorMessage.UNKNOWN_DDA_IDENTIFIER)
document = next(response, None)
return document
except DDAStorageGetException as e:
self.logger.error(f'Cannot get the DDA')
raise ApplicationException(DDAErrorCode.GENERIC, DDAErrorMessage.GENERIC, e)
def _schedule_task(self):
pass
def _validate_parameters(self):
pass
def _prepare_configs(self):
pass
def _prepare_modules(self):
self.data_storage = DDAStorage()

View file

@ -2,6 +2,7 @@ from __future__ import annotations
from piracyshield_service.base import BaseService from piracyshield_service.base import BaseService
from piracyshield_component.config import Config
from piracyshield_component.utils.time import Time from piracyshield_component.utils.time import Time
from piracyshield_component.exception import ApplicationException from piracyshield_component.exception import ApplicationException
@ -9,14 +10,16 @@ from piracyshield_data_model.forensic.archive.model import ForensicArchiveModel,
from piracyshield_data_storage.forensic.storage import ForensicStorage, ForensicStorageCreateException, ForensicStorageGetException, ForensicStorageUpdateException from piracyshield_data_storage.forensic.storage import ForensicStorage, ForensicStorageCreateException, ForensicStorageGetException, ForensicStorageUpdateException
from piracyshield_service.log.ticket.create import LogTicketCreateService from piracyshield_data_storage.cache.storage import CacheStorage
from piracyshield_service.importer.save_file import ImporterSaveFileService from piracyshield_service.log.ticket.create import LogTicketCreateService
from piracyshield_service.forensic.tasks.analyze_forensic_archive import analyze_forensic_archive_task_caller from piracyshield_service.forensic.tasks.analyze_forensic_archive import analyze_forensic_archive_task_caller
from piracyshield_service.forensic.errors import ForensicErrorCode, ForensicErrorMessage from piracyshield_service.forensic.errors import ForensicErrorCode, ForensicErrorMessage
import os
class ForensicCreateArchiveService(BaseService): class ForensicCreateArchiveService(BaseService):
""" """
@ -27,10 +30,14 @@ class ForensicCreateArchiveService(BaseService):
log_ticket_create_service = None log_ticket_create_service = None
data_storage_cache = None
data_storage = None data_storage = None
data_model = None data_model = None
application_archive_config = None
def __init__(self): def __init__(self):
""" """
Inizialize logger and required modules. Inizialize logger and required modules.
@ -38,9 +45,11 @@ class ForensicCreateArchiveService(BaseService):
super().__init__() super().__init__()
self._prepare_configs()
self._prepare_modules() self._prepare_modules()
def execute(self, ticket_id: str, archive_name: str, archive_content: bytes) -> bool | Exception: def execute(self, ticket_id: str, archive_name: str) -> bool | Exception:
""" """
:param ticket_id: the ticket identifier related to the archive. :param ticket_id: the ticket identifier related to the archive.
:param archive_name: the name of the archive. :param archive_name: the name of the archive.
@ -54,19 +63,24 @@ class ForensicCreateArchiveService(BaseService):
if not self._ticket_id_exists(ticket_id): if not self._ticket_id_exists(ticket_id):
raise ApplicationException(ForensicErrorCode.NO_HASH_FOR_TICKET, ForensicErrorMessage.NO_HASH_FOR_TICKET) raise ApplicationException(ForensicErrorCode.NO_HASH_FOR_TICKET, ForensicErrorMessage.NO_HASH_FOR_TICKET)
# put the file in cache # the file is already in the cache
cache_filename = self.importer_save_file_service.execute( if not self.data_storage_cache.exists(archive_name):
filename = model.get('name'), self.logger.debug(f'Forensic evidence `{archive_name}` not found in cache')
content = archive_content
)
self.logger.debug(f'Forensic evidence `{cache_filename}` moved to cache for ticket `{ticket_id}`') raise ApplicationException(ForensicErrorCode.GENERIC, ForensicErrorMessage.GENERIC)
extension = self._get_extension(archive_name)
if self._has_supported_extension(extension) == False:
raise ApplicationException(ForensicErrorCode.EXTENSION_NOT_SUPPORTED, ForensicErrorMessage.EXTENSION_NOT_SUPPORTED)
self.logger.debug(f'New forensic evidence `{archive_name}` in cache for ticket `{ticket_id}`')
try: try:
# update ticket with the archive name # update ticket with the archive name
self.data_storage.update_archive_name( self.data_storage.update_archive_name(
ticket_id = ticket_id, ticket_id = ticket_id,
archive_name = cache_filename, archive_name = archive_name,
status = model.get('status'), status = model.get('status'),
updated_at = Time.now_iso8601() updated_at = Time.now_iso8601()
) )
@ -114,6 +128,28 @@ class ForensicCreateArchiveService(BaseService):
raise ApplicationException(ForensicErrorCode.GENERIC, ForensicErrorMessage.GENERIC, e) raise ApplicationException(ForensicErrorCode.GENERIC, ForensicErrorMessage.GENERIC, e)
def _get_extension(self, filename: str) -> str:
"""
Extracts the extension from the filename.
:param filename: the name of the file.
:return: the extension of the file.
"""
_, extension = os.path.splitext(filename)
return extension
def _has_supported_extension(self, extension: str) -> bool:
"""
Checks wether the extension is supported.
:param extension: the extension of the file.
:return: true if supported.
"""
return extension.lower() in self.application_archive_config.get('supported_extensions')
def _schedule_task(self, ticket_id: str) -> bool | Exception: def _schedule_task(self, ticket_id: str) -> bool | Exception:
""" """
Schedules the archive analysis. Schedules the archive analysis.
@ -155,10 +191,14 @@ class ForensicCreateArchiveService(BaseService):
return model.to_dict() return model.to_dict()
except ForensicArchiveModelNameException: except ForensicArchiveModelNameException:
raise ApplicationException(ForensicErrorCode.ARCHIVE_NAME, TicketErrorMessage.ARCHIVE_NAME) raise ApplicationException(ForensicErrorCode.ARCHIVE_NAME, ForensicErrorMessage.ARCHIVE_NAME)
def _prepare_configs(self): def _prepare_configs(self) -> None:
pass """
Loads the configs.
"""
self.application_archive_config = Config('application').get('archive')
def _prepare_modules(self): def _prepare_modules(self):
""" """
@ -169,6 +209,6 @@ class ForensicCreateArchiveService(BaseService):
self.data_storage = ForensicStorage() self.data_storage = ForensicStorage()
self.importer_save_file_service = ImporterSaveFileService() self.data_storage_cache = CacheStorage()
self.log_ticket_create_service = LogTicketCreateService() self.log_ticket_create_service = LogTicketCreateService()

View file

@ -42,12 +42,6 @@ class ForensicCreateHashService(BaseService):
hash_string = hash_string hash_string = hash_string
) )
# check for already inserted hashes strings
if self._hash_string_exists(hash_string):
raise ApplicationException(ForensicErrorCode.HASH_STRING_EXISTS, ForensicErrorMessage.HASH_STRING_EXISTS)
self.logger.info(f'Created new hash `{hash_string}` for ticket `{ticket_id}`')
document = self._build_document( document = self._build_document(
model = model, model = model,
forensic_id = self._generate_forensic_id(), forensic_id = self._generate_forensic_id(),
@ -56,6 +50,20 @@ class ForensicCreateHashService(BaseService):
now = Time.now_iso8601() now = Time.now_iso8601()
) )
# let's search for a pre-existent hash
existent_hash = self._hash_string_exists(hash_string)
# we got one, let's update our new entry with those values
if existent_hash:
if 'archive_name' in existent_hash:
document['archive_name'] = existent_hash.get('archive_name')
if 'status' in existent_hash:
document['status'] = existent_hash.get('status')
if 'reason' in existent_hash:
document['reason'] = existent_hash.get('reason')
try: try:
self.data_storage.insert(document) self.data_storage.insert(document)
@ -64,6 +72,8 @@ class ForensicCreateHashService(BaseService):
raise ApplicationException(ForensicErrorCode.GENERIC, ForensicErrorMessage.GENERIC, e) raise ApplicationException(ForensicErrorCode.GENERIC, ForensicErrorMessage.GENERIC, e)
self.logger.info(f'Created hash `{hash_string}` for ticket `{ticket_id}`')
return True return True
def _hash_string_exists(self, hash_string: str) -> bool | Exception: def _hash_string_exists(self, hash_string: str) -> bool | Exception:
@ -72,12 +82,14 @@ class ForensicCreateHashService(BaseService):
hash_string = hash_string hash_string = hash_string
) )
if response.next(): if response.empty():
self.logger.debug(f'Hash string found for `{hash_string}`') return False
return True self.logger.debug(f'Found pre-existent hash for `{hash_string}`')
return False document = next(response, None)
return document
except ForensicStorageGetException as e: except ForensicStorageGetException as e:
self.logger.error(f'Could not verify `{hash_string}` existence') self.logger.error(f'Could not verify `{hash_string}` existence')

View file

@ -1,23 +1,24 @@
class ForensicErrorCode: class ForensicErrorCode:
GENERIC = '9001' GENERIC = '9000'
ARCHIVE_NAME = '9002' ARCHIVE_NAME = '9001'
HASH_TYPE_NOT_SUPPORTED = '9003' HASH_TYPE_NOT_SUPPORTED = '9002'
HASH_STRING_EMPTY = '9004' HASH_STRING_EMPTY = '9003'
HASH_STRING_NON_VALID = '9005' HASH_STRING_NON_VALID = '9004'
HASH_STRING_EXISTS = '9006' NO_HASH_FOR_TICKET = '9005'
NO_HASH_FOR_TICKET = '9007' EXTENSION_NOT_SUPPORTED = '9006'
class ForensicErrorMessage: class ForensicErrorMessage:
GENERIC = 'Error during the handling of the forensic evidence.' # error during the handling of the forensic evidence
GENERIC = 'Generic error.'
ARCHIVE_NAME = 'The archive name contains non valid characters.' ARCHIVE_NAME = 'The archive name contains non valid characters.'
@ -27,6 +28,6 @@ class ForensicErrorMessage:
HASH_STRING_NON_VALID = 'Forensic evidence hash non valid.' HASH_STRING_NON_VALID = 'Forensic evidence hash non valid.'
HASH_STRING_EXISTS = 'The hash string value is already present, meaning that this forensic evidence archive has already been submitted.'
NO_HASH_FOR_TICKET = 'This ticket does not have any forensic evidence hash.' NO_HASH_FOR_TICKET = 'This ticket does not have any forensic evidence hash.'
EXTENSION_NOT_SUPPORTED = 'The file extension is not supported.'

View file

@ -1,14 +1,18 @@
from piracyshield_service.task.base import BaseTask from piracyshield_service.task.base import BaseTask
from piracyshield_component.utils.time import Time
from piracyshield_component.exception import ApplicationException
from piracyshield_data_model.forensic.archive.status.model import ForensicArchiveStatusModel from piracyshield_data_model.forensic.archive.status.model import ForensicArchiveStatusModel
from piracyshield_forensic.analyze import ForensicAnalysis from piracyshield_forensic.analyze import ForensicAnalysis
from piracyshield_service.forensic.update_archive_status import ForensicUpdateArchiveStatusService from piracyshield_service.forensic.update_archive_status import ForensicUpdateArchiveStatusService
from piracyshield_service.forensic.get_by_ticket import ForensicGetByTicketService
from piracyshield_service.log.ticket.create import LogTicketCreateService from piracyshield_service.log.ticket.create import LogTicketCreateService
from piracyshield_component.exception import ApplicationException from piracyshield_data_storage.forensic.storage import ForensicStorage, ForensicStorageUpdateException
class AnalyzeForensicArchiveTask(BaseTask): class AnalyzeForensicArchiveTask(BaseTask):
@ -22,8 +26,12 @@ class AnalyzeForensicArchiveTask(BaseTask):
update_archive_status_service = None update_archive_status_service = None
get_by_ticket_service = None
ticket_id = None ticket_id = None
data_storage = None
def __init__(self, ticket_id: str): def __init__(self, ticket_id: str):
super().__init__() super().__init__()
@ -66,11 +74,24 @@ class AnalyzeForensicArchiveTask(BaseTask):
# NOTE: we won't clean the package from the cache here as we might want to schedule a cleaning process separately. # NOTE: we won't clean the package from the cache here as we might want to schedule a cleaning process separately.
self.update_archive_status_service.execute( # retrieve forensic data associated to this ticket identifier
ticket_id = self.ticket_id, forensic_data = self.get_by_ticket_service.execute(
status = ForensicArchiveStatusModel.APPROVED.value ticket_id = self.ticket_id
) )
# get other tickets with the same hash, if any
tickets = self._hash_string_exists(
hash_string = forensic_data.get('hash_string')
)
for ticket_data in tickets:
self.data_storage.update_archive_name(
ticket_id = ticket_data.get('ticket_id'),
archive_name = forensic_data.get('archive_name'),
status = ForensicArchiveStatusModel.APPROVED.value,
updated_at = Time.now_iso8601()
)
# log success operation # log success operation
self.log_ticket_create_service.execute( self.log_ticket_create_service.execute(
ticket_id = self.ticket_id, ticket_id = self.ticket_id,
@ -79,15 +100,35 @@ class AnalyzeForensicArchiveTask(BaseTask):
return True return True
def _hash_string_exists(self, hash_string: str) -> bool | Exception:
try:
response = self.data_storage.exists_hash_string(
hash_string = hash_string
)
if response.empty():
return False
return list(response.batch())
except ForensicStorageGetException as e:
self.logger.error(f'Could not verify `{hash_string}` existence')
raise ApplicationException(ForensicErrorCode.GENERIC, ForensicErrorMessage.GENERIC, e)
def before_run(self): def before_run(self):
""" """
Initialize required modules. Initialize required modules.
""" """
self.data_storage = ForensicStorage()
self.log_ticket_create_service = LogTicketCreateService() self.log_ticket_create_service = LogTicketCreateService()
self.forensic_analysis = ForensicAnalysis() self.forensic_analysis = ForensicAnalysis()
self.get_by_ticket_service = ForensicGetByTicketService()
self.update_archive_status_service = ForensicUpdateArchiveStatusService() self.update_archive_status_service = ForensicUpdateArchiveStatusService()
def after_run(self): def after_run(self):

View file

@ -1,12 +1,15 @@
# TODO: this extension will be merged into forensic.
class ImporterErrorCode: class ImporterErrorCode:
GENERIC = '9000' GENERIC = '9000'
EXTENSION_NOT_SUPPORTED = '9001' EXTENSION_NOT_SUPPORTED = '9006'
class ImporterErrorMessage: class ImporterErrorMessage:
GENERIC = 'Error during the saving of the file.' # error during the saving of the file
GENERIC = 'Generic error.'
EXTENSION_NOT_SUPPORTED = 'The file extension is not supported.' EXTENSION_NOT_SUPPORTED = 'The file extension is not supported.'

View file

@ -1,7 +1,7 @@
class LogErrorCode: class LogErrorCode:
GENERIC = '8000' GENERIC = '1300'
class LogErrorMessage: class LogErrorMessage:

View file

@ -0,0 +1,18 @@
from __future__ import annotations
from piracyshield_service.account.get_all import AccountGetAllService
from piracyshield_data_storage.provider.storage import ProviderStorage
class ProviderGetActiveService(AccountGetAllService):
"""
Retrieves all the active provider accounts.
"""
def __init__(self):
"""
Pass the data storage to the parent class.
"""
super().__init__(ProviderStorage)

View file

@ -22,18 +22,27 @@ from piracyshield_data_model.ticket.model import (
TicketModelAssignedToNonValidException TicketModelAssignedToNonValidException
) )
from piracyshield_data_model.ticket.status.model import TicketStatusModel from piracyshield_data_model.ticket.item.model import (
TicketItemModel,
from piracyshield_service.ticket.relation.establish import TicketRelationEstablishService TicketItemModelTicketIdentifierNonValidException,
from piracyshield_service.ticket.relation.abandon import TicketRelationAbandonService TicketItemModelTicketItemIdentifierNonValidException,
TicketItemModelGenreNonValidException,
TicketItemModelFQDNMissingException,
TicketItemModelFQDNNonValidException,
TicketItemModelIPv4MissingException,
TicketItemModelIPv4NonValidException,
TicketItemModelIPv6MissingException,
TicketItemModelIPv6NonValidException,
TicketItemModelProviderIdentifierMissingException,
TicketItemModelProviderIdentifierNonValidException
)
from piracyshield_data_storage.ticket.storage import TicketStorage, TicketStorageCreateException from piracyshield_data_storage.ticket.storage import TicketStorage, TicketStorageCreateException
from piracyshield_service.provider.get_all import ProviderGetAllService from piracyshield_service.provider.get_active import ProviderGetActiveService
from piracyshield_service.provider.exists_by_identifier import ProviderExistsByIdentifierService from piracyshield_service.provider.exists_by_identifier import ProviderExistsByIdentifierService
from piracyshield_service.ticket.tasks.ticket_initialize import ticket_initialize_task_caller from piracyshield_service.ticket.tasks.ticket_create import ticket_create_task_caller
from piracyshield_service.ticket.tasks.ticket_autoclose import ticket_autoclose_task_caller
from piracyshield_service.log.ticket.create import LogTicketCreateService from piracyshield_service.log.ticket.create import LogTicketCreateService
@ -43,6 +52,7 @@ from piracyshield_service.forensic.remove_by_ticket import ForensicRemoveByTicke
from piracyshield_service.dda.is_assigned_to_account import DDAIsAssignedToAccountService from piracyshield_service.dda.is_assigned_to_account import DDAIsAssignedToAccountService
from piracyshield_service.ticket.errors import TicketErrorCode, TicketErrorMessage from piracyshield_service.ticket.errors import TicketErrorCode, TicketErrorMessage
from piracyshield_service.ticket.item.errors import TicketItemErrorCode, TicketItemErrorMessage
from datetime import datetime, timedelta from datetime import datetime, timedelta
@ -58,13 +68,11 @@ class TicketCreateService(BaseService):
log_ticket_create_service = None log_ticket_create_service = None
ticket_relation_establish_service = None
ticket_relation_abandon_service = None
provider_exists_by_identifier_service = None provider_exists_by_identifier_service = None
provider_get_all_service = None provider_get_active_service = None
ticket_item_data_model = None
data_model = None data_model = None
@ -92,6 +100,23 @@ class TicketCreateService(BaseService):
created_by: str, created_by: str,
description: str = None description: str = None
) -> tuple | Exception: ) -> tuple | Exception:
# filter duplicates
fqdn = list(set(fqdn))
ipv4 = list(set(ipv4))
ipv6 = list(set(ipv6))
assigned_to = list(set(assigned_to))
# TODO: do not hardcode this.
# do not procees if ticket items exceed maximum limits
if len(fqdn) > 1000:
raise ApplicationException(TicketErrorCode.TOO_MANY_FQDN, TicketErrorMessage.TOO_MANY_FQDN)
if len(ipv4) > 1000:
raise ApplicationException(TicketErrorCode.TOO_MANY_IPV4, TicketErrorMessage.TOO_MANY_IPV4)
if len(ipv6) > 1000:
raise ApplicationException(TicketErrorCode.TOO_MANY_IPV6, TicketErrorMessage.TOO_MANY_IPV6)
model = self._validate_parameters( model = self._validate_parameters(
ticket_id = self._generate_ticket_id(), ticket_id = self._generate_ticket_id(),
dda_id = dda_id, dda_id = dda_id,
@ -102,25 +127,53 @@ class TicketCreateService(BaseService):
assigned_to = assigned_to assigned_to = assigned_to
) )
# check if the DDA identifier is assigned to this account # formal validation before going on with the creation process
if self.dda_is_assigned_to_account_service.execute( if fqdn:
dda_id = dda_id, for fqdn_value in fqdn:
account_id = created_by self._validate_ticket_item(
) == False: ticket_id = model.get('ticket_id'),
raise ApplicationException(TicketErrorCode.UNKNOWN_DDA_IDENTIFIER, TicketErrorMessage.UNKNOWN_DDA_IDENTIFIER) value = fqdn_value,
genre = 'fqdn'
)
# if specified, validate each provider_id if ipv4:
if assigned_to: for ipv4_value in ipv4:
self._validate_ticket_item(
ticket_id = model.get('ticket_id'),
value = ipv4_value,
genre = 'ipv4'
)
if ipv6:
for ipv6_value in ipv6:
self._validate_ticket_item(
ticket_id = model.get('ticket_id'),
value = ipv6_value,
genre = 'ipv6'
)
# verify DDA
self._verify_dda(
dda_id = dda_id,
created_by = created_by
)
if len(assigned_to):
# if specified, validate each provider identifier
self._verify_assigned_to(assigned_to) self._verify_assigned_to(assigned_to)
model['assigned_to'] = assigned_to
# otherwise collect all the providers and assign them to the ticket # otherwise collect all the providers and assign them to the ticket
else: else:
providers = self.provider_get_all_service.execute()
model['assigned_to'] = [] model['assigned_to'] = []
for provider in providers: active_providers = self.provider_get_active_service.execute()
model['assigned_to'].append(provider.get('account_id'))
for provider in active_providers:
provider_id = provider.get('account_id')
model['assigned_to'].append(provider_id)
self.forensic_create_hash_service.execute( self.forensic_create_hash_service.execute(
ticket_id = model.get('ticket_id'), ticket_id = model.get('ticket_id'),
@ -128,34 +181,13 @@ class TicketCreateService(BaseService):
reporter_id = created_by reporter_id = created_by
) )
# proceed to build the relation item <-> provider
# this part provides a check for duplicates, whitelisted items and error tickets
(fqdn_ticket_items, ipv4_ticket_items, ipv6_ticket_items) = self.ticket_relation_establish_service.execute(
ticket_id = model.get('ticket_id'),
providers = model.get('assigned_to'),
fqdn = model.get('fqdn') or None,
ipv4 = model.get('ipv4') or None,
ipv6 = model.get('ipv6') or None
)
(initialize_job_id, autoclose_job_id) = self._schedule_task(
ticket_id = model.get('ticket_id'),
revoke_time = model.get('settings').get('revoke_time'),
autoclose_time = model.get('settings').get('autoclose_time')
)
document = self._build_document( document = self._build_document(
model = model, model = model,
fqdn = fqdn, fqdn = fqdn,
ipv4 = ipv4, ipv4 = ipv4,
ipv6 = ipv6, ipv6 = ipv6,
now = Time.now_iso8601(), now = Time.now_iso8601(),
created_by = created_by, created_by = created_by
tasks = [
# append the task id so we can cancel its execution if needed
initialize_job_id,
autoclose_job_id
]
) )
try: try:
@ -163,14 +195,8 @@ class TicketCreateService(BaseService):
self.data_storage.insert(document) self.data_storage.insert(document)
except TicketStorageCreateException as e: except TicketStorageCreateException as e:
self.ticket_relation_abandon_service.execute(model.get('ticket_id'))
self.forensic_remove_by_ticket_service.execute(model.get('ticket_id')) self.forensic_remove_by_ticket_service.execute(model.get('ticket_id'))
# clean created tasks
for single_task in document.get('tasks'):
self.task_service.remove(single_task)
self.logger.error(f'Could not create the ticket') self.logger.error(f'Could not create the ticket')
raise ApplicationException(TicketErrorCode.GENERIC, TicketErrorMessage.GENERIC, e) raise ApplicationException(TicketErrorCode.GENERIC, TicketErrorMessage.GENERIC, e)
@ -183,6 +209,11 @@ class TicketCreateService(BaseService):
self.logger.info(f'Ticket `{model.get("ticket_id")}` created by `{document.get("metadata").get("created_by")}`') self.logger.info(f'Ticket `{model.get("ticket_id")}` created by `{document.get("metadata").get("created_by")}`')
# initialize the creation of the ticket items
self._schedule_task(
ticket_data = model
)
return ( return (
# ticket identifier # ticket identifier
model.get('ticket_id'), model.get('ticket_id'),
@ -198,6 +229,16 @@ class TicketCreateService(BaseService):
return self.identifier.generate() return self.identifier.generate()
def _verify_dda(self, dda_id: str, created_by: str) -> bool | Exception:
# check if the DDA identifier is assigned to this account
if self.dda_is_assigned_to_account_service.execute(
dda_id = dda_id,
account_id = created_by
) == False:
raise ApplicationException(TicketErrorCode.UNKNOWN_DDA_IDENTIFIER, TicketErrorMessage.UNKNOWN_DDA_IDENTIFIER)
return True
def _verify_assigned_to(self, assigned_to: list) -> bool | Exception: def _verify_assigned_to(self, assigned_to: list) -> bool | Exception:
""" """
Verifies each provider in the list. Verifies each provider in the list.
@ -206,27 +247,22 @@ class TicketCreateService(BaseService):
:return: true if correct, exception if not. :return: true if correct, exception if not.
""" """
try: for provider_id in assigned_to:
for provider_id in assigned_to: if self.provider_exists_by_identifier_service.execute(
if self.provider_exists_by_identifier_service.execute( account_id = provider_id
account_id = provider_id ) == False:
) == False: self.logger.error(f'Could not get assigned accounts: `{assigned_to}`')
raise ApplicationException(TicketErrorCode.NON_EXISTENT_ASSIGNED_TO, TicketErrorMessage.NON_EXISTENT_ASSIGNED_TO)
raise ApplicationException(TicketErrorCode.NON_EXISTENT_ASSIGNED_TO, TicketErrorMessage.NON_EXISTENT_ASSIGNED_TO)
return True return True
except:
self.logger.error(f'Could not get assigned accounts: `{assigned_to}`')
raise ApplicationException(TicketErrorCode.GENERIC, TicketErrorMessage.GENERIC, e)
def _build_document( def _build_document(
self, self,
model: dict, model: dict,
fqdn: list, fqdn: list,
ipv4: list, ipv4: list,
ipv6: list, ipv6: list,
tasks: list,
now: str, now: str,
created_by: str created_by: str
): ):
@ -241,7 +277,7 @@ class TicketCreateService(BaseService):
'status': model.get('status'), 'status': model.get('status'),
'assigned_to': model.get('assigned_to'), 'assigned_to': model.get('assigned_to'),
'settings': model.get('settings'), 'settings': model.get('settings'),
'tasks': tasks, 'tasks': [],
'metadata': { 'metadata': {
# creation date # creation date
'created_at': now, 'created_at': now,
@ -254,31 +290,16 @@ class TicketCreateService(BaseService):
} }
} }
def _schedule_task(self, ticket_id: str, revoke_time: int, autoclose_time: int) -> tuple | Exception: def _schedule_task(self, ticket_data: dict) -> None | Exception:
# schedule the initialization of the ticket
# move the ticket status to open after X seconds and perform the initial operations
try: try:
initialize_job_id = self.task_service.create( self.task_service.create(
task_caller = ticket_initialize_task_caller, task_caller = ticket_create_task_caller,
delay = revoke_time, delay = 1,
ticket_id = ticket_id ticket_data = ticket_data
) )
# move the ticket status to close after X seconds
autoclose_job_id = self.task_service.create(
task_caller = ticket_autoclose_task_caller,
delay = autoclose_time,
ticket_id = ticket_id
)
return (initialize_job_id, autoclose_job_id)
except Exception as e: except Exception as e:
self.ticket_relation_abandon_service.execute(ticket_id) self.logger.error(f'Could not create ticket items for `{ticket_data.get("ticket_id")}`')
self.forensic_remove_by_ticket_service.execute(ticket_id)
self.logger.error(f'Could not create the task for `{ticket_id}`')
raise ApplicationException(TicketErrorCode.GENERIC, TicketErrorMessage.GENERIC, e) raise ApplicationException(TicketErrorCode.GENERIC, TicketErrorMessage.GENERIC, e)
@ -335,25 +356,44 @@ class TicketCreateService(BaseService):
except TicketModelAssignedToNonValidException: except TicketModelAssignedToNonValidException:
raise ApplicationException(TicketErrorCode.NON_VALID_ASSIGNED_TO, TicketErrorMessage.NON_VALID_ASSIGNED_TO) raise ApplicationException(TicketErrorCode.NON_VALID_ASSIGNED_TO, TicketErrorMessage.NON_VALID_ASSIGNED_TO)
def _validate_ticket_item(self, ticket_id: str, value: str, genre: str) -> dict | Exception:
try:
self.ticket_item_data_model(
ticket_id = ticket_id, # placeholder
ticket_item_id = ticket_id, # placeholder
provider_id = ticket_id, # placeholder
value = value,
genre = genre,
is_active = False, # placeholder
is_duplicate = False, # placeholder
is_whitelisted = False, # placeholder
is_error = False # placeholder
)
return True
except Exception as e:
self.logger.error(f'Could not create the ticket item `{value}` for `{ticket_id}`')
raise ApplicationException(TicketItemErrorCode.GENERIC, TicketItemErrorMessage.GENERIC, e)
def _prepare_configs(self): def _prepare_configs(self):
pass pass
def _prepare_modules(self): def _prepare_modules(self):
self.data_model = TicketModel self.data_model = TicketModel
self.ticket_item_data_model = TicketItemModel
self.data_storage = TicketStorage() self.data_storage = TicketStorage()
self.identifier = Identifier() self.identifier = Identifier()
self.ticket_relation_establish_service = TicketRelationEstablishService()
self.ticket_relation_abandon_service = TicketRelationAbandonService()
self.forensic_create_hash_service = ForensicCreateHashService() self.forensic_create_hash_service = ForensicCreateHashService()
self.forensic_remove_by_ticket_service = ForensicRemoveByTicketService() self.forensic_remove_by_ticket_service = ForensicRemoveByTicketService()
self.provider_get_all_service = ProviderGetAllService() self.provider_get_active_service = ProviderGetActiveService()
self.provider_exists_by_identifier_service = ProviderExistsByIdentifierService() self.provider_exists_by_identifier_service = ProviderExistsByIdentifierService()

View file

@ -37,6 +37,12 @@ class TicketErrorCode:
TICKET_NOT_FOUND = '4017' TICKET_NOT_FOUND = '4017'
TOO_MANY_FQDN = '4018'
TOO_MANY_IPV4 = '4019'
TOO_MANY_IPV6 = '4020'
class TicketErrorMessage: class TicketErrorMessage:
GENERIC = 'Error during the handling of the ticket.' GENERIC = 'Error during the handling of the ticket.'
@ -74,3 +80,9 @@ class TicketErrorMessage:
REPORT_ERROR_TIME_EXCEEDED = 'Cannot create the ticket, exceeded max error reporting time.' REPORT_ERROR_TIME_EXCEEDED = 'Cannot create the ticket, exceeded max error reporting time.'
TICKET_NOT_FOUND = 'Ticket not found.' TICKET_NOT_FOUND = 'Ticket not found.'
TOO_MANY_FQDN = 'Too many ticket FQDN items.'
TOO_MANY_IPV4 = 'Too many IPv4 items.'
TOO_MANY_IPV6 = 'Too many IPv6 items.'

View file

@ -0,0 +1,122 @@
from __future__ import annotations
from piracyshield_service.base import BaseService
from piracyshield_component.utils.time import Time
from piracyshield_component.exception import ApplicationException
from piracyshield_data_model.ticket.item.model import TicketItemModel
from piracyshield_data_storage.ticket.item.storage import TicketItemStorage, TicketItemStorageCreateException
from piracyshield_service.ticket.item.errors import TicketItemErrorCode, TicketItemErrorMessage
class TicketItemCreateBatchService(BaseService):
"""
Create multiple ticket items in a single batch.
"""
data_model = None
data_storage = None
def __init__(self):
"""
Inizialize logger and required modules.
"""
super().__init__()
self._prepare_modules()
def execute(self, ticket_items: list) -> bool | Exception:
"""
:param ticket_items: a list of ticket items dictionaries.
:return
"""
batch = []
for ticket_item in ticket_items:
model = self._validate_parameters(**ticket_item)
batch.append(self._build_document(
model = model,
now = Time.now_iso8601()
))
try:
# insert the data into the database
self.data_storage.insert_many(batch)
except TicketItemStorageCreateException as e:
self.logger.error(f'Could not massively create ticket items')
raise ApplicationException(TicketItemErrorCode.GENERIC, TicketItemErrorMessage.GENERIC, e)
self.logger.info(f'Created {len(batch)} ticket items')
return True
def _build_document(self, model: dict, now: str) -> dict:
return {
'ticket_id': model.get('ticket_id'),
'ticket_item_id': model.get('ticket_item_id'),
'provider_id': model.get('provider_id'),
'value': model.get('value'),
'genre': model.get('genre'),
'status': model.get('status'),
'is_active': model.get('is_active'),
'is_duplicate': model.get('is_duplicate'),
'is_whitelisted': model.get('is_whitelisted'),
'is_error': model.get('is_error'),
'settings': model.get('settings'),
'metadata': {
'created_at': now,
'updated_at': now
}
}
def _schedule_task(self):
pass
def _validate_parameters(self,
ticket_id: str,
ticket_item_id: str,
provider_id: str,
value: str,
genre: str,
is_active: bool,
is_duplicate: bool,
is_whitelisted: bool,
is_error: bool
):
try:
model = self.data_model(
ticket_id = ticket_id,
ticket_item_id = ticket_item_id,
provider_id = provider_id,
value = value,
genre = genre,
is_active = is_active,
is_duplicate = is_duplicate,
is_whitelisted = is_whitelisted,
is_error = is_error
)
return model.to_dict()
# this has been already validated by the ticket service, but better safe than sorry
except Exception as e:
self.logger.error(f'Could not validate ticket item `{value}` for `{ticket_id}`')
raise ApplicationException(TicketItemErrorCode.GENERIC, TicketItemErrorMessage.GENERIC, e)
def _prepare_configs(self):
pass
def _prepare_modules(self):
self.data_model = TicketItemModel
self.data_storage = TicketItemStorage()

View file

@ -27,7 +27,7 @@ class TicketItemErrorCode:
class TicketItemErrorMessage: class TicketItemErrorMessage:
GENERIC = 'Error during the creation of the ticket item.' GENERIC = 'Generic error.'
TICKET_ITEM_NOT_FOUND = 'Ticket item not found.' TICKET_ITEM_NOT_FOUND = 'Ticket item not found.'

View file

@ -0,0 +1,52 @@
from __future__ import annotations
from piracyshield_service.base import BaseService
from piracyshield_component.exception import ApplicationException
from piracyshield_data_storage.ticket.item.storage import TicketItemStorage, TicketItemStorageGetException
from piracyshield_service.ticket.item.errors import TicketItemErrorCode, TicketItemErrorMessage
class TicketItemGetActiveService(BaseService):
"""
Returns all the blockable ticket items, grouped by genre.
This is currently used to build a cache.
"""
data_storage = None
def __init__(self):
"""
Inizialize logger and required modules.
"""
super().__init__()
self._prepare_modules()
def execute(self) -> list | Exception:
try:
response = self.data_storage.get_active()
document = next(response, None)
return document
except TicketItemStorageGetException as e:
self.logger.error(f'Could not get all the ticket items')
raise ApplicationException(TicketItemErrorCode.GENERIC, TicketItemErrorMessage.GENERIC, e)
def _schedule_task(self):
pass
def _validate_parameters(self):
pass
def _prepare_configs(self):
pass
def _prepare_modules(self):
self.data_storage = TicketItemStorage()

View file

@ -6,30 +6,35 @@ from piracyshield_component.security.identifier import Identifier
from piracyshield_component.exception import ApplicationException from piracyshield_component.exception import ApplicationException
from piracyshield_data_model.ticket.item.genre.model import TicketItemGenreModel from piracyshield_data_model.ticket.item.genre.model import TicketItemGenreModel
from piracyshield_data_model.whitelist.genre.model import WhitelistGenreModel
from piracyshield_service.ticket.item.create import TicketItemCreateService from piracyshield_service.ticket.item.create_batch import TicketItemCreateBatchService
from piracyshield_service.ticket.item.exists_by_value import TicketItemExistsByValueService from piracyshield_service.ticket.item.get_active import TicketItemGetActiveService
from piracyshield_service.ticket.error.get_by_ticket import TicketErrorGetByTicketService from piracyshield_service.whitelist.get_active import WhitelistGetActiveService
from piracyshield_service.whitelist.exists_by_value import WhitelistExistsByValueService
from piracyshield_service.ticket.item.errors import TicketItemErrorCode, TicketItemErrorMessage from piracyshield_service.ticket.item.errors import TicketItemErrorCode, TicketItemErrorMessage
from piracyshield_component_cidr_verifier import is_ipv4_in_cidr, is_ipv6_in_cidr
class TicketRelationEstablishService(BaseService): class TicketRelationEstablishService(BaseService):
""" """
Builds the relation of a ticket items and assigned providers. Builds the relation of a ticket items and assigned providers.
Each ticket item is duplicated and associated to a single provider. Each ticket item is created and associated to a single provider.
""" """
batch = []
ticket_item_cache = {}
whitelist_cache = {}
ticket_item_create_service = None ticket_item_create_service = None
whitelist_exists_by_value_service = None ticket_item_get_active_service = None
ticket_error_get_by_ticket_service = None whitelist_get_active_service = None
ticket_item_exists_by_value_service = None
identifier = None identifier = None
@ -42,7 +47,15 @@ class TicketRelationEstablishService(BaseService):
self._prepare_modules() self._prepare_modules()
# build ticket item cache
self._build_ticket_item_cache()
# build whitelist cache
self._build_whitelist_cache()
def execute(self, ticket_id: str, providers: list, fqdn: list = None, ipv4: list = None, ipv6: list = None) -> bool | Exception: def execute(self, ticket_id: str, providers: list, fqdn: list = None, ipv4: list = None, ipv6: list = None) -> bool | Exception:
self.batch = []
self.logger.debug(f'Establishing relations for `{ticket_id}`') self.logger.debug(f'Establishing relations for `{ticket_id}`')
fqdn_ticket_items = None fqdn_ticket_items = None
@ -50,7 +63,7 @@ class TicketRelationEstablishService(BaseService):
ipv6_ticket_items = None ipv6_ticket_items = None
if fqdn: if fqdn:
fqdn_ticket_items = self._establish_relation( fqdn_ticket_items = self._generate_relation(
ticket_id = ticket_id, ticket_id = ticket_id,
genre = TicketItemGenreModel.FQDN.value, genre = TicketItemGenreModel.FQDN.value,
items = fqdn, items = fqdn,
@ -58,7 +71,7 @@ class TicketRelationEstablishService(BaseService):
) )
if ipv4: if ipv4:
ipv4_ticket_items = self._establish_relation( ipv4_ticket_items = self._generate_relation(
ticket_id = ticket_id, ticket_id = ticket_id,
genre = TicketItemGenreModel.IPV4.value, genre = TicketItemGenreModel.IPV4.value,
items = ipv4, items = ipv4,
@ -66,18 +79,21 @@ class TicketRelationEstablishService(BaseService):
) )
if ipv6: if ipv6:
ipv6_ticket_items = self._establish_relation( ipv6_ticket_items = self._generate_relation(
ticket_id = ticket_id, ticket_id = ticket_id,
genre = TicketItemGenreModel.IPV6.value, genre = TicketItemGenreModel.IPV6.value,
items = ipv6, items = ipv6,
providers = providers providers = providers
) )
# insert batch
self.ticket_item_create_batch_service.execute(self.batch)
self.logger.info(f'Ticket relations completed') self.logger.info(f'Ticket relations completed')
return (fqdn_ticket_items, ipv4_ticket_items, ipv6_ticket_items) return (fqdn_ticket_items, ipv4_ticket_items, ipv6_ticket_items)
def _establish_relation(self, ticket_id: str, genre: str, items: list, providers: list) -> dict: def _generate_relation(self, ticket_id: str, genre: str, items: list, providers: list) -> dict:
ticket_items = [] ticket_items = []
for value in items: for value in items:
@ -92,23 +108,24 @@ class TicketRelationEstablishService(BaseService):
) )
is_whitelisted = self._is_whitelisted( is_whitelisted = self._is_whitelisted(
genre = genre,
value = value value = value
) )
is_error = False is_error = False
for provider_id in providers: for provider_id in providers:
self.ticket_item_create_service.execute( self.batch.append({
ticket_id = ticket_id, 'ticket_id': ticket_id,
ticket_item_id = ticket_item_id, 'ticket_item_id': ticket_item_id,
provider_id = provider_id, 'value': value,
value = value, 'genre': genre,
genre = genre, 'provider_id': provider_id,
is_active = is_active, 'is_active': is_active,
is_duplicate = is_duplicate, 'is_duplicate': is_duplicate,
is_whitelisted = is_whitelisted, 'is_whitelisted': is_whitelisted,
is_error = is_error 'is_error': is_error
) })
ticket_items.append({ ticket_items.append({
'value': value, 'value': value,
@ -122,36 +139,40 @@ class TicketRelationEstablishService(BaseService):
return ticket_items return ticket_items
def _is_duplicate(self, genre: str, value: str) -> bool: def _is_duplicate(self, genre: str, value: str) -> bool:
found_tickets = self.ticket_item_exists_by_value_service.execute( if genre == TicketItemGenreModel.FQDN.value and TicketItemGenreModel.FQDN.value in self.ticket_item_cache:
genre = genre, return value in self.ticket_item_cache.get(TicketItemGenreModel.FQDN.value)
value = value
)
# no ticket item found elif genre == TicketItemGenreModel.IPV4.value and TicketItemGenreModel.IPV4.value in self.ticket_item_cache:
if not len(found_tickets): return value in self.ticket_item_cache.get(TicketItemGenreModel.IPV4.value)
return False
# we have a ticket that contains this ticket item elif genre == TicketItemGenreModel.IPV6.value and TicketItemGenreModel.IPV6.value in self.ticket_item_cache:
if len(found_tickets): return value in self.ticket_item_cache.get(TicketItemGenreModel.IPV6.value)
# but let's search for a ticket error in this case
for ticket_blocking in found_tickets:
ticket_errors = self.ticket_error_get_by_ticket_service.execute(
ticket_blocking.get('ticket_id')
)
# found one, let's search for our item return False
if len(ticket_errors):
for ticket_error_response in ticket_errors:
# found the item, so we don't have any duplicate
if genre in ticket_error_response and ticket_error_response.get(genre) and value in ticket_error_response.get(genre):
return False
return True def _is_whitelisted(self, genre: str, value: str) -> bool:
if genre == TicketItemGenreModel.FQDN.value and TicketItemGenreModel.FQDN.value in self.whitelist_cache:
return value in self.whitelist_cache.get(WhitelistGenreModel.FQDN.value)
def _is_whitelisted(self, value: str) -> bool: elif genre == TicketItemGenreModel.IPV4.value and TicketItemGenreModel.IPV4.value in self.whitelist_cache:
return self.whitelist_exists_by_value_service.execute( if value in self.whitelist_cache.get(WhitelistGenreModel.IPV4.value):
value = value return True
)
if WhitelistGenreModel.CIDR_IPV4.value in self.whitelist_cache:
for cidr_ipv4 in self.whitelist_cache.get(WhitelistGenreModel.CIDR_IPV4.value):
if is_ipv4_in_cidr(value, cidr_ipv4) == True:
return True
elif genre == TicketItemGenreModel.IPV6.value and TicketItemGenreModel.IPV6.value in self.whitelist_cache:
if value in self.whitelist_cache.get(WhitelistGenreModel.IPV6.value):
return True
if WhitelistGenreModel.CIDR_IPV6.value in self.whitelist_cache:
for cidr_ipv6 in self.whitelist_cache.get(WhitelistGenreModel.CIDR_IPV6.value):
if is_ipv6_in_cidr(value, cidr_ipv6) == True:
return True
return False
def _generate_ticket_item_id(self) -> str: def _generate_ticket_item_id(self) -> str:
""" """
@ -160,6 +181,12 @@ class TicketRelationEstablishService(BaseService):
return self.identifier.generate() return self.identifier.generate()
def _build_ticket_item_cache(self):
self.ticket_item_cache = self.ticket_item_get_active_service.execute()
def _build_whitelist_cache(self):
self.whitelist_cache = self.whitelist_get_active_service.execute()
def _schedule_task(self): def _schedule_task(self):
pass pass
@ -170,12 +197,10 @@ class TicketRelationEstablishService(BaseService):
pass pass
def _prepare_modules(self): def _prepare_modules(self):
self.ticket_item_exists_by_value_service = TicketItemExistsByValueService() self.ticket_item_get_active_service = TicketItemGetActiveService()
self.ticket_error_get_by_ticket_service = TicketErrorGetByTicketService() self.ticket_item_create_batch_service = TicketItemCreateBatchService()
self.whitelist_exists_by_value_service = WhitelistExistsByValueService() self.whitelist_get_active_service = WhitelistGetActiveService()
self.ticket_item_create_service = TicketItemCreateService()
self.identifier = Identifier() self.identifier = Identifier()

View file

@ -32,19 +32,9 @@ class TicketAutocloseTask(BaseTask):
# TODO: must check if the ticket exists. # TODO: must check if the ticket exists.
# change status # change status
try: self.ticket_storage.update_status(
self.ticket_storage.update_status(
ticket_id = self.ticket_id,
ticket_status = TicketStatusModel.CLOSED.value
)
except TicketStorageUpdateException:
self.logger.error(f'Could not update the ticket `{self.ticket_id}`')
# log the operation
self.log_ticket_create_service.execute(
ticket_id = self.ticket_id, ticket_id = self.ticket_id,
message = f'Changed status to `{TicketStatusModel.CLOSED.value}`.' ticket_status = TicketStatusModel.CLOSED.value
) )
def before_run(self): def before_run(self):
@ -56,10 +46,19 @@ class TicketAutocloseTask(BaseTask):
self.log_ticket_create_service = LogTicketCreateService() self.log_ticket_create_service = LogTicketCreateService()
def after_run(self): def after_run(self):
pass # log the operation
self.log_ticket_create_service.execute(
ticket_id = self.ticket_id,
message = f'Changed status to `{TicketStatusModel.CLOSED.value}`.'
)
def on_failure(self): def on_failure(self):
pass self.logger.error(f'Could not update the ticket `{self.ticket_id}`')
self.ticket_storage.update_status(
ticket_id = self.ticket_id,
ticket_status = TicketStatusModel.CREATED.value
)
def ticket_autoclose_task_caller(**kwargs): def ticket_autoclose_task_caller(**kwargs):
t = TicketAutocloseTask(**kwargs) t = TicketAutocloseTask(**kwargs)

View file

@ -0,0 +1,134 @@
from piracyshield_service.task.base import BaseTask
from piracyshield_component.utils.time import Time
from piracyshield_data_model.ticket.status.model import TicketStatusModel
from piracyshield_data_storage.ticket.storage import TicketStorage
from piracyshield_service.ticket.relation.establish import TicketRelationEstablishService
from piracyshield_service.ticket.relation.abandon import TicketRelationAbandonService
from piracyshield_service.ticket.get import TicketGetService
from piracyshield_service.forensic.remove_by_ticket import ForensicRemoveByTicketService
from piracyshield_service.ticket.tasks.ticket_initialize import ticket_initialize_task_caller
from piracyshield_service.ticket.tasks.ticket_autoclose import ticket_autoclose_task_caller
from piracyshield_service.log.ticket.create import LogTicketCreateService
from piracyshield_service.task.service import TaskService
class TicketCreateTask(BaseTask):
"""
Creation of ticket items and subsequent tasks.
"""
pending_tasks = []
ticket_data = None
ticket_relation_establish_service = None
ticket_relation_abandon_service = None
ticket_get_service = None
ticket_storage = None
log_ticket_create_service = None
task_service = None
def __init__(self, ticket_data: dict):
super().__init__()
self.ticket_data = ticket_data
def run(self) -> bool:
"""
Starts the operations for the new ticket.
The status gets set to `open` as we make it visible for API pulls and notifications
"""
# proceed to build the relation item <-> provider
# this part provides a check for duplicates, whitelisted items and error tickets
(fqdn_ticket_items, ipv4_ticket_items, ipv6_ticket_items) = self.ticket_relation_establish_service.execute(
ticket_id = self.ticket_data.get('ticket_id'),
providers = self.ticket_data.get('assigned_to'),
fqdn = self.ticket_data.get('fqdn') or None,
ipv4 = self.ticket_data.get('ipv4') or None,
ipv6 = self.ticket_data.get('ipv6') or None
)
self.pending_tasks.append(self.task_service.create(
task_caller = ticket_initialize_task_caller,
delay = self.ticket_data.get('settings').get('revoke_time'),
ticket_id = self.ticket_data.get('ticket_id')
))
self.pending_tasks.append(self.task_service.create(
task_caller = ticket_autoclose_task_caller,
delay = self.ticket_data.get('settings').get('autoclose_time'),
ticket_id = self.ticket_data.get('ticket_id')
))
self.ticket_storage.update_task_list(
ticket_id = self.ticket_data.get('ticket_id'),
task_ids = self.pending_tasks,
updated_at = Time.now_iso8601()
)
return True
def before_run(self):
"""
Initialize required modules.
"""
self.ticket_storage = TicketStorage()
self.ticket_get_service = TicketGetService()
self.ticket_relation_establish_service = TicketRelationEstablishService()
self.ticket_relation_abandon_service = TicketRelationAbandonService()
self.forensic_remove_by_ticket_service = ForensicRemoveByTicketService()
self.log_ticket_create_service = LogTicketCreateService()
self.task_service = TaskService()
def after_run(self):
self.ticket_storage.update_status(
ticket_id = self.ticket_data.get('ticket_id'),
ticket_status = TicketStatusModel.CREATED.value
)
# log the operation
self.log_ticket_create_service.execute(
ticket_id = self.ticket_data.get('ticket_id'),
message = f'Completed the creation of all ticket items.'
)
def on_failure(self):
self.logger.error(f'Could not initialize ticket items for `{self.ticket_data.get("ticket_id")}`')
self.ticket_relation_abandon_service.execute(self.ticket_data.get('ticket_id'))
self.forensic_remove_by_ticket_service.execute(self.ticket_data.get('ticket_id'))
for single_task in self.pending_tasks:
self.task_service.remove(single_task)
self.ticket_storage.update_status(
ticket_id = self.ticket_data.get('ticket_id'),
ticket_status = TicketStatusModel.FAILED.value
)
def ticket_create_task_caller(**kwargs):
t = TicketCreateTask(**kwargs)
return t.execute()

View file

@ -2,7 +2,7 @@ from piracyshield_service.task.base import BaseTask
from piracyshield_data_model.ticket.status.model import TicketStatusModel from piracyshield_data_model.ticket.status.model import TicketStatusModel
from piracyshield_data_storage.ticket.storage import TicketStorage, TicketStorageUpdateException from piracyshield_data_storage.ticket.storage import TicketStorage
from piracyshield_service.log.ticket.create import LogTicketCreateService from piracyshield_service.log.ticket.create import LogTicketCreateService
@ -32,19 +32,9 @@ class TicketInitializeTask(BaseTask):
# TODO: must check if the ticket exists. # TODO: must check if the ticket exists.
# change status # change status
try: self.ticket_storage.update_status(
self.ticket_storage.update_status(
ticket_id = self.ticket_id,
ticket_status = TicketStatusModel.OPEN.value
)
except TicketStorageUpdateException:
self.logger.error(f'Could not update the ticket `{self.ticket_id}`')
# log the operation
self.log_ticket_create_service.execute(
ticket_id = self.ticket_id, ticket_id = self.ticket_id,
message = f'Changed status to `{TicketStatusModel.OPEN.value}`.' ticket_status = TicketStatusModel.OPEN.value
) )
def before_run(self): def before_run(self):
@ -56,10 +46,19 @@ class TicketInitializeTask(BaseTask):
self.log_ticket_create_service = LogTicketCreateService() self.log_ticket_create_service = LogTicketCreateService()
def after_run(self): def after_run(self):
pass # log the operation
self.log_ticket_create_service.execute(
ticket_id = self.ticket_id,
message = f'Changed status to `{TicketStatusModel.OPEN.value}`.'
)
def on_failure(self): def on_failure(self):
pass self.logger.error(f'Could not update the ticket `{self.ticket_id}`')
self.ticket_storage.update_status(
ticket_id = self.ticket_id,
ticket_status = TicketStatusModel.CREATED.value
)
def ticket_initialize_task_caller(**kwargs): def ticket_initialize_task_caller(**kwargs):
t = TicketInitializeTask(**kwargs) t = TicketInitializeTask(**kwargs)

View file

@ -1,17 +0,0 @@
[package]
name = "rs_cidr_verifier"
version = "0.1.0"
edition = "2021"
[profile.release]
opt-level = 3
lto = true
[lib]
name = "rs_cidr_verifier"
path = "src/lib.rs"
crate-type = ["cdylib"]
[dependencies]
pyo3 = { version = "0.20.2", features = ["extension-module"] }
ipnetwork = "0.20.0"

View file

@ -1,2 +0,0 @@
include Cargo.toml
recursive-include src *

View file

@ -1,34 +0,0 @@
use pyo3::prelude::*;
use ipnetwork::{Ipv4Network, Ipv6Network};
use std::net::{Ipv4Addr, Ipv6Addr};
use std::str::FromStr;
#[pyfunction]
fn is_ipv4_in_cidr(ip: &str, cidr: &str) -> PyResult<bool> {
let ip_addr = ip.parse::<Ipv4Addr>()
.map_err(|_| PyErr::new::<pyo3::exceptions::PyValueError, _>("Invalid IPv4 address"))?;
let cidr_net = Ipv4Network::from_str(cidr)
.map_err(|_| PyErr::new::<pyo3::exceptions::PyValueError, _>("Invalid IPv4 CIDR notation"))?;
Ok(cidr_net.contains(ip_addr))
}
#[pyfunction]
fn is_ipv6_in_cidr(ip: &str, cidr: &str) -> PyResult<bool> {
let ip_addr = ip.parse::<Ipv6Addr>()
.map_err(|_| PyErr::new::<pyo3::exceptions::PyValueError, _>("Invalid IPv6 address"))?;
let cidr_net = Ipv6Network::from_str(cidr)
.map_err(|_| PyErr::new::<pyo3::exceptions::PyValueError, _>("Invalid IPv6 CIDR notation"))?;
Ok(cidr_net.contains(ip_addr))
}
#[pymodule]
fn rs_cidr_verifier(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_pyfunction!(is_ipv4_in_cidr, m)?)?;
m.add_function(wrap_pyfunction!(is_ipv6_in_cidr, m)?)?;
Ok(())
}

View file

@ -14,6 +14,10 @@ from piracyshield_data_model.whitelist.model import (
WhitelistModelIPv4NonValidException, WhitelistModelIPv4NonValidException,
WhitelistModelIPv6MissingException, WhitelistModelIPv6MissingException,
WhitelistModelIPv6NonValidException, WhitelistModelIPv6NonValidException,
WhitelistModelCIDRIPv4MissingException,
WhitelistModelCIDRIPv4NonValidException,
WhitelistModelCIDRIPv6MissingException,
WhitelistModelCIDRIPv6NonValidException,
WhitelistModelRegistrarMissingException, WhitelistModelRegistrarMissingException,
WhitelistModelRegistrarNonValidException, WhitelistModelRegistrarNonValidException,
WhitelistModelASCodeMissingException, WhitelistModelASCodeMissingException,
@ -121,6 +125,12 @@ class WhitelistCreateService(BaseService):
case 'ipv6': case 'ipv6':
document['as_code'] = model.get('as_code') document['as_code'] = model.get('as_code')
case 'cidr_ipv4':
document['as_code'] = model.get('as_code')
case 'cidr_ipv6':
document['as_code'] = model.get('as_code')
return document return document
def _schedule_task(self): def _schedule_task(self):
@ -159,6 +169,18 @@ class WhitelistCreateService(BaseService):
except WhitelistModelIPv6NonValidException: except WhitelistModelIPv6NonValidException:
raise ApplicationException(WhitelistErrorCode.NON_VALID_IPV6, WhitelistErrorMessage.NON_VALID_IPV6) raise ApplicationException(WhitelistErrorCode.NON_VALID_IPV6, WhitelistErrorMessage.NON_VALID_IPV6)
except WhitelistModelCIDRIPv4MissingException:
raise ApplicationException(WhitelistErrorCode.MISSING_CIDR_IPV4, WhitelistErrorMessage.MISSING_CIDR_IPV4)
except WhitelistModelCIDRIPv4NonValidException:
raise ApplicationException(WhitelistErrorCode.NON_VALID_CIDR_IPV4, WhitelistErrorMessage.NON_VALID_CIDR_IPV4)
except WhitelistModelCIDRIPv6MissingException:
raise ApplicationException(WhitelistErrorCode.MISSING_CIDR_IPV6, WhitelistErrorMessage.MISSING_CIDR_IPV6)
except WhitelistModelCIDRIPv6NonValidException:
raise ApplicationException(WhitelistErrorCode.NON_VALID_CIDR_IPV6, WhitelistErrorMessage.NON_VALID_CIDR_IPV6)
except WhitelistModelRegistrarMissingException: except WhitelistModelRegistrarMissingException:
raise ApplicationException(WhitelistErrorCode.MISSING_REGISTRAR, WhitelistErrorMessage.MISSING_REGISTRAR) raise ApplicationException(WhitelistErrorCode.MISSING_REGISTRAR, WhitelistErrorMessage.MISSING_REGISTRAR)

View file

@ -17,21 +17,29 @@ class WhitelistErrorCode:
NON_VALID_IPV6 = '6007' NON_VALID_IPV6 = '6007'
MISSING_REGISTRAR = '6008' MISSING_CIDR_IPV4 = '6008'
NON_VALID_REGISTRAR = '6009' NON_VALID_CIDR_IPV4 = '6009'
MISSING_AS_CODE = '6010' MISSING_CIDR_IPV6 = '6010'
NON_VALID_AS_CODE = '6011' NON_VALID_CIDR_IPV6 = '6011'
ITEM_EXISTS = '6012' MISSING_REGISTRAR = '6012'
ITEM_HAS_TICKET = '6013' NON_VALID_REGISTRAR = '6013'
CANNOT_REMOVE = '6014' MISSING_AS_CODE = '6014'
CANNOT_SET_STATUS = '6015' NON_VALID_AS_CODE = '6015'
ITEM_EXISTS = '6016'
ITEM_HAS_TICKET = '6017'
CANNOT_REMOVE = '6018'
CANNOT_SET_STATUS = '6019'
class WhitelistErrorMessage: class WhitelistErrorMessage:
@ -51,9 +59,17 @@ class WhitelistErrorMessage:
NON_VALID_IPV6 = 'Non valid IPv6.' NON_VALID_IPV6 = 'Non valid IPv6.'
MISSING_CIDR_IPV4 = 'Missing CIDR IPv4 class'
NON_VALID_CIDR_IPV4 = 'Non valid CIDR IPv4 class'
MISSING_CIDR_IPV6 = 'Missing CIDR IPv6 class'
NON_VALID_CIDR_IPV6 = 'Non valid CIDR IPv6 class'
MISSING_REGISTRAR = 'Missing registrar.' MISSING_REGISTRAR = 'Missing registrar.'
NON_VALID_REGISTRAR = 'Non valid registrar. A registrar must be a string (allowed characters: ` -`)' NON_VALID_REGISTRAR = 'Non valid registrar. A registrar must be a string (allowed characters: ` .,-_`)'
MISSING_AS_CODE = 'Missing AS code.' MISSING_AS_CODE = 'Missing AS code.'

View file

@ -6,7 +6,7 @@ from piracyshield_component.exception import ApplicationException
from piracyshield_data_storage.whitelist.storage import WhitelistStorage, WhitelistStorageGetException from piracyshield_data_storage.whitelist.storage import WhitelistStorage, WhitelistStorageGetException
from piracyshield_service.ticket.errors import TicketErrorCode, TicketErrorMessage from piracyshield_service.whitelist.errors import WhitelistErrorCode, WhitelistErrorMessage
class WhitelistExistsByValueService(BaseService): class WhitelistExistsByValueService(BaseService):

View file

@ -0,0 +1,53 @@
from __future__ import annotations
from piracyshield_service.base import BaseService
from piracyshield_component.exception import ApplicationException
from piracyshield_data_storage.whitelist.storage import WhitelistStorage, WhitelistStorageGetException
from piracyshield_service.whitelist.errors import WhitelistErrorCode, WhitelistErrorMessage
class WhitelistGetActiveService(BaseService):
"""
Get all the active whitelist items.
This is currently used to build a cache.
"""
data_storage = None
def __init__(self):
"""
Inizialize logger and required modules.
"""
super().__init__()
self._prepare_modules()
def execute(self) -> list | Exception:
try:
response = self.data_storage.get_active()
document = next(response, None)
return document
except WhitelistStorageGetException as e:
self.logger.error(f'Cannot get all the whitelist items')
raise ApplicationException(WhitelistErrorCode.GENERIC, WhitelistErrorMessage.GENERIC, e)
def _schedule_task(self):
pass
def _validate_parameters(self):
pass
def _prepare_configs(self):
pass
def _prepare_modules(self):
self.data_storage = WhitelistStorage()

View file

@ -6,7 +6,7 @@ from piracyshield_component.exception import ApplicationException
from piracyshield_data_storage.whitelist.storage import WhitelistStorage, WhitelistStorageGetException from piracyshield_data_storage.whitelist.storage import WhitelistStorage, WhitelistStorageGetException
from piracyshield_service.ticket.errors import TicketErrorCode, TicketErrorMessage from piracyshield_service.whitelist.errors import WhitelistErrorCode, WhitelistErrorMessage
class WhitelistGetGlobalService(BaseService): class WhitelistGetGlobalService(BaseService):