perimeter_x 1.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/.gitignore +47 -0
- data/Dockerfile +50 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +55 -0
- data/LICENSE.txt +18 -0
- data/Rakefile +9 -0
- data/changelog.md +0 -0
- data/examples/app/controllers/home_controller.rb +9 -0
- data/examples/app/views/home/index.html.erb.dist +20 -0
- data/examples/config/initializers/perimeterx.rb.dist +8 -0
- data/examples/config/routes.rb +62 -0
- data/lib/perimeter_x.rb +149 -0
- data/lib/perimeterx/configuration.rb +37 -0
- data/lib/perimeterx/internal/clients/perimeter_x_activity_client.rb +92 -0
- data/lib/perimeterx/internal/clients/perimeter_x_risk_client.rb +28 -0
- data/lib/perimeterx/internal/exceptions/px_cookie_decryption_exception.rb +5 -0
- data/lib/perimeterx/internal/perimeter_x_context.rb +82 -0
- data/lib/perimeterx/internal/perimeter_x_cookie.rb +140 -0
- data/lib/perimeterx/internal/perimeter_x_cookie_v1.rb +42 -0
- data/lib/perimeterx/internal/perimeter_x_cookie_v3.rb +37 -0
- data/lib/perimeterx/internal/validators/perimeter_x_captcha_validator.rb +65 -0
- data/lib/perimeterx/internal/validators/perimeter_x_cookie_validator.rb +69 -0
- data/lib/perimeterx/internal/validators/perimeter_x_s2s_validator.rb +110 -0
- data/lib/perimeterx/utils/px_constants.rb +42 -0
- data/lib/perimeterx/utils/px_http_client.rb +55 -0
- data/lib/perimeterx/utils/px_logger.rb +17 -0
- data/lib/perimeterx/utils/px_template_factory.rb +31 -0
- data/lib/perimeterx/utils/templates/block.mustache +146 -0
- data/lib/perimeterx/utils/templates/captcha.mustache +185 -0
- data/lib/perimeterx/version.rb +3 -0
- data/perimeter_x.gemspec +39 -0
- data/readme.md +294 -0
- metadata +192 -0
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'perimeterx/utils/px_logger'
|
2
|
+
|
3
|
+
module PxModule
|
4
|
+
class PerimeterXContext
|
5
|
+
|
6
|
+
attr_accessor :context
|
7
|
+
attr_accessor :px_config
|
8
|
+
|
9
|
+
def initialize(px_config, req)
|
10
|
+
@logger = px_config[:logger];
|
11
|
+
@logger.debug("PerimeterXContext[initialize] ")
|
12
|
+
@context = Hash.new
|
13
|
+
|
14
|
+
@context[:px_cookie] = Hash.new
|
15
|
+
@context[:headers] = Hash.new
|
16
|
+
cookies = req.cookies
|
17
|
+
if (!cookies.empty?)
|
18
|
+
# Prepare hashed cookies
|
19
|
+
cookies.each do |k, v|
|
20
|
+
case k.to_s
|
21
|
+
when "_px3"
|
22
|
+
@context[:px_cookie][:v3] = v
|
23
|
+
when "_px"
|
24
|
+
@context[:px_cookie][:v1] = v
|
25
|
+
when "_pxCaptcha"
|
26
|
+
@context[:px_captcha] = v
|
27
|
+
end
|
28
|
+
end #end case
|
29
|
+
end #end empty cookies
|
30
|
+
|
31
|
+
req.headers.each do |k, v|
|
32
|
+
if (k.start_with? "HTTP_")
|
33
|
+
header = k.to_s.gsub("HTTP_", "")
|
34
|
+
header = header.gsub("_", "-").downcase
|
35
|
+
@context[:headers][header.to_sym] = v
|
36
|
+
end
|
37
|
+
end #end headers foreach
|
38
|
+
|
39
|
+
@context[:hostname]= req.server_name
|
40
|
+
@context[:user_agent] = req.user_agent ? req.user_agent : ''
|
41
|
+
@context[:uri] = px_config[:custom_uri] ? px_config[:custom_uri].call(req) : req.headers['REQUEST_URI']
|
42
|
+
@context[:full_url] = req.original_url
|
43
|
+
@context[:score] = 0
|
44
|
+
|
45
|
+
if px_config.key?(:custom_user_ip)
|
46
|
+
@context[:ip] = req.headers[px_config[:custom_user_ip]]
|
47
|
+
elsif px_config.key?(:px_custom_user_ip_method)
|
48
|
+
@context[:ip] = px_config[:px_custom_user_ip_method].call(req)
|
49
|
+
else
|
50
|
+
@context[:ip] = req.ip
|
51
|
+
end
|
52
|
+
|
53
|
+
if req.server_protocol
|
54
|
+
httpVer = req.server_protocol.split("/")
|
55
|
+
if httpVer.size > 0
|
56
|
+
@context[:http_version] = httpVer[1];
|
57
|
+
end
|
58
|
+
end
|
59
|
+
@context[:http_method] = req.method
|
60
|
+
|
61
|
+
end #end init
|
62
|
+
|
63
|
+
def set_block_action_type(action)
|
64
|
+
@context[:block_action] = case action
|
65
|
+
when "c"
|
66
|
+
"captcha"
|
67
|
+
when "b"
|
68
|
+
return "block"
|
69
|
+
when "j"
|
70
|
+
return "challenge"
|
71
|
+
else
|
72
|
+
return "captcha"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def get_px_cookie
|
77
|
+
cookie = @context[:px_cookie].key?(:v3) ? @context[:px_cookie][:v3] : @context[:px_cookie][:v1]
|
78
|
+
return cookie.tr(' ','+') if !cookie.nil?
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
require 'active_support/security_utils'
|
2
|
+
require 'base64'
|
3
|
+
require 'openssl'
|
4
|
+
require 'perimeterx/internal/exceptions/px_cookie_decryption_exception'
|
5
|
+
|
6
|
+
module PxModule
|
7
|
+
class PerimeterxCookie
|
8
|
+
attr_accessor :px_cookie, :px_config, :px_ctx, :cookie_secret, :decoded_cookie
|
9
|
+
|
10
|
+
def initialize(px_config)
|
11
|
+
@px_config = px_config
|
12
|
+
@logger = px_config[:logger]
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.px_cookie_factory(px_ctx, px_config)
|
16
|
+
if (px_ctx.context[:px_cookie].key?(:v3))
|
17
|
+
return PerimeterxCookieV3.new(px_config, px_ctx)
|
18
|
+
end
|
19
|
+
return PerimeterxCookieV1.new(px_config, px_ctx)
|
20
|
+
end
|
21
|
+
|
22
|
+
def cookie_score
|
23
|
+
#abstract, must be implemented
|
24
|
+
raise Exception.new("Unimplemented method")
|
25
|
+
end
|
26
|
+
|
27
|
+
def cookie_hmac
|
28
|
+
#abstract, must be implemented
|
29
|
+
raise Exception.new("Unimplemented method")
|
30
|
+
end
|
31
|
+
|
32
|
+
def valid_format?(cookie)
|
33
|
+
#abstract, must be implemented
|
34
|
+
raise Exception.new("Unimplemented method")
|
35
|
+
end
|
36
|
+
|
37
|
+
def cookie_block_action
|
38
|
+
#abstract, must be implemented
|
39
|
+
raise Exception.new("Unimplemented method")
|
40
|
+
end
|
41
|
+
|
42
|
+
def secured?
|
43
|
+
#abstract, must be implemented
|
44
|
+
raise Exception.new("Unimplemented method")
|
45
|
+
end
|
46
|
+
|
47
|
+
def is_valid?
|
48
|
+
return deserialize && !expired? && secured?
|
49
|
+
end
|
50
|
+
|
51
|
+
def cookie_time
|
52
|
+
return @decoded_cookie[:t]
|
53
|
+
end
|
54
|
+
|
55
|
+
def cookie_uuid
|
56
|
+
return @decoded_cookie[:u]
|
57
|
+
end
|
58
|
+
|
59
|
+
def cookie_vid
|
60
|
+
return @decoded_cookie[:v]
|
61
|
+
end
|
62
|
+
|
63
|
+
def high_score?
|
64
|
+
return cookie_score >= @px_config[:blocking_score]
|
65
|
+
end
|
66
|
+
|
67
|
+
def expired?
|
68
|
+
return cookie_time < (Time.now.to_f*1000).floor
|
69
|
+
end
|
70
|
+
|
71
|
+
|
72
|
+
def deserialize
|
73
|
+
if (!@decoded_cookie.nil?)
|
74
|
+
return true
|
75
|
+
end
|
76
|
+
|
77
|
+
# Decode or decrypt, depends on configuration
|
78
|
+
if (@px_config[:encryption_enabled])
|
79
|
+
cookie = decrypt(@px_cookie)
|
80
|
+
else
|
81
|
+
cookie = decode(@px_cookie)
|
82
|
+
end
|
83
|
+
|
84
|
+
if (cookie.nil?)
|
85
|
+
return false
|
86
|
+
end
|
87
|
+
|
88
|
+
if (!valid_format?(cookie))
|
89
|
+
return false
|
90
|
+
end
|
91
|
+
|
92
|
+
@decoded_cookie = cookie
|
93
|
+
|
94
|
+
return true
|
95
|
+
end
|
96
|
+
|
97
|
+
|
98
|
+
def decrypt(px_cookie)
|
99
|
+
begin
|
100
|
+
if (px_cookie.nil?)
|
101
|
+
return
|
102
|
+
end
|
103
|
+
px_cookie = px_cookie.gsub(' ', '+')
|
104
|
+
salt, iterations, cipher_text = px_cookie.split(':')
|
105
|
+
iterations = iterations.to_i
|
106
|
+
salt = Base64.decode64(salt)
|
107
|
+
cipher_text = Base64.decode64(cipher_text)
|
108
|
+
digest = OpenSSL::Digest::SHA256.new
|
109
|
+
value = OpenSSL::PKCS5.pbkdf2_hmac(@px_config[:cookie_key], salt, iterations, 48, digest)
|
110
|
+
key = value[0..31]
|
111
|
+
iv = value[32..-1]
|
112
|
+
cipher = OpenSSL::Cipher::AES256.new(:CBC)
|
113
|
+
cipher.decrypt
|
114
|
+
cipher.key = key
|
115
|
+
cipher.iv = iv
|
116
|
+
plaintext = cipher.update(cipher_text) + cipher.final
|
117
|
+
|
118
|
+
return eval(plaintext)
|
119
|
+
rescue Exception => e
|
120
|
+
@logger.debug("PerimeterxCookie[decrypt]: Cookie decrypt fail #{e.message}")
|
121
|
+
raise PxCookieDecryptionException.new("Cookie decrypt fail => #{e.message}");
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def decode(px_cookie)
|
126
|
+
return eval(Base64.decode64(px_cookie))
|
127
|
+
end
|
128
|
+
|
129
|
+
|
130
|
+
def hmac_valid?(hmac_str, cookie_hmac)
|
131
|
+
hmac = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA256.new, @cookie_secret, hmac_str)
|
132
|
+
# ref: https://thisdata.com/blog/timing-attacks-against-string-comparison/
|
133
|
+
password_correct = ActiveSupport::SecurityUtils.secure_compare(
|
134
|
+
::Digest::SHA256.hexdigest(cookie_hmac),
|
135
|
+
::Digest::SHA256.hexdigest(hmac)
|
136
|
+
)
|
137
|
+
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module PxModule
|
2
|
+
class PerimeterxCookieV1 < PerimeterxCookie
|
3
|
+
|
4
|
+
attr_accessor :px_config, :px_ctx
|
5
|
+
|
6
|
+
def initialize(px_config, px_ctx)
|
7
|
+
super(px_config)
|
8
|
+
@px_ctx = px_ctx
|
9
|
+
@px_cookie = px_ctx.get_px_cookie
|
10
|
+
@cookie_secret = px_config[:cookie_key]
|
11
|
+
@logger.debug("PerimeterxCookieV1[initialize]")
|
12
|
+
end
|
13
|
+
|
14
|
+
def cookie_score
|
15
|
+
return @decoded_cookie[:s][:b]
|
16
|
+
end
|
17
|
+
|
18
|
+
def cookie_hmac
|
19
|
+
return @decoded_cookie[:h]
|
20
|
+
end
|
21
|
+
|
22
|
+
def valid_format?(cookie)
|
23
|
+
return cookie.key?(:t) && cookie.key?(:s) && cookie[:s].key?(:b) && cookie.key?(:s) && cookie.key?(:v) && cookie.key?(:h)
|
24
|
+
end
|
25
|
+
|
26
|
+
def cookie_block_action
|
27
|
+
return 'c'
|
28
|
+
end
|
29
|
+
|
30
|
+
def secured?
|
31
|
+
base_hmac_str = "#{cookie_time}#{@decoded_cookie[:s][:a]}#{cookie_score}#{cookie_uuid}#{cookie_vid}"
|
32
|
+
|
33
|
+
hmac_str_withip = "#{base_hmac_str}#{@px_ctx.context[:ip]}#{@px_ctx.context[:user_agent]}"
|
34
|
+
|
35
|
+
hmac_str_withoutip = "#{base_hmac_str}#{@px_ctx.context[:user_agent]}"
|
36
|
+
|
37
|
+
return (hmac_valid?(hmac_str_withoutip, cookie_hmac) || hmac_valid?(hmac_str_withip, cookie_hmac))
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module PxModule
|
2
|
+
class PerimeterxCookieV3 < PerimeterxCookie
|
3
|
+
|
4
|
+
attr_accessor :px_config, :px_ctx, :cookie_hash
|
5
|
+
|
6
|
+
def initialize(px_config, px_ctx)
|
7
|
+
super(px_config)
|
8
|
+
hash, cookie = px_ctx.get_px_cookie().split(':', 2)
|
9
|
+
@px_cookie = cookie
|
10
|
+
@cookie_hash = hash
|
11
|
+
@px_ctx = px_ctx
|
12
|
+
@cookie_secret = px_config[:cookie_key]
|
13
|
+
@logger.debug("PerimeterxCookieV3[initialize]")
|
14
|
+
end
|
15
|
+
|
16
|
+
def cookie_score
|
17
|
+
return @decoded_cookie[:s]
|
18
|
+
end
|
19
|
+
|
20
|
+
def cookie_hmac
|
21
|
+
return @cookie_hash
|
22
|
+
end
|
23
|
+
|
24
|
+
def valid_format?(cookie)
|
25
|
+
return cookie.key?(:t) && cookie.key?(:s) && cookie.key?(:u) && cookie.key?(:u) && cookie.key?(:a)
|
26
|
+
end
|
27
|
+
|
28
|
+
def cookie_block_action
|
29
|
+
@decoded_cookie[:a]
|
30
|
+
end
|
31
|
+
|
32
|
+
def secured?
|
33
|
+
hmac_string = "#{@px_cookie}#{@px_ctx.context[:user_agent]}"
|
34
|
+
return hmac_valid?(hmac_string, cookie_hmac)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'perimeterx/internal/clients/perimeter_x_risk_client'
|
2
|
+
|
3
|
+
module PxModule
|
4
|
+
class PerimeterxCaptchaValidator < PerimeterxRiskClient
|
5
|
+
|
6
|
+
def initialize(px_config, http_client)
|
7
|
+
super(px_config, http_client)
|
8
|
+
end
|
9
|
+
|
10
|
+
def send_captcha_request(vid, uuid, captcha, px_ctx)
|
11
|
+
|
12
|
+
request_body = {
|
13
|
+
:request => {
|
14
|
+
:ip => px_ctx.context[:ip],
|
15
|
+
:headers => format_headers(px_ctx),
|
16
|
+
:uri => px_ctx.context[:uri]
|
17
|
+
},
|
18
|
+
:pxCaptcha => captcha,
|
19
|
+
:vid => vid,
|
20
|
+
:uuid => uuid,
|
21
|
+
:hostname => px_ctx.context[:hostname]
|
22
|
+
}
|
23
|
+
|
24
|
+
# Prepare request
|
25
|
+
headers = {
|
26
|
+
"Authorization" => "Bearer #{@px_config[:auth_token]}" ,
|
27
|
+
"Content-Type" => "application/json"
|
28
|
+
};
|
29
|
+
|
30
|
+
return @http_client.post(PxModule::API_V1_CAPTCHA, request_body, headers, @px_config[:api_timeout])
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
def verify(px_ctx)
|
35
|
+
captcha_validated = false
|
36
|
+
begin
|
37
|
+
if(!px_ctx.context.key?(:px_captcha))
|
38
|
+
return captcha_validated, px_ctx
|
39
|
+
end
|
40
|
+
captcha, vid, uuid = px_ctx.context[:px_captcha].split(':', 3)
|
41
|
+
if captcha.nil? || vid.nil? || uuid.nil?
|
42
|
+
return captcha_validated, px_ctx
|
43
|
+
end
|
44
|
+
|
45
|
+
px_ctx.context[:vid] = vid
|
46
|
+
px_ctx.context[:uuid] = uuid
|
47
|
+
response = send_captcha_request(vid, uuid, captcha, px_ctx)
|
48
|
+
|
49
|
+
if (response.status_code == 200)
|
50
|
+
response_body = eval(response.body)
|
51
|
+
if ( response_body[:status] == 0 )
|
52
|
+
captcha_validated = true
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
return captcha_validated, px_ctx
|
57
|
+
|
58
|
+
rescue Exception => e
|
59
|
+
@logger.error("PerimeterxCaptchaValidator[verify]: failed, returning false")
|
60
|
+
return captcha_validated, px_ctx
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'perimeterx/utils/px_constants'
|
2
|
+
require 'perimeterx/internal/perimeter_x_cookie'
|
3
|
+
require 'perimeterx/internal/perimeter_x_cookie_v1'
|
4
|
+
require 'perimeterx/internal/perimeter_x_cookie_v3'
|
5
|
+
|
6
|
+
module PxModule
|
7
|
+
class PerimeterxCookieValidator
|
8
|
+
|
9
|
+
attr_accessor :px_config
|
10
|
+
|
11
|
+
def initialize(px_config)
|
12
|
+
@px_config = px_config
|
13
|
+
@logger = px_config[:logger]
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
def verify(px_ctx)
|
18
|
+
begin
|
19
|
+
# Case no cookie
|
20
|
+
if px_ctx.context[:px_cookie].empty?
|
21
|
+
@logger.warn("PerimeterxCookieValidator:[verify]: cookie not found")
|
22
|
+
px_ctx.context[:s2s_call_reason] = PxModule::NO_COOKIE
|
23
|
+
return false, px_ctx
|
24
|
+
end
|
25
|
+
|
26
|
+
# Deserialize cookie start
|
27
|
+
cookie = PerimeterxCookie.px_cookie_factory(px_ctx, @px_config)
|
28
|
+
if (!cookie.deserialize())
|
29
|
+
@logger.warn("PerimeterxCookieValidator:[verify]: invalid cookie")
|
30
|
+
px_ctx.context[:s2s_call_reason] = PxModule::NO_COOKIE
|
31
|
+
return false, px_ctx
|
32
|
+
end
|
33
|
+
px_ctx.context[:decoded_cookie] = cookie.decoded_cookie
|
34
|
+
px_ctx.context[:score] = cookie.cookie_score()
|
35
|
+
px_ctx.context[:uuid] = cookie.decoded_cookie[:u]
|
36
|
+
px_ctx.context[:vid] = cookie.decoded_cookie[:v]
|
37
|
+
px_ctx.context[:block_action] = px_ctx.set_block_action_type(cookie.cookie_block_action())
|
38
|
+
px_ctx.context[:cookie_hmac] = cookie.cookie_hmac()
|
39
|
+
|
40
|
+
if (cookie.expired?)
|
41
|
+
@logger.warn("PerimeterxCookieValidator:[verify]: cookie expired")
|
42
|
+
px_ctx.context[:s2s_call_reason] = PxModule::EXPIRED_COOKIE
|
43
|
+
return false, px_ctx
|
44
|
+
end
|
45
|
+
|
46
|
+
if (cookie.high_score?)
|
47
|
+
@logger.warn("PerimeterxCookieValidator:[verify]: cookie high score")
|
48
|
+
px_ctx.context[:s2s_call_reason] = PxModule::COOKIE_HIGH_SCORE
|
49
|
+
return false, px_ctx
|
50
|
+
end
|
51
|
+
|
52
|
+
if (!cookie.secured?)
|
53
|
+
@logger.warn("PerimeterxCookieValidator:[verify]: cookie invalid hmac")
|
54
|
+
px_ctx.context[:s2s_call_reason] = PxModule::COOKIE_VALIDATION_FAILED
|
55
|
+
return false, px_ctx
|
56
|
+
end
|
57
|
+
|
58
|
+
@logger.debug("PerimeterxCookieValidator:[verify]: cookie validation passed succesfully")
|
59
|
+
|
60
|
+
return true, px_ctx
|
61
|
+
rescue Exception => e
|
62
|
+
@logger.error("PerimeterxCookieValidator:[verify]: exception while verifying cookie => #{e.message}")
|
63
|
+
px_ctx.context[:s2s_call_reason] = PxModule::COOKIE_DECRYPTION_FAILED
|
64
|
+
return false, px_ctx
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
require 'perimeterx/internal/clients/perimeter_x_risk_client'
|
2
|
+
|
3
|
+
module PxModule
|
4
|
+
class PerimeterxS2SValidator < PerimeterxRiskClient
|
5
|
+
|
6
|
+
def initialize(px_config, http_client)
|
7
|
+
super(px_config, http_client)
|
8
|
+
@logger.debug("PerimeterxS2SValidator[initialize]")
|
9
|
+
end
|
10
|
+
|
11
|
+
def send_risk_request(px_ctx)
|
12
|
+
@logger.debug("PerimeterxS2SValidator[send_risk_request]: send_risk_request")
|
13
|
+
|
14
|
+
risk_mode = PxModule::RISK_MODE_ACTIVE
|
15
|
+
if @px_config[:module_mode] == PxModule::MONITOR_MODE
|
16
|
+
risk_mode = PxModule::RISK_MODE_MONITOR
|
17
|
+
end
|
18
|
+
|
19
|
+
request_body = {
|
20
|
+
'request' => {
|
21
|
+
'ip' => px_ctx.context[:ip],
|
22
|
+
'headers' => format_headers(px_ctx),
|
23
|
+
'uri' => px_ctx.context[:uri],
|
24
|
+
'url' => px_ctx.context[:full_url]
|
25
|
+
},
|
26
|
+
'additional' => {
|
27
|
+
's2s_call_reason' => px_ctx.context[:s2s_call_reason],
|
28
|
+
'module_version' => @px_config[:sdk_name],
|
29
|
+
'http_method' => px_ctx.context[:http_method],
|
30
|
+
'http_version' => px_ctx.context[:http_version],
|
31
|
+
'risk_mode' => risk_mode
|
32
|
+
}
|
33
|
+
}
|
34
|
+
#Check for hmac
|
35
|
+
if px_ctx.context.key?(:cookie_hmac)
|
36
|
+
request_body[:additional][:px_cookie_hmac] = px_ctx.context[:cookie_hmac]
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
#Check for VID
|
41
|
+
if px_ctx.context.key?(:vid)
|
42
|
+
request_body[:vid] = px_ctx.context[:vid]
|
43
|
+
end
|
44
|
+
|
45
|
+
#Check for uuid
|
46
|
+
if px_ctx.context.key?(:uuid)
|
47
|
+
request_body[:uuid] = px_ctx.context[:uuid]
|
48
|
+
end
|
49
|
+
|
50
|
+
#S2S Call reason
|
51
|
+
decode_cookie_reasons = ['cookie_expired', 'cookie_validation_failed']
|
52
|
+
if decode_cookie_reasons.include? (px_ctx.context[:s2s_call_reason])
|
53
|
+
if (px_ctx.context.key?(:decoded_cookie))
|
54
|
+
request_body[:additional][:px_cookie] = px_ctx.context[:decoded_cookie]
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Prepare request
|
59
|
+
headers = {
|
60
|
+
"Authorization" => "Bearer #{@px_config[:auth_token]}" ,
|
61
|
+
"Content-Type" => "application/json"
|
62
|
+
};
|
63
|
+
|
64
|
+
# Custom risk handler
|
65
|
+
if (risk_mode == PxModule::ACTIVE_MODE && @px_config.key?(:custom_risk_handler))
|
66
|
+
response = @px_config[:custom_risk_handler].call(PxModule::API_V2_RISK, request_body, headers, @px_config[:api_timeout])
|
67
|
+
else
|
68
|
+
response = @http_client.post(PxModule::API_V2_RISK , request_body, headers)
|
69
|
+
end
|
70
|
+
return response
|
71
|
+
end
|
72
|
+
|
73
|
+
def verify(px_ctx)
|
74
|
+
@logger.debug("PerimeterxS2SValidator[verify]")
|
75
|
+
response = send_risk_request(px_ctx)
|
76
|
+
if (!response)
|
77
|
+
return px_ctx
|
78
|
+
end
|
79
|
+
px_ctx.context[:made_s2s_risk_api_call] = true
|
80
|
+
|
81
|
+
# From here response should be valid, if success or error
|
82
|
+
response_body = eval(response.content);
|
83
|
+
# When success
|
84
|
+
if (response.status == 200 && response_body.key?(:score) && response_body.key?(:action))
|
85
|
+
@logger.debug("PerimeterxS2SValidator[verify]: response ok")
|
86
|
+
score = response_body[:score]
|
87
|
+
px_ctx.context[:score] = score
|
88
|
+
px_ctx.context[:uuid] = response_body[:uuid]
|
89
|
+
px_ctx.context[:block_action] = px_ctx.set_block_action_type(response_body[:action])
|
90
|
+
if (response_body[:action] == 'j' && response_body.key?(:action_data) && response_body[:action_data].key?(:body))
|
91
|
+
px_ctx.context[:block_action_data] = response_body[:action_data][:body]
|
92
|
+
px_ctx.context[:blocking_reason] = 'challenge'
|
93
|
+
elsif (score >= @px_config[:blocking_score])
|
94
|
+
px_ctx.context[:blocking_reason] = 's2s_high_score'
|
95
|
+
end #end challange or blocking score
|
96
|
+
end #end success response
|
97
|
+
|
98
|
+
# When error
|
99
|
+
if(response.status != 200)
|
100
|
+
@logger.warn("PerimeterxS2SValidator[verify]: bad response, return code #{response.code}")
|
101
|
+
px_ctx.context[:uuid] = ""
|
102
|
+
px_ctx.context[:s2s_error_msg] = response_body[:message]
|
103
|
+
end
|
104
|
+
|
105
|
+
@logger.debug("PerimeterxS2SValidator[verify]: done")
|
106
|
+
return px_ctx
|
107
|
+
end #end method
|
108
|
+
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module PxModule
|
2
|
+
# Misc
|
3
|
+
MONITOR_MODE = 1
|
4
|
+
ACTIVE_MODE = 2
|
5
|
+
RISK_MODE_ACTIVE = "active_blocking"
|
6
|
+
RISK_MODE_MONITOR = "monitor"
|
7
|
+
SDK_NAME = "RUBY SDK v#{PxModule::VERSION}"
|
8
|
+
|
9
|
+
# Routes
|
10
|
+
API_V1_S2S = "/api/v1/collector/s2s"
|
11
|
+
API_V1_CAPTCHA = "/api/v1/risk/captcha"
|
12
|
+
API_V2_RISK = "/api/v2/risk"
|
13
|
+
|
14
|
+
# Activity Types
|
15
|
+
BLOCK_ACTIVITY = "block"
|
16
|
+
PAGE_REQUESTED_ACTIVITY = "page_requested"
|
17
|
+
|
18
|
+
# PxContext
|
19
|
+
NO_COOKIE = "no_cookie"
|
20
|
+
INVALID_COOKIE = "invalid cookie"
|
21
|
+
EXPIRED_COOKIE = "cookie_expired"
|
22
|
+
COOKIE_HIGH_SCORE = "cookie_high_score"
|
23
|
+
COOKIE_VALIDATION_FAILED = "cookie_validation_failed"
|
24
|
+
COOKIE_DECRYPTION_FAILED = "cookie_decryption_failed"
|
25
|
+
|
26
|
+
# Templates
|
27
|
+
BLOCK_TEMPLATE = "block.mustache"
|
28
|
+
CAPTCHA_TEMPLATE = "captcha.mustache"
|
29
|
+
|
30
|
+
# Tempalte Props
|
31
|
+
PROP_REF_ID = :refId
|
32
|
+
PROP_APP_ID = :appId
|
33
|
+
PROP_VID = :vid
|
34
|
+
PROP_UUID = :uuid
|
35
|
+
PROP_LOGO_VISIBILITY = :logoVisibility
|
36
|
+
PROP_CUSTOM_LOGO = :customLogo
|
37
|
+
PROP_CSS_REF = :cssRef
|
38
|
+
PROP_JS_REF = :jsRef
|
39
|
+
|
40
|
+
VISIBLE = 'visible'
|
41
|
+
HIDDEN = 'hidden'
|
42
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require "perimeterx/utils/px_logger"
|
2
|
+
require "httpclient"
|
3
|
+
|
4
|
+
module PxModule
|
5
|
+
class PxHttpClient
|
6
|
+
attr_accessor :px_config
|
7
|
+
attr_accessor :BASE_URL
|
8
|
+
attr_accessor :http_client
|
9
|
+
|
10
|
+
def initialize(px_config)
|
11
|
+
@px_config = px_config
|
12
|
+
@http_client = HTTPClient.new(:base_url => px_config[:perimeterx_server_host])
|
13
|
+
@logger = px_config[:logger]
|
14
|
+
@logger.debug("PxHttpClient[initialize]: HTTP client is being initilized with base_uri: #{px_config[:perimeterx_server_host]}")
|
15
|
+
end
|
16
|
+
|
17
|
+
def post(path, body, headers, api_timeout = 0, timeoute = 0)
|
18
|
+
s = Time.now
|
19
|
+
begin
|
20
|
+
@logger.debug("PxHttpClient[post]: posting to #{path} headers {#{headers.to_json()}} body: {#{body.to_json()}} ")
|
21
|
+
response = @http_client.post(path,
|
22
|
+
:header => headers,
|
23
|
+
:body => body.to_json(),
|
24
|
+
:timeout => api_timeout
|
25
|
+
)
|
26
|
+
rescue Net::OpenTimeout, Net::ReadTimeout => error
|
27
|
+
@logger.warn("PerimeterxS2SValidator[verify]: request timedout")
|
28
|
+
return false
|
29
|
+
end
|
30
|
+
e = Time.now
|
31
|
+
@logger.debug("PxHttpClient[post]: runtime: #{e-s}")
|
32
|
+
return response
|
33
|
+
end
|
34
|
+
|
35
|
+
def async_post(path, body, headers, api_timeout = 0, timeoute = 0)
|
36
|
+
@logger.debug("PxHttpClient[async_post]: posting to #{path} headers {#{headers.to_json()}} body: {#{body.to_json()}} ")
|
37
|
+
s = Time.now
|
38
|
+
begin
|
39
|
+
@logger.debug("PxHttpClient[post]: posting to #{path} headers {#{headers.to_json()}} body: {#{body.to_json()}} ")
|
40
|
+
response = @http_client.post_async(path,
|
41
|
+
:header => headers,
|
42
|
+
:body => body.to_json(),
|
43
|
+
:timeout => api_timeout
|
44
|
+
)
|
45
|
+
rescue Net::OpenTimeout, Net::ReadTimeout => error
|
46
|
+
@logger.warn("PerimeterxS2SValidator[verify]: request timedout")
|
47
|
+
return false
|
48
|
+
end
|
49
|
+
e = Time.now
|
50
|
+
@logger.debug("PxHttpClient[post]: runtime: #{e-s}")
|
51
|
+
return response
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'mustache'
|
2
|
+
require 'perimeterx/utils/px_constants'
|
3
|
+
module PxModule
|
4
|
+
module PxTemplateFactory
|
5
|
+
|
6
|
+
def self.get_template(px_ctx, px_config)
|
7
|
+
logger = px_config[:logger]
|
8
|
+
if (px_config[:challenge_enabled] && px_ctx.context[:block_action] == "challenge")
|
9
|
+
logger.debug("PxTemplateFactory[get_template]: px challange triggered")
|
10
|
+
return px_ctx.context[:block_action_data].html_safe
|
11
|
+
end
|
12
|
+
|
13
|
+
logger.debug("PxTemplateFactory[get_template]: rendering template")
|
14
|
+
template_type = px_config[:captcha_enabled] ? PxModule::CAPTCHA_TEMPLATE : BLOCK_TEMPLATE
|
15
|
+
|
16
|
+
Mustache.template_file = "#{File.dirname(__FILE__) }/templates/#{template_type}"
|
17
|
+
view = Mustache.new
|
18
|
+
|
19
|
+
view[PxModule::PROP_APP_ID] = px_config[:app_id]
|
20
|
+
view[PxModule::PROP_REF_ID] = px_ctx.context[:uuid]
|
21
|
+
view[PxModule::PROP_VID] = px_ctx.context[:vid]
|
22
|
+
view[PxModule::PROP_UUID] = px_ctx.context[:uuid]
|
23
|
+
view[PxModule::PROP_CUSTOM_LOGO] = px_config[:custom_logo]
|
24
|
+
view[PxModule::PROP_CSS_REF] = px_config[:css_ref]
|
25
|
+
view[PxModule::PROP_JS_REF] = px_config[:js_ref]
|
26
|
+
view[PxModule::PROP_LOGO_VISIBILITY] = px_config[:custom_logo] ? PxModule::VISIBLE : PxModule::HIDDEN
|
27
|
+
|
28
|
+
return view.render.html_safe
|
29
|
+
end
|
30
|
+
end #end class
|
31
|
+
end #end module
|