filebound_client 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.
@@ -0,0 +1,6 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task default: :spec
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'filebound_client'
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require 'irb'
14
+ IRB.start(__FILE__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,9 @@
1
+ ---
2
+ host: fb_host
3
+ api_base_uri: /api
4
+ username: username
5
+ password: password
6
+ use_ntlm: true
7
+ ntlm_user: user
8
+ ntlm_password: password
9
+ ntlm_domain: domain.com
@@ -0,0 +1,29 @@
1
+ lib = File.expand_path('lib', __dir__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'filebound_client/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'filebound_client'
7
+ spec.version = FileboundClient::VERSION
8
+ spec.authors = ['Bryan Richardson']
9
+ spec.email = ['brichardson@heiskell.com']
10
+
11
+ spec.summary = '%q{Provides connection to FileBound API}'
12
+ spec.homepage = 'https://github.com/JDHeiskell/filebound_client'
13
+ spec.license = 'MIT'
14
+
15
+ # Specify which files should be added to the gem when it is released.
16
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
17
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
18
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
19
+ end
20
+ spec.bindir = 'exe'
21
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
+ spec.require_paths = ['lib']
23
+
24
+ spec.add_development_dependency 'bundler', '~> 1.16.0'
25
+ spec.add_development_dependency 'rake', '~> 12.3.0'
26
+ spec.add_development_dependency 'yard', '~> 0.9.0'
27
+
28
+ spec.add_runtime_dependency 'ruby-ntlm', '~> 0.0.4'
29
+ end
@@ -0,0 +1,8 @@
1
+ # Extends Hash with additional helper methods
2
+ class Hash
3
+ # Allows for conversion of string keys to symbols
4
+ # @return [Hash] hash with symbols for keys
5
+ def symbolize_keys
6
+ each_with_object({}) { |(k, v), memo| memo[k.to_sym] = v }
7
+ end
8
+ end
@@ -0,0 +1,19 @@
1
+ # Extends Object with additional helper methods
2
+ class Object
3
+ # Adds blank? method to any object
4
+ # Lifted from: https://github.com/rails/rails/blob/master/activesupport/lib/active_support/core_ext/object/blank.rb
5
+ # @return [true, false]
6
+ def blank?
7
+ # rubocop:disable Style/DoubleNegation
8
+ respond_to?(:empty?) ? !!empty? : !self
9
+ # rubocop:enable Style/DoubleNegation
10
+ end
11
+
12
+ # Checks where object is greater than zero
13
+ # Returns false of object is nil
14
+ # @return [true, false]
15
+ def greater_than_zero?
16
+ return false if nil?
17
+ to_i > 0
18
+ end
19
+ end
@@ -0,0 +1,102 @@
1
+ require 'ext/object'
2
+ require 'ext/hash'
3
+ require 'filebound_client/version'
4
+ require 'filebound_client/configuration'
5
+ require 'filebound_client/config'
6
+ require 'filebound_client/connection'
7
+ require 'filebound_client/endpoints'
8
+ require 'logger'
9
+
10
+ # This is the core module
11
+ module FileboundClient
12
+ # This encapsulates all client/server communications
13
+ class Client
14
+ include FileboundClient::Endpoints
15
+
16
+ # General client exception class
17
+ class FileboundClientException < StandardError
18
+ attr_reader :result
19
+ def initialize(message, result)
20
+ super(message)
21
+ @result = result
22
+ end
23
+ end
24
+
25
+ # Creates, initialize and logs into the Filebound API
26
+ # @param [Hash] config a hash of configuration values
27
+ # @return [FileboundClient::Client] an instance of FileboundClient::Client
28
+ def self.connect(config)
29
+ connection = FileboundClient::Connection.build_connection(config)
30
+ raise FileboundClientException.new('Failed to login', 401) unless connection.login
31
+ new(connection)
32
+ end
33
+
34
+ # Initializes the client with the supplied Connection
35
+ # @param [Connection] connection the logged in Connection
36
+ # @return [FileboundClient::Client] an instance of FileboundClient::Client
37
+ def initialize(connection)
38
+ @connection = connection
39
+ end
40
+
41
+ # Executes a GET request on the current Filebound client session expecting JSON in the body of the response
42
+ # @param [String] url the resource url to request
43
+ # @param [Hash] query_params the optional query parameters to pass to the GET request
44
+ # @return [Hash] the JSON parsed hash of the response body
45
+ def get(url, query_params = nil)
46
+ JSON.parse(perform('get', url, query: query_params), symbolize_names: true)
47
+ end
48
+
49
+ # Executes a GET request on the current Filebound client session expecting binary in the body of the response
50
+ # @param [String] url the resource url to request
51
+ # @param [Hash] query_params the optional query parameters to pass to the GET request
52
+ # @return [String] Base64 encoded binary string
53
+ def get_binary(url, query_params = nil)
54
+ perform('get', url, query: query_params)
55
+ end
56
+
57
+ # Executes a PUT request on the current Filebound client session expecting JSON in the body of the request/response
58
+ # @param [String] url the resource url to request
59
+ # @param [Hash] query_params the optional query parameters to pass to the PUT request
60
+ # @param [Hash] body the hash that will be converted to JSON when inserted in the body of the request
61
+ # @return [Hash] the JSON parsed hash of the response body
62
+ def put(url, query_params = nil, body = nil)
63
+ params = { headers: { 'Content-Type' => 'application/json' }, query: query_params, body: body }
64
+ JSON.parse(perform('put', url, params), symbolize_names: true)
65
+ end
66
+
67
+ # Executes a POST request on the current Filebound client session expecting JSON in the body of the request/response
68
+ # @param [String] url the resource url to request
69
+ # @param [Hash] query_params the optional query parameters to pass to the POST request
70
+ # @param [Hash] body the hash that will be converted to JSON when inserted in the body of the request
71
+ # @return [Hash] the JSON parsed hash of the response body
72
+ def post(url, query_params = nil, body = nil)
73
+ params = { headers: { 'Content-Type' => 'application/json' }, query: query_params, body: body }
74
+ JSON.parse(perform('post', url, params), symbolize_names: true)
75
+ end
76
+
77
+ # Executes a DELETE request on the current Filebound client session
78
+ # @param [String] url the resource url to request
79
+ # @param [Hash] query_params the optional query parameters to pass to the DELETE request
80
+ # @return [true, false] true if resource was deleted successfully
81
+ def delete(url, query_params = nil)
82
+ perform('delete', url, query: query_params)
83
+ true
84
+ end
85
+
86
+ # The connection representing the currently logged on session with the Filebound API
87
+ # @return [FileboundClient::Connection] the security token used in all requests made to the Filebound server
88
+ attr_reader :connection
89
+
90
+ private_class_method :new
91
+
92
+ private
93
+
94
+ def perform(action, url, params)
95
+ response = connection.send(action, url, params)
96
+ # rubocop:disable Metrics/LineLength
97
+ raise FileboundClientException.new("#{action.upcase} request failed: #{response.code}", response.code) unless response.code == '200'
98
+ # rubocop:enable Metrics/LineLength
99
+ response.body
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,33 @@
1
+ module FileboundClient
2
+ # Config module
3
+ module Config
4
+ # Retrive the current configuration
5
+ # @return [FileboundClient::Configuration]
6
+ def configuration
7
+ @configuration
8
+ end
9
+
10
+ # Retrieve the default logger
11
+ # @return [Logger] the default logger
12
+ def default_logger
13
+ logger ||= Logger.new(STDOUT)
14
+ logger.level = Logger::DEBUG
15
+ logger
16
+ end
17
+
18
+ # Creates a new Configuration object and yields to the caller
19
+ # Also sets the logger
20
+ def configure
21
+ @configuration ||= Configuration.new
22
+ yield @configuration
23
+
24
+ @configuration.logger ||= default_logger
25
+ end
26
+
27
+ # Simple logger plugin for debugging
28
+ def logger(message)
29
+ return unless configuration.logger
30
+ configuration.logger.debug(message)
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,6 @@
1
+ module FileboundClient
2
+ # Encapsulate configuration data
3
+ class Configuration
4
+ attr_accessor :host, :api_base_uri, :username, :password, :logger, :ntlm_auth
5
+ end
6
+ end
@@ -0,0 +1,183 @@
1
+ require 'ntlm/http'
2
+ require 'filebound_client/config'
3
+ require 'json'
4
+
5
+ module FileboundClient
6
+ # Encapsulates low level logic to talk to Filebound server
7
+ class Connection
8
+ include Config
9
+
10
+ # The token returned from the Filebound server on successfully logging in
11
+ # @return [Guid] the security token used in all requests made to the Filebound server
12
+ attr_reader :token
13
+
14
+ # Creates a new connection using the supplied configuration
15
+ # @param [Configuration] config the Configuration to use for the connection
16
+ # @return [Connection] the newly created and initialized Connection
17
+ def self.build_connection(config)
18
+ new(config)
19
+ end
20
+
21
+ # Initialize a new connection with the supplied configuration
22
+ # @param [Configuration] config the Configuration to use for the connection
23
+ # @return [Connection] the newly created and initialized Connection
24
+ def initialize(config)
25
+ configure do |c|
26
+ c.host = config[:host]
27
+ c.api_base_uri = config[:api_base_uri]
28
+ c.username = config[:username]
29
+ c.password = config[:password]
30
+ # rubocop:disable Metrics/LineLength
31
+ c.ntlm_auth = { user: config[:ntlm_user], password: config[:ntlm_password], domain: config[:ntlm_domain] } if config[:use_ntlm]
32
+ # rubocop:enable Metrics/LineLength
33
+ end
34
+ end
35
+
36
+ private_class_method :new
37
+
38
+ # The Filebound server hostname and protocol
39
+ # @return [String] the Filebound server URL
40
+ # @example
41
+ # "http://localhost"
42
+ def host
43
+ configuration.host
44
+ end
45
+
46
+ # The base path to the API on the Filebound server
47
+ # @return [String] the base path to the API
48
+ # @example
49
+ # "/api"
50
+ def api_base_uri
51
+ configuration.api_base_uri
52
+ end
53
+
54
+ # The username to log on to the Filebound server with
55
+ # @return [String] the username
56
+ # @example
57
+ # "username"
58
+ def username
59
+ configuration.username
60
+ end
61
+
62
+ # The password to log on to the Filebound server with
63
+ # @return [String] the password
64
+ # @example
65
+ # "password"
66
+ def password
67
+ configuration.password
68
+ end
69
+
70
+ # The NTLM username to use for NTLM Authentication
71
+ # @return [String] the NTLM username
72
+ # @example
73
+ # "ntlm_user"
74
+ def ntlm_user
75
+ configuration.ntlm_auth[:user] if configuration.ntlm_auth
76
+ end
77
+
78
+ # The NTLM password to use for NTLM Authentication
79
+ # @return [String] the NTLM password
80
+ # @example
81
+ # "ntlm_password"
82
+ def ntlm_password
83
+ configuration.ntlm_auth[:password] if configuration.ntlm_auth
84
+ end
85
+
86
+ # The NTLM domain to use for NTLM Authentication
87
+ # @return [String] the NTLM domain
88
+ # @example
89
+ # "ntlm_domain"
90
+ def ntlm_domain
91
+ configuration.ntlm_auth[:domain] if configuration.ntlm_auth
92
+ end
93
+
94
+ # The authentication query parameters required by all requests to the API
95
+ # @return [Hash] the query parameters hash
96
+ def auth_params
97
+ { query: { guid: token } }
98
+ end
99
+
100
+ # Sends a GET request to the supplied resource using the supplied params hash
101
+ # @param [String] url the url that represents the resource
102
+ # @param [Hash] params the params Hash that will be sent in the request (keys: query, headers, body)
103
+ # @return [Net::HTTPResponse] the response from the GET request
104
+ def get(url, params)
105
+ request = Net::HTTP::Get.new(resource_url(url, query_params(params[:query])))
106
+ execute_request(request, params)
107
+ end
108
+
109
+ # Sends a PUT request to the supplied resource using the supplied params hash
110
+ # @param [String] url the url that represents the resource
111
+ # @param [Hash] params the params Hash that will be sent in the request (keys: query, headers, body)
112
+ # @return [Net::HTTPResponse] the response from the PUT request
113
+ def put(url, params)
114
+ request = Net::HTTP::Put.new(resource_url(url, query_params(params[:query])))
115
+ request.body = params[:body].to_json
116
+ execute_request(request, params)
117
+ end
118
+
119
+ # Sends a POST request to the supplied resource using the supplied params hash
120
+ # @param [String] url the url that represents the resource
121
+ # @param [Hash] params the params Hash that will be sent in the request (keys: query, headers, body)
122
+ # @return [Net::HTTPResponse] the response from the POST request
123
+ def post(url, params)
124
+ request = Net::HTTP::Post.new(resource_url(url, query_params(params[:query])))
125
+ request.body = params[:body].to_json
126
+ execute_request(request, params)
127
+ end
128
+
129
+ # Sends a DELETE request to the supplied resource using the supplied params hash
130
+ # @param [String] url the url that represents the resource
131
+ # @param [Hash] params the params Hash that will be sent in the request (keys: query, headers, body)
132
+ # @return [Net::HTTPResponse] the response from the DELETE request
133
+ def delete(url, params)
134
+ request = Net::HTTP::Delete.new(resource_url(url, query_params(params[:query])))
135
+ execute_request(request, params)
136
+ end
137
+
138
+ # Sends a POST request to the Filebound API's login endpoint to request a new security token
139
+ # @return [true, false] returns true if the login was successful and the token was set
140
+ def login
141
+ response = post('/login', body: { username: configuration.username, password: configuration.password },
142
+ headers: { 'Content-Type' => 'application/json' })
143
+ if response.is_a?(Net::HTTPSuccess)
144
+ @token = JSON.parse(response.body, symbolize_names: true)
145
+ true
146
+ else
147
+ false
148
+ end
149
+ end
150
+
151
+ private
152
+
153
+ def resource_url(url, query)
154
+ return "#{api_base_uri}/#{url.reverse.chomp('/').reverse}" unless query
155
+ query_string = query.map { |k, v| "#{k}=#{v}" }.join('&')
156
+ "#{api_base_uri}/#{url.reverse.chomp('/').reverse}?#{query_string}"
157
+ end
158
+
159
+ def query_params(params)
160
+ if params
161
+ auth_params[:query].merge!(params)
162
+ else
163
+ auth_params[:query]
164
+ end
165
+ end
166
+
167
+ def set_headers(request, headers)
168
+ if headers.respond_to?(:to_hash)
169
+ headers.each do |k, v|
170
+ request[k.to_s] = v.to_s
171
+ end
172
+ end
173
+ request
174
+ end
175
+
176
+ def execute_request(request, params)
177
+ http = Net::HTTP.new(configuration.host)
178
+ request = set_headers(request, params[:headers])
179
+ request.ntlm_auth(ntlm_user, ntlm_domain, ntlm_password) if configuration.ntlm_auth
180
+ http.request(request)
181
+ end
182
+ end
183
+ end
@@ -0,0 +1,52 @@
1
+ require 'filebound_client/endpoints/projects'
2
+ require 'filebound_client/endpoints/files'
3
+ require 'filebound_client/endpoints/documents'
4
+ require 'filebound_client/endpoints/version'
5
+ require 'filebound_client/endpoints/assignments'
6
+ require 'filebound_client/endpoints/dividers'
7
+ require 'filebound_client/endpoints/document_binary_data'
8
+ require 'filebound_client/endpoints/eform_data'
9
+ require 'filebound_client/endpoints/separators'
10
+
11
+ module FileboundClient
12
+ # Module for resource endpoints
13
+ module Endpoints
14
+ # Sets up macros for use by endpoints and includes all endpoints
15
+ # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
16
+ def self.included(klass)
17
+ klass.instance_eval do
18
+ # @!macro [attach] fb.allow_new
19
+ # Returns a new empty resource with defaulted values
20
+ # @return [Hash] a new hash of $1
21
+ def self.allow_new(name)
22
+ define_method("#{name}_new") do
23
+ get('/empty', template: name.to_s)
24
+ end
25
+ end
26
+
27
+ # @!macro [attach] fb.allow_all
28
+ # Returns an array $1 hashes
29
+ # @param [Hash] query_params params to pass to the request
30
+ # @return [Array] an array of $1 hashes
31
+ def self.allow_all(name)
32
+ define_method(name.to_s) do |query_params = nil|
33
+ get("/#{name}", query_params)
34
+ end
35
+ end
36
+ end
37
+
38
+ klass.class_eval do
39
+ include FileboundClient::Endpoints::Projects
40
+ include FileboundClient::Endpoints::Files
41
+ include FileboundClient::Endpoints::Documents
42
+ include FileboundClient::Endpoints::Version
43
+ include FileboundClient::Endpoints::Assignments
44
+ include FileboundClient::Endpoints::Dividers
45
+ include FileboundClient::Endpoints::DocumentBinaryData
46
+ include FileboundClient::Endpoints::EFormData
47
+ include FileboundClient::Endpoints::Separators
48
+ end
49
+ end
50
+ # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
51
+ end
52
+ end