vt_api 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 054fff89e1df6de6fcc9d8810d34a6ec8f96fb3e
4
+ data.tar.gz: 07a4e81bad492af92a4186aba30d3083de477f77
5
+ SHA512:
6
+ metadata.gz: 862429bae5fd52abb14e755c379ed72be1233632d9153bf491f6f71fbf56916ca4d4ab7de8014cfe2ef438f7b710530ba5bda1f18db7871b5e1ca96955fd4921
7
+ data.tar.gz: 36d82bbb40e0a0403b96427bd103b683f27a503de9485e487d3de5261af87b9c3df6274d357acf0cf3398494d773db2e1909fb3035a85ad60e34d2ea9f3ea6d7
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.idea/
2
+ /.bundle/
3
+ /.yardoc
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ sudo: false
3
+ language: ruby
4
+ cache: bundler
5
+ rvm:
6
+ - 2.5.1
7
+ before_install: gem install bundler -v 2.0.1
data/.yardopts ADDED
@@ -0,0 +1 @@
1
+ --verbose --no-private lib/**/*.rb - README.md LICENSE.md
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in vt_api.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,28 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ vt_api (0.1.1)
5
+ faraday (~> 0.15)
6
+ mimemagic (~> 0.3)
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ faraday (0.15.4)
12
+ multipart-post (>= 1.2, < 3)
13
+ mimemagic (0.3.3)
14
+ multipart-post (2.0.0)
15
+ rake (10.5.0)
16
+ yard (0.9.18)
17
+
18
+ PLATFORMS
19
+ x64-mingw32
20
+
21
+ DEPENDENCIES
22
+ bundler (~> 2.0)
23
+ rake (~> 10.0)
24
+ vt_api!
25
+ yard (~> 0.9)
26
+
27
+ BUNDLED WITH
28
+ 2.0.1
data/LICENSE.md ADDED
@@ -0,0 +1,170 @@
1
+ Apache License
2
+ ==============
3
+
4
+ _Version 2.0, January 2004_
5
+ _&lt;<http://www.apache.org/licenses/>&gt;_
6
+
7
+ ### Terms and Conditions for use, reproduction, and distribution
8
+
9
+ #### 1. Definitions
10
+
11
+ “License” shall mean the terms and conditions for use, reproduction, and
12
+ distribution as defined by Sections 1 through 9 of this document.
13
+
14
+ “Licensor” shall mean the copyright owner or entity authorized by the copyright
15
+ owner that is granting the License.
16
+
17
+ “Legal Entity” shall mean the union of the acting entity and all other entities
18
+ that control, are controlled by, or are under common control with that entity.
19
+ For the purposes of this definition, “control” means **(i)** the power, direct or
20
+ indirect, to cause the direction or management of such entity, whether by
21
+ contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or **(iii)** beneficial ownership of such entity.
23
+
24
+ “You” (or “Your”) shall mean an individual or Legal Entity exercising
25
+ permissions granted by this License.
26
+
27
+ “Source” form shall mean the preferred form for making modifications, including
28
+ but not limited to software source code, documentation source, and configuration
29
+ files.
30
+
31
+ “Object” form shall mean any form resulting from mechanical transformation or
32
+ translation of a Source form, including but not limited to compiled object code,
33
+ generated documentation, and conversions to other media types.
34
+
35
+ “Work” shall mean the work of authorship, whether in Source or Object form, made
36
+ available under the License, as indicated by a copyright notice that is included
37
+ in or attached to the work (an example is provided in the Appendix below).
38
+
39
+ “Derivative Works” shall mean any work, whether in Source or Object form, that
40
+ is based on (or derived from) the Work and for which the editorial revisions,
41
+ annotations, elaborations, or other modifications represent, as a whole, an
42
+ original work of authorship. For the purposes of this License, Derivative Works
43
+ shall not include works that remain separable from, or merely link (or bind by
44
+ name) to the interfaces of, the Work and Derivative Works thereof.
45
+
46
+ “Contribution” shall mean any work of authorship, including the original version
47
+ of the Work and any modifications or additions to that Work or Derivative Works
48
+ thereof, that is intentionally submitted to Licensor for inclusion in the Work
49
+ by the copyright owner or by an individual or Legal Entity authorized to submit
50
+ on behalf of the copyright owner. For the purposes of this definition,
51
+ “submitted” means any form of electronic, verbal, or written communication sent
52
+ to the Licensor or its representatives, including but not limited to
53
+ communication on electronic mailing lists, source code control systems, and
54
+ issue tracking systems that are managed by, or on behalf of, the Licensor for
55
+ the purpose of discussing and improving the Work, but excluding communication
56
+ that is conspicuously marked or otherwise designated in writing by the copyright
57
+ owner as “Not a Contribution.”
58
+
59
+ “Contributor” shall mean Licensor and any individual or Legal Entity on behalf
60
+ of whom a Contribution has been received by Licensor and subsequently
61
+ incorporated within the Work.
62
+
63
+ #### 2. Grant of Copyright License
64
+
65
+ Subject to the terms and conditions of this License, each Contributor hereby
66
+ grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
67
+ irrevocable copyright license to reproduce, prepare Derivative Works of,
68
+ publicly display, publicly perform, sublicense, and distribute the Work and such
69
+ Derivative Works in Source or Object form.
70
+
71
+ #### 3. Grant of Patent License
72
+
73
+ Subject to the terms and conditions of this License, each Contributor hereby
74
+ grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
75
+ irrevocable (except as stated in this section) patent license to make, have
76
+ made, use, offer to sell, sell, import, and otherwise transfer the Work, where
77
+ such license applies only to those patent claims licensable by such Contributor
78
+ that are necessarily infringed by their Contribution(s) alone or by combination
79
+ of their Contribution(s) with the Work to which such Contribution(s) was
80
+ submitted. If You institute patent litigation against any entity (including a
81
+ cross-claim or counterclaim in a lawsuit) alleging that the Work or a
82
+ Contribution incorporated within the Work constitutes direct or contributory
83
+ patent infringement, then any patent licenses granted to You under this License
84
+ for that Work shall terminate as of the date such litigation is filed.
85
+
86
+ #### 4. Redistribution
87
+
88
+ You may reproduce and distribute copies of the Work or Derivative Works thereof
89
+ in any medium, with or without modifications, and in Source or Object form,
90
+ provided that You meet the following conditions:
91
+
92
+ * **(a)** You must give any other recipients of the Work or Derivative Works a copy of
93
+ this License; and
94
+ * **(b)** You must cause any modified files to carry prominent notices stating that You
95
+ changed the files; and
96
+ * **(c)** You must retain, in the Source form of any Derivative Works that You distribute,
97
+ all copyright, patent, trademark, and attribution notices from the Source form
98
+ of the Work, excluding those notices that do not pertain to any part of the
99
+ Derivative Works; and
100
+ * **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any
101
+ Derivative Works that You distribute must include a readable copy of the
102
+ attribution notices contained within such NOTICE file, excluding those notices
103
+ that do not pertain to any part of the Derivative Works, in at least one of the
104
+ following places: within a NOTICE text file distributed as part of the
105
+ Derivative Works; within the Source form or documentation, if provided along
106
+ with the Derivative Works; or, within a display generated by the Derivative
107
+ Works, if and wherever such third-party notices normally appear. The contents of
108
+ the NOTICE file are for informational purposes only and do not modify the
109
+ License. You may add Your own attribution notices within Derivative Works that
110
+ You distribute, alongside or as an addendum to the NOTICE text from the Work,
111
+ provided that such additional attribution notices cannot be construed as
112
+ modifying the License.
113
+
114
+ You may add Your own copyright statement to Your modifications and may provide
115
+ additional or different license terms and conditions for use, reproduction, or
116
+ distribution of Your modifications, or for any such Derivative Works as a whole,
117
+ provided Your use, reproduction, and distribution of the Work otherwise complies
118
+ with the conditions stated in this License.
119
+
120
+ #### 5. Submission of Contributions
121
+
122
+ Unless You explicitly state otherwise, any Contribution intentionally submitted
123
+ for inclusion in the Work by You to the Licensor shall be under the terms and
124
+ conditions of this License, without any additional terms or conditions.
125
+ Notwithstanding the above, nothing herein shall supersede or modify the terms of
126
+ any separate license agreement you may have executed with Licensor regarding
127
+ such Contributions.
128
+
129
+ #### 6. Trademarks
130
+
131
+ This License does not grant permission to use the trade names, trademarks,
132
+ service marks, or product names of the Licensor, except as required for
133
+ reasonable and customary use in describing the origin of the Work and
134
+ reproducing the content of the NOTICE file.
135
+
136
+ #### 7. Disclaimer of Warranty
137
+
138
+ Unless required by applicable law or agreed to in writing, Licensor provides the
139
+ Work (and each Contributor provides its Contributions) on an “AS IS” BASIS,
140
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
141
+ including, without limitation, any warranties or conditions of TITLE,
142
+ NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
143
+ solely responsible for determining the appropriateness of using or
144
+ redistributing the Work and assume any risks associated with Your exercise of
145
+ permissions under this License.
146
+
147
+ #### 8. Limitation of Liability
148
+
149
+ In no event and under no legal theory, whether in tort (including negligence),
150
+ contract, or otherwise, unless required by applicable law (such as deliberate
151
+ and grossly negligent acts) or agreed to in writing, shall any Contributor be
152
+ liable to You for damages, including any direct, indirect, special, incidental,
153
+ or consequential damages of any character arising as a result of this License or
154
+ out of the use or inability to use the Work (including but not limited to
155
+ damages for loss of goodwill, work stoppage, computer failure or malfunction, or
156
+ any and all other commercial damages or losses), even if such Contributor has
157
+ been advised of the possibility of such damages.
158
+
159
+ #### 9. Accepting Warranty or Additional Liability
160
+
161
+ While redistributing the Work or Derivative Works thereof, You may choose to
162
+ offer, and charge a fee for, acceptance of support, warranty, indemnity, or
163
+ other liability obligations and/or rights consistent with this License. However,
164
+ in accepting such obligations, You may act only on Your own behalf and on Your
165
+ sole responsibility, not on behalf of any other Contributor, and only if You
166
+ agree to indemnify, defend, and hold each Contributor harmless for any liability
167
+ incurred by, or claims asserted against, such Contributor by reason of your
168
+ accepting any such warranty or additional liability.
169
+
170
+ _END OF TERMS AND CONDITIONS_
data/README.md ADDED
@@ -0,0 +1,61 @@
1
+ > __DISCLAIMER__: Tests for this project are not complete yet, coming soon!
2
+
3
+ # VtApi - VirusTotal API implementation
4
+
5
+ [![Gem Version](https://badge.fury.io/rb/vt_api.svg)](https://badge.fury.io/rb/vt_api)
6
+
7
+ VtApi is a Ruby binding to fresh VirusTotal 2.0 API.
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ ```ruby
14
+ gem 'vt_api'
15
+ ```
16
+
17
+ And then execute:
18
+
19
+ $ bundle
20
+
21
+ Or install it yourself as:
22
+
23
+ $ gem install vt_api
24
+
25
+ ## Supported API versions
26
+
27
+ * [VirusTotal Public API 2.0](https://developers.virustotal.com/v2.0/reference#public-vs-private-api)
28
+
29
+ More versions (i.e. VT Private API) are on the way.
30
+
31
+ ## Usage
32
+
33
+ Basic usage:
34
+
35
+ ```ruby
36
+ require 'vt_api'
37
+
38
+ VtApi.token = 'YOUR_TOKEN'
39
+
40
+ file = VtApi::ApiV2::File.report resource: 'fca0b14b72c426a67f06a771c9e2d67e6883972e' # => #<VtApi::ApiV2::File ...>
41
+ scans = file.scans # => [#<VtApi::ApiV2::File::AvResult ...>, ...]
42
+
43
+ scan_id = VtApi::ApiV2::File.schedule_scan file: 'foobar.txt' # => "92605f5aa57e20475a893cd63d998ffdafca8b1c6142fca225ea5638a2437fe6-2139190"
44
+
45
+ url = VtApi::ApiV2::URL.report resource: 'https://bitbucket.org/kkremen/vt_api' # => #<VtApi::ApiV2::URL ...>
46
+
47
+ comments = VtApi::ApiV2::Comments.get resource: 'fca0b14b72c426a67f06a771c9e2d67e6883972e' # => [#<VtApi::ApiV2::Comments::Comment ...>, ...]
48
+ comments = VtApi::ApiV2::Comments.put resource: 'fca0b14b72c426a67f06a771c9e2d67e6883972e', text: 'Lorem ipsum dolor sit amet...' # => true
49
+ ```
50
+ You can change HTTP adapter by using ```VtApi.configure```:
51
+
52
+ ```ruby
53
+ VtApi.configure do |opts|
54
+ opts.token = 'YOUR_TOKEN'
55
+ opts.adapter = :net_http # see Faraday gem for more adapters.
56
+ end
57
+ ```
58
+
59
+ ## License
60
+
61
+ The gem is available as open source under the terms of the [Apache 2.0 Public License](http://www.apache.org/licenses/LICENSE-2.0).
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ # require "rake/testtask"
3
+
4
+ # Rake::TestTask.new(:test) do |t|
5
+ # t.libs << "test"
6
+ # t.libs << "lib"
7
+ # t.test_files = FileList["test/**/*_test.rb"]
8
+ # end
9
+ #
10
+ # task :default => :test
@@ -0,0 +1,33 @@
1
+ require 'ostruct'
2
+
3
+ module VtApi
4
+ # Default options for VT API, common for all versions
5
+ DEFAULT_OPTIONS = OpenStruct.new(
6
+ token: nil,
7
+ adapter: :net_http
8
+ )
9
+
10
+ # Get current options
11
+ #
12
+ # @return [OpenStruct]
13
+ def self.options
14
+ @options ||= DEFAULT_OPTIONS
15
+ end
16
+
17
+ # Configure VtApi.
18
+ #
19
+ # @yield [opts] Configuration block.
20
+ # @yieldparam opts Options object.
21
+ # @return [Object] Block result.
22
+ def self.configure
23
+ yield options
24
+ end
25
+
26
+ # Set VtApi token (apikey).
27
+ #
28
+ # @param [String] token
29
+ # @return [String] Token.
30
+ def self.token=(token)
31
+ options[:token] = token
32
+ end
33
+ end
@@ -0,0 +1,59 @@
1
+ module VtApi
2
+ module ApiV2
3
+ # VT API 2.0 for comments
4
+ module Comments
5
+ # Representation of a comment
6
+ class Comment
7
+ attr_reader :text, :date_token, :date
8
+
9
+ # Creates new Comment object from API data.
10
+ # It is not recommended to use this directly.
11
+ # Refer to API methods instead.
12
+ #
13
+ # @see Comments.get
14
+ # @see Comments.put
15
+ #
16
+ # @param [String] date_token Date token as present in API (can be used in <code>Comments.get</code>).
17
+ # @param [String] text Comment text
18
+ def initialize(date_token, text)
19
+ @date_token = date_token
20
+ @date = DateTime.parse date_token
21
+ @text = text
22
+ end
23
+ end
24
+
25
+ # @see https://developers.virustotal.com/v2.0/reference#comments-get
26
+ # @see Comment#date_token
27
+ #
28
+ # @param [String] resource
29
+ # @param [String] before Must be in format of <code>date_token</code>. Refer to API docs for more info.
30
+ # @return [Array<Comment>] Array of parsed comments.
31
+ def self.get(resource:, before:)
32
+ resp = ApiV2.provider.request 'comments.get', apikey: VtApi.options.token, resource: resource, before: before
33
+
34
+ parse_comments resp
35
+ end
36
+
37
+ # @see https://developers.virustotal.com/v2.0/reference#comments-put
38
+ #
39
+ # @param [String] resource
40
+ # @param [String] text Comment text string.
41
+ # @return [Boolean] True if comment put successfully ('response_code' equals 1), false otherwise.
42
+ def self.put(resource:, text:)
43
+ resp = ApiV2.provider.request 'comments.put', apikey: VtApi.options.token, resource: resource, text: text
44
+
45
+ resp.response_code == 1
46
+ end
47
+
48
+ private
49
+
50
+ def self.parse_comments(api_resp)
51
+ if api_resp.response_code.nil? or api_resp.response_code != 1
52
+ []
53
+ else
54
+ api_resp.comments.map {|comment| Comment.new comment.date, comment.text}
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,156 @@
1
+ require 'date'
2
+ require 'pathname'
3
+
4
+ require 'faraday/upload_io'
5
+ require 'mimemagic'
6
+
7
+ require_relative '../config'
8
+ require_relative '../../internal/api_provider'
9
+ require_relative '../v2'
10
+
11
+ module VtApi
12
+ module ApiV2
13
+ # Class that represents VT Public API 2.0 for files.
14
+ class File
15
+
16
+ # Representation of particular AV result on file.
17
+ class AvResult
18
+ attr_reader :av_name, :triggered, :version, :threat_name, :update
19
+
20
+ # noinspection RubyResolve
21
+ def initialize(av_name, scan_obj)
22
+ @av_name = av_name
23
+ @triggered = scan_obj.detected
24
+ @version = scan_obj.version
25
+ @threat_name = scan_obj.result
26
+ @update = scan_obj.update
27
+ end
28
+ end
29
+
30
+ # Existing ID types
31
+ # @see #id
32
+ ID_TYPES = %w(scan_id resource_id md5 sha1 sha256)
33
+
34
+ class << self
35
+ # @see https://developers.virustotal.com/v2.0/reference#file-report
36
+ #
37
+ # @param [String] resource
38
+ # @return [File] File report if present, <code>nil</code> otherwise.
39
+ def report(resource:)
40
+ resp = ApiV2.provider.request 'file.report', apikey: VtApi.options.token, resource: resource
41
+ from_response resp
42
+ end
43
+
44
+ # @see https://developers.virustotal.com/v2.0/reference#file-rescan
45
+ #
46
+ # @param [String] resource
47
+ # @return [String] Scan ID.
48
+ def schedule_rescan(resource:)
49
+ resp = ApiV2.provider.request 'file.rescan', apikey: VtApi.options.token, resource: resource
50
+ resp.scan_id
51
+ end
52
+
53
+ # @see https://developers.virustotal.com/v2.0/reference#file-scan
54
+ #
55
+ # @param [String|Pathname|UploadIO] file
56
+ # @return [String] Scan ID.
57
+ def schedule_scan(file:)
58
+ if file.kind_of? String or file.kind_of? Pathname
59
+ file = file_to_io file
60
+ end
61
+
62
+ resp = ApiV2.provider.request 'file.scan', apikey: VtApi.options.token, file: file
63
+ resp.scan_id
64
+ end
65
+
66
+ private
67
+
68
+ def file_to_io(file)
69
+ file = ::File.realpath file
70
+ file = ::File.absolute_path file
71
+
72
+ mime = MimeMagic.by_path file
73
+
74
+ UploadIO.new file, mime
75
+ end
76
+ end
77
+
78
+ # Shorthand for #initialize.
79
+ #
80
+ # @see #initialize
81
+ def self.from_response(api_resp)
82
+ if api_resp.response_code.nil? or api_resp.response_code < 1
83
+ nil
84
+ else
85
+ report = self.new api_resp
86
+
87
+ report
88
+ end
89
+ end
90
+
91
+ attr_reader :scan_date, :permalink, :scans, :scan_count, :trigger_count
92
+
93
+ # Initializes new object from VT API response.
94
+ # @note Direct creation of object cas cause errors since it doesn't contain any validity checks. Use predefined API method bindings instead.
95
+ #
96
+ # @see .report
97
+ # @see .schedule_scan
98
+ # @see .schedule_rescan
99
+ #
100
+ # @param [OpenStruct] api_resp
101
+ def initialize(api_resp)
102
+ load_ids! api_resp
103
+ load_meta! api_resp
104
+ load_scans! api_resp
105
+ end
106
+
107
+ # @return [Fixnum]
108
+ def threat_level
109
+ @trigger_count.to_f / @scan_count
110
+ end
111
+
112
+ # Get file identifier.
113
+ # Since VT API offers many ways to identify the file, you can supply ID type to get specific file.
114
+ #
115
+ # @see ID_TYPES
116
+ #
117
+ # @param [Symbol|String] type
118
+ # @return [String] ID string of specified type.
119
+ def id(type = :id)
120
+ unless ID_TYPES.include? type.to_sym
121
+ raise ArgumentError, "There is no such id type (#{type}) in VT API 2.0"
122
+ end
123
+
124
+ @ids[type.to_sym]
125
+ end
126
+
127
+ private
128
+
129
+ # noinspection RubyResolve
130
+ def load_ids!(api_resp)
131
+ @ids = OpenStruct.new ({
132
+ scan_id: api_resp.scan_id,
133
+ md5: api_resp.md5,
134
+ sha1: api_resp.sha1,
135
+ sha256: api_resp.sha256,
136
+ resource_id: api_resp.resource
137
+ })
138
+ end
139
+
140
+ def load_meta!(api_resp)
141
+ @scan_date = DateTime.parse(api_resp.scan_date)
142
+ @permalink = URI(api_resp.permalink)
143
+ end
144
+
145
+ # noinspection RubyResolve
146
+ def load_scans!(api_resp)
147
+ @scan_count = api_resp.total
148
+ @trigger_count = api_resp.positives
149
+
150
+ @scans = api_resp.scans.to_h.to_a.map do |key, value|
151
+ [key, AvResult.new(key.to_s, value)]
152
+ end.to_h
153
+ end
154
+ end
155
+ end
156
+ end
@@ -0,0 +1,78 @@
1
+ module VtApi
2
+ module ApiV2
3
+ # Class that represents URL scan report.
4
+ class URL
5
+ class << self
6
+
7
+ # @see https://developers.virustotal.com/v2.0/reference#url-report
8
+ #
9
+ # @param [String] resource
10
+ # @param [Boolean] scan Shedule URL scan if it is not present in system.
11
+ # @return [URL] URL report object is present, <code>nil</code> otherwise.
12
+ def report(resource:, scan: false)
13
+ resp = ApiV2.provider.request 'url.report', apikey: VtApi.options.token, resource: resource, schedule_scan: (scan ? 1 : 0)
14
+ pp resp
15
+ from_response resp
16
+ end
17
+
18
+ # @see https://developers.virustotal.com/v2.0/reference#url-scan
19
+ #
20
+ # @param [String] url
21
+ # @return [String] Scheduled scan ID.
22
+ def schedule_scan(url:)
23
+ resp = ApiV2.provider.request 'url.report', apikey: VtApi.options.token, url: url
24
+ resp.scan_id
25
+ end
26
+
27
+ end
28
+
29
+ # Shorthand for #initialize.
30
+ #
31
+ # @see #initialize
32
+ def self.from_response(api_resp)
33
+ if api_resp.response_code.nil? or api_resp.response_code < 1
34
+ nil
35
+ else
36
+ report = self.new api_resp
37
+ report
38
+ end
39
+ end
40
+
41
+ attr_reader :id, :url, :scan_date, :permalink, :filescan_id
42
+
43
+ # Initializes new object from VT API response.
44
+ # @note Direct creation of object cas cause errors since it doesn't contain any validity checks. Use predefined API method bindings instead.
45
+ #
46
+ # @see .report
47
+ # @see .schedule_scan
48
+ #
49
+ # @param [OpenStruct] api_resp
50
+ def initialize(api_resp)
51
+ load_id!(api_resp)
52
+ load_meta!(api_resp)
53
+ load_scans!(api_resp)
54
+ end
55
+
56
+ private
57
+
58
+ # noinspection RubyResolve
59
+ def load_scans!(api_resp)
60
+ @scan_count = api_resp.total
61
+ @trigger_count = api_resp.positives
62
+ @scans = api_resp.scans
63
+ end
64
+
65
+ def load_meta!(api_resp)
66
+ @scan_date = api_resp.scan_date
67
+ @permalink = api_resp.permalink
68
+ @filescan_id = api_resp.filescan_id
69
+ end
70
+
71
+ # noinspection RubyResolve
72
+ def load_id!(api_resp)
73
+ @id = api_resp.scan_id
74
+ @url = api_resp.url
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,15 @@
1
+ require_relative 'config'
2
+ require_relative 'v2/file'
3
+ require_relative 'v2/url'
4
+
5
+ module VtApi
6
+ module ApiV2
7
+
8
+ # Get ApiProvider singleton configured for VT Public API 2.0.
9
+ #
10
+ # @return [ApiProvider]
11
+ def self.provider
12
+ @provider ||= ApiProvider.new Versions::API_V2, VtApi.options.adapter
13
+ end
14
+ end
15
+ end
data/lib/vt_api/api.rb ADDED
@@ -0,0 +1,2 @@
1
+ require_relative 'api/config'
2
+ require_relative 'api/v2'
@@ -0,0 +1,65 @@
1
+ require 'faraday'
2
+
3
+ require 'json'
4
+ require 'ostruct'
5
+
6
+ require_relative 'versions/default'
7
+ require_relative '../../vt_api'
8
+
9
+ module VtApi
10
+ # API intercation class.
11
+ #
12
+ # Uses [Faraday] for HTTP interaction.
13
+ class ApiProvider
14
+
15
+ # Create new ApiProvider.
16
+ #
17
+ # @param [ApiVersion] api_version API interface to use.
18
+ # @param [Object] adapter Faraday HTTP adapter.
19
+ # @return [Object]
20
+ def initialize(api_version = Versions::DEFAULT, adapter = :net_http)
21
+ unless api_version.kind_of? ApiVersion
22
+ raise ArgumentError, "Invalid API interface supplied! Must be subclass of 'ApiVersion', got #{api_version.class} instead"
23
+ end
24
+
25
+ @api = api_version
26
+ @connection = Faraday.new @api.base_url do |conn|
27
+ conn.request :multipart
28
+ conn.request :url_encoded
29
+
30
+ conn.adapter adapter
31
+ end
32
+ end
33
+
34
+ # Performs a request to given API method.
35
+ # Requested method must be described in API interface.
36
+ #
37
+ # @raise [UndefinedMethodError]
38
+ # @raise [MissingParametersError]
39
+ # @raise [ApiError]
40
+ #
41
+ # @param [String] method_name
42
+ # @param [Hash] params
43
+ # @return [OpenStruct] Method result object.
44
+ def request(method_name, params = {})
45
+ unless @api.endpoint? method_name
46
+ raise UndefinedMethodError, "Endpoint '#{method_name}' not found in '#{@api.version}' API interface."
47
+ end
48
+
49
+ endpoint = @api.endpoint method_name
50
+
51
+ unless endpoint.params_valid? (params)
52
+ raise MissingParametersError, "Missed parameters #{endpoint.missing_params(params).to_s} required by endpoint '#{method_name}'."
53
+ end
54
+
55
+ result = @connection.public_send(endpoint.method, endpoint.uri, params, {})
56
+
57
+ if @api.error? result.status
58
+ raise ApiError, "#{@api.version} error: #{@api.error(result.status)}"
59
+ end
60
+
61
+ # noinspection RubyResolve
62
+ JSON.parse result.body, object_class: OpenStruct
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,51 @@
1
+ module VtApi
2
+
3
+ # @abstract Base class for API interfaces.
4
+ class ApiVersion
5
+ # Get API base URL.
6
+ #
7
+ # @return [String]
8
+ def base_url
9
+ ''
10
+ end
11
+
12
+ # API interface name/version.
13
+ #
14
+ # @return [String]
15
+ def version
16
+ nil
17
+ end
18
+
19
+ # Get API HTTP-code description.
20
+ #
21
+ # @param [Integer] http_code
22
+ # @return [String] Error description.
23
+ def error(http_code)
24
+ nil
25
+ end
26
+
27
+ # Check whether API HTTP-code means error.
28
+ #
29
+ # @param [Integer] http_code
30
+ # @return [Boolean] Error description.
31
+ def error?(http_code)
32
+ false
33
+ end
34
+
35
+ # Get API method endpoint interface.
36
+ #
37
+ # @param [String] method API method name to be called.
38
+ # @return [VtApi::Endpoint]
39
+ def endpoint(method)
40
+ nil
41
+ end
42
+
43
+ # Check whether given method is defined in interface.
44
+ #
45
+ # @param [String] method Method name.
46
+ # @return [Boolean]
47
+ def endpoint?(method)
48
+ false
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,69 @@
1
+ require 'faraday/connection'
2
+
3
+ module VtApi
4
+ # API endpoint interface class.
5
+ class Endpoint
6
+ attr_accessor :uri, :method
7
+
8
+ # Initialize new endpoint.
9
+ #
10
+ # Interface parameters are described as in example below.
11
+ # @example
12
+ # {
13
+ # foo: true, # this describes required parameter
14
+ # bar: false # this describes a parameter that can be omitted
15
+ # }
16
+ #
17
+ # @param [Object] uri Relative URL of the endpoint.
18
+ # @param [Object] method HTTP method to be used for the endpoint.
19
+ # @param [Object] params Endpoint parameters interface.
20
+ def initialize(uri, method, params = {})
21
+ @uri = uri
22
+ @method = method
23
+ @params = params
24
+ end
25
+
26
+
27
+ # Check whether given parameters match endpoint interface.
28
+ #
29
+ # @param [Hash] passed_params
30
+ # @return [Boolean]
31
+ def params_valid?(passed_params)
32
+ @params.each do |param, required|
33
+ if required and passed_params[param].nil?
34
+ return false
35
+ end
36
+ end
37
+
38
+ true
39
+ end
40
+
41
+ # Get a list of missing parameters.
42
+ #
43
+ # @param [Hash] passed_params
44
+ # @return [Array]
45
+ def missing_params(passed_params)
46
+ missing = []
47
+
48
+ @params.each do |param, required|
49
+ if required and passed_params[param].nil?
50
+ missing << param
51
+ end
52
+ end
53
+
54
+ missing
55
+ end
56
+
57
+ private
58
+
59
+ # Remove redundant slashes from URL.
60
+ # @example
61
+ # clean_url 'http://google.com////search' # => 'http://google.com/search'
62
+ #
63
+ # @param [String] uri
64
+ # @return [String]
65
+ def clean_uri(uri)
66
+ uri.gsub %r{([^:])[\/]+}, '\1/'
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,130 @@
1
+ require_relative '../api_version'
2
+ require_relative '../endpoint'
3
+
4
+ module VtApi
5
+ module Versions
6
+
7
+ # VirusTotal Public API 2.0 singleton interface.
8
+ # Describes all available API methods (endpoints).
9
+ class ApiV2 < ApiVersion
10
+
11
+ # Root URI for all endpoints.
12
+ BASE_URI = 'https://virustotal.com/vtapi/v2/'
13
+
14
+ # VirusTotal Public API v2.0 interface description.
15
+ ENDPOINTS = {
16
+ 'file.report': Endpoint.new('file/report', :get, {
17
+ apikey: true,
18
+ resource: true
19
+ }),
20
+ 'file.scan': Endpoint.new('file/scan', :post, {
21
+ apikey: true,
22
+ file: true
23
+ }),
24
+ 'file.rescan': Endpoint.new('file/rescan', :post, {
25
+ apikey: true,
26
+ resource: true
27
+ }),
28
+ 'url.report': Endpoint.new('url/report', :get, {
29
+ apikey: true,
30
+ resource: true,
31
+ schedule_scan: false
32
+ }),
33
+ 'url.scan': Endpoint.new('url/scan', :post, {
34
+ apikey: true,
35
+ url: true
36
+ }),
37
+ 'domain.report': Endpoint.new('domain/report', :get, {
38
+ apikey: true,
39
+ domain: true
40
+ }),
41
+ 'ip-address.report': Endpoint.new('ip-address/report', :get, {
42
+ apikey: true,
43
+ domain: true
44
+ }),
45
+ 'comments.get': Endpoint.new('comments/get', :get, {
46
+ apikey: true,
47
+ resource: true,
48
+ before: false
49
+ }),
50
+ 'comments.put': Endpoint.new('comments/put', :post, {
51
+ apikey: true,
52
+ resource: true,
53
+ comment: true
54
+ }),
55
+ }
56
+
57
+ # List of possible HTTP error codes with descriptions
58
+ ERRORS = {
59
+ 204 => "Request rate limit exceeded.",
60
+ 400 => "Invalid arguments.",
61
+ 403 => "Access denied.",
62
+ 404 => "Endpoint not found."
63
+ }
64
+
65
+ class << self
66
+ # Get interface instance.
67
+ #
68
+ # @return [VtApi::Versions::ApiV2]
69
+ def instance
70
+ @instance ||= self.new
71
+ end
72
+ end
73
+
74
+ # Get API method endpoint interface.
75
+ #
76
+ # @param [String] method API method name to be called.
77
+ # @return [VtApi::Endpoint]
78
+ def endpoint(method)
79
+ ENDPOINTS[method.to_sym] unless ENDPOINTS[method.to_sym].nil?
80
+ end
81
+
82
+ # Check whether given method is defined in interface.
83
+ #
84
+ # @param [String] method Method name.
85
+ # @return [Boolean]
86
+ def endpoint?(method)
87
+ not endpoint(method).nil?
88
+ end
89
+
90
+ # Get API HTTP-code description.
91
+ #
92
+ # @param [Integer] http_code
93
+ # @return [String] Error description.
94
+ def error(http_code)
95
+ ERRORS[http_code] unless ERRORS[http_code].nil?
96
+ end
97
+
98
+ # Check whether API HTTP-code means error.
99
+ #
100
+ # @param [Integer] http_code
101
+ # @return [Boolean] Error description.
102
+ def error?(http_code)
103
+ not error(http_code).nil?
104
+ end
105
+
106
+ # Get API base URL.
107
+ #
108
+ # @return [String]
109
+ def base_url
110
+ BASE_URI
111
+ end
112
+
113
+ # API interface name/version.
114
+ #
115
+ # @return [String]
116
+ def version
117
+ 'VTAPI-2.0'
118
+ end
119
+
120
+ private
121
+
122
+ # Locked constructor.
123
+ def initialize
124
+ end
125
+ end
126
+
127
+ # Shorthand for <code>ApiV2.instance</code>
128
+ API_V2 = ApiV2.instance
129
+ end
130
+ end
@@ -0,0 +1,11 @@
1
+ require_relative 'api_v2'
2
+
3
+ module VtApi
4
+ module Versions
5
+
6
+ # Default API version used by ApiProvider
7
+ #
8
+ # @see VtApi::ApiProvider
9
+ DEFAULT = API_V2
10
+ end
11
+ end
@@ -0,0 +1,2 @@
1
+ require_relative 'versions/api_v2'
2
+ require_relative 'versions/default'
@@ -0,0 +1,4 @@
1
+ require_relative 'internal/api_provider'
2
+ require_relative 'internal/api_version'
3
+ require_relative 'internal/endpoint'
4
+ require_relative 'internal/versions'
@@ -0,0 +1,3 @@
1
+ module VtApi
2
+ VERSION = "0.1.2"
3
+ end
data/lib/vt_api.rb ADDED
@@ -0,0 +1,28 @@
1
+ require_relative 'vt_api/version'
2
+ require_relative 'vt_api/internal'
3
+ require_relative 'vt_api/api'
4
+
5
+ module VtApi
6
+
7
+ # Error is thrown when some of required request params are missing
8
+ #
9
+ # @see Endpoint#params_valid
10
+ # @see ApiProvider#request
11
+ class MissingParametersError < ArgumentError
12
+ end
13
+
14
+ # Error is thrown when requested method could not be found in interface.
15
+ #
16
+ # @see ApiVersion#endpoint?
17
+ # @see ApiProvider#request
18
+ class UndefinedMethodError < NoMethodError
19
+ end
20
+
21
+ # Error is thrown when API returns HTTP code marked by interface as error.
22
+ #
23
+ # @see ApiVersion#error?
24
+ # @see ApiVersion#error
25
+ # @see ApiProvider#request
26
+ class ApiError < RuntimeError
27
+ end
28
+ end
data/vt_api.gemspec ADDED
@@ -0,0 +1,45 @@
1
+ lib = File.expand_path('../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'vt_api/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'vt_api'
7
+ spec.version = VtApi::VERSION
8
+ spec.authors = [%{Karl 'Charon' Meinkopf}]
9
+ spec.email = ['kremen.karl@yandex.com']
10
+
11
+ spec.summary = %{VirusTotal API bindings for Ruby}
12
+ spec.description = %{}
13
+ spec.homepage = 'http://example.com/'
14
+ spec.license = ''
15
+
16
+ if spec.respond_to?(:metadata)
17
+ spec.metadata['allowed_push_host'] = 'https://rubygems.org/'
18
+
19
+ spec.metadata['homepage_uri'] = spec.homepage
20
+ spec.metadata['source_code_uri'] = 'http://example.com/'
21
+ spec.metadata['changelog_uri'] = 'http://example.com'
22
+ else
23
+ raise 'RubyGems 2.0 or newer is required to protect against public gem pushes.'
24
+ end
25
+
26
+ spec.files = []
27
+
28
+ Dir.chdir(File.expand_path('..', __FILE__)) do
29
+ spec.files += `git ls-files -z`.split(0.chr).reject {|f| f.match(%r{^(test|spec|features)/})}
30
+ end
31
+
32
+
33
+ spec.bindir = 'exe'
34
+ spec.executables = spec.files.grep(%r{^exe/}) {|f| File.basename(f)}
35
+ spec.require_paths = ['lib']
36
+
37
+ spec.add_development_dependency 'bundler', '~> 2.0'
38
+ spec.add_development_dependency 'rake', '~> 10.0'
39
+ spec.add_development_dependency 'yard', '~> 0.9'
40
+ # spec.add_development_dependency 'rspec', '~> 3.8'
41
+ # spec.add_development_dependency 'pry', '~> 0.12'
42
+ # spec.add_development_dependency 'webmock', '~> 3.5'
43
+ spec.add_dependency 'faraday', '~> 0.15'
44
+ spec.add_dependency 'mimemagic', '~> 0.3'
45
+ end
metadata ADDED
@@ -0,0 +1,142 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: vt_api
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.2
5
+ platform: ruby
6
+ authors:
7
+ - Karl 'Charon' Meinkopf
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2019-03-17 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: '2.0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: yard
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.9'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.9'
55
+ - !ruby/object:Gem::Dependency
56
+ name: faraday
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.15'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.15'
69
+ - !ruby/object:Gem::Dependency
70
+ name: mimemagic
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.3'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.3'
83
+ description: ''
84
+ email:
85
+ - kremen.karl@yandex.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - ".gitignore"
91
+ - ".travis.yml"
92
+ - ".yardopts"
93
+ - Gemfile
94
+ - Gemfile.lock
95
+ - LICENSE.md
96
+ - README.md
97
+ - Rakefile
98
+ - lib/vt_api.rb
99
+ - lib/vt_api/api.rb
100
+ - lib/vt_api/api/config.rb
101
+ - lib/vt_api/api/v2.rb
102
+ - lib/vt_api/api/v2/comments.rb
103
+ - lib/vt_api/api/v2/file.rb
104
+ - lib/vt_api/api/v2/url.rb
105
+ - lib/vt_api/internal.rb
106
+ - lib/vt_api/internal/api_provider.rb
107
+ - lib/vt_api/internal/api_version.rb
108
+ - lib/vt_api/internal/endpoint.rb
109
+ - lib/vt_api/internal/versions.rb
110
+ - lib/vt_api/internal/versions/api_v2.rb
111
+ - lib/vt_api/internal/versions/default.rb
112
+ - lib/vt_api/version.rb
113
+ - vt_api.gemspec
114
+ homepage: http://example.com/
115
+ licenses:
116
+ - ''
117
+ metadata:
118
+ allowed_push_host: https://rubygems.org/
119
+ homepage_uri: http://example.com/
120
+ source_code_uri: http://example.com/
121
+ changelog_uri: http://example.com
122
+ post_install_message:
123
+ rdoc_options: []
124
+ require_paths:
125
+ - lib
126
+ required_ruby_version: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - ">="
129
+ - !ruby/object:Gem::Version
130
+ version: '0'
131
+ required_rubygems_version: !ruby/object:Gem::Requirement
132
+ requirements:
133
+ - - ">="
134
+ - !ruby/object:Gem::Version
135
+ version: '0'
136
+ requirements: []
137
+ rubyforge_project:
138
+ rubygems_version: 2.6.14.1
139
+ signing_key:
140
+ specification_version: 4
141
+ summary: VirusTotal API bindings for Ruby
142
+ test_files: []