agile-proxy-jruby 0.1.25-jruby

Sign up to get free protection for your applications and to get access to all the features.
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