witch_doctor 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +115 -0
  4. data/Rakefile +42 -0
  5. data/app/controllers/witch_doctor/application_controller.rb +4 -0
  6. data/app/controllers/witch_doctor/virus_scans_controller.rb +60 -0
  7. data/app/helpers/witch_doctor/application_helper.rb +15 -0
  8. data/app/models/virus_scan.rb +41 -0
  9. data/config/routes.rb +3 -0
  10. data/db/migrate/20150209121818_create_witch_doctor_virus_scans.rb +19 -0
  11. data/lib/tasks/witch_doctor_tasks.rake +4 -0
  12. data/lib/witch_doctor.rb +6 -0
  13. data/lib/witch_doctor/antivirus.rb +36 -0
  14. data/lib/witch_doctor/antivirus_concern.rb +32 -0
  15. data/lib/witch_doctor/engine.rb +11 -0
  16. data/lib/witch_doctor/version.rb +3 -0
  17. data/spec/controllers/virus_scans_controller_spec.rb +145 -0
  18. data/spec/dummy/README.rdoc +261 -0
  19. data/spec/dummy/Rakefile +7 -0
  20. data/spec/dummy/app/assets/javascripts/application.js +15 -0
  21. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  22. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  23. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  24. data/spec/dummy/app/models/document.rb +11 -0
  25. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  26. data/spec/dummy/config.ru +4 -0
  27. data/spec/dummy/config/application.rb +59 -0
  28. data/spec/dummy/config/boot.rb +10 -0
  29. data/spec/dummy/config/database.yml +25 -0
  30. data/spec/dummy/config/environment.rb +5 -0
  31. data/spec/dummy/config/environments/development.rb +37 -0
  32. data/spec/dummy/config/environments/production.rb +67 -0
  33. data/spec/dummy/config/environments/test.rb +37 -0
  34. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  35. data/spec/dummy/config/initializers/inflections.rb +15 -0
  36. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  37. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  38. data/spec/dummy/config/initializers/session_store.rb +8 -0
  39. data/spec/dummy/config/initializers/witch_doctor.rb +1 -0
  40. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  41. data/spec/dummy/config/locales/en.yml +5 -0
  42. data/spec/dummy/config/routes.rb +3 -0
  43. data/spec/dummy/db/migrate/20150209121125_create_document.rb +10 -0
  44. data/spec/dummy/db/schema.rb +33 -0
  45. data/spec/dummy/public/404.html +26 -0
  46. data/spec/dummy/public/422.html +26 -0
  47. data/spec/dummy/public/500.html +25 -0
  48. data/spec/dummy/public/favicon.ico +0 -0
  49. data/spec/dummy/script/rails +6 -0
  50. data/spec/factories/witch_doctor_documents.rb +5 -0
  51. data/spec/factories/witch_doctor_virus_scans.rb +10 -0
  52. data/spec/helpers/application_helper_spec.rb +39 -0
  53. data/spec/lib/antivirus_spec.rb +58 -0
  54. data/spec/models/virus_scan_spec.rb +52 -0
  55. data/spec/spec_helper.rb +42 -0
  56. metadata +249 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: f73af9aaaf48c0f55ddd920215c0b29fa28f45d6
4
+ data.tar.gz: c6c58658eb260e4c8ced01a3f48032ce939186e0
5
+ SHA512:
6
+ metadata.gz: ea8065910e20b6ed1f9e7d807602e0f6afbce62f4413f2cfe1eb907203f140fd60915adb423b240c33b854908c856a13f77361f84a1afc95415b8baabd145418
7
+ data.tar.gz: d5581753ea307593ca0d78807c2c83120350c4970ed990b8c901f1ead2b1c2870d64f4b61c9030e199af605e28c16e5f138917450d59c1883ca250d5deec40d3
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2015 YOURNAME
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,115 @@
1
+ (check BADGES.md for build status & test coverage)
2
+
3
+ # Witch Doctor
4
+
5
+ Rails engine that provides simple API so that external antivirus
6
+ script can pull down files that need to be scanned and update their
7
+ results.
8
+
9
+
10
+ Engine was designed to work alongside [virus_scan_service gem](https://github.com/equivalent/virus_scan_service),
11
+ to which it provides a list of VirusScans. These are created upon file resource
12
+ create / update events.
13
+
14
+
15
+ ## CarrierWave
16
+
17
+ gem is by default assuming you use [Carriewave
18
+ gem](https://github.com/carrierwaveuploader/carrierwave)
19
+ however it's not a dependancy as long as `:mount_point` responds to
20
+ `url` call (look at `spec/dummy/app/models/document.rb`) for more
21
+ details
22
+
23
+
24
+ # Setup
25
+
26
+ In your application:
27
+
28
+ ```ruby
29
+ # Gemfile
30
+
31
+ # ...
32
+ gem 'witch_doctor', github: 'equivalent/witch_doctor'
33
+
34
+ ```
35
+
36
+ ```ruby
37
+ # config/routes.rb
38
+ MyCoolApplication::Application.routes.draw do
39
+ mount WitchDoctor::Engine => "/wd", :as => "witch_doctor"
40
+ # ...
41
+ end
42
+ ```
43
+
44
+ ```ruby
45
+ # /config/initializers/witch_doctor.rb
46
+
47
+ VirusScan.token = Rails
48
+ .application
49
+ .secrets
50
+ .fetch('antivirus_scan')
51
+ .fetch('token')
52
+ ```
53
+
54
+
55
+ ```sh
56
+ bundle install
57
+ rake db:migrate
58
+ ```
59
+
60
+ ## Optional
61
+
62
+ **Add helper**
63
+
64
+ ```ruby
65
+ # app/helpers/application_helper.rb
66
+ include WitchDoctor::ApplicationHelper
67
+ ```
68
+
69
+ after this you can use the `antivirus` helper
70
+
71
+ ```haml
72
+ = antivirus(@document, :attachment) do
73
+ - link_to @document.attachment_name, @document.attachment.url
74
+ ```
75
+
76
+ This will show the link when `VirusScan` for `@document` is `Clean`
77
+
78
+
79
+ # Overiding WitchDoctor Examples
80
+
81
+ ## extending controller
82
+
83
+ ```ruby
84
+ module WitchDoctor
85
+ module MyAppControllerExtension
86
+ def self.included(base)
87
+ base.force_ssl unless: :development?
88
+ base.skip_before_filter :do_stuff
89
+ end
90
+
91
+ def development?
92
+ Rails.env.in? ['test', 'development']
93
+ end
94
+ end
95
+ end
96
+ WitchDoctor::VirusScansController.send(:include,WitchDoctor::MyAppControllerExtension)
97
+ ```
98
+
99
+ ## extending Antivirus helper
100
+
101
+ ```ruby
102
+ include WitchDoctor::ApplicationHelper
103
+ module ApplicationHelper
104
+ alias_method :antivirus_without_view_requirement_respect, :antivirus
105
+ alias_method :antivirus, :antivirus_with_view_requirement_respect
106
+
107
+ def antivirus_with_view_requirement_respect(decorated_resource, mount_point)
108
+ if decorated_resource.send("view_requires_#{mount_point}_virus_check?")
109
+ antivirus_without_view_requirement_respect(decorated_resource, mount_point)
110
+ else
111
+ yield
112
+ end
113
+ end
114
+ end
115
+ ```
data/Rakefile ADDED
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+ begin
8
+ require 'rdoc/task'
9
+ rescue LoadError
10
+ require 'rdoc/rdoc'
11
+ require 'rake/rdoctask'
12
+ RDoc::Task = Rake::RDocTask
13
+ end
14
+
15
+ RDoc::Task.new(:rdoc) do |rdoc|
16
+ rdoc.rdoc_dir = 'rdoc'
17
+ rdoc.title = 'WitchDoctor'
18
+ rdoc.options << '--line-numbers'
19
+ rdoc.rdoc_files.include('README.rdoc')
20
+ rdoc.rdoc_files.include('lib/**/*.rb')
21
+ end
22
+
23
+ APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
24
+ load 'rails/tasks/engine.rake'
25
+
26
+ require 'rspec/core'
27
+ require 'rspec/core/rake_task'
28
+
29
+ desc "Run all specs in spec directory (excluding plugin specs)"
30
+ RSpec::Core::RakeTask.new(:spec => 'app:prepare_test_db')
31
+
32
+ namespace :app do
33
+ task :prepare_test_db do
34
+ Rails.env = "test"
35
+ Rake::Task["app:db:create"].invoke
36
+ Rake::Task["app:db:migrate"].invoke
37
+ end
38
+ end
39
+
40
+ task :default => :spec
41
+
42
+ Bundler::GemHelper.install_tasks
@@ -0,0 +1,4 @@
1
+ module WitchDoctor
2
+ class ApplicationController < ActionController::Base
3
+ end
4
+ end
@@ -0,0 +1,60 @@
1
+ require_dependency "witch_doctor/application_controller"
2
+
3
+ module WitchDoctor
4
+ class VirusScansController < ApplicationController
5
+ respond_to :json
6
+
7
+ def index
8
+ authenticate! do
9
+ @virus_scans = VirusScan
10
+ .not_scanned
11
+ .limit(2)
12
+ respond_with @virus_scans
13
+ end
14
+ end
15
+
16
+ def update
17
+ authenticate! do
18
+ respond_to do |format|
19
+ format.json do
20
+ begin
21
+ @virus_scan = VirusScan.where(scan_result: nil).find params[:id]
22
+ @virus_scan.update_attributes params[:virus_scan], as: :scan_update
23
+ if @virus_scan.errors.any?
24
+ render json: { errors: @virus_scan.errors }, status: 400
25
+ else
26
+ render json: @virus_scan.reload
27
+ end
28
+ rescue ActiveModel::MassAssignmentSecurity::Error => e
29
+ render json: { errors: { request: [e.to_s] } }, status: 406
30
+ rescue ActiveRecord::RecordNotFound => e
31
+ render json: { errors: { request: ['Record not found or already scanned'] } }, status: 404
32
+ end
33
+ end
34
+ format.html do
35
+ render json: { errors: { request: ['needs to be JSON request'] } }, status: 406
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def authenticate!
44
+ if provided_token == nil
45
+ render json: { errors: { request: ['Not Authenticated'] } }, status: 401
46
+ elsif provided_token.to_s == VirusScan.token
47
+ yield
48
+ else
49
+ render json: { errors: { request: ['Not Authorized'] } }, status: 403
50
+ end
51
+ end
52
+
53
+ def provided_token
54
+ (request.headers['HTTP_AUTHORIZATION'] || request.headers['rack.session']['Authorization'])
55
+ .to_s
56
+ .match(/Token\s+(.*)/) { |m| m[1] } \
57
+ || params[:token]
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,15 @@
1
+ module WitchDoctor
2
+ module ApplicationHelper
3
+ def antivirus(resource, mount_point)
4
+ catch(:file_not_scanned) do
5
+ if (av = resource.send("#{mount_point}_antivirus")) && av.clean?
6
+ yield
7
+ elsif av.error?
8
+ "Antivirus scan couldn't be completed"
9
+ else
10
+ 'File Contains Virus'
11
+ end
12
+ end || 'File waiting for Antivirus check'
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,41 @@
1
+ class VirusScan < ActiveRecord::Base
2
+ TokenNotSpecified = Class.new(StandardError)
3
+
4
+ class << self
5
+ attr_writer :token
6
+
7
+ def token
8
+ @token || raise(TokenNotSpecified)
9
+ end
10
+ end
11
+
12
+ attr_accessible :scan_result, :scanned_at, :mount_point
13
+ attr_accessible :scan_result, as: :scan_update
14
+
15
+ belongs_to :resource, polymorphic: true
16
+ scope :not_scanned, -> { where scan_result: nil }
17
+ validates_inclusion_of :scan_result, in: WitchDoctor::Antivirus::RESULTS, allow_nil: true
18
+
19
+ before_update :set_scanned_at, if: :scan_updated?
20
+
21
+ def as_json(options={})
22
+ attributes
23
+ .slice('id', 'scan_result', 'scanned_at')
24
+ .tap { |hash|
25
+ hash.merge!('file_url' => file_url)
26
+ }
27
+ end
28
+
29
+ # S3 will give url, file wil show mount point, we care just about s3
30
+ def file_url
31
+ resource.send(mount_point).url
32
+ end
33
+
34
+ def set_scanned_at
35
+ self.scanned_at = Time.now
36
+ end
37
+
38
+ def scan_updated?
39
+ scan_result.in? WitchDoctor::Antivirus::RESULTS
40
+ end
41
+ end
data/config/routes.rb ADDED
@@ -0,0 +1,3 @@
1
+ WitchDoctor::Engine.routes.draw do
2
+ resources :virus_scans
3
+ end
@@ -0,0 +1,19 @@
1
+ class CreateWitchDoctorVirusScans < ActiveRecord::Migration
2
+ def up
3
+ unless ActiveRecord::Base.connection.table_exists? 'virus_scans'
4
+ create_table :virus_scans do |t|
5
+ t.string :resource_type
6
+ t.integer :resource_id
7
+ t.string :scan_result
8
+ t.string :mount_point
9
+ t.datetime :scanned_at
10
+
11
+ t.timestamps
12
+ end
13
+ end
14
+ end
15
+
16
+ def down
17
+ drop_table :virus_scans
18
+ end
19
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :witch_doctor do
3
+ # # Task goes here
4
+ # end
@@ -0,0 +1,6 @@
1
+ require "witch_doctor/engine"
2
+ require "witch_doctor/antivirus"
3
+ require "witch_doctor/antivirus_concern"
4
+
5
+ module WitchDoctor
6
+ end
@@ -0,0 +1,36 @@
1
+ module WitchDoctor
2
+ class Antivirus
3
+ RESULTS = ['Clean', 'VirusInfected', 'FileDownloadError']
4
+ attr_reader :resource, :mount_point
5
+
6
+ def initialize(resource, mount_point)
7
+ @resource = resource
8
+ @mount_point = mount_point.to_s
9
+ end
10
+
11
+ def latest_scan
12
+ resource
13
+ .virus_scans
14
+ .select { |vs| vs.mount_point == mount_point }
15
+ .last
16
+ end
17
+
18
+ def checked?
19
+ latest_scan.scan_result.present?
20
+ end
21
+
22
+ def infected?
23
+ !clean? && !error?
24
+ end
25
+
26
+ def error?
27
+ throw :file_not_scanned unless checked?
28
+ latest_scan.scan_result == 'FileDownloadError'
29
+ end
30
+
31
+ def clean?
32
+ throw :file_not_scanned unless checked?
33
+ latest_scan.scan_result == 'Clean'
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,32 @@
1
+ module WitchDoctor
2
+ module AntivirusConcern
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ has_many :virus_scans, as: :resource
7
+ end
8
+
9
+ module ClassMethods
10
+ def schedule_virus_scan(options)
11
+ mount_point = options.fetch(:on)
12
+
13
+ after_save "schedule_#{mount_point}_virus_scan", if: "schedule_#{mount_point}_virus_scan?"
14
+
15
+ define_method("schedule_#{mount_point}_virus_scan") do
16
+ virus_scans.create!(mount_point: mount_point.to_s)
17
+ end
18
+
19
+ define_method("schedule_#{mount_point}_virus_scan?") do
20
+ # equivalent to: (created_at_changed? && logo.present?) || (logo_changed? && logo.present?)
21
+ (created_at_changed? && send(mount_point).present?) \
22
+ || (send("#{mount_point}_changed?") && send(mount_point).present?)
23
+ end
24
+
25
+ define_method("#{mount_point}_antivirus") do
26
+ instance_variable_get("@#{mount_point}_antivirus") \
27
+ || instance_variable_set("@#{mount_point}_antivirus", Antivirus.new(self, mount_point))
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,11 @@
1
+ module WitchDoctor
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace WitchDoctor
4
+ config.generators do |g|
5
+ g.test_framework :rspec, :fixture => false
6
+ g.fixture_replacement :factory_girl, :dir => 'spec/factories'
7
+ g.assets false
8
+ g.helper false
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,3 @@
1
+ module WitchDoctor
2
+ VERSION = "0.0.3"
3
+ end