Skip to content

Commit

Permalink
DEV: Modernize the Antivirus plugin. (#59)
Browse files Browse the repository at this point in the history
* DEV: Modernize the Antivirus plugin.

Autoload files, and annotate models.

* Use new plugin show route

* DEV: Fix template

If there is only a top level route for the admin show
for the plugin, it will not be an `index` route, it will
just be the route name itself as the top level.

* Fix build and add styles to template

---------

Co-authored-by: Martin Brennan <[email protected]>
  • Loading branch information
romanrizzi and martin-brennan committed Jul 2, 2024
1 parent 6bab8a2 commit 815aed3
Show file tree
Hide file tree
Showing 35 changed files with 237 additions and 164 deletions.
2 changes: 1 addition & 1 deletion .rubocop.yml
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
inherit_gem:
rubocop-discourse: stree-compat.yml
rubocop-discourse: stree-compat.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { ajax } from "discourse/lib/ajax";
import DiscourseRoute from "discourse/routes/discourse";

export default class DiscourseAntivirusStatsRoute extends DiscourseRoute {
model() {
return ajax("/admin/plugins/discourse-antivirus/stats");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<section class="antivirus-stats admin-detail pull-left">
<div class="antivirus-stats__header">
<h3>{{i18n "antivirus.stats.title"}}</h3>
</div>

<table class="antivirus-stats__table">
<thead>
<tr>
<th>{{i18n "antivirus.version"}}</th>
<th>{{i18n "antivirus.database_version"}}</th>
<th>{{i18n "antivirus.database_updated_at"}}</th>
</tr>
</thead>
<tbody>
{{#each this.model.versions as |version|}}
<tr>
<td>{{version.antivirus}}</td>
<td>{{version.database}}</td>
<td>{{version.updated_at}}</td>
</tr>
{{/each}}
</tbody>
</table>

<div class="antivirus-stats__sub-header">
<h4>{{i18n "antivirus.stats.data"}}</h4>
</div>

<table class="antivirus-stats__table">
<thead>
<tr>
<th>{{i18n "antivirus.stats.total_scans"}}</th>
<th>{{i18n "antivirus.stats.recently_scanned"}}</th>
<th>{{i18n "antivirus.stats.quarantined"}}</th>
<th>{{i18n "antivirus.stats.found"}}</th>
</tr>
</thead>
<tbody>
<tr>
<td>{{this.model.stats.scans}}</td>
<td>{{this.model.stats.recently_scanned}}</td>
<td>{{this.model.stats.quarantined}}</td>
<td>{{this.model.stats.found}}</td>
</tr>
</tbody>
</table>

</section>
15 changes: 15 additions & 0 deletions app/controllers/discourse_antivirus/admin/antivirus_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# frozen_string_literal: true

module DiscourseAntivirus
module Admin
class AntivirusController < ::Admin::AdminController
requires_plugin ::DiscourseAntivirus::PLUGIN_NAME

def index
antivirus = DiscourseAntivirus::ClamAv.instance

render json: DiscourseAntivirus::BackgroundScan.new(antivirus).stats
end
end
end
end
13 changes: 0 additions & 13 deletions app/controllers/discourse_antivirus/antivirus_controller.rb

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ class CreateScannedUploads < ::Jobs::Scheduled
def execute(_args)
return unless SiteSetting.discourse_antivirus_enabled?

scanner = DiscourseAntivirus::BackgroundScan.new(DiscourseAntivirus::ClamAV.instance)
scanner = DiscourseAntivirus::BackgroundScan.new(DiscourseAntivirus::ClamAv.instance)
scanner.queue_batch
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ class FetchAntivirusVersion < ::Jobs::Scheduled
def execute(_args)
return unless SiteSetting.discourse_antivirus_enabled?

DiscourseAntivirus::ClamAV.instance.update_versions
DiscourseAntivirus::ClamAv.instance.update_versions
end
end
end
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ class ScanBatch < ::Jobs::Scheduled
def execute(_args)
return unless SiteSetting.discourse_antivirus_enabled?

antivirus = DiscourseAntivirus::ClamAV.instance
antivirus = DiscourseAntivirus::ClamAv.instance
return unless antivirus.accepting_connections?

DiscourseAntivirus::BackgroundScan.new(antivirus).scan_batch
Expand Down
37 changes: 37 additions & 0 deletions models/reviewable_upload.rb → app/models/reviewable_upload.rb
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,40 @@ def build_action(actions, id, icon:, bundle: nil, confirm: false, button_class:
end
end
end

# == Schema Information
#
# Table name: reviewables
#
# id :bigint not null, primary key
# type :string not null
# status :integer default("pending"), not null
# created_by_id :integer not null
# reviewable_by_moderator :boolean default(FALSE), not null
# reviewable_by_group_id :integer
# category_id :integer
# topic_id :integer
# score :float default(0.0), not null
# potential_spam :boolean default(FALSE), not null
# target_id :integer
# target_type :string
# target_created_by_id :integer
# payload :json
# version :integer default(0), not null
# latest_score :datetime
# created_at :datetime not null
# updated_at :datetime not null
# force_review :boolean default(FALSE), not null
# reject_reason :text
#
# Indexes
#
# idx_reviewables_score_desc_created_at_desc (score,created_at)
# index_reviewables_on_reviewable_by_group_id (reviewable_by_group_id)
# index_reviewables_on_status_and_created_at (status,created_at)
# index_reviewables_on_status_and_score (status,score)
# index_reviewables_on_status_and_type (status,type)
# index_reviewables_on_target_id_where_post_type_eq_post (target_id) WHERE ((target_type)::text = 'Post'::text)
# index_reviewables_on_topic_id_and_status_and_created_by_id (topic_id,status,created_by_id)
# index_reviewables_on_type_and_target_id (type,target_id) UNIQUE
#
20 changes: 20 additions & 0 deletions models/scanned_upload.rb → app/models/scanned_upload.rb
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,23 @@ def handle_scan_result(result)
result[:found] ? move_to_quarantine!(result[:message]) : save!
end
end

# == Schema Information
#
# Table name: scanned_uploads
#
# id :bigint not null, primary key
# upload_id :integer
# next_scan_at :datetime
# quarantined :boolean default(FALSE), not null
# scans :integer default(0), not null
# virus_database_version_used :integer
# created_at :datetime not null
# updated_at :datetime not null
# last_scan_failed :boolean default(FALSE), not null
# scan_result :string
#
# Indexes
#
# index_scanned_uploads_on_upload_id (upload_id) UNIQUE
#
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# frozen_string_literal: true

module DiscourseAntivirus
class EnableDiscourseAntivirusValidator
def initialize(opts = {})
@opts = opts
end

def valid_value?(val)
return true if val == "f"

DiscourseAntivirus::ClamAv.correctly_configured?
end

def error_message
I18n.t("site_settings.errors.antivirus_srv_record_required")
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export default {
resource: "admin.adminPlugins.show",
path: "/plugins",

map() {
this.route("discourse-antivirus-stats", { path: "stats" });
},
};
8 changes: 0 additions & 8 deletions assets/javascripts/discourse/antivirus-route-map.js

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { PLUGIN_NAV_MODE_TOP } from "discourse/lib/admin-plugin-config-nav";
import { withPluginApi } from "discourse/lib/plugin-api";

export default {
name: "discourse-antivirus-admin-plugin-configuration-nav",

initialize(container) {
const currentUser = container.lookup("service:current-user");
if (!currentUser || !currentUser.admin) {
return;
}

withPluginApi("1.1.0", (api) => {
api.addAdminPluginConfigurationNav(
"discourse-antivirus",
PLUGIN_NAV_MODE_TOP,
[
{
label: "antivirus.stats.title",
route: "adminPlugins.show.discourse-antivirus-stats",
},
]
);
});
},
};
17 changes: 0 additions & 17 deletions assets/javascripts/discourse/routes/admin-plugins-antivirus.js

This file was deleted.

43 changes: 0 additions & 43 deletions assets/javascripts/discourse/templates/admin/plugins-antivirus.hbs

This file was deleted.

9 changes: 9 additions & 0 deletions assets/stylesheets/antivirus-stats.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.antivirus-stats {
&__sub-header {
margin-top: 15px;
}

&__header {
margin-bottom: 5px;
}
}
2 changes: 2 additions & 0 deletions config/locales/client.en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ en:
clamav_unavailable: We cannot establish a connection with the antivirus software. File scanning will be temporarily disabled.

stats:
title: Stats
data: Scan results
total_scans: Scanned Files
recently_scanned: Recently Scanned
quarantined: Quarantined
Expand Down
7 changes: 4 additions & 3 deletions config/routes.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# frozen_string_literal: true

DiscourseAntivirus::Engine.routes.draw do
root to: "antivirus#index"
get "/stats" => "antivirus#index"
Discourse::Application.routes.draw do
scope "/admin/plugins/discourse-antivirus", constraints: AdminConstraint.new do
get "/stats" => "discourse_antivirus/admin/antivirus#index"
end
end
2 changes: 1 addition & 1 deletion config/settings.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
plugins:
discourse_antivirus_enabled:
default: false
validator: EnableDiscourseAntivirusValidator
validator: DiscourseAntivirus::EnableDiscourseAntivirusValidator
antivirus_live_scan_images:
default: false
antivirus_srv_record:
Expand Down
2 changes: 1 addition & 1 deletion lib/discourse_antivirus/background_scan.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def stats

{
versions: @antivirus.versions,
background_scan_stats: {
stats: {
scans: scanned_upload_stats[0] || 0,
recently_scanned: scanned_upload_stats[1] || 0,
quarantined: scanned_upload_stats[2] || 0,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
# frozen_string_literal: true

module DiscourseAntivirus
class ClamAV
class ClamAv
VIRUS_FOUND = Class.new(StandardError)
PLUGIN_NAME = "discourse-antivirus"
STORE_KEY = "clamav-versions"
DOWNLOAD_FAILED = "Download failed"
UNAVAILABLE = "unavailable"

def self.instance
new(Discourse.store, DiscourseAntivirus::ClamAVServicesPool.new)
new(Discourse.store, DiscourseAntivirus::ClamAvServicesPool.new)
end

def self.correctly_configured?
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

module DiscourseAntivirus
class ClamAVHealthMetric < ::DiscoursePrometheus::InternalMetric::Custom
class ClamAvHealthMetric < ::DiscoursePrometheus::InternalMetric::Custom
attribute :name, :labels, :description, :value, :type

def initialize
Expand All @@ -15,7 +15,7 @@ def collect
last_check = @@clamav_stats[:last_check]

if (!last_check || should_recheck?(last_check))
antivirus = DiscourseAntivirus::ClamAV.instance
antivirus = DiscourseAntivirus::ClamAv.instance
available = antivirus.accepting_connections? ? 1 : 0

@@clamav_stats[:status] = available
Expand Down
Loading

0 comments on commit 815aed3

Please sign in to comment.