agile-proxy-jruby 0.1.25-jruby

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. checksums.yaml +7 -0
  2. data/.bowerrc +3 -0
  3. data/.gitignore +10 -0
  4. data/.rspec +2 -0
  5. data/.rubocop.yml +36 -0
  6. data/.travis.yml +10 -0
  7. data/Gemfile +4 -0
  8. data/Guardfile +20 -0
  9. data/LICENSE +22 -0
  10. data/README.md +131 -0
  11. data/Rakefile +15 -0
  12. data/agile-proxy.gemspec +60 -0
  13. data/assets/index.html +39 -0
  14. data/assets/ui/app/AgileProxyApi.js +31 -0
  15. data/assets/ui/app/app.js +1 -0
  16. data/assets/ui/app/controller/Stubs.js +64 -0
  17. data/assets/ui/app/controller/main.js +12 -0
  18. data/assets/ui/app/directive/AppEnhancedFormElement.js +21 -0
  19. data/assets/ui/app/directive/AppFor.js +16 -0
  20. data/assets/ui/app/directive/AppResponseEditor.js +54 -0
  21. data/assets/ui/app/model/RequestSpec.js +6 -0
  22. data/assets/ui/app/routes.js +11 -0
  23. data/assets/ui/app/service/Dialog.js +49 -0
  24. data/assets/ui/app/service/DomId.js +10 -0
  25. data/assets/ui/app/service/Error.js +7 -0
  26. data/assets/ui/app/service/Stub.js +36 -0
  27. data/assets/ui/app/view/404.html +2 -0
  28. data/assets/ui/app/view/dialog/error.html +10 -0
  29. data/assets/ui/app/view/dialog/yesNo.html +8 -0
  30. data/assets/ui/app/view/responses/editForm.html +78 -0
  31. data/assets/ui/app/view/status.html +1 -0
  32. data/assets/ui/app/view/stubs.html +19 -0
  33. data/assets/ui/app/view/stubs/edit.html +58 -0
  34. data/assets/ui/css/main.css +3 -0
  35. data/bin/agile_proxy +4 -0
  36. data/bower.json +27 -0
  37. data/config.yml +6 -0
  38. data/db.yml +10 -0
  39. data/db/migrations/20140818110800_create_users.rb +9 -0
  40. data/db/migrations/20140818134700_create_applications.rb +10 -0
  41. data/db/migrations/20140818135200_create_request_specs.rb +13 -0
  42. data/db/migrations/20140821115300_create_responses.rb +14 -0
  43. data/db/migrations/20140823082900_add_method_to_request_specs.rb +7 -0
  44. data/db/migrations/20140823083900_rename_request_spec_columns.rb +8 -0
  45. data/db/migrations/20141031072100_add_url_type_to_request_specs.rb +8 -0
  46. data/db/migrations/20141105125600_add_conditions_to_request_specs.rb +7 -0
  47. data/db/migrations/20141106083100_add_username_and_password_to_applications.rb +8 -0
  48. data/db/migrations/20141119143800_add_record_to_applications.rb +7 -0
  49. data/db/migrations/20141119174300_create_recordings.rb +18 -0
  50. data/db/migrations/20150221152500_add_record_requests_to_request_specs.rb +7 -0
  51. data/db/schema.rb +78 -0
  52. data/db/seed.rb +26 -0
  53. data/echo_server.rb +19 -0
  54. data/examples/README.md +1 -0
  55. data/examples/facebook_api.html +59 -0
  56. data/examples/tumblr_api.html +22 -0
  57. data/lib/agile_proxy.rb +8 -0
  58. data/lib/agile_proxy/api/applications.rb +77 -0
  59. data/lib/agile_proxy/api/recordings.rb +52 -0
  60. data/lib/agile_proxy/api/request_spec_recordings.rb +52 -0
  61. data/lib/agile_proxy/api/request_specs.rb +86 -0
  62. data/lib/agile_proxy/api/root.rb +45 -0
  63. data/lib/agile_proxy/cli.rb +116 -0
  64. data/lib/agile_proxy/config.rb +66 -0
  65. data/lib/agile_proxy/handlers/handler.rb +43 -0
  66. data/lib/agile_proxy/handlers/proxy_handler.rb +111 -0
  67. data/lib/agile_proxy/handlers/request_handler.rb +75 -0
  68. data/lib/agile_proxy/handlers/stub_handler.rb +146 -0
  69. data/lib/agile_proxy/mitm.crt +22 -0
  70. data/lib/agile_proxy/mitm.key +27 -0
  71. data/lib/agile_proxy/model/application.rb +20 -0
  72. data/lib/agile_proxy/model/recording.rb +17 -0
  73. data/lib/agile_proxy/model/request_spec.rb +48 -0
  74. data/lib/agile_proxy/model/response.rb +51 -0
  75. data/lib/agile_proxy/model/user.rb +17 -0
  76. data/lib/agile_proxy/proxy_connection.rb +112 -0
  77. data/lib/agile_proxy/rack/get_only_cache.rb +30 -0
  78. data/lib/agile_proxy/route.rb +106 -0
  79. data/lib/agile_proxy/router.rb +99 -0
  80. data/lib/agile_proxy/server.rb +119 -0
  81. data/lib/agile_proxy/servers/api.rb +40 -0
  82. data/lib/agile_proxy/servers/request_spec.rb +40 -0
  83. data/lib/agile_proxy/servers/request_spec_direct.rb +35 -0
  84. data/lib/agile_proxy/version.rb +6 -0
  85. data/load_proxy.js +39 -0
  86. data/log/.gitkeep +0 -0
  87. data/spec/common_helper.rb +32 -0
  88. data/spec/fixtures/example_static_file.html +1 -0
  89. data/spec/fixtures/test-server.crt +15 -0
  90. data/spec/fixtures/test-server.key +15 -0
  91. data/spec/integration/helpers/request_spec_helper.rb +84 -0
  92. data/spec/integration/specs/lib/server_spec.rb +474 -0
  93. data/spec/integration_spec_helper.rb +16 -0
  94. data/spec/spec_helper.rb +39 -0
  95. data/spec/support/test_server.rb +105 -0
  96. data/spec/unit/agile_proxy/api/applications_spec.rb +102 -0
  97. data/spec/unit/agile_proxy/api/common_helper.rb +31 -0
  98. data/spec/unit/agile_proxy/api/recordings_spec.rb +115 -0
  99. data/spec/unit/agile_proxy/api/request_spec_recordings_spec.rb +119 -0
  100. data/spec/unit/agile_proxy/api/request_specs_spec.rb +159 -0
  101. data/spec/unit/agile_proxy/handlers/handler_spec.rb +8 -0
  102. data/spec/unit/agile_proxy/handlers/proxy_handler_spec.rb +138 -0
  103. data/spec/unit/agile_proxy/handlers/request_handler_spec.rb +76 -0
  104. data/spec/unit/agile_proxy/handlers/stub_handler_spec.rb +177 -0
  105. data/spec/unit/agile_proxy/model/recording_spec.rb +0 -0
  106. data/spec/unit/agile_proxy/model/request_spec_spec.rb +45 -0
  107. data/spec/unit/agile_proxy/model/response_spec.rb +38 -0
  108. data/spec/unit/agile_proxy/server_spec.rb +91 -0
  109. data/spec/unit/agile_proxy/servers/api_spec.rb +35 -0
  110. data/spec/unit/agile_proxy/servers/request_spec_direct_spec.rb +51 -0
  111. data/spec/unit/agile_proxy/servers/request_spec_spec.rb +35 -0
  112. 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