mirror of
https://github.com/trustedsec/hate_crack.git
synced 2026-07-28 14:47:22 -07:00
The live Hashview tests previously could only run against a remote server
configured in config.json, and the CLI ignored env-var overrides, so the
suite always hit prod. Prod runs an older Hashview whose /v1/jobs/add 500s
on a notify_email kwarg, which masked real client/server drift.
Wire the suite up to a local Hashview docker stack:
- main.py: HASHVIEW_URL / HASHVIEW_API_KEY env vars now override config.json,
so the CLI can be pointed at a local instance without editing config.
- tests/_hashview_local.py + pytest_configure: when HASHVIEW_TEST_LOCAL=1,
bring up + seed the Hashview compose stack (HASHVIEW_REPO), verify
authenticated API access, export the HASHVIEW_* env, and tear down
(HASHVIEW_KEEP=1 keeps it). Runs in configure so collection-time skipif
on HASHVIEW_TEST_REAL sees the exported env.
- tests/hashview_local_seed.py: seed an admin api_key + non-default password
(else Hashview's setup guard redirects every request to /setup), a
customer, a hashfile, and cracked "effective task" data (else /v1/jobs/add
has no tasks to schedule).
- api.py: download_left_hashes now uses GET /v1/hashfiles/<id>; the old
/v1/hashfiles/<id>/left route 404s on current servers.
- subprocess tests: assert on the CLI's actual output ("Job ID:") rather than
the literal "Job created", and strip ambient HASHVIEW_* creds from the
no-key-configured check so the env override doesn't defeat it.
- README/CLAUDE: document HASHVIEW_TEST_LOCAL and the env overrides.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
153 lines
5.0 KiB
Python
153 lines
5.0 KiB
Python
"""Seed a local Hashview docker stack to the state hate_crack's live tests need.
|
|
|
|
Runs INSIDE the Hashview ``app`` container (``PYTHONPATH=/``), where the
|
|
``hashview`` package is importable. Idempotent.
|
|
|
|
Brings the database to:
|
|
- admin user (id=1) with a known ``api_key`` (so hate_crack can authenticate)
|
|
- a Settings row (so the /setup wizard does not intercept requests)
|
|
- a Customer at ``HASHVIEW_CUSTOMER_ID``
|
|
- a Hashfile at ``HASHVIEW_HASHFILE_ID`` holding one (uncracked) hash, so the
|
|
``download-hashes`` / left-list endpoints return data
|
|
- at least one *cracked* Hashes row per hash type in
|
|
``HASHVIEW_SEED_HASH_TYPES`` (comma separated, default ``0,1000``) attached to
|
|
a real Task. ``/v1/jobs/add`` derives its "top 10 effective tasks" from
|
|
cracked hashes of the uploaded file's hash type; without this it answers
|
|
"Not enough data to determine effective tasks" and job creation fails.
|
|
|
|
Required env: HASHVIEW_API_KEY, HASHVIEW_CUSTOMER_ID, HASHVIEW_HASHFILE_ID.
|
|
Optional env: HASHVIEW_SEED_HASH_TYPES, HASHVIEW_SEED_TASK_ID (default 1).
|
|
"""
|
|
import os
|
|
import sys
|
|
|
|
from flask import Flask
|
|
from flask_bcrypt import Bcrypt
|
|
|
|
from hashview.config import Config
|
|
from hashview.models import (
|
|
Customers,
|
|
Hashes,
|
|
HashfileHashes,
|
|
Hashfiles,
|
|
Settings,
|
|
Tasks,
|
|
Users,
|
|
db,
|
|
)
|
|
|
|
REQUIRED_ENV = (
|
|
"HASHVIEW_API_KEY",
|
|
"HASHVIEW_CUSTOMER_ID",
|
|
"HASHVIEW_HASHFILE_ID",
|
|
)
|
|
|
|
|
|
def build_app() -> Flask:
|
|
app = Flask(__name__)
|
|
app.config.from_object(Config)
|
|
db.init_app(app)
|
|
Bcrypt(app)
|
|
return app
|
|
|
|
|
|
def seed(app: Flask) -> None:
|
|
api_key = os.environ["HASHVIEW_API_KEY"]
|
|
customer_id = int(os.environ["HASHVIEW_CUSTOMER_ID"])
|
|
hashfile_id = int(os.environ["HASHVIEW_HASHFILE_ID"])
|
|
task_id = int(os.environ.get("HASHVIEW_SEED_TASK_ID", "1"))
|
|
hash_types = [
|
|
int(x) for x in os.environ.get("HASHVIEW_SEED_HASH_TYPES", "0,1000").split(",") if x.strip()
|
|
]
|
|
|
|
bcrypt = Bcrypt(app)
|
|
|
|
with app.app_context():
|
|
admin = db.session.get(Users, 1)
|
|
if admin is None:
|
|
raise RuntimeError(
|
|
"Admin user (id=1) not found. App must finish booting before seeding."
|
|
)
|
|
admin.api_key = api_key
|
|
admin.admin = True
|
|
# Hashview's setup guard (do_gui_setup_if_needed) redirects every request
|
|
# to /setup/admin-pass while the admin password is still the default.
|
|
# Set a non-default password so the API is reachable without the wizard.
|
|
admin.password = bcrypt.generate_password_hash(
|
|
os.environ.get("HASHVIEW_ADMIN_PASSWORD", "hate-crack-local-pass")
|
|
).decode("utf-8")
|
|
admin.email_address = admin.email_address or "admin@hate-crack.local"
|
|
|
|
if db.session.query(Settings).first() is None:
|
|
db.session.add(
|
|
Settings(retention_period=30, max_runtime_jobs=0, max_runtime_tasks=0)
|
|
)
|
|
|
|
if db.session.get(Customers, customer_id) is None:
|
|
db.session.add(Customers(id=customer_id, name="hate_crack Local Customer"))
|
|
|
|
if db.session.get(Tasks, task_id) is None:
|
|
raise RuntimeError(
|
|
f"Task id={task_id} not found. Default tasks should exist after app boot."
|
|
)
|
|
|
|
if db.session.get(Hashfiles, hashfile_id) is None:
|
|
db.session.add(
|
|
Hashfiles(
|
|
id=hashfile_id,
|
|
name="hate-crack-local-hashfile",
|
|
customer_id=customer_id,
|
|
owner_id=admin.id,
|
|
)
|
|
)
|
|
db.session.flush()
|
|
hash_row = Hashes(
|
|
sub_ciphertext="d41d8cd98f00b204e9800998ecf8427e",
|
|
ciphertext="d41d8cd98f00b204e9800998ecf8427e",
|
|
hash_type=0,
|
|
cracked=False,
|
|
)
|
|
db.session.add(hash_row)
|
|
db.session.flush()
|
|
db.session.add(
|
|
HashfileHashes(
|
|
hash_id=hash_row.id, username="local-user", hashfile_id=hashfile_id
|
|
)
|
|
)
|
|
|
|
# Cracked "effective task" data, one row per hash type the tests upload.
|
|
for ht in hash_types:
|
|
sub = f"seed-cracked-{ht}"
|
|
exists = (
|
|
db.session.query(Hashes)
|
|
.filter_by(sub_ciphertext=sub, hash_type=ht, cracked=True)
|
|
.first()
|
|
)
|
|
if exists is None:
|
|
db.session.add(
|
|
Hashes(
|
|
sub_ciphertext=sub,
|
|
ciphertext=sub,
|
|
hash_type=ht,
|
|
cracked=True,
|
|
task_id=task_id,
|
|
plaintext="password",
|
|
)
|
|
)
|
|
|
|
db.session.commit()
|
|
|
|
|
|
def main() -> int:
|
|
missing = [k for k in REQUIRED_ENV if not os.getenv(k)]
|
|
if missing:
|
|
print(f"hashview_local_seed: missing env vars: {missing}", file=sys.stderr)
|
|
return 2
|
|
seed(build_app())
|
|
print("hashview_local_seed: ok")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|