agile-proxy 0.1.0

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 (103) hide show
  1. checksums.yaml +7 -0
  2. data/.bowerrc +3 -0
  3. data/.gitignore +8 -0
  4. data/.rspec +2 -0
  5. data/.rubocop.yml +36 -0
  6. data/.travis.yml +8 -0
  7. data/Gemfile +4 -0
  8. data/Gemfile.lock +267 -0
  9. data/Guardfile +20 -0
  10. data/LICENSE +22 -0
  11. data/README.md +93 -0
  12. data/Rakefile +13 -0
  13. data/agile-proxy.gemspec +50 -0
  14. data/assets/index.html +39 -0
  15. data/assets/ui/app/HttpFlexibleProxyApi.js +31 -0
  16. data/assets/ui/app/app.js +1 -0
  17. data/assets/ui/app/controller/Stubs.js +64 -0
  18. data/assets/ui/app/controller/main.js +12 -0
  19. data/assets/ui/app/directive/AppEnhancedFormElement.js +21 -0
  20. data/assets/ui/app/directive/AppFor.js +16 -0
  21. data/assets/ui/app/directive/AppResponseEditor.js +54 -0
  22. data/assets/ui/app/model/RequestSpec.js +6 -0
  23. data/assets/ui/app/routes.js +10 -0
  24. data/assets/ui/app/service/Dialog.js +49 -0
  25. data/assets/ui/app/service/DomId.js +10 -0
  26. data/assets/ui/app/service/Error.js +7 -0
  27. data/assets/ui/app/service/Stub.js +36 -0
  28. data/assets/ui/app/view/404.html +2 -0
  29. data/assets/ui/app/view/dialog/error.html +10 -0
  30. data/assets/ui/app/view/dialog/yesNo.html +8 -0
  31. data/assets/ui/app/view/responses/editForm.html +78 -0
  32. data/assets/ui/app/view/status.html +1 -0
  33. data/assets/ui/app/view/stubs.html +19 -0
  34. data/assets/ui/app/view/stubs/edit.html +58 -0
  35. data/assets/ui/css/main.css +3 -0
  36. data/bin/agile_proxy +113 -0
  37. data/bower.json +27 -0
  38. data/config.yml +6 -0
  39. data/db.yml +10 -0
  40. data/db/migrations/20140818110800_create_users.rb +9 -0
  41. data/db/migrations/20140818134700_create_applications.rb +10 -0
  42. data/db/migrations/20140818135200_create_request_specs.rb +13 -0
  43. data/db/migrations/20140821115300_create_responses.rb +14 -0
  44. data/db/migrations/20140823082900_add_method_to_request_specs.rb +7 -0
  45. data/db/migrations/20140823083900_rename_request_spec_columns.rb +8 -0
  46. data/db/migrations/20141031072100_add_url_type_to_request_specs.rb +8 -0
  47. data/db/migrations/20141105125600_add_conditions_to_request_specs.rb +7 -0
  48. data/db/migrations/20141106083100_add_username_and_password_to_applications.rb +8 -0
  49. data/db/migrations/20141119143800_add_record_to_applications.rb +7 -0
  50. data/db/migrations/20141119174300_create_recordings.rb +18 -0
  51. data/db/schema.rb +78 -0
  52. data/examples/README.md +1 -0
  53. data/examples/facebook_api.html +59 -0
  54. data/examples/tumblr_api.html +22 -0
  55. data/lib/agile_proxy.rb +8 -0
  56. data/lib/agile_proxy/api/applications.rb +77 -0
  57. data/lib/agile_proxy/api/recordings.rb +52 -0
  58. data/lib/agile_proxy/api/request_specs.rb +85 -0
  59. data/lib/agile_proxy/api/root.rb +41 -0
  60. data/lib/agile_proxy/config.rb +63 -0
  61. data/lib/agile_proxy/handlers/handler.rb +43 -0
  62. data/lib/agile_proxy/handlers/proxy_handler.rb +110 -0
  63. data/lib/agile_proxy/handlers/request_handler.rb +57 -0
  64. data/lib/agile_proxy/handlers/stub_handler.rb +113 -0
  65. data/lib/agile_proxy/mitm.crt +22 -0
  66. data/lib/agile_proxy/mitm.key +27 -0
  67. data/lib/agile_proxy/model/application.rb +20 -0
  68. data/lib/agile_proxy/model/recording.rb +16 -0
  69. data/lib/agile_proxy/model/request_spec.rb +47 -0
  70. data/lib/agile_proxy/model/response.rb +56 -0
  71. data/lib/agile_proxy/model/user.rb +17 -0
  72. data/lib/agile_proxy/proxy_connection.rb +113 -0
  73. data/lib/agile_proxy/route.rb +106 -0
  74. data/lib/agile_proxy/router.rb +99 -0
  75. data/lib/agile_proxy/server.rb +85 -0
  76. data/lib/agile_proxy/servers/api.rb +41 -0
  77. data/lib/agile_proxy/servers/request_spec.rb +30 -0
  78. data/lib/agile_proxy/version.rb +6 -0
  79. data/load_proxy.js +39 -0
  80. data/log/.gitkeep +0 -0
  81. data/spec/common_helper.rb +32 -0
  82. data/spec/fixtures/test-server.crt +15 -0
  83. data/spec/fixtures/test-server.key +15 -0
  84. data/spec/integration/helpers/request_spec_helper.rb +60 -0
  85. data/spec/integration/specs/lib/server_spec.rb +407 -0
  86. data/spec/integration_spec_helper.rb +18 -0
  87. data/spec/spec_helper.rb +39 -0
  88. data/spec/support/test_server.rb +75 -0
  89. data/spec/unit/agile_proxy/api/applications_spec.rb +102 -0
  90. data/spec/unit/agile_proxy/api/common_helper.rb +31 -0
  91. data/spec/unit/agile_proxy/api/recordings_spec.rb +115 -0
  92. data/spec/unit/agile_proxy/api/request_specs_spec.rb +159 -0
  93. data/spec/unit/agile_proxy/handlers/handler_spec.rb +8 -0
  94. data/spec/unit/agile_proxy/handlers/proxy_handler_spec.rb +138 -0
  95. data/spec/unit/agile_proxy/handlers/request_handler_spec.rb +55 -0
  96. data/spec/unit/agile_proxy/handlers/stub_handler_spec.rb +154 -0
  97. data/spec/unit/agile_proxy/model/recording_spec.rb +0 -0
  98. data/spec/unit/agile_proxy/model/request_spec_spec.rb +45 -0
  99. data/spec/unit/agile_proxy/model/response_spec.rb +38 -0
  100. data/spec/unit/agile_proxy/server_spec.rb +88 -0
  101. data/spec/unit/agile_proxy/servers/api_spec.rb +31 -0
  102. data/spec/unit/agile_proxy/servers/request_spec_spec.rb +32 -0
  103. metadata +618 -0
@@ -0,0 +1,63 @@
1
+ require 'logger'
2
+ require 'tmpdir'
3
+ # Agile Proxy
4
+ module AgileProxy
5
+ #
6
+ # Configuration for the agile proxy
7
+ class Config
8
+ DEFAULT_WHITELIST = ['127.0.0.1', 'localhost']
9
+ RANDOM_AVAILABLE_PORT = 0 # https://github.com/eventmachine/eventmachine/wiki/FAQ#wiki-can-i-start-a-server-on-a-random-available-port
10
+
11
+ attr_accessor :logger, :cache, :cache_request_headers, :whitelist, :path_blacklist, :ignore_params,
12
+ :persist_cache, :ignore_cache_port, :non_successful_cache_disabled, :non_successful_error_level,
13
+ :non_whitelisted_requests_disabled, :cache_path, :proxy_port, :proxied_request_inactivity_timeout,
14
+ :proxied_request_connect_timeout, :dynamic_jsonp, :dynamic_jsonp_keys,
15
+ :webserver_host, :webserver_port, :database_config_file, :environment
16
+
17
+ def initialize
18
+ @logger = defined?(Rails) ? Rails.logger : Logger.new(STDOUT)
19
+ reset
20
+ end
21
+
22
+ # Resets the configuration with the defaults
23
+ def reset
24
+ @cache = true
25
+ @cache_request_headers = false
26
+ @whitelist = DEFAULT_WHITELIST
27
+ @path_blacklist = []
28
+ @ignore_params = []
29
+ @persist_cache = false
30
+ @dynamic_jsonp = false
31
+ @dynamic_jsonp_keys = ['callback']
32
+ @ignore_cache_port = true
33
+ @non_successful_cache_disabled = false
34
+ @non_successful_error_level = :warn
35
+ @non_whitelisted_requests_disabled = false
36
+ @cache_path = File.join(Dir.tmpdir, 'agile-proxy')
37
+ @proxy_port = RANDOM_AVAILABLE_PORT
38
+ @proxied_request_inactivity_timeout = 10 # defaults from https://github.com/igrigorik/em-http-request/wiki/Redirects-and-Timeouts
39
+ @proxied_request_connect_timeout = 5
40
+ @webserver_port = 3020
41
+ @webserver_host = 'localhost'
42
+ @database_config_file = File.join(File.dirname(__FILE__), '..', '..', 'config.yml')
43
+ @environment = ENV['AGILE_PROXY_ENV']
44
+ end
45
+ end
46
+
47
+ # Configures the system using a block which has the global instance of this config yielded
48
+ def self.configure
49
+ yield config if block_given?
50
+ config
51
+ end
52
+
53
+ # Common log method - sends the log to the appropriate place
54
+ def self.log(*args)
55
+ config.logger.send(*args) unless config.logger.nil?
56
+ end
57
+
58
+ private
59
+
60
+ def self.config
61
+ @config ||= Config.new
62
+ end
63
+ end
@@ -0,0 +1,43 @@
1
+ require 'rack'
2
+ module AgileProxy
3
+ # A mixin that all handlers must include
4
+ module Handler
5
+ ##
6
+ #
7
+ # Handles an incoming rack request and returns a rack response.
8
+ #
9
+ # This method accepts rack request parameters and must return
10
+ # a rack response array containing [status, headers, content]
11
+ # , or [404, {}, ''] if the request cannot be fulfilled.
12
+ #
13
+ # @param _env [Hash] The rack environment
14
+ # @return [Array] An array of [status, headers, content]
15
+ # Returns status of 404 if the request cannot be fulfilled.
16
+ def call(_env)
17
+ [500, {}, 'The handler has not overridden the handle_request method!']
18
+ end
19
+
20
+ private
21
+
22
+ def username_password(env)
23
+ Base64.decode64(env['HTTP_PROXY_AUTHORIZATION'].sub(/^Basic /, '')).split(':') if proxy_auth? env
24
+ end
25
+
26
+ def proxy_auth?(env)
27
+ env.key?('HTTP_PROXY_AUTHORIZATION') && env['HTTP_PROXY_AUTHORIZATION'] =~ /^Basic /
28
+ end
29
+
30
+ def downcase_header_name(name)
31
+ name.split(/_/).drop(1).map { |word| word.downcase.capitalize }.join('-')
32
+ end
33
+
34
+ def downcased_headers(env)
35
+ headers = {}
36
+ env.each do |name, value|
37
+ next unless name =~ /^HTTP_/
38
+ headers[downcase_header_name(name)] = value
39
+ end
40
+ headers
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,110 @@
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
81
+ end
82
+
83
+ def disabled_request?(url)
84
+ return false unless AgileProxy.config.non_whitelisted_requests_disabled
85
+
86
+ uri = URI(url)
87
+ # In isolated environments, you may want to stop the request from happening
88
+ # or else you get "getaddrinfo: Name or service not known" errors
89
+ blacklisted_path?(uri.path) || !whitelisted_url?(uri)
90
+ end
91
+
92
+ def allowed_response_code?(status)
93
+ successful_status?(status)
94
+ end
95
+
96
+ def whitelisted_url?(url)
97
+ !AgileProxy.config.whitelist.index do |v|
98
+ v =~ /^#{url.host}(?::#{url.port})?$/
99
+ end.nil?
100
+ end
101
+
102
+ def blacklisted_path?(path)
103
+ !AgileProxy.config.path_blacklist.index { |bl| path.include?(bl) }.nil?
104
+ end
105
+
106
+ def successful_status?(status)
107
+ (200..299).include?(status) || status == 304
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,57 @@
1
+ require 'agile_proxy/model/application'
2
+ require 'agile_proxy/model/recording'
3
+ require 'rack'
4
+ module AgileProxy
5
+ #
6
+ # =The Central Request Handler
7
+ #
8
+ # As a request is made from the client to the server via the proxy server, it comes through an instance of this class.
9
+ #
10
+ # This class will then pass the request on to the StubHandler, then finally the ProxyHandler
11
+ class RequestHandler
12
+ extend Forwardable
13
+ include Handler
14
+
15
+ def_delegators :stub_handler, :stub
16
+
17
+ # A rack endpoint
18
+ #
19
+ # This method is called as a rack endpoint and returns a rack response.
20
+ #
21
+ # @param env [Hash] The 'rack' environment
22
+ # @return [Array] The rack response (status, headers, content)
23
+ def call(env)
24
+ request = ActionDispatch::Request.new(env)
25
+ username, password = username_password env
26
+ application = Application.where(username: username, password: password).first
27
+ body = request.body.read
28
+ request.body.rewind
29
+ rack_response = rack_app.call(env)
30
+ if rack_response[0] == 404
31
+ rack_response = [
32
+ 500,
33
+ {},
34
+ "Connection to #{request.url}#{body} not stubbed and new http connections are disabled"
35
+ ]
36
+ end
37
+ if application.record_requests
38
+ application.recordings.create request_headers: request.headers,
39
+ request_body: body,
40
+ request_url: request.url,
41
+ request_method: request.request_method,
42
+ response_headers: rack_response[1],
43
+ response_body: rack_response[2],
44
+ response_status: rack_response[0]
45
+ end
46
+ rack_response
47
+ end
48
+
49
+ private
50
+
51
+ def rack_app
52
+ @__app ||= Rack::Builder.new do
53
+ run Rack::Cascade.new([StubHandler.new, ProxyHandler.new])
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,113 @@
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 'rack/parser'
6
+ require 'action_dispatch'
7
+ require 'base64'
8
+ module AgileProxy
9
+ # = The stub handler
10
+ #
11
+ # This class is resonsible for matching incoming requests
12
+ # with a stub (request spec) and if one exists, respond with it
13
+ # rather than the real response from the destination server.
14
+ class StubHandler
15
+ include Handler
16
+
17
+ # Called by 'rack' as an endpoint.
18
+ #
19
+ # Fetches a 'short list' of potential matching request specs and builds a routing table to allow
20
+ # a router to do the hard work of parsing parameters, matching them, matching URL's etc...
21
+ #
22
+ # @param env [Hash] The rack environment
23
+ # @return [Array] The rack response [status, headers, body]
24
+ def call(env)
25
+ request = ActionDispatch::Request.new(env)
26
+ username, password = username_password env
27
+ @route_set = ActionDispatch::Routing::RouteSet.new
28
+
29
+ my_short_list = short_list(username, password, request.request_method, request.url).all.to_a
30
+ headers = downcased_headers(env)
31
+ body = request.body.read
32
+ request.body.rewind
33
+ setup_router my_short_list, request, headers, body
34
+ rack_app.call(env)
35
+ end
36
+
37
+ private
38
+
39
+ def setup_router(my_short_list, request, headers, body)
40
+ me = self
41
+ @route_set.draw do
42
+ route_set_instance = self
43
+ me.instance_eval do
44
+ my_short_list.each(&add_to_router(request, headers, body, route_set_instance))
45
+ end
46
+ end
47
+ end
48
+
49
+ def add_to_router(request, headers, body, route_set)
50
+ proc do |spec|
51
+ path = URI.parse(spec.url).path
52
+ path = '/' if path == ''
53
+ method = spec.http_method.downcase.to_sym
54
+ method = :get if method == :head
55
+ route_spec = {
56
+ path => proc do |router_env|
57
+ AgileProxy.log(:info, "agile-proxy: STUB #{method} for '#{request.url}'")
58
+ spec.call(router_env['action_dispatch.request.parameters'], headers, body)
59
+ end
60
+ }
61
+ route_spec[:constraints] = ActiveSupport::JSON.decode(spec.conditions).symbolize_keys unless spec.conditions.empty?
62
+ route_set.send method, route_spec
63
+ end
64
+ end
65
+
66
+ def collection(username, password)
67
+ Application.where(username: username, password: password).first.request_specs
68
+ end
69
+
70
+ def short_list(username, password, _method, url)
71
+ parsed_url = URI.parse(url)
72
+ parsed_url.path = ''
73
+ parsed_url.query = nil
74
+ parsed_url.fragment = nil
75
+ # @TODO This is being lazy - the ruby code will do all the finding work.
76
+ # This has to change as we go towards mutli user and larger data sets
77
+ collection(username, password).where('url LIKE ?', "#{parsed_url}%")
78
+ end
79
+
80
+ def rack_app
81
+ route_set = @route_set
82
+ Rack::Builder.new do
83
+ use ActionDispatch::ParamsParser
84
+ use MergePostAndGetParams
85
+ run route_set
86
+ end
87
+ end
88
+ end
89
+ #
90
+ # @private
91
+ # A rack middleware to merge post and get parameters ready for routing
92
+ class MergePostAndGetParams
93
+ def initialize(app)
94
+ @app = app
95
+ end
96
+
97
+ def call(env)
98
+ request = ActionDispatch::Request.new(env)
99
+ env['action_dispatch.request.parameters'].merge!(
100
+ env['action_dispatch.request.request_parameters']
101
+ ) unless request.content_length.zero?
102
+ @app.call(env)
103
+ end
104
+ end
105
+ end
106
+ module ActionDispatch
107
+ module Routing
108
+ # Extension to action dispatch to ensure all request parameters are collected not just GET
109
+ class RouteSet
110
+ PARAMETERS_KEY = 'action_dispatch.request.parameters'
111
+ end
112
+ end
113
+ 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
17
+ has_many :recordings
18
+ validates_uniqueness_of :password, scope: :username
19
+ end
20
+ end