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,52 @@
1
+ module AgileProxy
2
+ module Api
3
+ #
4
+ # = A grape API for recordings
5
+ #
6
+ # If the application is set to allow recordings, each HTTP request and response passing
7
+ # through the proxy server will be recorded.
8
+ #
9
+ # This API allows access to those recordings via REST
10
+ #
11
+ class Recordings < Grape::API
12
+ include Grape::Kaminari
13
+ helpers do
14
+ # Convenient access to the record specified in the id parameter
15
+ def record
16
+ current_application.recordings.where(id: params[:id]).first
17
+ end
18
+
19
+ def default_json_spec
20
+ {}
21
+ end
22
+ end
23
+
24
+ resource :recordings do
25
+ desc 'List all recordings made for the application'
26
+ paginate per_page: 20, max_per_page: 200
27
+ get do
28
+ authenticate!
29
+ scope = current_application.recordings
30
+ { recordings: paginate(scope).as_json(default_json_spec), total: scope.count }
31
+ end
32
+ desc 'Delete all rcordings for the application'
33
+ delete do
34
+ authenticate!
35
+ scope = current_application.recordings
36
+ scope.destroy_all
37
+ { recordings: [], total: 0 }
38
+ end
39
+ desc 'Get a recording by id'
40
+ get ':id' do
41
+ authenticate!
42
+ record.as_json(default_json_spec)
43
+ end
44
+ delete ':id' do
45
+ authenticate!
46
+ record.tap(&:destroy).as_json(default_json_spec)
47
+ end
48
+
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,52 @@
1
+ module AgileProxy
2
+ module Api
3
+ #
4
+ # = A grape API for recordings
5
+ #
6
+ # If the application is set to allow recordings, each HTTP request and response passing
7
+ # through the proxy server will be recorded.
8
+ #
9
+ # This API allows access to those recordings via REST
10
+ #
11
+ class RequestSpecRecordings < Grape::API
12
+ include Grape::Kaminari
13
+ helpers do
14
+ # Convenient access to the record specified in the id parameter
15
+ def record
16
+ current_application.recordings.where(request_spec_id: params[:request_spec_id], id: params[:id]).first
17
+ end
18
+
19
+ def default_json_spec
20
+ {}
21
+ end
22
+ end
23
+
24
+ resource :recordings do
25
+ desc 'List all recordings made for the request spec'
26
+ paginate per_page: 20, max_per_page: 200
27
+ get do
28
+ authenticate!
29
+ scope = current_application.recordings.where({request_spec_id: params[:request_spec_id]})
30
+ { recordings: paginate(scope).as_json(default_json_spec), total: scope.count }
31
+ end
32
+ desc 'Delete all recordings for the application'
33
+ delete do
34
+ authenticate!
35
+ scope = current_application.recordings
36
+ scope.destroy_all request_spec_id: params[:request_spec_id]
37
+ { recordings: [], total: 0 }
38
+ end
39
+ desc 'Get a recording by id'
40
+ get ':id' do
41
+ authenticate!
42
+ record.as_json(default_json_spec)
43
+ end
44
+ delete ':id' do
45
+ authenticate!
46
+ record.tap(&:destroy).as_json(default_json_spec)
47
+ end
48
+
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,86 @@
1
+ require 'agile_proxy/model/request_spec'
2
+ require 'grape-kaminari'
3
+ module AgileProxy
4
+ module Api
5
+ #
6
+ # = A grape API for request specifications
7
+ #
8
+ # A 'request specification' is what is known as a 'Stub' in the UI.
9
+ #
10
+ # It defines an input and output spec for a HTTP(s) request.
11
+ #
12
+ # For example, we could say
13
+ # 'When http://www.mybing.com/search.html is requested with some specific query parameters, then respond with this'
14
+ #
15
+ # This API allows full CRUD access to these request specifications, but only those belonging to the logged in user.
16
+ #
17
+ class RequestSpecs < Grape::API
18
+ include Grape::Kaminari
19
+ helpers do
20
+ # We only allow selected parameters through - spec and note
21
+ def permitted_params
22
+ @permitted_params ||= declared(
23
+ params,
24
+ { include_missing: false },
25
+ [:spec, :note, :response, :http_method, :url, :url_type, :conditions, :record_requests]
26
+ )
27
+ end
28
+
29
+ # Convenient access to the record specified in the id parameter
30
+ def record
31
+ current_application.request_specs.where(id: params[:id]).first
32
+ end
33
+
34
+ # Convenient access to the record parameters from a POST or a PUT, only permitted will be returned
35
+ # Note that for some reason, to do with rack or grape,
36
+ # when we send a large body, the request_spec does not come through
37
+ # so, to work around this, we inject the request spec in afterwards if it is missing.
38
+ def record_params
39
+ p = permitted_params.with_indifferent_access
40
+ p.merge!(user_id: current_user.id, application_id: current_application.id)
41
+ p[:response_attributes] = p.delete(:response) if p.key?(:response)
42
+ p
43
+ end
44
+
45
+ def default_json_spec
46
+ { include: { response: { except: [:created_at, :updated_at] } } }
47
+ end
48
+
49
+ end
50
+ resource :request_specs do
51
+ desc 'List all request specifications for the application'
52
+ paginate per_page: 50, max_per_page: 200
53
+ get do
54
+ authenticate!
55
+ scope = current_application.request_specs
56
+ { request_specs: paginate(scope).as_json(default_json_spec), total: scope.count }
57
+ end
58
+ delete do
59
+ authenticate!
60
+ scope = current_application.request_specs
61
+ scope.destroy_all
62
+ { request_specs: [], total: 0 }
63
+ end
64
+ desc 'Create a new request specification'
65
+ post do
66
+ authenticate!
67
+ current_application.request_specs.create(record_params).as_json(default_json_spec)
68
+ end
69
+ get ':id' do
70
+ authenticate!
71
+ record.as_json(default_json_spec)
72
+ end
73
+ desc 'Update a request specification'
74
+ put ':id' do
75
+ authenticate!
76
+ record.tap { |r| r.update_attributes(record_params) }.as_json(default_json_spec)
77
+ end
78
+ delete ':id' do
79
+ authenticate!
80
+ record.tap(&:destroy).as_json(default_json_spec)
81
+ end
82
+
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,45 @@
1
+ require_relative 'request_specs'
2
+ require_relative 'request_spec_recordings'
3
+ require_relative 'applications'
4
+ require_relative 'recordings'
5
+ module AgileProxy
6
+ module Api
7
+ #
8
+ # = The API Root
9
+ #
10
+ # This is the root of the entire API for use with REST. It will not be accessed directly
11
+ #
12
+ class Root < Grape::API
13
+ version 'v1', vendor: 'agile-proxy'
14
+ format :json
15
+ helpers do
16
+ # Provides easy access to the current user.
17
+ # As we are currently only single user, then we just return the first user
18
+ def current_user
19
+ ::AgileProxy::User.first
20
+ end
21
+ # Secured methods must call this first
22
+ def authenticate!
23
+ # Do nothing yet
24
+ end
25
+ # Provides easy access to the current application whether
26
+ # specified in the URL or not (defaults to the first if not)
27
+ def current_application
28
+ fail 'Application ID is missing' unless params.key?(:application_id)
29
+ applications = current_user.applications
30
+ applications.where(id: params[:application_id]).first
31
+ end
32
+ end
33
+ namespace 'users/:user_id' do
34
+ mount Api::Applications
35
+ namespace '/applications/:application_id' do
36
+ mount Api::Recordings
37
+ mount Api::RequestSpecs
38
+ namespace '/request_specs/:request_spec_id' do
39
+ mount Api::RequestSpecRecordings
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,116 @@
1
+ require 'agile_proxy'
2
+ require 'thor'
3
+ require 'active_record'
4
+ require_relative '../../db/seed'
5
+
6
+ module AgileProxy
7
+ include ActiveRecord::Tasks
8
+ class Cli < Thor
9
+ class << self
10
+ def data_dir_base
11
+ if RUBY_PLATFORM =~ /win32/
12
+ ENV['APPDATA']
13
+ elsif RUBY_PLATFORM =~ /linux/
14
+ ENV['HOME']
15
+ elsif RUBY_PLATFORM =~ /darwin/
16
+ ENV['HOME']
17
+ elsif RUBY_PLATFORM =~ /freebsd/
18
+ ENV['HOME']
19
+ else
20
+ ENV['HOME']
21
+ end
22
+ end
23
+
24
+ def data_dir
25
+ if Dir.pwd == File.expand_path('../..', File.dirname(__FILE__))
26
+ Dir.pwd
27
+ else
28
+ File.join data_dir_base, '.agile_proxy'
29
+ end
30
+ end
31
+
32
+ def environment
33
+ ENV['AGILE_PROXY_ENV'] || (Dir.pwd == File.expand_path('../..', File.dirname(__FILE__)) ? 'development' : 'production')
34
+ end
35
+ end
36
+ package_name 'Http Flexible Proxy'
37
+ desc 'start PROXY_PORT WEBSERVER_PORT', 'Runs the agile proxy'
38
+ method_options data_dir: data_dir, database_config_file: 'db.yml', env: environment
39
+ def start(proxy_port = nil, server_port = nil, webserver_port = nil)
40
+ ensure_database_config_file_exists database_config_file(options)
41
+ puts "Data dir is #{options.data_dir}, environment is #{options.env}"
42
+ setup_for_migrations(options)
43
+ ::AgileProxy.configure do |config|
44
+ config.proxy_port = proxy_port unless proxy_port.nil?
45
+ config.server_port = server_port unless server_port.nil?
46
+ config.webserver_port = webserver_port unless webserver_port.nil?
47
+ config.environment = options.env
48
+ config.database_config_file = database_config_file(options)
49
+ end
50
+ server = AgileProxy::Server.new
51
+ update_db
52
+ server.start
53
+ end
54
+
55
+ private
56
+
57
+ def setup_for_migrations(options)
58
+ ActiveRecord::Tasks::DatabaseTasks.db_dir = options.data_dir
59
+ ActiveRecord::Tasks::DatabaseTasks.migrations_paths = [File.expand_path('../../db/migrations', File.dirname(__FILE__))]
60
+ ActiveRecord::Tasks::DatabaseTasks.env = options.env
61
+ ActiveRecord::Tasks::DatabaseTasks.root = File.expand_path('../..', __FILE__)
62
+ ActiveRecord::Migrator.migrations_paths = ActiveRecord::Tasks::DatabaseTasks.migrations_paths
63
+ end
64
+ def run_migrations
65
+ ActiveRecord::Migration.verbose = true
66
+ ActiveRecord::Migrator.migrate(ActiveRecord::Migrator.migrations_paths, nil) do |migration|
67
+ ENV["SCOPE"].blank? || (ENV["SCOPE"] == migration.scope)
68
+ end
69
+ end
70
+
71
+ def seed_database
72
+ Seed.load_seed
73
+ end
74
+
75
+ def server
76
+ AgileProxy::Server.new
77
+ end
78
+
79
+ def update_db
80
+ ActiveRecord::Tasks::DatabaseTasks.create_current
81
+ run_migrations
82
+ seed_database
83
+ # Rake::Task['db:create'].invoke
84
+ # Rake::Task['db:migrate'].invoke
85
+
86
+
87
+ end
88
+
89
+ def ensure_database_config_file_exists(fn)
90
+ return if File.exist? fn
91
+ FileUtils.mkdir_p File.dirname fn
92
+ db = {
93
+ :development => {
94
+ adapter: 'sqlite3',
95
+ database: File.join(File.dirname(fn), 'db', 'development.db')
96
+ },
97
+ :test => {
98
+ adapter: 'sqlite3',
99
+ database: File.join(File.dirname(fn), 'db', 'test.db')
100
+ },
101
+ :production => {
102
+ adapter: 'sqlite3',
103
+ database: File.join(File.dirname(fn), 'db', 'production.db')
104
+ }
105
+ }
106
+ File.open(fn, 'w') {|f| f.write(db.to_yaml) }
107
+ end
108
+ def database_config_file(options)
109
+ if File.exist? options.database_config_file
110
+ options.database_config_file
111
+ else
112
+ File.join(options.data_dir, options.database_config_file)
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,66 @@
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, :server_host, :server_port, :database_config_file, :environment, :enable_cache
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
+ @server_port = 3030
43
+ @server_host = 'localhost'
44
+ @database_config_file = File.join(File.dirname(__FILE__), '..', '..', 'config.yml')
45
+ @environment = ENV['AGILE_PROXY_ENV']
46
+ @enable_cache = false
47
+ end
48
+ end
49
+
50
+ # Configures the system using a block which has the global instance of this config yielded
51
+ def self.configure
52
+ yield config if block_given?
53
+ config
54
+ end
55
+
56
+ # Common log method - sends the log to the appropriate place
57
+ def self.log(*args)
58
+ config.logger.send(*args) unless config.logger.nil?
59
+ end
60
+
61
+ private
62
+
63
+ def self.config
64
+ @config ||= Config.new
65
+ end
66
+ 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