mirror of
https://github.com/modrinth/code.git
synced 2026-08-31 12:05:53 +00:00
adjust schema
This commit is contained in:
Generated
-9
@@ -2909,15 +2909,6 @@ version = "0.1.10"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "26bf8fc351c5ed29b5c2f0cbbac1b209b74f60ecd62e675a998df72c49af5204"
|
checksum = "26bf8fc351c5ed29b5c2f0cbbac1b209b74f60ecd62e675a998df72c49af5204"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "delphi-cel-experiment"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"cel",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "der"
|
name = "der"
|
||||||
version = "0.7.10"
|
version = "0.7.10"
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ members = [
|
|||||||
"packages/app-lib",
|
"packages/app-lib",
|
||||||
"packages/ariadne",
|
"packages/ariadne",
|
||||||
"packages/daedalus",
|
"packages/daedalus",
|
||||||
"packages/delphi-cel-experiment",
|
|
||||||
"packages/labrinth-derive",
|
"packages/labrinth-derive",
|
||||||
"packages/modrinth-content-management",
|
"packages/modrinth-content-management",
|
||||||
"packages/modrinth-log",
|
"packages/modrinth-log",
|
||||||
|
|||||||
@@ -461,10 +461,16 @@ const TEST_INPUTS: Labrinth.TechReview.Internal.RuleInput[] = [
|
|||||||
sha512: 'fedcba9876543210',
|
sha512: 'fedcba9876543210',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
scope: {
|
project: {
|
||||||
project_id: 'example-project',
|
id: 'example-project',
|
||||||
version_id: 'example-version',
|
types: ['mod'],
|
||||||
file_id: 'example-file',
|
},
|
||||||
|
version: {
|
||||||
|
id: 'example-version',
|
||||||
|
loaders: ['fabric'],
|
||||||
|
},
|
||||||
|
file: {
|
||||||
|
id: 'example-file',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -489,10 +495,16 @@ const TEST_INPUTS: Labrinth.TechReview.Internal.RuleInput[] = [
|
|||||||
sha512: '0123456789abcdef',
|
sha512: '0123456789abcdef',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
scope: {
|
project: {
|
||||||
project_id: 'telemetry-project',
|
id: 'telemetry-project',
|
||||||
version_id: 'telemetry-version',
|
types: ['mod'],
|
||||||
file_id: 'telemetry-file',
|
},
|
||||||
|
version: {
|
||||||
|
id: 'telemetry-version',
|
||||||
|
loaders: ['neoforge'],
|
||||||
|
},
|
||||||
|
file: {
|
||||||
|
id: 'telemetry-file',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|||||||
Generated
+22
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "SELECT COUNT(*) FROM payouts_values_notifications WHERE notified = FALSE AND user_id = $1",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"ordinal": 0,
|
||||||
|
"name": "count",
|
||||||
|
"type_info": "Int8"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Int8"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
null
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hash": "0f3d943e4fc48a94363b77c8a7d36eb1dd626e77331d8278c406df952691be4c"
|
||||||
|
}
|
||||||
Generated
+12
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "\n INSERT INTO users (id, username, email, role)\n VALUES\n (1000, 'userland', 'userland@modrinth.com', 'developer'),\n (1001, 'useful', 'useful@modrinth.com', 'developer'),\n (1002, 'Useless', 'useless@modrinth.com', 'developer')\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Left": []
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "0ffebf149d50149d0c128478e6267a2296efc16763cdbd5d8c295a1a7ca08021"
|
||||||
|
}
|
||||||
Generated
+32
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "\n SELECT id AS \"id!: DelphiRuleId\", name, rule\n FROM delphi_rules\n WHERE NOT delete_on_next_revision\n ORDER BY priority DESC, id\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"ordinal": 0,
|
||||||
|
"name": "id!: DelphiRuleId",
|
||||||
|
"type_info": "Int8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 1,
|
||||||
|
"name": "name",
|
||||||
|
"type_info": "Varchar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 2,
|
||||||
|
"name": "rule",
|
||||||
|
"type_info": "Varchar"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Left": []
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hash": "182be4110f45b6be6a9174200a07a887cebbaeab0a8c1da8b008e0c5e94fbc4e"
|
||||||
|
}
|
||||||
Generated
+26
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "\n SELECT\n id,\n status AS \"status: PayoutStatus\"\n FROM payouts\n ORDER BY id\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"ordinal": 0,
|
||||||
|
"name": "id",
|
||||||
|
"type_info": "Int8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 1,
|
||||||
|
"name": "status: PayoutStatus",
|
||||||
|
"type_info": "Varchar"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Left": []
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hash": "1adbd24d815107e13bc1440c7a8f4eeff66ab4165a9f4980032e114db4dc1286"
|
||||||
|
}
|
||||||
Generated
+20
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "SELECT COUNT(*) FROM payouts_values_notifications WHERE notified = FALSE",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"ordinal": 0,
|
||||||
|
"name": "count",
|
||||||
|
"type_info": "Int8"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Left": []
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
null
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hash": "20cff8fdf7971e91c9d473b9a4663ce02ca16781e32232ae0fa7a0af1973d3a4"
|
||||||
|
}
|
||||||
Generated
+14
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "UPDATE mods SET status = 'processing' WHERE id = $1",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Int8"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "329c009635ad2a4c214da2f4651b8c14fe329deac983bc2f43ba74e066f9cf8d"
|
||||||
|
}
|
||||||
Generated
+12
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "\n INSERT INTO users (id, username, email, role)\n VALUES (2100, 'prefix_under_score', 'prefix_under_score@modrinth.com', 'developer')\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Left": []
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "38ace0abad724d1111b79a72c7c8518f1272690ce7766dbdc41ec1e98f7f1fec"
|
||||||
|
}
|
||||||
Generated
-26
@@ -1,26 +0,0 @@
|
|||||||
{
|
|
||||||
"db_name": "PostgreSQL",
|
|
||||||
"query": "\n SELECT id AS \"id!: DelphiRuleId\", rule\n FROM delphi_rules\n WHERE NOT delete_on_next_revision\n ORDER BY priority DESC, id\n ",
|
|
||||||
"describe": {
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"ordinal": 0,
|
|
||||||
"name": "id!: DelphiRuleId",
|
|
||||||
"type_info": "Int8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 1,
|
|
||||||
"name": "rule",
|
|
||||||
"type_info": "Varchar"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"parameters": {
|
|
||||||
"Left": []
|
|
||||||
},
|
|
||||||
"nullable": [
|
|
||||||
false,
|
|
||||||
false
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"hash": "3f1aaeed6e394294fa32da7e7f83765fb64bd9499a796470d544466886e1a808"
|
|
||||||
}
|
|
||||||
Generated
+16
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "\n INSERT INTO users (id, username, email, role)\n VALUES ($1, $2, $3, 'developer')\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Int8",
|
||||||
|
"Varchar",
|
||||||
|
"Varchar"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "5cb65218a2a4a7343130d838f8a78df08d25844f1019d539712b0cb686e4ae81"
|
||||||
|
}
|
||||||
Generated
+15
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "INSERT INTO payouts_values_notifications (date_available, user_id, notified)\n VALUES ($1, $2, FALSE)\n ON CONFLICT (date_available, user_id) DO NOTHING",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Timestamptz",
|
||||||
|
"Int8"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "6678cd4c51771cfaae2be8021ba66908ea41a06ba858dc5b523aef6aae27b850"
|
||||||
|
}
|
||||||
Generated
+12
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "CREATE SCHEMA public;",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Left": []
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "66868d0b35229abffa3f668eaef19dc0cdef502935dd5939a2f3ab89b0766ad2"
|
||||||
|
}
|
||||||
Generated
+16
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "INSERT INTO payouts_values (user_id, mod_id, amount, created, date_available)\n VALUES ($1, NULL, $2, NOW(), $3)",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Int8",
|
||||||
|
"Numeric",
|
||||||
|
"Timestamptz"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "69a1cb4b7f1115a990d1fc4805d58541fc78e910111c09ba3d50a12d9ca4a9f8"
|
||||||
|
}
|
||||||
+14
-2
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"db_name": "PostgreSQL",
|
"db_name": "PostgreSQL",
|
||||||
"query": "\n SELECT\n detail.id,\n detail.key,\n issue.issue_type,\n detail.severity AS \"severity: DelphiSeverity\",\n detail.jar,\n detail.file_path,\n detail.data AS \"data: Json<HashMap<String, serde_json::Value>>\",\n report.delphi_version,\n file.size AS \"size?\",\n file.id AS \"file_id?\",\n version.id AS \"version_id?\",\n version.mod_id AS \"project_id?\",\n COALESCE(file_hashes.hashes, '{}'::jsonb)\n AS \"hashes!: Json<BTreeMap<String, String>>\"\n FROM delphi_report_issue_details detail\n INNER JOIN delphi_report_issues issue ON issue.id = detail.issue_id\n INNER JOIN delphi_reports report ON report.id = issue.report_id\n LEFT JOIN files file ON file.id = report.file_id\n LEFT JOIN versions version ON version.id = file.version_id\n LEFT JOIN LATERAL (\n SELECT\n jsonb_object_agg(algorithm, encode(hash, 'hex')) AS hashes\n FROM hashes\n WHERE hashes.file_id = file.id\n ) file_hashes ON TRUE\n WHERE detail.id = ANY($1::bigint[])\n ORDER BY detail.id\n ",
|
"query": "\n SELECT\n detail.id,\n detail.key,\n issue.issue_type,\n detail.severity AS \"severity: DelphiSeverity\",\n detail.jar,\n detail.file_path,\n detail.data AS \"data: Json<HashMap<String, serde_json::Value>>\",\n report.delphi_version,\n file.size AS \"size?\",\n file.id AS \"file_id?\",\n version.id AS \"version_id?\",\n version.mod_id AS \"project_id?\",\n COALESCE(version_metadata.project_types, ARRAY[]::text[])\n AS \"project_types!: Vec<String>\",\n COALESCE(version_metadata.loaders, ARRAY[]::text[])\n AS \"loaders!: Vec<String>\",\n COALESCE(file_hashes.hashes, '{}'::jsonb)\n AS \"hashes!: Json<BTreeMap<String, String>>\"\n FROM delphi_report_issue_details detail\n INNER JOIN delphi_report_issues issue ON issue.id = detail.issue_id\n INNER JOIN delphi_reports report ON report.id = issue.report_id\n LEFT JOIN files file ON file.id = report.file_id\n LEFT JOIN versions version ON version.id = file.version_id\n LEFT JOIN LATERAL (\n SELECT\n ARRAY_AGG(\n DISTINCT project_type.name::text\n ORDER BY project_type.name::text\n ) FILTER (WHERE project_type.name IS NOT NULL)\n AS project_types,\n ARRAY_AGG(\n DISTINCT loader.loader\n ORDER BY loader.loader\n ) AS loaders\n FROM loaders_versions loader_version\n INNER JOIN loaders loader\n ON loader.id = loader_version.loader_id\n LEFT JOIN loaders_project_types loader_project_type\n ON loader_project_type.joining_loader_id = loader_version.loader_id\n LEFT JOIN project_types project_type\n ON project_type.id = loader_project_type.joining_project_type_id\n WHERE loader_version.version_id = version.id\n ) version_metadata ON TRUE\n LEFT JOIN LATERAL (\n SELECT\n jsonb_object_agg(algorithm, encode(hash, 'hex')) AS hashes\n FROM hashes\n WHERE hashes.file_id = file.id\n ) file_hashes ON TRUE\n WHERE detail.id = ANY($1::bigint[])\n ORDER BY detail.id\n ",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
@@ -79,6 +79,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ordinal": 12,
|
"ordinal": 12,
|
||||||
|
"name": "project_types!: Vec<String>",
|
||||||
|
"type_info": "TextArray"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 13,
|
||||||
|
"name": "loaders!: Vec<String>",
|
||||||
|
"type_info": "VarcharArray"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 14,
|
||||||
"name": "hashes!: Json<BTreeMap<String, String>>",
|
"name": "hashes!: Json<BTreeMap<String, String>>",
|
||||||
"type_info": "Jsonb"
|
"type_info": "Jsonb"
|
||||||
}
|
}
|
||||||
@@ -101,8 +111,10 @@
|
|||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
null
|
null
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "868d19328921b9a3bd56fa9354e5e75d09c76a3eb3ed9c89b05b9aab13505bf9"
|
"hash": "7e7e2e9f67158b94eb62834cbfd65fd5165d02ebcc0f8454554cbd870e4ddb42"
|
||||||
}
|
}
|
||||||
+14
-2
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"db_name": "PostgreSQL",
|
"db_name": "PostgreSQL",
|
||||||
"query": "\n SELECT\n detail.id,\n detail.key,\n issue.issue_type,\n detail.severity AS \"severity: DelphiSeverity\",\n detail.jar,\n detail.file_path,\n detail.data AS \"data: Json<HashMap<String, serde_json::Value>>\",\n report.delphi_version,\n file.size AS \"size?\",\n file.id AS \"file_id?\",\n version.id AS \"version_id?\",\n version.mod_id AS \"project_id?\",\n COALESCE(file_hashes.hashes, '{}'::jsonb)\n AS \"hashes!: Json<BTreeMap<String, String>>\"\n FROM delphi_report_issue_details detail\n INNER JOIN delphi_report_issues issue ON issue.id = detail.issue_id\n INNER JOIN delphi_reports report ON report.id = issue.report_id\n LEFT JOIN files file ON file.id = report.file_id\n LEFT JOIN versions version ON version.id = file.version_id\n LEFT JOIN (\n SELECT\n file_id,\n jsonb_object_agg(algorithm, encode(hash, 'hex')) AS hashes\n FROM hashes\n GROUP BY file_id\n ) file_hashes ON file_hashes.file_id = file.id\n ORDER BY detail.id\n ",
|
"query": "\n SELECT\n detail.id,\n detail.key,\n issue.issue_type,\n detail.severity AS \"severity: DelphiSeverity\",\n detail.jar,\n detail.file_path,\n detail.data AS \"data: Json<HashMap<String, serde_json::Value>>\",\n report.delphi_version,\n file.size AS \"size?\",\n file.id AS \"file_id?\",\n version.id AS \"version_id?\",\n version.mod_id AS \"project_id?\",\n COALESCE(version_metadata.project_types, ARRAY[]::text[])\n AS \"project_types!: Vec<String>\",\n COALESCE(version_metadata.loaders, ARRAY[]::text[])\n AS \"loaders!: Vec<String>\",\n COALESCE(file_hashes.hashes, '{}'::jsonb)\n AS \"hashes!: Json<BTreeMap<String, String>>\"\n FROM delphi_report_issue_details detail\n INNER JOIN delphi_report_issues issue ON issue.id = detail.issue_id\n INNER JOIN delphi_reports report ON report.id = issue.report_id\n LEFT JOIN files file ON file.id = report.file_id\n LEFT JOIN versions version ON version.id = file.version_id\n LEFT JOIN (\n SELECT\n loader_version.version_id,\n ARRAY_AGG(\n DISTINCT project_type.name::text\n ORDER BY project_type.name::text\n ) FILTER (WHERE project_type.name IS NOT NULL)\n AS project_types,\n ARRAY_AGG(\n DISTINCT loader.loader\n ORDER BY loader.loader\n ) AS loaders\n FROM loaders_versions loader_version\n INNER JOIN loaders loader\n ON loader.id = loader_version.loader_id\n LEFT JOIN loaders_project_types loader_project_type\n ON loader_project_type.joining_loader_id = loader_version.loader_id\n LEFT JOIN project_types project_type\n ON project_type.id = loader_project_type.joining_project_type_id\n GROUP BY loader_version.version_id\n ) version_metadata\n ON version_metadata.version_id = version.id\n LEFT JOIN (\n SELECT\n file_id,\n jsonb_object_agg(algorithm, encode(hash, 'hex')) AS hashes\n FROM hashes\n GROUP BY file_id\n ) file_hashes ON file_hashes.file_id = file.id\n ORDER BY detail.id\n ",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
@@ -79,6 +79,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ordinal": 12,
|
"ordinal": 12,
|
||||||
|
"name": "project_types!: Vec<String>",
|
||||||
|
"type_info": "TextArray"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 13,
|
||||||
|
"name": "loaders!: Vec<String>",
|
||||||
|
"type_info": "VarcharArray"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 14,
|
||||||
"name": "hashes!: Json<BTreeMap<String, String>>",
|
"name": "hashes!: Json<BTreeMap<String, String>>",
|
||||||
"type_info": "Jsonb"
|
"type_info": "Jsonb"
|
||||||
}
|
}
|
||||||
@@ -99,8 +109,10 @@
|
|||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
null
|
null
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "2fb03e1de120cf921146b6dbd35691117412407c4225f966c013fdfef21a82fb"
|
"hash": "8537362d9c11e437ada9ce97bb604666378d1d24981ab8113badded16fb63413"
|
||||||
}
|
}
|
||||||
Generated
+15
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "UPDATE moderation_locks SET locked_at = NOW() - ($1::bigint * INTERVAL '1 minute') - INTERVAL '1 second' WHERE project_id = $2",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Int8",
|
||||||
|
"Int8"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "8ca7f46618bd72cfd5fe1f246d41a31da1885cc22bb61be6853950ba52be9aa7"
|
||||||
|
}
|
||||||
+14
-2
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"db_name": "PostgreSQL",
|
"db_name": "PostgreSQL",
|
||||||
"query": "\n SELECT\n detail.key,\n issue.issue_type,\n detail.severity AS \"severity: DelphiSeverity\",\n detail.jar,\n detail.file_path,\n detail.data AS \"data: Json<HashMap<String, serde_json::Value>>\",\n report.delphi_version,\n file.size AS \"size?\",\n file.id AS \"file_id?\",\n version.id AS \"version_id?\",\n version.mod_id AS \"project_id?\",\n COALESCE(file_hashes.hashes, '{}'::jsonb)\n AS \"hashes!: Json<BTreeMap<String, String>>\"\n FROM delphi_report_issue_details detail\n INNER JOIN delphi_report_issues issue ON issue.id = detail.issue_id\n INNER JOIN delphi_reports report ON report.id = issue.report_id\n LEFT JOIN files file ON file.id = report.file_id\n LEFT JOIN versions version ON version.id = file.version_id\n LEFT JOIN LATERAL (\n SELECT\n jsonb_object_agg(algorithm, encode(hash, 'hex')) AS hashes\n FROM hashes\n WHERE hashes.file_id = file.id\n ) file_hashes ON TRUE\n WHERE detail.id = $1\n ",
|
"query": "\n SELECT\n detail.key,\n issue.issue_type,\n detail.severity AS \"severity: DelphiSeverity\",\n detail.jar,\n detail.file_path,\n detail.data AS \"data: Json<HashMap<String, serde_json::Value>>\",\n report.delphi_version,\n file.size AS \"size?\",\n file.id AS \"file_id?\",\n version.id AS \"version_id?\",\n version.mod_id AS \"project_id?\",\n COALESCE(version_metadata.project_types, ARRAY[]::text[])\n AS \"project_types!: Vec<String>\",\n COALESCE(version_metadata.loaders, ARRAY[]::text[])\n AS \"loaders!: Vec<String>\",\n COALESCE(file_hashes.hashes, '{}'::jsonb)\n AS \"hashes!: Json<BTreeMap<String, String>>\"\n FROM delphi_report_issue_details detail\n INNER JOIN delphi_report_issues issue ON issue.id = detail.issue_id\n INNER JOIN delphi_reports report ON report.id = issue.report_id\n LEFT JOIN files file ON file.id = report.file_id\n LEFT JOIN versions version ON version.id = file.version_id\n LEFT JOIN LATERAL (\n SELECT\n ARRAY_AGG(\n DISTINCT project_type.name::text\n ORDER BY project_type.name::text\n ) FILTER (WHERE project_type.name IS NOT NULL)\n AS project_types,\n ARRAY_AGG(\n DISTINCT loader.loader\n ORDER BY loader.loader\n ) AS loaders\n FROM loaders_versions loader_version\n INNER JOIN loaders loader\n ON loader.id = loader_version.loader_id\n LEFT JOIN loaders_project_types loader_project_type\n ON loader_project_type.joining_loader_id = loader_version.loader_id\n LEFT JOIN project_types project_type\n ON project_type.id = loader_project_type.joining_project_type_id\n WHERE loader_version.version_id = version.id\n ) version_metadata ON TRUE\n LEFT JOIN LATERAL (\n SELECT\n jsonb_object_agg(algorithm, encode(hash, 'hex')) AS hashes\n FROM hashes\n WHERE hashes.file_id = file.id\n ) file_hashes ON TRUE\n WHERE detail.id = $1\n ",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
@@ -74,6 +74,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ordinal": 11,
|
"ordinal": 11,
|
||||||
|
"name": "project_types!: Vec<String>",
|
||||||
|
"type_info": "TextArray"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 12,
|
||||||
|
"name": "loaders!: Vec<String>",
|
||||||
|
"type_info": "VarcharArray"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 13,
|
||||||
"name": "hashes!: Json<BTreeMap<String, String>>",
|
"name": "hashes!: Json<BTreeMap<String, String>>",
|
||||||
"type_info": "Jsonb"
|
"type_info": "Jsonb"
|
||||||
}
|
}
|
||||||
@@ -95,8 +105,10 @@
|
|||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
null
|
null
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "1f318b0dda5aaf58b9e403f87fcacfcd223bd67f6d5ba95868ace4373a98c67d"
|
"hash": "b536d7870ba77ace55e309938c3ba93c7016b2a6f18aa4f22ea57f604d82c1da"
|
||||||
}
|
}
|
||||||
Generated
+20
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "SELECT status AS \"status: PayoutStatus\" FROM payouts WHERE id = 1",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"ordinal": 0,
|
||||||
|
"name": "status: PayoutStatus",
|
||||||
|
"type_info": "Varchar"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Left": []
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
false
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hash": "b92b5bb7d179c4fcdbc45600ccfd2402f52fea71e27b08e7926fcc2a9e62c0f3"
|
||||||
|
}
|
||||||
Generated
+12
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "DROP SCHEMA public CASCADE;",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Left": []
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "c65295f61b18f7be4fdf30b3e2c2c36b9db3770508be4acbb6b441ed8006d9c7"
|
||||||
|
}
|
||||||
Generated
+14
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "UPDATE users SET email_verified = TRUE WHERE id = $1",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Int8"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "cb626a36deffd73e67de2dc4789bd875675779a173fb2cd41ba365c1acca96f9"
|
||||||
|
}
|
||||||
Generated
+18
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "\n INSERT INTO payouts (id, method, platform_id, status, user_id, amount, created)\n VALUES ($1, $2, $3, $4, $5, 10.0, NOW())\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Int8",
|
||||||
|
"Text",
|
||||||
|
"Text",
|
||||||
|
"Varchar",
|
||||||
|
"Int8"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "cd5ccd618fb3cc41646a6de86f9afedb074492b4ec7f2457c14113f5fd13aa02"
|
||||||
|
}
|
||||||
Generated
+17
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "\n INSERT INTO payouts (id, method, platform_id, status, user_id, amount, created)\n VALUES ($1, $2, NULL, $3, $4, 10.00, NOW())\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Int8",
|
||||||
|
"Text",
|
||||||
|
"Varchar",
|
||||||
|
"Int8"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "cec4240c7c848988b3dfd13e3f8e5c93783c7641b019fdb698a1ec0be1393606"
|
||||||
|
}
|
||||||
Generated
+20
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "SELECT pg_advisory_unlock(1)",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"ordinal": 0,
|
||||||
|
"name": "pg_advisory_unlock",
|
||||||
|
"type_info": "Bool"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Left": []
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
null
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hash": "fabba52b34e14196b6305671c060d934b9afc6dccc5fc77454fbe45365bdf429"
|
||||||
|
}
|
||||||
Generated
+22
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "SELECT COUNT(*) FROM notifications WHERE user_id = $1 AND body->>'type' = 'payout_available'",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"ordinal": 0,
|
||||||
|
"name": "count",
|
||||||
|
"type_info": "Int8"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Int8"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
null
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hash": "fd5c773a61d35bcd71503ec4d5f86e8917cfab9679d5064074681663ba467e41"
|
||||||
|
}
|
||||||
@@ -158,8 +158,9 @@ pub async fn test_rule(
|
|||||||
})?;
|
})?;
|
||||||
|
|
||||||
let rule = request.rule;
|
let rule = request.rule;
|
||||||
|
let expression = rule.clone();
|
||||||
let program =
|
let program =
|
||||||
tokio::task::spawn_blocking(move || cel::Program::compile(&rule))
|
tokio::task::spawn_blocking(move || cel::Program::compile(&expression))
|
||||||
.await
|
.await
|
||||||
.wrap_internal_err("failed to join cel compilation task")?
|
.wrap_internal_err("failed to join cel compilation task")?
|
||||||
.map_err(|error| {
|
.map_err(|error| {
|
||||||
@@ -168,12 +169,11 @@ pub async fn test_rule(
|
|||||||
let mut effects = Vec::with_capacity(request.inputs.len());
|
let mut effects = Vec::with_capacity(request.inputs.len());
|
||||||
|
|
||||||
for (index, input) in request.inputs.iter().enumerate() {
|
for (index, input) in request.inputs.iter().enumerate() {
|
||||||
let effect = super::rules_scan::evaluate_rule(&program, input)
|
let evaluation =
|
||||||
.map_err(|error| {
|
super::rules_scan::evaluate_rule(&program, &rule, input);
|
||||||
ApiError::Request(eyre!(
|
let effect = evaluation.wrap_request_err(format!(
|
||||||
"failed to evaluate test input {index}: {error}"
|
"failed to evaluate test input {index}"
|
||||||
))
|
))?;
|
||||||
})?;
|
|
||||||
effects.push(effect);
|
effects.push(effect);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -58,7 +58,9 @@ pub struct RuleInput {
|
|||||||
pub trace: RuleTrace,
|
pub trace: RuleTrace,
|
||||||
pub scan: RuleScan,
|
pub scan: RuleScan,
|
||||||
pub artifact: RuleArtifact,
|
pub artifact: RuleArtifact,
|
||||||
pub scope: RuleScope,
|
pub project: RuleProject,
|
||||||
|
pub version: RuleVersion,
|
||||||
|
pub file: RuleFile,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, utoipa::ToSchema)]
|
#[derive(Deserialize, Serialize, utoipa::ToSchema)]
|
||||||
@@ -83,14 +85,26 @@ pub struct RuleArtifact {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, utoipa::ToSchema)]
|
#[derive(Deserialize, Serialize, utoipa::ToSchema)]
|
||||||
pub struct RuleScope {
|
pub struct RuleProject {
|
||||||
pub project_id: Option<String>,
|
pub id: Option<String>,
|
||||||
pub version_id: Option<String>,
|
pub types: Vec<String>,
|
||||||
pub file_id: Option<String>,
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, utoipa::ToSchema)]
|
||||||
|
pub struct RuleVersion {
|
||||||
|
pub id: Option<String>,
|
||||||
|
pub loaders: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, utoipa::ToSchema)]
|
||||||
|
pub struct RuleFile {
|
||||||
|
pub id: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct CompiledRule {
|
struct CompiledRule {
|
||||||
id: DelphiRuleId,
|
id: DelphiRuleId,
|
||||||
|
name: String,
|
||||||
|
expression: String,
|
||||||
program: cel::Program,
|
program: cel::Program,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,6 +211,10 @@ pub async fn get_detail_rule_input(
|
|||||||
file.id AS "file_id?",
|
file.id AS "file_id?",
|
||||||
version.id AS "version_id?",
|
version.id AS "version_id?",
|
||||||
version.mod_id AS "project_id?",
|
version.mod_id AS "project_id?",
|
||||||
|
COALESCE(version_metadata.project_types, ARRAY[]::text[])
|
||||||
|
AS "project_types!: Vec<String>",
|
||||||
|
COALESCE(version_metadata.loaders, ARRAY[]::text[])
|
||||||
|
AS "loaders!: Vec<String>",
|
||||||
COALESCE(file_hashes.hashes, '{}'::jsonb)
|
COALESCE(file_hashes.hashes, '{}'::jsonb)
|
||||||
AS "hashes!: Json<BTreeMap<String, String>>"
|
AS "hashes!: Json<BTreeMap<String, String>>"
|
||||||
FROM delphi_report_issue_details detail
|
FROM delphi_report_issue_details detail
|
||||||
@@ -204,6 +222,26 @@ pub async fn get_detail_rule_input(
|
|||||||
INNER JOIN delphi_reports report ON report.id = issue.report_id
|
INNER JOIN delphi_reports report ON report.id = issue.report_id
|
||||||
LEFT JOIN files file ON file.id = report.file_id
|
LEFT JOIN files file ON file.id = report.file_id
|
||||||
LEFT JOIN versions version ON version.id = file.version_id
|
LEFT JOIN versions version ON version.id = file.version_id
|
||||||
|
LEFT JOIN LATERAL (
|
||||||
|
SELECT
|
||||||
|
ARRAY_AGG(
|
||||||
|
DISTINCT project_type.name::text
|
||||||
|
ORDER BY project_type.name::text
|
||||||
|
) FILTER (WHERE project_type.name IS NOT NULL)
|
||||||
|
AS project_types,
|
||||||
|
ARRAY_AGG(
|
||||||
|
DISTINCT loader.loader
|
||||||
|
ORDER BY loader.loader
|
||||||
|
) AS loaders
|
||||||
|
FROM loaders_versions loader_version
|
||||||
|
INNER JOIN loaders loader
|
||||||
|
ON loader.id = loader_version.loader_id
|
||||||
|
LEFT JOIN loaders_project_types loader_project_type
|
||||||
|
ON loader_project_type.joining_loader_id = loader_version.loader_id
|
||||||
|
LEFT JOIN project_types project_type
|
||||||
|
ON project_type.id = loader_project_type.joining_project_type_id
|
||||||
|
WHERE loader_version.version_id = version.id
|
||||||
|
) version_metadata ON TRUE
|
||||||
LEFT JOIN LATERAL (
|
LEFT JOIN LATERAL (
|
||||||
SELECT
|
SELECT
|
||||||
jsonb_object_agg(algorithm, encode(hash, 'hex')) AS hashes
|
jsonb_object_agg(algorithm, encode(hash, 'hex')) AS hashes
|
||||||
@@ -237,10 +275,16 @@ pub async fn get_detail_rule_input(
|
|||||||
size: detail.size,
|
size: detail.size,
|
||||||
hashes: detail.hashes.0,
|
hashes: detail.hashes.0,
|
||||||
},
|
},
|
||||||
scope: RuleScope {
|
project: RuleProject {
|
||||||
project_id: detail.project_id.map(|id| to_base62(id as u64)),
|
id: detail.project_id.map(|id| to_base62(id as u64)),
|
||||||
version_id: detail.version_id.map(|id| to_base62(id as u64)),
|
types: detail.project_types,
|
||||||
file_id: detail.file_id.map(|id| to_base62(id as u64)),
|
},
|
||||||
|
version: RuleVersion {
|
||||||
|
id: detail.version_id.map(|id| to_base62(id as u64)),
|
||||||
|
loaders: detail.loaders,
|
||||||
|
},
|
||||||
|
file: RuleFile {
|
||||||
|
id: detail.file_id.map(|id| to_base62(id as u64)),
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
@@ -331,7 +375,7 @@ pub async fn scan_rules(
|
|||||||
}
|
}
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
tracing::error!(error = ?error, "delphi rule scan failed");
|
tracing::error!(error = ?error, "delphi rule scan failed");
|
||||||
let message = error.to_string();
|
let message = format!("{error:#}");
|
||||||
let event = RuleScanErrorEvent { message: &message };
|
let event = RuleScanErrorEvent { message: &message };
|
||||||
if let Ok(data) = serde_json::to_string(&event) {
|
if let Ok(data) = serde_json::to_string(&event) {
|
||||||
let _ = sender.send(Bytes::from(format!(
|
let _ = sender.send(Bytes::from(format!(
|
||||||
@@ -402,6 +446,10 @@ async fn run_scan(
|
|||||||
file.id AS "file_id?",
|
file.id AS "file_id?",
|
||||||
version.id AS "version_id?",
|
version.id AS "version_id?",
|
||||||
version.mod_id AS "project_id?",
|
version.mod_id AS "project_id?",
|
||||||
|
COALESCE(version_metadata.project_types, ARRAY[]::text[])
|
||||||
|
AS "project_types!: Vec<String>",
|
||||||
|
COALESCE(version_metadata.loaders, ARRAY[]::text[])
|
||||||
|
AS "loaders!: Vec<String>",
|
||||||
COALESCE(file_hashes.hashes, '{}'::jsonb)
|
COALESCE(file_hashes.hashes, '{}'::jsonb)
|
||||||
AS "hashes!: Json<BTreeMap<String, String>>"
|
AS "hashes!: Json<BTreeMap<String, String>>"
|
||||||
FROM delphi_report_issue_details detail
|
FROM delphi_report_issue_details detail
|
||||||
@@ -409,6 +457,28 @@ async fn run_scan(
|
|||||||
INNER JOIN delphi_reports report ON report.id = issue.report_id
|
INNER JOIN delphi_reports report ON report.id = issue.report_id
|
||||||
LEFT JOIN files file ON file.id = report.file_id
|
LEFT JOIN files file ON file.id = report.file_id
|
||||||
LEFT JOIN versions version ON version.id = file.version_id
|
LEFT JOIN versions version ON version.id = file.version_id
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT
|
||||||
|
loader_version.version_id,
|
||||||
|
ARRAY_AGG(
|
||||||
|
DISTINCT project_type.name::text
|
||||||
|
ORDER BY project_type.name::text
|
||||||
|
) FILTER (WHERE project_type.name IS NOT NULL)
|
||||||
|
AS project_types,
|
||||||
|
ARRAY_AGG(
|
||||||
|
DISTINCT loader.loader
|
||||||
|
ORDER BY loader.loader
|
||||||
|
) AS loaders
|
||||||
|
FROM loaders_versions loader_version
|
||||||
|
INNER JOIN loaders loader
|
||||||
|
ON loader.id = loader_version.loader_id
|
||||||
|
LEFT JOIN loaders_project_types loader_project_type
|
||||||
|
ON loader_project_type.joining_loader_id = loader_version.loader_id
|
||||||
|
LEFT JOIN project_types project_type
|
||||||
|
ON project_type.id = loader_project_type.joining_project_type_id
|
||||||
|
GROUP BY loader_version.version_id
|
||||||
|
) version_metadata
|
||||||
|
ON version_metadata.version_id = version.id
|
||||||
LEFT JOIN (
|
LEFT JOIN (
|
||||||
SELECT
|
SELECT
|
||||||
file_id,
|
file_id,
|
||||||
@@ -458,19 +528,29 @@ async fn run_scan(
|
|||||||
size: detail.size,
|
size: detail.size,
|
||||||
hashes: detail.hashes.0,
|
hashes: detail.hashes.0,
|
||||||
},
|
},
|
||||||
scope: RuleScope {
|
project: RuleProject {
|
||||||
project_id: detail.project_id.map(|id| to_base62(id as u64)),
|
id: detail.project_id.map(|id| to_base62(id as u64)),
|
||||||
version_id: detail.version_id.map(|id| to_base62(id as u64)),
|
types: detail.project_types,
|
||||||
file_id: detail.file_id.map(|id| to_base62(id as u64)),
|
},
|
||||||
|
version: RuleVersion {
|
||||||
|
id: detail.version_id.map(|id| to_base62(id as u64)),
|
||||||
|
loaders: detail.loaders,
|
||||||
|
},
|
||||||
|
file: RuleFile {
|
||||||
|
id: detail.file_id.map(|id| to_base62(id as u64)),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
for rule in &rules {
|
for rule in &rules {
|
||||||
let effect = evaluate_rule(&rule.program, &input)
|
let effect = evaluate_rule(
|
||||||
|
&rule.program,
|
||||||
|
&rule.expression,
|
||||||
|
&input,
|
||||||
|
)
|
||||||
.wrap_err_with(|| {
|
.wrap_err_with(|| {
|
||||||
format!(
|
format!(
|
||||||
"failed to evaluate delphi rule {} for detail {detail_id}",
|
"failed to evaluate delphi rule '{}' for detail {detail_id}",
|
||||||
rule.id.0
|
rule.name
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
if let Some(effect) = effect {
|
if let Some(effect) = effect {
|
||||||
@@ -642,6 +722,10 @@ pub(crate) async fn materialize_current_rule_effects(
|
|||||||
file.id AS "file_id?",
|
file.id AS "file_id?",
|
||||||
version.id AS "version_id?",
|
version.id AS "version_id?",
|
||||||
version.mod_id AS "project_id?",
|
version.mod_id AS "project_id?",
|
||||||
|
COALESCE(version_metadata.project_types, ARRAY[]::text[])
|
||||||
|
AS "project_types!: Vec<String>",
|
||||||
|
COALESCE(version_metadata.loaders, ARRAY[]::text[])
|
||||||
|
AS "loaders!: Vec<String>",
|
||||||
COALESCE(file_hashes.hashes, '{}'::jsonb)
|
COALESCE(file_hashes.hashes, '{}'::jsonb)
|
||||||
AS "hashes!: Json<BTreeMap<String, String>>"
|
AS "hashes!: Json<BTreeMap<String, String>>"
|
||||||
FROM delphi_report_issue_details detail
|
FROM delphi_report_issue_details detail
|
||||||
@@ -649,6 +733,26 @@ pub(crate) async fn materialize_current_rule_effects(
|
|||||||
INNER JOIN delphi_reports report ON report.id = issue.report_id
|
INNER JOIN delphi_reports report ON report.id = issue.report_id
|
||||||
LEFT JOIN files file ON file.id = report.file_id
|
LEFT JOIN files file ON file.id = report.file_id
|
||||||
LEFT JOIN versions version ON version.id = file.version_id
|
LEFT JOIN versions version ON version.id = file.version_id
|
||||||
|
LEFT JOIN LATERAL (
|
||||||
|
SELECT
|
||||||
|
ARRAY_AGG(
|
||||||
|
DISTINCT project_type.name::text
|
||||||
|
ORDER BY project_type.name::text
|
||||||
|
) FILTER (WHERE project_type.name IS NOT NULL)
|
||||||
|
AS project_types,
|
||||||
|
ARRAY_AGG(
|
||||||
|
DISTINCT loader.loader
|
||||||
|
ORDER BY loader.loader
|
||||||
|
) AS loaders
|
||||||
|
FROM loaders_versions loader_version
|
||||||
|
INNER JOIN loaders loader
|
||||||
|
ON loader.id = loader_version.loader_id
|
||||||
|
LEFT JOIN loaders_project_types loader_project_type
|
||||||
|
ON loader_project_type.joining_loader_id = loader_version.loader_id
|
||||||
|
LEFT JOIN project_types project_type
|
||||||
|
ON project_type.id = loader_project_type.joining_project_type_id
|
||||||
|
WHERE loader_version.version_id = version.id
|
||||||
|
) version_metadata ON TRUE
|
||||||
LEFT JOIN LATERAL (
|
LEFT JOIN LATERAL (
|
||||||
SELECT
|
SELECT
|
||||||
jsonb_object_agg(algorithm, encode(hash, 'hex')) AS hashes
|
jsonb_object_agg(algorithm, encode(hash, 'hex')) AS hashes
|
||||||
@@ -683,19 +787,25 @@ pub(crate) async fn materialize_current_rule_effects(
|
|||||||
size: detail.size,
|
size: detail.size,
|
||||||
hashes: detail.hashes.0,
|
hashes: detail.hashes.0,
|
||||||
},
|
},
|
||||||
scope: RuleScope {
|
project: RuleProject {
|
||||||
project_id: detail.project_id.map(|id| to_base62(id as u64)),
|
id: detail.project_id.map(|id| to_base62(id as u64)),
|
||||||
version_id: detail.version_id.map(|id| to_base62(id as u64)),
|
types: detail.project_types,
|
||||||
file_id: detail.file_id.map(|id| to_base62(id as u64)),
|
},
|
||||||
|
version: RuleVersion {
|
||||||
|
id: detail.version_id.map(|id| to_base62(id as u64)),
|
||||||
|
loaders: detail.loaders,
|
||||||
|
},
|
||||||
|
file: RuleFile {
|
||||||
|
id: detail.file_id.map(|id| to_base62(id as u64)),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
for rule in &rules {
|
for rule in &rules {
|
||||||
let effect =
|
let effect = evaluate_rule(&rule.program, &rule.expression, &input)
|
||||||
evaluate_rule(&rule.program, &input).wrap_err_with(|| {
|
.wrap_err_with(|| {
|
||||||
format!(
|
format!(
|
||||||
"failed to evaluate delphi rule {} for detail {}",
|
"failed to evaluate delphi rule '{}' for detail {}",
|
||||||
rule.id.0, detail.id
|
rule.name, detail.id
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
if let Some(effect) = effect {
|
if let Some(effect) = effect {
|
||||||
@@ -717,7 +827,7 @@ async fn fetch_compiled_rules(
|
|||||||
) -> Result<Vec<CompiledRule>> {
|
) -> Result<Vec<CompiledRule>> {
|
||||||
let rules = sqlx::query!(
|
let rules = sqlx::query!(
|
||||||
r#"
|
r#"
|
||||||
SELECT id AS "id!: DelphiRuleId", rule
|
SELECT id AS "id!: DelphiRuleId", name, rule
|
||||||
FROM delphi_rules
|
FROM delphi_rules
|
||||||
WHERE NOT delete_on_next_revision
|
WHERE NOT delete_on_next_revision
|
||||||
ORDER BY priority DESC, id
|
ORDER BY priority DESC, id
|
||||||
@@ -734,12 +844,14 @@ async fn fetch_compiled_rules(
|
|||||||
let program =
|
let program =
|
||||||
cel::Program::compile(&rule.rule).map_err(|error| {
|
cel::Program::compile(&rule.rule).map_err(|error| {
|
||||||
eyre!(
|
eyre!(
|
||||||
"failed to compile delphi rule {}: {error}",
|
"failed to compile delphi rule '{}': {error}",
|
||||||
rule.id.0
|
rule.name
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
Ok(CompiledRule {
|
Ok(CompiledRule {
|
||||||
id: rule.id,
|
id: rule.id,
|
||||||
|
name: rule.name,
|
||||||
|
expression: rule.rule,
|
||||||
program,
|
program,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -799,6 +911,21 @@ async fn insert_materialized_effects(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn evaluate_rule(
|
pub(super) fn evaluate_rule(
|
||||||
|
program: &cel::Program,
|
||||||
|
expression: &str,
|
||||||
|
input: &RuleInput,
|
||||||
|
) -> Result<Option<DelphiRuleEffect>> {
|
||||||
|
evaluate_rule_inner(program, input)
|
||||||
|
.wrap_err_with(|| {
|
||||||
|
let input = serde_json::to_string(input).unwrap_or_else(|error| {
|
||||||
|
format!("<failed to serialize CEL input: {error}>")
|
||||||
|
});
|
||||||
|
format!("CEL input: {input}")
|
||||||
|
})
|
||||||
|
.wrap_err_with(|| format!("CEL expression: {expression}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn evaluate_rule_inner(
|
||||||
program: &cel::Program,
|
program: &cel::Program,
|
||||||
input: &RuleInput,
|
input: &RuleInput,
|
||||||
) -> Result<Option<DelphiRuleEffect>> {
|
) -> Result<Option<DelphiRuleEffect>> {
|
||||||
@@ -816,8 +943,14 @@ pub(super) fn evaluate_rule(
|
|||||||
.add_variable("artifact", &input.artifact)
|
.add_variable("artifact", &input.artifact)
|
||||||
.wrap_err("failed to add `artifact` to cel context")?;
|
.wrap_err("failed to add `artifact` to cel context")?;
|
||||||
context
|
context
|
||||||
.add_variable("scope", &input.scope)
|
.add_variable("project", &input.project)
|
||||||
.wrap_err("failed to add `scope` to cel context")?;
|
.wrap_err("failed to add `project` to cel context")?;
|
||||||
|
context
|
||||||
|
.add_variable("version", &input.version)
|
||||||
|
.wrap_err("failed to add `version` to cel context")?;
|
||||||
|
context
|
||||||
|
.add_variable("file", &input.file)
|
||||||
|
.wrap_err("failed to add `file` to cel context")?;
|
||||||
|
|
||||||
let value = program
|
let value = program
|
||||||
.execute(&context)
|
.execute(&context)
|
||||||
|
|||||||
@@ -2335,10 +2335,16 @@ export namespace Labrinth {
|
|||||||
size: number | null
|
size: number | null
|
||||||
hashes: Record<string, string>
|
hashes: Record<string, string>
|
||||||
}
|
}
|
||||||
scope: {
|
project: {
|
||||||
project_id: string | null
|
id: string | null
|
||||||
version_id: string | null
|
types: string[]
|
||||||
file_id: string | null
|
}
|
||||||
|
version: {
|
||||||
|
id: string | null
|
||||||
|
loaders: string[]
|
||||||
|
}
|
||||||
|
file: {
|
||||||
|
id: string | null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "delphi-cel-experiment"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition.workspace = true
|
|
||||||
publish = false
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
cel = { workspace = true }
|
|
||||||
serde = { workspace = true, features = ["derive"] }
|
|
||||||
serde_json = { workspace = true }
|
|
||||||
@@ -1,174 +0,0 @@
|
|||||||
use std::{collections::BTreeMap, error::Error, io};
|
|
||||||
|
|
||||||
use cel::{Context, Program};
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
const EFFECT_RULE: &str = r#"
|
|
||||||
trace.issue_type == "OBFUSCATED_NAMES"
|
|
||||||
&& trace.severity == "high"
|
|
||||||
&& "confidence" in trace.data
|
|
||||||
&& trace.data.confidence >= 0.9
|
|
||||||
? {
|
|
||||||
"severity": "low"
|
|
||||||
}
|
|
||||||
: null
|
|
||||||
"#;
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
|
||||||
struct RuleInput {
|
|
||||||
schema_version: u32,
|
|
||||||
trace: TraceInput,
|
|
||||||
scan: ScanInput,
|
|
||||||
artifact: ArtifactInput,
|
|
||||||
scope: ScopeInput,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
|
||||||
struct TraceInput {
|
|
||||||
key: String,
|
|
||||||
issue_type: String,
|
|
||||||
severity: DelphiSeverity,
|
|
||||||
jar: Option<String>,
|
|
||||||
file_path: String,
|
|
||||||
data: BTreeMap<String, serde_json::Value>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
|
||||||
struct ScanInput {
|
|
||||||
delphi_version: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
|
||||||
struct ArtifactInput {
|
|
||||||
size: u32,
|
|
||||||
hashes: BTreeMap<String, String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
|
||||||
struct ScopeInput {
|
|
||||||
project_id: String,
|
|
||||||
version_id: String,
|
|
||||||
file_id: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
|
|
||||||
#[serde(rename_all = "snake_case")]
|
|
||||||
enum DelphiSeverity {
|
|
||||||
Hidden,
|
|
||||||
Low,
|
|
||||||
Medium,
|
|
||||||
High,
|
|
||||||
Severe,
|
|
||||||
Malware,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Deserialize)]
|
|
||||||
#[serde(deny_unknown_fields)]
|
|
||||||
struct RuleEffect {
|
|
||||||
severity: DelphiSeverity,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn Error>> {
|
|
||||||
let input = example_input();
|
|
||||||
println!(
|
|
||||||
"CEL input context:\n{}",
|
|
||||||
serde_json::to_string_pretty(&input)?
|
|
||||||
);
|
|
||||||
|
|
||||||
let effect = evaluate_rule(EFFECT_RULE, &input)?;
|
|
||||||
println!("\nCEL map decoded as RuleEffect:\n{effect:#?}");
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn example_input() -> RuleInput {
|
|
||||||
RuleInput {
|
|
||||||
schema_version: 1,
|
|
||||||
trace: TraceInput {
|
|
||||||
key: "known-safe:obfuscated-bootstrap".to_string(),
|
|
||||||
issue_type: "OBFUSCATED_NAMES".to_string(),
|
|
||||||
severity: DelphiSeverity::High,
|
|
||||||
jar: Some("META-INF/jars/embedded.jar".to_string()),
|
|
||||||
file_path: "com/example/Bootstrap.class".to_string(),
|
|
||||||
data: BTreeMap::from([
|
|
||||||
("confidence".to_string(), serde_json::json!(0.97)),
|
|
||||||
("symbol_count".to_string(), serde_json::json!(42)),
|
|
||||||
]),
|
|
||||||
},
|
|
||||||
scan: ScanInput { delphi_version: 17 },
|
|
||||||
artifact: ArtifactInput {
|
|
||||||
size: 412_892,
|
|
||||||
hashes: BTreeMap::from([
|
|
||||||
("sha1".to_string(), "0123456789abcdef".to_string()),
|
|
||||||
("sha512".to_string(), "fedcba9876543210".to_string()),
|
|
||||||
]),
|
|
||||||
},
|
|
||||||
scope: ScopeInput {
|
|
||||||
project_id: "AANobbMI".to_string(),
|
|
||||||
version_id: "IIJJKKLL".to_string(),
|
|
||||||
file_id: "XXYYZZ00".to_string(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn evaluate_rule(
|
|
||||||
expression: &str,
|
|
||||||
input: &RuleInput,
|
|
||||||
) -> Result<Option<RuleEffect>, Box<dyn Error>> {
|
|
||||||
let mut context = Context::default();
|
|
||||||
context.add_variable("schema_version", input.schema_version)?;
|
|
||||||
context.add_variable("trace", &input.trace)?;
|
|
||||||
context.add_variable("scan", &input.scan)?;
|
|
||||||
context.add_variable("artifact", &input.artifact)?;
|
|
||||||
context.add_variable("scope", &input.scope)?;
|
|
||||||
|
|
||||||
let value = Program::compile(expression)?.execute(&context)?;
|
|
||||||
let json = value
|
|
||||||
.json()
|
|
||||||
.map_err(|error| invalid_data(error.to_string()))?;
|
|
||||||
match json {
|
|
||||||
serde_json::Value::Null => Ok(None),
|
|
||||||
value => Ok(Some(serde_json::from_value(value)?)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn invalid_data(message: impl Into<String>) -> io::Error {
|
|
||||||
io::Error::new(io::ErrorKind::InvalidData, message.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn decodes_a_map_as_an_effect() {
|
|
||||||
let effect = evaluate_rule(EFFECT_RULE, &example_input())
|
|
||||||
.expect("rule should evaluate");
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
effect,
|
|
||||||
Some(RuleEffect {
|
|
||||||
severity: DelphiSeverity::Low,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn returns_none_when_a_rule_does_not_match() {
|
|
||||||
let mut input = example_input();
|
|
||||||
input.trace.issue_type = "NETWORK_ACCESS".to_string();
|
|
||||||
|
|
||||||
let effect =
|
|
||||||
evaluate_rule(EFFECT_RULE, &input).expect("rule should evaluate");
|
|
||||||
|
|
||||||
assert_eq!(effect, None);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn rejects_unknown_map_fields() {
|
|
||||||
let error = evaluate_rule(r#"{"unknown": true}"#, &example_input())
|
|
||||||
.expect_err("unknown fields should be rejected");
|
|
||||||
|
|
||||||
assert!(error.to_string().contains("unknown field"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user