blob: bcc85fd4213696f2e8d9ac940a8838c8b2b54c70 [file]
import uuid
import marshmallow as ma
from app.objects.interfaces.i_object import FirstClassObjectInterface
from app.utility.base_object import BaseObject
DEFAULT_OBJECTIVE_ID = '495a9828-cab1-44dd-a0ca-66e58177d8cc'
class AdversarySchema(ma.Schema):
class Meta:
unknown = ma.EXCLUDE
adversary_id = ma.fields.String()
name = ma.fields.String()
description = ma.fields.String()
atomic_ordering = ma.fields.List(ma.fields.String())
objective = ma.fields.String()
tags = ma.fields.List(ma.fields.String(), allow_none=True)
has_repeatable_abilities = ma.fields.Boolean(dump_only=True)
plugin = ma.fields.String(load_default=None)
@ma.pre_load
def fix_id(self, adversary, **_):
if 'id' in adversary:
adversary['adversary_id'] = adversary.pop('id')
return adversary
@ma.pre_load
def phase_to_atomic_ordering(self, adversary, **_):
"""
Convert legacy adversary phases to atomic ordering
"""
if 'phases' in adversary and 'atomic_ordering' in adversary:
raise ma.ValidationError('atomic_ordering and phases cannot be used at the same time', 'phases', adversary)
elif 'phases' in adversary:
adversary['atomic_ordering'] = [ab_id for phase in adversary.get('phases', {}).values() for ab_id in phase]
del adversary['phases']
return adversary
@ma.pre_load
def remove_properties(self, data, **_):
data.pop('has_repeatable_abilities', None)
return data
@ma.post_load
def build_adversary(self, data, **kwargs):
return None if kwargs.get('partial') is True else Adversary(**data)
class Adversary(FirstClassObjectInterface, BaseObject):
schema = AdversarySchema()
@property
def unique(self):
return self.hash('%s' % self.adversary_id)
def __init__(self, name='', adversary_id='', description='', atomic_ordering=(), objective='', tags=None, plugin=''):
super().__init__()
self.adversary_id = adversary_id if adversary_id else str(uuid.uuid4())
self.name = name
self.description = description
self.atomic_ordering = atomic_ordering
self.objective = objective or DEFAULT_OBJECTIVE_ID
self.tags = set(tags) if tags else set()
self.has_repeatable_abilities = False
self.plugin = plugin
def store(self, ram):
existing = self.retrieve(ram['adversaries'], self.unique)
if not existing:
ram['adversaries'].append(self)
return self.retrieve(ram['adversaries'], self.unique)
existing.update('name', self.name)
existing.update('description', self.description)
existing.update('atomic_ordering', self.atomic_ordering)
existing.update('objective', self.objective)
existing.update('tags', self.tags)
existing.update('has_repeatable_abilities', self.check_repeatable_abilities(ram['abilities']))
existing.update('plugin', self.plugin)
return existing
def verify(self, log, abilities, objectives):
for ability_id in self.atomic_ordering:
if not next((ability for ability in abilities if ability.ability_id == ability_id), None):
log.warning('Ability referenced in adversary %s but not found: %s', self.adversary_id, ability_id)
if not self.objective:
self.objective = DEFAULT_OBJECTIVE_ID
elif not next((objective for objective in objectives if objective.id == self.objective), None):
log.warning('Objective referenced in adversary %s but not found: %s. Setting default objective.',
self.adversary_id, self.objective)
self.objective = DEFAULT_OBJECTIVE_ID
self.has_repeatable_abilities = self.check_repeatable_abilities(abilities)
def has_ability(self, ability):
for a in self.atomic_ordering:
if ability == a:
return True
return False
async def which_plugin(self):
return self.plugin
def check_repeatable_abilities(self, ability_list):
return any(ab.repeatable for ab_id in self.atomic_ordering for ab in ability_list if ab.ability_id == ab_id)