datadome_module 0.0.1
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/README.md +2 -0
- data/lib/configurable.rb +47 -0
- data/lib/constants.rb +6 -0
- data/lib/datadome_module.rb +63 -0
- data/lib/md.rb +9 -0
- data/lib/process_assessment.rb +88 -0
- data/lib/request_data.rb +163 -0
- data/lib/response.rb +20 -0
- metadata +64 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 8355aa5ad6cd96c92ca7687377a12f606187ffdd9953f654b072413fc57dbb6d
|
|
4
|
+
data.tar.gz: '01189eea068a33e18fdca611f5d2067d8250dff42572731479603cb18ee1cada'
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: ddf8bf4e80915030cb0cb35790e1349c31a5406395ce35fb56e2a09dd0773f6c3c02f53c38cc508284c4238a9e12bac453f89112dbb5d5fe5b2936ef3f92c7d2
|
|
7
|
+
data.tar.gz: c5fd0b027c5a3bc96f16a5062b63371f5d6b488e92ba232d2952e3b491f5fc2c2120bc08b4b846cf93e11cd61db5ad00a7c174c422279309f5a24568ed90cf31
|
data/README.md
ADDED
data/lib/configurable.rb
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Configurable
|
|
4
|
+
|
|
5
|
+
ALL_ENDPOINT_INCLUSION_REGEXP = "/(.)+/"
|
|
6
|
+
STATIC_PAGE_EXCLUSION_REGEXP = "\\.(avi|flv|mka|mkv|mov|mp4|mpeg|mpg|mp3|flac|ogg|ogm|opus|wav|webm|webp|bmp|gif|ico|jpeg|jpg|png|svg|svgz|swf|eot|otf|ttf|woff|woff2|css|less|js|map|json)$"
|
|
7
|
+
|
|
8
|
+
def self.included(base)
|
|
9
|
+
base.extend(ClassMethods)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
module ClassMethods
|
|
13
|
+
def configure
|
|
14
|
+
yield configuration
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def configuration
|
|
18
|
+
@configuration ||= Configuration.new
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
class Configuration
|
|
23
|
+
attr_accessor :is_datadome_assessment_enabled,
|
|
24
|
+
:logger,
|
|
25
|
+
:visible_endpoints_regex,
|
|
26
|
+
:hidden_endpoints_regex,
|
|
27
|
+
:datadome_timeout,
|
|
28
|
+
:datadome_read_timeout,
|
|
29
|
+
:custom_datadome_payload_headers,
|
|
30
|
+
:protection_api_host,
|
|
31
|
+
:custom_payload,
|
|
32
|
+
:datadome_server_side_key
|
|
33
|
+
|
|
34
|
+
def initialize
|
|
35
|
+
@is_datadome_assessment_enabled = lambda { true }
|
|
36
|
+
@logger = Logger.new($stdout)
|
|
37
|
+
@visible_endpoints_regex = lambda { ENV.fetch('DATADOME_URL_PATTERN_INCLUSION', ALL_ENDPOINT_INCLUSION_REGEXP).to_s }
|
|
38
|
+
@hidden_endpoints_regex = lambda { ENV.fetch('DATADOME_URL_PATTERN_EXCLUSION', STATIC_PAGE_EXCLUSION_REGEXP).to_s }
|
|
39
|
+
@datadome_timeout = lambda { ENV.fetch('DATADOME_TIMEOUT', 300).to_i }
|
|
40
|
+
@datadome_read_timeout = lambda { ENV.fetch('DATADOME_READ_TIMEOUT', 200).to_i }
|
|
41
|
+
@custom_datadome_payload_headers = {}
|
|
42
|
+
@custom_payload = lambda { {} }
|
|
43
|
+
@protection_api_host = ENV.fetch('DATADOME_ENDPOINT', 'api.datadome.co')
|
|
44
|
+
@datadome_server_side_key = lambda { ENV['DATADOME_SERVER_SIDE_KEY'] }
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
data/lib/constants.rb
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'faraday'
|
|
4
|
+
require 'request_data'
|
|
5
|
+
require 'response'
|
|
6
|
+
require 'process_assessment'
|
|
7
|
+
require 'configurable'
|
|
8
|
+
require 'constants'
|
|
9
|
+
require 'md'
|
|
10
|
+
|
|
11
|
+
class DataDomeModule
|
|
12
|
+
include Configurable
|
|
13
|
+
|
|
14
|
+
def initialize(app)
|
|
15
|
+
@app = app
|
|
16
|
+
@configuration = self.class.configuration
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def call(env)
|
|
20
|
+
request = ActionDispatch::Request.new(env)
|
|
21
|
+
|
|
22
|
+
assessment_result = datadome_assessment(request)
|
|
23
|
+
return @app.call(env) unless assessment_result
|
|
24
|
+
return assessment_result.response_array unless assessment_result.legitimate_request?
|
|
25
|
+
|
|
26
|
+
status, headers, payload = @app.call(env)
|
|
27
|
+
headers = headers.merge(assessment_result.headers)
|
|
28
|
+
|
|
29
|
+
[status, headers, payload]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def datadome_assessment(request)
|
|
35
|
+
return unless datadome_assessment_enabled?
|
|
36
|
+
return if endpoint_hidden_from_datadome?(request)
|
|
37
|
+
return unless endpoint_exposed_to_datadome?(request)
|
|
38
|
+
|
|
39
|
+
ProcessAssessment.for(request)
|
|
40
|
+
rescue StandardError => e
|
|
41
|
+
MD.logger.error("DataDomeModule Error: #{e.message}")
|
|
42
|
+
return
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def endpoint_exposed_to_datadome?(request)
|
|
48
|
+
visible_endpoints_regex = Regexp.new(@configuration.visible_endpoints_regex.call.to_s)
|
|
49
|
+
|
|
50
|
+
visible_endpoints_regex.match?(request.url)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def endpoint_hidden_from_datadome?(request)
|
|
54
|
+
hidden_endpoints_regex = Regexp.new(@configuration.hidden_endpoints_regex.call.to_s)
|
|
55
|
+
return false if hidden_endpoints_regex == //
|
|
56
|
+
|
|
57
|
+
hidden_endpoints_regex.match?(request.host + request.path)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def datadome_assessment_enabled?
|
|
61
|
+
@configuration.is_datadome_assessment_enabled.call
|
|
62
|
+
end
|
|
63
|
+
end
|
data/lib/md.rb
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class ProcessAssessment
|
|
4
|
+
|
|
5
|
+
attr_reader :request
|
|
6
|
+
|
|
7
|
+
def initialize(request)
|
|
8
|
+
@request = request
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def self.for(request)
|
|
12
|
+
new(request).run
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def run
|
|
16
|
+
return successful_response if datadome_server_side_key_blank?
|
|
17
|
+
|
|
18
|
+
body = RequestData.for(request, datadome_server_side_key)
|
|
19
|
+
|
|
20
|
+
dd_response = client.post(VALIDATE_REQUEST_PATH, body)
|
|
21
|
+
|
|
22
|
+
return successful_response unless POSSIBLE_STATUS_CODES.include?(dd_response.status)
|
|
23
|
+
return successful_response if dd_response.status != dd_response.headers['X-DataDomeResponse'].to_i
|
|
24
|
+
|
|
25
|
+
Response.new(
|
|
26
|
+
status: dd_response.status,
|
|
27
|
+
headers: headers_hash(dd_response),
|
|
28
|
+
payload: dd_response.body
|
|
29
|
+
)
|
|
30
|
+
rescue Faraday::TimeoutError
|
|
31
|
+
MD.logger.error("#{self.class}: Protection API request timed out")
|
|
32
|
+
successful_response
|
|
33
|
+
rescue Faraday::ConnectionFailed => e
|
|
34
|
+
MD.logger.error("#{self.class}: Protection API request connection failed: #{e.message}")
|
|
35
|
+
successful_response
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def datadome_server_side_key_blank?
|
|
41
|
+
if datadome_server_side_key.blank?
|
|
42
|
+
MD.logger.error("#{self.class}: DataDome server side key is missing")
|
|
43
|
+
|
|
44
|
+
return true
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
false
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def headers_hash(dd_response)
|
|
51
|
+
datadome_header_keys = dd_response.headers['X-DataDome-Headers']&.split || []
|
|
52
|
+
|
|
53
|
+
datadome_header_keys.each_with_object(Hash.new(0)) do |header, hash|
|
|
54
|
+
hash[header] = dd_response.headers[header]
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def client
|
|
59
|
+
Faraday.new(api_uri) do |builder|
|
|
60
|
+
builder.request :url_encoded
|
|
61
|
+
builder.headers['Content-Type'] = 'application/x-www-form-urlencoded'
|
|
62
|
+
builder.headers['User-Agent'] = 'DataDome'
|
|
63
|
+
builder.headers['X-DataDome-X-Set-Cookie'] = 'true' if request.headers['HTTP_X_DATADOME_CLIENTID'].present?
|
|
64
|
+
builder.options.timeout = milliseconds_to_seconds(DataDomeModule.configuration.datadome_timeout.call)
|
|
65
|
+
builder.options.open_timeout = milliseconds_to_seconds(DataDomeModule.configuration.datadome_read_timeout.call)
|
|
66
|
+
builder.adapter Faraday.default_adapter
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def api_uri
|
|
71
|
+
url = DataDomeModule.configuration.protection_api_host
|
|
72
|
+
url = "https://#{url}" unless url.start_with?('https://')
|
|
73
|
+
|
|
74
|
+
URI(url)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def successful_response
|
|
78
|
+
@successful_response ||= Response.new(status: 200)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def datadome_server_side_key
|
|
82
|
+
@datadome_server_side_key ||= DataDomeModule.configuration.datadome_server_side_key.call
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def milliseconds_to_seconds(milliseconds)
|
|
86
|
+
milliseconds / 1000.0
|
|
87
|
+
end
|
|
88
|
+
end
|
data/lib/request_data.rb
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class RequestData
|
|
4
|
+
|
|
5
|
+
attr_reader :request, :datadome_server_side_key
|
|
6
|
+
|
|
7
|
+
delegate :headers, :cookies, to: :request
|
|
8
|
+
|
|
9
|
+
DATA_LIMITS = {
|
|
10
|
+
SecCHDeviceMemory: 8,
|
|
11
|
+
SecCHUAMobile: 8,
|
|
12
|
+
SecFetchUser: 8,
|
|
13
|
+
TlsProtocol: 8,
|
|
14
|
+
SecCHUAArch: 16,
|
|
15
|
+
SecCHUAPlatform: 32,
|
|
16
|
+
SecFetchDest: 32,
|
|
17
|
+
SecFetchMode: 32,
|
|
18
|
+
ContentType: 64,
|
|
19
|
+
SecFetchSite: 64,
|
|
20
|
+
TlsCipher: 64,
|
|
21
|
+
AcceptCharset: 128,
|
|
22
|
+
AcceptEncoding: 128,
|
|
23
|
+
CacheControl: 128,
|
|
24
|
+
ClientID: 128,
|
|
25
|
+
Connection: 128,
|
|
26
|
+
From: 128,
|
|
27
|
+
Pragma: 128,
|
|
28
|
+
SecCHUA: 128,
|
|
29
|
+
SecCHUAModel: 128,
|
|
30
|
+
TrueClientIP: 128,
|
|
31
|
+
'X-Real-IP': 128,
|
|
32
|
+
'X-Requested-With': 128,
|
|
33
|
+
AcceptLanguage: 256,
|
|
34
|
+
SecCHUAFullVersionList: 256,
|
|
35
|
+
Via: 256,
|
|
36
|
+
Accept: 512,
|
|
37
|
+
HeadersList: 512,
|
|
38
|
+
Host: 512,
|
|
39
|
+
Origin: 512,
|
|
40
|
+
ServerHostname: 512,
|
|
41
|
+
ServerName: 512,
|
|
42
|
+
XForwardedForIP: 512,
|
|
43
|
+
UserAgent: 768,
|
|
44
|
+
Referer: 1024,
|
|
45
|
+
Request: 2048,
|
|
46
|
+
}.freeze
|
|
47
|
+
|
|
48
|
+
HEADERS_SHORTENED_FROM_ENDING = %i[XForwardedForIP].freeze
|
|
49
|
+
|
|
50
|
+
INTERNAL_MODULE_NAME = 'Ruby'.freeze
|
|
51
|
+
|
|
52
|
+
def initialize(request, datadome_server_side_key)
|
|
53
|
+
@request = request
|
|
54
|
+
@datadome_server_side_key = datadome_server_side_key
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def self.for(request, datadome_server_side_key)
|
|
58
|
+
new(request, datadome_server_side_key).run
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def run
|
|
62
|
+
URI.encode_www_form(limited_size_payload)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private
|
|
66
|
+
|
|
67
|
+
def limited_size_payload
|
|
68
|
+
limited_payload = payload.merge(custom_headers_hash).merge(DataDomeModule.configuration.custom_payload.call)
|
|
69
|
+
|
|
70
|
+
HEADERS_SHORTENED_FROM_ENDING.each do |header|
|
|
71
|
+
limited_payload[header] = slice_ending(limited_payload[header], header)
|
|
72
|
+
end
|
|
73
|
+
DATA_LIMITS.each_key do |header|
|
|
74
|
+
limited_payload[header] = limited_payload[header].byteslice(0..DATA_LIMITS[header]-1) if limited_payload[header].is_a?(String)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
limited_payload
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def payload
|
|
81
|
+
@payload ||=
|
|
82
|
+
{
|
|
83
|
+
'Key': datadome_server_side_key,
|
|
84
|
+
'Accept': headers['HTTP_ACCEPT'],
|
|
85
|
+
'AcceptCharset': headers['HTTP_ACCEPT_CHARSET'],
|
|
86
|
+
'AcceptEncoding': headers['HTTP_ACCEPT_ENCODING'],
|
|
87
|
+
'AcceptLanguage': headers['HTTP_ACCEPT_LANGUAGE'],
|
|
88
|
+
'APIConnectionState': 'new',
|
|
89
|
+
'AuthorizationLen': request.authorization&.size,
|
|
90
|
+
'CacheControl': headers['HTTP_CACHE_CONTROL'],
|
|
91
|
+
'ClientID': client_id,
|
|
92
|
+
'Connection': headers['HTTP_CONNECTION'],
|
|
93
|
+
'ContentType': headers['Content-Type'],
|
|
94
|
+
'CookiesLen': headers['HTTP_COOKIE'].to_s.size,
|
|
95
|
+
'From': headers['HTTP_FROM'],
|
|
96
|
+
'HeadersList': headers_list,
|
|
97
|
+
'Host': headers['HTTP_HOST'],
|
|
98
|
+
'IP': request.remote_ip,
|
|
99
|
+
'JA3': headers['HTTP_X_JA3_FINGERPRINT'],
|
|
100
|
+
'Method': request.method,
|
|
101
|
+
'ModuleVersion': MODULE_VERSION,
|
|
102
|
+
'Origin': headers['HTTP_ORIGIN'],
|
|
103
|
+
'Port': request.port,
|
|
104
|
+
'PostParamLen': headers['Content-Length'].to_i,
|
|
105
|
+
'Pragma': headers['HTTP_PRAGMA'],
|
|
106
|
+
'Protocol': request.protocol.gsub('://', ''),
|
|
107
|
+
'Referer': headers['HTTP_REFERER'],
|
|
108
|
+
'Request': [request.path, request.query_string].reject(&:blank?).join('?'),
|
|
109
|
+
'RequestModuleName': INTERNAL_MODULE_NAME,
|
|
110
|
+
'SecCHDeviceMemory': headers['HTTP_SEC_CH_DEVICE_MEMORY'],
|
|
111
|
+
'SecCHUA': headers['HTTP_SEC_CH_UA'],
|
|
112
|
+
'SecCHUAArch': headers['HTTP_SEC_CH_UA_ARCH'],
|
|
113
|
+
'SecCHUAFullVersionList': headers['HTTP_SEC_CH_UA_FULL_VERSION_LIST'],
|
|
114
|
+
'SecCHUAMobile': headers['HTTP_SEC_CH_UA_MOBILE'],
|
|
115
|
+
'SecCHUAModel': headers['HTTP_SEC_CH_UA_MODEL'],
|
|
116
|
+
'SecCHUAPlatform': headers['HTTP_SEC_CH_UA_PLATFORM'],
|
|
117
|
+
'SecFetchDest': headers['HTTP_SEC_FETCH_DEST'],
|
|
118
|
+
'SecFetchMode': headers['HTTP_SEC_FETCH_MODE'],
|
|
119
|
+
'SecFetchSite': headers['HTTP_SEC_FETCH_SITE'],
|
|
120
|
+
'SecFetchUser': headers['HTTP_SEC_FETCH_USER'],
|
|
121
|
+
'ServerHostname': headers['HTTP_HOST'],
|
|
122
|
+
'ServerName': Socket.gethostname,
|
|
123
|
+
'TimeRequest': (Time.zone.now.to_f * 1000 * 1000).round,
|
|
124
|
+
'TlsCipher': request.env['rack.ssl_cipher'],
|
|
125
|
+
'TlsProtocol': request.env['rack.ssl_protocol'],
|
|
126
|
+
'TrueClientIP': headers['HTTP_TRUE_CLIENT_IP'],
|
|
127
|
+
'UserAgent': headers['HTTP_USER_AGENT'],
|
|
128
|
+
'Via': headers['HTTP_VIA'],
|
|
129
|
+
'XForwardedForIP': headers['HTTP_X_FORWARDED_FOR'],
|
|
130
|
+
'X-Requested-With': headers['HTTP_X_REQUESTED_WITH'],
|
|
131
|
+
'X-Real-IP': headers['HTTP_X_REAL_IP'],
|
|
132
|
+
}.compact
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def custom_headers_hash
|
|
136
|
+
custom_header_names = DataDomeModule.configuration.custom_datadome_payload_headers
|
|
137
|
+
return {} unless custom_header_names
|
|
138
|
+
|
|
139
|
+
custom_header_names.to_a.each_with_object({}) do |header_name, hash|
|
|
140
|
+
hash[header_name[0]] = headers[header_name[1]] if headers[header_name[1]]
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def client_id
|
|
145
|
+
headers['HTTP_X_DATADOME_CLIENTID'].present? ? headers['HTTP_X_DATADOME_CLIENTID'] : cookies['datadome']
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def slice_ending(string, size_key)
|
|
149
|
+
return nil unless string
|
|
150
|
+
return string if string.bytesize <= DATA_LIMITS[size_key]
|
|
151
|
+
|
|
152
|
+
string[-DATA_LIMITS[size_key]..-1]
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def headers_list
|
|
156
|
+
return headers['X-Header-List'] if headers['X-Header-List'].present?
|
|
157
|
+
|
|
158
|
+
headers.env
|
|
159
|
+
.select { |k, _| k.in?(ActionDispatch::Http::Headers::CGI_VARIABLES) || k =~ /^HTTP_/ }
|
|
160
|
+
.map { |k, _| k }
|
|
161
|
+
.join(',')
|
|
162
|
+
end
|
|
163
|
+
end
|
data/lib/response.rb
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Response
|
|
4
|
+
|
|
5
|
+
attr_reader :headers
|
|
6
|
+
|
|
7
|
+
def initialize(status: 200, headers: {}, payload: nil)
|
|
8
|
+
@status = status
|
|
9
|
+
@headers = headers
|
|
10
|
+
@payload = payload
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def response_array
|
|
14
|
+
[@status, @headers, [@payload]]
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def legitimate_request?
|
|
18
|
+
@status == 200
|
|
19
|
+
end
|
|
20
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: datadome_module
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- DataDome
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2024-02-16 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: faraday
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '1.8'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '1.8'
|
|
27
|
+
description: Used to assess requests with DataDome Protection API.
|
|
28
|
+
email: support@datadome.co
|
|
29
|
+
executables: []
|
|
30
|
+
extensions: []
|
|
31
|
+
extra_rdoc_files: []
|
|
32
|
+
files:
|
|
33
|
+
- README.md
|
|
34
|
+
- lib/configurable.rb
|
|
35
|
+
- lib/constants.rb
|
|
36
|
+
- lib/datadome_module.rb
|
|
37
|
+
- lib/md.rb
|
|
38
|
+
- lib/process_assessment.rb
|
|
39
|
+
- lib/request_data.rb
|
|
40
|
+
- lib/response.rb
|
|
41
|
+
homepage: https://datadome.co/
|
|
42
|
+
licenses:
|
|
43
|
+
- Apache-2.0
|
|
44
|
+
metadata: {}
|
|
45
|
+
post_install_message:
|
|
46
|
+
rdoc_options: []
|
|
47
|
+
require_paths:
|
|
48
|
+
- lib
|
|
49
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '0'
|
|
54
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
55
|
+
requirements:
|
|
56
|
+
- - ">="
|
|
57
|
+
- !ruby/object:Gem::Version
|
|
58
|
+
version: '0'
|
|
59
|
+
requirements: []
|
|
60
|
+
rubygems_version: 3.4.19
|
|
61
|
+
signing_key:
|
|
62
|
+
specification_version: 4
|
|
63
|
+
summary: DataDome integration that detects and protects against bot activity.
|
|
64
|
+
test_files: []
|