perimeter_x 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|