ratonvirus 0.1.2 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f4ccf906781401ec2e9126acf96165d278b0dce5ecf2b10cfa71373490433d98
4
- data.tar.gz: dc56500ae5839a3987c47e96d05ceb9e4627c412edd953d0dbf1fc2c02069c52
3
+ metadata.gz: cc7e931fe59e66c07af39b8468730cb9f4c23e7680bd1a6110c1598cab73e328
4
+ data.tar.gz: 3bd536062079bb898448a87688563ec82abca7d04b075731f7a5a2002a6ea901
5
5
  SHA512:
6
- metadata.gz: 830a364f9355592158d7580c8755b576ea6a591951e48832ec6d83a308d5d5b0bdcaec3822d7dd47a5120eebc264c2037762202e0f53c42c3d10781d8c67f081
7
- data.tar.gz: 68562672a90dafe0d4705378cc94549d2cf64858987a14901b73607da595b747e72f0d45c52a06f9b1595aba6ab3118998ee5e23901ff438f31bdf6aa399852f
6
+ metadata.gz: 81b5a5ebb0c057392a0923d6c1cd292044845423d8459d0eec21c02d261ab581b0e6aaae23457ea28453fe7221154f2619f24d2b1b8202ce74e469ea446913b6
7
+ data.tar.gz: 3dab899e7141a7bdac10bb2bf4bc3c003744f8923fb4375e26cc79dc82b8efbfb6809bf221fc600daa07be78b5c7403e7dca80db23022f4a1d9944a6b3512b41
data/CHANGELOG.md CHANGED
@@ -1,3 +1,29 @@
1
+ # v0.3.1
2
+
3
+ Fixed:
4
+
5
+ - Issue related to the EICAR test scanner. Related to [#16](https://github.com/mainio/ratonvirus/pull/16)
6
+
7
+ # v0.3.0
8
+
9
+ Changed:
10
+
11
+ - Minimum Ruby version is now set to 2.5
12
+
13
+ Fixed:
14
+
15
+ - Issue related with scanning files with CarrierWave storage engine using remote storage engines such as Fog. Related
16
+ to [#9](https://github.com/mainio/ratonvirus/pull/9)
17
+
18
+ # v0.2.0
19
+
20
+ Support for Rails 6
21
+
22
+ The ActiveStorage storage engine has been updated and partly rewritten due to changes in its API. The new API introduces
23
+ a changes concept in the library which this update takes in to account. In the new API, the blobs will not get uploaded
24
+ to the storage service before the validations have been successful, which led to rethinking how this storage engine
25
+ works in Ratonvirus.
26
+
1
27
  # v0.1.2
2
28
 
3
29
  Fixed:
@@ -5,7 +31,6 @@ Fixed:
5
31
  - Backport: Issue related with scanning files with CarrierWave storage engine using remote storage engines such as Fog.
6
32
  Related to #9
7
33
 
8
-
9
34
  # v0.1.1
10
35
 
11
36
  Fixed:
data/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  Rails antivirus made easy.
4
4
  Developed by [Mainio Tech](https://www.mainiotech.fi/).
5
5
 
6
- [![Build Status](https://travis-ci.com/mainio/ratonvirus.svg?branch=master)](https://travis-ci.com/mainio/ratonvirus)
6
+ [![Build Status](https://github.com/mainio/ratonvirus/actions/workflows/ci_ratonvirus.yml/badge.svg)](https://github.com/mainio/ratonvirus/actions)
7
7
  [![codecov](https://codecov.io/gh/mainio/ratonvirus/branch/master/graph/badge.svg)](https://codecov.io/gh/mainio/ratonvirus)
8
8
 
9
9
  Ratonvirus allows your Rails application to rat on the viruses that your users
@@ -4,6 +4,8 @@ module Ratonvirus
4
4
  class Error < StandardError; end
5
5
 
6
6
  class InvalidError < Error; end
7
+
7
8
  class NotDefinedError < Error; end
9
+
8
10
  class NotImplementedError < Error; end
9
11
  end
@@ -11,8 +11,8 @@ module Ratonvirus
11
11
  end
12
12
  end
13
13
 
14
- attr_reader :config
15
- attr_reader :errors # Only available after `virus?` has been called.
14
+ # :errors - Only available after `virus?` has been called.
15
+ attr_reader :config, :errors
16
16
 
17
17
  def initialize(configuration = {})
18
18
  @config = default_config.merge!(configuration)
@@ -8,7 +8,13 @@ module Ratonvirus
8
8
  class Eicar < Base
9
9
  # SHA256 digest of the EICAR test file for virus testing
10
10
  # See: https://en.wikipedia.org/wiki/EICAR_test_file
11
- EICAR_SHA256 = "131f95c51cc819465fa1797f6ccacf9d494aaaff46fa3eac73ae63ffbdfd8267"
11
+ #
12
+ # This includes both, the default hash and a hash with for the file saved
13
+ # with a newline at the end of it.
14
+ EICAR_SHA256 = %w(
15
+ 275a021bbfb6489e54d471899f7db9d1663fc695ec2fe2a2c4538aabf651fd0f
16
+ 131f95c51cc819465fa1797f6ccacf9d494aaaff46fa3eac73ae63ffbdfd8267
17
+ ).freeze
12
18
 
13
19
  class << self
14
20
  def executable?
@@ -19,11 +25,11 @@ module Ratonvirus
19
25
  protected
20
26
 
21
27
  def run_scan(path)
22
- if !File.file?(path)
23
- errors << :antivirus_file_not_found
24
- else
28
+ if File.file?(path)
25
29
  sha256 = Digest::SHA256.file path
26
- errors << :antivirus_virus_detected if sha256 == EICAR_SHA256
30
+ errors << :antivirus_virus_detected if EICAR_SHA256.include?(sha256.to_s)
31
+ else
32
+ errors << :antivirus_file_not_found
27
33
  end
28
34
  rescue StandardError
29
35
  errors << :antivirus_client_error
@@ -3,21 +3,11 @@
3
3
  module Ratonvirus
4
4
  module Storage
5
5
  class ActiveStorage < Base
6
- def changed?(_record, _attribute)
7
- # With Active Storage we assume the record is always changed because
8
- # there is currently no way to know if the attribute has actually
9
- # changed.
10
- #
11
- # Calling record.changed? will not also work because it is not marked
12
- # as dirty in case the Active Storage attachment has changed.
13
- #
14
- # NOTE:
15
- # This should be changed in the future as the `attachment_changes` was
16
- # introduced to Rails by this commit:
17
- # https://github.com/rails/rails/commit/e8682c5bf051517b0b265e446aa1a7eccfd47bf7
18
- #
19
- # However, it is still not available in Rails 5.2.x.
20
- true
6
+ include Ratonvirus::Storage::Support::IoHandling
7
+
8
+ def changed?(record, attribute)
9
+ resource = record.public_send attribute
10
+ !resource.record.attachment_changes[resource.name].nil?
21
11
  end
22
12
 
23
13
  def accept?(resource)
@@ -25,72 +15,94 @@ module Ratonvirus
25
15
  resource.is_a?(::ActiveStorage::Attached::Many)
26
16
  end
27
17
 
28
- def process(resource, &_block)
18
+ def process(resource, &block)
29
19
  return unless block_given?
30
20
  return if resource.nil?
31
-
32
21
  return unless resource.attached?
33
22
 
34
- if resource.is_a?(::ActiveStorage::Attached::One)
35
- yield processable(resource.attachment) if resource.attachment
36
- elsif resource.is_a?(::ActiveStorage::Attached::Many)
37
- resource.attachments.each do |attachment|
38
- yield processable(attachment)
39
- end
23
+ change = resource.record.attachment_changes[resource.name]
24
+
25
+ case change
26
+ when ::ActiveStorage::Attached::Changes::CreateOne
27
+ handle_create_one(change, &block)
28
+ when ::ActiveStorage::Attached::Changes::CreateMany
29
+ handle_create_many(change, &block)
40
30
  end
41
31
  end
42
32
 
43
33
  def asset_path(asset, &block)
44
34
  return unless block_given?
45
- return if asset.nil?
46
- return unless asset.blob
35
+ return unless asset.is_a?(Array)
47
36
 
48
- blob_path asset.blob, &block
37
+ ext = asset[0].filename.extension_with_delimiter
38
+ case asset[1]
39
+ when ActionDispatch::Http::UploadedFile, Rack::Test::UploadedFile
40
+ # These files should be already locally stored but their permissions
41
+ # can prevent the virus scanner executable from accessing them.
42
+ # Therefore, a temporary file is created for them as well.
43
+ io_path(asset[1], ext, &block)
44
+ when Hash
45
+ io = asset[1].fetch(:io)
46
+ io_path(io, ext, &block) if io
47
+ when ::ActiveStorage::Blob
48
+ asset[1].open do |tempfile|
49
+ prepare_for_scanner tempfile.path
50
+ yield tempfile.path
51
+ end
52
+ end
49
53
  end
50
54
 
55
+ # This is actually only required for the dyncamic blob uploads but for
56
+ # consistency, it is handled for all the cases accordingly either by
57
+ # closing the tempfile of the upload which also removes the file when
58
+ # called with the bang method. For the IO references, the IO is closed
59
+ # which should trigger the file deletion by latest at the Rack or Ruby
60
+ # level during garbage collection. There is no guarantee that the file
61
+ # for which the IO was opened would be deleted beause the IO itself is
62
+ # not necessarily associated with an actual file.
51
63
  def asset_remove(asset)
52
- asset.purge
64
+ return unless asset.is_a?(Array)
65
+
66
+ case asset[1]
67
+ when ActionDispatch::Http::UploadedFile, Rack::Test::UploadedFile
68
+ # This removes the temp file from the system.
69
+ asset[1].tempfile.close!
70
+ when Hash
71
+ # No guarantee all references for the file are deleted.
72
+ io = asset[1].fetch(:io)
73
+ io.close
74
+ when ::ActiveStorage::Blob
75
+ # This deletes the dynamically uploaded blobs that might not be
76
+ # associated with any record at this point. This ensures the blobs are
77
+ # not left "hanging" in the storage system and the database in case
78
+ # automatic file deletion is applied.
79
+ asset[1].purge
80
+ end
53
81
  end
54
82
 
55
83
  private
56
84
 
57
- # This creates a local copy of the blob for the scanning process. A
58
- # local copy is needed for processing because the actual blob may be
59
- # stored at a remote storage service (such as Amazon S3), meaning it
60
- # cannot be otherwise processed locally.
61
- #
62
- # NOTE:
63
- # Later implementations of Active Storage have the blob.open method that
64
- # provides similar functionality. However, Rails 5.2.x still does not
65
- # include this functionality, so we need to take care of it ourselves.
66
- #
67
- # This was introduced in the following commit:
68
- # https://github.com/rails/rails/commit/ee21b7c2eb64def8f00887a9fafbd77b85f464f1
69
- #
70
- # SEE:
71
- # https://edgeguides.rubyonrails.org/active_storage_overview.html#downloading-files
72
- def blob_path(blob)
73
- tempfile = Tempfile.open(
74
- ["Ratonvirus", blob.filename.extension_with_delimiter],
75
- tempdir
76
- )
77
-
78
- begin
79
- tempfile.binmode
80
- blob.download { |chunk| tempfile.write(chunk) }
81
- tempfile.flush
82
- tempfile.rewind
83
-
84
- yield tempfile.path
85
- rescue StandardError
86
- return
87
- ensure
88
- tempfile.close!
85
+ def handle_create_one(change, &block)
86
+ yield_processable_from(change, &block)
87
+ end
88
+
89
+ def handle_create_many(change, &block)
90
+ change.send(:subchanges).each do |subchange|
91
+ yield_processable_from(subchange, &block)
89
92
  end
90
93
  end
91
94
 
92
- def tempdir
93
- Dir.tmpdir
95
+ def yield_processable_from(change, &_block)
96
+ attachable = change.attachable
97
+ return unless attachable
98
+ return if attachable.is_a?(::ActiveStorage::Blob)
99
+
100
+ # If the attachable is a string, it is a reference to an already
101
+ # existing blob. This can happen e.g. when the file blob is uploaded
102
+ # dynamically before the form is submitted.
103
+ attachable = change.attachment.blob if attachable.is_a?(String)
104
+
105
+ yield processable([change.attachment, attachable])
94
106
  end
95
107
  end
96
108
  end
@@ -37,8 +37,11 @@ module Ratonvirus
37
37
 
38
38
  def asset_remove(asset)
39
39
  path = asset.file.path
40
+ delete_dir = asset.file.is_a?(::CarrierWave::SanitizedFile)
40
41
  asset.remove!
41
42
 
43
+ return unless delete_dir
44
+
42
45
  # Remove the temp cache dir if it exists
43
46
  dir = File.dirname(path)
44
47
  FileUtils.remove_dir(dir) if File.directory?(dir)
@@ -88,37 +88,37 @@ module Ratonvirus
88
88
  def define_backend(backend_type, backend_subclass)
89
89
  class_eval <<-CODE, __FILE__, __LINE__ + 1
90
90
  # Getter for #{backend_type}
91
- def self.#{backend_type}
92
- @#{backend_type} ||= create_#{backend_type}
93
- end
91
+ def self.#{backend_type} # def self.foo
92
+ @#{backend_type} ||= create_#{backend_type} # @foo ||= create_foo
93
+ end # end
94
94
 
95
95
  # Setter for #{backend_type}
96
- def self.#{backend_type}=(#{backend_type}_value)
97
- set_backend(
98
- :#{backend_type},
99
- "#{backend_subclass}",
100
- #{backend_type}_value
101
- )
102
- end
96
+ def self.#{backend_type}=(#{backend_type}_value) # def self.foo=(foo_value)
97
+ set_backend( # set_backend(
98
+ :#{backend_type}, # :foo
99
+ "#{backend_subclass}", # "Foo"
100
+ #{backend_type}_value # foo_value
101
+ ) # )
102
+ end # end
103
103
 
104
104
  # Destroys the currently active #{backend_type}.
105
105
  # The #{backend_type} is re-initialized when the getter is called.
106
- def self.destroy_#{backend_type}
107
- @#{backend_type} = nil
108
- end
106
+ def self.destroy_#{backend_type} # def self.destroy_foo
107
+ @#{backend_type} = nil # @foo = nil
108
+ end # end
109
109
 
110
110
  # Creates a new backend instance
111
111
  # private
112
- def self.create_#{backend_type}
113
- if @#{backend_type}_defs.nil?
114
- raise NotDefinedError.new("#{backend_subclass} not defined!")
115
- end
116
-
117
- @#{backend_type}_defs[:klass].new(
118
- @#{backend_type}_defs[:config]
119
- )
120
- end
121
- private_class_method :create_#{backend_type}
112
+ def self.create_#{backend_type} # def self.create_foo
113
+ if @#{backend_type}_defs.nil? # if @foo_defs.nil?
114
+ raise NotDefinedError.new("#{backend_subclass} not defined!") # raise NotDefinedError.new("Foo not defined")
115
+ end # end
116
+ #
117
+ @#{backend_type}_defs[:klass].new( # @foo_defs[:klass].new(
118
+ @#{backend_type}_defs[:config] # @foo_defs[:config]
119
+ ) # )
120
+ end # end
121
+ private_class_method :create_#{backend_type} # private_class_method :create_foo
122
122
  CODE
123
123
  end
124
124
 
@@ -154,12 +154,13 @@ module Ratonvirus
154
154
  subtype = backend_value.class
155
155
  config = backend_value.config
156
156
  else
157
- if backend_value.is_a?(Array)
157
+ case backend_value
158
+ when Array
158
159
  subtype = backend_value.shift
159
160
  config = backend_value.shift || {}
160
161
 
161
162
  raise InvalidError, "Invalid #{backend_type} type: #{subtype}" unless subtype.is_a?(Symbol)
162
- elsif backend_value.is_a?(Symbol)
163
+ when Symbol
163
164
  subtype = backend_value
164
165
  config = {}
165
166
  else
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Ratonvirus
4
- VERSION = "0.1.2"
4
+ VERSION = "0.3.1"
5
5
  end
@@ -3,19 +3,17 @@
3
3
  namespace :ratonvirus do
4
4
  desc "Tests if the antivirus scanner is available and properly configured"
5
5
  task test: :environment do
6
- begin
7
- if Ratonvirus.scanner.available?
8
- puts "Ratonvirus correctly configured."
9
- else
10
- puts "Ratonvirus scanner is not available!"
11
- puts ""
12
- puts "Please refer to Ratonvirus documentation for proper configuration."
13
- end
14
- rescue StandardError
15
- puts "Ratonvirus scanner is not configured."
6
+ if Ratonvirus.scanner.available?
7
+ puts "Ratonvirus correctly configured."
8
+ else
9
+ puts "Ratonvirus scanner is not available!"
16
10
  puts ""
17
11
  puts "Please refer to Ratonvirus documentation for proper configuration."
18
12
  end
13
+ rescue StandardError
14
+ puts "Ratonvirus scanner is not configured."
15
+ puts ""
16
+ puts "Please refer to Ratonvirus documentation for proper configuration."
19
17
  end
20
18
 
21
19
  desc "Scans the given file through the antivirus scanner"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ratonvirus
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Antti Hukkanen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-03-05 00:00:00.000000000 Z
11
+ date: 2022-03-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -16,28 +16,28 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '5.0'
19
+ version: '6.0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '5.0'
26
+ version: '6.0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '12.3'
33
+ version: '13.0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '12.3'
40
+ version: '13.0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rspec
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -58,98 +58,98 @@ dependencies:
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: '3.0'
61
+ version: '4.0'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: '3.0'
68
+ version: '4.0'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: simplecov
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: 0.16.0
75
+ version: 0.18.0
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: 0.16.0
82
+ version: 0.18.0
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: activemodel
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
87
  - - "~>"
88
88
  - !ruby/object:Gem::Version
89
- version: '5.0'
89
+ version: '6.0'
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
- version: '5.0'
96
+ version: '6.0'
97
97
  - !ruby/object:Gem::Dependency
98
98
  name: activestorage
99
99
  requirement: !ruby/object:Gem::Requirement
100
100
  requirements:
101
101
  - - "~>"
102
102
  - !ruby/object:Gem::Version
103
- version: '5.0'
103
+ version: '6.0'
104
104
  type: :development
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
108
  - - "~>"
109
109
  - !ruby/object:Gem::Version
110
- version: '5.0'
110
+ version: '6.0'
111
111
  - !ruby/object:Gem::Dependency
112
112
  name: carrierwave
113
113
  requirement: !ruby/object:Gem::Requirement
114
114
  requirements:
115
115
  - - "~>"
116
116
  - !ruby/object:Gem::Version
117
- version: '1.2'
117
+ version: '2.1'
118
118
  type: :development
119
119
  prerelease: false
120
120
  version_requirements: !ruby/object:Gem::Requirement
121
121
  requirements:
122
122
  - - "~>"
123
123
  - !ruby/object:Gem::Version
124
- version: '1.2'
124
+ version: '2.1'
125
125
  - !ruby/object:Gem::Dependency
126
126
  name: rubocop
127
127
  requirement: !ruby/object:Gem::Requirement
128
128
  requirements:
129
129
  - - "~>"
130
130
  - !ruby/object:Gem::Version
131
- version: 0.64.0
131
+ version: 1.11.0
132
132
  type: :development
133
133
  prerelease: false
134
134
  version_requirements: !ruby/object:Gem::Requirement
135
135
  requirements:
136
136
  - - "~>"
137
137
  - !ruby/object:Gem::Version
138
- version: 0.64.0
138
+ version: 1.11.0
139
139
  - !ruby/object:Gem::Dependency
140
140
  name: rubocop-rspec
141
141
  requirement: !ruby/object:Gem::Requirement
142
142
  requirements:
143
143
  - - "~>"
144
144
  - !ruby/object:Gem::Version
145
- version: '1.32'
145
+ version: 2.2.0
146
146
  type: :development
147
147
  prerelease: false
148
148
  version_requirements: !ruby/object:Gem::Requirement
149
149
  requirements:
150
150
  - - "~>"
151
151
  - !ruby/object:Gem::Version
152
- version: '1.32'
152
+ version: 2.2.0
153
153
  description: Adds antivirus check capability for Rails applications.
154
154
  email:
155
155
  - antti.hukkanen@mainiotech.fi
@@ -193,7 +193,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
193
193
  requirements:
194
194
  - - ">="
195
195
  - !ruby/object:Gem::Version
196
- version: '0'
196
+ version: '2.5'
197
197
  required_rubygems_version: !ruby/object:Gem::Requirement
198
198
  requirements:
199
199
  - - ">="