Skip to content

Commit 609a568

Browse files
committed
feat: update to use new Delphi issue schema
1 parent 44427a9 commit 609a568

File tree

4 files changed

+100
-58
lines changed

4 files changed

+100
-58
lines changed

apps/labrinth/migrations/20250810155316_delphi-reports.sql

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
CREATE TYPE delphi_report_severity AS ENUM ('low', 'medium', 'high', 'severe');
1+
CREATE TYPE delphi_severity AS ENUM ('low', 'medium', 'high', 'severe');
22

33
CREATE TYPE delphi_report_issue_status AS ENUM ('pending', 'approved', 'rejected');
44

@@ -11,7 +11,7 @@ CREATE TABLE delphi_reports (
1111
delphi_version INTEGER NOT NULL,
1212
artifact_url VARCHAR(2048) NOT NULL,
1313
created TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP NOT NULL,
14-
severity DELPHI_REPORT_SEVERITY NOT NULL,
14+
severity DELPHI_SEVERITY NOT NULL,
1515
UNIQUE (file_id, delphi_version)
1616
);
1717
CREATE INDEX delphi_version ON delphi_reports (delphi_version);
@@ -29,16 +29,18 @@ CREATE TABLE delphi_report_issues (
2929
);
3030
CREATE INDEX delphi_report_issue_by_status_and_type ON delphi_report_issues (status, issue_type);
3131

32-
-- A Java class affected by a Delphi report issue. Every affected
33-
-- Java class belongs to a specific issue, and an issue can have zero,
34-
-- one, or more affected classes. (Some issues may be artifact-wide,
32+
-- The details of a Delphi report issue, which contain data about a
33+
-- Java class affected by it. Every Delphi report issue details object
34+
-- belongs to a specific issue, and an issue can have zero, one, or
35+
-- more details attached to it. (Some issues may be artifact-wide,
3536
-- or otherwise not really specific to any particular class.)
36-
CREATE TABLE delphi_report_issue_java_classes (
37+
CREATE TABLE delphi_report_issue_details (
3738
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
3839
issue_id BIGINT NOT NULL REFERENCES delphi_report_issues (id)
3940
ON DELETE CASCADE
4041
ON UPDATE CASCADE,
4142
internal_class_name TEXT NOT NULL,
4243
decompiled_source TEXT,
43-
UNIQUE (issue_id, internal_class_name)
44+
data JSONB NOT NULL,
45+
severity DELPHI_SEVERITY NOT NULL
4446
);

apps/labrinth/src/database/models/delphi_report_item.rs

Lines changed: 48 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
use std::{
2+
collections::HashMap,
23
fmt::{self, Display, Formatter},
34
ops::Deref,
45
};
56

67
use chrono::{DateTime, Utc};
78
use serde::{Deserialize, Serialize};
9+
use sqlx::types::Json;
810

911
use crate::database::models::{
10-
DBFileId, DBProjectId, DatabaseError, DelphiReportId, DelphiReportIssueId,
11-
DelphiReportIssueJavaClassId,
12+
DBFileId, DBProjectId, DatabaseError, DelphiReportId,
13+
DelphiReportIssueDetailsId, DelphiReportIssueId,
1214
};
1315

1416
/// A Delphi malware analysis report for a project version file.
@@ -25,7 +27,7 @@ pub struct DBDelphiReport {
2527
pub delphi_version: i32,
2628
pub artifact_url: String,
2729
pub created: DateTime<Utc>,
28-
pub severity: DelphiReportSeverity,
30+
pub severity: DelphiSeverity,
2931
}
3032

3133
impl DBDelphiReport {
@@ -44,20 +46,20 @@ impl DBDelphiReport {
4446
self.file_id as Option<DBFileId>,
4547
self.delphi_version,
4648
self.artifact_url,
47-
self.severity as DelphiReportSeverity,
49+
self.severity as DelphiSeverity,
4850
)
4951
.fetch_one(&mut **transaction)
5052
.await?))
5153
}
5254
}
5355

54-
/// A severity level for a Delphi report.
56+
/// A severity level reported by Delphi.
5557
#[derive(
5658
Deserialize, Serialize, Debug, Clone, Copy, PartialEq, Eq, Hash, sqlx::Type,
5759
)]
5860
#[serde(rename_all = "UPPERCASE")]
59-
#[sqlx(type_name = "delphi_report_severity", rename_all = "snake_case")]
60-
pub enum DelphiReportSeverity {
61+
#[sqlx(type_name = "delphi_severity", rename_all = "snake_case")]
62+
pub enum DelphiSeverity {
6163
Low,
6264
Medium,
6365
High,
@@ -122,7 +124,7 @@ impl Display for DelphiReportListOrder {
122124
pub struct DelphiReportIssueResult {
123125
pub issue: DBDelphiReportIssue,
124126
pub report: DBDelphiReport,
125-
pub java_classes: Vec<DBDelphiReportIssueJavaClass>,
127+
pub details: Vec<DBDelphiReportIssueDetails>,
126128
pub project_id: Option<DBProjectId>,
127129
pub project_published: Option<DateTime<Utc>>,
128130
}
@@ -164,11 +166,11 @@ impl DBDelphiReportIssue {
164166
issue_type,
165167
delphi_report_issues.status AS "status: DelphiReportIssueStatus",
166168
167-
file_id, delphi_version, artifact_url, created, severity AS "severity: DelphiReportSeverity",
168-
json_array(SELECT to_jsonb(delphi_report_issue_java_classes)
169-
FROM delphi_report_issue_java_classes
169+
file_id, delphi_version, artifact_url, created, severity AS "severity: DelphiSeverity",
170+
json_array(SELECT to_jsonb(delphi_report_issue_details)
171+
FROM delphi_report_issue_details
170172
WHERE issue_id = delphi_report_issues.id
171-
) AS "classes: sqlx::types::Json<Vec<DBDelphiReportIssueJavaClass>>",
173+
) AS "details: sqlx::types::Json<Vec<DBDelphiReportIssueDetails>>",
172174
versions.mod_id AS "project_id?", mods.published AS "project_published?"
173175
FROM delphi_report_issues
174176
INNER JOIN delphi_reports ON delphi_reports.id = report_id
@@ -182,8 +184,8 @@ impl DBDelphiReportIssue {
182184
CASE WHEN $3 = 'created_asc' THEN delphi_reports.created ELSE TO_TIMESTAMP(0) END ASC,
183185
CASE WHEN $3 = 'created_desc' THEN delphi_reports.created ELSE TO_TIMESTAMP(0) END DESC,
184186
CASE WHEN $3 = 'pending_status_first' THEN delphi_report_issues.status ELSE 'pending'::delphi_report_issue_status END ASC,
185-
CASE WHEN $3 = 'severity_asc' THEN delphi_reports.severity ELSE 'low'::delphi_report_severity END ASC,
186-
CASE WHEN $3 = 'severity_desc' THEN delphi_reports.severity ELSE 'low'::delphi_report_severity END DESC
187+
CASE WHEN $3 = 'severity_asc' THEN delphi_reports.severity ELSE 'low'::delphi_severity END ASC,
188+
CASE WHEN $3 = 'severity_desc' THEN delphi_reports.severity ELSE 'low'::delphi_severity END DESC
187189
OFFSET $5
188190
LIMIT $4
189191
"#,
@@ -208,10 +210,10 @@ impl DBDelphiReportIssue {
208210
created: row.created,
209211
severity: row.severity,
210212
},
211-
java_classes: row
212-
.classes
213+
details: row
214+
.details
213215
.into_iter()
214-
.flat_map(|class_list| class_list.0)
216+
.flat_map(|details_list| details_list.0)
215217
.collect(),
216218
project_id: row.project_id.map(DBProjectId),
217219
project_published: row.project_published,
@@ -221,37 +223,54 @@ impl DBDelphiReportIssue {
221223
}
222224
}
223225

224-
/// A Java class affected by a Delphi report issue. Every affected
225-
/// Java class belongs to a specific issue, and an issue can have zero,
226-
/// one, or more affected classes. (Some issues may be artifact-wide,
226+
/// The details of a Delphi report issue, which contain data about a
227+
/// Java class affected by it. Every Delphi report issue details object
228+
/// belongs to a specific issue, and an issue can have zero, one, or
229+
/// more details attached to it. (Some issues may be artifact-wide,
227230
/// or otherwise not really specific to any particular class.)
228231
#[derive(Debug, Deserialize, Serialize)]
229-
pub struct DBDelphiReportIssueJavaClass {
230-
pub id: DelphiReportIssueJavaClassId,
232+
pub struct DBDelphiReportIssueDetails {
233+
pub id: DelphiReportIssueDetailsId,
231234
pub issue_id: DelphiReportIssueId,
232235
pub internal_class_name: InternalJavaClassName,
233236
pub decompiled_source: Option<DecompiledJavaClassSource>,
237+
pub data: Json<HashMap<String, serde_json::Value>>,
238+
pub severity: DelphiSeverity,
234239
}
235240

236-
impl DBDelphiReportIssueJavaClass {
237-
pub async fn upsert(
241+
impl DBDelphiReportIssueDetails {
242+
pub async fn insert(
238243
&self,
239244
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
240-
) -> Result<DelphiReportIssueJavaClassId, DatabaseError> {
241-
Ok(DelphiReportIssueJavaClassId(sqlx::query_scalar!(
245+
) -> Result<DelphiReportIssueDetailsId, DatabaseError> {
246+
Ok(DelphiReportIssueDetailsId(sqlx::query_scalar!(
242247
"
243-
INSERT INTO delphi_report_issue_java_classes (issue_id, internal_class_name, decompiled_source)
244-
VALUES ($1, $2, $3)
245-
ON CONFLICT (issue_id, internal_class_name) DO UPDATE SET decompiled_source = $3
248+
INSERT INTO delphi_report_issue_details (issue_id, internal_class_name, decompiled_source, data, severity)
249+
VALUES ($1, $2, $3, $4, $5)
246250
RETURNING id
247251
",
248252
self.issue_id as DelphiReportIssueId,
249253
self.internal_class_name.0,
250254
self.decompiled_source.as_ref().map(|decompiled_source| &decompiled_source.0),
255+
&self.data as &Json<HashMap<String, serde_json::Value>>,
256+
self.severity as DelphiSeverity,
251257
)
252258
.fetch_one(&mut **transaction)
253259
.await?))
254260
}
261+
262+
pub async fn remove_all_by_issue_id(
263+
issue_id: DelphiReportIssueId,
264+
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
265+
) -> Result<u64, DatabaseError> {
266+
Ok(sqlx::query!(
267+
"DELETE FROM delphi_report_issue_details WHERE issue_id = $1",
268+
issue_id as DelphiReportIssueId,
269+
)
270+
.execute(&mut **transaction)
271+
.await?
272+
.rows_affected())
273+
}
255274
}
256275

257276
/// A [Java class name] with dots replaced by forward slashes (/).

apps/labrinth/src/database/models/ids.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,4 +281,4 @@ id_type!(ReportTypeId as i32);
281281
id_type!(StatusId as i32);
282282
id_type!(DelphiReportId as i64);
283283
id_type!(DelphiReportIssueId as i64);
284-
id_type!(DelphiReportIssueJavaClassId as i64);
284+
id_type!(DelphiReportIssueDetailsId as i64);

apps/labrinth/src/routes/internal/delphi.rs

Lines changed: 42 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,13 @@ use crate::{
1212
auth::check_is_moderator_from_headers,
1313
database::{
1414
models::{
15-
DBFileId, DelphiReportId, DelphiReportIssueId,
16-
DelphiReportIssueJavaClassId,
15+
DBFileId, DelphiReportId, DelphiReportIssueDetailsId,
16+
DelphiReportIssueId,
1717
delphi_report_item::{
1818
DBDelphiReport, DBDelphiReportIssue,
19-
DBDelphiReportIssueJavaClass, DecompiledJavaClassSource,
20-
DelphiReportIssueStatus, DelphiReportListOrder,
21-
DelphiReportSeverity, InternalJavaClassName,
19+
DBDelphiReportIssueDetails, DecompiledJavaClassSource,
20+
DelphiReportIssueStatus, DelphiReportListOrder, DelphiSeverity,
21+
InternalJavaClassName,
2222
},
2323
},
2424
redis::RedisPool,
@@ -59,6 +59,14 @@ static DELPHI_CLIENT: LazyLock<reqwest::Client> = LazyLock::new(|| {
5959
.unwrap()
6060
});
6161

62+
#[derive(Deserialize)]
63+
struct DelphiReportIssueDetails {
64+
pub internal_class_name: InternalJavaClassName,
65+
pub decompiled_source: Option<DecompiledJavaClassSource>,
66+
pub data: HashMap<String, serde_json::Value>,
67+
pub severity: DelphiSeverity,
68+
}
69+
6270
#[derive(Deserialize)]
6371
struct DelphiReport {
6472
pub url: String,
@@ -69,11 +77,8 @@ struct DelphiReport {
6977
/// A sequential, monotonically increasing version number for the
7078
/// Delphi version that generated this report.
7179
pub delphi_version: i32,
72-
pub issues: HashMap<
73-
String,
74-
HashMap<InternalJavaClassName, Option<DecompiledJavaClassSource>>,
75-
>,
76-
pub severity: DelphiReportSeverity,
80+
pub issues: HashMap<String, Vec<DelphiReportIssueDetails>>,
81+
pub severity: DelphiSeverity,
7782
}
7883

7984
impl DelphiReport {
@@ -88,12 +93,19 @@ impl DelphiReport {
8893
format!("⚠️ Suspicious traces found at {}", self.url);
8994

9095
for (issue, trace) in &self.issues {
91-
for (class, code) in trace {
92-
let code = code.as_deref().map(|code| &**code);
96+
for DelphiReportIssueDetails {
97+
internal_class_name,
98+
decompiled_source,
99+
..
100+
} in trace
101+
{
93102
write!(
94103
&mut message_header,
95-
"\n issue {issue} found at class `{class}`:\n```\n{}\n```",
96-
code.unwrap_or("No decompiled source available")
104+
"\n issue {issue} found at class `{internal_class_name}`:\n```\n{}\n```",
105+
decompiled_source.as_ref().map_or(
106+
"No decompiled source available",
107+
|decompiled_source| &**decompiled_source
108+
)
97109
)
98110
.ok();
99111
}
@@ -141,7 +153,7 @@ async fn ingest_report(
141153
.upsert(&mut transaction)
142154
.await?;
143155

144-
for (issue_type, issue_java_classes) in report.issues {
156+
for (issue_type, issue_details) in report.issues {
145157
let issue_id = DBDelphiReportIssue {
146158
id: DelphiReportIssueId(0), // This will be set by the database
147159
report_id,
@@ -151,14 +163,23 @@ async fn ingest_report(
151163
.upsert(&mut transaction)
152164
.await?;
153165

154-
for (internal_class_name, decompiled_source) in issue_java_classes {
155-
DBDelphiReportIssueJavaClass {
156-
id: DelphiReportIssueJavaClassId(0), // This will be set by the database
166+
// This is required to handle the case where the same Delphi version is re-run on the same file
167+
DBDelphiReportIssueDetails::remove_all_by_issue_id(
168+
issue_id,
169+
&mut transaction,
170+
)
171+
.await?;
172+
173+
for issue_detail in issue_details {
174+
DBDelphiReportIssueDetails {
175+
id: DelphiReportIssueDetailsId(0), // This will be set by the database
157176
issue_id,
158-
internal_class_name,
159-
decompiled_source,
177+
internal_class_name: issue_detail.internal_class_name,
178+
decompiled_source: issue_detail.decompiled_source,
179+
data: issue_detail.data.into(),
180+
severity: issue_detail.severity,
160181
}
161-
.upsert(&mut transaction)
182+
.insert(&mut transaction)
162183
.await?;
163184
}
164185
}

0 commit comments

Comments
 (0)