perimeter_x 1.1.0 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (33) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +1 -0
  3. data/.travis.yml +3 -0
  4. data/Dockerfile +19 -43
  5. data/Gemfile.lock +36 -30
  6. data/Rakefile +1 -0
  7. data/changelog.md +63 -0
  8. data/examples/app/controllers/home_controller.rb +1 -1
  9. data/lib/perimeter_x.rb +152 -70
  10. data/lib/perimeterx/configuration.rb +71 -22
  11. data/lib/perimeterx/internal/clients/perimeter_x_activity_client.rb +36 -15
  12. data/lib/perimeterx/internal/exceptions/px_config_exception.rb +6 -0
  13. data/lib/perimeterx/internal/{perimeter_x_cookie_v1.rb → payload/perimeter_x_cookie_v1.rb} +1 -1
  14. data/lib/perimeterx/internal/{perimeter_x_cookie_v3.rb → payload/perimeter_x_cookie_v3.rb} +1 -1
  15. data/lib/perimeterx/internal/{perimeter_x_cookie.rb → payload/perimeter_x_payload.rb} +12 -4
  16. data/lib/perimeterx/internal/payload/perimeter_x_token_v1.rb +38 -0
  17. data/lib/perimeterx/internal/payload/perimeter_x_token_v3.rb +36 -0
  18. data/lib/perimeterx/internal/perimeter_x_context.rb +66 -31
  19. data/lib/perimeterx/internal/validators/hash_schema_validator.rb +26 -0
  20. data/lib/perimeterx/internal/validators/perimeter_x_cookie_validator.rb +29 -21
  21. data/lib/perimeterx/internal/validators/perimeter_x_s2s_validator.rb +34 -10
  22. data/lib/perimeterx/utils/px_constants.rb +35 -17
  23. data/lib/perimeterx/utils/px_http_client.rb +29 -35
  24. data/lib/perimeterx/utils/px_template_factory.rb +18 -8
  25. data/lib/perimeterx/utils/templates/block_template.mustache +175 -0
  26. data/lib/perimeterx/utils/templates/ratelimit.mustache +9 -0
  27. data/lib/perimeterx/version.rb +1 -1
  28. data/perimeter_x.gemspec +5 -4
  29. data/readme.md +95 -32
  30. metadata +53 -24
  31. data/lib/perimeterx/internal/validators/perimeter_x_captcha_validator.rb +0 -65
  32. data/lib/perimeterx/utils/templates/block.mustache +0 -146
  33. data/lib/perimeterx/utils/templates/captcha.mustache +0 -185
@@ -0,0 +1,26 @@
1
+ def validate_hash_schema(hash, schema)
2
+ hash.each do |key, value|
3
+ if schema.key?(key) && value != nil
4
+ # validate value types in hash are according to schema
5
+ if !schema[key][:types].include?(value.class)
6
+ raise PxConfigurationException.new("PerimeterX: Type of #{key} should be one of #{schema[key][:types]} but instead is #{value.class}")
7
+ end
8
+
9
+ # validate arrays elments types are according to schema
10
+ if value.class == Array
11
+ value.each do |element|
12
+ if !schema[key][:allowed_element_types].include?(element.class)
13
+ raise PxConfigurationException.new("PerimeterX: #{key} may only contain elements of the following types: #{schema[key][:allowed_element_types]} but includes element of type #{element.class}")
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+
20
+ # validate required fields exist in hash
21
+ schema.each do |key, value|
22
+ if value[:required] && hash[key].nil?
23
+ raise PxConfigurationException.new("PerimeterX: #{key} configuration is missing")
24
+ end
25
+ end
26
+ 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
@@ -16,57 +18,63 @@ module PxModule
16
18
 
17
19
  def verify(px_ctx)
18
20
  begin
19
- # Case no cookie
20
21
  if px_ctx.context[:px_cookie].empty?
21
- @logger.warn("PerimeterxCookieValidator:[verify]: cookie not found")
22
+ @logger.warn("PerimeterxCookieValidator:[verify]: no cookie")
22
23
  px_ctx.context[:s2s_call_reason] = PxModule::NO_COOKIE
23
24
  return false, px_ctx
24
25
  end
25
-
26
+
26
27
  # Deserialize cookie start
27
- cookie = PerimeterxCookie.px_cookie_factory(px_ctx, @px_config)
28
- if (!cookie.deserialize())
28
+ cookie = PerimeterxPayload.px_cookie_factory(px_ctx, @px_config)
29
+ if !cookie.deserialize()
29
30
  @logger.warn("PerimeterxCookieValidator:[verify]: invalid cookie")
31
+ px_ctx.context[:px_orig_cookie] = px_ctx.get_px_cookie
30
32
  px_ctx.context[:s2s_call_reason] = PxModule::COOKIE_DECRYPTION_FAILED
31
33
  return false, px_ctx
32
34
  end
33
35
  px_ctx.context[:decoded_cookie] = cookie.decoded_cookie
34
36
  px_ctx.context[:score] = cookie.cookie_score()
35
37
  px_ctx.context[:uuid] = cookie.decoded_cookie[:u]
36
- px_ctx.context[:vid] = cookie.decoded_cookie[:v]
37
38
  px_ctx.context[:block_action] = px_ctx.set_block_action_type(cookie.cookie_block_action())
38
39
  px_ctx.context[:cookie_hmac] = cookie.cookie_hmac()
39
40
 
40
- if (cookie.expired?)
41
+ vid = cookie.decoded_cookie[:v]
42
+ if vid.is_a?(String) && vid.match(PxModule::VID_REGEX)
43
+ px_ctx.context[:vid_source] = "risk_cookie"
44
+ px_ctx.context[:vid] = vid
45
+ end
46
+
47
+ if cookie.expired?
41
48
  @logger.warn("PerimeterxCookieValidator:[verify]: cookie expired")
42
49
  px_ctx.context[:s2s_call_reason] = PxModule::EXPIRED_COOKIE
43
50
  return false, px_ctx
44
51
  end
45
52
 
46
- if (cookie.high_score?)
53
+ if cookie.high_score?
47
54
  @logger.warn("PerimeterxCookieValidator:[verify]: cookie high score")
48
- px_ctx.context[:s2s_call_reason] = PxModule::COOKIE_HIGH_SCORE
55
+ px_ctx.context[:blocking_reason] = 'cookie_high_score'
49
56
  return true, px_ctx
50
57
  end
51
58
 
52
- if (!cookie.secured?)
59
+ if !cookie.secured?
53
60
  @logger.warn("PerimeterxCookieValidator:[verify]: cookie invalid hmac")
54
- px_ctx.context[:s2s_call_reason] = PxModule::COOKIE_VALIDATION_FAILED
61
+ px_ctx.context[:s2s_call_reason] = PxModule:: COOKIE_VALIDATION_FAILED
62
+ return false, px_ctx
63
+ end
64
+
65
+ if px_ctx.context[:sensitive_route]
66
+ @logger.info("PerimeterxCookieValidator:[verify]: cookie was verified but route is sensitive")
67
+ px_ctx.context[:s2s_call_reason] = PxModule::SENSITIVE_ROUTE
55
68
  return false, px_ctx
56
69
  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
70
 
64
71
  @logger.debug("PerimeterxCookieValidator:[verify]: cookie validation passed succesfully")
65
72
 
73
+ px_ctx.context[:pass_reason] = 'cookie'
66
74
  return true, px_ctx
67
75
  rescue Exception => e
68
76
  @logger.error("PerimeterxCookieValidator:[verify]: exception while verifying cookie => #{e.message}")
69
- px_ctx.context[:px_orig_cookie] = cookie.px_cookie
77
+ px_ctx.context[:px_orig_cookie] = px_ctx.context[:px_cookie]
70
78
  px_ctx.context[:s2s_call_reason] = PxModule::COOKIE_DECRYPTION_FAILED
71
79
  return false, px_ctx
72
80
  end
@@ -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
@@ -31,6 +31,17 @@ module PxModule
31
31
  :risk_mode => risk_mode
32
32
  }
33
33
  }
34
+
35
+ #Check for cookie_origin
36
+ if !px_ctx.context[:px_cookie].empty?
37
+ request_body[:additional][:cookie_origin] = px_ctx.context[:cookie_origin]
38
+ end
39
+
40
+ #Override s2s_call_reason in case of mobile error
41
+ if !px_ctx.context[:mobile_error].nil?
42
+ request_body[:additional][:s2s_call_reason] = "mobile_error_#{px_ctx.context[:mobile_error]}"
43
+ end
44
+
34
45
  #Check for hmac
35
46
  @logger.debug("px_ctx cookie_hmac key = #{px_ctx.context.key?(:cookie_hmac)}, value is: #{px_ctx.context[:cookie_hmac]}")
36
47
  if px_ctx.context.key?(:cookie_hmac)
@@ -66,11 +77,19 @@ module PxModule
66
77
  };
67
78
 
68
79
  # Custom risk handler
80
+ risk_start = Time.now
69
81
  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])
82
+ response = @px_config[:custom_risk_handler].call(PxModule::API_V3_RISK, request_body, headers, @px_config[:api_timeout], @px_config[:api_timeout_connection])
71
83
  else
72
- response = @http_client.post(PxModule::API_V2_RISK , request_body, headers)
84
+ response = @http_client.post(PxModule::API_V3_RISK , request_body, headers, @px_config[:api_timeout], @px_config[:api_timeout_connection])
85
+ end
86
+
87
+ # Set risk_rtt
88
+ if(response)
89
+ risk_end = Time.now
90
+ px_ctx.context[:risk_rtt] = ((risk_end-risk_start)*1000).round
73
91
  end
92
+
74
93
  return response
75
94
  end
76
95
 
@@ -78,14 +97,15 @@ module PxModule
78
97
  @logger.debug("PerimeterxS2SValidator[verify]")
79
98
  response = send_risk_request(px_ctx)
80
99
  if (!response)
100
+ px_ctx.context[:pass_reason] = "s2s_timeout"
81
101
  return px_ctx
82
102
  end
83
103
  px_ctx.context[:made_s2s_risk_api_call] = true
84
104
 
85
105
  # From here response should be valid, if success or error
86
- response_body = eval(response.content);
106
+ response_body = eval(response.body);
87
107
  # When success
88
- if (response.status == 200 && response_body.key?(:score) && response_body.key?(:action))
108
+ if (response.code == 200 && response_body.key?(:score) && response_body.key?(:action) && response_body.key?(:status) && response_body[:status] == 0 )
89
109
  @logger.debug("PerimeterxS2SValidator[verify]: response ok")
90
110
  score = response_body[:score]
91
111
  px_ctx.context[:score] = score
@@ -96,14 +116,18 @@ module PxModule
96
116
  px_ctx.context[:blocking_reason] = 'challenge'
97
117
  elsif (score >= @px_config[:blocking_score])
98
118
  px_ctx.context[:blocking_reason] = 's2s_high_score'
119
+ else
120
+ px_ctx.context[:pass_reason] = 's2s'
99
121
  end #end challange or blocking score
100
122
  end #end success response
101
123
 
102
124
  # 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]
125
+ risk_error_status = response_body && response_body.key?(:status) && response_body[:status] == -1
126
+ if(response.code != 200 || risk_error_status)
127
+ @logger.warn("PerimeterxS2SValidator[verify]: bad response, returned code #{response.code} #{risk_error_status ? "risk status: -1" : ""}")
128
+ px_ctx.context[:pass_reason] = 'request_failed'
129
+ px_ctx.context[:uuid] = (!response_body || response_body[:uuid].nil?)? "" : response_body[:uuid]
130
+ px_ctx.context[:s2s_error_msg] = !response_body || response_body[:message].nil? ? 'unknown' : response_body[:message]
107
131
  end
108
132
 
109
133
  @logger.debug("PerimeterxS2SValidator[verify]: done")
@@ -4,33 +4,34 @@ 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_V3_RISK = '/api/v3/risk'
15
14
 
16
15
  # Activity Types
17
- BLOCK_ACTIVITY = "block"
18
- PAGE_REQUESTED_ACTIVITY = "page_requested"
16
+ BLOCK_ACTIVITY = 'block'
17
+ PAGE_REQUESTED_ACTIVITY = 'page_requested'
19
18
 
20
19
  # 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"
20
+ NO_COOKIE = 'no_cookie'
21
+ INVALID_COOKIE = 'invalid_cookie'
22
+ EXPIRED_COOKIE = 'cookie_expired'
23
+ COOKIE_HIGH_SCORE = 'cookie_high_score'
24
+ COOKIE_VALIDATION_FAILED = 'cookie_validation_failed'
25
+ COOKIE_DECRYPTION_FAILED = 'cookie_decryption_failed'
26
+ SENSITIVE_ROUTE = 'sensitive_route'
28
27
 
29
28
  # Templates
30
- BLOCK_TEMPLATE = "block.mustache"
31
- CAPTCHA_TEMPLATE = "captcha.mustache"
29
+ CHALLENGE_TEMPLATE = 'block_template'
30
+ TEMPLATE_EXT = '.mustache'
31
+ RATELIMIT_TEMPLATE = 'ratelimit'
32
32
 
33
- # Tempalte Props
33
+
34
+ # Template Props
34
35
  PROP_REF_ID = :refId
35
36
  PROP_APP_ID = :appId
36
37
  PROP_VID = :vid
@@ -39,7 +40,24 @@ module PxModule
39
40
  PROP_CUSTOM_LOGO = :customLogo
40
41
  PROP_CSS_REF = :cssRef
41
42
  PROP_JS_REF = :jsRef
43
+ PROP_BLOCK_SCRIPT = :blockScript
44
+ PROP_JS_CLIENT_SRC = :jsClientSrc
45
+ PROP_HOST_URL = :hostUrl
46
+ PROP_FIRST_PARTY_ENABLED = :firstPartyEnabled
47
+
48
+ # Hosts
49
+ CLIENT_HOST = 'client.px-cloud.net'
50
+ CAPTCHA_HOST = 'captcha.px-cloud.net'
42
51
 
43
52
  VISIBLE = 'visible'
44
53
  HIDDEN = 'hidden'
54
+
55
+ # Mobile SDK
56
+ TOKEN_HEADER = 'X-PX-AUTHORIZATION'
57
+ ORIGINAL_TOKEN_HEADER = 'X-PX-ORIGINAL-TOKEN'
58
+
59
+ # Regular Expressions
60
+ VID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/
61
+ MOBILE_TOKEN_V3_REGEX = /\A3:(.*)\z/
62
+ MOBILE_ERROR_REGEX = /\A[0-9]\z/
45
63
  end
@@ -1,53 +1,47 @@
1
- require "perimeterx/utils/px_logger"
2
- require "httpclient"
1
+ require 'perimeterx/utils/px_logger'
2
+ require 'typhoeus'
3
+ require 'concurrent'
3
4
 
4
5
  module PxModule
5
6
  class PxHttpClient
7
+ include Concurrent::Async
8
+
6
9
  attr_accessor :px_config
7
- attr_accessor :BASE_URL
8
- attr_accessor :http_client
10
+ attr_accessor :px_client
9
11
 
10
12
  def initialize(px_config)
11
13
  @px_config = px_config
12
- @http_client = HTTPClient.new(:base_url => px_config[:perimeterx_server_host])
13
14
  @logger = px_config[:logger]
14
- @logger.debug("PxHttpClient[initialize]: HTTP client is being initilized with base_uri: #{px_config[:perimeterx_server_host]}")
15
+ @logger.debug("PxHttpClient[initialize]: HTTP client is being initilized with base_uri: #{px_config[:backend_url]}")
15
16
  end
16
17
 
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
18
+ # Runs a POST command to Perimeter X servers
19
+ # Params:
20
+ # +path+:: string containing uri
21
+ # +body+:: hash object, containing the request body, must be converted to json format
22
+ # +headers+:: hash object, hold headers
23
+ # +api_timeout+:: int, sets the timeout for a request
24
+ # +connection_timeout+:: int, sets the timeout for opening a connection
34
25
 
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()}} ")
26
+ def post(path, body, headers, api_timeout = 1, connection_timeout = 1)
37
27
  s = Time.now
38
28
  begin
39
29
  @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
30
+ response = Typhoeus.post(
31
+ "#{px_config[:backend_url]}#{path}",
32
+ headers: headers,
33
+ body: body.to_json,
34
+ timeout: api_timeout,
35
+ connecttimeout: connection_timeout
36
+ )
37
+ if response.timed_out?
38
+ @logger.warn('PerimeterxS2SValidator[verify]: request timed out')
39
+ return false
40
+ end
41
+ ensure
42
+ e = Time.now
43
+ @logger.debug("PxHttpClient[post]: runtime: #{(e-s) * 1000.0}")
48
44
  end
49
- e = Time.now
50
- @logger.debug("PxHttpClient[post]: runtime: #{e-s}")
51
45
  return response
52
46
  end
53
47
 
@@ -3,19 +3,25 @@ require 'perimeterx/utils/px_constants'
3
3
  module PxModule
4
4
  module PxTemplateFactory
5
5
 
6
- def self.get_template(px_ctx, px_config)
6
+ def self.get_template(px_ctx, px_config, px_template_object)
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
15
-
16
- Mustache.template_file = "#{File.dirname(__FILE__) }/templates/#{template_type}"
17
13
  view = Mustache.new
18
14
 
15
+ if (px_ctx.context[:block_action] == 'rate_limit')
16
+ logger.debug('PxTemplateFactory[get_template]: rendering ratelimit template')
17
+ template_type = RATELIMIT_TEMPLATE
18
+ else
19
+ logger.debug('PxTemplateFactory[get_template]: rendering template')
20
+ template_type = CHALLENGE_TEMPLATE
21
+ end
22
+
23
+ Mustache.template_file = "#{File.dirname(__FILE__) }/templates/#{template_type}#{PxModule::TEMPLATE_EXT}"
24
+
19
25
  view[PxModule::PROP_APP_ID] = px_config[:app_id]
20
26
  view[PxModule::PROP_REF_ID] = px_ctx.context[:uuid]
21
27
  view[PxModule::PROP_VID] = px_ctx.context[:vid]
@@ -23,9 +29,13 @@ module PxModule
23
29
  view[PxModule::PROP_CUSTOM_LOGO] = px_config[:custom_logo]
24
30
  view[PxModule::PROP_CSS_REF] = px_config[:css_ref]
25
31
  view[PxModule::PROP_JS_REF] = px_config[:js_ref]
32
+ view[PxModule::PROP_HOST_URL] = "https://collector-#{px_config[:app_id]}.perimeterx.net"
26
33
  view[PxModule::PROP_LOGO_VISIBILITY] = px_config[:custom_logo] ? PxModule::VISIBLE : PxModule::HIDDEN
34
+ view[PxModule::PROP_BLOCK_SCRIPT] = px_template_object[:block_script]
35
+ view[PxModule::PROP_JS_CLIENT_SRC] = px_template_object[:js_client_src]
36
+ view[PxModule::PROP_FIRST_PARTY_ENABLED] = false
27
37
 
28
38
  return view.render.html_safe
29
39
  end
30
40
  end #end class
31
- end #end module
41
+ end #end module
@@ -0,0 +1,175 @@
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: 100%;
56
+ }
57
+
58
+ .page-title-wrapper {
59
+ flex-grow: 2;
60
+ }
61
+
62
+ .page-title {
63
+ flex-direction: column-reverse;
64
+ }
65
+
66
+ .content-wrapper {
67
+ flex-grow: 5;
68
+ }
69
+
70
+ .content {
71
+ flex-direction: column;
72
+ }
73
+
74
+ .page-footer-wrapper {
75
+ align-items: center;
76
+ flex-grow: 0.2;
77
+ background-color: #000;
78
+ color: #c5c5c5;
79
+ font-size: 70%;
80
+ }
81
+
82
+ @media (min-width: 768px) {
83
+ html, body {
84
+ height: 100%;
85
+ }
86
+ }
87
+ </style>
88
+ <!-- Custom CSS -->
89
+ {{#cssRef}}
90
+ <link rel="stylesheet" type="text/css" href="{{{cssRef}}}"/>
91
+ {{/cssRef}}
92
+ </head>
93
+
94
+ <body>
95
+ <section class="container">
96
+ <div class="customer-logo-wrapper">
97
+ <div class="customer-logo">
98
+ <img src="{{customLogo}}" alt="Logo"/>
99
+ </div>
100
+ </div>
101
+ <div class="page-title-wrapper">
102
+ <div class="page-title">
103
+ <h1>Please verify you are a human</h1>
104
+ </div>
105
+ </div>
106
+ <div class="content-wrapper">
107
+ <div class="content">
108
+
109
+ <div id="px-captcha">
110
+ </div>
111
+ <p>
112
+ Access to this page has been denied because we believe you are using automation tools to browse the
113
+ 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
128
+ them from loading.
129
+ </p>
130
+ <p>
131
+ Reference ID: #{{refId}}
132
+ </p>
133
+ </div>
134
+ </div>
135
+ <div class="page-footer-wrapper">
136
+ <div class="page-footer">
137
+ <p>
138
+ Powered by
139
+ <a href="https://www.perimeterx.com/whywasiblocked">PerimeterX</a>
140
+ , Inc.
141
+ </p>
142
+ </div>
143
+ </div>
144
+ </section>
145
+ <!-- Px -->
146
+ <script>
147
+ window._pxAppId = '{{appId}}';
148
+ window._pxJsClientSrc = '{{{jsClientSrc}}}';
149
+ window._pxFirstPartyEnabled = {{firstPartyEnabled}};
150
+ window._pxVid = '{{vid}}';
151
+ window._pxUuid = '{{uuid}}';
152
+ window._pxHostUrl = '{{{hostUrl}}}';
153
+ </script>
154
+ <script>
155
+ var s = document.createElement('script');
156
+ s.src = '{{{blockScript}}}';
157
+ var p = document.getElementsByTagName('head')[0];
158
+ p.insertBefore(s, null);
159
+ if ({{firstPartyEnabled}}) {
160
+ s.onerror = function () {
161
+ s = document.createElement('script');
162
+ var suffixIndex = '{{{blockScript}}}'.indexOf('captcha.js');
163
+ var temperedBlockScript = '{{{blockScript}}}'.substring(suffixIndex);
164
+ s.src = '//captcha.px-cdn.net/{{appId}}/' + temperedBlockScript;
165
+ p.parentNode.insertBefore(s, p);
166
+ };
167
+ }
168
+ </script>
169
+
170
+ <!-- Custom Script -->
171
+ {{#jsRef}}
172
+ <script src="{{{jsRef}}}"></script>
173
+ {{/jsRef}}
174
+ </body>
175
+ </html>