perimeter_x 1.2.0 → 1.3.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 52e3418c30763e225706ca5ee7a3a88958c4a210
4
- data.tar.gz: 4671a1eb52edc4cfdb9ba4e85932c2dcb7e33dbb
3
+ metadata.gz: 1f3a1b696f592b2f1478cc376a96475a5d5f0778
4
+ data.tar.gz: e859865af132477c953d6aed1a7c70bb927b432e
5
5
  SHA512:
6
- metadata.gz: 13ca73c6ac3c22ff0d9d93d42cab42049a06e458f9596538cd2225a07dd1ea9e20db8bf4c6921519ed29c349e717eb929643051ef1510337b1414bf6ea8f6c01
7
- data.tar.gz: 2bc1a8fe0845f3899537bb34151c545ea7c6a5343b271c7bceb4d221a9551d74dec174042e30b02e7e29d533d7e0453235163ee0635d2275576c75ed4758efd5
6
+ metadata.gz: 1f67c50466d0a347d7d5e3dc6c30f66332a3dabb27a34152e49c36a6614fa06ce81bc02d8f50ba2e061f9fb4cef8b21e9fb684d2d2b8b138ffab87298d0e1f81
7
+ data.tar.gz: 35d39968545b296e04f02bed0bb2c67b40ca1ff6773e352533fbe06e2795ad630a838d159fe517a12702e79d5054d81ef828fa966e9278dec7fd6eaa37dd2e06
data/.gitignore CHANGED
@@ -1,4 +1,5 @@
1
1
  *.rbc
2
+ *.iml
2
3
  capybara-*.html
3
4
  .rspec
4
5
  /log
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.3
@@ -1,10 +1,11 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- perimeter_x (1.0.5)
4
+ perimeter_x (1.3.0)
5
5
  activesupport (>= 4.2.0)
6
- httpclient (= 2.8.2.4)
6
+ concurrent-ruby (~> 1.0, >= 1.0.5)
7
7
  mustache (~> 1.0, >= 1.0.3)
8
+ typhoeus (~> 1.1, >= 1.1.2)
8
9
 
9
10
  GEM
10
11
  remote: https://rubygems.org/
@@ -16,13 +17,15 @@ GEM
16
17
  tzinfo (~> 1.1)
17
18
  concurrent-ruby (1.0.5)
18
19
  diff-lcs (1.3)
19
- httpclient (2.8.2.4)
20
- i18n (0.8.1)
20
+ ethon (0.10.1)
21
+ ffi (>= 1.3.0)
22
+ ffi (1.9.18)
23
+ i18n (0.8.6)
21
24
  metaclass (0.0.4)
22
25
  minitest (5.10.1)
23
26
  mocha (1.2.1)
24
27
  metaclass (~> 0.0.1)
25
- mustache (1.0.4)
28
+ mustache (1.0.5)
26
29
  rake (10.4.2)
27
30
  rspec (3.5.0)
28
31
  rspec-core (~> 3.5.0)
@@ -38,6 +41,8 @@ GEM
38
41
  rspec-support (~> 3.5.0)
39
42
  rspec-support (3.5.0)
40
43
  thread_safe (0.3.6)
44
+ typhoeus (1.1.2)
45
+ ethon (>= 0.9.0)
41
46
  tzinfo (1.2.3)
42
47
  thread_safe (~> 0.1)
43
48
 
data/Rakefile CHANGED
@@ -3,6 +3,7 @@ begin
3
3
 
4
4
  RSpec::Core::RakeTask.new(:spec)
5
5
 
6
+ task :default => :spec
6
7
  task :test => :spec
7
8
  rescue LoadError
8
9
  # no rspec available
@@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](http://keepachangelog.com/)
6
6
  and this project adheres to [Semantic Versioning](http://semver.org/).
7
7
 
8
+ ## [1.3.0] - 2017-06-04
9
+ ### Added
10
+ - Sending client_uuid on page_requested activities
11
+ - Supporting mobile sdk
12
+ ### Fixed
13
+ - Using `request.env` instead of `env`
14
+
8
15
  ## [1.2.0] - 2017-06-04
9
16
  ### Fixed
10
17
  - Default timeouts for post api requests
@@ -27,3 +34,4 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
27
34
  - Constants on px_constants
28
35
  - Cookie Validation flow when cookie score was over the configured threshold
29
36
  - Using symbols instead of strings for requests body
37
+
@@ -1,7 +1,7 @@
1
1
  class HomeController < ApplicationController
2
2
  include PxModule
3
3
 
4
- before_filter :px_verify_request
4
+ before_action :px_verify_request
5
5
 
6
6
  def index
7
7
  end
@@ -1,3 +1,6 @@
1
+ require 'concurrent'
2
+ require 'json'
3
+ require 'base64'
1
4
  require 'perimeterx/configuration'
2
5
  require 'perimeterx/utils/px_logger'
3
6
  require 'perimeterx/utils/px_constants'
@@ -10,29 +13,46 @@ require 'perimeterx/internal/validators/perimeter_x_cookie_validator'
10
13
  require 'perimeterx/internal/validators/perimeter_x_captcha_validator'
11
14
 
12
15
  module PxModule
13
-
14
16
  # Module expose API
15
17
  def px_verify_request
16
- verified, px_ctx = PerimeterX.instance.verify(env)
18
+ verified, px_ctx = PerimeterX.instance.verify(request.env)
17
19
 
18
20
  # Invalidate _pxCaptcha, can be done only on the controller level
19
- cookies[:_pxCaptcha] = { value: "", expires: -1.minutes.from_now }
21
+ cookies[:_pxCaptcha] = {value: "", expires: -1.minutes.from_now}
20
22
 
21
- if (!verified)
23
+ unless verified
22
24
  # In case custon block handler exists
23
25
  if (PerimeterX.instance.px_config.key?(:custom_block_handler))
24
- PerimeterX.instance.px_config[:logger].debug("PxModule[px_verify_request]: custom_block_handler triggered")
26
+ PerimeterX.instance.px_config[:logger].debug('PxModule[px_verify_request]: custom_block_handler triggered')
25
27
  return instance_exec(px_ctx, &PerimeterX.instance.px_config[:custom_block_handler])
26
28
  else
27
29
  # Generate template
28
- PerimeterX.instance.px_config[:logger].debug("PxModule[px_verify_request]: sending default block page")
30
+ PerimeterX.instance.px_config[:logger].debug('PxModule[px_verify_request]: sending default block page')
29
31
  html = PxTemplateFactory.get_template(px_ctx, PerimeterX.instance.px_config)
30
- response.headers["Content-Type"] = "text/html"
32
+ response.headers['Content-Type'] = 'text/html'
31
33
  response.status = 403
32
- render :html => html
34
+ # Web handler
35
+ if px_ctx.context[:cookie_origin] == 'cookie'
36
+ PerimeterX.instance.px_config[:logger].debug('PxModule[px_verify_request]: web block')
37
+ response.headers['Content-Type'] = 'text/html'
38
+ render :html => html
39
+ else # Mobile SDK
40
+ PerimeterX.instance.px_config[:logger].debug('PxModule[px_verify_request]: mobile sdk block')
41
+ response.headers['Content-Type'] = 'application/json'
42
+ hash_json = {
43
+ :action => px_ctx.context[:block_action],
44
+ :uuid => px_ctx.context[:uuid],
45
+ :vid => px_ctx.context[:vid],
46
+ :appId => PerimeterX.instance.px_config[:app_id],
47
+ :page => Base64.strict_encode64(html),
48
+ :collectorUrl => "https://collector-#{PerimeterX.instance.px_config[:app_id]}.perimeterx.net"
49
+ }
50
+ render :json => hash_json
51
+ end
33
52
  end
34
53
  end
35
54
 
55
+ # Request was verified
36
56
  return verified
37
57
  end
38
58
 
@@ -41,7 +61,7 @@ module PxModule
41
61
  end
42
62
 
43
63
 
44
- # PerimtereX Module
64
+ # PerimeterX Module
45
65
  class PerimeterX
46
66
  @@__instance = nil
47
67
  @@mutex = Mutex.new
@@ -62,19 +82,19 @@ module PxModule
62
82
 
63
83
  def self.instance
64
84
  return @@__instance if !@@__instance.nil?
65
- raise Exception.new("Please initialize perimeter x first")
85
+ raise Exception.new('Please initialize perimeter x first')
66
86
  end
67
87
 
68
88
 
69
89
  #Instance Methods
70
90
  def verify(env)
71
91
  begin
72
- @logger.debug("PerimeterX[pxVerify]")
73
- req = ActionDispatch::Request.new(env)
92
+ @logger.debug('PerimeterX[pxVerify]')
74
93
  if (!@px_config[:module_enabled])
75
- @logger.warn("Module is disabled")
94
+ @logger.warn('Module is disabled')
76
95
  return true
77
96
  end
97
+ req = ActionDispatch::Request.new(env)
78
98
  px_ctx = PerimeterXContext.new(@px_config, req)
79
99
 
80
100
  # Captcha phase
@@ -96,7 +116,7 @@ module PxModule
96
116
  end
97
117
  rescue Exception => e
98
118
  @logger.error("#{e.backtrace.first}: #{e.message} (#{e.class})")
99
- e.backtrace.drop(1).map { |s| @logger.error("\t#{s}") }
119
+ e.backtrace.drop(1).map {|s| @logger.error("\t#{s}")}
100
120
  return true
101
121
  end
102
122
  end
@@ -111,11 +131,11 @@ module PxModule
111
131
  @px_cookie_validator = PerimeterxCookieValidator.new(@px_config)
112
132
  @px_s2s_validator = PerimeterxS2SValidator.new(@px_config, @px_http_client)
113
133
  @px_captcha_validator = PerimeterxCaptchaValidator.new(@px_config, @px_http_client)
114
- @logger.debug("PerimeterX[initialize]")
134
+ @logger.debug('PerimeterX[initialize]Z')
115
135
  end
116
136
 
117
137
  private def handle_verification(px_ctx)
118
- @logger.debug("PerimeterX[handle_verification]")
138
+ @logger.debug('PerimeterX[handle_verification]')
119
139
  @logger.debug("PerimeterX[handle_verification]: processing ended - score:#{px_ctx.context[:score]}, uuid:#{px_ctx.context[:uuid]}")
120
140
 
121
141
  score = px_ctx.context[:score]
@@ -130,12 +150,12 @@ module PxModule
130
150
  @px_activity_client.send_block_activity(px_ctx)
131
151
 
132
152
  # In case were in monitor mode, end here
133
- if(@px_config[:module_mode] == PxModule::MONITOR_MODE)
134
- @logger.debug("PerimeterX[handle_verification]: monitor mode is on, passing request")
153
+ if (@px_config[:module_mode] == PxModule::MONITOR_MODE)
154
+ @logger.debug('PerimeterX[handle_verification]: monitor mode is on, passing request')
135
155
  return true
136
156
  end
137
157
 
138
- @logger.debug("PerimeterX[handle_verification]: verification ended, the request should be blocked")
158
+ @logger.debug('PerimeterX[handle_verification]: verification ended, the request should be blocked')
139
159
 
140
160
  return false, px_ctx
141
161
  end
@@ -20,7 +20,7 @@ module PxModule
20
20
  :api_connect_timeout => 1,
21
21
  :api_timeout => 1,
22
22
  :max_buffer_len => 10,
23
- :send_page_activities => false,
23
+ :send_page_activities => true,
24
24
  :send_block_activities => true,
25
25
  :sdk_name => PxModule::SDK_NAME,
26
26
  :debug => false,
@@ -31,7 +31,7 @@ module PxModule
31
31
 
32
32
  def initialize(params)
33
33
  PX_DEFAULT[:perimeterx_server_host] = "https://sapi-#{params[:app_id].downcase}.perimeterx.net"
34
- @configuration = PX_DEFAULT.merge(params);
34
+ @configuration = PX_DEFAULT.merge(params)
35
35
  @configuration[:logger] = PxLogger.new(@configuration[:debug])
36
36
  end
37
37
  end
@@ -18,6 +18,8 @@ module PxModule
18
18
  end
19
19
 
20
20
  details[:module_version] = @px_config[:sdk_name]
21
+ details[:cookie_origin] = px_ctx.context[:cookie_origin]
22
+
21
23
  px_data = {
22
24
  :type => activity_type,
23
25
  :headers => format_headers(px_ctx),
@@ -53,9 +55,9 @@ module PxModule
53
55
  end
54
56
 
55
57
  details = {
56
- :block_uuid => px_ctx.context[:uuid],
57
- :block_score => px_ctx.context[:score],
58
- :block_reason => px_ctx.context[:block_reason]
58
+ :block_uuid => px_ctx.context[:uuid],
59
+ :block_score => px_ctx.context[:score],
60
+ :block_reason => px_ctx.context[:blocking_reason]
59
61
  }
60
62
 
61
63
  send_to_perimeterx(PxModule::BLOCK_ACTIVITY, px_ctx, details)
@@ -70,7 +72,8 @@ module PxModule
70
72
 
71
73
  details = {
72
74
  :http_version => px_ctx.context[:http_version],
73
- :http_method => px_ctx.context[:http_method]
75
+ :http_method => px_ctx.context[:http_method],
76
+ :client_uuid => px_ctx.context[:uuid]
74
77
  }
75
78
 
76
79
  if (px_ctx.context.key?(:decoded_cookie))
@@ -1,5 +1,5 @@
1
1
  module PxModule
2
- class PerimeterxCookieV1 < PerimeterxCookie
2
+ class PerimeterxCookieV1 < PerimeterxPayload
3
3
 
4
4
  attr_accessor :px_config, :px_ctx
5
5
 
@@ -1,5 +1,5 @@
1
1
  module PxModule
2
- class PerimeterxCookieV3 < PerimeterxCookie
2
+ class PerimeterxCookieV3 < PerimeterxPayload
3
3
 
4
4
  attr_accessor :px_config, :px_ctx, :cookie_hash
5
5
 
@@ -4,7 +4,7 @@ require 'openssl'
4
4
  require 'perimeterx/internal/exceptions/px_cookie_decryption_exception'
5
5
 
6
6
  module PxModule
7
- class PerimeterxCookie
7
+ class PerimeterxPayload
8
8
  attr_accessor :px_cookie, :px_config, :px_ctx, :cookie_secret, :decoded_cookie
9
9
 
10
10
  def initialize(px_config)
@@ -13,7 +13,12 @@ module PxModule
13
13
  end
14
14
 
15
15
  def self.px_cookie_factory(px_ctx, px_config)
16
- if (px_ctx.context[:px_cookie].key?(:v3))
16
+ if px_ctx.context[:cookie_origin] == 'header'
17
+ if (px_ctx.context[:px_cookie].key?(:v3))
18
+ return PerimeterxTokenV3.new(px_config,px_ctx)
19
+ end
20
+ return PerimeterxTokenV1.new(px_config,px_ctx)
21
+ elsif (px_ctx.context[:px_cookie].key?(:v3))
17
22
  return PerimeterxCookieV3.new(px_config, px_ctx)
18
23
  end
19
24
  return PerimeterxCookieV1.new(px_config, px_ctx)
@@ -131,8 +136,8 @@ module PxModule
131
136
  hmac = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA256.new, @cookie_secret, hmac_str)
132
137
  # ref: https://thisdata.com/blog/timing-attacks-against-string-comparison/
133
138
  password_correct = ActiveSupport::SecurityUtils.secure_compare(
134
- ::Digest::SHA256.hexdigest(cookie_hmac),
135
- ::Digest::SHA256.hexdigest(hmac)
139
+ ::Digest::SHA256.hexdigest(cookie_hmac),
140
+ ::Digest::SHA256.hexdigest(hmac)
136
141
  )
137
142
 
138
143
  end
@@ -0,0 +1,38 @@
1
+ module PxModule
2
+ class PerimeterxTokenV1 < PerimeterxPayload
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('PerimeterxTokenV1[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
+ hmac_str = "#{cookie_time}#{@decoded_cookie[:s][:a]}#{cookie_score}#{cookie_uuid}#{cookie_vid}"
32
+
33
+ return hmac_valid?(hmac_str, cookie_hmac)
34
+ end
35
+
36
+ end
37
+
38
+ end
@@ -0,0 +1,36 @@
1
+ module PxModule
2
+ class PerimeterxTokenV3 < PerimeterxPayload
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('PerimeterxTokenV3[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
+ return hmac_valid?(@px_cookie, cookie_hmac)
34
+ end
35
+ end
36
+ end
@@ -7,31 +7,42 @@ module PxModule
7
7
  attr_accessor :px_config
8
8
 
9
9
  def initialize(px_config, req)
10
- @logger = px_config[:logger];
11
- @logger.debug("PerimeterXContext[initialize] ")
10
+ @logger = px_config[:logger]
11
+ @logger.debug('PerimeterXContext[initialize]')
12
12
  @context = Hash.new
13
13
 
14
14
  @context[:px_cookie] = Hash.new
15
15
  @context[:headers] = Hash.new
16
+ @context[:cookie_origin] = 'cookie'
16
17
  cookies = req.cookies
17
- if (!cookies.empty?)
18
+
19
+ # Get token from header
20
+ if req.headers[PxModule::TOKEN_HEADER]
21
+ @context[:cookie_origin] = 'header'
22
+ token = req.headers[PxModule::TOKEN_HEADER]
23
+ if token.include? ':'
24
+ exploded_token = token.split(':', 2)
25
+ cookie_sym = "v#{exploded_token[0]}".to_sym
26
+ @context[:px_cookie][cookie_sym] = exploded_token[1]
27
+ end
28
+ elsif !cookies.empty? # Get cookie from jar
18
29
  # Prepare hashed cookies
19
30
  cookies.each do |k, v|
20
31
  case k.to_s
21
- when "_px3"
32
+ when '_px3'
22
33
  @context[:px_cookie][:v3] = v
23
- when "_px"
34
+ when '_px'
24
35
  @context[:px_cookie][:v1] = v
25
- when "_pxCaptcha"
36
+ when '_pxCaptcha'
26
37
  @context[:px_captcha] = v
27
38
  end
28
39
  end #end case
29
40
  end #end empty cookies
30
41
 
31
42
  req.headers.each do |k, v|
32
- if (k.start_with? "HTTP_")
33
- header = k.to_s.gsub("HTTP_", "")
34
- header = header.gsub("_", "-").downcase
43
+ if (k.start_with? 'HTTP_')
44
+ header = k.to_s.gsub('HTTP_', '')
45
+ header = header.gsub('_', '-').downcase
35
46
  @context[:headers][header.to_sym] = v
36
47
  end
37
48
  end #end headers foreach
@@ -52,9 +63,9 @@ module PxModule
52
63
  end
53
64
 
54
65
  if req.server_protocol
55
- httpVer = req.server_protocol.split("/")
66
+ httpVer = req.server_protocol.split('/')
56
67
  if httpVer.size > 0
57
- @context[:http_version] = httpVer[1];
68
+ @context[:http_version] = httpVer[1]
58
69
  end
59
70
  end
60
71
  @context[:http_method] = req.method
@@ -65,19 +76,19 @@ module PxModule
65
76
  sensitive_routes.each do |sensitive_route|
66
77
  return true if uri.start_with? sensitive_route
67
78
  end
68
- return false
79
+ false
69
80
  end
70
81
 
71
82
  def set_block_action_type(action)
72
83
  @context[:block_action] = case action
73
- when "c"
74
- "captcha"
75
- when "b"
76
- return "block"
77
- when "j"
78
- return "challenge"
84
+ when 'c'
85
+ 'captcha'
86
+ when 'b'
87
+ return 'block'
88
+ when 'j'
89
+ return 'challenge'
79
90
  else
80
- return "captcha"
91
+ return captcha
81
92
  end
82
93
  end
83
94
 
@@ -48,7 +48,7 @@ module PxModule
48
48
 
49
49
  if (response.status_code == 200)
50
50
  response_body = eval(response.body)
51
- if ( response_body[:status] == 0 )
51
+ if ( response_body[:code] == 0 )
52
52
  captcha_validated = true
53
53
  end
54
54
  end
@@ -1,7 +1,9 @@
1
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'
2
+ require 'perimeterx/internal/payload/perimeter_x_payload'
3
+ require 'perimeterx/internal/payload/perimeter_x_token_v1'
4
+ require 'perimeterx/internal/payload/perimeter_x_token_v3'
5
+ require 'perimeterx/internal/payload/perimeter_x_cookie_v1'
6
+ require 'perimeterx/internal/payload/perimeter_x_cookie_v3'
5
7
 
6
8
  module PxModule
7
9
  class PerimeterxCookieValidator
@@ -24,7 +26,7 @@ module PxModule
24
26
  end
25
27
 
26
28
  # Deserialize cookie start
27
- cookie = PerimeterxCookie.px_cookie_factory(px_ctx, @px_config)
29
+ cookie = PerimeterxPayload.px_cookie_factory(px_ctx, @px_config)
28
30
  if (!cookie.deserialize())
29
31
  @logger.warn("PerimeterxCookieValidator:[verify]: invalid cookie")
30
32
  px_ctx.context[:s2s_call_reason] = PxModule::COOKIE_DECRYPTION_FAILED
@@ -46,6 +48,7 @@ module PxModule
46
48
  if (cookie.high_score?)
47
49
  @logger.warn("PerimeterxCookieValidator:[verify]: cookie high score")
48
50
  px_ctx.context[:s2s_call_reason] = PxModule::COOKIE_HIGH_SCORE
51
+ px_ctx.context[:blocking_reason] = 'cookie_high_score'
49
52
  return true, px_ctx
50
53
  end
51
54
 
@@ -54,12 +57,12 @@ module PxModule
54
57
  px_ctx.context[:s2s_call_reason] = PxModule::COOKIE_VALIDATION_FAILED
55
58
  return false, px_ctx
56
59
  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
60
+
61
+ if (px_ctx.context[:sensitive_route])
62
+ @logger.info("PerimeterxCookieValidator:[verify]: cookie was verified but route is sensitive")
63
+ px_ctx.context[:s2s_call_reason] = PxModule::SENSITIVE_ROUTE
64
+ return false, px_ctx
65
+ end
63
66
 
64
67
  @logger.debug("PerimeterxCookieValidator:[verify]: cookie validation passed succesfully")
65
68
 
@@ -5,11 +5,11 @@ module PxModule
5
5
 
6
6
  def initialize(px_config, http_client)
7
7
  super(px_config, http_client)
8
- @logger.debug("PerimeterxS2SValidator[initialize]")
8
+ @logger.debug('PerimeterxS2SValidator[initialize]')
9
9
  end
10
10
 
11
11
  def send_risk_request(px_ctx)
12
- @logger.debug("PerimeterxS2SValidator[send_risk_request]: send_risk_request")
12
+ @logger.debug('PerimeterxS2SValidator[send_risk_request]: send_risk_request')
13
13
 
14
14
  risk_mode = PxModule::RISK_MODE_ACTIVE
15
15
  if @px_config[:module_mode] == PxModule::MONITOR_MODE
@@ -26,6 +26,7 @@ module PxModule
26
26
  :additional => {
27
27
  :s2s_call_reason => px_ctx.context[:s2s_call_reason],
28
28
  :module_version => @px_config[:sdk_name],
29
+ :cookie_origin => px_ctx.context[:cookie_origin],
29
30
  :http_method => px_ctx.context[:http_method],
30
31
  :http_version => px_ctx.context[:http_version],
31
32
  :risk_mode => risk_mode
@@ -103,7 +104,7 @@ module PxModule
103
104
  if(response.code != 200)
104
105
  @logger.warn("PerimeterxS2SValidator[verify]: bad response, return code #{response.code}")
105
106
  px_ctx.context[:uuid] = ""
106
- px_ctx.context[:s2s_error_msg] = response_body[:message]
107
+ px_ctx.context[:s2s_error_msg] = !response_body || response_body[:message].nil? ? 'unknown' : response_body[:message]
107
108
  end
108
109
 
109
110
  @logger.debug("PerimeterxS2SValidator[verify]: done")
@@ -4,33 +4,35 @@ module PxModule
4
4
  # Misc
5
5
  MONITOR_MODE = 1
6
6
  ACTIVE_MODE = 2
7
- RISK_MODE_ACTIVE = "active_blocking"
8
- RISK_MODE_MONITOR = "monitor"
7
+ RISK_MODE_ACTIVE = 'active_blocking'
8
+ RISK_MODE_MONITOR = 'monitor'
9
9
  SDK_NAME = "RUBY SDK v#{PxModule::VERSION}"
10
10
 
11
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"
12
+ API_V1_S2S = '/api/v1/collector/s2s'
13
+ API_V1_CAPTCHA = '/api/v1/risk/captcha'
14
+ API_V2_RISK = '/api/v2/risk'
15
15
 
16
16
  # Activity Types
17
- BLOCK_ACTIVITY = "block"
18
- PAGE_REQUESTED_ACTIVITY = "page_requested"
17
+ BLOCK_ACTIVITY = 'block'
18
+ PAGE_REQUESTED_ACTIVITY = 'page_requested'
19
19
 
20
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"
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
28
 
29
29
  # Templates
30
- BLOCK_TEMPLATE = "block.mustache"
31
- CAPTCHA_TEMPLATE = "captcha.mustache"
30
+ BLOCK_TEMPLATE = 'block'
31
+ CAPTCHA_TEMPLATE = 'captcha'
32
+ TEMPLATE_EXT = '.mustache'
32
33
 
33
- # Tempalte Props
34
+
35
+ # Template Props
34
36
  PROP_REF_ID = :refId
35
37
  PROP_APP_ID = :appId
36
38
  PROP_VID = :vid
@@ -39,7 +41,11 @@ module PxModule
39
41
  PROP_CUSTOM_LOGO = :customLogo
40
42
  PROP_CSS_REF = :cssRef
41
43
  PROP_JS_REF = :jsRef
44
+ HOST_URL = :hostUrl
42
45
 
43
46
  VISIBLE = 'visible'
44
47
  HIDDEN = 'hidden'
48
+
49
+ # Mobile SDK
50
+ TOKEN_HEADER = 'X-PX-AUTHORIZATION'
45
51
  end
@@ -15,7 +15,7 @@ module PxModule
15
15
  @logger.debug("PxHttpClient[initialize]: HTTP client is being initilized with base_uri: #{px_config[:perimeterx_server_host]}")
16
16
  end
17
17
 
18
- # Runs a POST commant to Perimeter X servers
18
+ # Runs a POST command to Perimeter X servers
19
19
  # Params:
20
20
  # +path+:: string containing uri
21
21
  # +body+:: hash object, containing the request body, must be converted to json format
@@ -5,15 +5,20 @@ module PxModule
5
5
 
6
6
  def self.get_template(px_ctx, px_config)
7
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")
8
+ if (px_config[:challenge_enabled] && px_ctx.context[:block_action] == 'challenge')
9
+ logger.debug('PxTemplateFactory[get_template]: px challange triggered')
10
10
  return px_ctx.context[:block_action_data].html_safe
11
11
  end
12
12
 
13
- logger.debug("PxTemplateFactory[get_template]: rendering template")
14
- template_type = px_config[:captcha_enabled] ? PxModule::CAPTCHA_TEMPLATE : BLOCK_TEMPLATE
13
+ logger.debug('PxTemplateFactory[get_template]: rendering template')
14
+ template_type = px_ctx.context[:block_action] == 'captcha' ? PxModule::CAPTCHA_TEMPLATE : BLOCK_TEMPLATE
15
15
 
16
- Mustache.template_file = "#{File.dirname(__FILE__) }/templates/#{template_type}"
16
+ template_postfix = ''
17
+ if px_ctx.context[:cookie_origin] == 'header'
18
+ template_postfix = '.mobile'
19
+ end
20
+
21
+ Mustache.template_file = "#{File.dirname(__FILE__) }/templates/#{template_type}#{template_postfix}#{PxModule::TEMPLATE_EXT}"
17
22
  view = Mustache.new
18
23
 
19
24
  view[PxModule::PROP_APP_ID] = px_config[:app_id]
@@ -23,9 +28,10 @@ module PxModule
23
28
  view[PxModule::PROP_CUSTOM_LOGO] = px_config[:custom_logo]
24
29
  view[PxModule::PROP_CSS_REF] = px_config[:css_ref]
25
30
  view[PxModule::PROP_JS_REF] = px_config[:js_ref]
31
+ view[PxModule::HOST_URL] = "https://collector-#{px_config[:app_id]}.perimeterx.net"
26
32
  view[PxModule::PROP_LOGO_VISIBILITY] = px_config[:custom_logo] ? PxModule::VISIBLE : PxModule::HIDDEN
27
33
 
28
34
  return view.render.html_safe
29
35
  end
30
36
  end #end class
31
- end #end module
37
+ end #end module
@@ -0,0 +1,133 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>Access to this page has been denied.</title>
7
+ <link href="https://fonts.googleapis.com/css?family=Open+Sans:300" rel="stylesheet">
8
+ <style>
9
+ html,body{
10
+ margin: 0;
11
+ padding: 0;
12
+ font-family: 'Open Sans', sans-serif;
13
+ color: #000;
14
+ }
15
+
16
+ a{
17
+ color: #c5c5c5;
18
+ text-decoration: none;
19
+ }
20
+
21
+ .container{
22
+ align-items: center;
23
+ display: flex;
24
+ flex: 1;
25
+ justify-content: space-between;
26
+ flex-direction: column;
27
+ height: 100%;
28
+ }
29
+
30
+ .container > div {
31
+ width: 100%;
32
+ display: flex;
33
+ justify-content:center;
34
+ }
35
+
36
+ .container > div > div {
37
+ display: flex;
38
+ width: 80%;
39
+ }
40
+
41
+ .customer-logo-wrapper{
42
+ padding-top: 2rem;
43
+ flex-grow: 0;
44
+ background-color: #fff;
45
+ visibility: {{logoVisibility}};
46
+ }
47
+
48
+ .customer-logo{
49
+ border-bottom: 1px solid #000;
50
+ }
51
+
52
+ .customer-logo > img{
53
+ padding-bottom: 1rem;
54
+ max-height: 50px;
55
+ max-width: auto;
56
+ }
57
+
58
+ .page-title-wrapper{
59
+ flex-grow: 2;
60
+ }
61
+ .page-title {
62
+ flex-direction: column-reverse;
63
+ }
64
+
65
+ .content-wrapper{
66
+ flex-grow: 5;
67
+ }
68
+ .content{
69
+ flex-direction: column;
70
+ }
71
+
72
+ .page-footer-wrapper{
73
+ align-items: center;
74
+ flex-grow: 0.2;
75
+ background-color: #000;
76
+ color: #c5c5c5;
77
+ font-size: 70%;
78
+ }
79
+ @media (min-width:768px){
80
+ html,body{
81
+ height: 100%;
82
+ }
83
+ }
84
+ </style>
85
+ <!-- Custom CSS -->
86
+ {{# cssRef }}
87
+ <link rel="stylesheet" type="text/css" href="{{cssRef}}" />
88
+ {{/ cssRef }}
89
+ </head>
90
+ <body>
91
+ <section class="container">
92
+ <div class="customer-logo-wrapper">
93
+ <div class="customer-logo">
94
+ <img src="{{customLogo}}" alt="Logo"/>
95
+ </div>
96
+ </div>
97
+ <div class="page-title-wrapper">
98
+ <div class="page-title">
99
+ <h1>Access to this page has been denied.</h1>
100
+ </div>
101
+ </div>
102
+ <div class="content-wrapper">
103
+ <div class="content">
104
+ <p>
105
+ You have been blocked because we believe you are using automation tools to browse the website.
106
+ </p>
107
+ <p>
108
+ Please note that Javascript and Cookies must be enabled on your browser to access the website.
109
+ </p>
110
+ <p>
111
+ If you think you have been blocked by mistake, please contact the website administrator with the reference ID below.
112
+ </p>
113
+ <p>
114
+ Reference ID: #{{refId}}
115
+ </p>
116
+ </div>
117
+ </div>
118
+ <div class="page-footer-wrapper">
119
+ <div class="page-footer">
120
+ <p>
121
+ Powered by
122
+ <a href="https://www.perimeterx.com">PerimeterX</a>
123
+ , Inc.
124
+ </p>
125
+ </div>
126
+ </div>
127
+ </section>
128
+ <!-- Custom Script -->
129
+ {{# jsRef }}
130
+ <script src="{{jsRef}}"></script>
131
+ {{/ jsRef }}
132
+ </body>
133
+ </html>
@@ -0,0 +1,196 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>Access to this page has been denied.</title>
7
+ <link href="https://fonts.googleapis.com/css?family=Open+Sans:300" rel="stylesheet">
8
+ <style>
9
+ html,body{
10
+ margin: 0;
11
+ padding: 0;
12
+ font-family: 'Open Sans', sans-serif;
13
+ color: #000;
14
+ }
15
+
16
+ a{
17
+ color: #c5c5c5;
18
+ text-decoration: none;
19
+ }
20
+
21
+ .container{
22
+ align-items: center;
23
+ display: flex;
24
+ flex: 1;
25
+ justify-content: space-between;
26
+ flex-direction: column;
27
+ height: 100%;
28
+ }
29
+
30
+ .container > div {
31
+ width: 100%;
32
+ display: flex;
33
+ justify-content:center;
34
+ }
35
+
36
+ .container > div > div {
37
+ display: flex;
38
+ width: 80%;
39
+ }
40
+
41
+ .customer-logo-wrapper{
42
+ padding-top: 2rem;
43
+ flex-grow: 0;
44
+ background-color: #fff;
45
+ visibility: {{logoVisibility}};
46
+ }
47
+
48
+ .customer-logo{
49
+ border-bottom: 1px solid #000;
50
+ }
51
+
52
+ .customer-logo > img{
53
+ padding-bottom: 1rem;
54
+ max-height: 50px;
55
+ max-width: auto;
56
+ }
57
+
58
+ .page-title-wrapper{
59
+ flex-grow: 2;
60
+ }
61
+ .page-title {
62
+ flex-direction: column-reverse;
63
+ }
64
+
65
+ .content-wrapper{
66
+ flex-grow: 5;
67
+ }
68
+ .content{
69
+ flex-direction: column;
70
+ }
71
+
72
+ .page-footer-wrapper{
73
+ align-items: center;
74
+ flex-grow: 0.2;
75
+ background-color: #000;
76
+ color: #c5c5c5;
77
+ font-size: 70%;
78
+ }
79
+
80
+ @media (min-width:768px){
81
+ html,body{
82
+ height: 100%;
83
+ }
84
+ }
85
+ </style>
86
+ <!-- Custom CSS -->
87
+ {{#cssRef}}
88
+ <link rel="stylesheet" type="text/css" href="{{cssRef}}" />
89
+ {{/cssRef}}
90
+ <script src="https://www.google.com/recaptcha/api.js" async defer></script>
91
+ </head>
92
+
93
+ <body>
94
+ <section class="container">
95
+ <div class="customer-logo-wrapper">
96
+ <div class="customer-logo">
97
+ <img src="{{customLogo}}" alt="Logo"/>
98
+ </div>
99
+ </div>
100
+ <div class="page-title-wrapper">
101
+ <div class="page-title">
102
+ <h1>Please verify you are a human</h1>
103
+ </div>
104
+ </div>
105
+ <div class="content-wrapper">
106
+ <div class="content">
107
+ <p>
108
+ Please click "I am not a robot" to continue
109
+ </p>
110
+ <div class="g-recaptcha" data-sitekey="6Lcj-R8TAAAAABs3FrRPuQhLMbp5QrHsHufzLf7b" data-callback="handleCaptcha" data-theme="dark">
111
+ </div>
112
+ <p>
113
+ Access to this page has been denied because we believe you are using automation tools to browse the website.
114
+ </p>
115
+ <p>
116
+ This may happen as a result of the following:
117
+ </p>
118
+ <ul>
119
+ <li>
120
+ Javascript is disabled or blocked by an extension (ad blockers for example)
121
+ </li>
122
+ <li>
123
+ Your browser does not support cookies
124
+ </li>
125
+ </ul>
126
+ <p>
127
+ Please make sure that Javascript and cookies are enabled on your browser and that you are not blocking them from loading.
128
+ </p>
129
+ <p>
130
+ Reference ID: #{{refId}}
131
+ </p>
132
+ </div>
133
+ </div>
134
+ <div class="page-footer-wrapper">
135
+ <div class="page-footer">
136
+ <p>
137
+ Powered by
138
+ <a href="https://www.perimeterx.com">PerimeterX</a>
139
+ , Inc.
140
+ </p>
141
+ </div>
142
+ </div>
143
+ </section>
144
+ <!-- Captcha -->
145
+ <script>
146
+ function captchaSolved(res) {
147
+ window.location.href = '/px/captcha_callback?status=' + res.status;
148
+ }
149
+
150
+ function handleCaptcha(response) {
151
+ var appId = '{{appId}}';
152
+ var vid = '{{vid}}';
153
+ var uuid = '{{uuid}}';
154
+ var collectorUrl = '{{{hostUrl}}}';
155
+ var req = new XMLHttpRequest();
156
+ req.open('POST', collectorUrl + '/api/v1/collector/captcha');
157
+ req.setRequestHeader('Content-Type', 'application/json');
158
+ req.addEventListener('error', function() {
159
+ captchaSolved({
160
+ status: 1
161
+ });
162
+ });
163
+ req.addEventListener('cancel', function() {
164
+ captchaSolved({
165
+ status: 2
166
+ });
167
+ });
168
+ req.addEventListener('load', function() {
169
+ if (req.status == 200) {
170
+ try {
171
+ var responseJSON = JSON.parse(req.responseText);
172
+ return captchaSolved(responseJSON);
173
+ } catch (ex) {}
174
+ }
175
+ captchaSolved({
176
+ status: 3
177
+ });
178
+ });
179
+ req.send(JSON.stringify({
180
+ appId: appId,
181
+ uuid: uuid,
182
+ vid: vid,
183
+ pxCaptcha: response,
184
+ hostname: window.location.hostname,
185
+ request: {
186
+ url: window.location.href
187
+ }
188
+ }));
189
+ }
190
+ </script>
191
+ <!-- Custom Script -->
192
+ {{#jsRef}}
193
+ <script src="{{jsRef}}"></script>
194
+ {{/jsRef}}
195
+ </body>
196
+ </html>
@@ -1,3 +1,3 @@
1
1
  module PxModule
2
- VERSION = '1.2.0'
2
+ VERSION = '1.3.0'
3
3
  end
data/readme.md CHANGED
@@ -61,7 +61,7 @@ On the Rails controller include the PerimeterX SDK via the before_action and cal
61
61
  class HomeController < ApplicationController
62
62
  include PxModule
63
63
 
64
- before_filter :px_verify_request
64
+ before_action :px_verify_request
65
65
  ...
66
66
  ...
67
67
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: perimeter_x
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nitzan Goldfeder
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-06-07 00:00:00.000000000 Z
11
+ date: 2017-07-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -156,6 +156,7 @@ extra_rdoc_files:
156
156
  - changelog.md
157
157
  files:
158
158
  - ".gitignore"
159
+ - ".travis.yml"
159
160
  - Dockerfile
160
161
  - Gemfile
161
162
  - Gemfile.lock
@@ -171,10 +172,12 @@ files:
171
172
  - lib/perimeterx/internal/clients/perimeter_x_activity_client.rb
172
173
  - lib/perimeterx/internal/clients/perimeter_x_risk_client.rb
173
174
  - lib/perimeterx/internal/exceptions/px_cookie_decryption_exception.rb
175
+ - lib/perimeterx/internal/payload/perimeter_x_cookie_v1.rb
176
+ - lib/perimeterx/internal/payload/perimeter_x_cookie_v3.rb
177
+ - lib/perimeterx/internal/payload/perimeter_x_payload.rb
178
+ - lib/perimeterx/internal/payload/perimeter_x_token_v1.rb
179
+ - lib/perimeterx/internal/payload/perimeter_x_token_v3.rb
174
180
  - lib/perimeterx/internal/perimeter_x_context.rb
175
- - lib/perimeterx/internal/perimeter_x_cookie.rb
176
- - lib/perimeterx/internal/perimeter_x_cookie_v1.rb
177
- - lib/perimeterx/internal/perimeter_x_cookie_v3.rb
178
181
  - lib/perimeterx/internal/validators/perimeter_x_captcha_validator.rb
179
182
  - lib/perimeterx/internal/validators/perimeter_x_cookie_validator.rb
180
183
  - lib/perimeterx/internal/validators/perimeter_x_s2s_validator.rb
@@ -182,7 +185,9 @@ files:
182
185
  - lib/perimeterx/utils/px_http_client.rb
183
186
  - lib/perimeterx/utils/px_logger.rb
184
187
  - lib/perimeterx/utils/px_template_factory.rb
188
+ - lib/perimeterx/utils/templates/block.mobile.mustache
185
189
  - lib/perimeterx/utils/templates/block.mustache
190
+ - lib/perimeterx/utils/templates/captcha.mobile.mustache
186
191
  - lib/perimeterx/utils/templates/captcha.mustache
187
192
  - lib/perimeterx/version.rb
188
193
  - perimeter_x.gemspec