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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +5 -3
  3. data/Dockerfile +5 -3
  4. data/Gemfile +1 -1
  5. data/Gemfile.lock +44 -2
  6. data/LICENSE.txt +9 -12
  7. data/Rakefile +9 -2
  8. data/changelog.md +20 -0
  9. data/examples/app/controllers/home_controller.rb +9 -0
  10. data/examples/app/views/home/index.html.erb.dist +20 -0
  11. data/examples/config/initializers/perimeterx.rb.dist +8 -0
  12. data/examples/{routes.rb → config/routes.rb} +0 -0
  13. data/lib/perimeter_x.rb +109 -33
  14. data/lib/perimeterx/configuration.rb +25 -17
  15. data/lib/perimeterx/internal/clients/perimeter_x_activity_client.rb +92 -0
  16. data/lib/perimeterx/internal/clients/perimeter_x_risk_client.rb +28 -0
  17. data/lib/perimeterx/internal/exceptions/px_cookie_decryption_exception.rb +5 -0
  18. data/lib/perimeterx/internal/perimeter_x_context.rb +81 -53
  19. data/lib/perimeterx/internal/perimeter_x_cookie.rb +140 -0
  20. data/lib/perimeterx/internal/perimeter_x_cookie_v1.rb +42 -0
  21. data/lib/perimeterx/internal/perimeter_x_cookie_v3.rb +37 -0
  22. data/lib/perimeterx/internal/validators/perimeter_x_captcha_validator.rb +65 -0
  23. data/lib/perimeterx/internal/validators/perimeter_x_cookie_validator.rb +76 -0
  24. data/lib/perimeterx/internal/validators/perimeter_x_s2s_validator.rb +114 -0
  25. data/lib/perimeterx/utils/px_constants.rb +45 -0
  26. data/lib/perimeterx/utils/px_http_client.rb +47 -26
  27. data/lib/perimeterx/utils/px_logger.rb +12 -6
  28. data/lib/perimeterx/utils/px_template_factory.rb +31 -0
  29. data/lib/perimeterx/utils/templates/block.mustache +146 -0
  30. data/lib/perimeterx/utils/templates/captcha.mustache +185 -0
  31. data/lib/perimeterx/version.rb +2 -2
  32. data/perimeter_x.gemspec +6 -1
  33. data/readme.md +218 -34
  34. metadata +90 -11
  35. data/bin/console +0 -14
  36. data/bin/setup +0 -8
  37. data/examples/home_controller.rb.dist +0 -23
  38. data/lib/perimeterx/internal/perimeter_x_risk_client.rb +0 -29
  39. 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
- class PxHttpClient
5
- L = PxLogger.instance
6
- attr_accessor :px_config
7
- attr_accessor :BASE_URL
8
- attr_accessor :http_client
4
+ module PxModule
5
+ class PxHttpClient
6
+ attr_accessor :px_config
7
+ attr_accessor :BASE_URL
8
+ attr_accessor :http_client
9
9
 
10
- def initialize(px_config)
11
- L.info("PxHttpClient[initialize]: HTTP client is being initilized with base_uri: #{px_config['perimeterx_server_host']}")
12
- @px_config = px_config
13
- @http_client = HTTPClient.new(:base_url => px_config['perimeterx_server_host'])
14
- end
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
- def post(path, body, headers, connection_timeout = 0, timeoute = 0)
17
- s = Time.now
18
- begin
19
- L.info("PxHttpClient[post]: posting to #{path} headers {#{headers.to_json()}} body: {#{body.to_json()}} ")
20
- response = @http_client.post(path,
21
- :header => headers,
22
- :body => body.to_json(),
23
- :timeout => @px_config['api_timeout']
24
- )
25
- rescue Net::OpenTimeout, Net::ReadTimeout => error
26
- L.warn("PerimeterxS2SValidator[verify]: request timedout")
27
- return false
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
- def self.instance
7
- return @@instance
8
- end
6
+ def initialize(debug)
7
+ if debug
8
+ super(STDOUT)
9
+ else
10
+ super(nil)
11
+ end
12
+
13
+ end
9
14
 
10
- private_class_method :new
15
+ end
16
+
11
17
  end