mirror of
https://github.com/yogeshojha/rengine.git
synced 2026-10-20 23:31:31 +02:00
- sync the account's reports, bounties and standing from the HackerOne hacker API into bounty_reports, bounty_awards and bounty_accounts; money comes from /payments/earnings, totals stay per currency - /bounty-hub/<platform> as a Bounty Hub child in the sidebar, in the /scans idiom: stat strip, severity toggles, monthly chart with drag range, stage tabs with filtered counts, URL filters, expandable rows, keyboard navigation and a by-program view - program names open the Bounty Hub program sheet, which links back to the reports on that program and fetches unfetched scope on open - hourly report sync gated on bug bounty mode and the sync interval Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
276 lines
9.4 KiB
Python
276 lines
9.4 KiB
Python
"""Refresh the bug bounty program list and every program's structured scope."""
|
|
|
|
from celery import shared_task
|
|
from sqlalchemy import select, text
|
|
from sqlmodel import col
|
|
|
|
from app.config import settings
|
|
from app.database import get_sync_session
|
|
from shared.definitions.bounty_feed import FEEDS_BY_PLATFORM
|
|
from shared.definitions.bounty_programs import (
|
|
PLATFORMS_BY_KEY,
|
|
BountyPlatform,
|
|
ProgramSource,
|
|
ScopeAccess,
|
|
notify_enabled,
|
|
notify_events,
|
|
)
|
|
from shared.definitions.notifications import BountyChange, bounty_changes
|
|
from shared.logging import get_logger
|
|
from shared.models.bounty_program import BountyEventRow, BountyProgram
|
|
from shared.services.bounty_feed import (
|
|
feed_due,
|
|
mark_feed_synced,
|
|
sync_feeds,
|
|
sync_platform,
|
|
)
|
|
from shared.services.bounty_programs import (
|
|
mark_synced,
|
|
scopes_to_refresh,
|
|
sync_due,
|
|
sync_programs,
|
|
sync_scopes,
|
|
)
|
|
from shared.services.bounty_providers import (
|
|
BountyProvider,
|
|
BountyProviderError,
|
|
CredentialsError,
|
|
configured_providers,
|
|
provider_for,
|
|
)
|
|
from shared.services.bounty_reports import reports_enabled
|
|
from shared.services.bounty_reports import sync_reports as store_reports
|
|
from shared.services.celery_dispatch import dispatch_watch_reconcile
|
|
from shared.services.locks import (
|
|
BOUNTY_FEED,
|
|
bounty_platform,
|
|
bounty_reports,
|
|
sync_lock,
|
|
)
|
|
from shared.services.notification_sync import SyncNotificationPublisher
|
|
from shared.utils.datetime import utc_now
|
|
|
|
logger = get_logger(__name__)
|
|
|
|
SCOPE_FAILURE_BUDGET = 25
|
|
ALERT_LIMIT = 40
|
|
|
|
|
|
def _notify(session, since) -> int:
|
|
"""Delta-only, and only the change kinds the operator asked to hear about."""
|
|
stored = session.execute(
|
|
text("SELECT bounty_settings FROM instance_settings LIMIT 1")
|
|
).scalar_one_or_none()
|
|
if not notify_enabled(stored):
|
|
return 0
|
|
kinds = notify_events(stored)
|
|
if not kinds:
|
|
return 0
|
|
rows = (
|
|
session.execute(
|
|
select(BountyEventRow)
|
|
.where(
|
|
BountyEventRow.created_at >= since,
|
|
col(BountyEventRow.kind).in_(sorted(kinds)),
|
|
)
|
|
.order_by(BountyEventRow.created_at.desc())
|
|
.limit(ALERT_LIMIT)
|
|
)
|
|
.scalars()
|
|
.all()
|
|
)
|
|
payload = bounty_changes(
|
|
[
|
|
BountyChange(
|
|
kind=r.kind,
|
|
program=r.program_name,
|
|
handle=r.handle,
|
|
asset=r.asset_identifier,
|
|
)
|
|
for r in rows
|
|
]
|
|
)
|
|
if payload is None:
|
|
return 0
|
|
try:
|
|
SyncNotificationPublisher(settings.redis_url).publish(
|
|
session=session,
|
|
type=payload["type"],
|
|
severity=payload["severity"],
|
|
title=payload["title"],
|
|
message=payload["message"],
|
|
metadata=payload.get("metadata"),
|
|
)
|
|
except Exception:
|
|
logger.warning("bounty change notification failed", exc_info=True)
|
|
return len(rows)
|
|
|
|
|
|
def _sync_platform(session, provider: BountyProvider, *, scopes: bool) -> dict:
|
|
"""One platform's programs, then the scope of every program that moved."""
|
|
try:
|
|
result = sync_programs(session, provider)
|
|
except BountyProviderError as exc:
|
|
logger.warning(
|
|
"bounty program sync failed", platform=provider.platform, error=str(exc)
|
|
)
|
|
return {"platform": provider.platform, "error": str(exc)}
|
|
if not scopes:
|
|
return result
|
|
|
|
assets = 0
|
|
failed = 0
|
|
denied = 0
|
|
for program in scopes_to_refresh(session, provider):
|
|
try:
|
|
assets += sync_scopes(session, program, provider)
|
|
denied += program.scope_access == ScopeAccess.DENIED.value
|
|
except CredentialsError as exc:
|
|
logger.warning(
|
|
"bounty scope sync rejected",
|
|
platform=provider.platform,
|
|
error=str(exc),
|
|
)
|
|
break
|
|
except BountyProviderError as exc:
|
|
failed += 1
|
|
logger.info(
|
|
"bounty scope sync failed",
|
|
platform=provider.platform,
|
|
handle=program.handle,
|
|
error=str(exc),
|
|
)
|
|
if failed >= SCOPE_FAILURE_BUDGET:
|
|
logger.warning(
|
|
"bounty scope sync abandoned",
|
|
platform=provider.platform,
|
|
failed=failed,
|
|
)
|
|
break
|
|
return {
|
|
**result,
|
|
"assets": assets,
|
|
"scope_failures": failed,
|
|
"scope_denied": denied,
|
|
}
|
|
|
|
|
|
@shared_task(name="app.tasks.bounty_programs.sync")
|
|
def sync(scopes: bool = True, force: bool = True, platform: str | None = None) -> dict:
|
|
"""Pull programs, then each program's scope, for every connected platform."""
|
|
started = utc_now()
|
|
with get_sync_session() as session:
|
|
if not force and not sync_due(session):
|
|
return {"skipped": "not_due"}
|
|
providers = (
|
|
[p for p in [provider_for(session, platform)] if p]
|
|
if platform
|
|
else configured_providers(session)
|
|
)
|
|
if not providers:
|
|
logger.info("bounty program sync skipped, no platform credentials")
|
|
return {"skipped": "not_configured"}
|
|
|
|
results = []
|
|
for provider in providers:
|
|
with sync_lock(session, bounty_platform(provider.platform)) as held:
|
|
if not held:
|
|
results.append(
|
|
{"platform": provider.platform, "skipped": "already_running"}
|
|
)
|
|
continue
|
|
results.append(_sync_platform(session, provider, scopes=scopes))
|
|
|
|
mark_synced(session)
|
|
alerted = _notify(session, started)
|
|
if scopes:
|
|
dispatch_watch_reconcile()
|
|
return {"platforms": results, "alerted": alerted}
|
|
|
|
|
|
def _reports(session, provider: BountyProvider) -> dict:
|
|
with sync_lock(session, bounty_reports(provider.platform)) as held:
|
|
if not held:
|
|
return {"platform": provider.platform, "skipped": "already_running"}
|
|
try:
|
|
result = store_reports(session, provider)
|
|
except Exception as exc:
|
|
session.rollback()
|
|
logger.warning(
|
|
"bounty report sync failed", platform=provider.platform, error=str(exc)
|
|
)
|
|
return {"platform": provider.platform, "error": str(exc)}
|
|
return {"platform": provider.platform, **(result or {"skipped": "unsupported"})}
|
|
|
|
|
|
@shared_task(name="app.tasks.bounty_programs.sync_reports")
|
|
def sync_reports(platform: str | None = None, force: bool = True) -> dict:
|
|
"""The account's own reports and bounties on every connected platform that has them."""
|
|
with get_sync_session() as session:
|
|
if not force and not reports_enabled(session):
|
|
return {"skipped": "not_due"}
|
|
providers = (
|
|
[p for p in [provider_for(session, platform)] if p]
|
|
if platform
|
|
else configured_providers(session)
|
|
)
|
|
tracked = [p for p in providers if PLATFORMS_BY_KEY[p.platform].tracks_reports]
|
|
if not tracked:
|
|
return {"skipped": "not_configured"}
|
|
return {"platforms": [_reports(session, p) for p in tracked]}
|
|
|
|
|
|
@shared_task(name="app.tasks.bounty_programs.sync_program")
|
|
def sync_program(handle: str, platform: str = BountyPlatform.HACKERONE.value) -> dict:
|
|
"""Refresh one program's scope, for a program opened before the sweep reached it."""
|
|
with get_sync_session() as session:
|
|
program = session.execute(
|
|
select(BountyProgram).where(
|
|
BountyProgram.platform == platform,
|
|
BountyProgram.handle == handle,
|
|
)
|
|
).scalar_one_or_none()
|
|
if not program:
|
|
return {"error": "unknown program"}
|
|
feed = program.source == ProgramSource.FEED.value
|
|
key = BOUNTY_FEED if feed else bounty_platform(platform)
|
|
with sync_lock(session, key) as held:
|
|
if not held:
|
|
return {"skipped": "already_running"}
|
|
if feed:
|
|
spec = FEEDS_BY_PLATFORM.get(platform)
|
|
return (
|
|
sync_platform(session, spec)
|
|
if spec
|
|
else {"error": "unknown platform"}
|
|
)
|
|
provider = provider_for(session, platform)
|
|
if not provider:
|
|
return {"skipped": "not_configured"}
|
|
try:
|
|
return {"assets": sync_scopes(session, program, provider)}
|
|
except BountyProviderError as exc:
|
|
logger.info(
|
|
"bounty scope sync failed",
|
|
platform=platform,
|
|
handle=handle,
|
|
error=str(exc),
|
|
)
|
|
return {"error": str(exc)}
|
|
|
|
|
|
@shared_task(name="app.tasks.bounty_programs.sync_feed")
|
|
def sync_feed(force: bool = True) -> dict:
|
|
"""Public scope for the platforms with no researcher API."""
|
|
started = utc_now()
|
|
with get_sync_session() as session, sync_lock(session, BOUNTY_FEED) as held:
|
|
if not held:
|
|
return {"skipped": "already_running"}
|
|
if not force and not feed_due(session):
|
|
return {"skipped": "not_due"}
|
|
result = sync_feeds(session)
|
|
if result["platforms"]:
|
|
mark_feed_synced(session)
|
|
dispatch_watch_reconcile()
|
|
return {**result, "alerted": _notify(session, started)}
|