ruby-common 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/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Gemfile +16 -0
- data/Gemfile.lock +34 -0
- data/LICENSE.md +21 -0
- data/README.md +174 -0
- data/Rakefile +4 -0
- data/lib/ruby-common/Signature4Verifier.rb +29 -0
- data/lib/ruby-common/Signature5Verifier.rb +31 -0
- data/lib/ruby-common/common/Exceptions.rb +7 -0
- data/lib/ruby-common/common/Ipv6Utils.rb +27 -0
- data/lib/ruby-common/common/PhpUnpack.rb +96 -0
- data/lib/ruby-common/common/Utils.rb +22 -0
- data/lib/ruby-common/common/VerifierConstants.rb +9 -0
- data/lib/ruby-common/v4/AsymmetricOpenSSL.rb +11 -0
- data/lib/ruby-common/v4/Signature4VerificationResult.rb +40 -0
- data/lib/ruby-common/v4/Signature4VerifierService.rb +203 -0
- data/lib/ruby-common/v5/AbstractSymmetricCrypt.rb +25 -0
- data/lib/ruby-common/v5/CryptFactory.rb +20 -0
- data/lib/ruby-common/v5/CryptMethodConstans.rb +10 -0
- data/lib/ruby-common/v5/DecryptResult.rb +27 -0
- data/lib/ruby-common/v5/MyOpenSSL.rb +39 -0
- data/lib/ruby-common/v5/OpenSSLAEAD.rb +41 -0
- data/lib/ruby-common/v5/PhpUnserializer.rb +88 -0
- data/lib/ruby-common/v5/Secretbox.rb +14 -0
- data/lib/ruby-common/v5/Signature5VerificationResult.rb +154 -0
- data/lib/ruby-common/v5/Signature5VerifierService.rb +90 -0
- data/lib/ruby-common/v5/StructUnpacker.rb +110 -0
- data/lib/ruby-common/version.rb +7 -0
- data/lib/ruby-common.rb +7 -0
- data/sig/ruby/common.rbs +6 -0
- metadata +120 -0
@@ -0,0 +1,203 @@
|
|
1
|
+
require 'base64'
|
2
|
+
require 'date'
|
3
|
+
require 'openssl'
|
4
|
+
require 'securerandom'
|
5
|
+
|
6
|
+
require_relative '../common/PhpUnpack.rb'
|
7
|
+
require_relative '../common/Ipv6Utils.rb'
|
8
|
+
require_relative './AsymmetricOpenSSL.rb'
|
9
|
+
require_relative '../common/Utils.rb'
|
10
|
+
require_relative '../common/Exceptions'
|
11
|
+
require_relative '../common/VerifierConstants.rb'
|
12
|
+
require_relative './Signature4VerificationResult.rb'
|
13
|
+
|
14
|
+
|
15
|
+
# Helper class to represent field information
|
16
|
+
class Field
|
17
|
+
attr_reader :name, :type
|
18
|
+
|
19
|
+
def initialize(name, type)
|
20
|
+
@name = name
|
21
|
+
@type = type
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Signature4VerifierService class
|
26
|
+
class Signature4VerifierService
|
27
|
+
FIELD_IDS = {
|
28
|
+
0x00 => Field.new('requestTime', 'ulong'),
|
29
|
+
0x01 => Field.new('signatureTime', 'ulong'),
|
30
|
+
0x10 => Field.new('ipv4', 'ulong'),
|
31
|
+
0x40 => Field.new(nil, 'ushort'),
|
32
|
+
0x80 => Field.new('masterSignType', 'uchar'),
|
33
|
+
0x81 => Field.new('customerSignType', 'uchar'),
|
34
|
+
0xC0 => Field.new('masterToken', 'string'),
|
35
|
+
0xC1 => Field.new('customerToken', 'string'),
|
36
|
+
0xC2 => Field.new('masterTokenV6', 'string'),
|
37
|
+
0xC3 => Field.new('customerTokenV6', 'string'),
|
38
|
+
0xc4 => Field.new('ipv6', 'string'),
|
39
|
+
0xc5 => Field.new('masterChecksum', 'string'),
|
40
|
+
0xd0 => Field.new('userAgent', 'string') #DEBUG FIELD
|
41
|
+
}.freeze
|
42
|
+
|
43
|
+
def self.verifySignature(signature, user_agent, key, ip_addresses, expiry, is_key_base64_encoded)
|
44
|
+
validation_result = {}
|
45
|
+
|
46
|
+
begin
|
47
|
+
data = parse4(signature)
|
48
|
+
rescue VersionError
|
49
|
+
data = parse3(signature)
|
50
|
+
end
|
51
|
+
|
52
|
+
sign_role_token = data["customerToken"]
|
53
|
+
|
54
|
+
if sign_role_token.nil? || sign_role_token.empty?
|
55
|
+
raise VerifyError, 'sign role signature mismatch'
|
56
|
+
end
|
57
|
+
|
58
|
+
sign_type = data["customerSignType"]
|
59
|
+
|
60
|
+
ip_addresses.each do |ip_address|
|
61
|
+
next if ip_address.nil? || ip_address.empty?
|
62
|
+
|
63
|
+
token = if IpV6Utils.validate(ip_address)
|
64
|
+
next unless data.key?("customerTokenV6")
|
65
|
+
IpV6Utils.abbreviate(ip_address)
|
66
|
+
data["customerTokenV6"]
|
67
|
+
else
|
68
|
+
next unless data.key?("customerToken")
|
69
|
+
data["customerToken"]
|
70
|
+
end
|
71
|
+
|
72
|
+
signature_time = data['signatureTime'].first
|
73
|
+
request_time = data['requestTime'].first
|
74
|
+
|
75
|
+
VerifierConstants::RESULTS.each do |result, verdict|
|
76
|
+
signature_base = get_base(result, request_time, signature_time, ip_address, user_agent)
|
77
|
+
|
78
|
+
case sign_type.first
|
79
|
+
when 1 # HASH_SHA256
|
80
|
+
is_hashed_data_equal_to_token = SignatureVerifierUtils.encode(
|
81
|
+
is_key_base64_encoded ? SignatureVerifierUtils.base64_decode(key) : key,
|
82
|
+
signature_base
|
83
|
+
) == token
|
84
|
+
|
85
|
+
if is_hashed_data_equal_to_token
|
86
|
+
if is_expired(expiry, signature_time, request_time)
|
87
|
+
return Signature4VerificationResult.is_expired
|
88
|
+
end
|
89
|
+
|
90
|
+
return Signature4VerificationResult.new(
|
91
|
+
score: result.to_i,
|
92
|
+
verdict: verdict,
|
93
|
+
ip_address: ip_address,
|
94
|
+
request_time: request_time,
|
95
|
+
signature_time: signature_time
|
96
|
+
)
|
97
|
+
end
|
98
|
+
when 2 # SIGN_SHA256
|
99
|
+
if AsymmetricOpenSSL.verify_data(signature_base, token, key)
|
100
|
+
return Signature4VerificationResult.new(
|
101
|
+
score: result.to_i,
|
102
|
+
verdict: verdict,
|
103
|
+
ip_address: ip_address,
|
104
|
+
request_time: request_time,
|
105
|
+
signature_time: signature_time
|
106
|
+
)
|
107
|
+
end
|
108
|
+
else
|
109
|
+
raise VerifyError, 'unrecognized signature'
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
raise StructParseError, 'no verdict'
|
114
|
+
end
|
115
|
+
|
116
|
+
class << self
|
117
|
+
private
|
118
|
+
|
119
|
+
def parse3(signature)
|
120
|
+
sign_decoded = SignatureVerifierUtils.base64_decode(signature)
|
121
|
+
unpack_result = PhpUnpack.unpack('Cversion/NrequestTime/NsignatureTime/CmasterSignType/nmasterTokenLength', sign_buffer)
|
122
|
+
|
123
|
+
version = unpack_result['version']
|
124
|
+
raise VersionError, 'Invalid signature version' if version != 3
|
125
|
+
|
126
|
+
timestamp = unpack_result['timestamp']
|
127
|
+
raise SignatureParseError, 'invalid timestamp (future time)' if timestamp > (Time.now.to_i)
|
128
|
+
|
129
|
+
master_token_length = unpack_result['masterTokenLength']
|
130
|
+
#TODO CO TO KURWA JEST ZA METODA?
|
131
|
+
master_token = get_bytes_and_advance_position(sign_decoded, master_token_length)
|
132
|
+
unpack_result['masterToken'] = master_token
|
133
|
+
|
134
|
+
customer_data = PhpUnpack.unpack('CcustomerSignType/ncustomerTokenLength', sign_buffer)
|
135
|
+
customer_token_length = customer_data['customerTokenLength']
|
136
|
+
customer_token = get_bytes_and_advance_position(sign_decoded, customer_token_length)
|
137
|
+
customer_data['customerToken'] = customer_token
|
138
|
+
|
139
|
+
unpack_result.merge!(customer_data)
|
140
|
+
end
|
141
|
+
|
142
|
+
def parse4(signature)
|
143
|
+
sign_decoded = SignatureVerifierUtils.base64_decode(signature)
|
144
|
+
raise SignatureParseError, 'invalid base64 payload' if sign_decoded.empty?
|
145
|
+
|
146
|
+
data = PhpUnpack.unpack('Cversion/CfieldNum', sign_decoded)
|
147
|
+
|
148
|
+
version = data['version'].first
|
149
|
+
raise VersionError, 'Invalid signature version' if version != 4
|
150
|
+
|
151
|
+
field_num = data['fieldNum'].first
|
152
|
+
field_num.times do |i|
|
153
|
+
header = PhpUnpack.unpack('CfieldId', sign_decoded)
|
154
|
+
|
155
|
+
raise SignatureParseError, 'premature end of signature 0x01' if header.empty? || !header.key?('fieldId')
|
156
|
+
|
157
|
+
field = FIELD_IDS[header['fieldId'].first]
|
158
|
+
v = {}
|
159
|
+
|
160
|
+
case field&.type
|
161
|
+
when 'uchar'
|
162
|
+
v = PhpUnpack.unpack('Cv', sign_decoded)
|
163
|
+
data[field.name] = v['v'] if v.key?('v')
|
164
|
+
when 'ushort'
|
165
|
+
v = PhpUnpack.unpack('nv', sign_decoded)
|
166
|
+
data[field.name] = v['v'] if v.key?('v')
|
167
|
+
when 'ulong'
|
168
|
+
v = PhpUnpack.unpack('Nv', sign_decoded)
|
169
|
+
data[field.name] = v['v'] if v.key?('v')
|
170
|
+
when 'string'
|
171
|
+
l = PhpUnpack.unpack('nl', sign_decoded)
|
172
|
+
raise SignatureParseError, 'premature end of signature 0x05' unless l.key?('l')
|
173
|
+
|
174
|
+
l_length = l['l'].first
|
175
|
+
new_v = sign_decoded.slice!(0, l_length)
|
176
|
+
v['v'] = new_v
|
177
|
+
data[field.name] = new_v
|
178
|
+
|
179
|
+
raise SignatureParseError, 'premature end of signature 0x06' if new_v.bytesize != l_length
|
180
|
+
else
|
181
|
+
raise SignatureParseError, 'unsupported variable type'
|
182
|
+
end
|
183
|
+
end
|
184
|
+
data.delete(field_num.to_s)
|
185
|
+
data
|
186
|
+
end
|
187
|
+
|
188
|
+
def is_expired(expiry, signature_time, request_time)
|
189
|
+
return false if expiry.nil?
|
190
|
+
|
191
|
+
current_epoch_in_seconds = (Time.now.to_f * 1000).to_i / 1000
|
192
|
+
|
193
|
+
signature_time_expired = (signature_time + expiry) < current_epoch_in_seconds
|
194
|
+
request_time_expired = request_time + expiry < current_epoch_in_seconds
|
195
|
+
|
196
|
+
signature_time_expired || request_time_expired
|
197
|
+
end
|
198
|
+
|
199
|
+
def get_base(verdict, request_time, signature_time, ip_address, user_agent)
|
200
|
+
[verdict, request_time, signature_time, ip_address, user_agent].join("\n")
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require_relative './DecryptResult.rb'
|
2
|
+
|
3
|
+
class AbstractSymmetricCrypt
|
4
|
+
@@method_size = 2
|
5
|
+
|
6
|
+
def parse(payload, lengths)
|
7
|
+
total_length = @@method_size + lengths.values.inject(0) { |sum, value| sum + value }
|
8
|
+
|
9
|
+
raise DecryptError, "Premature data end" if payload.size < total_length
|
10
|
+
|
11
|
+
decrypt_result = DecryptResult.new
|
12
|
+
|
13
|
+
decrypt_result.method = PhpUnpack.unpack("vX", payload)['X'].first
|
14
|
+
|
15
|
+
lengths.each do |key, length|
|
16
|
+
bytes_for_key = payload.byteslice(0,length)
|
17
|
+
payload.slice!(0,length)
|
18
|
+
decrypt_result.byte_buffer_map[key] = bytes_for_key
|
19
|
+
end
|
20
|
+
|
21
|
+
decrypt_result.data = payload
|
22
|
+
|
23
|
+
decrypt_result
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class CryptFactory
|
2
|
+
def self.create_from_payload(payload)
|
3
|
+
header = payload.byteslice(0,2)
|
4
|
+
return create_crypt(header)
|
5
|
+
end
|
6
|
+
|
7
|
+
private
|
8
|
+
def self.create_crypt(name)
|
9
|
+
case name.unpack("v").first
|
10
|
+
when MyOpenSSL::METHOD
|
11
|
+
MyOpenSSL.new
|
12
|
+
when OpenSSLAEAD::METHOD
|
13
|
+
OpenSSLAEAD.new
|
14
|
+
when Secretbox::METHOD
|
15
|
+
Secretbox.new
|
16
|
+
else
|
17
|
+
raise SignatureParseError, "Unsupported crypt class"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
class DecryptResult
|
2
|
+
def initialize
|
3
|
+
@method = nil
|
4
|
+
@byte_buffer_map = {}
|
5
|
+
@data = nil
|
6
|
+
end
|
7
|
+
|
8
|
+
def method
|
9
|
+
@method
|
10
|
+
end
|
11
|
+
|
12
|
+
def method=(method)
|
13
|
+
@method = method
|
14
|
+
end
|
15
|
+
|
16
|
+
def byte_buffer_map
|
17
|
+
@byte_buffer_map
|
18
|
+
end
|
19
|
+
|
20
|
+
def data
|
21
|
+
@data
|
22
|
+
end
|
23
|
+
|
24
|
+
def data=(data)
|
25
|
+
@data = data
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require_relative "./AbstractSymmetricCrypt.rb"
|
2
|
+
require_relative "./CryptMethodConstans.rb"
|
3
|
+
|
4
|
+
|
5
|
+
class MyOpenSSL < AbstractSymmetricCrypt
|
6
|
+
METHOD = 0x0200
|
7
|
+
|
8
|
+
def initialize(crypt_method: "AES-256-CBC")
|
9
|
+
if CryptMethodConstans::CRYPT_METHODS.key?(crypt_method)
|
10
|
+
@crypt_method = crypt_method
|
11
|
+
@crypt_iv = CryptMethodConstans::CRYPT_METHODS[crypt_method]
|
12
|
+
else
|
13
|
+
raise DecryptError, "Method not supported #{crypt_method}"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def decrypt_with_key(payload, key)
|
18
|
+
lengths = {"iv" => @crypt_iv}
|
19
|
+
result = parse(payload, lengths)
|
20
|
+
|
21
|
+
raise DecryptError, 'Unrecognized payload' if result.method != METHOD;
|
22
|
+
|
23
|
+
return decode(payload, key, result.byte_buffer_map['iv'])
|
24
|
+
end
|
25
|
+
|
26
|
+
def decode(input, key, iv)
|
27
|
+
begin
|
28
|
+
cipher = OpenSSL::Cipher.new(@crypt_method)
|
29
|
+
cipher.decrypt
|
30
|
+
|
31
|
+
cipher.key = key.bytes.pack('C*')
|
32
|
+
cipher.iv = iv.bytes.pack('C*')
|
33
|
+
|
34
|
+
return cipher.update(input) + cipher.final
|
35
|
+
rescue StandardError => e
|
36
|
+
raise DecryptError, "Decryption OpenSSL failed: #{e.message}"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require_relative "./AbstractSymmetricCrypt.rb"
|
2
|
+
|
3
|
+
class OpenSSLAEAD < AbstractSymmetricCrypt
|
4
|
+
METHOD = 0x0201
|
5
|
+
|
6
|
+
def initialize(tag: 16, crypt_method: "AES-256-GCM")
|
7
|
+
@tag= tag
|
8
|
+
@crypt_method = crypt_method
|
9
|
+
@crypt_iv = CryptMethodConstans::CRYPT_METHODS[crypt_method]
|
10
|
+
end
|
11
|
+
|
12
|
+
def decrypt_with_key(payload, key)
|
13
|
+
lengths = {"iv" => @crypt_iv, "tag" => @tag}
|
14
|
+
result = parse(payload, lengths)
|
15
|
+
|
16
|
+
raise DecryptError, 'Unrecognized payload' if result.method != METHOD;
|
17
|
+
|
18
|
+
return decode(
|
19
|
+
result.data,
|
20
|
+
key,
|
21
|
+
result.byte_buffer_map['iv'],
|
22
|
+
result.byte_buffer_map['tag']
|
23
|
+
)
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
def decode(input, key, iv, tag)
|
28
|
+
cipher = OpenSSL::Cipher.new(@crypt_method)
|
29
|
+
cipher.decrypt
|
30
|
+
|
31
|
+
cipher.key = key.bytes.pack('C*')
|
32
|
+
cipher.iv = iv.bytes.pack('C*')
|
33
|
+
cipher.auth_tag = tag.bytes.pack('C*')
|
34
|
+
|
35
|
+
return cipher.update(input) + cipher.final
|
36
|
+
rescue OpenSSL::Cipher::CipherError => e
|
37
|
+
raise DecryptError.new("Decryption failed: #{e.message}")
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
class PhpUnserializer
|
2
|
+
def initialize(data)
|
3
|
+
@data = data
|
4
|
+
@index = 0
|
5
|
+
end
|
6
|
+
|
7
|
+
def unserialize
|
8
|
+
type = @data[@index]
|
9
|
+
@index += 2
|
10
|
+
|
11
|
+
case type
|
12
|
+
when 'i' then parse_int
|
13
|
+
when 'd' then parse_float
|
14
|
+
when 'b' then parse_boolean
|
15
|
+
when 's' then parse_string
|
16
|
+
when 'a' then parse_array
|
17
|
+
when 'O' then parse_object
|
18
|
+
else
|
19
|
+
raise ArgumentError, "Unsupported type: #{type}"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def parse_int
|
26
|
+
semi_colon_index = @data.index(';', @index)
|
27
|
+
int_str = @data[@index...semi_colon_index]
|
28
|
+
@index = semi_colon_index + 1
|
29
|
+
int_str.to_i
|
30
|
+
end
|
31
|
+
|
32
|
+
def parse_float
|
33
|
+
semi_colon_index = @data.index(';', @index)
|
34
|
+
float_str = @data[@index...semi_colon_index]
|
35
|
+
@index = semi_colon_index + 1
|
36
|
+
float_str.to_f
|
37
|
+
end
|
38
|
+
|
39
|
+
def parse_boolean
|
40
|
+
bool_char = @data[@index]
|
41
|
+
@index += 2
|
42
|
+
bool_char == '1'
|
43
|
+
end
|
44
|
+
|
45
|
+
def parse_string
|
46
|
+
colon_index = @data.index(':', @index)
|
47
|
+
length = @data[@index...colon_index].to_i
|
48
|
+
@index = colon_index + 2
|
49
|
+
str = @data[@index...@index + length]
|
50
|
+
@index += length + 2
|
51
|
+
str
|
52
|
+
end
|
53
|
+
|
54
|
+
def parse_array
|
55
|
+
colon_index = @data.index(':', @index)
|
56
|
+
length = @data[@index...colon_index].to_i
|
57
|
+
@index = colon_index + 2
|
58
|
+
map = {}
|
59
|
+
length.times do
|
60
|
+
key = unserialize
|
61
|
+
value = unserialize
|
62
|
+
map[key] = value
|
63
|
+
end
|
64
|
+
@index += 1
|
65
|
+
map
|
66
|
+
end
|
67
|
+
|
68
|
+
def parse_object
|
69
|
+
colon_index = @data.index(':', @index)
|
70
|
+
class_name_length = @data[@index...colon_index].to_i
|
71
|
+
@index = colon_index + 2
|
72
|
+
class_name = @data[@index...@index + class_name_length]
|
73
|
+
@index += class_name_length + 2
|
74
|
+
|
75
|
+
colon_index = @data.index(':', @index)
|
76
|
+
length = @data[@index...colon_index].to_i
|
77
|
+
@index = colon_index + 2
|
78
|
+
fields = {}
|
79
|
+
length.times do
|
80
|
+
key = unserialize
|
81
|
+
value = unserialize
|
82
|
+
fields[key] = value
|
83
|
+
end
|
84
|
+
@index += 1
|
85
|
+
|
86
|
+
{ class_name => fields }
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'rbnacl'
|
2
|
+
|
3
|
+
class Secretbox < AbstractSymmetricCrypt
|
4
|
+
METHOD = 0x0101
|
5
|
+
|
6
|
+
def decrypt_with_key(payload, key)
|
7
|
+
nonce_bytes = 24
|
8
|
+
parse = parse(payload, { iv: nonce_bytes })
|
9
|
+
secret_box = RbNaCl::SecretBox.new(key)
|
10
|
+
return secret_box.decrypt(parse.byte_buffer_map[:iv], parse.data)
|
11
|
+
rescue RbNaCl::CryptoError
|
12
|
+
raise 'Decryption failed'
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
class Signature5VerificationResult
|
2
|
+
# Zone-id
|
3
|
+
attr_accessor :zone_id
|
4
|
+
# Detection result as number, one of following: 0, 3, 6, 9
|
5
|
+
attr_accessor :result
|
6
|
+
# Detection result as text, one of following: ok, junk, proxy, bot
|
7
|
+
attr_accessor :verdict
|
8
|
+
# Visitor's User Agent
|
9
|
+
attr_accessor :visitor_user_agent
|
10
|
+
# Data
|
11
|
+
attr_accessor :data
|
12
|
+
# IPv4 address
|
13
|
+
attr_accessor :ipv4_ip
|
14
|
+
# Number of bytes required for IP matching
|
15
|
+
attr_accessor :ipv4_v
|
16
|
+
# IPv6 address
|
17
|
+
attr_accessor :ipv6_ip
|
18
|
+
# Number of left-most bytes of IPv6 address needed to match
|
19
|
+
attr_accessor :ipv6_v
|
20
|
+
# Number of CPU logical cores gathered from navigator.hardwareConcurrency
|
21
|
+
attr_accessor :cpu_cores
|
22
|
+
# Amount of RAM memory in GB gathered from navigator.deviceMemory
|
23
|
+
attr_accessor :ram
|
24
|
+
# Timezone offset from GMT in minutes
|
25
|
+
attr_accessor :tz_offset
|
26
|
+
# User-Agent Client Hints Platform
|
27
|
+
attr_accessor :b_platform
|
28
|
+
# Content of Sec-CH-UA-Platform-Version request header
|
29
|
+
attr_accessor :platform_v
|
30
|
+
# GPU Model obtained from WebGL and WebGPU APIs
|
31
|
+
attr_accessor :gpu
|
32
|
+
# Detected iPhone/iPad model by Adscore AppleSense
|
33
|
+
attr_accessor :apple_sense
|
34
|
+
# Physical screen horizontal resolution
|
35
|
+
attr_accessor :horizontal_resolution
|
36
|
+
# Physical screen vertical resolution
|
37
|
+
attr_accessor :vertical_resolution
|
38
|
+
# Adscore TrueUA-enriched User-Agent
|
39
|
+
attr_accessor :true_ua
|
40
|
+
# Adscore True Location Country
|
41
|
+
attr_accessor :true_ua_location
|
42
|
+
# Adscore True Location Confidence
|
43
|
+
attr_accessor :true_ua_location_c
|
44
|
+
# Adscore TrueUA-enriched Client Hints header Sec-CH-UA
|
45
|
+
attr_accessor :truech_ua
|
46
|
+
# Adscore TrueUA-enriched Client Hints header Sec-CH-UA-Arch
|
47
|
+
attr_accessor :truech_arch
|
48
|
+
# Adscore TrueUA-enriched Client Hints header Sec-CH-UA-Bitness
|
49
|
+
attr_accessor :truech_bitness
|
50
|
+
# Adscore TrueUA-enriched Client Hints header Sec-CH-UA-Model
|
51
|
+
attr_accessor :truech_model
|
52
|
+
# Adscore TrueUA-enriched Client Hints header Sec-CH-UA-Platform
|
53
|
+
attr_accessor :truech_platform
|
54
|
+
# Adscore TrueUA-enriched Client Hints header Sec-CH-UA-Platform-Version
|
55
|
+
attr_accessor :truech_platform_v
|
56
|
+
# Adscore TrueUA-enriched Client Hints header Sec-CH-UA-Full-Version
|
57
|
+
attr_accessor :truech_full_v
|
58
|
+
# Adscore TrueUA-enriched Client Hints header Sec-CH-UA-Mobile
|
59
|
+
attr_accessor :truech_mobile
|
60
|
+
# Indicates whether visitor is using Private Browsing (Incognito) Mode
|
61
|
+
attr_accessor :incognito
|
62
|
+
# Adscore zone subId
|
63
|
+
attr_accessor :sub_id
|
64
|
+
# Request time
|
65
|
+
attr_accessor :request_time
|
66
|
+
# Signature time
|
67
|
+
attr_accessor :signature_time
|
68
|
+
# Signature time
|
69
|
+
attr_accessor :h_signature_time
|
70
|
+
# Token
|
71
|
+
attr_accessor :token
|
72
|
+
# Other, which has not been mapped to a field, or getting error during parsing
|
73
|
+
attr_accessor :additional_data
|
74
|
+
|
75
|
+
def initialize(hash)
|
76
|
+
@zone_id = hash.delete('zone_id')&.to_i
|
77
|
+
@result = hash.delete('result')&.to_i
|
78
|
+
@verdict = hash.delete('verdict')
|
79
|
+
@visitor_user_agent = hash.delete('b.ua')
|
80
|
+
@data = hash.delete('data')
|
81
|
+
@ipv4_ip = hash.delete('ipv4.ip')
|
82
|
+
@ipv4_v = hash.delete('ipv4.v')&.to_i
|
83
|
+
@ipv6_ip = hash.delete('ipv6.ip')
|
84
|
+
@ipv6_v = hash.delete('ipv6.v')&.to_i
|
85
|
+
@cpu_cores = hash.delete('b.cpucores')&.to_i
|
86
|
+
@ram = hash.delete('b.ram')&.to_i
|
87
|
+
@tz_offset = hash.delete('b.tzoffset')&.to_i
|
88
|
+
@b_platform = hash.delete('b.platform')
|
89
|
+
@platform_v = hash.delete('b.platform.v')
|
90
|
+
@gpu = hash.delete('b.gpu')
|
91
|
+
@apple_sense = hash.delete('apple_sense')
|
92
|
+
@horizontal_resolution = hash.delete('b.sr.w')&.to_i
|
93
|
+
@vertical_resolution = hash.delete('b.sr.h')&.to_i
|
94
|
+
@true_ua = hash.delete('b.trueua')
|
95
|
+
@true_ua_location = hash.delete('b.trueloc.c')
|
96
|
+
@true_ua_location_c = hash.delete('b.truech.location.c')&.to_i
|
97
|
+
@truech_ua = hash.delete('b.truech.ua')
|
98
|
+
@truech_arch = hash.delete('b.truech.arch')
|
99
|
+
@truech_bitness = hash.delete('b.truech.bitness')&.to_i
|
100
|
+
@truech_model = hash.delete('b.truech.model')
|
101
|
+
@truech_platform = hash.delete('b.truech.platform')
|
102
|
+
@truech_platform_v = hash.delete('b.truech.platform.v')
|
103
|
+
@truech_full_v = hash.delete('b.truech.full.v')
|
104
|
+
@truech_mobile = hash.delete('b.truech.mobile')
|
105
|
+
@incognito = hash.delete('incognito')
|
106
|
+
@sub_id = hash.delete('sub_id')
|
107
|
+
@request_time = hash.delete('requestTime')&.to_i
|
108
|
+
@h_signature_time = hash.delete('HsignatureTime')
|
109
|
+
@signature_time = hash.delete('signatureTime')
|
110
|
+
@token = hash.delete('token')
|
111
|
+
@additional_data = hash
|
112
|
+
end
|
113
|
+
|
114
|
+
def to_s
|
115
|
+
<<~STRING
|
116
|
+
Zone ID: #{@zone_id}
|
117
|
+
Result: #{@result}
|
118
|
+
Verdict: #{@verdict}
|
119
|
+
Visitor User Agent: #{@visitor_user_agent}
|
120
|
+
Data: #{@data}
|
121
|
+
IPv4 IP: #{@ipv4_ip}
|
122
|
+
IPv4 Version: #{@ipv4_v}
|
123
|
+
IPv6 IP: #{@ipv6_ip}
|
124
|
+
IPv6 Version: #{@ipv6_v}
|
125
|
+
CPU Cores: #{@cpu_cores}
|
126
|
+
RAM: #{@ram}
|
127
|
+
Time Zone Offset: #{@tz_offset}
|
128
|
+
Browser Platform: #{@b_platform}
|
129
|
+
Platform Version: #{@platform_v}
|
130
|
+
GPU: #{@gpu}
|
131
|
+
Apple Sense: #{@apple_sense}
|
132
|
+
Horizontal Resolution: #{@horizontal_resolution}
|
133
|
+
Vertical Resolution: #{@vertical_resolution}
|
134
|
+
True User Agent: #{@true_ua}
|
135
|
+
True User Agent Location: #{@true_ua_location}
|
136
|
+
True User Agent Location Code: #{@true_ua_location_c}
|
137
|
+
Truech User Agent: #{@truech_ua}
|
138
|
+
Truech Architecture: #{@truech_arch}
|
139
|
+
Truech Bitness: #{@truech_bitness}
|
140
|
+
Truech Model: #{@truech_model}
|
141
|
+
Truech Platform: #{@truech_platform}
|
142
|
+
Truech Platform Version: #{@truech_platform_v}
|
143
|
+
Truech Full Version: #{@truech_full_v}
|
144
|
+
Truech Mobile: #{@truech_mobile}
|
145
|
+
Incognito: #{@incognito}
|
146
|
+
Subscriber ID: #{@sub_id}
|
147
|
+
Request Time: #{@request_time}
|
148
|
+
Signature Time: #{@signature_time}
|
149
|
+
H Signature Time: #{@h_signature_time}
|
150
|
+
Token: #{@token}
|
151
|
+
Additional Data: #{@additional_data}
|
152
|
+
STRING
|
153
|
+
end
|
154
|
+
end
|