vpndetection 5.9.0 → 5.10.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 +4 -4
- data/README.md +7 -6
- data/lib/vpndetection/api/account_wire_api.rb +1 -1
- data/lib/vpndetection/api/api_keys_wire_api.rb +1 -1
- data/lib/vpndetection/api/authorization_wire_api.rb +1 -1
- data/lib/vpndetection/api/database_wire_api.rb +1 -1
- data/lib/vpndetection/api/entitlement_wire_api.rb +1 -1
- data/lib/vpndetection/api/lookup_wire_api.rb +5 -5
- data/lib/vpndetection/api/organization_wire_api.rb +1 -1
- data/lib/vpndetection/api_client.rb +1 -1
- data/lib/vpndetection/api_error.rb +1 -1
- data/lib/vpndetection/api_model_base.rb +1 -1
- data/lib/vpndetection/bogon.rb +4 -1
- data/lib/vpndetection/client.rb +2 -2
- data/lib/vpndetection/configuration.rb +1 -1
- data/lib/vpndetection/models/account_create_apikey_request.rb +1 -1
- data/lib/vpndetection/models/account_created_apikey.rb +1 -1
- data/lib/vpndetection/models/account_org.rb +1 -1
- data/lib/vpndetection/models/account_org_ref.rb +1 -1
- data/lib/vpndetection/models/account_org_wrap.rb +1 -1
- data/lib/vpndetection/models/account_rc.rb +1 -1
- data/lib/vpndetection/models/account_revealed_apikey.rb +1 -1
- data/lib/vpndetection/models/account_user.rb +1 -1
- data/lib/vpndetection/models/apikey_detail.rb +1 -1
- data/lib/vpndetection/models/apikey_list.rb +1 -1
- data/lib/vpndetection/models/asn.rb +274 -0
- data/lib/vpndetection/models/batch_lookup_error.rb +1 -1
- data/lib/vpndetection/models/batch_lookup_request.rb +1 -1
- data/lib/vpndetection/models/batch_lookup_response.rb +1 -1
- data/lib/vpndetection/models/class_detail.rb +1 -1
- data/lib/vpndetection/models/client_registration.rb +1 -1
- data/lib/vpndetection/models/client_registration_request.rb +1 -1
- data/lib/vpndetection/models/database.rb +29 -2
- data/lib/vpndetection/models/database_checksums_response.rb +1 -1
- data/lib/vpndetection/models/database_format.rb +1 -1
- data/lib/vpndetection/models/database_format_size.rb +1 -1
- data/lib/vpndetection/models/database_list.rb +1 -1
- data/lib/vpndetection/models/database_metadata.rb +1 -1
- data/lib/vpndetection/models/database_metadata_column.rb +1 -1
- data/lib/vpndetection/models/database_version.rb +1 -1
- data/lib/vpndetection/models/db_checksums.rb +1 -1
- data/lib/vpndetection/models/device_authorization.rb +1 -1
- data/lib/vpndetection/models/download.rb +29 -2
- data/lib/vpndetection/models/download_list.rb +1 -1
- data/lib/vpndetection/models/entitlement.rb +1 -1
- data/lib/vpndetection/models/entitlement_apikey.rb +1 -1
- data/lib/vpndetection/models/entitlement_error.rb +1 -1
- data/lib/vpndetection/models/entitlement_plan.rb +1 -1
- data/lib/vpndetection/models/entitlement_usage.rb +1 -1
- data/lib/vpndetection/models/error_envelope.rb +1 -1
- data/lib/vpndetection/models/identity.rb +1 -1
- data/lib/vpndetection/models/location.rb +409 -0
- data/lib/vpndetection/models/lookup_error.rb +1 -1
- data/lib/vpndetection/models/lookup_response.rb +40 -22
- data/lib/vpndetection/models/oauth_error.rb +1 -1
- data/lib/vpndetection/models/oauth_metadata.rb +1 -1
- data/lib/vpndetection/models/proxy_detail.rb +1 -1
- data/lib/vpndetection/models/pubproxy_detail.rb +1 -1
- data/lib/vpndetection/models/standing.rb +1 -1
- data/lib/vpndetection/models/token_response.rb +1 -1
- data/lib/vpndetection/models/vpn_detail.rb +4 -4
- data/lib/vpndetection/result.rb +41 -17
- data/lib/vpndetection/transport.rb +103 -9
- data/lib/vpndetection/version.rb +1 -1
- metadata +3 -1
data/lib/vpndetection/result.rb
CHANGED
|
@@ -24,6 +24,15 @@ module VPNDetection
|
|
|
24
24
|
|
|
25
25
|
DETAILS = %w[vpn hosting relay tor cdn resproxy dcproxy mobproxy pubproxy].freeze
|
|
26
26
|
|
|
27
|
+
# Where the address is, and the AS announcing it: every keyed answer carries
|
|
28
|
+
# both, each member present, an empty string or AS 0 where nothing is known.
|
|
29
|
+
LOCATION = %w[
|
|
30
|
+
continent continent_name country country_name timezone currency_code currency_name currency_symbol
|
|
31
|
+
flag_emoji flag_unicode
|
|
32
|
+
].freeze
|
|
33
|
+
|
|
34
|
+
ASN = %w[asn name domain type prefix].freeze
|
|
35
|
+
|
|
27
36
|
# The response exactly as it came off the wire, with its original names.
|
|
28
37
|
# Frozen, because the cache hands this same object to every later caller of
|
|
29
38
|
# the address.
|
|
@@ -66,7 +75,7 @@ module VPNDetection
|
|
|
66
75
|
end
|
|
67
76
|
|
|
68
77
|
# Whether the address belongs to a hosting or cloud provider, or nil when
|
|
69
|
-
# your plan does not include it.
|
|
78
|
+
# your plan does not include it. Every paid plan.
|
|
70
79
|
def is_hosting
|
|
71
80
|
@raw['is_hosting']
|
|
72
81
|
end
|
|
@@ -77,7 +86,7 @@ module VPNDetection
|
|
|
77
86
|
end
|
|
78
87
|
|
|
79
88
|
# Whether the address is a privacy relay egress, or nil when your plan does
|
|
80
|
-
# not include it.
|
|
89
|
+
# not include it. Free and paid plans.
|
|
81
90
|
def is_relay
|
|
82
91
|
@raw['is_relay']
|
|
83
92
|
end
|
|
@@ -88,7 +97,7 @@ module VPNDetection
|
|
|
88
97
|
end
|
|
89
98
|
|
|
90
99
|
# Whether the address is a Tor node, or nil when your plan does not include
|
|
91
|
-
# it.
|
|
100
|
+
# it. Free and paid plans.
|
|
92
101
|
def is_tor
|
|
93
102
|
@raw['is_tor']
|
|
94
103
|
end
|
|
@@ -99,7 +108,7 @@ module VPNDetection
|
|
|
99
108
|
end
|
|
100
109
|
|
|
101
110
|
# Whether the address belongs to a CDN, or nil when your plan does not
|
|
102
|
-
# include it.
|
|
111
|
+
# include it. Every paid plan.
|
|
103
112
|
def is_cdn
|
|
104
113
|
@raw['is_cdn']
|
|
105
114
|
end
|
|
@@ -110,7 +119,7 @@ module VPNDetection
|
|
|
110
119
|
end
|
|
111
120
|
|
|
112
121
|
# Whether the address was seen in a residential proxy pool, or nil when your
|
|
113
|
-
# plan does not include it.
|
|
122
|
+
# plan does not include it. Every paid plan.
|
|
114
123
|
def is_resproxy
|
|
115
124
|
@raw['is_resproxy']
|
|
116
125
|
end
|
|
@@ -121,7 +130,7 @@ module VPNDetection
|
|
|
121
130
|
end
|
|
122
131
|
|
|
123
132
|
# Whether the address was seen in a datacenter proxy pool, or nil when your
|
|
124
|
-
# plan does not include it.
|
|
133
|
+
# plan does not include it. Every paid plan.
|
|
125
134
|
def is_dcproxy
|
|
126
135
|
@raw['is_dcproxy']
|
|
127
136
|
end
|
|
@@ -132,7 +141,7 @@ module VPNDetection
|
|
|
132
141
|
end
|
|
133
142
|
|
|
134
143
|
# Whether the address was seen in a mobile proxy pool, or nil when your plan
|
|
135
|
-
# does not include it.
|
|
144
|
+
# does not include it. Every paid plan.
|
|
136
145
|
def is_mobproxy
|
|
137
146
|
@raw['is_mobproxy']
|
|
138
147
|
end
|
|
@@ -144,7 +153,7 @@ module VPNDetection
|
|
|
144
153
|
|
|
145
154
|
# Whether a public proxy, one anyone can connect through, listens on the
|
|
146
155
|
# address or sends its traffic out from it, or nil when your plan does not
|
|
147
|
-
# include it.
|
|
156
|
+
# include it. Every paid plan.
|
|
148
157
|
def is_pubproxy
|
|
149
158
|
@raw['is_pubproxy']
|
|
150
159
|
end
|
|
@@ -154,42 +163,43 @@ module VPNDetection
|
|
|
154
163
|
@raw['is_pubproxy'] == true
|
|
155
164
|
end
|
|
156
165
|
|
|
157
|
-
# Detail for `is_vpn`: provider, last_seen
|
|
166
|
+
# Detail for `is_vpn`: provider, last_seen and confidence. Every paid plan.
|
|
167
|
+
# `method` is deprecated: no plan is answered with it since 2026-10-20.
|
|
158
168
|
def vpn
|
|
159
169
|
@raw['vpn']
|
|
160
170
|
end
|
|
161
171
|
|
|
162
|
-
# Detail for `is_hosting`.
|
|
172
|
+
# Detail for `is_hosting`. Every paid plan.
|
|
163
173
|
def hosting
|
|
164
174
|
@raw['hosting']
|
|
165
175
|
end
|
|
166
176
|
|
|
167
|
-
# Detail for `is_relay`.
|
|
177
|
+
# Detail for `is_relay`. Free and paid plans.
|
|
168
178
|
def relay
|
|
169
179
|
@raw['relay']
|
|
170
180
|
end
|
|
171
181
|
|
|
172
|
-
# Detail for `is_tor`.
|
|
182
|
+
# Detail for `is_tor`. Free and paid plans.
|
|
173
183
|
def tor
|
|
174
184
|
@raw['tor']
|
|
175
185
|
end
|
|
176
186
|
|
|
177
|
-
# Detail for `is_cdn`.
|
|
187
|
+
# Detail for `is_cdn`. Every paid plan.
|
|
178
188
|
def cdn
|
|
179
189
|
@raw['cdn']
|
|
180
190
|
end
|
|
181
191
|
|
|
182
|
-
# Detail for `is_resproxy`.
|
|
192
|
+
# Detail for `is_resproxy`. Every paid plan.
|
|
183
193
|
def resproxy
|
|
184
194
|
@raw['resproxy']
|
|
185
195
|
end
|
|
186
196
|
|
|
187
|
-
# Detail for `is_dcproxy`.
|
|
197
|
+
# Detail for `is_dcproxy`. Every paid plan.
|
|
188
198
|
def dcproxy
|
|
189
199
|
@raw['dcproxy']
|
|
190
200
|
end
|
|
191
201
|
|
|
192
|
-
# Detail for `is_mobproxy`.
|
|
202
|
+
# Detail for `is_mobproxy`. Every paid plan.
|
|
193
203
|
def mobproxy
|
|
194
204
|
@raw['mobproxy']
|
|
195
205
|
end
|
|
@@ -198,11 +208,25 @@ module VPNDetection
|
|
|
198
208
|
# num_proxies, last_seen and last_usable. num_ports is 0 when the address
|
|
199
209
|
# is only an exit and num_proxies 0 when it is only an entry; last_usable is
|
|
200
210
|
# an empty string when no request through a proxy on the address was
|
|
201
|
-
# answered.
|
|
211
|
+
# answered. Every paid plan.
|
|
202
212
|
def pubproxy
|
|
203
213
|
@raw['pubproxy']
|
|
204
214
|
end
|
|
205
215
|
|
|
216
|
+
# Where the address is: continent, country, timezone, the country's
|
|
217
|
+
# currency and flag, as a wire hash (`result.location['country']`). Free and
|
|
218
|
+
# paid plans.
|
|
219
|
+
def location
|
|
220
|
+
@raw['location']
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
# The AS announcing the address: asn, name, domain, type and prefix, as a
|
|
224
|
+
# wire hash (`result.asn['name']`), with asn 0 where none does. Free and paid
|
|
225
|
+
# plans.
|
|
226
|
+
def asn
|
|
227
|
+
@raw['asn']
|
|
228
|
+
end
|
|
229
|
+
|
|
206
230
|
def to_h
|
|
207
231
|
@raw
|
|
208
232
|
end
|
|
@@ -86,11 +86,18 @@ module VPNDetection
|
|
|
86
86
|
# the spec requires. The generated reader raised a RuntimeError, a
|
|
87
87
|
# JSON::ParserError or an ArgumentError for those, none of them retried, and
|
|
88
88
|
# handed an empty or non-object body back as nil, as if it were the answer.
|
|
89
|
+
#
|
|
90
|
+
# Every member present is checked against the type the spec gives it, at any
|
|
91
|
+
# depth, before the answer is handed back: the generated reader converts with
|
|
92
|
+
# `to_i`, so `"version":"x"` came back as 0 and `"version":"3"` as 3, and it
|
|
93
|
+
# skips a list member sent as null, so `"versions": null` came back as nil
|
|
94
|
+
# (5.9.0, measured 2026-10-20).
|
|
89
95
|
def deserialize(response, return_type)
|
|
90
96
|
data = super
|
|
91
|
-
|
|
97
|
+
raise Error.new(:server_error, "the API answered with no #{return_type}", status: response.code) if data.nil?
|
|
92
98
|
|
|
93
|
-
|
|
99
|
+
Transport.check_type!(JSON.parse(response.body.to_s), return_type.to_s, 'the answer')
|
|
100
|
+
data
|
|
94
101
|
rescue Error
|
|
95
102
|
raise
|
|
96
103
|
rescue StandardError => e
|
|
@@ -182,6 +189,7 @@ module VPNDetection
|
|
|
182
189
|
|
|
183
190
|
body = parse_object(response)
|
|
184
191
|
begin
|
|
192
|
+
check_type!(body, 'Entitlement', 'the answer')
|
|
185
193
|
Entitlement.build_from_hash(body)
|
|
186
194
|
rescue StandardError => e
|
|
187
195
|
raise Error.new(:server_error, "could not read the answer as Entitlement: #{e.message}",
|
|
@@ -194,15 +202,21 @@ module VPNDetection
|
|
|
194
202
|
raise Error.from_status(response.code, response.headers, response.body) unless response.success?
|
|
195
203
|
|
|
196
204
|
body = parse_object(response)
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
205
|
+
begin
|
|
206
|
+
check_type!(body, 'LookupResponse', 'the answer')
|
|
207
|
+
rescue TypeError => e
|
|
208
|
+
raise Error.new(:server_error, "could not read the answer as LookupResponse: #{e.message}",
|
|
209
|
+
status: response.code)
|
|
210
|
+
end
|
|
211
|
+
Result.new(body)
|
|
200
212
|
end
|
|
201
213
|
|
|
202
|
-
# Whether a parsed
|
|
203
|
-
#
|
|
204
|
-
def self.
|
|
205
|
-
|
|
214
|
+
# Whether a parsed value is the generated `type`: a batch entry that is not
|
|
215
|
+
# is that address's error alone.
|
|
216
|
+
def self.conforms?(value, type)
|
|
217
|
+
check_type!(value, type, 'the entry')
|
|
218
|
+
rescue TypeError
|
|
219
|
+
false
|
|
206
220
|
end
|
|
207
221
|
|
|
208
222
|
# The two maps of a batch answer, each present even when empty. An answer
|
|
@@ -269,5 +283,85 @@ module VPNDetection
|
|
|
269
283
|
end
|
|
270
284
|
|
|
271
285
|
private_class_method :transport_failure?, :parse_object, :oauth_refusal
|
|
286
|
+
|
|
287
|
+
# The members the spec requires of each answer a lookup, a batch, the
|
|
288
|
+
# entitlement or a database call reads. One that is not nullable must be
|
|
289
|
+
# there and not null. Every plan's lookup carries `ip` and `is_vpn`, and a
|
|
290
|
+
# detail's members are as optional as the plan makes them.
|
|
291
|
+
# test/client_test.rb holds this to spec/openapi.yaml.
|
|
292
|
+
REQUIRED = {
|
|
293
|
+
'LookupResponse' => %i[ip is_vpn],
|
|
294
|
+
'VpnDetail' => [],
|
|
295
|
+
'ClassDetail' => [],
|
|
296
|
+
'ProxyDetail' => [],
|
|
297
|
+
'PubproxyDetail' => [],
|
|
298
|
+
'Location' => %i[continent continent_name country country_name timezone currency_code currency_name
|
|
299
|
+
currency_symbol flag_emoji flag_unicode],
|
|
300
|
+
'Asn' => %i[asn name domain type prefix],
|
|
301
|
+
'BatchLookupError' => %i[status error],
|
|
302
|
+
'Entitlement' => %i[org_id apikey plan usage],
|
|
303
|
+
'EntitlementApikey' => %i[id expires allowed_cidrs],
|
|
304
|
+
'EntitlementPlan' => %i[key tier],
|
|
305
|
+
'EntitlementUsage' => %i[requests quota hard_limit window_start window_end],
|
|
306
|
+
'DatabaseList' => %i[databases],
|
|
307
|
+
'Database' => %i[base name summary license_type starts expires renews_at notice_due_at in_term standing
|
|
308
|
+
open versions],
|
|
309
|
+
'DatabaseVersion' => %i[id version formats],
|
|
310
|
+
'DatabaseFormatSize' => %i[format bytes],
|
|
311
|
+
'DatabaseMetadata' => %i[id updated entries schema],
|
|
312
|
+
'DatabaseMetadataColumn' => %i[name type],
|
|
313
|
+
'DatabaseChecksumsResponse' => %i[id format checksums],
|
|
314
|
+
'DbChecksums' => %i[md5 sha1 sha256 sha512],
|
|
315
|
+
'DownloadList' => %i[downloads],
|
|
316
|
+
'Download' => %i[dataset_id format outcome sample open bytes http_status apikey_id client_ip user_agent
|
|
317
|
+
created],
|
|
318
|
+
}.freeze
|
|
319
|
+
|
|
320
|
+
# True when the parsed `value` has the generated `type`, a model's members
|
|
321
|
+
# recursively; raises TypeError naming where it does not. No list entry or
|
|
322
|
+
# map value the spec describes may be null: through 5.9.0 one came back as
|
|
323
|
+
# nil, or as 0 in `size`, and a lookup's `"is_hosting": "yes"` came back as
|
|
324
|
+
# the answer.
|
|
325
|
+
def self.check_type!(value, type, where)
|
|
326
|
+
raise TypeError, "#{where} is null" if value.nil?
|
|
327
|
+
|
|
328
|
+
typed =
|
|
329
|
+
case type
|
|
330
|
+
when 'String', 'Date', 'Time' then value.is_a?(String)
|
|
331
|
+
when 'Integer' then value.is_a?(Integer)
|
|
332
|
+
when 'Float' then value.is_a?(Numeric)
|
|
333
|
+
when 'Boolean' then [true, false].include?(value)
|
|
334
|
+
when 'Object' then true
|
|
335
|
+
when /\AArray<(?<inner>.+)>\z/
|
|
336
|
+
inner = Regexp.last_match(:inner)
|
|
337
|
+
value.is_a?(Array) && value.each_with_index.all? { |v, i| check_type!(v, inner, "#{where}[#{i}]") }
|
|
338
|
+
when /\AHash<String, ?(?<inner>.+)>\z/
|
|
339
|
+
inner = Regexp.last_match(:inner)
|
|
340
|
+
value.is_a?(Hash) && value.all? { |k, v| check_type!(v, inner, "#{where}.#{k}") }
|
|
341
|
+
else
|
|
342
|
+
model = VPNDetection.const_get(type)
|
|
343
|
+
if model.respond_to?(:openapi_types)
|
|
344
|
+
value.is_a?(Hash) && model.openapi_types.all? { |attr, member| check_member!(model, attr, member, value, where) }
|
|
345
|
+
else
|
|
346
|
+
value.is_a?(String) # an enum
|
|
347
|
+
end
|
|
348
|
+
end
|
|
349
|
+
return true if typed
|
|
350
|
+
|
|
351
|
+
raise TypeError, "#{where} is not a #{type}"
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
# A member absent or null reads as absent, unless the spec requires it and
|
|
355
|
+
# does not make it nullable.
|
|
356
|
+
def self.check_member!(model, attr, member, value, where)
|
|
357
|
+
key = model.attribute_map[attr].to_s
|
|
358
|
+
return check_type!(value[key], member.to_s, "#{where}.#{key}") unless value[key].nil?
|
|
359
|
+
return true unless REQUIRED.fetch(model.name.split('::').last).include?(attr)
|
|
360
|
+
return true if model.openapi_nullable.include?(attr)
|
|
361
|
+
|
|
362
|
+
raise TypeError, "#{where}.#{key} is #{value.key?(key) ? 'null' : 'missing'}"
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
private_class_method :check_member!
|
|
272
366
|
end
|
|
273
367
|
end
|
data/lib/vpndetection/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: vpndetection
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 5.
|
|
4
|
+
version: 5.10.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Mslm Dev
|
|
@@ -78,6 +78,7 @@ files:
|
|
|
78
78
|
- lib/vpndetection/models/account_user.rb
|
|
79
79
|
- lib/vpndetection/models/apikey_detail.rb
|
|
80
80
|
- lib/vpndetection/models/apikey_list.rb
|
|
81
|
+
- lib/vpndetection/models/asn.rb
|
|
81
82
|
- lib/vpndetection/models/batch_lookup_error.rb
|
|
82
83
|
- lib/vpndetection/models/batch_lookup_request.rb
|
|
83
84
|
- lib/vpndetection/models/batch_lookup_response.rb
|
|
@@ -103,6 +104,7 @@ files:
|
|
|
103
104
|
- lib/vpndetection/models/entitlement_usage.rb
|
|
104
105
|
- lib/vpndetection/models/error_envelope.rb
|
|
105
106
|
- lib/vpndetection/models/identity.rb
|
|
107
|
+
- lib/vpndetection/models/location.rb
|
|
106
108
|
- lib/vpndetection/models/lookup_error.rb
|
|
107
109
|
- lib/vpndetection/models/lookup_response.rb
|
|
108
110
|
- lib/vpndetection/models/oauth_error.rb
|