filebound_client 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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