perimeter_x 1.0.6.pre.alpha → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +5 -3
- data/Dockerfile +5 -3
- data/Gemfile +1 -1
- data/Gemfile.lock +44 -2
- data/LICENSE.txt +9 -12
- data/Rakefile +9 -2
- data/changelog.md +20 -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/{routes.rb → config/routes.rb} +0 -0
- data/lib/perimeter_x.rb +109 -33
- data/lib/perimeterx/configuration.rb +25 -17
- 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 +81 -53
- 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 +76 -0
- data/lib/perimeterx/internal/validators/perimeter_x_s2s_validator.rb +114 -0
- data/lib/perimeterx/utils/px_constants.rb +45 -0
- data/lib/perimeterx/utils/px_http_client.rb +47 -26
- data/lib/perimeterx/utils/px_logger.rb +12 -6
- 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 +2 -2
- data/perimeter_x.gemspec +6 -1
- data/readme.md +218 -34
- metadata +90 -11
- data/bin/console +0 -14
- data/bin/setup +0 -8
- data/examples/home_controller.rb.dist +0 -23
- data/lib/perimeterx/internal/perimeter_x_risk_client.rb +0 -29
- data/lib/perimeterx/internal/perimeter_x_s2s_validator.rb +0 -67
@@ -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,76 @@
|
|
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::COOKIE_DECRYPTION_FAILED
|
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 true, 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
|
+
if (px_ctx.context[:sensitive_route])
|
59
|
+
@logger.info("PerimeterxCookieValidator:[verify]: cookie was verified but route is sensitive")
|
60
|
+
px_ctx.context[:s2s_call_reason] = PxModule::SENSITIVE_ROUTE
|
61
|
+
return false, px_ctx
|
62
|
+
end
|
63
|
+
|
64
|
+
@logger.debug("PerimeterxCookieValidator:[verify]: cookie validation passed succesfully")
|
65
|
+
|
66
|
+
return true, px_ctx
|
67
|
+
rescue Exception => e
|
68
|
+
@logger.error("PerimeterxCookieValidator:[verify]: exception while verifying cookie => #{e.message}")
|
69
|
+
px_ctx.context[:px_orig_cookie] = cookie.px_cookie
|
70
|
+
px_ctx.context[:s2s_call_reason] = PxModule::COOKIE_DECRYPTION_FAILED
|
71
|
+
return false, px_ctx
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,114 @@
|
|
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
|
+
@logger.debug("px_ctx cookie_hmac key = #{px_ctx.context.key?(:cookie_hmac)}, value is: #{px_ctx.context[:cookie_hmac]}")
|
36
|
+
if px_ctx.context.key?(:cookie_hmac)
|
37
|
+
request_body[:additional][:px_cookie_hmac] = px_ctx.context[:cookie_hmac]
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
#Check for VID
|
42
|
+
if px_ctx.context.key?(:vid)
|
43
|
+
request_body[:vid] = px_ctx.context[:vid]
|
44
|
+
end
|
45
|
+
|
46
|
+
#Check for uuid
|
47
|
+
if px_ctx.context.key?(:uuid)
|
48
|
+
request_body[:uuid] = px_ctx.context[:uuid]
|
49
|
+
end
|
50
|
+
|
51
|
+
#S2S Call reason
|
52
|
+
decode_cookie_reasons = [PxModule::EXPIRED_COOKIE, PxModule::COOKIE_VALIDATION_FAILED]
|
53
|
+
if ( px_ctx.context[:s2s_call_reason] == PxModule::COOKIE_DECRYPTION_FAILED )
|
54
|
+
@logger.debug("PerimeterxS2SValidator[send_risk_request]: attaching px_orig_cookie to request")
|
55
|
+
request_body[:additional][:px_orig_cookie] = px_ctx.context[:px_orig_cookie]
|
56
|
+
elsif decode_cookie_reasons.include? (px_ctx.context[:s2s_call_reason])
|
57
|
+
if (px_ctx.context.key?(:decoded_cookie))
|
58
|
+
request_body[:additional][:px_cookie] = px_ctx.context[:decoded_cookie]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# Prepare request
|
63
|
+
headers = {
|
64
|
+
"Authorization" => "Bearer #{@px_config[:auth_token]}" ,
|
65
|
+
"Content-Type" => "application/json"
|
66
|
+
};
|
67
|
+
|
68
|
+
# Custom risk handler
|
69
|
+
if (risk_mode == PxModule::ACTIVE_MODE && @px_config.key?(:custom_risk_handler))
|
70
|
+
response = @px_config[:custom_risk_handler].call(PxModule::API_V2_RISK, request_body, headers, @px_config[:api_timeout])
|
71
|
+
else
|
72
|
+
response = @http_client.post(PxModule::API_V2_RISK , request_body, headers)
|
73
|
+
end
|
74
|
+
return response
|
75
|
+
end
|
76
|
+
|
77
|
+
def verify(px_ctx)
|
78
|
+
@logger.debug("PerimeterxS2SValidator[verify]")
|
79
|
+
response = send_risk_request(px_ctx)
|
80
|
+
if (!response)
|
81
|
+
return px_ctx
|
82
|
+
end
|
83
|
+
px_ctx.context[:made_s2s_risk_api_call] = true
|
84
|
+
|
85
|
+
# From here response should be valid, if success or error
|
86
|
+
response_body = eval(response.content);
|
87
|
+
# When success
|
88
|
+
if (response.status == 200 && response_body.key?(:score) && response_body.key?(:action))
|
89
|
+
@logger.debug("PerimeterxS2SValidator[verify]: response ok")
|
90
|
+
score = response_body[:score]
|
91
|
+
px_ctx.context[:score] = score
|
92
|
+
px_ctx.context[:uuid] = response_body[:uuid]
|
93
|
+
px_ctx.context[:block_action] = px_ctx.set_block_action_type(response_body[:action])
|
94
|
+
if (response_body[:action] == 'j' && response_body.key?(:action_data) && response_body[:action_data].key?(:body))
|
95
|
+
px_ctx.context[:block_action_data] = response_body[:action_data][:body]
|
96
|
+
px_ctx.context[:blocking_reason] = 'challenge'
|
97
|
+
elsif (score >= @px_config[:blocking_score])
|
98
|
+
px_ctx.context[:blocking_reason] = 's2s_high_score'
|
99
|
+
end #end challange or blocking score
|
100
|
+
end #end success response
|
101
|
+
|
102
|
+
# When error
|
103
|
+
if(response.status != 200)
|
104
|
+
@logger.warn("PerimeterxS2SValidator[verify]: bad response, return code #{response.code}")
|
105
|
+
px_ctx.context[:uuid] = ""
|
106
|
+
px_ctx.context[:s2s_error_msg] = response_body[:message]
|
107
|
+
end
|
108
|
+
|
109
|
+
@logger.debug("PerimeterxS2SValidator[verify]: done")
|
110
|
+
return px_ctx
|
111
|
+
end #end method
|
112
|
+
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'perimeterx/version'
|
2
|
+
|
3
|
+
module PxModule
|
4
|
+
# Misc
|
5
|
+
MONITOR_MODE = 1
|
6
|
+
ACTIVE_MODE = 2
|
7
|
+
RISK_MODE_ACTIVE = "active_blocking"
|
8
|
+
RISK_MODE_MONITOR = "monitor"
|
9
|
+
SDK_NAME = "RUBY SDK v#{PxModule::VERSION}"
|
10
|
+
|
11
|
+
# Routes
|
12
|
+
API_V1_S2S = "/api/v1/collector/s2s"
|
13
|
+
API_V1_CAPTCHA = "/api/v1/risk/captcha"
|
14
|
+
API_V2_RISK = "/api/v2/risk"
|
15
|
+
|
16
|
+
# Activity Types
|
17
|
+
BLOCK_ACTIVITY = "block"
|
18
|
+
PAGE_REQUESTED_ACTIVITY = "page_requested"
|
19
|
+
|
20
|
+
# PxContext
|
21
|
+
NO_COOKIE = "no_cookie"
|
22
|
+
INVALID_COOKIE = "invalid_cookie"
|
23
|
+
EXPIRED_COOKIE = "cookie_expired"
|
24
|
+
COOKIE_HIGH_SCORE = "cookie_high_score"
|
25
|
+
COOKIE_VALIDATION_FAILED = "cookie_validation_failed"
|
26
|
+
COOKIE_DECRYPTION_FAILED = "cookie_decryption_failed"
|
27
|
+
SENSITIVE_ROUTE = "sensitive_route"
|
28
|
+
|
29
|
+
# Templates
|
30
|
+
BLOCK_TEMPLATE = "block.mustache"
|
31
|
+
CAPTCHA_TEMPLATE = "captcha.mustache"
|
32
|
+
|
33
|
+
# Tempalte Props
|
34
|
+
PROP_REF_ID = :refId
|
35
|
+
PROP_APP_ID = :appId
|
36
|
+
PROP_VID = :vid
|
37
|
+
PROP_UUID = :uuid
|
38
|
+
PROP_LOGO_VISIBILITY = :logoVisibility
|
39
|
+
PROP_CUSTOM_LOGO = :customLogo
|
40
|
+
PROP_CSS_REF = :cssRef
|
41
|
+
PROP_JS_REF = :jsRef
|
42
|
+
|
43
|
+
VISIBLE = 'visible'
|
44
|
+
HIDDEN = 'hidden'
|
45
|
+
end
|
@@ -1,34 +1,55 @@
|
|
1
1
|
require "perimeterx/utils/px_logger"
|
2
2
|
require "httpclient"
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
4
|
+
module PxModule
|
5
|
+
class PxHttpClient
|
6
|
+
attr_accessor :px_config
|
7
|
+
attr_accessor :BASE_URL
|
8
|
+
attr_accessor :http_client
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
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
|
15
16
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
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
|
28
33
|
end
|
29
|
-
e = Time.now
|
30
|
-
L.info("PxHttpClient[post]: runtime: #{e-s}")
|
31
|
-
return response
|
32
|
-
end
|
33
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
|
34
55
|
end
|
@@ -1,11 +1,17 @@
|
|
1
1
|
require 'logger'
|
2
|
+
module PxModule
|
2
3
|
|
3
|
-
class PxLogger
|
4
|
-
@@instance = Logger.new(STDOUT)
|
4
|
+
class PxLogger < Logger
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
6
|
+
def initialize(debug)
|
7
|
+
if debug
|
8
|
+
super(STDOUT)
|
9
|
+
else
|
10
|
+
super(nil)
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
9
14
|
|
10
|
-
|
15
|
+
end
|
16
|
+
|
11
17
|
end
|