virus_scan_service 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 371745c9fa7656ebc95bff2a471861961978f3eb
4
+ data.tar.gz: 6149799e687ed9a11730e558606fd854936c65a4
5
+ SHA512:
6
+ metadata.gz: 92f8d01f68a75caefaedf6dd19cdf21107ec3b70fc5a36da5000b1db71d4728e49f73e8dab5afd506add7a84d36b0aa352df70c4002b48f863ada8962918ef30
7
+ data.tar.gz: ce4c379926311585102ec0053f63dc07f9e4b21818809e0f93f0bdbebebf3cd2968ec1b611b976d2ba023f30c9268f08047737b1c93eebbec2e392a6651492b4
@@ -0,0 +1,25 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ spec/tmp
17
+ test/version_tmp
18
+ tmp
19
+ *.bundle
20
+ *.so
21
+ *.o
22
+ *.a
23
+ mkmf.log
24
+ .ruby-version
25
+ .ruby-gemset
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
@@ -0,0 +1,15 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 2.0.0
5
+ - 2.1.0
6
+ - 2.1.1
7
+ - 2.1.2
8
+ - 2.1.3
9
+ - 2.1.4
10
+ - 2.1.5
11
+ - 2.2.0
12
+ - ruby-head
13
+ addons:
14
+ code_climate:
15
+ repo_token: f559a050ffd5245a5ef38516fd61ea91a8b38f5e9545d2af3076c51ba715d07b
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Tomas Valent
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,70 @@
1
+ [![Build Status](https://travis-ci.org/equivalent/virus_scan_service.svg)](https://travis-ci.org/equivalent/virus_scan_service)
2
+ [![Code Climate](https://codeclimate.com/github/equivalent/virus_scan_service/badges/gpa.svg)](https://codeclimate.com/github/equivalent/virus_scan_service)
3
+ [![Test Coverage](https://codeclimate.com/github/equivalent/virus_scan_service/badges/coverage.svg)](https://codeclimate.com/github/equivalent/virus_scan_service)
4
+
5
+ # VirusScanService
6
+
7
+ Service gem that provide virus scan runner that will pull down list of
8
+ files to be scanned from your application server, lunch antivirus check (currently only
9
+ Kasperky Endponit Security runner Windows or Linux) and send scan result
10
+ back to server.
11
+
12
+ You don't need to have this script running on the same server
13
+ as application server VM. (Article comming soon)
14
+
15
+ Originaly built to work along [witch_doctor engine gem](https://github.com/equivalent/witch_doctor)
16
+ however that is not required. All your server has to do
17
+ is provide API that this secvice can comunicate with:
18
+
19
+ #### GET `/wd/virus_scans` `ContentType: application/json`
20
+
21
+ response
22
+
23
+ ```json
24
+ [{"id":"123","scan_result":"","file_url":"http://thisis.test/download/file.png"}]
25
+ ```
26
+
27
+ #### PUT `/wd/virus_scans/123` `ContentType: application/json`
28
+
29
+ request body
30
+
31
+ ```json
32
+ {"virus_scan":{"scan_result":"Clean"}}
33
+ ```
34
+
35
+ response
36
+
37
+ ```json
38
+ {"id":"123","scan_result":"Clean","file_url":"http://thisis.test/download/file.png"}
39
+ ```
40
+
41
+ For more examples check `spec/courier_spec.rb`, `spec/support/request_response_mocks.rb
42
+
43
+
44
+ ## Statuses
45
+
46
+ * `Clean`
47
+ * `VirusInfected`
48
+ * `FileDownloadError` - couldn't download asset
49
+
50
+ ## Installation
51
+
52
+ Add this line to your external script Gemfile:
53
+
54
+ gem 'virus_scan_service'
55
+
56
+ And then execute:
57
+
58
+ $ bundle
59
+
60
+ ## Usage
61
+
62
+ check https://github.com/equivalent/virus_scan_daemon
63
+
64
+ ## Contributing
65
+
66
+ 1. Fork it ( https://github.com/[my-github-username]/virus_scan_service/fork )
67
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
68
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
69
+ 4. Push to the branch (`git push origin my-new-feature`)
70
+ 5. Create a new Pull Request
@@ -0,0 +1,13 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rspec/core'
3
+ require 'rspec/core/rake_task'
4
+
5
+ begin
6
+ require 'rspec/core/rake_task'
7
+ RSpec::Core::RakeTask.new(:spec)
8
+ task :default => :spec
9
+ rescue LoadError
10
+ # no rspec available
11
+ end
12
+
13
+ task :default => :spec
@@ -0,0 +1,11 @@
1
+ require 'net/https'
2
+ require 'pathname'
3
+ require 'json'
4
+ require 'virus_scan_service/version'
5
+ require 'virus_scan_service/build_http'
6
+ require 'virus_scan_service/kaspersky_runner'
7
+ require 'virus_scan_service/courier'
8
+ require 'virus_scan_service/default_logger'
9
+
10
+ module VirusScanService
11
+ end
@@ -0,0 +1,14 @@
1
+ module VirusScanService
2
+ module BuildHttp
3
+ def build_http
4
+ if uri.scheme == 'https'
5
+ http = Net::HTTP.new(uri.host, uri.port)
6
+ http.use_ssl = true
7
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
8
+ http
9
+ else
10
+ Net::HTTP.new(uri.host, uri.port)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,85 @@
1
+ module VirusScanService
2
+ class Courier
3
+ RequestNotSuccessful = Class.new(StandardError)
4
+
5
+ include BuildHttp
6
+
7
+ attr_reader :token
8
+ attr_accessor :num_of_scans, :logger
9
+
10
+ def initialize(options)
11
+ @token = options.fetch(:token)
12
+ @host = options.fetch(:host)
13
+ @num_of_scans = 1
14
+ @logger = DefaultLogger.new
15
+ end
16
+
17
+ def call
18
+ scheduled_scans
19
+ .first(num_of_scans)
20
+ .each do |scheduled_scan|
21
+ resoult = yield(scheduled_scan.fetch('file_url'))
22
+ update_scan_result(scheduled_scan.fetch('id'), resoult)
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def uri
29
+ @uri ||= URI.parse(@host)
30
+ end
31
+
32
+ def scheduled_scans
33
+ uri.path = '/wd/virus_scans'
34
+ logger.info "GET #{uri.to_s}"
35
+
36
+ http = build_http
37
+ scans_req = Net::HTTP::Get.new(uri.to_s)
38
+ scans_req.add_field("Authorization", "Token #{token}")
39
+ scans_req['Accept'] ='application/json'
40
+
41
+ response = http.request(scans_req)
42
+
43
+ check_status(response) {
44
+ response.body # array of virus_scans
45
+ }
46
+ end
47
+
48
+ def update_scan_result(scan_id, result)
49
+ uri.path = "/wd/virus_scans/#{scan_id}"
50
+ logger.info "PUT #{uri.to_s}"
51
+
52
+ http = build_http
53
+
54
+ scan_push_req = Net::HTTP::Put.new(uri.to_s)
55
+ scan_push_req.add_field("Authorization", "Token #{token}")
56
+ scan_push_req['Accept'] = 'application/json'
57
+ scan_push_req.add_field('Content-Type', 'application/json')
58
+ scan_push_req
59
+ .body = {"virus_scan" => {'scan_result' => result}}
60
+ .to_json
61
+
62
+ response = http.request(scan_push_req)
63
+
64
+ check_status(response) {
65
+ response.body # result JSON
66
+ }
67
+ end
68
+
69
+ def json(body)
70
+ logger.debug "Response body #{body}"
71
+ JSON.parse(body)
72
+ end
73
+
74
+ def check_status(response)
75
+ if response.class == Net::HTTPOK
76
+ logger.info "Response status OK 200"
77
+ json(yield)
78
+ else
79
+ logger.info "Response status #{response.class}"
80
+ logger.info yield
81
+ raise RequestNotSuccessful
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,11 @@
1
+ module VirusScanService
2
+ class DefaultLogger
3
+ def info(msg)
4
+ puts msg
5
+ end
6
+
7
+ def debug(msg)
8
+ puts msg
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,136 @@
1
+ require_relative 'kaspersky_runner/linux_executor'
2
+ require_relative 'kaspersky_runner/windows_executor'
3
+
4
+ module VirusScanService
5
+ class KasperskyRunner
6
+ ScanLogPathNotSet = Class.new(StandardError)
7
+ ScanLogParseError = Class.new(StandardError)
8
+ AntivirusExecNotSet = Class.new(StandardError)
9
+ RequestNotSuccessful = Class.new(StandardError)
10
+
11
+ include BuildHttp
12
+
13
+ attr_reader :url, :result
14
+ attr_writer :scan_folder, :archive_folder
15
+ attr_accessor :scan_log_path, :timestamp_builder, :antivirus_exec
16
+
17
+ def initialize(url)
18
+ @url = url
19
+ @timestamp_builder = ->{ Time.now.to_i.to_s }
20
+ end
21
+
22
+ def call
23
+ begin
24
+ pull_file
25
+ begin
26
+ ensure_no_scan_log_exist
27
+ scan_file
28
+ set_result
29
+ ensure
30
+ remove_file
31
+ archive_scan_log if File.exist?(scan_log_path)
32
+ end
33
+ rescue URI::InvalidURIError, RequestNotSuccessful
34
+ set_result_download_error
35
+ end
36
+ return nil
37
+ end
38
+
39
+ def scan_file_path
40
+ scan_folder.join(filename)
41
+ end
42
+
43
+ def scan_folder
44
+ @scan_folder ||= Pathname
45
+ .new('/tmp')
46
+ .join('scans')
47
+ .tap do |path|
48
+ FileUtils.mkdir_p(path)
49
+ end
50
+ end
51
+
52
+ def archive_folder
53
+ @archive_folder ||= Pathname
54
+ .new('/tmp')
55
+ .join('scans')
56
+ .tap do |path|
57
+ FileUtils.mkdir_p(path)
58
+ end
59
+ end
60
+
61
+ private
62
+ def archive_scan_log
63
+ archive_name = "#{File.basename(scan_log_path.to_s, '.*')}_#{timestamp_builder.call}.log"
64
+ FileUtils.mv(scan_log_path, archive_folder.join(archive_name))
65
+ end
66
+
67
+ def remove_file
68
+ begin
69
+ FileUtils.rm_r(scan_folder.join(filename))
70
+ rescue => e
71
+ # kaspersky is automatically removing suspicious files
72
+ # this is rescue ensures that after kasperky removes that file
73
+ # script wont blow up
74
+ #
75
+ # For whatever reason under Windows using
76
+ #
77
+ # if File.exist?(scan_folder.join(filename))
78
+ #
79
+ # wont help to determin if file was removed by kaspersky
80
+ #
81
+ # That's why this captures if exception matches Permission deny @ unlink_internal
82
+ raise e unless e.to_s.match('unlink_internal')
83
+ end
84
+ end
85
+
86
+ def ensure_no_scan_log_exist
87
+ FileUtils.rm scan_log_path if File.exist?(scan_log_path)
88
+ end
89
+
90
+ def set_result
91
+ result = File.read(scan_log_path || raise(ScanLogPathNotSet))
92
+ result.scan(/Total detected:\s*(\d+)/) do |threat_count, *other|
93
+ if threat_count == ''
94
+ raise ScanLogParseError
95
+ elsif threat_count == '0'
96
+ @result = 'Clean'
97
+ else
98
+ @result = 'VirusInfected'
99
+ end
100
+ end
101
+
102
+ raise ScanLogParseError if @result.nil?
103
+ end
104
+
105
+ def set_result_download_error
106
+ @result = 'FileDownloadError'
107
+ end
108
+
109
+ def scan_file
110
+ (antivirus_exec || raise(AntivirusExecNotSet))
111
+ .scan(scan_file_path, scan_log_path)
112
+ end
113
+
114
+ def pull_file
115
+ http = build_http
116
+
117
+ request = Net::HTTP::Get.new(uri.to_s)
118
+ response = http.request(request)
119
+
120
+ raise(RequestNotSuccessful) unless response.class == Net::HTTPOK
121
+
122
+ open(scan_file_path, 'wb') do |file|
123
+ file.write(response.body)
124
+ file.close
125
+ end
126
+ end
127
+
128
+ def uri
129
+ @uri ||= URI.parse(url)
130
+ end
131
+
132
+ def filename
133
+ File.basename(uri.path)
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,15 @@
1
+ module VirusScanService
2
+ class KasperskyRunner
3
+ class LinuxExecutor
4
+ # not tested on real production as I'm using windows VM for virus scans
5
+ def scan(file_path, log_path)
6
+ system 'sudo',
7
+ '/opt/kaspersky/kes4lwks/bin/kes4lwks-control',
8
+ '--scan-file',
9
+ file_path.to_s,
10
+ ">>",
11
+ log_path.to_s
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,9 @@
1
+ module VirusScanService
2
+ class KasperskyRunner
3
+ class WindowsExecutor
4
+ def scan(file_path, log_path)
5
+ system(*%W{avp.com SCAN #{file_path} /i4 /fa /RA:#{log_path}})
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,3 @@
1
+ module VirusScanService
2
+ VERSION = "0.0.8"
3
+ end
@@ -0,0 +1,35 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe VirusScanService::Courier do
4
+ include RequestResponseMocks
5
+
6
+ let(:courier) {
7
+ described_class
8
+ .new(token: 'abcdefg', host: 'http://thisisa.test')
9
+ .tap { |c| c.logger = Struct::NullLogger.new }
10
+ }
11
+
12
+ before do
13
+ server_response_list do
14
+ '[{"id":"123","scan_result":"","file_url":"http://thisis.test/download/file.png"}]'
15
+ end
16
+
17
+ server_request_put(id: 123, status: 'Clean') do
18
+ '{"id":"123","scan_result":"Clean","file_url":"http://thisis.test/download/file.png"}'
19
+ end
20
+ end
21
+
22
+ it do
23
+ expect(DummyViruscheckRunner)
24
+ .to receive(:new)
25
+ .with('http://thisis.test/download/file.png')
26
+ .once
27
+ .and_call_original
28
+
29
+ courier.call do |file_url|
30
+ casp = DummyViruscheckRunner.new(file_url)
31
+ casp.call
32
+ casp.result
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,39 @@
1
+ 2015-01-27 16:07:41 Scan_Objects$0433 starting 1%
2
+ ; --- Settings ---
3
+ ; Action on detect: Disinfect automatically
4
+ ; Scan objects: All objects
5
+ ; Use iChecker: Yes
6
+ ; Use iSwift: Yes
7
+ ; Try disinfect: No
8
+ ; Try delete: Yes
9
+ ; Try delete container: Yes
10
+ ; Exclude by mask: No
11
+ ; Include by mask: No
12
+ ; Objects to scan:
13
+ ; "C:\Users\David\Downloads\Ebuyer-Invoice.pdf" Enable=Yes Recursive=No
14
+ ; ------------------
15
+ 2015-01-27 16:07:41 C:\Users\David\Downloads\Ebuyer-Invoice.pdf ok (iSwift)
16
+ 2015-01-27 16:07:41 Scan_Objects$0433 running 100%
17
+ 2015-01-27 16:07:41 Scan_Objects$0433 completed
18
+ ; --- Statistics ---
19
+ ; Current time: 2015-01-27 16:07:41
20
+ ; Time Start: 2015-01-27 16:07:41
21
+ ; Time Finish: 2015-01-27 16:07:41
22
+ ; Completion: 100%
23
+ ; Processed objects: 1
24
+ ; Total detected: 0
25
+ ; Detected exact: 0
26
+ ; Suspicions: 0
27
+ ; Treats detected: 0
28
+ ; Untreated: 0
29
+ ; Disinfected: 0
30
+ ; Quarantined: 0
31
+ ; Deleted: 0
32
+ ; Skipped: 0
33
+ ; Archived: 0
34
+ ; Packed: 0
35
+ ; Password protected: 0
36
+ ; Corrupted: 0
37
+ ; Errors: 0
38
+ ; Last object:
39
+ ; ------------------
@@ -0,0 +1,39 @@
1
+ 2015-01-27 16:07:41 Scan_Objects$0433 starting 1%
2
+ ; --- Settings ---
3
+ ; Action on detect: Disinfect automatically
4
+ ; Scan objects: All objects
5
+ ; Use iChecker: Yes
6
+ ; Use iSwift: Yes
7
+ ; Try disinfect: No
8
+ ; Try delete: Yes
9
+ ; Try delete container: Yes
10
+ ; Exclude by mask: No
11
+ ; Include by mask: No
12
+ ; Objects to scan:
13
+ ; "C:\Users\David\Downloads\Ebuyer-Invoice.pdf" Enable=Yes Recursive=No
14
+ ; ------------------
15
+ 2015-01-27 16:07:41 C:\Users\David\Downloads\Ebuyer-Invoice.pdf ok (iSwift)
16
+ 2015-01-27 16:07:41 Scan_Objects$0433 running 100%
17
+ 2015-01-27 16:07:41 Scan_Objects$0433 completed
18
+ ; --- Statistics ---
19
+ ; Current time: 2015-01-27 16:07:41
20
+ ; Time Start: 2015-01-27 16:07:41
21
+ ; Time Finish: 2015-01-27 16:07:41
22
+ ; Completion: 100%
23
+ ; Processed objects: 1
24
+ ; Total detected:
25
+ ; Detected exact: 0
26
+ ; Suspicions: 0
27
+ ; Treats detected: 0
28
+ ; Untreated: 0
29
+ ; Disinfected: 0
30
+ ; Quarantined: 0
31
+ ; Deleted: 0
32
+ ; Skipped: 0
33
+ ; Archived: 0
34
+ ; Packed: 0
35
+ ; Password protected: 0
36
+ ; Corrupted: 0
37
+ ; Errors: 0
38
+ ; Last object:
39
+ ; ------------------
@@ -0,0 +1,39 @@
1
+ 2015-01-27 16:07:41 Scan_Objects$0433 starting 1%
2
+ ; --- Settings ---
3
+ ; Action on detect: Disinfect automatically
4
+ ; Scan objects: All objects
5
+ ; Use iChecker: Yes
6
+ ; Use iSwift: Yes
7
+ ; Try disinfect: No
8
+ ; Try delete: Yes
9
+ ; Try delete container: Yes
10
+ ; Exclude by mask: No
11
+ ; Include by mask: No
12
+ ; Objects to scan:
13
+ ; "C:\Users\David\Downloads\Ebuyer-Invoice.pdf" Enable=Yes Recursive=No
14
+ ; ------------------
15
+ 2015-01-27 16:07:41 C:\Users\David\Downloads\Ebuyer-Invoice.pdf ok (iSwift)
16
+ 2015-01-27 16:07:41 Scan_Objects$0433 running 100%
17
+ 2015-01-27 16:07:41 Scan_Objects$0433 completed
18
+ ; --- Statistics ---
19
+ ; Current time: 2015-01-27 16:07:41
20
+ ; Time Start: 2015-01-27 16:07:41
21
+ ; Time Finish: 2015-01-27 16:07:41
22
+ ; Completion: 100%
23
+ ; Processed objects: 1
24
+ ; Total detected: 1
25
+ ; Detected exact: 0
26
+ ; Suspicions: 0
27
+ ; Treats detected: 0
28
+ ; Untreated: 0
29
+ ; Disinfected: 0
30
+ ; Quarantined: 0
31
+ ; Deleted: 0
32
+ ; Skipped: 0
33
+ ; Archived: 0
34
+ ; Packed: 0
35
+ ; Password protected: 0
36
+ ; Corrupted: 0
37
+ ; Errors: 0
38
+ ; Last object:
39
+ ; ------------------
@@ -0,0 +1,21 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe VirusScanService::KasperskyRunner::LinuxExecutor do
4
+ subject { described_class.new }
5
+
6
+ let(:desired_cmd) {
7
+ 'sudo ' +
8
+ '/opt/kaspersky/kes4lwks/bin/kes4lwks-control ' +
9
+ '--scan-file /tmp/scan_file >> /tmp/bar.log'
10
+ }
11
+
12
+ describe '#scan' do
13
+ it 'should exectute correct command' do
14
+ expect(subject)
15
+ .to receive(:system)
16
+ .with(*desired_cmd.split(' '))
17
+
18
+ subject.scan(Pathname.new('/tmp').join('scan_file'), Pathname.new('/tmp').join('bar.log'))
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,19 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe VirusScanService::KasperskyRunner::WindowsExecutor do
4
+ subject { described_class.new }
5
+
6
+ let(:desired_cmd) {
7
+ "avp.com SCAN /tmp/scan_file /i4 /fa /RA:/tmp/bar.log"
8
+ }
9
+
10
+ describe '#scan' do
11
+ it 'should exectute correct command' do
12
+ expect(subject)
13
+ .to receive(:system)
14
+ .with(*desired_cmd.split(' '))
15
+
16
+ subject.scan(Pathname.new('/tmp').join('scan_file'), Pathname.new('/tmp').join('bar.log'))
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,165 @@
1
+ require 'spec_helper'
2
+ require 'fileutils'
3
+ require 'forwardable'
4
+
5
+ RSpec.describe VirusScanService::KasperskyRunner do
6
+ class DummyAntivirusRunner
7
+ extend Forwardable
8
+
9
+ attr_reader :spec
10
+ def_delegator :spec, :expect
11
+ def_delegator :spec, :eq
12
+
13
+ def initialize(spec)
14
+ @spec = spec
15
+ end
16
+
17
+ def scan(file_path, log_path)
18
+ expect(file_path.to_s).to eq 'spec/tmp/scans/file.png'
19
+ expect(log_path.to_s).to eq 'spec/tmp/kaspersky_test.log'
20
+ FileUtils.cp(spec.scan_log, 'spec/tmp/kaspersky_test.log')
21
+ end
22
+ end
23
+
24
+ let(:file_url) { 'http://thisis.test/download/file.png' }
25
+
26
+ let(:runner) {
27
+ described_class
28
+ .new(file_url)
29
+ .tap do |runner|
30
+ runner.timestamp_builder = ->{ '012345678' }
31
+ runner.scan_log_path = 'spec/tmp/kaspersky_test.log'
32
+ runner.scan_folder = Pathname
33
+ .new('spec')
34
+ .join('tmp')
35
+ .join('scans')
36
+ .tap do |path| FileUtils.mkdir_p(path) end
37
+ runner.archive_folder = Pathname
38
+ .new('spec')
39
+ .join('tmp')
40
+ .join('scans_archive')
41
+ .tap do |path| FileUtils.mkdir_p(path) end
42
+ runner.antivirus_exec = DummyAntivirusRunner.new(self)
43
+ end
44
+ }
45
+
46
+ let(:scan_log) {
47
+ spec_root
48
+ .join('fixtures')
49
+ .join('virus_result_clean.log')
50
+ }
51
+
52
+ describe "#call" do
53
+ after do
54
+ if File.exist?("spec/tmp/scans_archive/kaspersky_test_012345678.log")
55
+ FileUtils.rm "spec/tmp/scans_archive/kaspersky_test_012345678.log"
56
+ end
57
+ end
58
+
59
+ let(:call) { runner.call }
60
+
61
+ context 'when valid url' do
62
+ before do
63
+ stub_request(:get, "http://thisis.test/download/file.png")
64
+ .with(:headers => {
65
+ 'Accept'=>'*/*',
66
+ 'User-Agent'=>'Ruby'
67
+ })
68
+ .to_return(:status => 200, :body => "This-is-a-file-content", :headers => {})
69
+ end
70
+
71
+ before do
72
+ expect(runner).to receive(:ensure_no_scan_log_exist).and_call_original
73
+ end
74
+
75
+ context '' do
76
+ before { allow(runner).to receive(:remove_file) } # skip file remove
77
+
78
+ it 'downloads the file from net' do
79
+ call
80
+ expect(File.read(runner.scan_file_path))
81
+ .to eq "This-is-a-file-content"
82
+ end
83
+ end
84
+
85
+ it 'should remove scanned file' do
86
+ call
87
+ expect(File.exist?(runner.scan_file_path)).to be false
88
+ end
89
+
90
+ it 'should archive scan log' do
91
+ call
92
+ expect(File.exist?('spec/tmp/kaspersky_test.log')).to be false
93
+ expect(File.exist?("spec/tmp/scans_archive/kaspersky_test_012345678.log")).to be true
94
+ end
95
+
96
+ context 'when no threats detected' do
97
+ before { call }
98
+ it 'sets the result' do
99
+ expect(runner.result).to eq 'Clean'
100
+ end
101
+ end
102
+
103
+ context 'when virus detected' do
104
+ before { call }
105
+ let(:scan_log) {
106
+ spec_root
107
+ .join('fixtures')
108
+ .join('virus_result_threat.log')
109
+ }
110
+
111
+ it 'sets the result' do
112
+ expect(runner.result).to eq 'VirusInfected'
113
+ end
114
+ end
115
+
116
+ context 'when log has error' do
117
+ let(:scan_log) {
118
+ spec_root
119
+ .join('fixtures')
120
+ .join('virus_result_error.log')
121
+ }
122
+
123
+ it 'sets the result' do
124
+ expect { runner.call }
125
+ .to raise_error(VirusScanService::KasperskyRunner::ScanLogParseError)
126
+ end
127
+ end
128
+ end
129
+
130
+ context 'when nil file_url' do
131
+ let(:file_url) { nil }
132
+
133
+ before { call }
134
+
135
+ it 'should set resoult to FileDownloadError' do
136
+ expect(runner.result).to eq 'FileDownloadError'
137
+ end
138
+ end
139
+
140
+ context 'when file pull ends up in status not successful status' do
141
+ before do
142
+ stub_request(:get, "http://thisis.test/download/file.png")
143
+ .with(:headers => {
144
+ 'Accept'=>'*/*',
145
+ 'User-Agent'=>'Ruby'
146
+ })
147
+ .to_return(:status => 500, :body => "", :headers => {})
148
+ end
149
+
150
+ before { call }
151
+
152
+ it 'should set resoult to FileDownloadError' do
153
+ expect(runner.result).to eq 'FileDownloadError'
154
+ end
155
+ end
156
+ end
157
+
158
+ describe 'private #ensure_no_scan_log_exist' do
159
+ it 'should existing scan log before scan begin' do
160
+ FileUtils.cp(scan_log, 'spec/tmp/kaspersky_test.log') # pre-existing scan
161
+ runner.send(:ensure_no_scan_log_exist)
162
+ expect(File.exist?('spec/tmp/kaspersky_test.log')).to be false
163
+ end
164
+ end
165
+ end
@@ -0,0 +1,106 @@
1
+ require "codeclimate-test-reporter"
2
+ CodeClimate::TestReporter.start
3
+
4
+ require 'virus_scan_service'
5
+ require 'webmock/rspec'
6
+ require 'pathname'
7
+ require 'pry' if ENV['PRY'] # PRY=true rspec spec
8
+
9
+ WebMock.disable_net_connect!(:allow => "codeclimate.com")
10
+
11
+ def spec_root
12
+ Pathname.new('./spec')
13
+ end
14
+
15
+ Dir.glob(spec_root.join('support').join('*.rb')) do |file|
16
+ require file
17
+ end
18
+ # This file was generated by the `rspec --init` command. Conventionally, all
19
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
20
+ # The generated `.rspec` file contains `--require spec_helper` which will cause this
21
+ # file to always be loaded, without a need to explicitly require it in any files.
22
+ #
23
+ # Given that it is always loaded, you are encouraged to keep this file as
24
+ # light-weight as possible. Requiring heavyweight dependencies from this file
25
+ # will add to the boot time of your test suite on EVERY test run, even for an
26
+ # individual file that may not need all of that loaded. Instead, consider making
27
+ # a separate helper file that requires the additional dependencies and performs
28
+ # the additional setup, and require it from the spec files that actually need it.
29
+ #
30
+ # The `.rspec` file also contains a few flags that are not defaults but that
31
+ # users commonly want.
32
+ #
33
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
34
+ RSpec.configure do |config|
35
+ # rspec-expectations config goes here. You can use an alternate
36
+ # assertion/expectation library such as wrong or the stdlib/minitest
37
+ # assertions if you prefer.
38
+ config.expect_with :rspec do |expectations|
39
+ # This option will default to `true` in RSpec 4. It makes the `description`
40
+ # and `failure_message` of custom matchers include text for helper methods
41
+ # defined using `chain`, e.g.:
42
+ # be_bigger_than(2).and_smaller_than(4).description
43
+ # # => "be bigger than 2 and smaller than 4"
44
+ # ...rather than:
45
+ # # => "be bigger than 2"
46
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
47
+ end
48
+
49
+ # rspec-mocks config goes here. You can use an alternate test double
50
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
51
+ config.mock_with :rspec do |mocks|
52
+ # Prevents you from mocking or stubbing a method that does not exist on
53
+ # a real object. This is generally recommended, and will default to
54
+ # `true` in RSpec 4.
55
+ mocks.verify_partial_doubles = true
56
+ end
57
+
58
+ # The settings below are suggested to provide a good initial experience
59
+ # with RSpec, but feel free to customize to your heart's content.
60
+ =begin
61
+ # These two settings work together to allow you to limit a spec run
62
+ # to individual examples or groups you care about by tagging them with
63
+ # `:focus` metadata. When nothing is tagged with `:focus`, all examples
64
+ # get run.
65
+ config.filter_run :focus
66
+ config.run_all_when_everything_filtered = true
67
+
68
+ # Limits the available syntax to the non-monkey patched syntax that is recommended.
69
+ # For more details, see:
70
+ # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
71
+ # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
72
+ # - http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3#new__config_option_to_disable_rspeccore_monkey_patching
73
+ config.disable_monkey_patching!
74
+
75
+ # This setting enables warnings. It's recommended, but in some cases may
76
+ # be too noisy due to issues in dependencies.
77
+ config.warnings = true
78
+
79
+ # Many RSpec users commonly either run the entire suite or an individual
80
+ # file, and it's useful to allow more verbose output when running an
81
+ # individual spec file.
82
+ if config.files_to_run.one?
83
+ # Use the documentation formatter for detailed output,
84
+ # unless a formatter has already been configured
85
+ # (e.g. via a command-line flag).
86
+ config.default_formatter = 'doc'
87
+ end
88
+
89
+ # Print the 10 slowest examples and example groups at the
90
+ # end of the spec run, to help surface which specs are running
91
+ # particularly slow.
92
+ config.profile_examples = 10
93
+
94
+ # Run specs in random order to surface order dependencies. If you find an
95
+ # order dependency and want to debug it, you can fix the order by providing
96
+ # the seed, which is printed after each run.
97
+ # --seed 1234
98
+ config.order = :random
99
+
100
+ # Seed global randomization in this process using the `--seed` CLI option.
101
+ # Setting this allows you to use `--seed` to deterministically reproduce
102
+ # test failures related to randomization by passing the same `--seed` value
103
+ # as the one that triggered the failure.
104
+ Kernel.srand config.seed
105
+ =end
106
+ end
@@ -0,0 +1,14 @@
1
+ class DummyViruscheckRunner
2
+ attr_accessor :url
3
+
4
+ def initialize(url)
5
+ @url = url
6
+ end
7
+
8
+ def call
9
+ end
10
+
11
+ def result
12
+ 'Clean'
13
+ end
14
+ end
@@ -0,0 +1,9 @@
1
+ Struct.new('NullLogger') do
2
+ def info(*)
3
+ nil
4
+ end
5
+
6
+ def debug(*)
7
+ nil
8
+ end
9
+ end
@@ -0,0 +1,19 @@
1
+ module RequestResponseMocks
2
+ def server_response_list
3
+ stub_request(:get, "http://thisisa.test/wd/virus_scans")
4
+ .with(:headers => {'Accept'=>'application/json', 'Authorization'=>'Token abcdefg', 'User-Agent'=>'Ruby'})
5
+ .to_return(:status => 200, :body => yield, :headers => {})
6
+ end
7
+
8
+ def server_request_put(options)
9
+ stub_request(:put, "http://thisisa.test/wd/virus_scans/#{options.fetch(:id)}")
10
+ .with(body: %Q{{"virus_scan":{"scan_result":"#{options.fetch(:status)}"}}},
11
+ headers: {
12
+ 'Accept'=>'application/json',
13
+ 'Authorization'=>'Token abcdefg',
14
+ 'Content-Type'=>'application/json',
15
+ 'User-Agent'=>'Ruby'
16
+ })
17
+ .to_return(:status => 200, :body => yield, :headers => {})
18
+ end
19
+ end
@@ -0,0 +1,30 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'virus_scan_service/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "virus_scan_service"
8
+ spec.version = VirusScanService::VERSION
9
+ spec.authors = ["Tomas Valent"]
10
+ spec.email = ["equivalent@eq8.eu"]
11
+ spec.summary = %q{Servce gem for triggering Virus checks}
12
+ spec.description = 'Gem contains runner that will pull JSON request ' +
13
+ 'with list of files to scan, and run antivirus check ' +
14
+ 'on that file. After that runner will send JSON PUT ' +
15
+ 'request with scan results'
16
+ spec.homepage = ""
17
+ spec.license = "MIT"
18
+
19
+ spec.files = `git ls-files -z`.split("\x0")
20
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
21
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
22
+ spec.require_paths = ["lib"]
23
+
24
+ spec.add_development_dependency "bundler", "~> 1.6"
25
+ spec.add_development_dependency "rake"
26
+ spec.add_development_dependency "rspec", "~> 3"
27
+ spec.add_development_dependency "webmock"
28
+ spec.add_development_dependency "pry"
29
+ spec.add_development_dependency "codeclimate-test-reporter"
30
+ end
metadata ADDED
@@ -0,0 +1,168 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: virus_scan_service
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.8
5
+ platform: ruby
6
+ authors:
7
+ - Tomas Valent
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-02-11 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.6'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.6'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3'
55
+ - !ruby/object:Gem::Dependency
56
+ name: webmock
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: pry
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: codeclimate-test-reporter
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: Gem contains runner that will pull JSON request with list of files to
98
+ scan, and run antivirus check on that file. After that runner will send JSON PUT
99
+ request with scan results
100
+ email:
101
+ - equivalent@eq8.eu
102
+ executables: []
103
+ extensions: []
104
+ extra_rdoc_files: []
105
+ files:
106
+ - ".gitignore"
107
+ - ".rspec"
108
+ - ".travis.yml"
109
+ - Gemfile
110
+ - LICENSE.txt
111
+ - README.md
112
+ - Rakefile
113
+ - lib/virus_scan_service.rb
114
+ - lib/virus_scan_service/build_http.rb
115
+ - lib/virus_scan_service/courier.rb
116
+ - lib/virus_scan_service/default_logger.rb
117
+ - lib/virus_scan_service/kaspersky_runner.rb
118
+ - lib/virus_scan_service/kaspersky_runner/linux_executor.rb
119
+ - lib/virus_scan_service/kaspersky_runner/windows_executor.rb
120
+ - lib/virus_scan_service/version.rb
121
+ - spec/courier_spec.rb
122
+ - spec/fixtures/virus_result_clean.log
123
+ - spec/fixtures/virus_result_error.log
124
+ - spec/fixtures/virus_result_threat.log
125
+ - spec/kaspersky_runner/linux_executor_spec.rb
126
+ - spec/kaspersky_runner/windows_executor_spec.rb
127
+ - spec/kaspersky_runner_spec.rb
128
+ - spec/spec_helper.rb
129
+ - spec/support/dummy_viruscheck_runner.rb
130
+ - spec/support/null_logger.rb
131
+ - spec/support/request_response_mocks.rb
132
+ - virus_scan_service.gemspec
133
+ homepage: ''
134
+ licenses:
135
+ - MIT
136
+ metadata: {}
137
+ post_install_message:
138
+ rdoc_options: []
139
+ require_paths:
140
+ - lib
141
+ required_ruby_version: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ required_rubygems_version: !ruby/object:Gem::Requirement
147
+ requirements:
148
+ - - ">="
149
+ - !ruby/object:Gem::Version
150
+ version: '0'
151
+ requirements: []
152
+ rubyforge_project:
153
+ rubygems_version: 2.2.2
154
+ signing_key:
155
+ specification_version: 4
156
+ summary: Servce gem for triggering Virus checks
157
+ test_files:
158
+ - spec/courier_spec.rb
159
+ - spec/fixtures/virus_result_clean.log
160
+ - spec/fixtures/virus_result_error.log
161
+ - spec/fixtures/virus_result_threat.log
162
+ - spec/kaspersky_runner/linux_executor_spec.rb
163
+ - spec/kaspersky_runner/windows_executor_spec.rb
164
+ - spec/kaspersky_runner_spec.rb
165
+ - spec/spec_helper.rb
166
+ - spec/support/dummy_viruscheck_runner.rb
167
+ - spec/support/null_logger.rb
168
+ - spec/support/request_response_mocks.rb