Skip to content
Snippets Groups Projects
Unverified Commit 33023459 authored by Ahmad Farhat's avatar Ahmad Farhat Committed by GitHub
Browse files

Added ClamAV integration (#5640)

parent ede08747
No related branches found
No related tags found
No related merge requests found
Showing
with 206 additions and 16 deletions
......@@ -11,6 +11,7 @@ gem 'aws-sdk-s3', require: false
gem 'bcrypt', '~> 3.1.7'
gem 'bigbluebutton-api-ruby', '1.9.1'
gem 'bootsnap', require: false
gem 'clamby', '~> 1.6.10'
gem 'cssbundling-rails', '>= 1.3.3'
gem 'data_migrate', '>= 9.2.0'
gem 'dotenv-rails'
......
......@@ -133,6 +133,7 @@ GEM
case_transform (0.2)
activesupport
childprocess (4.1.0)
clamby (1.6.10)
concurrent-ruby (1.2.2)
connection_pool (2.4.1)
crack (0.4.5)
......@@ -505,6 +506,7 @@ DEPENDENCIES
bigbluebutton-api-ruby (= 1.9.1)
bootsnap
capybara
clamby (~> 1.6.10)
cssbundling-rails (>= 1.3.3)
data_migrate (>= 9.2.0)
debug
......@@ -545,9 +547,3 @@ DEPENDENCIES
web-console (>= 4.2.1)
webdrivers
webmock
RUBY VERSION
ruby 3.1.0p0
BUNDLED WITH
2.3.9
......@@ -425,6 +425,7 @@
"file_size_too_large": "The file size is too large.",
"file_upload_error": "The file can't be uploaded.",
"signin_required": "You must be signed in to access this page.",
"malware_detected": "Malware Detected! The file you uploaded may contain malware. Please check your file and try again.",
"roles": {
"role_assigned": "This role can't be deleted because it is assigned to at least one user."
},
......
......@@ -51,10 +51,7 @@ module Api
site_setting.update(value: params[:site_setting][:value].to_s)
end
unless update
return render_error status: :bad_request,
errors: Rails.configuration.custom_error_msgs[:record_invalid]
end
return render_error status: :bad_request, errors: site_setting.errors.to_a unless update
render_data status: :ok
end
......
......@@ -110,7 +110,7 @@ module Api
create_default_room(user)
render_data status: :ok
else
render_error errors: Rails.configuration.custom_error_msgs[:record_invalid]
render_error errors: user.errors.to_a
end
end
......
......@@ -74,7 +74,11 @@ export default function useUpdateSiteSetting(name) {
handleSuccess();
},
onError: (error) => {
if (error.response.data.errors.includes('Image MalwareDetected')) {
toast.error(t('toast.error.malware_detected'));
} else {
handleError(error, t, toast);
}
},
},
);
......
......@@ -41,7 +41,11 @@ export default function useUploadPresentation(friendlyId) {
toast.success(t('toast.success.room.presentation_updated'));
},
onError: (error) => {
if (error.response.data.errors.includes('Presentation MalwareDetected')) {
toast.error(t('toast.error.malware_detected'));
} else {
handleError(error, t, toast);
}
},
});
......
......@@ -45,8 +45,12 @@ export default function useCreateAvatar(currentUser) {
queryClient.invalidateQueries('getUser');
toast.success(t('toast.success.user.avatar_updated'));
},
onError: () => {
onError: (error) => {
if (error.response.data.errors.includes('Avatar MalwareDetected')) {
toast.error(t('toast.error.malware_detected'));
} else {
toast.error(t('toast.error.problem_completing_action'));
}
},
},
);
......
......@@ -19,4 +19,8 @@
class ApplicationRecord < ActiveRecord::Base
primary_abstract_class
self.implicit_order_column = 'created_at'
def virus_scan?
ENV.fetch('CLAMAV_SCANNING', 'false') == 'true'
end
end
......@@ -38,6 +38,8 @@ class Room < ApplicationRecord
validates :recordings_processing, numericality: { only_integer: true, greater_than_or_equal_to: 0 }
before_validation :set_friendly_id, :set_meeting_id, on: :create
before_save :scan_presentation_for_virus
after_create :create_meeting_options
attr_accessor :shared, :active, :participants
......@@ -99,4 +101,15 @@ class Room < ApplicationRecord
rescue StandardError
retry
end
def scan_presentation_for_virus
return if !virus_scan? || !attachment_changes['presentation']
path = attachment_changes['presentation']&.attachable&.tempfile&.path
return true if Clamby.safe?(path)
errors.add(:presentation, 'MalwareDetected')
throw :abort
end
end
......@@ -31,4 +31,19 @@ class SiteSetting < ApplicationRecord
validates :image,
content_type: Rails.configuration.uploads[:images][:formats],
size: { less_than: Rails.configuration.uploads[:images][:max_size] }
before_save :scan_image_for_virus
private
def scan_image_for_virus
return if !virus_scan? || !attachment_changes['image']
path = attachment_changes['image']&.attachable&.tempfile&.path
return true if Clamby.safe?(path)
errors.add(:image, 'MalwareDetected')
throw :abort
end
end
......@@ -71,6 +71,7 @@ class User < ApplicationRecord
validate :check_user_role_provider, if: :role_changed?
before_validation :set_session_token, on: :create
before_save :scan_avatar_for_virus
scope :with_provider, ->(current_provider) { where(provider: current_provider) }
......@@ -214,4 +215,17 @@ class User < ApplicationRecord
errors.add(:user_provider, 'has to be the same as the Role provider') if provider != role.provider
end
private
def scan_avatar_for_virus
return if !virus_scan? || !attachment_changes['avatar']
path = attachment_changes['avatar']&.attachable&.tempfile&.path
return true if Clamby.safe?(path)
errors.add(:avatar, 'MalwareDetected')
throw :abort
end
end
# BigBlueButton open source conferencing system - http://www.bigbluebutton.org/.
#
# Copyright (c) 2022 BigBlueButton Inc. and by respective authors (see below).
#
# This program is free software; you can redistribute it and/or modify it under the
# terms of the GNU Lesser General Public License as published by the Free Software
# Foundation; either version 3.0 of the License, or (at your option) any later
# version.
#
# Greenlight is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
# PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License along
# with Greenlight; if not, see <http://www.gnu.org/licenses/>.
# frozen_string_literal: true
Clamby.configure({
check: ENV.fetch('CLAMAV_SCANNING', 'false') == 'true',
daemonize: ENV.fetch('CLAMAV_DAEMONIZE', 'true') == 'true',
fdpass: ENV.fetch('CLAMAV_DAEMONIZE', 'true') == 'true',
config_file: ENV.fetch('CLAMAV_CONFIG', nil)
})
......@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.1].define(version: 2023_07_05_183747) do
ActiveRecord::Schema[7.1].define(version: 2023_12_18_154727) do
# These are extensions that must be enabled in order to support this database
enable_extension "pgcrypto"
enable_extension "plpgsql"
......
......@@ -90,3 +90,10 @@ LOG_LEVEL=info
# RAILS_LOG_REMOTE_NAME=xxx.papertrailapp.com
# RAILS_LOG_REMOTE_PORT=99999
# RAILS_LOG_REMOTE_TAG=greenlight-v3
## ClamAV Virus Scanning
# If you have ClamAV installed on the same machine as your Greenlight deployment, you can enable automatic virus scanning
# for presentations, avatars, and BrandingImage. If a malicious file is detected, the user will be informed and asked
# to check their file.
#CLAMAV_SCANNING=true
#CLAMAV_DAEMONIZE=true
......@@ -86,6 +86,42 @@ RSpec.describe Room, type: :model do
end
end
describe 'before_save' do
describe '#scan_presentation_for_virus' do
let(:room) { create(:room) }
before do
allow_any_instance_of(described_class).to receive(:virus_scan?).and_return(true)
end
it 'makes a call to ClamAV if CLAMAV_SCANNING=true' do
expect(Clamby).to receive(:safe?)
room.presentation.attach(fixture_file_upload(file_fixture('default-avatar.png'), 'image/png'))
end
it 'adds an error if the file is not safe' do
allow(Clamby).to receive(:safe?).and_return(false)
room.presentation.attach(fixture_file_upload(file_fixture('default-avatar.png'), 'image/png'))
expect(room.errors[:presentation]).to eq(['MalwareDetected'])
end
it 'does not makes a call to ClamAV if the image is not changing' do
expect(Clamby).not_to receive(:safe?)
room.update(name: 'New Name')
end
it 'does not makes a call to ClamAV if CLAMAV_SCANNING=false' do
allow_any_instance_of(described_class).to receive(:virus_scan?).and_return(false)
expect(Clamby).not_to receive(:safe?)
room.presentation.attach(fixture_file_upload(file_fixture('default-avatar.png'), 'image/png'))
end
end
end
context 'after_create' do
describe 'create_meeting_options' do
let!(:viewer_access_code) { create(:meeting_option, name: 'glViewerAccessCode', default_value: '') }
......
......@@ -52,4 +52,40 @@ RSpec.describe SiteSetting, type: :model do
end
end
end
describe 'before_save' do
describe '#scan_image_for_virus' do
let(:site_setting) { create(:site_setting) }
before do
allow_any_instance_of(described_class).to receive(:virus_scan?).and_return(true)
end
it 'makes a call to ClamAV if CLAMAV_SCANNING=true' do
expect(Clamby).to receive(:safe?)
site_setting.image.attach(fixture_file_upload(file_fixture('default-avatar.png'), 'image/png'))
end
it 'adds an error if the file is not safe' do
allow(Clamby).to receive(:safe?).and_return(false)
site_setting.image.attach(fixture_file_upload(file_fixture('default-avatar.png'), 'image/png'))
expect(site_setting.errors[:image]).to eq(['MalwareDetected'])
end
it 'does not makes a call to ClamAV if the image is not changing' do
expect(Clamby).not_to receive(:safe?)
site_setting.update(provider: 'New Provider')
end
it 'does not makes a call to ClamAV if CLAMAV_SCANNING=false' do
allow_any_instance_of(described_class).to receive(:virus_scan?).and_return(false)
expect(Clamby).not_to receive(:safe?)
site_setting.image.attach(fixture_file_upload(file_fixture('default-avatar.png'), 'image/png'))
end
end
end
end
......@@ -352,4 +352,38 @@ RSpec.describe User, type: :model do
expect(user.errors[:user_provider]).not_to be_empty
end
end
describe '#scan_avatar_for_virus' do
let(:user) { create(:user) }
before do
allow_any_instance_of(described_class).to receive(:virus_scan?).and_return(true)
end
it 'makes a call to ClamAV if CLAMAV_SCANNING=true' do
expect(Clamby).to receive(:safe?)
user.avatar.attach(fixture_file_upload(file_fixture('default-avatar.png'), 'image/png'))
end
it 'adds an error if the file is not safe' do
allow(Clamby).to receive(:safe?).and_return(false)
user.avatar.attach(fixture_file_upload(file_fixture('default-avatar.png'), 'image/png'))
expect(user.errors[:avatar]).to eq(['MalwareDetected'])
end
it 'does not makes a call to ClamAV if the image is not changing' do
expect(Clamby).not_to receive(:safe?)
user.update(name: 'New Name')
end
it 'does not makes a call to ClamAV if CLAMAV_SCANNING=false' do
allow_any_instance_of(described_class).to receive(:virus_scan?).and_return(false)
expect(Clamby).not_to receive(:safe?)
user.avatar.attach(fixture_file_upload(file_fixture('default-avatar.png'), 'image/png'))
end
end
end
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment