reach-ruby 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +7 -0
  2. data/.dockerignore +1 -0
  3. data/.gitignore +18 -0
  4. data/.rubocop.yml +58 -0
  5. data/.rubocop_todo.yml +193 -0
  6. data/AUTHORS.md +52 -0
  7. data/CHANGES.md +3 -0
  8. data/CODE_OF_CONDUCT.md +73 -0
  9. data/CONTRIBUTING.md +163 -0
  10. data/Dockerfile +9 -0
  11. data/Gemfile +3 -0
  12. data/ISSUE_TEMPLATE.md +30 -0
  13. data/LICENSE +21 -0
  14. data/Makefile +34 -0
  15. data/PULL_REQUEST_TEMPLATE.md +31 -0
  16. data/README.md +237 -0
  17. data/Rakefile +10 -0
  18. data/UPGRADE.md +5 -0
  19. data/VERSIONS.md +35 -0
  20. data/advanced-examples/custom-http-client.md +166 -0
  21. data/examples/examples.rb +23 -0
  22. data/githooks/pre-commit +1 -0
  23. data/lib/rack/reach_webhook_authentication.rb +72 -0
  24. data/lib/reach-ruby/framework/reach_response.rb +19 -0
  25. data/lib/reach-ruby/framework/request.rb +41 -0
  26. data/lib/reach-ruby/framework/response.rb +18 -0
  27. data/lib/reach-ruby/framework/rest/domain.rb +39 -0
  28. data/lib/reach-ruby/framework/rest/error.rb +51 -0
  29. data/lib/reach-ruby/framework/rest/helper.rb +11 -0
  30. data/lib/reach-ruby/framework/rest/page.rb +144 -0
  31. data/lib/reach-ruby/framework/rest/resource.rb +23 -0
  32. data/lib/reach-ruby/framework/rest/version.rb +240 -0
  33. data/lib/reach-ruby/framework/serialize.rb +81 -0
  34. data/lib/reach-ruby/framework/values.rb +9 -0
  35. data/lib/reach-ruby/http/http_client.rb +82 -0
  36. data/lib/reach-ruby/http.rb +5 -0
  37. data/lib/reach-ruby/rest/api/authentix/.openapi-generator/FILES +10 -0
  38. data/lib/reach-ruby/rest/api/authentix/.openapi-generator/VERSION +1 -0
  39. data/lib/reach-ruby/rest/api/authentix/.openapi-generator-ignore +23 -0
  40. data/lib/reach-ruby/rest/api/authentix/authentication_trial_item.rb +418 -0
  41. data/lib/reach-ruby/rest/api/authentix/authentication_trial_stat_item.rb +279 -0
  42. data/lib/reach-ruby/rest/api/authentix/configuration_item/authentication_control_item.rb +214 -0
  43. data/lib/reach-ruby/rest/api/authentix/configuration_item/authentication_item.rb +449 -0
  44. data/lib/reach-ruby/rest/api/authentix/configuration_item.rb +583 -0
  45. data/lib/reach-ruby/rest/api/authentix.rb +72 -0
  46. data/lib/reach-ruby/rest/api/messaging/.openapi-generator/FILES +2 -0
  47. data/lib/reach-ruby/rest/api/messaging/.openapi-generator/VERSION +1 -0
  48. data/lib/reach-ruby/rest/api/messaging/.openapi-generator-ignore +23 -0
  49. data/lib/reach-ruby/rest/api/messaging/messaging_item.rb +582 -0
  50. data/lib/reach-ruby/rest/api/messaging.rb +51 -0
  51. data/lib/reach-ruby/rest/api.rb +50 -0
  52. data/lib/reach-ruby/rest/client.rb +130 -0
  53. data/lib/reach-ruby/rest.rb +13 -0
  54. data/lib/reach-ruby/security/request_validator.rb +149 -0
  55. data/lib/reach-ruby/util/configuration.rb +25 -0
  56. data/lib/reach-ruby/version.rb +3 -0
  57. data/lib/reach-ruby.rb +44 -0
  58. data/reach-ruby.gemspec +38 -0
  59. data/sonar-project.properties +13 -0
  60. metadata +267 -0
@@ -0,0 +1,50 @@
1
+ ###
2
+ # This code was generated by
3
+ # ___ ___ _ ___ _ _ _____ _ _ _ ___ ___ _ ___ ___ ___ _ ___ ___ ___ _ _ ___ ___ _ _____ ___ ___
4
+ # | _ \ __| /_\ / __| || |__|_ _/_\ | | | |/ | \ / / | /_\ | _ ) __|___ / _ \ /_\ |_ _|__ / __| __| \| | __| _ \ /_\_ _/ _ \| _ \
5
+ # | / _| / _ \ (__| __ |___|| |/ _ \| |__| ' < \ V /| |__ / _ \| _ \__ \___| (_) / _ \ | |___| (_ | _|| .` | _|| / / _ \| || (_) | /
6
+ # |_|_\___/_/ \_\___|_||_| |_/_/ \_\____|_|\_\ |_| |____/_/ \_\___/___/ \___/_/ \_\___| \___|___|_|\_|___|_|_\/_/ \_\_| \___/|_|_\
7
+ #
8
+ # NOTE: This class is auto generated by OpenAPI Generator.
9
+ # https://openapi-generator.tech
10
+ # Do not edit the class manually.
11
+ #
12
+ # frozen_string_literal: true
13
+
14
+ module Reach
15
+ module REST
16
+ class Api < Domain
17
+ ##
18
+ # Initialize the Api Domain
19
+ def initialize(reach)
20
+ super
21
+
22
+ @base_url = 'https://api.reach.talkylabs.com'
23
+ @host = 'api.reach.talkylabs.com'
24
+ @port = 443
25
+
26
+ # Versions
27
+ @messaging = nil
28
+ @authentix = nil
29
+ end
30
+
31
+ ##
32
+ # Version Messaging
33
+ def messaging
34
+ @messaging ||= Messaging.new self
35
+ end
36
+
37
+ ##
38
+ # Version Authentix
39
+ def authentix
40
+ @authentix ||= Authentix.new self
41
+ end
42
+
43
+ ##
44
+ # Provide a user friendly representation
45
+ def to_s
46
+ '#<Reach::REST::Api>'
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,130 @@
1
+ ###
2
+ # This code was generated by
3
+ # ___ ___ _ ___ _ _ _____ _ _ _ ___ ___ _ ___ ___ ___ _ ___ ___ ___ _ _ ___ ___ _ _____ ___ ___
4
+ # | _ \ __| /_\ / __| || |__|_ _/_\ | | | |/ | \ / / | /_\ | _ ) __|___ / _ \ /_\ |_ _|__ / __| __| \| | __| _ \ /_\_ _/ _ \| _ \
5
+ # | / _| / _ \ (__| __ |___|| |/ _ \| |__| ' < \ V /| |__ / _ \| _ \__ \___| (_) / _ \ | |___| (_ | _|| .` | _|| / / _ \| || (_) | /
6
+ # |_|_\___/_/ \_\___|_||_| |_/_/ \_\____|_|\_\ |_| |____/_/ \_\___/___/ \___/_/ \_\___| \___|___|_|\_|___|_|_\/_/ \_\_| \___/|_|_\
7
+ #
8
+ # NOTE: This class is auto generated by OpenAPI Generator.
9
+ # https://openapi-generator.tech
10
+ # Do not edit the class manually.
11
+ #
12
+ # frozen_string_literal: true
13
+
14
+ module Reach
15
+ module REST
16
+ ##
17
+ # A client for accessing the Reach API.
18
+ class Client
19
+
20
+ attr_accessor :http_client, :username, :password, :auth_token, :logger, :user_agent_extensions
21
+
22
+ ##
23
+ # Initializes the Reach Client
24
+ def initialize(username=nil, password=nil, http_client=nil, logger=nil, user_agent_extensions=nil)
25
+ @username = username || Reach.username
26
+ @password = password || Reach.auth_token
27
+ @auth_token = @password
28
+ @auth = [@username, @password]
29
+ @http_client = http_client || Reach.http_client || Reach::HTTP::Client.new
30
+ @logger = logger || Reach.logger
31
+ @user_agent_extensions = user_agent_extensions || []
32
+
33
+ # Domains
34
+ @api = nil
35
+ end
36
+
37
+ ##
38
+ # Makes a request to the Reach API using the configured http client
39
+ # Authentication information is automatically added if none is provided
40
+ def request(host, port, method, uri, params={}, data={}, headers={}, auth=nil, timeout=nil)
41
+ auth ||= @auth
42
+
43
+ ruby_config = RbConfig::CONFIG
44
+ headers['User-Agent'] = "reach-ruby/#{Reach::VERSION} (#{ruby_config["host_os"]} #{ruby_config["host_cpu"]}) Ruby/#{RUBY_VERSION}"
45
+ headers['Accept-Charset'] = 'utf-8'
46
+
47
+ user_agent_extensions.each { |extension| headers['User-Agent'] += " #{extension}" }
48
+
49
+ if method == 'POST' && !headers['Content-Type']
50
+ headers['Content-Type'] = 'application/x-www-form-urlencoded'
51
+ end
52
+
53
+ unless headers['Accept']
54
+ headers['Accept'] = 'application/json'
55
+ end
56
+
57
+ uri = build_uri(uri)
58
+
59
+ if @logger
60
+ @logger.debug("--BEGIN REACH API Request--")
61
+ @logger.debug("Request Method: <#{method}>")
62
+
63
+ headers.each do |key, value|
64
+ unless key.downcase == 'authorization' || key.downcase == 'apikey' || key.downcase == 'apiuser'
65
+ @logger.debug("#{key}:#{value}")
66
+ end
67
+ end
68
+
69
+ url = URI(uri)
70
+ @logger.debug("Host:#{url.host}")
71
+ @logger.debug("Path:#{url.path}")
72
+ @logger.debug("Query:#{url.query}")
73
+ @logger.debug("Request Params:#{params}")
74
+ end
75
+
76
+ response = @http_client.request(
77
+ host,
78
+ port,
79
+ method,
80
+ uri,
81
+ params,
82
+ data,
83
+ headers,
84
+ auth,
85
+ timeout
86
+ )
87
+
88
+ if @logger
89
+ @logger.debug("Response Status Code:#{response.status_code}")
90
+ @logger.debug("Response Headers:#{response.headers}")
91
+ @logger.debug("--END REACH API REQUEST--")
92
+ end
93
+
94
+ response
95
+ end
96
+
97
+ ##
98
+ # Build the final request uri
99
+ def build_uri(uri)
100
+ parsed_url = URI(uri)
101
+ parsed_url.to_s
102
+ end
103
+
104
+ ##
105
+ # Access the Api Reach Domain
106
+ def api
107
+ @api ||= Api.new self
108
+ end
109
+
110
+ ##
111
+ # Acess the Messaging api version
112
+ def messaging
113
+ self.api.messaging
114
+ end
115
+
116
+ ##
117
+ # Acess the Authentix api version
118
+ def authentix
119
+ self.api.authentix
120
+ end
121
+
122
+ ##
123
+ # Provide a user friendly representation
124
+ def to_s
125
+ "#<Reach::REST::Client #{@username}>"
126
+ end
127
+ end
128
+
129
+ end
130
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ Dir[File.join(__dir__, 'framework/rest/*.rb')].sort.each do |file|
4
+ require file
5
+ end
6
+
7
+ Dir[File.join(__dir__, 'rest/*.rb')].sort.each do |file|
8
+ require file
9
+ end
10
+
11
+ Dir[File.join(__dir__, 'rest/**/*.rb')].sort.each do |file|
12
+ require file
13
+ end
@@ -0,0 +1,149 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Reach
4
+ module Security
5
+ class RequestValidator
6
+ ##
7
+ # Initialize a Request Validator. auth_token will either be grabbed from the global Reach object or you can
8
+ # pass it in here.
9
+ #
10
+ # @param [String] auth_token Your account auth token, used to sign requests
11
+ def initialize(auth_token = nil)
12
+ @auth_token = auth_token || Reach.auth_token
13
+ raise ArgumentError, 'Auth token is required' if @auth_token.nil?
14
+ end
15
+
16
+ ##
17
+ # Validates that after hashing a request with Reach's request-signing algorithm, the hash matches the signature parameter
18
+ #
19
+ # @param [String] url The url sent to your server, including any query parameters
20
+ # @param [String, Hash, #to_unsafe_h] params In most cases, this is the POST parameters as a hash. If you received
21
+ # a bodySHA256 parameter in the query string, this parameter can instead be the POST body as a string to
22
+ # validate JSON or other text-based payloads that aren't x-www-form-urlencoded.
23
+ # @param [String] signature The expected signature, from the X-Reach-Signature header of the request
24
+ #
25
+ # @return [Boolean] whether or not the computed signature matches the signature parameter
26
+ def validate(url, params, signature)
27
+ parsed_url = URI(url)
28
+ url_with_port = add_port(parsed_url)
29
+ url_without_port = remove_port(parsed_url)
30
+
31
+ valid_body = true # default succeed, since body not always provided
32
+ params_hash = body_or_hash(params)
33
+ unless params_hash.is_a? Enumerable
34
+ body_hash = URI.decode_www_form(parsed_url.query || '').to_h['bodySHA256']
35
+ params_hash = build_hash_for(params)
36
+ valid_body = !(params_hash.nil? || body_hash.nil?) && secure_compare(params_hash, body_hash)
37
+ params_hash = {}
38
+ end
39
+
40
+ # Check signature of the url with and without port numbers
41
+ # since signature generation on the back end is inconsistent
42
+ valid_signature_with_port = secure_compare(build_signature_for(url_with_port, params_hash), signature)
43
+ valid_signature_without_port = secure_compare(build_signature_for(url_without_port, params_hash), signature)
44
+
45
+ valid_body && (valid_signature_with_port || valid_signature_without_port)
46
+ end
47
+
48
+ ##
49
+ # Build a SHA256 hash for a body string
50
+ #
51
+ # @param [String] body String to hash
52
+ #
53
+ # @return [String] A hex-encoded SHA256 of the body string
54
+ def build_hash_for(body)
55
+ hasher = OpenSSL::Digest.new('sha256')
56
+ hasher.hexdigest(body)
57
+ end
58
+
59
+ ##
60
+ # Build a SHA1-HMAC signature for a url and parameter hash
61
+ #
62
+ # @param [String] url The request url, including any query parameters
63
+ # @param [#join] params The POST parameters
64
+ #
65
+ # @return [String] A base64 encoded SHA1-HMAC
66
+ def build_signature_for(url, params)
67
+ data = url + params.sort.join
68
+ digest = OpenSSL::Digest.new('sha1')
69
+ Base64.strict_encode64(OpenSSL::HMAC.digest(digest, @auth_token, data))
70
+ end
71
+
72
+ private
73
+
74
+ # Compares two strings in constant time to avoid timing attacks.
75
+ # Borrowed from ActiveSupport::MessageVerifier.
76
+ # https://github.com/rails/rails/blob/master/activesupport/lib/active_support/message_verifier.rb
77
+ def secure_compare(a, b)
78
+ return false unless a.bytesize == b.bytesize
79
+
80
+ l = a.unpack("C#{a.bytesize}")
81
+
82
+ res = 0
83
+ b.each_byte { |byte| res |= byte ^ l.shift }
84
+ res.zero?
85
+ end
86
+
87
+ # `ActionController::Parameters` no longer, as of Rails 5, inherits
88
+ # from `Hash` so the `sort` method, used above in `build_signature_for`
89
+ # is deprecated.
90
+ #
91
+ # `to_unsafe_h` was introduced in Rails 4.2.1, before then it is still
92
+ # possible to sort on an ActionController::Parameters object.
93
+ #
94
+ # We use `to_unsafe_h` as `to_h` returns a hash of the permitted
95
+ # parameters only and we need all the parameters to create the signature.
96
+ def body_or_hash(params_or_body)
97
+ if params_or_body.respond_to?(:to_unsafe_h)
98
+ params_or_body.to_unsafe_h
99
+ else
100
+ params_or_body
101
+ end
102
+ end
103
+
104
+ ##
105
+ # Adds the standard port to the url if it doesn't already have one
106
+ #
107
+ # @param [URI] parsed_url The parsed request url
108
+ #
109
+ # @return [String] The URL with a port number
110
+ def add_port(parsed_url)
111
+ if parsed_url.port.nil? || parsed_url.port == parsed_url.default_port
112
+ build_url_with_port_for(parsed_url)
113
+ else
114
+ parsed_url.to_s
115
+ end
116
+ end
117
+
118
+ ##
119
+ # Removes the port from the url
120
+ #
121
+ # @param [URI] parsed_url The parsed request url
122
+ #
123
+ # @return [String] The URL without a port number
124
+ def remove_port(parsed_url)
125
+ parsed_url.port = nil
126
+ parsed_url.to_s
127
+ end
128
+
129
+ ##
130
+ # Builds the url from its component pieces, with the standard port
131
+ #
132
+ # @param [URI] parsed_url The parsed request url
133
+ #
134
+ # @return [String] The URL with the standard port number
135
+ def build_url_with_port_for(parsed_url)
136
+ url = ''
137
+
138
+ url += parsed_url.scheme ? "#{parsed_url.scheme}://" : ''
139
+ url += parsed_url.userinfo ? "#{parsed_url.userinfo}@" : ''
140
+ url += parsed_url.host ? "#{parsed_url.host}:#{parsed_url.port}" : ''
141
+ url += parsed_url.path
142
+ url += parsed_url.query ? "?#{parsed_url.query}" : ''
143
+ url += parsed_url.fragment ? "##{parsed_url.fragment}" : ''
144
+
145
+ url
146
+ end
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Reach
4
+ module Util
5
+ class Configuration
6
+ attr_accessor :username, :auth_token, :http_client, :logger
7
+
8
+ def username=(value)
9
+ @username = value
10
+ end
11
+
12
+ def auth_token=(value)
13
+ @auth_token = value
14
+ end
15
+
16
+ def http_client=(value)
17
+ @http_client = value
18
+ end
19
+
20
+ def logger=(value)
21
+ @logger = value
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,3 @@
1
+ module Reach
2
+ VERSION = '1.0.0'
3
+ end
data/lib/reach-ruby.rb ADDED
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'net/http'
4
+ require 'net/https'
5
+ require 'cgi'
6
+ require 'openssl'
7
+ require 'base64'
8
+ require 'forwardable'
9
+ require 'time'
10
+ require 'json'
11
+
12
+ require 'reach-ruby/version' unless defined?(Reach::VERSION)
13
+ require 'rack/reach_webhook_authentication' if defined?(Rack) && defined?(Rack::MediaType)
14
+
15
+ require 'reach-ruby/security/request_validator'
16
+ require 'reach-ruby/util/configuration'
17
+
18
+ Dir[File.join(__dir__, 'reach-ruby/framework/*.rb')].sort.each do |file|
19
+ require file
20
+ end
21
+
22
+ module Reach
23
+ extend SingleForwardable
24
+
25
+ autoload :HTTP, File.join(__dir__, 'reach-ruby', 'http.rb')
26
+ autoload :REST, File.join(__dir__, 'reach-ruby', 'rest.rb')
27
+
28
+ def_delegators :configuration, :username, :auth_token, :http_client, :logger
29
+
30
+ ##
31
+ # Pre-configure with ApiUser and ApiKey so that you don't need to
32
+ # pass them to various initializers each time.
33
+ def self.configure(&block)
34
+ yield configuration
35
+ end
36
+
37
+ ##
38
+ # Returns an existing or instantiates a new configuration object.
39
+ def self.configuration
40
+ @configuration ||= Util::Configuration.new
41
+ end
42
+
43
+ private_class_method :configuration
44
+ end
@@ -0,0 +1,38 @@
1
+ # coding: utf-8
2
+
3
+ lib = File.expand_path('../lib', __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'reach-ruby/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = 'reach-ruby'
9
+ spec.version = Reach::VERSION
10
+ spec.authors = ['Reach API Team']
11
+ spec.summary = 'The official library for communicating with the Reach REST API'
12
+ spec.description = 'The official library for communicating with the Reach REST API'
13
+ spec.homepage = 'https://github.com/talkylabs/reach-ruby'
14
+ spec.license = 'MIT'
15
+ spec.metadata = { 'yard.run' => 'yri' } # use "yard" to build full HTML docs
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match?(%r{^(spec)/}) }
18
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
19
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
20
+ spec.require_paths = ['lib']
21
+ spec.required_ruby_version = '>= 2.0.0'
22
+ spec.extra_rdoc_files = ['README.md', 'LICENSE']
23
+ spec.rdoc_options = ['--line-numbers', '--inline-source', '--title', 'reach-ruby', '--main', 'README.md']
24
+
25
+ spec.add_dependency('nokogiri', '>= 1.6', '< 2.0')
26
+ spec.add_dependency('faraday', '>= 0.9', '< 3.0')
27
+ # Workaround for RBX <= 2.2.1, should be fixed in next version
28
+ spec.add_dependency('rubysl') if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'rbx'
29
+
30
+ spec.add_development_dependency 'bundler', '>= 1.5', '< 3.0'
31
+ spec.add_development_dependency 'equivalent-xml', '~> 0.6'
32
+ spec.add_development_dependency 'fakeweb', '~> 1.3'
33
+ spec.add_development_dependency 'rack', '~> 2.0'
34
+ spec.add_development_dependency 'rake', '~> 13.0'
35
+ spec.add_development_dependency 'rspec', '~> 3.0'
36
+ spec.add_development_dependency 'yard', '~> 0.9.9'
37
+ spec.add_development_dependency 'logger', '~> 1.4.2'
38
+ end
@@ -0,0 +1,13 @@
1
+ sonar.projectKey=talkylabs_reach-ruby
2
+ sonar.projectName=reach-ruby
3
+ sonar.organization=talkylabs
4
+
5
+ sonar.sources=lib/reach-ruby
6
+ # Exclude any auto-generated source code
7
+ sonar.exclusions=lib/reach-ruby/rest/**/*
8
+ sonar.tests=spec/
9
+ # Exclude any auto-generated integration tests
10
+ sonar.test.exclusions=spec/integration/**/*.spec.rb
11
+
12
+ # For Code Coverage analysis
13
+ sonar.ruby.coverage.reportPaths=coverage/coverage.json