agile-proxy-jruby 0.1.25-jruby
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.bowerrc +3 -0
- data/.gitignore +10 -0
- data/.rspec +2 -0
- data/.rubocop.yml +36 -0
- data/.travis.yml +10 -0
- data/Gemfile +4 -0
- data/Guardfile +20 -0
- data/LICENSE +22 -0
- data/README.md +131 -0
- data/Rakefile +15 -0
- data/agile-proxy.gemspec +60 -0
- data/assets/index.html +39 -0
- data/assets/ui/app/AgileProxyApi.js +31 -0
- data/assets/ui/app/app.js +1 -0
- data/assets/ui/app/controller/Stubs.js +64 -0
- data/assets/ui/app/controller/main.js +12 -0
- data/assets/ui/app/directive/AppEnhancedFormElement.js +21 -0
- data/assets/ui/app/directive/AppFor.js +16 -0
- data/assets/ui/app/directive/AppResponseEditor.js +54 -0
- data/assets/ui/app/model/RequestSpec.js +6 -0
- data/assets/ui/app/routes.js +11 -0
- data/assets/ui/app/service/Dialog.js +49 -0
- data/assets/ui/app/service/DomId.js +10 -0
- data/assets/ui/app/service/Error.js +7 -0
- data/assets/ui/app/service/Stub.js +36 -0
- data/assets/ui/app/view/404.html +2 -0
- data/assets/ui/app/view/dialog/error.html +10 -0
- data/assets/ui/app/view/dialog/yesNo.html +8 -0
- data/assets/ui/app/view/responses/editForm.html +78 -0
- data/assets/ui/app/view/status.html +1 -0
- data/assets/ui/app/view/stubs.html +19 -0
- data/assets/ui/app/view/stubs/edit.html +58 -0
- data/assets/ui/css/main.css +3 -0
- data/bin/agile_proxy +4 -0
- data/bower.json +27 -0
- data/config.yml +6 -0
- data/db.yml +10 -0
- data/db/migrations/20140818110800_create_users.rb +9 -0
- data/db/migrations/20140818134700_create_applications.rb +10 -0
- data/db/migrations/20140818135200_create_request_specs.rb +13 -0
- data/db/migrations/20140821115300_create_responses.rb +14 -0
- data/db/migrations/20140823082900_add_method_to_request_specs.rb +7 -0
- data/db/migrations/20140823083900_rename_request_spec_columns.rb +8 -0
- data/db/migrations/20141031072100_add_url_type_to_request_specs.rb +8 -0
- data/db/migrations/20141105125600_add_conditions_to_request_specs.rb +7 -0
- data/db/migrations/20141106083100_add_username_and_password_to_applications.rb +8 -0
- data/db/migrations/20141119143800_add_record_to_applications.rb +7 -0
- data/db/migrations/20141119174300_create_recordings.rb +18 -0
- data/db/migrations/20150221152500_add_record_requests_to_request_specs.rb +7 -0
- data/db/schema.rb +78 -0
- data/db/seed.rb +26 -0
- data/echo_server.rb +19 -0
- data/examples/README.md +1 -0
- data/examples/facebook_api.html +59 -0
- data/examples/tumblr_api.html +22 -0
- data/lib/agile_proxy.rb +8 -0
- data/lib/agile_proxy/api/applications.rb +77 -0
- data/lib/agile_proxy/api/recordings.rb +52 -0
- data/lib/agile_proxy/api/request_spec_recordings.rb +52 -0
- data/lib/agile_proxy/api/request_specs.rb +86 -0
- data/lib/agile_proxy/api/root.rb +45 -0
- data/lib/agile_proxy/cli.rb +116 -0
- data/lib/agile_proxy/config.rb +66 -0
- data/lib/agile_proxy/handlers/handler.rb +43 -0
- data/lib/agile_proxy/handlers/proxy_handler.rb +111 -0
- data/lib/agile_proxy/handlers/request_handler.rb +75 -0
- data/lib/agile_proxy/handlers/stub_handler.rb +146 -0
- data/lib/agile_proxy/mitm.crt +22 -0
- data/lib/agile_proxy/mitm.key +27 -0
- data/lib/agile_proxy/model/application.rb +20 -0
- data/lib/agile_proxy/model/recording.rb +17 -0
- data/lib/agile_proxy/model/request_spec.rb +48 -0
- data/lib/agile_proxy/model/response.rb +51 -0
- data/lib/agile_proxy/model/user.rb +17 -0
- data/lib/agile_proxy/proxy_connection.rb +112 -0
- data/lib/agile_proxy/rack/get_only_cache.rb +30 -0
- data/lib/agile_proxy/route.rb +106 -0
- data/lib/agile_proxy/router.rb +99 -0
- data/lib/agile_proxy/server.rb +119 -0
- data/lib/agile_proxy/servers/api.rb +40 -0
- data/lib/agile_proxy/servers/request_spec.rb +40 -0
- data/lib/agile_proxy/servers/request_spec_direct.rb +35 -0
- data/lib/agile_proxy/version.rb +6 -0
- data/load_proxy.js +39 -0
- data/log/.gitkeep +0 -0
- data/spec/common_helper.rb +32 -0
- data/spec/fixtures/example_static_file.html +1 -0
- data/spec/fixtures/test-server.crt +15 -0
- data/spec/fixtures/test-server.key +15 -0
- data/spec/integration/helpers/request_spec_helper.rb +84 -0
- data/spec/integration/specs/lib/server_spec.rb +474 -0
- data/spec/integration_spec_helper.rb +16 -0
- data/spec/spec_helper.rb +39 -0
- data/spec/support/test_server.rb +105 -0
- data/spec/unit/agile_proxy/api/applications_spec.rb +102 -0
- data/spec/unit/agile_proxy/api/common_helper.rb +31 -0
- data/spec/unit/agile_proxy/api/recordings_spec.rb +115 -0
- data/spec/unit/agile_proxy/api/request_spec_recordings_spec.rb +119 -0
- data/spec/unit/agile_proxy/api/request_specs_spec.rb +159 -0
- data/spec/unit/agile_proxy/handlers/handler_spec.rb +8 -0
- data/spec/unit/agile_proxy/handlers/proxy_handler_spec.rb +138 -0
- data/spec/unit/agile_proxy/handlers/request_handler_spec.rb +76 -0
- data/spec/unit/agile_proxy/handlers/stub_handler_spec.rb +177 -0
- data/spec/unit/agile_proxy/model/recording_spec.rb +0 -0
- data/spec/unit/agile_proxy/model/request_spec_spec.rb +45 -0
- data/spec/unit/agile_proxy/model/response_spec.rb +38 -0
- data/spec/unit/agile_proxy/server_spec.rb +91 -0
- data/spec/unit/agile_proxy/servers/api_spec.rb +35 -0
- data/spec/unit/agile_proxy/servers/request_spec_direct_spec.rb +51 -0
- data/spec/unit/agile_proxy/servers/request_spec_spec.rb +35 -0
- metadata +736 -0
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'agile_proxy/handlers/handler'
|
2
|
+
require 'eventmachine'
|
3
|
+
require 'em-synchrony/em-http'
|
4
|
+
|
5
|
+
module AgileProxy
|
6
|
+
# = The handler used for proxying the request on to the original server
|
7
|
+
#
|
8
|
+
# This handler is responsible for proxying the request through to the original server and passing back its response
|
9
|
+
#
|
10
|
+
# It works as a rack end point as usual :-)
|
11
|
+
#
|
12
|
+
class ProxyHandler
|
13
|
+
include Handler
|
14
|
+
|
15
|
+
# The endpoint called by 'rack'
|
16
|
+
#
|
17
|
+
# Requests the response back from the destination server and passes it back to the client
|
18
|
+
#
|
19
|
+
# @param env [Hash] The rack environment hash
|
20
|
+
# @return [Array] The rack response array (status, headers, body)
|
21
|
+
def call(env)
|
22
|
+
request = ActionDispatch::Request.new(env)
|
23
|
+
method = request.request_method.downcase
|
24
|
+
body = request.body.read
|
25
|
+
request.body.rewind
|
26
|
+
url = request.url
|
27
|
+
if handles_request?(request)
|
28
|
+
req = EventMachine::HttpRequest.new(request.url,
|
29
|
+
inactivity_timeout: AgileProxy.config.proxied_request_inactivity_timeout,
|
30
|
+
connect_timeout: AgileProxy.config.proxied_request_connect_timeout
|
31
|
+
)
|
32
|
+
|
33
|
+
req = req.send(method.downcase, build_request_options(request.headers, body))
|
34
|
+
|
35
|
+
if req.error
|
36
|
+
return [500, {}, ["Request to #{request.url} failed with error: #{req.error}"]]
|
37
|
+
end
|
38
|
+
|
39
|
+
if req.response
|
40
|
+
response = process_response(req)
|
41
|
+
|
42
|
+
unless allowed_response_code?(response[:status])
|
43
|
+
AgileProxy.log(:warn, "agile-proxy: Received response status code #{response[:status]} for '#{url}'")
|
44
|
+
end
|
45
|
+
|
46
|
+
AgileProxy.log(:info, "agile-proxy: PROXY #{request.request_method} succeeded for '#{request.url}'")
|
47
|
+
return [response[:status], response[:headers], response[:content]]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
[404, {}, ['Not proxied']]
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def handles_request?(request)
|
56
|
+
!disabled_request?(request.url)
|
57
|
+
end
|
58
|
+
|
59
|
+
def build_request_options(headers, body)
|
60
|
+
headers = Hash[headers.map { |k, v| [k.downcase, v] }]
|
61
|
+
headers.delete('accept-encoding')
|
62
|
+
|
63
|
+
req_opts = {
|
64
|
+
redirects: 0,
|
65
|
+
keepalive: false,
|
66
|
+
head: headers,
|
67
|
+
ssl: { verify: false }
|
68
|
+
}
|
69
|
+
req_opts[:body] = body if body
|
70
|
+
req_opts
|
71
|
+
end
|
72
|
+
|
73
|
+
def process_response(req)
|
74
|
+
response = {
|
75
|
+
status: req.response_header.status,
|
76
|
+
headers: req.response_header.raw,
|
77
|
+
content: [req.response.force_encoding('BINARY')] }
|
78
|
+
response[:headers].merge!('Connection' => 'close')
|
79
|
+
response[:headers].delete('Transfer-Encoding')
|
80
|
+
response[:headers].merge!('Cache-Control' => 'max-age=3600')
|
81
|
+
response
|
82
|
+
end
|
83
|
+
|
84
|
+
def disabled_request?(url)
|
85
|
+
return false unless AgileProxy.config.non_whitelisted_requests_disabled
|
86
|
+
|
87
|
+
uri = URI(url)
|
88
|
+
# In isolated environments, you may want to stop the request from happening
|
89
|
+
# or else you get "getaddrinfo: Name or service not known" errors
|
90
|
+
blacklisted_path?(uri.path) || !whitelisted_url?(uri)
|
91
|
+
end
|
92
|
+
|
93
|
+
def allowed_response_code?(status)
|
94
|
+
successful_status?(status)
|
95
|
+
end
|
96
|
+
|
97
|
+
def whitelisted_url?(url)
|
98
|
+
!AgileProxy.config.whitelist.index do |v|
|
99
|
+
v =~ /^#{url.host}(?::#{url.port})?$/
|
100
|
+
end.nil?
|
101
|
+
end
|
102
|
+
|
103
|
+
def blacklisted_path?(path)
|
104
|
+
!AgileProxy.config.path_blacklist.index { |bl| path.include?(bl) }.nil?
|
105
|
+
end
|
106
|
+
|
107
|
+
def successful_status?(status)
|
108
|
+
(200..299).include?(status) || status == 304
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'agile_proxy/model/application'
|
2
|
+
require 'agile_proxy/model/recording'
|
3
|
+
require 'rack'
|
4
|
+
require 'forwardable'
|
5
|
+
require 'agile_proxy/rack/get_only_cache'
|
6
|
+
module AgileProxy
|
7
|
+
#
|
8
|
+
# =The Central Request Handler
|
9
|
+
#
|
10
|
+
# As a request is made from the client to the server via the proxy server, it comes through an instance of this class.
|
11
|
+
#
|
12
|
+
# This class will then pass the request on to the StubHandler, then finally the ProxyHandler
|
13
|
+
class RequestHandler
|
14
|
+
extend Forwardable
|
15
|
+
include Handler
|
16
|
+
|
17
|
+
def_delegators :stub_handler, :stub
|
18
|
+
|
19
|
+
def initialize(options = {})
|
20
|
+
@options = options
|
21
|
+
end
|
22
|
+
# A rack endpoint
|
23
|
+
#
|
24
|
+
# This method is called as a rack endpoint and returns a rack response.
|
25
|
+
#
|
26
|
+
# @param env [Hash] The 'rack' environment
|
27
|
+
# @return [Array] The rack response (status, headers, content)
|
28
|
+
def call(env)
|
29
|
+
request = ActionDispatch::Request.new(env)
|
30
|
+
username, password = username_password env
|
31
|
+
application = Application.where(username: username, password: password).first
|
32
|
+
body = request.body.read
|
33
|
+
request.body.rewind
|
34
|
+
rack_response = rack_app.call(env)
|
35
|
+
if rack_response[0] == 404
|
36
|
+
rack_response = [
|
37
|
+
500,
|
38
|
+
{},
|
39
|
+
"Connection to #{request.url}#{body} not stubbed and new http connections are disabled"
|
40
|
+
]
|
41
|
+
end
|
42
|
+
request_spec = env['agile_proxy.request_spec']
|
43
|
+
exclude_headers = ['@env', 'rack.errors', 'rack.logger']
|
44
|
+
if application.record_requests || (request_spec && request_spec.record_requests)
|
45
|
+
application.recordings.create request_headers: request.headers.reject {|key, value| exclude_headers.include?(key)},
|
46
|
+
request_body: body,
|
47
|
+
request_url: request.url,
|
48
|
+
request_method: request.request_method,
|
49
|
+
response_headers: rack_response[1],
|
50
|
+
response_body: rack_response[2],
|
51
|
+
response_status: rack_response[0],
|
52
|
+
request_spec_id: request_spec ? request_spec.id : nil
|
53
|
+
end
|
54
|
+
rack_response
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def rack_app
|
60
|
+
stub_handler = stub_handler_app
|
61
|
+
proxy_handler = proxy_handler_app
|
62
|
+
options = @options
|
63
|
+
@__app ||= ::Rack::Builder.new do
|
64
|
+
use Rack::GetOnlyCache if options[:enable_cache]
|
65
|
+
run ::Rack::Cascade.new([stub_handler, proxy_handler])
|
66
|
+
end
|
67
|
+
end
|
68
|
+
def stub_handler_app
|
69
|
+
@_stub_handler_app ||= StubHandler.new
|
70
|
+
end
|
71
|
+
def proxy_handler_app
|
72
|
+
@_proxy_handler_app ||= ProxyHandler.new
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
require 'agile_proxy/handlers/handler'
|
2
|
+
require 'agile_proxy/model/request_spec'
|
3
|
+
require 'agile_proxy/router'
|
4
|
+
require 'agile_proxy/model/application'
|
5
|
+
require 'action_dispatch'
|
6
|
+
require 'base64'
|
7
|
+
module AgileProxy
|
8
|
+
# = The stub handler
|
9
|
+
#
|
10
|
+
# This class is resonsible for matching incoming requests
|
11
|
+
# with a stub (request spec) and if one exists, respond with it
|
12
|
+
# rather than the real response from the destination server.
|
13
|
+
class StubHandler
|
14
|
+
include Handler
|
15
|
+
|
16
|
+
# Called by 'rack' as an endpoint.
|
17
|
+
#
|
18
|
+
# Fetches a 'short list' of potential matching request specs and builds a routing table to allow
|
19
|
+
# a router to do the hard work of parsing parameters, matching them, matching URL's etc...
|
20
|
+
#
|
21
|
+
# @param env [Hash] The rack environment
|
22
|
+
# @return [Array] The rack response [status, headers, body]
|
23
|
+
def call(env)
|
24
|
+
request = ActionDispatch::Request.new(env)
|
25
|
+
username, password = username_password env
|
26
|
+
@route_set = ActionDispatch::Routing::RouteSet.new
|
27
|
+
|
28
|
+
my_short_list = short_list(username, password, request.request_method, request.url).all.to_a.reverse
|
29
|
+
headers = downcased_headers(env)
|
30
|
+
body = request.body.read
|
31
|
+
request.body.rewind
|
32
|
+
setup_router my_short_list, request, headers, body
|
33
|
+
rack_app.call(env)
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def setup_router(my_short_list, request, headers, body)
|
39
|
+
me = self
|
40
|
+
@route_set.draw do
|
41
|
+
route_set_instance = self
|
42
|
+
me.instance_eval do
|
43
|
+
my_short_list.each(&add_to_router(request, headers, body, route_set_instance))
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def add_to_router(request, headers, body, route_set)
|
49
|
+
# This needs a bit of explaining as the router did not quite behave as expected.
|
50
|
+
# This proc calls route definition methods such as get, post etc.. with the path and handler
|
51
|
+
# the handler being an inline proc.
|
52
|
+
# But, we also pass a constraints handler
|
53
|
+
# After constraints are handled though, the 'action_dispatch.request.parameters' are deleted
|
54
|
+
# from the env, so our handler preserves them in agile_proxy.parameters.
|
55
|
+
# A bit weird, but it works reliably.
|
56
|
+
proc do |spec|
|
57
|
+
path = URI.parse(spec.url).path
|
58
|
+
path = '/' if path == ''
|
59
|
+
method = spec.http_method.downcase.to_sym
|
60
|
+
method = :get if method == :head
|
61
|
+
route_spec = {
|
62
|
+
path => proc do |router_env|
|
63
|
+
AgileProxy.log(:info, "agile-proxy: STUB #{method} for '#{request.url}'")
|
64
|
+
router_env['agile_proxy.request_spec'] = spec
|
65
|
+
spec.call(HashWithIndifferentAccess.new(router_env['action_dispatch.request.path_parameters'].merge(router_env['action_dispatch.request.query_parameters']).merge(router_env['action_dispatch.request.request_parameters'])), headers, body)
|
66
|
+
end
|
67
|
+
}
|
68
|
+
route_spec[:constraints] = ->(request) {
|
69
|
+
ret_value = spec.conditions_json.all? do |k, v|
|
70
|
+
request.params.key?(k) && request.params[k] == v
|
71
|
+
end
|
72
|
+
ret_value
|
73
|
+
}
|
74
|
+
route_set.send method, route_spec
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def collection(username, password)
|
79
|
+
#An empty string and NULL are the same thing here. The UI has no way of sending NULL when creating an application
|
80
|
+
if (username.nil? && password.nil?)
|
81
|
+
Application.where('(username = ? OR username IS NULL) AND (password = ? OR password IS NULL)', '', '').first.request_specs
|
82
|
+
else
|
83
|
+
Application.where(username: username, password: password).first.request_specs
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def short_list(username, password, _method, url)
|
88
|
+
parsed_url = URI.parse(url)
|
89
|
+
parsed_url.path = ''
|
90
|
+
parsed_url.query = nil
|
91
|
+
parsed_url.fragment = nil
|
92
|
+
# @TODO This is being lazy - the ruby code will do all the finding work.
|
93
|
+
# This has to change as we go towards mutli user and larger data sets
|
94
|
+
collection(username, password).where('url LIKE ?', "#{parsed_url}%")
|
95
|
+
end
|
96
|
+
|
97
|
+
def rack_app
|
98
|
+
route_set = @route_set
|
99
|
+
text_handler = plain_text_handler
|
100
|
+
::Rack::Builder.new do
|
101
|
+
use ActionDispatch::ParamsParser, Mime::TEXT => text_handler
|
102
|
+
use MergePostAndGetParams
|
103
|
+
use ::Rack::ContentLength
|
104
|
+
run route_set
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def plain_text_handler
|
109
|
+
proc do |raw_post|
|
110
|
+
data_as_array = raw_post.split("\n").map do |line|
|
111
|
+
arr = line.split('=')
|
112
|
+
arr << nil if arr.length == 1
|
113
|
+
arr
|
114
|
+
end.flatten
|
115
|
+
data = Hash[*data_as_array]
|
116
|
+
ActionDispatch::Request::Utils.deep_munge(data).with_indifferent_access
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
#
|
121
|
+
# @private
|
122
|
+
# A rack middleware to merge post and get parameters ready for routing
|
123
|
+
class MergePostAndGetParams
|
124
|
+
def initialize(app)
|
125
|
+
@app = app
|
126
|
+
end
|
127
|
+
|
128
|
+
def call(env)
|
129
|
+
request = ActionDispatch::Request.new(env)
|
130
|
+
request.params #Might seem odd, but action dispatch request is a bit naughty and adds the appropriate
|
131
|
+
# bits to ENV during access to this getter when used for the first time
|
132
|
+
env['action_dispatch.request.parameters'].merge!(
|
133
|
+
env['action_dispatch.request.request_parameters']
|
134
|
+
) unless request.content_length.zero?
|
135
|
+
@app.call(env)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
module ActionDispatch
|
140
|
+
module Routing
|
141
|
+
# Extension to action dispatch to ensure all request parameters are collected not just GET
|
142
|
+
class RouteSet
|
143
|
+
PARAMETERS_KEY = 'action_dispatch.request.parameters'
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
-----BEGIN CERTIFICATE-----
|
2
|
+
MIIDojCCAooCCQCYeVsjl+UFxzANBgkqhkiG9w0BAQUFADCBkjELMAkGA1UEBhMC
|
3
|
+
VVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MSEwHwYDVQQK
|
4
|
+
ExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxFjAUBgNVBAMTDVB1ZmZpbmcgQmls
|
5
|
+
bHkxIzAhBgkqhkiG9w0BCQEWFG9sbHkuc21pdGhAZ21haWwuY29tMB4XDTEyMTAw
|
6
|
+
MjA2NDIxMVoXDTEzMTAwMjA2NDIxMVowgZIxCzAJBgNVBAYTAlVTMQswCQYDVQQI
|
7
|
+
EwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEhMB8GA1UEChMYSW50ZXJuZXQg
|
8
|
+
V2lkZ2l0cyBQdHkgTHRkMRYwFAYDVQQDEw1QdWZmaW5nIEJpbGx5MSMwIQYJKoZI
|
9
|
+
hvcNAQkBFhRvbGx5LnNtaXRoQGdtYWlsLmNvbTCCASIwDQYJKoZIhvcNAQEBBQAD
|
10
|
+
ggEPADCCAQoCggEBALW7WQIIGCgUk28x1eIV0+VDG6SZV8dosJPU3WM4zLT0KLsv
|
11
|
+
2+5KNO373KyXhIDQqkv+4+1myVYj5wkvDSFMLYvBTNmypS7Xh6+wyKjQQ5wupXoB
|
12
|
+
BNrJw5CbfUX9Kwq31H6aO1ItWZ6QHjW4dpE3AOu7ohwwyyL2Q0QkzTeP5b6SofbV
|
13
|
+
9y1DpeH5L2an84LIjm1XrNbswY0p0CDT7TTsmYoXnw0MD5I73rT4arOOKWdVrD/G
|
14
|
+
lbvfcZN6c+nN713tGI9XfM5cQEXUsbSY4NKuF+jn7B2tsx+QRVwUyMwS8ZVpDt5q
|
15
|
+
DMBwROskWTMLKxeuxxpmjFWHlLuBlJ0M/rKII98CAwEAATANBgkqhkiG9w0BAQUF
|
16
|
+
AAOCAQEAMkhR7JDQKcYDy5Yb80cPIjw5kaEfTb+/Vh6DLLEtwEVIc2PcIvXCBZRo
|
17
|
+
79Esa42BaNEaWPxRVAs0Doubh4IdGk9qp9a/71gqhi3iNe//depX5VLf1+5LnQtV
|
18
|
+
BbFruXUkzOF4kykm5Fcf9yQ9W951d0qaT6K9LefSsaoMrQjgKCeFAcIOXjqHyWns
|
19
|
+
KAO34C1kzAFemMY20RcgZf9/+09Zs/ZiDBeoWTc+juaa9zCgvg/1FKHqsA/iLg6/
|
20
|
+
VcdOquI9fJ5hRTZFkPoFLpxf2O1H3IpyNXmpcJDFvKfh7wIcHkLWIg5BFXc2S4+v
|
21
|
+
bNFEKqipZH+D9++jXGyumxA8VoZOOA==
|
22
|
+
-----END CERTIFICATE-----
|
@@ -0,0 +1,27 @@
|
|
1
|
+
-----BEGIN RSA PRIVATE KEY-----
|
2
|
+
MIIEowIBAAKCAQEAtbtZAggYKBSTbzHV4hXT5UMbpJlXx2iwk9TdYzjMtPQouy/b
|
3
|
+
7ko07fvcrJeEgNCqS/7j7WbJViPnCS8NIUwti8FM2bKlLteHr7DIqNBDnC6legEE
|
4
|
+
2snDkJt9Rf0rCrfUfpo7Ui1ZnpAeNbh2kTcA67uiHDDLIvZDRCTNN4/lvpKh9tX3
|
5
|
+
LUOl4fkvZqfzgsiObVes1uzBjSnQINPtNOyZihefDQwPkjvetPhqs44pZ1WsP8aV
|
6
|
+
u99xk3pz6c3vXe0Yj1d8zlxARdSxtJjg0q4X6OfsHa2zH5BFXBTIzBLxlWkO3moM
|
7
|
+
wHBE6yRZMwsrF67HGmaMVYeUu4GUnQz+sogj3wIDAQABAoIBAEM8cF7vDbjue+m8
|
8
|
+
32wJNV9yJ60LSs2tLv9S1yHZpusgFl3DBDSyYcjW0TtNx6k9CnSZdkykJcNn/xeH
|
9
|
+
v+zc2VEGkF9O2Axvk3TuDB9hBlKnc3OjIt+rnF5JGN0nIKCTiNvaRi5ONwUSPwsT
|
10
|
+
F1L8rauJvR1+8/kYcaSplP+EjrSlvaKtUPNKfB8V6IktVLHiBUFs7nWoOGSaEB4/
|
11
|
+
8jlji4s8xzVammTRg175mhOkgqdI+cWfXnn8PhOJZ2XjZz5eFOWxdvNldBbsm9Yc
|
12
|
+
g3URB9FpOPMY8DvgZNrAYp6/7C9ACecAIZAnDK0/aQOa95UeeCEbmAv59F2OqOc2
|
13
|
+
35HUSrkCgYEA55e+o7pxUY7YPVopBz27JWANk/zwMN1ZgDlR87Q0ENmrjOB8kPmz
|
14
|
+
02SHuVoXMPj/SD/S4J1NY2o7u5ofSpxUsdofCETsCeCbqhshRLqCu7yCjGagHknY
|
15
|
+
M4dJdRoFPN03gy+qHB7EKT0U89lvwRsjShc8h56wXsRHP6qM2uVgGyUCgYEAyOJh
|
16
|
+
ZPKBa0WVG9NxFS4Ej7CDpmWA415PyCrTK9cA2LCA0sWXHAhJ9guUx4zlqwLkfYBr
|
17
|
+
mmobAKjllFOUiECFLs49Oy/vD9Kw/ga9KO6eU9E/j9XHLVYupkPEVWeM7jzaQBul
|
18
|
+
CvMcPGgXX/84Xgtjjim8RuAPIgmRHSDfqRFUtbMCgYEAmQwxEiZuKMXLpY/luUFU
|
19
|
+
Yfi+QGRRnxlIwnIe9HzMQ651rl3UNEKwUi0HfLhKxzRmECsNgx6xO9fCrdHGiBoT
|
20
|
+
5o0NIPvbORPUC3BuZesT5llHtN1FR37pf/QR2W9essBGpU1kj7zNSatyI0w4jFcQ
|
21
|
+
1S/R8pYuXBI+O5bMCwS2pHkCgYBqFKG53RXav/PtrcqZlKNz/ZKH3DIj3zniSjsZ
|
22
|
+
e4BG7W4Z353cf8QO2i7G8fCWTgC7BYXNFRsNTiNuIHTfPrMV9HMBPl7PzEMK4iQh
|
23
|
+
6WBSgr0+B3YWytv3kPGs5/HUHO5jzDVrgtX2UEGHwA7UGs+H0yJJiyhyoPqwlxuE
|
24
|
+
/FHvYQKBgEJaI/DaSpxJotq56lBHzTte1icbD8IFXMlbmWyIFxONWxlz98tQ2ypp
|
25
|
+
WhMGIzMsFE1X+P8vRbc865t36UMpucjRuAJ3MTeeCa60EghmdfajzA2ZWh/MnEyA
|
26
|
+
yvTr72g5gj5Yi/Zbiy1xeTo3UeaBxiCi8xyWAGodYg8891UVKqJu
|
27
|
+
-----END RSA PRIVATE KEY-----
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require_relative 'user'
|
2
|
+
require_relative 'recording'
|
3
|
+
module AgileProxy
|
4
|
+
#
|
5
|
+
# = The Application Model
|
6
|
+
#
|
7
|
+
# The application model is responsible for storing and retrieving information about the application
|
8
|
+
# that the application under test / development is using.
|
9
|
+
#
|
10
|
+
# This is found using the username and password, therefore, a username and password must be globally unique.
|
11
|
+
#
|
12
|
+
# The reason for doing it this way is because we can only pass username and password in the URL for the proxy server
|
13
|
+
#
|
14
|
+
class Application < ActiveRecord::Base
|
15
|
+
belongs_to :user
|
16
|
+
has_many :request_specs, :dependent => :destroy
|
17
|
+
has_many :recordings, :dependent => :delete_all
|
18
|
+
validates_uniqueness_of :password, scope: :username
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require_relative 'application'
|
2
|
+
require 'active_record'
|
3
|
+
module AgileProxy
|
4
|
+
#
|
5
|
+
# = The recording model
|
6
|
+
#
|
7
|
+
# When an application is set to allow recording, every HTTP(s) request/response cycle coming through the proxy
|
8
|
+
# will create an instance of this model and persist it to the database.
|
9
|
+
#
|
10
|
+
# An API is then available to access this data via REST for the UI or test suite etc...
|
11
|
+
#
|
12
|
+
class Recording < ActiveRecord::Base
|
13
|
+
belongs_to :application
|
14
|
+
serialize :request_headers, JSON
|
15
|
+
serialize :response_headers, JSON
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require_relative 'application'
|
2
|
+
require_relative 'user'
|
3
|
+
require_relative 'response'
|
4
|
+
module AgileProxy
|
5
|
+
#
|
6
|
+
# = The Request Spec model
|
7
|
+
# The request spec is an input/output specification that incoming HTTP(s) requests are matched against.
|
8
|
+
#
|
9
|
+
# It uses the action dispatch router to do this matching,
|
10
|
+
# which is fed data from the database as it's input - i.e. its routing
|
11
|
+
# table is generated on the fly.
|
12
|
+
#
|
13
|
+
# This model is responsible not only for retrieving and persisting these
|
14
|
+
# request specifications, but also for creating the response
|
15
|
+
#
|
16
|
+
class RequestSpec < ActiveRecord::Base
|
17
|
+
belongs_to :application
|
18
|
+
belongs_to :user
|
19
|
+
belongs_to :response, :dependent => :destroy
|
20
|
+
accepts_nested_attributes_for :response
|
21
|
+
validates_inclusion_of :url_type, in: %w(url regex)
|
22
|
+
validates_presence_of :application_id
|
23
|
+
def initialize(attrs = {})
|
24
|
+
attrs[:http_method] = attrs.delete(:method) if attrs.key?(:method)
|
25
|
+
super
|
26
|
+
end
|
27
|
+
# The conditions are at present, stored as a JSON string. This is editable as a string in the UI, and therefore
|
28
|
+
# accessible using 'conditions' as normal.
|
29
|
+
# This method returns a JSON decoded version of this as a HASH
|
30
|
+
# @return [Hash] decoded conditions
|
31
|
+
def conditions_json
|
32
|
+
ActiveSupport::JSON.decode(conditions).with_indifferent_access
|
33
|
+
end
|
34
|
+
# This method's output is a 'rack' response, but its input is not.
|
35
|
+
# When the router has determined that this request spec is the one that is going to be sent to the client,
|
36
|
+
# it will call this method with the request's parameters, headers and body.
|
37
|
+
#
|
38
|
+
# if no response has been specified, an empty body will be returned,
|
39
|
+
# otherwise a 'rack' version of the response is returned
|
40
|
+
# @param params [Hash] the request parameters
|
41
|
+
# @param headers [Hash] The request headers
|
42
|
+
# @param body [String] The request body
|
43
|
+
# @return [Array] The rack response
|
44
|
+
def call(params, headers, body)
|
45
|
+
response.nil? ? [204, { 'Content-Type' => 'text/plain'}, ''] : response.to_rack(params, headers, body)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|