witch_doctor 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
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