reach-ruby 1.0.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 (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