diff --git a/app/flags/operations/flag_3.py b/app/flags/operations/flag_3.py index e7a213c..cd1e98b 100644 --- a/app/flags/operations/flag_3.py +++ b/app/flags/operations/flag_3.py @@ -1,5 +1,9 @@ +import logging + from plugins.training.app.c_flag import Flag +log = logging.getLogger(__name__) + class OperationsFlag3(Flag): name = 'Empty operation' @@ -10,12 +14,42 @@ class OperationsFlag3(Flag): ) extra_info = ( - 'During an autonomous adversary emulation exercise, the operation will only run tasks in the adversary profile. ' - 'Manual and potential links allow an operator to "toss in" additional TTPs into a live, autonomous operation.' + 'During an autonomous adversary emulation exercise, the operation will only run tasks in the adversary profile. ' + 'Manual and potential links allow an operator to "toss in" additional TTPs into a live, autonomous operation.' ) async def verify(self, services): - for op in await services.get('data_svc').locate('operations'): - if op.finish and op.adversary.adversary_id == 'ad-hoc' and len(op.chain) >= 5 and not op.group: + data_svc = services.get('data_svc') + if not data_svc: + log.debug("[training.flag3] data_svc is None!") + return False + + operations = await data_svc.locate('operations') + for op in operations: + adversary = getattr(op, 'adversary', None) + adversary_id = getattr(adversary, 'adversary_id', None) + log.debug("[training.flag3] Checking operation '%s' | state=%s, group=%s, adversary_id=%s", + op.name, op.state, op.group, adversary_id) + + # Gather all link lists that might exist + chain_links = getattr(op, 'chain', []) or [] + potential_links = getattr(op, 'potential_links', []) or [] + manual_links = getattr(op, 'links', []) or [] + + total_links = len(chain_links) + len(potential_links) + len(manual_links) + + log.debug("[training.flag3] Found %d chain links, %d potential links, %d manual links (total=%d)", + len(chain_links), len(potential_links), len(manual_links), total_links) + + # Verify completion conditions + if ( + op.finish + and adversary_id == 'ad-hoc' + and total_links >= 5 + and not op.group + ): + log.debug("[training.flag3] Operation '%s' meets all criteria!", op.name) return True + + log.debug("[training.flag3] No operation met the criteria.") return False diff --git a/gui/views/training.vue b/gui/views/training.vue index eef9805..eb653ce 100644 --- a/gui/views/training.vue +++ b/gui/views/training.vue @@ -17,7 +17,9 @@ const certificates = ref([ { name: "Blue Certificate" }, ]); -const learnerName = ref(localStorage.getItem('trainingCertName') || ''); +let _savedCertName = ''; +try { _savedCertName = localStorage.getItem('trainingCertName') || ''; } catch (e) { /* storage unavailable */ } +const learnerName = ref(_savedCertName); const showIssueModal = ref(false); const skipSelectionWatcher = ref(false); @@ -67,7 +69,9 @@ watch( function saveNameLocally() { const n = learnerName.value.trim(); - if (n) localStorage.setItem('trainingCertName', n); + if (n) { + try { localStorage.setItem('trainingCertName', n); } catch (e) { /* storage unavailable */ } + } } async function issueCertificate() { @@ -117,12 +121,15 @@ async function issueCertificate() { } const restoreSelections = () => { - const savedState = localStorage.getItem("trainingState"); + let savedState = null; + try { savedState = localStorage.getItem("trainingState"); } catch (e) { /* storage unavailable */ } if (savedState) { try { const parsed = JSON.parse(savedState); selectedCert.value = parsed.selectedCert || ""; - selectedBadge.value = parsed.selectedBadge || ""; + // Support both new key (selectedBadgeName) and old key (selectedBadge) for migration + const badgeName = parsed.selectedBadgeName || parsed.selectedBadge?.name || ""; + selectedBadge.value = badgeName ? { name: badgeName } : null; } catch (err) { console.warn("Failed to parse saved training state:", err); } @@ -134,9 +141,9 @@ const persistSelections = () => { if (!selectedCert.value) return; const state = { selectedCert: selectedCert.value, - selectedBadge: selectedBadge.value, + selectedBadgeName: selectedBadge.value?.name || "", }; - localStorage.setItem("trainingState", JSON.stringify(state)); + try { localStorage.setItem("trainingState", JSON.stringify(state)); } catch (e) { /* storage unavailable */ } }; const getTraining = () => {