proxy_rb 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/.env +3 -0
  3. data/.gitignore +1 -0
  4. data/.rubocop.yml +1 -0
  5. data/.travis.yml +8 -2
  6. data/Gemfile +7 -3
  7. data/README.md +42 -8
  8. data/Rakefile +8 -23
  9. data/bin/bootstrap +10 -0
  10. data/{script → bin}/console +1 -0
  11. data/bin/test +3 -0
  12. data/cucumber.yml +2 -0
  13. data/fixtures/proxy-config/.rspec +2 -0
  14. data/fixtures/proxy-config/README.md +18 -0
  15. data/fixtures/proxy-config/Rakefile +10 -0
  16. data/fixtures/proxy-config/bin/http_server +14 -0
  17. data/fixtures/proxy-config/spec/spec_helper.rb +8 -0
  18. data/fixtures/proxy-config/spec/support/aruba.rb +2 -0
  19. data/fixtures/proxy-config/spec/support/proxy_rb.rb +2 -0
  20. data/lib/proxy_rb/basic_configuration/in_config_wrapper.rb +28 -0
  21. data/lib/proxy_rb/basic_configuration/option.rb +32 -0
  22. data/lib/proxy_rb/basic_configuration.rb +148 -0
  23. data/lib/proxy_rb/configuration.rb +42 -0
  24. data/lib/proxy_rb/credentials.rb +52 -0
  25. data/lib/proxy_rb/errors.rb +5 -0
  26. data/lib/proxy_rb/http_downloader.rb +23 -0
  27. data/lib/proxy_rb/http_proxy.rb +52 -0
  28. data/lib/proxy_rb/main.rb +8 -2
  29. data/lib/proxy_rb/no_proxy.rb +26 -0
  30. data/lib/proxy_rb/password_fetchers/basic_password_fetcher.rb +12 -0
  31. data/lib/proxy_rb/password_fetchers/environment_password_fetcher.rb +34 -0
  32. data/lib/proxy_rb/password_fetchers/vault_password_fetcher.rb +47 -0
  33. data/lib/proxy_rb/password_fetchers.rb +6 -0
  34. data/lib/proxy_rb/proxy_url.rb +73 -0
  35. data/lib/proxy_rb/proxy_url_parser.rb +56 -0
  36. data/lib/proxy_rb/request.rb +36 -0
  37. data/lib/proxy_rb/resource.rb +28 -0
  38. data/lib/proxy_rb/response.rb +20 -0
  39. data/lib/proxy_rb/rspec/helpers/http_proxy.rb +78 -0
  40. data/lib/proxy_rb/rspec/helpers/passwords.rb +29 -0
  41. data/lib/proxy_rb/rspec.rb +9 -6
  42. data/lib/proxy_rb/user_passwords/environment_user_password.rb +30 -0
  43. data/lib/proxy_rb/user_passwords/vault_user_password.rb +27 -0
  44. data/lib/proxy_rb/user_passwords.rb +7 -0
  45. data/lib/proxy_rb/version.rb +2 -1
  46. data/lib/proxy_rb.rb +2 -0
  47. data/proxy_rb.gemspec +5 -3
  48. metadata +40 -9
  49. data/Gemfile.lock +0 -263
  50. data/script/bootstrap +0 -7
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+ require 'shellwords'
3
+
4
+ require 'proxy_rb/credentials'
5
+ require 'proxy_rb/proxy_url_parser'
6
+
7
+ # ProxyRb
8
+ module ProxyRb
9
+ # Represent proxy
10
+ class HttpProxy
11
+ protected
12
+
13
+ attr_reader :url, :credentials
14
+
15
+ public
16
+
17
+ def initialize(parser)
18
+ @url = parser.proxy_url
19
+ @credentials = parser.credentials
20
+ end
21
+
22
+ # Convert to parameters for PhantomJS
23
+ #
24
+ # @return [Array]
25
+ # An array of parameters for PhantomJS
26
+ def to_phantom_js
27
+ result = []
28
+ result << "--proxy=#{url}" unless url.empty?
29
+ result << "--proxy-auth=#{credentials}" unless credentials.empty?
30
+
31
+ result
32
+ end
33
+
34
+ # Convert to symbol to reference the proxy
35
+ #
36
+ # @return [Symbol]
37
+ # <host>_<port>_<credentials>
38
+ def to_sym
39
+ Shellwords.escape(*[host, port, credentials.user_name].compact.join('_')).to_sym
40
+ end
41
+
42
+ private
43
+
44
+ def host
45
+ url.host
46
+ end
47
+
48
+ def port
49
+ url.port
50
+ end
51
+ end
52
+ end
data/lib/proxy_rb/main.rb CHANGED
@@ -1,14 +1,20 @@
1
+ # frozen_string_literal: true
2
+ require 'logger'
3
+
1
4
  # Main
2
5
  module ProxyRb
3
6
  @debug_mode = false
7
+ @logger = Logger.new($stderr)
4
8
 
5
9
  class << self
6
- private
10
+ protected
7
11
 
8
12
  attr_accessor :debug_mode
9
13
 
10
14
  public
11
15
 
16
+ attr_reader :logger
17
+
12
18
  def debug_mode_enabled?
13
19
  debug_mode == true
14
20
  end
@@ -18,7 +24,7 @@ module ProxyRb
18
24
  %w(pry byebug).each { |l| require l }
19
25
  end
20
26
 
21
- def require_file_matching_pattern(pattern)
27
+ def require_files_matching_pattern(pattern)
22
28
  root = File.expand_path('../', __FILE__)
23
29
  path = File.join(root, pattern)
24
30
  Dir.glob(path).each { |f| require_relative f }
@@ -0,0 +1,26 @@
1
+ # No proxy
2
+ class NoProxy
3
+ def self.to_s
4
+ ""
5
+ end
6
+
7
+ def self.nil?
8
+ true
9
+ end
10
+
11
+ def self.empty?
12
+ true
13
+ end
14
+
15
+ def nil?
16
+ self.class.nil?
17
+ end
18
+
19
+ def empty?
20
+ self.class.empty?
21
+ end
22
+
23
+ def to_s
24
+ ""
25
+ end
26
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+ module ProxyRb
3
+ # Fetch password for user...
4
+ module PasswordFetchers
5
+ # ... process environment
6
+ class BasicPasswordFetcher
7
+ def call(_user_name)
8
+ raise NotImplementedError, 'You need to implement `#call` for a valid password fetcher'
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+ require 'proxy_rb/password_fetchers/basic_password_fetcher'
3
+ require 'proxy_rb/user_passwords/environment_user_password'
4
+
5
+ module ProxyRb
6
+ # Fetch password for user...
7
+ module PasswordFetchers
8
+ # ... process environment
9
+ class EnvironmentPasswordFetcher < BasicPasswordFetcher
10
+ include Contracts::Core
11
+ include Contracts::Builtin
12
+
13
+ protected
14
+
15
+ attr_reader :prefix
16
+
17
+ public
18
+
19
+ # Prefix of key in Environment
20
+ def initialize(prefix:)
21
+ @prefix = prefix
22
+ end
23
+
24
+ # @param [String] user_name
25
+ # Look up user name
26
+ Contract String => String
27
+ def call(user_name)
28
+ UserPasswords::EnvironmentUserPassword.new(
29
+ ENV[format('%s_%s', prefix, user_name.upcase)]
30
+ ).to_s
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+ require 'proxy_rb/password_fetchers/basic_password_fetcher'
3
+ require 'proxy_rb/user_passwords/vault_user_password'
4
+
5
+ begin
6
+ require 'vault'
7
+ rescue LoadError
8
+ ProxyRb.logger.error 'Please add `vault` to your `Gemfile` to use this password fetcher'
9
+ end
10
+
11
+ module ProxyRb
12
+ # Fetch password for user...
13
+ module PasswordFetchers
14
+ # ... from HashiCorp Vault
15
+ class VaultPasswordFetcher < BasicPasswordFetcher
16
+ include Contracts::Core
17
+ include Contracts::Builtin
18
+
19
+ protected
20
+
21
+ attr_reader :prefix, :client
22
+
23
+ public
24
+
25
+ # @param [String] prefix
26
+ # Prefix used to look up password for user name
27
+ #
28
+ # @param [Vault::Client] client
29
+ # The client used to connect to central "Vault" server
30
+ def initialize(prefix:, client: ::Vault::Client.new(address: ENV['VAULT_ADDR']))
31
+ @prefix = prefix
32
+ @client = client
33
+ end
34
+
35
+ # @param [String] user_name
36
+ # Look up user name
37
+ Contract String => String
38
+ def call(user_name)
39
+ client.with_retries(::Vault::HTTPConnectionError, ::Vault::HTTPError) do |_attempt, _e|
40
+ UserPasswords::VaultUserPassword.new(
41
+ ::Vault.logical.read(File.join(prefix, user_name))
42
+ ).to_s
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+ module ProxyRb
3
+ # Fetch password for user...
4
+ module PasswordFetcher
5
+ end
6
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+ require 'addressable/uri'
3
+
4
+ # ProxyRb
5
+ module ProxyRb
6
+ # A ProxyURL
7
+ class ProxyUrl
8
+ # Build a URL from hash
9
+ #
10
+ # @param [Hash] hash
11
+ # An Hash representing the url parts
12
+ #
13
+ # @return [ProxyUrl]
14
+ # The built url
15
+ def self.build(hash)
16
+ new(Addressable::URI.new(hash))
17
+ end
18
+
19
+ # Create URL from string
20
+ #
21
+ # @param [String] string
22
+ # The url
23
+ #
24
+ # @return [ProxyUrl]
25
+ # The parsed url
26
+ def self.parse(string)
27
+ new(Addressable::URI.heuristic_parse(string.to_s))
28
+ end
29
+
30
+ protected
31
+
32
+ attr_reader :url
33
+
34
+ public
35
+
36
+ def initialize(url)
37
+ @url = url
38
+ end
39
+
40
+ %i(host user password port).each do |m|
41
+ define_method m do
42
+ return nil if empty?
43
+
44
+ url.public_send m
45
+ end
46
+ end
47
+
48
+ def to_s
49
+ return "" if empty?
50
+
51
+ url.to_s
52
+ end
53
+
54
+ # Check if url is empty
55
+ def empty?
56
+ url.nil? || url.empty?
57
+ end
58
+
59
+ # Return URL without user name and password
60
+ #
61
+ # @return [ProxyUrl]
62
+ # The cleaned url
63
+ def without_user_name_and_password
64
+ return self.class.new(nil) if empty?
65
+
66
+ h = url.to_hash
67
+ h.delete :user
68
+ h.delete :password
69
+
70
+ self.class.build(h)
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+ require 'uri'
3
+ require 'addressable/uri'
4
+ require 'proxy_rb/proxy_url'
5
+
6
+ # ProxyRb
7
+ module ProxyRb
8
+ # Parse urls for proxies
9
+ class ProxyUrlParser
10
+ protected
11
+
12
+ attr_reader :raw_url
13
+
14
+ public
15
+
16
+ attr_reader :proxy_url, :credentials
17
+
18
+ # @param [String] url
19
+ # The url for the proxy configuration
20
+ def initialize(url)
21
+ temporary_url = ProxyUrl.parse(url)
22
+
23
+ @proxy_url = temporary_url.without_user_name_and_password
24
+ @credentials = Credentials.new(temporary_url.user, temporary_url.password)
25
+ end
26
+
27
+ private
28
+
29
+ def url_hash_for_hostname_port
30
+ splitted_url = raw_url.to_s.split(/:/)
31
+
32
+ url_hash = {}
33
+ url_hash[:host] = splitted_url.first
34
+ url_hash[:path] = '/'
35
+ url_hash[:port] = splitted_url.last unless splitted_url.first == splitted_url.last
36
+
37
+ url_hash
38
+ end
39
+
40
+ def url_hash_for_url
41
+ Addressable::URI.parse(raw_url).to_hash
42
+ end
43
+
44
+ def url?(u)
45
+ url = Addressable::URI.parse(u)
46
+
47
+ return false if url.nil?
48
+ return false unless /\A[[:alnum:]]+\Z/ === url.scheme
49
+ return true if url && url.host
50
+
51
+ false
52
+ rescue Addressable::URI::InvalidURIError
53
+ false
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+ # ProxyRb
3
+ module ProxyRb
4
+ # An HTTP request
5
+ class Request
6
+ protected
7
+
8
+ attr_reader :page
9
+
10
+ public
11
+
12
+ def initialize(page)
13
+ @page = page
14
+ end
15
+
16
+ # Was the request successful
17
+ #
18
+ # @return [TrueClass, FalseClass]
19
+ # The result
20
+ def successful?
21
+ page.status_code.to_s.start_with?('2', '3')
22
+ end
23
+
24
+ # def forbidden?
25
+ # page.status_code == 403
26
+ # end
27
+
28
+ # def invalid?
29
+ # page.status_code == 401
30
+ # end
31
+
32
+ # def redirected?
33
+ # page.status_code.to_s.start_with? '3'
34
+ # end
35
+ end
36
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+ require 'addressable/uri'
3
+
4
+ # ProxyRb
5
+ module ProxyRb
6
+ # A resource
7
+ class Resource
8
+ attr_accessor :content
9
+
10
+ private
11
+
12
+ attr_reader :url
13
+
14
+ public
15
+
16
+ def initialize(url)
17
+ @url = Addressable::URI.parse(url)
18
+ end
19
+
20
+ # Convert resource to url
21
+ #
22
+ # @return [String] url
23
+ # The url
24
+ def to_url
25
+ url.to_s
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+ # ProxyRb
3
+ module ProxyRb
4
+ # A response
5
+ class Response
6
+ protected
7
+
8
+ attr_reader :page
9
+
10
+ public
11
+
12
+ def initialize(page)
13
+ @page = page
14
+ end
15
+
16
+ # def forbidden?
17
+ # page.status_code == 403
18
+ # end
19
+ end
20
+ end
@@ -1,11 +1,89 @@
1
+ # frozen_string_literal: true
2
+ require 'capybara/poltergeist'
3
+
4
+ require 'proxy_rb'
5
+ require 'proxy_rb/resource'
6
+ require 'proxy_rb/http_downloader'
7
+ require 'proxy_rb/http_proxy'
8
+ require 'proxy_rb/request'
9
+ require 'proxy_rb/response'
10
+
1
11
  # Main Module
2
12
  module ProxyRb
3
13
  # Main Module
4
14
  module Rspec
5
15
  # Helpers
6
16
  module Helpers
17
+ # Non includable internal helper methods
18
+ #
19
+ # Not for use by normal users
20
+ module NonIncludes
21
+ class << self
22
+ def clear_environment
23
+ %w(
24
+ http_proxy
25
+ https_proxy
26
+ HTTP_PROXY
27
+ HTTPS_PROXY
28
+ ).each { |v| ENV.delete v }
29
+ end
30
+
31
+ def register_capybara_driver_for_proxy(proxy)
32
+ options = {
33
+ phantomjs_options: proxy.to_phantom_js,
34
+ js_errors: false,
35
+ phantomjs_logger: $stderr
36
+ }
37
+
38
+ ::Capybara.register_driver proxy.to_sym do |app|
39
+ ::Capybara::Poltergeist::Driver.new(app, options)
40
+ end
41
+
42
+ ::Capybara.run_server = false
43
+ ::Capybara.current_driver = proxy.to_sym
44
+ end
45
+ end
46
+ end
47
+
7
48
  # For http proxy
8
49
  module HttpProxy
50
+ include ::Capybara::DSL
51
+
52
+ # The proxy based on subject
53
+ def proxy
54
+ ProxyRb::HttpProxy.new(ProxyUrlParser.new(subject))
55
+ end
56
+
57
+ # Visit an URL
58
+ #
59
+ # @param [String] url
60
+ def visit(url)
61
+ resource = Resource.new(url)
62
+
63
+ NonIncludes.clear_environment
64
+ NonIncludes.register_capybara_driver_for_proxy(proxy)
65
+
66
+ begin
67
+ super(resource.to_url)
68
+ rescue ::Capybara::Poltergeist::TimeoutError
69
+ raise ProxyRb::UrlTimeoutError, "Failed to fetch #{resource.to_url}: Timeout occured."
70
+ end
71
+ end
72
+
73
+ # !@method download
74
+ #
75
+ # @see #visit
76
+ alias download visit
77
+
78
+ # Get access to the request made
79
+ def request
80
+ Request.new(page)
81
+ end
82
+
83
+ # Get access to the response get
84
+ def response
85
+ Response.new(page)
86
+ end
9
87
  end
10
88
  end
11
89
  end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+ require 'contracts'
3
+
4
+ # ProxyRb
5
+ module ProxyRb
6
+ # Rspec
7
+ module Rspec
8
+ # Helpers
9
+ module Helpers
10
+ # Helpers to access passwords
11
+ module Passwords
12
+ include Contracts::Core
13
+ include Contracts::Builtin
14
+
15
+ Contract Or[String, Symbol] => Maybe[String]
16
+ # Get password for user
17
+ #
18
+ # @param [String] user_name
19
+ # The user name
20
+ #
21
+ # @return [String]
22
+ # The password
23
+ def password(user_name)
24
+ ProxyRb.config.password_fetcher.call(user_name.to_s)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -1,13 +1,14 @@
1
+ # frozen_string_literal: true
1
2
  require 'rspec'
2
- require 'capybara/poltergeist'
3
3
  require 'capybara/rspec'
4
4
 
5
- require 'proxy_rb/rspec'
5
+ require 'proxy_rb'
6
+ require 'proxy_rb/no_proxy'
6
7
 
7
- ProxyRb.require_file_matching_pattern('rspec/helpers/*.rb')
8
- ProxyRb.require_file_matching_pattern('rspec/matchers/*.rb')
9
- ProxyRb.require_file_matching_pattern('rspec/shared_examples/*.rb')
10
- ProxyRb.require_file_matching_pattern('rspec/shared_contexts/*.rb')
8
+ ProxyRb.require_files_matching_pattern('rspec/helpers/*.rb')
9
+ ProxyRb.require_files_matching_pattern('rspec/matchers/*.rb')
10
+ ProxyRb.require_files_matching_pattern('rspec/shared_examples/*.rb')
11
+ ProxyRb.require_files_matching_pattern('rspec/shared_contexts/*.rb')
11
12
 
12
13
  # Main Module
13
14
  module ProxyRb
@@ -18,4 +19,6 @@ end
18
19
 
19
20
  RSpec.configure do |config|
20
21
  config.include ProxyRb::Rspec::Helpers::HttpProxy, type: :http_proxy
22
+ config.include ProxyRb::Rspec::Helpers::Passwords, type: :http_proxy
23
+ config.include Capybara::RSpecMatchers, type: :http_proxy
21
24
  end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+ # ProxyRb
3
+ module ProxyRb
4
+ # User passwords
5
+ module UserPasswords
6
+ # Be a password from environment
7
+ class EnvironmentUserPassword
8
+ include Contracts::Core
9
+ include Contracts::Builtin
10
+
11
+ protected
12
+
13
+ attr_reader :response
14
+
15
+ public
16
+
17
+ def initialize(response)
18
+ @response = response
19
+ end
20
+
21
+ # Return password
22
+ #
23
+ # @return [String]
24
+ Contract None => String
25
+ def to_s
26
+ response.to_s
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+ # ProxyRb
3
+ module ProxyRb
4
+ # User Passwords
5
+ module UserPasswords
6
+ # Be a password return from vault
7
+ class VaultUserPassword
8
+ include Contracts::Core
9
+ include Contracts::Builtin
10
+
11
+ protected
12
+
13
+ attr_reader :response
14
+
15
+ public
16
+
17
+ def initialize(response)
18
+ @response = response.data
19
+ end
20
+
21
+ Contract None => String
22
+ def to_s
23
+ response[:password].to_s
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+ # ProxyRb
3
+ module ProxyRb
4
+ # User Passwords
5
+ module UserPasswords
6
+ end
7
+ end
@@ -1,4 +1,5 @@
1
+ # frozen_string_literal: true
1
2
  # Main Module
2
3
  module ProxyRb
3
- VERSION = '0.1.0'
4
+ VERSION = '0.2.0'
4
5
  end
data/lib/proxy_rb.rb CHANGED
@@ -1,4 +1,6 @@
1
+ # frozen_string_literal: true
1
2
  require 'proxy_rb/version'
3
+ require 'proxy_rb/configuration'
2
4
  require 'proxy_rb/main'
3
5
 
4
6
  # Main module
data/proxy_rb.gemspec CHANGED
@@ -1,4 +1,5 @@
1
1
  # coding: utf-8
2
+ # frozen_string_literal: true
2
3
  lib = File.expand_path('../lib', __FILE__)
3
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
5
  require 'proxy_rb/version'
@@ -24,8 +25,9 @@ Gem::Specification.new do |spec|
24
25
  spec.add_runtime_dependency 'poltergeist'
25
26
 
26
27
  spec.add_runtime_dependency 'addressable'
27
- spec.add_runtime_dependency 'activesupport'
28
- spec.add_runtime_dependency 'uglifier'
28
+ spec.add_runtime_dependency 'contracts'
29
+ spec.add_runtime_dependency 'excon'
30
+ # spec.add_runtime_dependency 'phantomjs'
29
31
 
30
- spec.required_ruby_version = '~> 2.0'
32
+ spec.required_ruby_version = '~> 2.3'
31
33
  end