Sources that always work (botvrij, disconnect, oisd, openphish, phishtank, quad9) drop their user-facing enable_* option; the rule's enabled/disabled state is now solely controlled by the SDK rule toggle. Sources that require credentials (criminalip, malwarebazaar, otx, pulsedive, safebrowsing, threatfox, urlhaus, virustotal) instead implement the new SourcePrecheck interface so the host UI can surface "not configured" before attempting a query.
95 lines
3.1 KiB
Go
95 lines
3.1 KiB
Go
package checker
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
|
|
sdk "git.happydns.org/checker-sdk-go/checker"
|
|
)
|
|
|
|
func TestURLhausSource_NoResults(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_, _ = w.Write([]byte(`{"query_status":"no_results"}`))
|
|
}))
|
|
defer srv.Close()
|
|
|
|
s := &urlhausSource{endpoint: srv.URL}
|
|
results := s.Query(context.Background(), "example.com", "example.com", sdk.CheckerOptions{"urlhaus_auth_key": "k"})
|
|
if len(results) != 1 {
|
|
t.Fatalf("expected 1 result, got %d", len(results))
|
|
}
|
|
r := results[0]
|
|
listed, _ := s.Evaluate(r)
|
|
if !r.Enabled || listed || r.Error != "" {
|
|
t.Fatalf("expected enabled+clean, got %+v, Evaluate listed=%v", r, listed)
|
|
}
|
|
}
|
|
|
|
func TestURLhausSource_Listed(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
_ = r.ParseForm()
|
|
if r.FormValue("host") == "" {
|
|
t.Errorf("missing host form value")
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_, _ = w.Write([]byte(`{
|
|
"query_status":"ok",
|
|
"urlhaus_reference":"https://urlhaus.abuse.ch/host/example.com/",
|
|
"urls":[
|
|
{"url":"http://example.com/payload.exe","url_status":"online","threat":"malware_download","tags":["exe","emotet"],"date_added":"2024-01-01","urlhaus_reference":"https://urlhaus.abuse.ch/url/1/"}
|
|
]
|
|
}`))
|
|
}))
|
|
defer srv.Close()
|
|
|
|
s := &urlhausSource{endpoint: srv.URL}
|
|
r := s.Query(context.Background(), "example.com", "example.com", sdk.CheckerOptions{"urlhaus_auth_key": "k"})[0]
|
|
if len(r.Evidence) != 1 {
|
|
t.Fatalf("expected 1 evidence item, got %+v", r)
|
|
}
|
|
if listed, severity := s.Evaluate(r); !listed || severity != SeverityCrit {
|
|
t.Errorf("expected Evaluate()=(true, crit), got (%v, %q)", listed, severity)
|
|
}
|
|
if r.Evidence[0].Status != "online" {
|
|
t.Errorf("evidence status = %q", r.Evidence[0].Status)
|
|
}
|
|
|
|
// Details should round-trip.
|
|
var d urlhausDetails
|
|
if err := json.Unmarshal(r.Details, &d); err != nil || len(d.URLs) != 1 || d.URLs[0].Threat != "malware_download" {
|
|
t.Errorf("details round-trip wrong: %+v", d)
|
|
}
|
|
|
|
// Rich detail renderer should produce a non-empty table.
|
|
html, err := s.RenderDetail(r)
|
|
if err != nil || !strings.Contains(string(html), "payload.exe") {
|
|
t.Errorf("RenderDetail: html=%q err=%v", html, err)
|
|
}
|
|
}
|
|
|
|
func TestURLhausSource_HTTPError(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusUnauthorized)
|
|
_, _ = w.Write([]byte("missing key"))
|
|
}))
|
|
defer srv.Close()
|
|
|
|
s := &urlhausSource{endpoint: srv.URL}
|
|
r := s.Query(context.Background(), "example.com", "example.com", sdk.CheckerOptions{"urlhaus_auth_key": "k"})[0]
|
|
if r.Error == "" || !strings.Contains(r.Error, "401") {
|
|
t.Errorf("expected 401 error, got %+v", r)
|
|
}
|
|
}
|
|
|
|
func TestURLhausSource_NoAuthKey(t *testing.T) {
|
|
s := &urlhausSource{endpoint: "http://nope"}
|
|
r := s.Query(context.Background(), "example.com", "example.com", nil)[0]
|
|
if r.Enabled {
|
|
t.Errorf("expected disabled when no auth key, got %+v", r)
|
|
}
|
|
}
|