perimeter_x 1.2.0 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +1 -0
  3. data/.travis.yml +3 -0
  4. data/Dockerfile +12 -7
  5. data/Gemfile.lock +36 -30
  6. data/Rakefile +1 -0
  7. data/changelog.md +58 -0
  8. data/examples/app/controllers/home_controller.rb +1 -1
  9. data/lib/perimeter_x.rb +195 -71
  10. data/lib/perimeterx/configuration.rb +74 -22
  11. data/lib/perimeterx/internal/clients/perimeter_x_activity_client.rb +32 -6
  12. data/lib/perimeterx/internal/exceptions/px_config_exception.rb +6 -0
  13. data/lib/perimeterx/internal/first_party/px_first_party.rb +124 -0
  14. data/lib/perimeterx/internal/{perimeter_x_cookie_v1.rb → payload/perimeter_x_cookie_v1.rb} +1 -1
  15. data/lib/perimeterx/internal/{perimeter_x_cookie_v3.rb → payload/perimeter_x_cookie_v3.rb} +1 -1
  16. data/lib/perimeterx/internal/{perimeter_x_cookie.rb → payload/perimeter_x_payload.rb} +12 -4
  17. data/lib/perimeterx/internal/payload/perimeter_x_token_v1.rb +38 -0
  18. data/lib/perimeterx/internal/payload/perimeter_x_token_v3.rb +36 -0
  19. data/lib/perimeterx/internal/perimeter_x_context.rb +74 -32
  20. data/lib/perimeterx/internal/validators/hash_schema_validator.rb +26 -0
  21. data/lib/perimeterx/internal/validators/perimeter_x_cookie_validator.rb +29 -21
  22. data/lib/perimeterx/internal/validators/perimeter_x_s2s_validator.rb +33 -9
  23. data/lib/perimeterx/utils/px_constants.rb +35 -17
  24. data/lib/perimeterx/utils/px_http_client.rb +60 -3
  25. data/lib/perimeterx/utils/px_template_factory.rb +18 -8
  26. data/lib/perimeterx/utils/templates/block_template.mustache +175 -0
  27. data/lib/perimeterx/utils/templates/ratelimit.mustache +9 -0
  28. data/lib/perimeterx/version.rb +1 -1
  29. data/perimeter_x.gemspec +3 -3
  30. data/readme.md +115 -31
  31. metadata +24 -20
  32. data/lib/perimeterx/internal/validators/perimeter_x_captcha_validator.rb +0 -65
  33. data/lib/perimeterx/utils/templates/block.mustache +0 -146
  34. data/lib/perimeterx/utils/templates/captcha.mustache +0 -185
@@ -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] = px_template_object[:host_url]
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] = px_ctx.context[:first_party_enabled]
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>
@@ -0,0 +1,9 @@
1
+ <html>
2
+ <head>
3
+ <title>Too Many Requests</title>
4
+ </head>
5
+ <body>
6
+ <h1>Too Many Requests</h1>
7
+ <p>Reached maximum requests limitation, try again soon.</p>
8
+ </body>
9
+ </html>
@@ -1,3 +1,3 @@
1
1
  module PxModule
2
- VERSION = '1.2.0'
2
+ VERSION = '2.2.0'
3
3
  end
@@ -22,8 +22,8 @@ Gem::Specification.new do |gem|
22
22
  gem.bindir = "exe"
23
23
  gem.executables = gem.files.grep(%r{^exe/}) { |f| File.basename(f) }
24
24
  gem.require_paths = ["lib"]
25
- gem.add_development_dependency "bundler", "~> 1.14"
26
- gem.add_development_dependency "rake", "~> 10.0"
25
+ gem.add_development_dependency "bundler", ">= 2.1"
26
+ gem.add_development_dependency "rake", ">= 12.3"
27
27
 
28
28
  gem.extra_rdoc_files = ["readme.md", "changelog.md"]
29
29
  gem.rdoc_options = ["--line-numbers", "--inline-source", "--title", "PerimeterX"]
@@ -33,7 +33,7 @@ Gem::Specification.new do |gem|
33
33
  gem.add_dependency('concurrent-ruby', '~> 1.0', '>= 1.0.5')
34
34
  gem.add_dependency('typhoeus', '~> 1.1', '>= 1.1.2')
35
35
  gem.add_dependency('mustache', '~> 1.0', '>= 1.0.3')
36
- gem.add_dependency('activesupport', '>= 4.2.0')
36
+ gem.add_dependency('activesupport', '>= 5.2.4.3')
37
37
 
38
38
  gem.add_development_dependency 'rspec', '~> 3.0'
39
39
  gem.add_development_dependency 'mocha', '~> 1.2', '>= 1.2.1'
data/readme.md CHANGED
@@ -1,20 +1,26 @@
1
- ![image](http://media.marketwire.com/attachments/201604/34215_PerimeterX_logo.jpg)
1
+ [![Build Status](https://travis-ci.org/PerimeterX/perimeterx-ruby-sdk.svg?branch=master)](https://travis-ci.org/PerimeterX/perimeterx-ruby-sdk)
2
+
3
+ ![image](https://storage.googleapis.com/perimeterx-logos/primary_logo_red_cropped.png)
2
4
  #
3
5
  [PerimeterX](http://www.perimeterx.com) Ruby SDK
4
6
  =============================================================
5
7
 
8
+ > Latest stable version: [v2.1.0](https://rubygems.org/gems/perimeter_x)
9
+
6
10
  Table of Contents
7
11
  -----------------
8
- - [Usage](#usage)
12
+ **[Usage](#usage)**
9
13
  * [Dependencies](#dependencies)
10
14
  * [Installation](#installation)
11
15
  * [Basic Usage Example](#basic-usage)
12
- - [Configuration](#configuration)
16
+
17
+ **[Configuration](#configuration)**
13
18
  * [Configuring Required Parameters](#requireied-params)
14
19
  * [Blocking Score](#blocking-score)
20
+ * [Custom Verification Action](#custom-verification-action)
15
21
  * [Custom Block Page](#custom-block-page)
16
- * [Custom Block Action](#custom-block-action)
17
22
  * [Enable/Disable Captcha](#captcha-support)
23
+ * [Select Captcha Provider](#captcha-provider)
18
24
  * [Extracting Real IP Address](#real-ip)
19
25
  * [Custom URI](#custom-uri)
20
26
  * [Filter Sensitive Headers](#sensitive-headers)
@@ -23,7 +29,11 @@ Table of Contents
23
29
  * [Additional Page Activity Handler](#additional-page-activity-handler)
24
30
  * [Monitor Only](#logging)
25
31
  * [Debug Mode](#debug-mode)
26
- - [Contributing](#contributing)
32
+ * [Whitelist Routes](#whitelist-routes)
33
+ * [Update Configuration on Runtime](#update-config)
34
+ * [First Party](#first-party)
35
+
36
+ **[Contributing](#contributing)**
27
37
 
28
38
  <a name="Usage"></a>
29
39
  <a name="dependencies"></a> Dependencies
@@ -61,7 +71,7 @@ On the Rails controller include the PerimeterX SDK via the before_action and cal
61
71
  class HomeController < ApplicationController
62
72
  include PxModule
63
73
 
64
- before_filter :px_verify_request
74
+ before_action :px_verify_request
65
75
  ...
66
76
  ...
67
77
  end
@@ -80,7 +90,7 @@ All parameters are obtainable via the PerimeterX Portal. (Applications and Polic
80
90
 
81
91
  <a name="blocking-score"></a>**Changing the Minimum Score for Blocking**
82
92
 
83
- >Note: Default blocking value: 70
93
+ >Note: Default blocking value: 100
84
94
 
85
95
  ```ruby
86
96
  params = {
@@ -90,69 +100,71 @@ params = {
90
100
  }
91
101
  ```
92
102
 
103
+ <a name="custom-verification-action"></a>**Custom Verification Handler**
93
104
 
105
+ > Note: This handler replaces the now deprecated `custom_block_handler`.
94
106
 
95
- <a name="custom-block-action"></a>**Custom Verification Handler**
107
+ A custom verification handler is being executed inside `px_verify_request` instead of the the default behavior and allows a user to use a custom action based on the risk score returned by PerimeterX.
96
108
 
97
- A custom verification handler is being executed inside ``px_verify_request`` instead of the the default behavior and allows a user to use a custom action based on the risk score returned by PerimeterX.
109
+ When implemented, this method receives a hash variable as input which represents data from the PerimeterX context of the request (px_ctx).
98
110
 
99
- When implemented, this method receives a hash variable as input which represents data from the PerimeterX context of the request (px_ctx).
111
+ - `px_ctx.context[:score]` - contains the risk score
112
+ - `px_ctx.context[:uuid]` - contains the request UUID
113
+ - `px_ctx.context[:verified]` - contains indication whether the request passed verification or was blocked (inspect `px_ctx.context[:block_reason]` for block reason)
100
114
 
101
- - `px_ctx[:score] ` contains the risk score
102
- - `px_ctx[:uuid] ` contains the request UUID
103
-
104
- >> Note: to determine whether to return a captcha/block page (HTML) or block JSON payload a reference key on the context will be available: ```px_ctx.context[:format]```
115
+ > Note: to determine whether to return a captcha/block page (HTML) or block JSON payload a reference key on the context will be available: ```px_ctx.context[:format]```
105
116
 
106
117
  To replace the default verification behavior, add the configuration a lambda member as shown in the example below.
107
118
 
108
- The method must return boolen value.
109
-
110
119
  ```ruby
111
120
  params = {
112
121
  :app_id => <APP_ID>,
113
122
  :auth_token => <AUTH_TOKEN>,
114
- :custom_block_handler => -> (px_ctx) {
123
+ :custom_verification_handler => -> (px_ctx) {
115
124
  if px_ctx.context[:score] >= 60
116
- # take your action and retun a message or JSON with a status code of 403 and option UUID of the request. Can return false and include action in the px_middleware method.
125
+ # take your action and render an html page or JSON with applicable status code.
126
+ render json: { :score => px_ctx.context[:score] }
117
127
  end
118
- return true
119
128
  }
120
129
  }
121
130
  ```
122
131
 
132
+ > Note: Unlike previous versions, the method no longer needs to return a boolean value.
133
+
123
134
  **Example**
124
- ### Serving a Custom HTML Page ###
135
+ #### Serving a Custom HTML Page ####
125
136
  ```ruby
126
137
 
127
- params[:custom_block_handler] = -> (px_ctx)
128
- {
138
+ params = {
139
+ :app_id => <APP_ID>,
140
+ :auth_token => <AUTH_TOKEN>,
141
+ ...
142
+ :custom_verification_handler => -> (px_ctx) {
129
143
  block_score = px_ctx.context[:score];
130
- block_uuid = px_ctx.context[:uuid];
144
+ client_uuid = px_ctx.context[:uuid];
131
145
  full_url = px_ctx.context[:full_url];
132
146
 
133
147
  html = "<html>
134
148
  <body>
135
149
  <div>Access to #{full_url} has been blocked.</div>
136
- <div>Block reference - #{block_uuid} </div>
150
+ <div>Block reference - #{client_uuid} </div>
137
151
  <div>Block score - #{block_score} </div>
138
152
  </body>
139
153
  </html>".html_safe
140
154
  response.headers["Content-Type"] = "text/html"
141
155
  response.status = 403
142
156
  render :html => html
143
- return false
144
- };
145
-
146
- PxModule.configure(params)
157
+ }
158
+ }
147
159
  ```
148
160
 
149
- <a name="real-ip"></a>** Custom User IP **
161
+ <a name="real-ip"></a>**Custom User IP**
150
162
 
151
163
  > Note: IP extraction, according to your network setup, is very important. It is common to have a load balancer/proxy on top of your applications, in which case the PerimeterX module will send the system's internal IP as the user's. In order to properly perform processing and detection on server-to-server calls, PerimeterX module needs the real user's IP.
152
164
 
153
165
  By default the clients IP is taken from the ``REMOTE_ADDR`` header, in case the user decides to use different header or custom function that extract the header the following key should be added to the configuration
154
166
 
155
- *** Custom header ***
167
+ ***Custom header***
156
168
  ```ruby
157
169
  configuration = {
158
170
  "app_id" => <APP_ID>,
@@ -160,7 +172,7 @@ configuration = {
160
172
  "custom_user_ip" => <HTTP_HEADER_NAME>,
161
173
  ```
162
174
 
163
- *** Custom Function ***
175
+ ***Custom Function***
164
176
  > Note: the function receive as a first parameter the controller request and must return the ip at the end as string
165
177
 
166
178
  ```ruby
@@ -222,6 +234,17 @@ By enabling CAPTCHA support, a CAPTCHA will be served as part of the block page,
222
234
  params[:captcha_enabled] = false
223
235
  ```
224
236
 
237
+ <a name="captcha-provider"></a>**Select CAPTCHA Provider**
238
+
239
+ The CAPTCHA part of the block page can use one of the following:
240
+ * [reCAPTCHA](https://www.google.com/recaptcha)
241
+
242
+ Default: 'reCaptcha'
243
+
244
+ ```ruby
245
+ captchaProvider = "reCaptcha"
246
+ ```
247
+
225
248
  <a name="custom-uri"></a>**Custom URI**
226
249
 
227
250
  Default: 'REQUEST_URI'
@@ -285,6 +308,67 @@ Enables debug logging mode to STDOUT
285
308
  params[:debug] = true
286
309
  ```
287
310
 
311
+ <a name="whitelist-routes"></a>**Whitelist Routes**
312
+ Default: []
313
+
314
+ An array of route prefixes and/or regular expressions that are always whitelisted and not validated by PerimeterX.
315
+ A string value of a path will be treated as a prefix.
316
+ A regexp value of a path will be treated as is.
317
+
318
+ ```ruby
319
+ params[:whitelist_routes] = ["/example", /\A\/example\z/]
320
+ ```
321
+
322
+ <a name="update-config"></a>**Update Configuration on Runtime**
323
+
324
+ As mentioned before, PerimeterX Module should be configured in `<rails_app>/config/initializers/perimeterx.rb`.
325
+ However, it is possible to override configuration options on each request.
326
+ To do so, send the configuration options as an argument when calling to `px_verify_request` as described in the following example.
327
+ Notice that in case of an invalid argument, the module will raise an error. Therefore, when using this feature, make sure to wrap the call to `px_verify_request` with begin and rescue. It is highly recommended to log the error message to follow such errors.
328
+
329
+ ```ruby
330
+ class HomeController < ApplicationController
331
+ include PxModule
332
+
333
+
334
+ before_action do call_perimeterx_verify_request end
335
+
336
+ def call_perimeterx_verify_request
337
+ params = {
338
+ :blocking_score => 70,
339
+ :module_mode => 2
340
+ }
341
+ begin
342
+ px_verify_request(params)
343
+ rescue StandardError => e
344
+ # $stdout.write(e.message)
345
+ end
346
+ end
347
+
348
+ end
349
+ ```
350
+
351
+ <a name="first-party"></a>**First Party**
352
+ To enable first party on your enforcer, add the following routes to your `config/routes.rb` file:
353
+
354
+ ```ruby
355
+ get '/:appid_postfix/init.js', to: 'home#index', constraints: { appid_postfix: /XXXXXXXX/ }
356
+ get '/:appid_postfix/captcha/:all', to: 'home#index', constraints: { appid_postfix: /XXXXXXXX/, all:/.*/ }
357
+ post '/:appid_postfix/xhr/:all', to: 'home#index', constraints: { appid_postfix: /XXXXXXXX/, all:/.*/ }
358
+ ```
359
+
360
+ Notice that all occurences of `XXXXXXXX` should be replaced with your px_app_id without the "PX" prefix. For example, if your px_app_id is `PX2H4seK9L`, reeplace `XXXXXXXX` with `2H4seK9L`.
361
+ In case you are using more than one px_app_id, provide all of them with a `|` sign between them. For example: 2H4seK9L|9bMs6K94|Lc5kPMNx
362
+
363
+
364
+ First Party configuration:
365
+ Default: true
366
+
367
+ ```ruby
368
+ params[:first_party_enabled] = false
369
+ ```
370
+
371
+
288
372
  <a name="contributing"></a># Contributing #
289
373
  ------------------------------
290
374
  The following steps are welcome when contributing to our project.