Skip to content

Commit 2655d88

Browse files
authored
Merge pull request #46 from PerimeterX/dev
Dev
2 parents cb8fd0b + 33484be commit 2655d88

29 files changed

+427
-1202
lines changed

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
language: ruby
22
rvm:
3-
- 2.3
3+
- 2.7.1

Dockerfile

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,26 @@
11
# Based on manual compile instructions at http://wiki.nginx.org/HttpLuaModule#Installation
2-
FROM ruby:2.3.0
2+
FROM ruby:2.7.1
33

4-
RUN apt-get update && apt-get --force-yes -qq -y install \
5-
nodejs
6-
ENV RAILS_VERSION 4.2.0
4+
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg -o /root/yarn-pubkey.gpg && apt-key add /root/yarn-pubkey.gpg
5+
RUN echo "deb https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list
6+
RUN apt-get update && apt-get install -y --no-install-recommends nodejs yarn vim
7+
8+
ENV RAILS_VERSION 6.0.3.2
79
RUN gem install rails --version "$RAILS_VERSION"
810
RUN gem install bundler
911
RUN mkdir -p /tmp/ruby_sandbox
1012
WORKDIR /tmp/ruby_sandbox
11-
RUN git clone https://github.com/PerimeterX/perimeterx-ruby-sdk.git
13+
COPY lib /tmp/ruby_sandbox/lib
14+
COPY Gemfile /tmp/ruby_sandbox/
15+
COPY perimeter_x.gemspec /tmp/ruby_sandbox/
16+
COPY Rakefile /tmp/ruby_sandbox/
1217
RUN rails new webapp
1318
WORKDIR /tmp/ruby_sandbox/webapp
1419

1520
RUN rails generate controller home index
1621
WORKDIR /tmp/ruby_sandbox/webapp
1722
EXPOSE 3000
18-
RUN sed -i '2i gem "perimeter_x", :path => "/tmp/ruby_sandbox/perimeterx-ruby-sdk"' /tmp/ruby_sandbox/webapp/Gemfile
23+
RUN sed -i '2i gem "perimeter_x", :path => "/tmp/ruby_sandbox"' /tmp/ruby_sandbox/webapp/Gemfile
1924
RUN bundler update
20-
COPY ./examples/ /tmp/ruby_sandbox/webapp
25+
COPY ./dev/site/ /tmp/ruby_sandbox/webapp
2126
CMD ["rails","server","-b","0.0.0.0"]

Gemfile.lock

Lines changed: 32 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,61 @@
11
PATH
22
remote: .
33
specs:
4-
perimeter_x (1.4.0)
5-
activesupport (>= 4.2.0)
4+
perimeter_x (2.0.0)
5+
activesupport (>= 5.2.4.3)
66
concurrent-ruby (~> 1.0, >= 1.0.5)
77
mustache (~> 1.0, >= 1.0.3)
88
typhoeus (~> 1.1, >= 1.1.2)
99

1010
GEM
1111
remote: https://rubygems.org/
1212
specs:
13-
activesupport (5.0.2)
13+
activesupport (6.0.3.2)
1414
concurrent-ruby (~> 1.0, >= 1.0.2)
15-
i18n (~> 0.7)
15+
i18n (>= 0.7, < 2)
1616
minitest (~> 5.1)
1717
tzinfo (~> 1.1)
18-
concurrent-ruby (1.0.5)
19-
diff-lcs (1.3)
20-
ethon (0.10.1)
18+
zeitwerk (~> 2.2, >= 2.2.2)
19+
concurrent-ruby (1.1.6)
20+
diff-lcs (1.4.4)
21+
ethon (0.12.0)
2122
ffi (>= 1.3.0)
22-
ffi (1.9.18)
23-
i18n (0.8.6)
24-
metaclass (0.0.4)
25-
minitest (5.10.1)
26-
mocha (1.2.1)
27-
metaclass (~> 0.0.1)
28-
mustache (1.0.5)
29-
rake (10.4.2)
30-
rspec (3.5.0)
31-
rspec-core (~> 3.5.0)
32-
rspec-expectations (~> 3.5.0)
33-
rspec-mocks (~> 3.5.0)
34-
rspec-core (3.5.4)
35-
rspec-support (~> 3.5.0)
36-
rspec-expectations (3.5.0)
23+
ffi (1.13.1)
24+
i18n (1.8.3)
25+
concurrent-ruby (~> 1.0)
26+
minitest (5.14.1)
27+
mocha (1.11.2)
28+
mustache (1.1.1)
29+
rake (13.0.1)
30+
rspec (3.9.0)
31+
rspec-core (~> 3.9.0)
32+
rspec-expectations (~> 3.9.0)
33+
rspec-mocks (~> 3.9.0)
34+
rspec-core (3.9.2)
35+
rspec-support (~> 3.9.3)
36+
rspec-expectations (3.9.2)
3737
diff-lcs (>= 1.2.0, < 2.0)
38-
rspec-support (~> 3.5.0)
39-
rspec-mocks (3.5.0)
38+
rspec-support (~> 3.9.0)
39+
rspec-mocks (3.9.1)
4040
diff-lcs (>= 1.2.0, < 2.0)
41-
rspec-support (~> 3.5.0)
42-
rspec-support (3.5.0)
41+
rspec-support (~> 3.9.0)
42+
rspec-support (3.9.3)
4343
thread_safe (0.3.6)
44-
typhoeus (1.1.2)
44+
typhoeus (1.4.0)
4545
ethon (>= 0.9.0)
46-
tzinfo (1.2.3)
46+
tzinfo (1.2.7)
4747
thread_safe (~> 0.1)
48+
zeitwerk (2.3.1)
4849

4950
PLATFORMS
5051
ruby
5152

5253
DEPENDENCIES
53-
bundler (~> 1.14)
54+
bundler (>= 2.1)
5455
mocha (~> 1.2, >= 1.2.1)
5556
perimeter_x!
56-
rake (~> 10.0)
57+
rake (>= 12.3)
5758
rspec (~> 3.0)
5859

5960
BUNDLED WITH
60-
1.14.6
61+
2.1.4

changelog.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,31 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](http://keepachangelog.com/)
66
and this project adheres to [Semantic Versioning](http://semver.org/).
77

8+
## [2.0.0] - 2020-07-24
9+
### Added
10+
- Added fields to Block Activity: simulated_block, http_version, http_method, risk_rtt, px_orig_cookie
11+
- Added fields to page_requested activity: pass_reason, risk_rtt, px_orig_cookie
12+
- Added px_orig_cookie field to risk_api in case of cookie_decryption_failed
13+
- Added support for captcha v2
14+
- Added support for Advanced Blocking Response
15+
- Added support for whitelise routes
16+
- Added support for bypass monitor header
17+
- Added support for extracting vid from _pxvid cookie
18+
- Added support for rate limit
19+
- Added risk_cookie_max_iterations configuration
20+
21+
### Fixed
22+
- Updated dependencies
23+
- Updated sample site dockerfile
24+
- Fixed monitor mode
25+
- Fixed send_page_activities and send_block_activities configurations
26+
- Updated risk to v3
27+
- Refactored ip header extraction
28+
- Renamed block_uuid field to client_uuid
29+
- Renamed perimeterx_server_host configuration to backend_url
30+
- Updated risk_response handling: pass the request if risk_response.status is -1
31+
- Forcing http header values to be utf8
32+
833
## [1.4.0] - 2018-03-18
934
### Fixed
1035
- Incorrect assigment for s2s_call_reason

lib/perimeter_x.rb

Lines changed: 75 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
require 'concurrent'
22
require 'json'
33
require 'base64'
4+
require 'uri'
45
require 'perimeterx/configuration'
56
require 'perimeterx/utils/px_logger'
67
require 'perimeterx/utils/px_constants'
@@ -10,7 +11,6 @@
1011
require 'perimeterx/internal/clients/perimeter_x_activity_client'
1112
require 'perimeterx/internal/validators/perimeter_x_s2s_validator'
1213
require 'perimeterx/internal/validators/perimeter_x_cookie_validator'
13-
require 'perimeterx/internal/validators/perimeter_x_captcha_validator'
1414

1515
module PxModule
1616
# Module expose API
@@ -25,27 +25,58 @@ def px_verify_request
2525
return instance_exec(px_ctx, &px_config[:custom_verification_handler])
2626
end
2727

28-
# Invalidate _pxCaptcha, can be done only on the controller level
29-
cookies[:_pxCaptcha] = {value: "", expires: -1.minutes.from_now}
30-
31-
unless px_ctx.nil? || px_ctx.context[:verified]
28+
unless px_ctx.nil? || px_ctx.context[:verified] || (px_config[:module_mode] == PxModule::MONITOR_MODE && !px_ctx.context[:should_bypass_monitor])
3229
# In case custom block handler exists (soon to be deprecated)
3330
if px_config.key?(:custom_block_handler)
3431
px_config[:logger].debug("#{msg_title}: custom_block_handler triggered")
3532
px_config[:logger].debug(
3633
"#{msg_title}: Please note that custom_block_handler is deprecated. Use custom_verification_handler instead.")
3734
return instance_exec(px_ctx, &px_config[:custom_block_handler])
3835
else
39-
# Generate template
40-
px_config[:logger].debug("#{msg_title}: sending default block page")
41-
html = PxTemplateFactory.get_template(px_ctx, px_config)
42-
response.headers['Content-Type'] = 'text/html'
43-
response.status = 403
36+
if px_ctx.context[:block_action]== 'rate_limit'
37+
px_config[:logger].debug("#{msg_title}: sending rate limit page")
38+
response.status = 429
39+
else
40+
px_config[:logger].debug("#{msg_title}: sending default block page")
41+
response.status = 403
42+
end
43+
44+
is_mobile = px_ctx.context[:cookie_origin] == 'header' ? '1' : '0'
45+
action = px_ctx.context[:block_action][0,1]
46+
47+
px_template_object = {
48+
block_script: "//#{PxModule::CAPTCHA_HOST}/#{px_config[:app_id]}/captcha.js?a=#{action}&u=#{px_ctx.context[:uuid]}&v=#{px_ctx.context[:vid]}&m=#{is_mobile}",
49+
js_client_src: "//#{PxModule::CLIENT_HOST}/#{px_config[:app_id]}/main.min.js"
50+
}
51+
52+
html = PxTemplateFactory.get_template(px_ctx, px_config, px_template_object)
53+
4454
# Web handler
4555
if px_ctx.context[:cookie_origin] == 'cookie'
46-
px_config[:logger].debug('#{msg_title}: web block')
47-
response.headers['Content-Type'] = 'text/html'
48-
render :html => html
56+
57+
accept_header_value = request.headers['accept'] || request.headers['content-type'];
58+
is_json_response = px_ctx.context[:block_action] != 'rate_limit' && accept_header_value && accept_header_value.split(',').select {|e| e.downcase.include? 'application/json'}.length > 0;
59+
60+
if (is_json_response)
61+
px_config[:logger].debug("#{msg_title}: advanced blocking response response")
62+
response.headers['Content-Type'] = 'application/json'
63+
64+
hash_json = {
65+
:appId => px_config[:app_id],
66+
:jsClientSrc => px_template_object[:js_client_src],
67+
:firstPartyEnabled => false,
68+
:uuid => px_ctx.context[:uuid],
69+
:vid => px_ctx.context[:vid],
70+
:hostUrl => "https://collector-#{px_config[:app_id]}.perimeterx.net",
71+
:blockScript => px_template_object[:block_script],
72+
}
73+
74+
render :json => hash_json
75+
else
76+
px_config[:logger].debug('#{msg_title}: web block')
77+
response.headers['Content-Type'] = 'text/html'
78+
render :html => html
79+
end
4980
else # Mobile SDK
5081
px_config[:logger].debug("#{msg_title}: mobile sdk block")
5182
response.headers['Content-Type'] = 'application/json'
@@ -99,19 +130,27 @@ def self.instance
99130
#Instance Methods
100131
def verify(env)
101132
begin
133+
134+
# check module_enabled
102135
@logger.debug('PerimeterX[pxVerify]')
103136
if !@px_config[:module_enabled]
104137
@logger.warn('Module is disabled')
105138
return nil
106139
end
107-
req = ActionDispatch::Request.new(env)
108-
px_ctx = PerimeterXContext.new(@px_config, req)
109140

110-
# Captcha phase
111-
captcha_verified, px_ctx = @px_captcha_validator.verify(px_ctx)
112-
if captcha_verified
113-
return handle_verification(px_ctx)
141+
req = ActionDispatch::Request.new(env)
142+
143+
# filter whitelist routes
144+
url_path = URI.parse(req.original_url).path
145+
if url_path && !url_path.empty?
146+
if check_whitelist_routes(px_config[:whitelist_routes], url_path)
147+
@logger.debug("PerimeterX[pxVerify]: whitelist route: #{url_path}")
148+
return nil
149+
end
114150
end
151+
152+
# create context
153+
px_ctx = PerimeterXContext.new(@px_config, req)
115154

116155
# Cookie phase
117156
cookie_verified, px_ctx = @px_cookie_validator.verify(px_ctx)
@@ -136,15 +175,16 @@ def verify(env)
136175

137176
@px_cookie_validator = PerimeterxCookieValidator.new(@px_config)
138177
@px_s2s_validator = PerimeterxS2SValidator.new(@px_config, @px_http_client)
139-
@px_captcha_validator = PerimeterxCaptchaValidator.new(@px_config, @px_http_client)
140-
@logger.debug('PerimeterX[initialize]Z')
178+
@logger.debug('PerimeterX[initialize]')
141179
end
142180

143181
private def handle_verification(px_ctx)
144182
@logger.debug('PerimeterX[handle_verification]')
145183
@logger.debug("PerimeterX[handle_verification]: processing ended - score:#{px_ctx.context[:score]}, uuid:#{px_ctx.context[:uuid]}")
146184

147185
score = px_ctx.context[:score]
186+
px_ctx.context[:should_bypass_monitor] = @px_config[:bypass_monitor_header] && px_ctx.context[:headers][@px_config[:bypass_monitor_header].to_sym] == '1';
187+
148188
px_ctx.context[:verified] = score < @px_config[:blocking_score]
149189
# Case PASS request
150190
if px_ctx.context[:verified]
@@ -157,8 +197,8 @@ def verify(env)
157197
@px_activity_client.send_block_activity(px_ctx)
158198

159199
# In case were in monitor mode, end here
160-
if @px_config[:module_mode] == PxModule::MONITOR_MODE
161-
@logger.debug('PerimeterX[handle_verification]: monitor mode is on, passing request')
200+
if @px_config[:module_mode] == PxModule::MONITOR_MODE && !px_ctx.context[:should_bypass_monitor]
201+
@logger.debug("PerimeterX[handle_verification]: monitor mode is on, passing request")
162202
return px_ctx
163203
end
164204

@@ -167,6 +207,18 @@ def verify(env)
167207
return px_ctx
168208
end
169209

210+
private def check_whitelist_routes(whitelist_routes, path)
211+
whitelist_routes.each do |whitelist_route|
212+
if whitelist_route.is_a?(Regexp) && path.match(whitelist_route)
213+
return true
214+
end
215+
if whitelist_route.is_a?(String) && path.start_with?(whitelist_route)
216+
return true
217+
end
218+
end
219+
false
220+
end
221+
170222
private_class_method :new
171223
end
172224
end

lib/perimeterx/configuration.rb

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,29 +8,33 @@ class Configuration
88
attr_accessor :PX_DEFAULT
99

1010
PX_DEFAULT = {
11-
:app_id => nil,
12-
:cookie_key => nil,
13-
:auth_token => nil,
14-
:module_enabled => true,
15-
:captcha_provider => "reCaptcha",
16-
:challenge_enabled => true,
17-
:encryption_enabled => true,
18-
:blocking_score => 70,
19-
:sensitive_headers => ["http-cookie", "http-cookies"],
20-
:api_connect_timeout => 1,
21-
:api_timeout => 1,
22-
:max_buffer_len => 10,
23-
:send_page_activities => true,
24-
:send_block_activities => true,
25-
:sdk_name => PxModule::SDK_NAME,
26-
:debug => false,
27-
:module_mode => PxModule::ACTIVE_MODE,
28-
:local_proxy => false,
29-
:sensitive_routes => []
11+
:app_id => nil,
12+
:cookie_key => nil,
13+
:auth_token => nil,
14+
:module_enabled => true,
15+
:challenge_enabled => true,
16+
:encryption_enabled => true,
17+
:blocking_score => 100,
18+
:sensitive_headers => ["http-cookie", "http-cookies"],
19+
:api_connect_timeout => 1,
20+
:api_timeout => 1,
21+
:max_buffer_len => 10,
22+
:send_page_activities => true,
23+
:send_block_activities => true,
24+
:sdk_name => PxModule::SDK_NAME,
25+
:debug => false,
26+
:module_mode => PxModule::MONITOR_MODE,
27+
:local_proxy => false,
28+
:sensitive_routes => [],
29+
:whitelist_routes => [],
30+
:ip_headers => [],
31+
:ip_header_function => nil,
32+
:bypass_monitor_header => nil,
33+
:risk_cookie_max_iterations => 5000
3034
}
3135

3236
def initialize(params)
33-
PX_DEFAULT[:perimeterx_server_host] = "https://sapi-#{params[:app_id].downcase}.perimeterx.net"
37+
PX_DEFAULT[:backend_url] = "https://sapi-#{params[:app_id].downcase}.perimeterx.net"
3438
@configuration = PX_DEFAULT.merge(params)
3539
@configuration[:logger] = PxLogger.new(@configuration[:debug])
3640
end

0 commit comments

Comments
 (0)