vpndetection 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +171 -0
- data/lib/vpndetection/api/database_api.rb +351 -0
- data/lib/vpndetection/api/lookup_api.rb +85 -0
- data/lib/vpndetection/api_client.rb +397 -0
- data/lib/vpndetection/api_error.rb +58 -0
- data/lib/vpndetection/api_model_base.rb +88 -0
- data/lib/vpndetection/bogon.rb +60 -0
- data/lib/vpndetection/bogons.rb +67 -0
- data/lib/vpndetection/cache.rb +35 -0
- data/lib/vpndetection/client.rb +133 -0
- data/lib/vpndetection/configuration.rb +326 -0
- data/lib/vpndetection/database.rb +167 -0
- data/lib/vpndetection/errors.rb +107 -0
- data/lib/vpndetection/models/class_detail.rb +169 -0
- data/lib/vpndetection/models/dataset_checksums.rb +174 -0
- data/lib/vpndetection/models/dataset_checksums_response.rb +216 -0
- data/lib/vpndetection/models/dataset_format_size.rb +201 -0
- data/lib/vpndetection/models/dataset_list.rb +166 -0
- data/lib/vpndetection/models/dataset_metadata.rb +280 -0
- data/lib/vpndetection/models/dataset_metadata_column.rb +199 -0
- data/lib/vpndetection/models/download.rb +276 -0
- data/lib/vpndetection/models/download_list.rb +166 -0
- data/lib/vpndetection/models/error_envelope.rb +164 -0
- data/lib/vpndetection/models/licensed_dataset.rb +358 -0
- data/lib/vpndetection/models/licensed_version.rb +262 -0
- data/lib/vpndetection/models/lookup_error.rb +166 -0
- data/lib/vpndetection/models/lookup_response.rb +343 -0
- data/lib/vpndetection/models/proxy_detail.rb +199 -0
- data/lib/vpndetection/models/vpn_detail.rb +179 -0
- data/lib/vpndetection/result.rb +211 -0
- data/lib/vpndetection/retries.rb +33 -0
- data/lib/vpndetection/transport.rb +116 -0
- data/lib/vpndetection/version.rb +5 -0
- data/lib/vpndetection.rb +40 -0
- metadata +109 -0
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
=begin
|
|
2
|
+
#VPNDetection API
|
|
3
|
+
|
|
4
|
+
#The VPNDetection API: classify any IP address, and download the datasets behind the answers. See https://docs.vpndetection.io for guides and https://github.com/vpndetection-io for the official client libraries.
|
|
5
|
+
|
|
6
|
+
The version of the OpenAPI document: 2026.09.04
|
|
7
|
+
Contact: support@vpndetection.io
|
|
8
|
+
Generated by: https://openapi-generator.tech
|
|
9
|
+
Generator version: 7.25.0
|
|
10
|
+
|
|
11
|
+
=end
|
|
12
|
+
|
|
13
|
+
require 'date'
|
|
14
|
+
require 'time'
|
|
15
|
+
|
|
16
|
+
module VPNDetection
|
|
17
|
+
# What is known about the VPN attribution. Every key is present when the object is populated, empty values included; the object is `{}` when `is_vpn` is false. `confidence` and `method` are max only, so on a lower plan they are absent from a populated object rather than empty.
|
|
18
|
+
class VpnDetail < ApiModelBase
|
|
19
|
+
# The VPN provider, or an empty string for an unattributed range.
|
|
20
|
+
attr_accessor :provider
|
|
21
|
+
|
|
22
|
+
# The most recent date this address was observed as VPN infrastructure.
|
|
23
|
+
attr_accessor :last_seen
|
|
24
|
+
|
|
25
|
+
# How strongly the attribution is supported. Max only.
|
|
26
|
+
attr_accessor :confidence
|
|
27
|
+
|
|
28
|
+
# How the address was attributed to the provider. Max only.
|
|
29
|
+
attr_accessor :method
|
|
30
|
+
|
|
31
|
+
# Attribute mapping from ruby-style variable name to JSON key.
|
|
32
|
+
def self.attribute_map
|
|
33
|
+
{
|
|
34
|
+
:'provider' => :'provider',
|
|
35
|
+
:'last_seen' => :'last_seen',
|
|
36
|
+
:'confidence' => :'confidence',
|
|
37
|
+
:'method' => :'method'
|
|
38
|
+
}
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Returns attribute mapping this model knows about
|
|
42
|
+
def self.acceptable_attribute_map
|
|
43
|
+
attribute_map
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Returns all the JSON keys this model knows about
|
|
47
|
+
def self.acceptable_attributes
|
|
48
|
+
acceptable_attribute_map.values
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Attribute type mapping.
|
|
52
|
+
def self.openapi_types
|
|
53
|
+
{
|
|
54
|
+
:'provider' => :'String',
|
|
55
|
+
:'last_seen' => :'Date',
|
|
56
|
+
:'confidence' => :'String',
|
|
57
|
+
:'method' => :'String'
|
|
58
|
+
}
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# List of attributes with nullable: true
|
|
62
|
+
def self.openapi_nullable
|
|
63
|
+
Set.new([
|
|
64
|
+
])
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Initializes the object
|
|
68
|
+
# @param [Hash] attributes Model attributes in the form of hash
|
|
69
|
+
def initialize(attributes = {})
|
|
70
|
+
if (!attributes.is_a?(Hash))
|
|
71
|
+
fail ArgumentError, "The input argument (attributes) must be a hash in `VPNDetection::VpnDetail` initialize method"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# check to see if the attribute exists and convert string to symbol for hash key
|
|
75
|
+
acceptable_attribute_map = self.class.acceptable_attribute_map
|
|
76
|
+
attributes = attributes.each_with_object({}) { |(k, v), h|
|
|
77
|
+
if (!acceptable_attribute_map.key?(k.to_sym))
|
|
78
|
+
fail ArgumentError, "`#{k}` is not a valid attribute in `VPNDetection::VpnDetail`. Please check the name to make sure it's valid. List of attributes: " + acceptable_attribute_map.keys.inspect
|
|
79
|
+
end
|
|
80
|
+
h[k.to_sym] = v
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if attributes.key?(:'provider')
|
|
84
|
+
self.provider = attributes[:'provider']
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
if attributes.key?(:'last_seen')
|
|
88
|
+
self.last_seen = attributes[:'last_seen']
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
if attributes.key?(:'confidence')
|
|
92
|
+
self.confidence = attributes[:'confidence']
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
if attributes.key?(:'method')
|
|
96
|
+
self.method = attributes[:'method']
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Show invalid properties with the reasons. Usually used together with valid?
|
|
101
|
+
# @return Array for valid properties with the reasons
|
|
102
|
+
def list_invalid_properties
|
|
103
|
+
warn '[DEPRECATED] the `list_invalid_properties` method is obsolete'
|
|
104
|
+
invalid_properties = Array.new
|
|
105
|
+
invalid_properties
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Check to see if the all the properties in the model are valid
|
|
109
|
+
# @return true if the model is valid
|
|
110
|
+
def valid?
|
|
111
|
+
warn '[DEPRECATED] the `valid?` method is obsolete'
|
|
112
|
+
true
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Checks equality by comparing each attribute.
|
|
116
|
+
# @param [Object] Object to be compared
|
|
117
|
+
def ==(o)
|
|
118
|
+
return true if self.equal?(o)
|
|
119
|
+
self.class == o.class &&
|
|
120
|
+
provider == o.provider &&
|
|
121
|
+
last_seen == o.last_seen &&
|
|
122
|
+
confidence == o.confidence &&
|
|
123
|
+
method == o.method
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# @see the `==` method
|
|
127
|
+
# @param [Object] Object to be compared
|
|
128
|
+
def eql?(o)
|
|
129
|
+
self == o
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Calculates hash code according to all attributes.
|
|
133
|
+
# @return [Integer] Hash code
|
|
134
|
+
def hash
|
|
135
|
+
[provider, last_seen, confidence, method].hash
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Builds the object from hash
|
|
139
|
+
# @param [Hash] attributes Model attributes in the form of hash
|
|
140
|
+
# @return [Object] Returns the model itself
|
|
141
|
+
def self.build_from_hash(attributes)
|
|
142
|
+
return nil unless attributes.is_a?(Hash)
|
|
143
|
+
attributes = attributes.transform_keys(&:to_sym)
|
|
144
|
+
transformed_hash = {}
|
|
145
|
+
openapi_types.each_pair do |key, type|
|
|
146
|
+
if attributes.key?(attribute_map[key]) && attributes[attribute_map[key]].nil?
|
|
147
|
+
transformed_hash["#{key}"] = nil
|
|
148
|
+
elsif type =~ /\AArray<(.*)>/i
|
|
149
|
+
# check to ensure the input is an array given that the attribute
|
|
150
|
+
# is documented as an array but the input is not
|
|
151
|
+
if attributes[attribute_map[key]].is_a?(Array)
|
|
152
|
+
transformed_hash["#{key}"] = attributes[attribute_map[key]].map { |v| _deserialize($1, v) }
|
|
153
|
+
end
|
|
154
|
+
elsif !attributes[attribute_map[key]].nil?
|
|
155
|
+
transformed_hash["#{key}"] = _deserialize(type, attributes[attribute_map[key]])
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
new(transformed_hash)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Returns the object in the form of hash
|
|
162
|
+
# @return [Hash] Returns the object in the form of hash
|
|
163
|
+
def to_hash
|
|
164
|
+
hash = {}
|
|
165
|
+
self.class.attribute_map.each_pair do |attr, param|
|
|
166
|
+
value = self.send(attr)
|
|
167
|
+
if value.nil?
|
|
168
|
+
is_nullable = self.class.openapi_nullable.include?(attr)
|
|
169
|
+
next if !is_nullable || (is_nullable && !instance_variable_defined?(:"@#{attr}"))
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
hash[param] = _to_hash(value)
|
|
173
|
+
end
|
|
174
|
+
hash
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
end
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module VPNDetection
|
|
4
|
+
# What a lookup answers.
|
|
5
|
+
#
|
|
6
|
+
# An **absent** member is one your plan does not include. It never means "we
|
|
7
|
+
# could not check", so `nil` and `false` are genuinely different answers: `nil`
|
|
8
|
+
# is "not in your plan", `false` is "checked, and no".
|
|
9
|
+
#
|
|
10
|
+
# Ruby makes that easy to lose, because `nil` and `false` are both falsy. Read
|
|
11
|
+
# the field itself (`is_hosting`) when the difference matters, ask
|
|
12
|
+
# {#included?} when you need to know whether your plan carries it at all, and
|
|
13
|
+
# use the predicate (`hosting?`) when you only care whether the address is
|
|
14
|
+
# flagged. A predicate always answers `true` or `false`, never `nil`.
|
|
15
|
+
#
|
|
16
|
+
# A detail object that is present but empty (`{}`) means the flag above it is
|
|
17
|
+
# false. A populated one always carries every one of its keys. Details are the
|
|
18
|
+
# wire hashes with their original string keys, so `result.vpn['provider']`.
|
|
19
|
+
class Result
|
|
20
|
+
FLAGS = %w[
|
|
21
|
+
is_vpn is_hosting is_relay is_tor is_cdn is_resproxy is_dcproxy is_mobproxy
|
|
22
|
+
].freeze
|
|
23
|
+
|
|
24
|
+
DETAILS = %w[vpn hosting relay tor cdn resproxy dcproxy mobproxy].freeze
|
|
25
|
+
|
|
26
|
+
# The response exactly as it came off the wire, with its original names.
|
|
27
|
+
# Frozen, because the cache hands this same object to every later caller of
|
|
28
|
+
# the address.
|
|
29
|
+
attr_reader :raw
|
|
30
|
+
|
|
31
|
+
def initialize(raw, bogon: false)
|
|
32
|
+
@raw = deep_freeze(raw)
|
|
33
|
+
@bogon = bogon
|
|
34
|
+
freeze
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# The address that was looked up, normalized.
|
|
38
|
+
def ip
|
|
39
|
+
@raw['ip']
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Set when this answer was computed locally rather than served. Always
|
|
43
|
+
# `true` or `false`.
|
|
44
|
+
def bogon?
|
|
45
|
+
@bogon
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Whether your plan includes a member at all, told apart from a member your
|
|
49
|
+
# plan includes that answered false.
|
|
50
|
+
#
|
|
51
|
+
# result.included?(:is_hosting) # => false, not in your plan
|
|
52
|
+
# result.is_hosting # => nil
|
|
53
|
+
def included?(field)
|
|
54
|
+
@raw.key?(field.to_s)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Whether the address is VPN infrastructure. Every plan includes this.
|
|
58
|
+
def is_vpn
|
|
59
|
+
@raw['is_vpn']
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# `is_vpn` with an absent member read as false.
|
|
63
|
+
def vpn?
|
|
64
|
+
@raw['is_vpn'] == true
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Whether the address belongs to a hosting or cloud provider, or nil when
|
|
68
|
+
# your plan does not include it. Starter and above.
|
|
69
|
+
def is_hosting
|
|
70
|
+
@raw['is_hosting']
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# `is_hosting` with an absent member read as false.
|
|
74
|
+
def hosting?
|
|
75
|
+
@raw['is_hosting'] == true
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Whether the address is a privacy relay egress, or nil when your plan does
|
|
79
|
+
# not include it. Starter and above.
|
|
80
|
+
def is_relay
|
|
81
|
+
@raw['is_relay']
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# `is_relay` with an absent member read as false.
|
|
85
|
+
def relay?
|
|
86
|
+
@raw['is_relay'] == true
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Whether the address is a Tor node, or nil when your plan does not include
|
|
90
|
+
# it. Starter and above.
|
|
91
|
+
def is_tor
|
|
92
|
+
@raw['is_tor']
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# `is_tor` with an absent member read as false.
|
|
96
|
+
def tor?
|
|
97
|
+
@raw['is_tor'] == true
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Whether the address belongs to a CDN, or nil when your plan does not
|
|
101
|
+
# include it. Starter and above.
|
|
102
|
+
def is_cdn
|
|
103
|
+
@raw['is_cdn']
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# `is_cdn` with an absent member read as false.
|
|
107
|
+
def cdn?
|
|
108
|
+
@raw['is_cdn'] == true
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Whether the address was seen in a residential proxy pool, or nil when your
|
|
112
|
+
# plan does not include it. Max only.
|
|
113
|
+
def is_resproxy
|
|
114
|
+
@raw['is_resproxy']
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# `is_resproxy` with an absent member read as false.
|
|
118
|
+
def resproxy?
|
|
119
|
+
@raw['is_resproxy'] == true
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Whether the address was seen in a datacenter proxy pool, or nil when your
|
|
123
|
+
# plan does not include it. Max only.
|
|
124
|
+
def is_dcproxy
|
|
125
|
+
@raw['is_dcproxy']
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# `is_dcproxy` with an absent member read as false.
|
|
129
|
+
def dcproxy?
|
|
130
|
+
@raw['is_dcproxy'] == true
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Whether the address was seen in a mobile proxy pool, or nil when your plan
|
|
134
|
+
# does not include it. Max only.
|
|
135
|
+
def is_mobproxy
|
|
136
|
+
@raw['is_mobproxy']
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# `is_mobproxy` with an absent member read as false.
|
|
140
|
+
def mobproxy?
|
|
141
|
+
@raw['is_mobproxy'] == true
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Detail for `is_vpn`: provider, last_seen, and on max confidence and method.
|
|
145
|
+
def vpn
|
|
146
|
+
@raw['vpn']
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Detail for `is_hosting`. Scale and above.
|
|
150
|
+
def hosting
|
|
151
|
+
@raw['hosting']
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Detail for `is_relay`. Scale and above.
|
|
155
|
+
def relay
|
|
156
|
+
@raw['relay']
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Detail for `is_tor`. Scale and above.
|
|
160
|
+
def tor
|
|
161
|
+
@raw['tor']
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# Detail for `is_cdn`. Scale and above.
|
|
165
|
+
def cdn
|
|
166
|
+
@raw['cdn']
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Detail for `is_resproxy`. Max only.
|
|
170
|
+
def resproxy
|
|
171
|
+
@raw['resproxy']
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Detail for `is_dcproxy`. Max only.
|
|
175
|
+
def dcproxy
|
|
176
|
+
@raw['dcproxy']
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# Detail for `is_mobproxy`. Max only.
|
|
180
|
+
def mobproxy
|
|
181
|
+
@raw['mobproxy']
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def to_h
|
|
185
|
+
@raw
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def ==(other)
|
|
189
|
+
other.is_a?(Result) && other.raw == @raw && other.bogon? == @bogon
|
|
190
|
+
end
|
|
191
|
+
alias eql? ==
|
|
192
|
+
|
|
193
|
+
def hash
|
|
194
|
+
[@raw, @bogon].hash
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def inspect
|
|
198
|
+
"#<#{self.class} ip=#{ip.inspect} is_vpn=#{is_vpn.inspect} bogon=#{@bogon}>"
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
private
|
|
202
|
+
|
|
203
|
+
def deep_freeze(value)
|
|
204
|
+
case value
|
|
205
|
+
when Hash then value.each { |_, v| deep_freeze(v) }.freeze
|
|
206
|
+
when Array then value.each { |v| deep_freeze(v) }.freeze
|
|
207
|
+
else value.freeze
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module VPNDetection
|
|
4
|
+
# What may be tried again, and how long to wait first.
|
|
5
|
+
#
|
|
6
|
+
# Only a 5xx, a transport failure and a 429 that carried `Retry-After` are
|
|
7
|
+
# worth another attempt. Every other 4xx is a client error and fails on the
|
|
8
|
+
# first try, so a bad dataset id is not asked for three times.
|
|
9
|
+
module Retries
|
|
10
|
+
BACKOFF_SECONDS = 0.25
|
|
11
|
+
|
|
12
|
+
module_function
|
|
13
|
+
|
|
14
|
+
def with_retries(retries)
|
|
15
|
+
attempt = 0
|
|
16
|
+
begin
|
|
17
|
+
yield
|
|
18
|
+
rescue Error => e
|
|
19
|
+
raise unless e.retryable? && attempt < retries
|
|
20
|
+
|
|
21
|
+
attempt += 1
|
|
22
|
+
sleep(delay_for(e, attempt))
|
|
23
|
+
retry
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# A server-supplied delay always wins, including a `Retry-After: 0`, which
|
|
28
|
+
# is the server saying "immediately" rather than saying nothing.
|
|
29
|
+
def delay_for(error, attempt)
|
|
30
|
+
error.retry_after_seconds || BACKOFF_SECONDS * (2**(attempt - 1))
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'cgi'
|
|
4
|
+
require 'json'
|
|
5
|
+
require 'uri'
|
|
6
|
+
|
|
7
|
+
module VPNDetection
|
|
8
|
+
# The generated wire client, with the three things it gets wrong for this API
|
|
9
|
+
# corrected in one place.
|
|
10
|
+
class Transport < ApiClient
|
|
11
|
+
# The same path template the generated LookupApi holds. Both are asserted
|
|
12
|
+
# against each other in the test suite, because the batch builds its own
|
|
13
|
+
# requests to queue them on a hydra and cannot go through the generated
|
|
14
|
+
# method, which runs each request as it builds it.
|
|
15
|
+
LOOKUP_PATH = '/{ip}'
|
|
16
|
+
|
|
17
|
+
# The generated Configuration applies EVERY security scheme the spec lists,
|
|
18
|
+
# so a keyless client would send `Authorization: Bearer `, an empty
|
|
19
|
+
# `X-Api-Key` and an empty `apikey` query parameter. The API answers 401 to
|
|
20
|
+
# the first of those, which is not what a keyless caller asked for.
|
|
21
|
+
class Config < Configuration
|
|
22
|
+
def initialize(api_key: nil, base_url: DEFAULT_BASE_URL, timeout: DEFAULT_TIMEOUT)
|
|
23
|
+
super()
|
|
24
|
+
uri = URI.parse(base_url)
|
|
25
|
+
self.scheme = uri.scheme
|
|
26
|
+
self.host = uri.port == uri.default_port ? uri.host : "#{uri.host}:#{uri.port}"
|
|
27
|
+
self.base_path = uri.path
|
|
28
|
+
self.access_token = api_key
|
|
29
|
+
self.timeout = timeout
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def auth_settings
|
|
33
|
+
return {} if access_token.nil? || access_token.to_s.empty?
|
|
34
|
+
|
|
35
|
+
{
|
|
36
|
+
'bearerAuth' => {
|
|
37
|
+
type: 'bearer', in: 'header', key: 'Authorization',
|
|
38
|
+
value: "Bearer #{access_token}"
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def initialize(config = Config.new)
|
|
45
|
+
super
|
|
46
|
+
@default_headers['User-Agent'] = "vpndetection-ruby/#{VERSION}"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# `follow_location = opts[:follow_location] || true` in the generated client
|
|
50
|
+
# is true for every value it can be given, so the database download's 302
|
|
51
|
+
# would be chased and a multi-gigabyte dataset read into memory. Nothing
|
|
52
|
+
# this API serves is meant to be followed.
|
|
53
|
+
def build_request(http_method, path, opts = {})
|
|
54
|
+
request = super
|
|
55
|
+
request.options[:followlocation] = false
|
|
56
|
+
request
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# A GET for the presigned link the download endpoint hands out.
|
|
60
|
+
#
|
|
61
|
+
# Built here rather than through {#build_request} so it carries NO
|
|
62
|
+
# credential: the presigned URL authorizes itself, and forwarding the API key
|
|
63
|
+
# would hand it to a host with no business holding it. Redirects ARE followed,
|
|
64
|
+
# unlike every other request this client makes, because this one IS the far
|
|
65
|
+
# side of a redirect; the guard exists to stop the API's own 302 pulling a
|
|
66
|
+
# dataset into memory, not to stop object storage from moving a bucket.
|
|
67
|
+
#
|
|
68
|
+
# The whole-request timeout is dropped and only the connect phase is bounded.
|
|
69
|
+
# Ten seconds is a sane ceiling on a lookup and the wrong one on 1.79 GB.
|
|
70
|
+
def storage_request(url)
|
|
71
|
+
options = {
|
|
72
|
+
method: :get,
|
|
73
|
+
headers: { 'User-Agent' => @default_headers['User-Agent'] },
|
|
74
|
+
followlocation: true,
|
|
75
|
+
maxredirs: 5,
|
|
76
|
+
connecttimeout: @config.timeout,
|
|
77
|
+
ssl_verifypeer: @config.verify_ssl,
|
|
78
|
+
ssl_verifyhost: @config.verify_ssl_host ? 2 : 0,
|
|
79
|
+
}
|
|
80
|
+
options[:cainfo] = @config.ssl_ca_cert if @config.ssl_ca_cert
|
|
81
|
+
Typhoeus::Request.new(url, options)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def lookup_request(ip)
|
|
85
|
+
build_request(
|
|
86
|
+
:GET, LOOKUP_PATH.sub('{ip}', CGI.escape(ip.to_s)),
|
|
87
|
+
header_params: { 'Accept' => 'application/json' },
|
|
88
|
+
auth_names: %w[bearerAuth apiKeyHeader apiKeyQuery],
|
|
89
|
+
)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def self.lookup_result(response)
|
|
93
|
+
raise Error.from_transport(response) if transport_failure?(response)
|
|
94
|
+
raise Error.from_status(response.code, response.headers, response.body) unless response.success?
|
|
95
|
+
|
|
96
|
+
Result.new(parse_object(response))
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def self.transport_failure?(response)
|
|
100
|
+
response.timed_out? || response.code.to_i.zero?
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def self.parse_object(response)
|
|
104
|
+
body = JSON.parse(response.body.to_s)
|
|
105
|
+
return body if body.is_a?(Hash)
|
|
106
|
+
|
|
107
|
+
raise Error.new(:server_error, 'the API answered with something other than an object',
|
|
108
|
+
status: response.code)
|
|
109
|
+
rescue JSON::ParserError => e
|
|
110
|
+
raise Error.new(:server_error, "could not parse the response body: #{e.message}",
|
|
111
|
+
status: response.code)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
private_class_method :transport_failure?, :parse_object
|
|
115
|
+
end
|
|
116
|
+
end
|
data/lib/vpndetection.rb
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'vpndetection/version'
|
|
4
|
+
|
|
5
|
+
# The wire layer, generated from spec/openapi.yaml by scripts/generate.sh.
|
|
6
|
+
# Models are loaded by glob because the set of them is the generator's to
|
|
7
|
+
# decide; they subclass ApiModelBase and so must follow it.
|
|
8
|
+
require 'vpndetection/api_client'
|
|
9
|
+
require 'vpndetection/api_error'
|
|
10
|
+
require 'vpndetection/api_model_base'
|
|
11
|
+
require 'vpndetection/configuration'
|
|
12
|
+
Dir[File.join(__dir__, 'vpndetection', 'models', '*.rb')].sort.each { |model| require model }
|
|
13
|
+
Dir[File.join(__dir__, 'vpndetection', 'api', '*.rb')].sort.each { |api| require api }
|
|
14
|
+
|
|
15
|
+
require 'vpndetection/bogons'
|
|
16
|
+
require 'vpndetection/errors'
|
|
17
|
+
require 'vpndetection/result'
|
|
18
|
+
require 'vpndetection/bogon'
|
|
19
|
+
require 'vpndetection/cache'
|
|
20
|
+
require 'vpndetection/retries'
|
|
21
|
+
require 'vpndetection/transport'
|
|
22
|
+
require 'vpndetection/database'
|
|
23
|
+
require 'vpndetection/client'
|
|
24
|
+
|
|
25
|
+
# The official Ruby client library for the VPNDetection API.
|
|
26
|
+
#
|
|
27
|
+
# client = VPNDetection::Client.new
|
|
28
|
+
# client.lookup('45.83.91.1').is_vpn # => true
|
|
29
|
+
module VPNDetection
|
|
30
|
+
# Whether an address is a bogon: private, loopback, link-local, documentation,
|
|
31
|
+
# multicast or otherwise not routable on the public internet, including the
|
|
32
|
+
# IPv6 equivalents and the 6to4 and Teredo ranges that wrap them.
|
|
33
|
+
#
|
|
34
|
+
# These can never be VPN or proxy infrastructure, so a client answers them
|
|
35
|
+
# itself and they never cost a request. The same predicate is on the client,
|
|
36
|
+
# which is how the README teaches it.
|
|
37
|
+
def self.bogon?(ip)
|
|
38
|
+
Bogon.bogon?(ip)
|
|
39
|
+
end
|
|
40
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: vpndetection
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Mslm Dev
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: lru_redux
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '1.1'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '1.1'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: typhoeus
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '1.4'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '1.4'
|
|
40
|
+
description: Query the VPNDetection API for anonymity detection including VPNs, residential
|
|
41
|
+
proxies, Tor nodes, hosting servers, CDNs and relays.
|
|
42
|
+
email:
|
|
43
|
+
- support@vpndetection.io
|
|
44
|
+
executables: []
|
|
45
|
+
extensions: []
|
|
46
|
+
extra_rdoc_files: []
|
|
47
|
+
files:
|
|
48
|
+
- LICENSE
|
|
49
|
+
- README.md
|
|
50
|
+
- lib/vpndetection.rb
|
|
51
|
+
- lib/vpndetection/api/database_api.rb
|
|
52
|
+
- lib/vpndetection/api/lookup_api.rb
|
|
53
|
+
- lib/vpndetection/api_client.rb
|
|
54
|
+
- lib/vpndetection/api_error.rb
|
|
55
|
+
- lib/vpndetection/api_model_base.rb
|
|
56
|
+
- lib/vpndetection/bogon.rb
|
|
57
|
+
- lib/vpndetection/bogons.rb
|
|
58
|
+
- lib/vpndetection/cache.rb
|
|
59
|
+
- lib/vpndetection/client.rb
|
|
60
|
+
- lib/vpndetection/configuration.rb
|
|
61
|
+
- lib/vpndetection/database.rb
|
|
62
|
+
- lib/vpndetection/errors.rb
|
|
63
|
+
- lib/vpndetection/models/class_detail.rb
|
|
64
|
+
- lib/vpndetection/models/dataset_checksums.rb
|
|
65
|
+
- lib/vpndetection/models/dataset_checksums_response.rb
|
|
66
|
+
- lib/vpndetection/models/dataset_format_size.rb
|
|
67
|
+
- lib/vpndetection/models/dataset_list.rb
|
|
68
|
+
- lib/vpndetection/models/dataset_metadata.rb
|
|
69
|
+
- lib/vpndetection/models/dataset_metadata_column.rb
|
|
70
|
+
- lib/vpndetection/models/download.rb
|
|
71
|
+
- lib/vpndetection/models/download_list.rb
|
|
72
|
+
- lib/vpndetection/models/error_envelope.rb
|
|
73
|
+
- lib/vpndetection/models/licensed_dataset.rb
|
|
74
|
+
- lib/vpndetection/models/licensed_version.rb
|
|
75
|
+
- lib/vpndetection/models/lookup_error.rb
|
|
76
|
+
- lib/vpndetection/models/lookup_response.rb
|
|
77
|
+
- lib/vpndetection/models/proxy_detail.rb
|
|
78
|
+
- lib/vpndetection/models/vpn_detail.rb
|
|
79
|
+
- lib/vpndetection/result.rb
|
|
80
|
+
- lib/vpndetection/retries.rb
|
|
81
|
+
- lib/vpndetection/transport.rb
|
|
82
|
+
- lib/vpndetection/version.rb
|
|
83
|
+
homepage: https://vpndetection.io
|
|
84
|
+
licenses:
|
|
85
|
+
- MIT
|
|
86
|
+
metadata:
|
|
87
|
+
homepage_uri: https://vpndetection.io
|
|
88
|
+
source_code_uri: https://github.com/vpndetection-io/sdk-ruby
|
|
89
|
+
bug_tracker_uri: https://github.com/vpndetection-io/sdk-ruby/issues
|
|
90
|
+
changelog_uri: https://github.com/vpndetection-io/sdk-ruby/releases
|
|
91
|
+
documentation_uri: https://docs.vpndetection.io
|
|
92
|
+
rdoc_options: []
|
|
93
|
+
require_paths:
|
|
94
|
+
- lib
|
|
95
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
96
|
+
requirements:
|
|
97
|
+
- - ">="
|
|
98
|
+
- !ruby/object:Gem::Version
|
|
99
|
+
version: '3.1'
|
|
100
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
101
|
+
requirements:
|
|
102
|
+
- - ">="
|
|
103
|
+
- !ruby/object:Gem::Version
|
|
104
|
+
version: '0'
|
|
105
|
+
requirements: []
|
|
106
|
+
rubygems_version: 3.6.9
|
|
107
|
+
specification_version: 4
|
|
108
|
+
summary: Official Ruby client library for the VPNDetection API.
|
|
109
|
+
test_files: []
|