Witch Doctor
Rails engine that provides simple API so that external antivirus script can pull down files that need to be scanned and update their results.
Engine was designed to work alongside virus_scan_service gem, to which it provides a list of VirusScans. These are created upon file resource create / update events.
API is trying to comply with JSON API standard
CarrierWave
gem is by default assuming you use Carriewave
gem
however it's not a dependancy as long as :mount_point
responds to
url
call (look at spec/dummy/app/models/document.rb
) for more
details
working along ActiveModel::Serializer
please read more in docs/action_model_serializer.md
Setup
In your application:
# Gemfile
# ...
gem 'witch_doctor'
# config/routes.rb
MyCoolApplication::Application.routes.draw do
mount WitchDoctor::Engine => "/wd", :as => "witch_doctor"
# ...
end
# /config/initializers/witch_doctor.rb
WitchDoctor.token = Rails
.application
.secrets
.fetch('antivirus_scan')
.fetch('token')
bundle install
rake db:migrate
Optional
Add helper
# app/helpers/application_helper.rb
include WitchDoctor::ApplicationHelper
after this you can use the antivirus
helper
= antivirus(@document, :attachment) do
- link_to @document.attachment_name, @document.attachment.url
This will show the link when VirusScan
for @document
is Clean
Overiding WitchDoctor Examples
extending controller
module WitchDoctor
module MyAppControllerExtension
def self.included(base)
base.force_ssl unless: :development?
base.skip_before_filter :do_stuff
end
def development?
Rails.env.in? ['test', 'development']
end
end
end
WitchDoctor::VirusScansController.send(:include,WitchDoctor::MyAppControllerExtension)
extending Antivirus helper
include WitchDoctor::ApplicationHelper
module ApplicationHelper
alias_method :antivirus_without_view_requirement_respect, :antivirus
alias_method :antivirus, :antivirus_with_view_requirement_respect
def antivirus_with_view_requirement_respect(decorated_resource, mount_point)
if decorated_resource.send("view_requires_#{mount_point}_virus_check?")
antivirus_without_view_requirement_respect(decorated_resource, mount_point)
else
yield
end
end
end
Testing
Make sure you turn of virus_scan_scheduling_on
option so that gem wont
create extra records when your tests are running
# config/initializers/witch_doctor.rb
WitchDoctor.skip_virus_scan_scheduling = true
turn it on only when needed
# spec/request/virus_scan.rb
# ...
before do
WitchDoctor.skip_virus_scan_scheduling = false
end
after do
WitchDoctor.skip_virus_scan_scheduling = true
end
# ...
The gem/engine is pretty well tested but I recomend to write interation test for every application it is introduced to.
Example with RSpec request test:
require 'spec_helper'
RSpec.describe 'VirusScans', :type => :request do
before(:all) { WitchDoctor.time_stamper = -> { Time.now.midnight } }
after(:all) { WitchDoctor.time_stamper = (reset_stamper_to_default = nil) }
let(:token) { '1234' }
let!(:virus_scan) { FactoryGirl.create(:document).virus_scans.last }
describe 'GET index' do
before do
get "/wd/virus_scans", token: token, format: 'json'
end
it 'responds with success' do
expect(response.status).to be 200
end
it 'expect the JSON response to be JSON API hash' do
expect(JSON.parse response.body).to eq({
"data" => [
{
"id" => virus_scan.id,
"scan_result" => nil,
"scanned_at" => nil,
"file_url" => "/uploads/documents/#{virus_scan.id}/passport.jpg" # don't care about file storage (tests)
# as virus scans are needed only on s3
}
]
})
end
end
describe 'PUT update' do
let(:virus_scan_params) { { scan_result: 'Clean' } }
before do
put "/wd/virus_scans/#{virus_scan.id}",
{ format: 'json', virus_scan: virus_scan_params },
{ 'Authorization' => "Token 1234" }
end
it 'responds with success' do
expect(response.status).to be 200
end
it 'expect to update existing virus_scan' do
expect(JSON.parse response.body).to eq({
"data" => {
"id" => virus_scan.id,
"scan_result" => 'Clean',
"scanned_at" => Time.now.midnight.utc.iso8601,
"file_url" => "/uploads/documents/#{virus_scan.id}/passport.jpg"
}
})
end
end
end