#!/usr/bin/env bash
set -Eeuo pipefail
ACTION="${1:-setup}"
ROOT="${2:-/tmp/soria-cyber-threats-identity-${USER:-student}}"
MARKER=.soria-cyber-threats-identity
fail(){ printf 'ERROR: %s\n' "$*" >&2; exit 1; }
safe(){ case "$ROOT" in /tmp/soria-cyber-*|"$HOME"/soria-cyber-*) ;; *) fail "refused lab root: $ROOT";; esac; [ "$ROOT" != /tmp ] || fail 'refused /tmp'; [ "$ROOT" != "$HOME" ] || fail 'refused HOME'; }
marked(){ [ -f "$ROOT/$MARKER" ] || fail "missing marker: $ROOT/$MARKER"; }
rows(){ awk 'END{print NR?NR-1:0}' "$1"; }
unique(){ awk -F, -v c="$2" -v l="$3" 'NR==1{next}!$c{print "empty "l > "/dev/stderr";exit 1}seen[$c]++{print "duplicate "l": "$c > "/dev/stderr";exit 1}' "$1" || fail "$3 validation failed"; }
setup(){
  if [ -e "$ROOT" ]; then marked; rm -rf "$ROOT"/{refs,input,output,evidence}; else mkdir -p "$ROOT"; :>"$ROOT/$MARKER"; fi
  mkdir -p "$ROOT"/{refs,input,output,evidence}
  cat >"$ROOT/refs/trusted-domains.csv" <<'CSV'
domain
soria.example.invalid
supplier.example.invalid
CSV
  cat >"$ROOT/input/messages.csv" <<'CSV'
message_id,channel,claimed_identity,sender_domain,link_domain,request_type,urgency,unexpected,attachment
MSG-001,email,Support SORIA,soria-helpdesk.example.invalid,soria-login.example.invalid,credentials,high,yes,none
MSG-002,email,IT SORIA,soria.example.invalid,,maintenance,low,no,none
MSG-003,email,Fournisseur facturation,supplier.example.invalid,,payment,high,yes,invoice.xlsm
MSG-004,sms,Direction,,,payment,high,yes,none
MSG-005,email,Portail identité,soria-security.example.invalid,soria-qr.example.invalid,authentication,high,yes,qr.png
MSG-006,email,Ressources humaines,soria.example.invalid,,information,low,no,policy.pdf
MSG-007,push,Fournisseur identité,,,approve_login,high,yes,none
MSG-008,voice,Service bancaire,,,recovery_code,high,yes,none
CSV
  cat >"$ROOT/input/auth-threats.csv" <<'CSV'
scenario_id,accounts_targeted,passwords_tried,breach_source,session_artifact,repeated_pushes
AUTH-001,1,50,no,no,0
AUTH-002,20,1,no,no,0
AUTH-003,12,12,yes,no,0
AUTH-004,1,0,no,yes,0
AUTH-005,1,0,no,no,8
AUTH-006,1,1,no,no,0
CSV
  cat >"$ROOT/input/accounts.csv" <<'CSV'
account_id,role,privilege,password_unique,password_manager,mfa_method,phishing_resistant,recovery_channel,recovery_verified
ACC-001,administrator,high,yes,yes,FIDO2,yes,offline_recovery_key,yes
ACC-002,administrator,high,yes,yes,TOTP,no,helpdesk_callback,no
ACC-003,finance,high,no,no,SMS,no,email,no
ACC-004,sales,standard,yes,yes,PUSH,no,manager_approval,yes
ACC-005,warehouse,standard,yes,no,PASSWORD_ONLY,no,helpdesk_callback,yes
ACC-006,external_support,external,yes,yes,FIDO2,yes,sponsor_approval,yes
ACC-007,service_account,high,yes,managed,NON_INTERACTIVE_KEY,n_a,vault_rotation,yes
ACC-008,communication,standard,yes,yes,TOTP,no,offline_codes,yes
CSV
  cat >"$ROOT/input/malware-chains.csv" <<'CSV'
chain_id,entry_vector,user_action,first_effect,secondary_effect,business_consequence,response
MAL-001,unexpected_macro_attachment,open_and_enable_content,endpoint_compromise,credential_access,erp_disruption,ISOLATE_AND_REPORT
MAL-002,stolen_credentials,login,account_compromise,data_exfiltration,customer_confidentiality_loss,REVOKE_SESSION_AND_REPORT
MAL-003,unpatched_service,no_user_action,server_compromise,lateral_movement,backup_disruption,CONTAIN_AND_ESCALATE
MAL-004,fake_update,install,malware_execution,encryption,operations_stopped,ISOLATE_AND_REPORT
CSV
  cat >"$ROOT/input/reporting.csv" <<'CSV'
case_id,message_id,user_action,proof_preserved,alternate_channel,reported_to
CASE-001,MSG-001,no_interaction,yes,known_it_number,security_team
CASE-002,MSG-004,replied_to_message,no,none,none
CASE-003,MSG-007,denied_push,yes,known_it_number,security_team
CASE-004,MSG-003,opened_attachment,no,known_it_number,security_team
CSV
  cat >"$ROOT/input/proof-boundary.md" <<'MD'
# Frontière de preuve
Le laboratoire utilise seulement des données synthétiques et des domaines `.invalid`. Il n'ouvre aucun lien, ne résout aucun domaine, ne contacte aucune messagerie, ne teste aucun secret, ne s'authentifie à aucun service et n'exécute aucun code malveillant.
MD
  (cd "$ROOT"; sha256sum refs/*.csv input/* >evidence/sources-before.sha256)
  printf 'Cybersecurity threats and identity lab ready: %s\n' "$ROOT"
}
status(){ marked; printf 'Messages: %s\nAuth scenarios: %s\nAccounts: %s\nMalware chains: %s\nReports: %s\n' "$(rows "$ROOT/input/messages.csv")" "$(rows "$ROOT/input/auth-threats.csv")" "$(rows "$ROOT/input/accounts.csv")" "$(rows "$ROOT/input/malware-chains.csv")" "$(rows "$ROOT/input/reporting.csv")"; }
run(){
  marked
  unique "$ROOT/input/messages.csv" 1 'message id'; unique "$ROOT/input/auth-threats.csv" 1 'auth scenario id'; unique "$ROOT/input/accounts.csv" 1 'account id'; unique "$ROOT/input/malware-chains.csv" 1 'malware chain id'; unique "$ROOT/input/reporting.csv" 1 'report case id'
  awk -F, 'NR>1{if($4!=""&&$4!~/[.]invalid$/)exit 1;if($5!=""&&$5!~/[.]invalid$/)exit 1}' "$ROOT/input/messages.csv" || fail 'only .invalid domains are allowed'
  awk -F, -v OFS=, '
    FNR==NR{if(FNR>1)t[$1]=1;next} FNR==1{print "message_id,classification,risk_score,action,reasons";next}
    {base=($6~/^(credentials|payment|authentication|approve_login|recovery_code)$/)?3:0;s=base;r="REQUEST_"toupper($6);a="VERIFY_SOURCE";
     if($7=="high"){s+=2;r=r"+URGENCY"}if($8=="yes"){s++;r=r"+UNEXPECTED"}if($4!=""&&!t[$4]){s+=2;r=r"+UNTRUSTED_SENDER_DOMAIN"}if($5!=""&&!t[$5]){s+=2;r=r"+UNTRUSTED_LINK_DOMAIN"}if($9~/[.]xlsm$/){s+=3;r=r"+ACTIVE_ATTACHMENT"}if($9=="qr.png"){s+=2;r=r"+QR_AUTHENTICATION"}
     c=s>=6?"PHISHING_SUSPECTED":(s>=3?"SUSPICIOUS_VERIFY":"ROUTINE_VERIFY");
     if($6=="maintenance")a="VERIFY_CALENDAR";else if($6=="credentials")a="DO_NOT_INTERACT_REPORT";else if($6=="payment")a="VERIFY_OUT_OF_BAND_REPORT";else if($6=="authentication")a="DO_NOT_SCAN_REPORT_VERIFY";else if($6=="approve_login"){c="AUTH_PROMPT_ABUSE";a="DENY_AND_REPORT"}else if($6=="recovery_code"){c="SOCIAL_ENGINEERING";a="END_CALL_VERIFY_REPORT"}
     if($9~/[.]xlsm$/)a="DO_NOT_OPEN_REPORT_VERIFY";print $1,c,s,a,r}' "$ROOT/refs/trusted-domains.csv" "$ROOT/input/messages.csv" >"$ROOT/output/message-triage.csv"
  awk -F, -v OFS=, 'NR==1{print "scenario_id,classification,reason";next}{c="SOCIAL_ENGINEERING";r="SECRET_DISCLOSED_AFTER_DECEPTION";if($5=="yes"){c="SESSION_THEFT";r="AUTHENTICATED_SESSION_ARTIFACT_REUSED"}else if($6>=5){c="MFA_FATIGUE";r="REPEATED_UNSOLICITED_APPROVALS"}else if($4=="yes"){c="CREDENTIAL_STUFFING";r="BREACHED_CREDENTIAL_PAIRS_REPLAYED"}else if($2>5&&$3<=2){c="PASSWORD_SPRAYING";r="FEW_PASSWORDS_MANY_ACCOUNTS"}else if($2==1&&$3>=10){c="ONLINE_GUESSING";r="MANY_PASSWORDS_ONE_ACCOUNT"}print $1,c,r}' "$ROOT/input/auth-threats.csv" >"$ROOT/output/auth-threats.csv"
  awk -F, -v OFS=, 'NR==1{print "account_id,decision,priority,reasons";next}{d="PASS_WITH_REVIEW";p="NORMAL";r="BASELINE_MET";if($2=="service_account"){d="REVIEW_NON_INTERACTIVE_KEY";p="HIGH";r="VAULT_ROTATION_AND_SCOPE_REQUIRED"}else if($4=="no"&&$3=="high"){d="BLOCK_PASSWORD_REUSE_AND_WEAK_RECOVERY";p="CRITICAL";r="PASSWORD_REUSE+HIGH_PRIVILEGE";if($9=="no")r=r"+RECOVERY_UNVERIFIED"}else if($9=="no"){d="BLOCK_RECOVERY_UNVERIFIED";p="HIGH";r="RECOVERY_CHANNEL_NOT_VERIFIED"}else if($6=="PASSWORD_ONLY"){d="ENABLE_MFA";p="HIGH";r="SINGLE_FACTOR_ONLY"}else if($7=="yes"){d="PASS_PHISHING_RESISTANT";r="CRYPTOGRAPHIC_DOMAIN_BOUND_AUTHENTICATION"}else if($6=="PUSH"){d="IMPROVE_NON_RESISTANT_MFA";p="MEDIUM";r="PUSH_APPROVAL_NOT_PHISHING_RESISTANT"}else{d="ACCEPTABLE_MFA_NOT_PHISHING_RESISTANT";p="MEDIUM";r="MANUAL_OTP_ENTRY_CAN_BE_RELAYED"}if($5=="no")r=r"+NO_PASSWORD_MANAGER";print $1,d,p,r}' "$ROOT/input/accounts.csv" >"$ROOT/output/identity-review.csv"
  awk -F, -v OFS=, 'NR==1{print "chain_id,entry_vector,consequence_path,response,lab_execution";next}{print $1,$2,$4" -> "$5" -> "$6,$7,"NO_PAYLOAD_EXECUTED"}' "$ROOT/input/malware-chains.csv" >"$ROOT/output/malware-map.csv"
  awk -F, -v OFS=, 'FNR==NR{if(FNR>1)m[$1]=1;next}FNR==1{print "case_id,message_id,decision,action,reasons";next}{if(!m[$2])exit 1;d="REPORT_ACCEPTED";a="PRESERVE_AND_REPORT";r="SAFE_RESPONSE_AND_EVIDENCE";if($3=="opened_attachment"){d="ESCALATE_POSSIBLE_COMPROMISE";a="STOP_WORK_ISOLATE_AND_CONTACT_SECURITY";r="ATTACHMENT_OPENED"}else if($3=="replied_to_message"){d="BLOCK_UNSAFE_RESPONSE";a="STOP_REPLY_USE_KNOWN_CHANNEL";r="ATTACKER_CONTROLLED_CHANNEL_USED"}else if($4!="yes"||$5=="none"||$6=="none"){d="BLOCK_INCOMPLETE_REPORT";a="PRESERVE_VERIFY_AND_REPORT";r="MISSING_EVIDENCE_OR_TRUSTED_CHANNEL"}print $1,$2,d,a,r}' "$ROOT/input/messages.csv" "$ROOT/input/reporting.csv" >"$ROOT/output/reporting-decisions.csv" || fail 'invalid reporting reference'
  { printf '# Synthèse lot 2\n\n'; printf -- '- messages nécessitant prudence : %s\n' "$(awk -F, 'NR>1&&$2!="ROUTINE_VERIFY"{c++}END{print c+0}' "$ROOT/output/message-triage.csv")"; printf -- '- comptes bloqués : %s\n\n' "$(awk -F, 'NR>1&&$2~/^BLOCK_/{c++}END{print c+0}' "$ROOT/output/identity-review.csv")"; cat "$ROOT/input/proof-boundary.md"; } >"$ROOT/evidence/summary.md"
  (cd "$ROOT"; sha256sum refs/*.csv input/* >evidence/sources-after.sha256; sha256sum output/*.csv evidence/summary.md >evidence/outputs.sha256)
  diff -u "$ROOT/evidence/sources-before.sha256" "$ROOT/evidence/sources-after.sha256" >"$ROOT/evidence/source.diff" || fail 'source fixtures changed'
  printf 'Synthetic triage and identity review completed.\n'
}
validate(){
  marked
  for f in output/message-triage.csv output/auth-threats.csv output/identity-review.csv output/malware-map.csv output/reporting-decisions.csv evidence/summary.md evidence/outputs.sha256 evidence/source.diff; do [ -f "$ROOT/$f" ] || fail "missing $f"; done
  [ "$(rows "$ROOT/input/messages.csv")" -eq 8 ] || fail 'expected 8 messages'; [ "$(awk -F, 'NR>1&&$2!="ROUTINE_VERIFY"{c++}END{print c+0}' "$ROOT/output/message-triage.csv")" -eq 6 ] || fail 'expected 6 cautious messages'
  grep -Fq 'MSG-001,PHISHING_SUSPECTED,10,DO_NOT_INTERACT_REPORT' "$ROOT/output/message-triage.csv" || fail 'MSG-001 missing'; grep -Fq 'MSG-005,PHISHING_SUSPECTED,12,DO_NOT_SCAN_REPORT_VERIFY' "$ROOT/output/message-triage.csv" || fail 'MSG-005 missing'; grep -Fq 'MSG-007,AUTH_PROMPT_ABUSE,6,DENY_AND_REPORT' "$ROOT/output/message-triage.csv" || fail 'MSG-007 missing'
  grep -Fq 'AUTH-001,ONLINE_GUESSING' "$ROOT/output/auth-threats.csv" || fail 'guessing missing'; grep -Fq 'AUTH-002,PASSWORD_SPRAYING' "$ROOT/output/auth-threats.csv" || fail 'spraying missing'; grep -Fq 'AUTH-003,CREDENTIAL_STUFFING' "$ROOT/output/auth-threats.csv" || fail 'stuffing missing'; grep -Fq 'AUTH-004,SESSION_THEFT' "$ROOT/output/auth-threats.csv" || fail 'session theft missing'; grep -Fq 'AUTH-005,MFA_FATIGUE' "$ROOT/output/auth-threats.csv" || fail 'MFA fatigue missing'
  grep -Fq 'ACC-001,PASS_PHISHING_RESISTANT' "$ROOT/output/identity-review.csv" || fail 'resistant account missing'; grep -Fq 'ACC-003,BLOCK_PASSWORD_REUSE_AND_WEAK_RECOVERY,CRITICAL' "$ROOT/output/identity-review.csv" || fail 'password reuse blocker missing'; grep -Fq 'ACC-005,ENABLE_MFA,HIGH' "$ROOT/output/identity-review.csv" || fail 'MFA gap missing'; [ "$(awk -F, 'NR>1&&$2~/^BLOCK_/{c++}END{print c+0}' "$ROOT/output/identity-review.csv")" -eq 2 ] || fail 'expected 2 blocked accounts'
  [ "$(awk -F, 'NR>1&&$5=="NO_PAYLOAD_EXECUTED"{c++}END{print c+0}' "$ROOT/output/malware-map.csv")" -eq 4 ] || fail 'malware boundary missing'; grep -Fq 'CASE-002,MSG-004,BLOCK_UNSAFE_RESPONSE' "$ROOT/output/reporting-decisions.csv" || fail 'unsafe response missing'; grep -Fq 'CASE-004,MSG-003,ESCALATE_POSSIBLE_COMPROMISE' "$ROOT/output/reporting-decisions.csv" || fail 'escalation missing'
  [ ! -s "$ROOT/evidence/source.diff" ] || fail 'source diff not empty'; (cd "$ROOT"; sha256sum -c evidence/sources-after.sha256 >/dev/null; sha256sum -c evidence/outputs.sha256 >/dev/null) || fail 'checksum failure'
  printf 'Validation successful: phishing identity malware consequences and reporting are correct.\n'
}
reset(){ marked; rm -rf "$ROOT"; printf 'Cybersecurity threats and identity lab removed: %s\n' "$ROOT"; }
safe
case "$ACTION" in setup)setup;;status)status;;run)run;;validate)validate;;reset)reset;;*)fail "unknown action: $ACTION";;esac
