763e06c127
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
127 lines
5.4 KiB
Python
127 lines
5.4 KiB
Python
"""Registra/actualiza un perfil de Chromium (y opcionalmente sus cuentas) en osint_db.
|
|
|
|
Wrapper cliente del service local `osint_db` (FastAPI + DuckDB single-writer) que
|
|
mantiene el catalogo de perfiles del navegador usados para investigaciones multicuenta
|
|
OSINT. En una sola llamada hace:
|
|
|
|
1. POST /api/browser-profile con la metadata del perfil (upsert idempotente).
|
|
2. Un POST /api/browser-profile/account por cada cuenta de la lista `accounts`.
|
|
|
|
Funcion impura: hace red (HTTP al service). No lanza; devuelve un dict de estado.
|
|
El service responde SIEMPRE HTTP 200 con body `{"status":...}` (se parsea el body).
|
|
"""
|
|
|
|
from browser._osint_db_client import post_json
|
|
|
|
|
|
def browser_profile_register(
|
|
profile_dir: str,
|
|
label: str = "",
|
|
persona: str = "",
|
|
purpose: str = "",
|
|
note_path: str = "",
|
|
tags: list | None = None,
|
|
notes: str = "",
|
|
user_data_dir: str = "",
|
|
status: str = "active",
|
|
accounts: list | None = None,
|
|
base_url: str = "http://127.0.0.1:8771",
|
|
) -> dict:
|
|
"""Registra o actualiza un perfil Chromium y sus cuentas en el catalogo osint_db.
|
|
|
|
Args:
|
|
profile_dir: nombre del directorio real del perfil Chromium (ej. "Profile 1",
|
|
"Default", "osint_01"). Es la PK del perfil; el upsert es idempotente sobre el.
|
|
label: etiqueta humana del perfil (ej. "Persona Maria - OSINT"). "" para omitir.
|
|
persona: identidad/alias ficticio asociado al perfil. "" para omitir.
|
|
purpose: proposito de la investigacion (ej. "rastreo cuentas falsas"). "" para omitir.
|
|
note_path: ruta (rel al vault) de la nota OSINT ligada al perfil. "" para omitir.
|
|
tags: lista de strings de etiquetas (ej. ["osint", "sock-puppet"]). None -> [].
|
|
notes: notas libres sobre el perfil. "" para omitir.
|
|
user_data_dir: directorio user-data-dir de Chromium si NO es el default del wrapper.
|
|
"" -> el perfil hereda el default chromium-cdp al abrirlo.
|
|
status: estado del perfil (active|archived|burned...). Default "active".
|
|
accounts: lista de dicts de cuentas a registrar, cada uno
|
|
{service, identity, secret_ref?, role?, status?, notes?}. None -> sin cuentas.
|
|
`secret_ref` es una REFERENCIA al secreto (ej. "pass show osint/p1/gmail"),
|
|
NUNCA el password en claro.
|
|
base_url: base del service osint_db. Default http://127.0.0.1:8771.
|
|
|
|
Returns:
|
|
Caso ok: {"status":"ok", "profile_dir": str, "accounts": int (cuentas registradas
|
|
con exito), "account_errors": list (errores por cuenta, vacia si todo OK)}.
|
|
Caso error (fallo del POST del perfil): {"status":"error", "error": str}.
|
|
"""
|
|
try:
|
|
profile_payload: dict = {"profile_dir": profile_dir, "status": status}
|
|
if label:
|
|
profile_payload["label"] = label
|
|
if persona:
|
|
profile_payload["persona"] = persona
|
|
if purpose:
|
|
profile_payload["purpose"] = purpose
|
|
if note_path:
|
|
profile_payload["note_path"] = note_path
|
|
if tags:
|
|
profile_payload["tags"] = list(tags)
|
|
if notes:
|
|
profile_payload["notes"] = notes
|
|
if user_data_dir:
|
|
profile_payload["user_data_dir"] = user_data_dir
|
|
|
|
resp = post_json(base_url, "/api/browser-profile", profile_payload)
|
|
if resp.get("status") != "ok":
|
|
return {
|
|
"status": "error",
|
|
"error": resp.get("error", f"el service rechazo el perfil: {resp}"),
|
|
}
|
|
|
|
registered_accounts = 0
|
|
account_errors: list = []
|
|
for acc in accounts or []:
|
|
if not isinstance(acc, dict) or not acc.get("service") or not acc.get("identity"):
|
|
account_errors.append(
|
|
{"account": acc, "error": "cuenta requiere al menos {service, identity}"}
|
|
)
|
|
continue
|
|
acc_payload = {"profile_dir": profile_dir}
|
|
for key in ("service", "identity", "secret_ref", "role", "status", "notes"):
|
|
if acc.get(key):
|
|
acc_payload[key] = acc[key]
|
|
acc_resp = post_json(base_url, "/api/browser-profile/account", acc_payload)
|
|
if acc_resp.get("status") == "ok":
|
|
registered_accounts += 1
|
|
else:
|
|
account_errors.append(
|
|
{
|
|
"account": {"service": acc.get("service"), "identity": acc.get("identity")},
|
|
"error": acc_resp.get("error", str(acc_resp)),
|
|
}
|
|
)
|
|
|
|
return {
|
|
"status": "ok",
|
|
"profile_dir": profile_dir,
|
|
"accounts": registered_accounts,
|
|
"account_errors": account_errors,
|
|
}
|
|
except Exception as e: # noqa: BLE001 - contrato: nunca lanzar
|
|
return {"status": "error", "error": f"{type(e).__name__}: {e}"}
|
|
|
|
|
|
if __name__ == "__main__":
|
|
# Smoke contra un puerto muerto: ejercita la degradacion graceful (service inaccesible).
|
|
res = browser_profile_register(
|
|
"Profile 1",
|
|
label="Persona Maria - OSINT",
|
|
persona="maria_ficticia",
|
|
purpose="rastreo cuentas falsas",
|
|
tags=["osint", "sock-puppet"],
|
|
accounts=[{"service": "gmail", "identity": "maria@example.com",
|
|
"secret_ref": "pass show osint/p1/gmail"}],
|
|
base_url="http://127.0.0.1:1",
|
|
)
|
|
assert res["status"] == "error", res
|
|
print("browser_profile_register smoke OK (service caido -> status error)")
|
|
print(f" {res}")
|