Skip to content

Commit 8551056

Browse files
authored
Merge pull request #239 from zendesk/keith/rewrite_validation_for_requests
[MPORT-502] Rewrite Requests validation class
2 parents 197b4ac + c8703d4 commit 8551056

File tree

3 files changed

+85
-97
lines changed

3 files changed

+85
-97
lines changed

lib/zendesk_apps_support/validations/requests.rb

Lines changed: 48 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,69 @@
11
# frozen_string_literal: true
22

3-
require 'uri'
43
require 'ipaddress_2'
4+
require 'uri'
55

66
module ZendeskAppsSupport
77
module Validations
88
module Requests
99
class << self
10-
HTTP_REQUEST_METHODS = %w[GET HEAD POST PUT DELETE CONNECT OPTIONS TRACE].freeze
11-
12-
REQUEST_CALLS = [
13-
/\w+\.request\(['"](.*)['"]/i, # ZAF request
14-
/(?:\$|jQuery)\.(?:ajax|get|post|getJSON)\(['"](.*)['"]/i, # jQuery request
15-
/\w+\.open\(['"](?:#{Regexp.union(HTTP_REQUEST_METHODS).source})['"],\s?['"](.*)['"]/i, # XMLHttpRequest
16-
/fetch\(['"](.*)['"]/i # fetch
17-
].freeze
10+
IP_ADDRESS = /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/
1811

1912
def call(package)
2013
errors = []
2114
files = package.js_files + package.html_files
2215

2316
files.each do |file|
24-
contents = file.read
25-
REQUEST_CALLS.each do |request_pattern|
26-
request = contents.match(request_pattern)
27-
next unless request
28-
uri = URI(request.captures[0])
29-
if uri.scheme == 'http'
30-
package.warnings << I18n.t('txt.apps.admin.warning.app_build.insecure_http_request',
31-
uri: uri,
32-
file: file.relative_path)
33-
end
34-
35-
next unless IPAddress.valid? uri.host
36-
ip = IPAddress.parse uri.host
37-
38-
blocked_ip_type = if ip.private?
39-
I18n.t('txt.apps.admin.error.app_build.blocked_request_private')
40-
elsif ip.loopback?
41-
I18n.t('txt.apps.admin.error.app_build.blocked_request_loopback')
42-
elsif ip.link_local?
43-
I18n.t('txt.apps.admin.error.app_build.blocked_request_link_local')
44-
end
45-
46-
next unless blocked_ip_type
47-
errors << I18n.t('txt.apps.admin.error.app_build.blocked_request',
48-
type: blocked_ip_type,
49-
uri: uri.host,
50-
file: file.relative_path)
17+
file_content = file.read
18+
19+
http_protocol_urls = find_address_containing_http(file_content)
20+
if http_protocol_urls.any?
21+
package.warnings << I18n.t(
22+
'txt.apps.admin.warning.app_build.insecure_http_request',
23+
uri: http_protocol_urls.join(I18n.t('txt.apps.admin.error.app_build.listing_comma')),
24+
file: file.relative_path
25+
)
26+
end
27+
28+
ip_addresses = file_content.scan(IP_ADDRESS)
29+
if ip_addresses.any?
30+
errors << blocked_ips_validation(file.relative_path, ip_addresses)
5131
end
5232
end
33+
5334
errors
5435
end
36+
37+
private
38+
39+
def blocked_ips_validation(file_path, ip_addresses)
40+
ip_addresses.each_with_object([]) do |ip_address, error_messages|
41+
blocked_type = blocked_ip_type(ip_address)
42+
next unless blocked_type
43+
44+
error_messages << I18n.t(
45+
'txt.apps.admin.error.app_build.blocked_request',
46+
type: blocked_type,
47+
uri: ip_address,
48+
file: file_path
49+
)
50+
end
51+
end
52+
53+
def blocked_ip_type(ip_address)
54+
block_type =
55+
case IPAddress.parse(ip_address)
56+
when proc(&:private?) then 'private'
57+
when proc(&:loopback?) then 'loopback'
58+
when proc(&:link_local?) then 'link_local'
59+
end
60+
61+
block_type && I18n.t("txt.apps.admin.error.app_build.blocked_request_#{block_type}")
62+
end
63+
64+
def find_address_containing_http(file_content)
65+
file_content.scan(URI.regexp(['http'])).map(&:compact).map(&:last)
66+
end
5567
end
5668
end
5769
end

spec/validations/mime_spec.rb

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
require 'spec_helper'
44

55
describe ZendeskAppsSupport::Validations::Mime do
6-
let(:subject) { ZendeskAppsSupport::Validations::Mime }
76
let(:package) { double('Package', files: []) }
87

98
def add_to_package(pathname)

spec/validations/requests_spec.rb

Lines changed: 37 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -2,75 +2,52 @@
22

33
require 'spec_helper'
44

5-
def request_function(style, address)
6-
case style
7-
when 'ZAF'
8-
"function request() { client.request('#{address}') }"
9-
when 'jQuery'
10-
"function request() { jQuery.get('#{address}') }"
11-
when 'jQuery$'
12-
"function request() { $.ajax('#{address}') }"
13-
when 'XMLHttpRequest'
14-
"function request() { xhr.open('get', '#{address}') }"
15-
when 'fetch'
16-
"function request() { fetch('#{address}') }"
17-
end
18-
end
19-
20-
request_function_styles = %w[ZAF jQuery jQuery$ XMLHttpRequest fetch]
5+
describe ZendeskAppsSupport::Validations::Requests do
6+
let(:package) { double('Package', warnings: [], html_files: []) }
7+
let(:app_file) { double('AppFile', relative_path: 'app_file.js') }
218

22-
shared_examples 'an insecure request' do |file_path, function_style|
23-
address = 'http://foo.com'
24-
let(:markup) { request_function(function_style, address) }
9+
before { allow(package).to receive(:js_files) { [app_file] } }
2510

26-
it "and raise a warning inside #{function_style} style requests" do
27-
errors = subject.call(package)
28-
expect(package.warnings[0]).to include('insecure HTTP request', address, file_path)
29-
expect(errors).to be_empty
30-
end
31-
end
11+
context 'http protocols check' do
12+
it 'returns no warnings for files that contain https urls' do
13+
allow(app_file).to receive(:read) { "client.instance(\"https://foo-bar.com\");\r\n\t" }
3214

33-
blocked_ips = {
34-
private: {
35-
range: '10.0.0.0/8, 172.16.0.0/12, or 192.168.0.0/16',
36-
example: '192.168.0.1'
37-
},
38-
loopback: {
39-
range: '127.0.0.0/8',
40-
example: '127.0.0.1'
41-
},
42-
link_local: {
43-
range: '169.254.0.0/16',
44-
example: '169.254.0.1'
45-
}
46-
}
15+
subject.call(package)
16+
expect(package.warnings).to be_empty
17+
end
4718

48-
shared_examples 'a blocked ip' do |file_path, function_style, ip_type, ip|
49-
let(:markup) { request_function(function_style, "https://#{ip}") }
19+
it 'returns warning with request information when files contain http url' do
20+
allow(app_file).to receive(:read) { "client.instance(\"http://foo-bar.com\");\r\n\t" }
5021

51-
it "and throw a #{ip_type} ip error inside #{function_style} style request calls" do
52-
errors = subject.call(package)
53-
expect(package.warnings).to be_empty
54-
expect(errors[0]).to include("request to a #{ip_type} ip", ip, file_path)
22+
subject.call(package)
23+
expect(package.warnings[0]).to include(
24+
'Possible insecure HTTP request',
25+
'foo-bar.com',
26+
'in app_file.js',
27+
'Consider using the HTTPS protocol instead.'
28+
)
29+
end
5530
end
56-
end
5731

58-
describe ZendeskAppsSupport::Validations::Requests do
59-
app_js_path = 'assets/app.js'
60-
let(:app_js) { double('AppFile', relative_path: app_js_path, read: markup) }
61-
let(:subject) { ZendeskAppsSupport::Validations::Requests }
62-
let(:package) { double('Package', js_files: [app_js], html_files: [], warnings: []) }
32+
context 'IPs check' do
33+
it 'returns no validation error when scanning regular IP' do
34+
allow(app_file).to receive(:read) { "client.instance(\"64.233.191.255\");\r\n\t" }
35+
expect(subject.call(package).flatten).to be_empty
36+
end
6337

64-
describe 'using the http protocol' do
65-
request_function_styles.each { |function_style| it_behaves_like 'an insecure request', app_js_path, function_style }
66-
end
38+
it 'returns a validation error when scanning private IP' do
39+
allow(app_file).to receive(:read) { "//var x = '192.168.0.1'\r\n \tclient.get(x)" }
40+
expect(subject.call(package).flatten[0]).to include('request to a private ip 192.168.0.1')
41+
end
42+
43+
it 'returns a validation error when scanning loopback IP' do
44+
allow(app_file).to receive(:read) { "//var x = '127.0.0.1'\r\n \tclient.get(x)" }
45+
expect(subject.call(package).flatten[0]).to include('request to a loopback ip 127.0.0.1')
46+
end
6747

68-
blocked_ips.each do |type, ip|
69-
describe "to #{ip[:range]} range ips" do
70-
request_function_styles.each do |function_style|
71-
type_localised = ZendeskAppsSupport::I18n.t("txt.apps.admin.error.app_build.blocked_request_#{type}")
72-
it_behaves_like 'a blocked ip', app_js_path, function_style, type_localised, ip[:example]
73-
end
48+
it 'returns a validation error when scanning link_local IP' do
49+
allow(app_file).to receive(:read) { "//var x = '169.254.0.1'\r\n \tclient.get(x)" }
50+
expect(subject.call(package).flatten[0]).to include('request to a link-local ip 169.254.0.1')
7451
end
7552
end
7653
end

0 commit comments

Comments
 (0)