visor-auth 0.0.1
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.
- data/bin/visor-admin +282 -0
- data/bin/visor-auth +8 -0
- data/lib/auth/backends/base.rb +167 -0
- data/lib/auth/backends/mongo_db.rb +165 -0
- data/lib/auth/backends/mysql_db.rb +186 -0
- data/lib/auth/cli.rb +333 -0
- data/lib/auth/client.rb +149 -0
- data/lib/auth/server.rb +215 -0
- data/lib/auth/version.rb +5 -0
- data/lib/visor-auth.rb +12 -0
- data/spec/lib/server_spec.rb +189 -0
- metadata +203 -0
@@ -0,0 +1,186 @@
|
|
1
|
+
require 'mysql2'
|
2
|
+
require 'json'
|
3
|
+
require 'uri'
|
4
|
+
|
5
|
+
module Visor
|
6
|
+
module Auth
|
7
|
+
module Backends
|
8
|
+
|
9
|
+
# The MySQL Backend for the VISoR Auth.
|
10
|
+
#
|
11
|
+
class MySQL < Base
|
12
|
+
include Visor::Common::Exception
|
13
|
+
|
14
|
+
# Connection constants
|
15
|
+
#
|
16
|
+
# Default MySQL database
|
17
|
+
DEFAULT_DB = 'visor'
|
18
|
+
# Default MySQL host address
|
19
|
+
DEFAULT_HOST = '127.0.0.1'
|
20
|
+
# Default MySQL host port
|
21
|
+
DEFAULT_PORT = 3306
|
22
|
+
# Default MySQL user
|
23
|
+
DEFAULT_USER = 'visor'
|
24
|
+
# Default MySQL password
|
25
|
+
DEFAULT_PASSWORD = 'passwd'
|
26
|
+
|
27
|
+
#CREATE DATABASE visor;
|
28
|
+
#CREATE USER 'visor'@'localhost' IDENTIFIED BY 'visor';
|
29
|
+
#SET PASSWORD FOR 'visor'@'localhost' = PASSWORD('passwd');
|
30
|
+
#GRANT ALL PRIVILEGES ON visor.* TO 'visor'@'localhost';
|
31
|
+
|
32
|
+
# Initializes a MongoDB Backend instance.
|
33
|
+
#
|
34
|
+
# @option [Hash] opts Any of the available options can be passed.
|
35
|
+
#
|
36
|
+
# @option opts [String] :uri The connection uri, if provided, no other option needs to be setted.
|
37
|
+
# @option opts [String] :db (DEFAULT_DB) The wanted database.
|
38
|
+
# @option opts [String] :host (DEFAULT_HOST) The host address.
|
39
|
+
# @option opts [Integer] :port (DEFAULT_PORT) The port to be used.
|
40
|
+
# @option opts [String] :user (DEFAULT_USER) The user to be used.
|
41
|
+
# @option opts [String] :password (DEFAULT_PASSWORD) The password to be used.
|
42
|
+
# @option opts [Object] :conn The connection pool to access database.
|
43
|
+
#
|
44
|
+
def self.connect(opts = {})
|
45
|
+
opts[:uri] ||= ''
|
46
|
+
uri = URI.parse(opts[:uri])
|
47
|
+
opts[:db] = uri.path ? uri.path.gsub('/', '') : DEFAULT_DB
|
48
|
+
opts[:host] = uri.host || DEFAULT_HOST
|
49
|
+
opts[:port] = uri.port || DEFAULT_PORT
|
50
|
+
opts[:user] = uri.user || DEFAULT_USER
|
51
|
+
opts[:password] = uri.password || DEFAULT_PASSWORD
|
52
|
+
|
53
|
+
self.new opts
|
54
|
+
end
|
55
|
+
|
56
|
+
def initialize(opts)
|
57
|
+
super opts
|
58
|
+
@conn = connection
|
59
|
+
@conn.query %[
|
60
|
+
CREATE TABLE IF NOT EXISTS `#{opts[:db]}`.`users` (
|
61
|
+
`_id` VARCHAR(45) NOT NULL ,
|
62
|
+
`access_key` VARCHAR(45) NOT NULL ,
|
63
|
+
`secret_key` VARCHAR(45) NOT NULL ,
|
64
|
+
`email` VARCHAR(45) NOT NULL ,
|
65
|
+
`created_at` DATETIME NULL ,
|
66
|
+
`updated_at` DATETIME NULL ,
|
67
|
+
PRIMARY KEY (`_id`) )
|
68
|
+
ENGINE = InnoDB;
|
69
|
+
]
|
70
|
+
end
|
71
|
+
|
72
|
+
# Establishes and returns a MySQL database connection and
|
73
|
+
# creates Images table if it does not exists.
|
74
|
+
#
|
75
|
+
# @return [Mysql2::Client] It returns a database client object.
|
76
|
+
#
|
77
|
+
def connection
|
78
|
+
Mysql2::Client.new(host: @host, port: @port, database: @db,
|
79
|
+
username: @user, password: @password)
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
# Returns an array with the registered users.
|
84
|
+
#
|
85
|
+
# @option [Hash] filters Users attributes for filtering the returned results.
|
86
|
+
# Besides common attributes filters, the following options can be passed to.
|
87
|
+
#
|
88
|
+
# @return [Array] The users information.
|
89
|
+
#
|
90
|
+
# @raise [NotFound] If there are no registered users.
|
91
|
+
#
|
92
|
+
def get_users(filters = {})
|
93
|
+
validate_query_filters filters unless filters.empty?
|
94
|
+
filter = filters.empty? ? 1 : to_sql_where(filters)
|
95
|
+
users = @conn.query("SELECT * FROM users WHERE #{filter}", symbolize_keys: true).to_a
|
96
|
+
raise NotFound, "No users found." if users.empty? && filters.empty?
|
97
|
+
raise NotFound, "No users found with given parameters." if users.empty?
|
98
|
+
users
|
99
|
+
end
|
100
|
+
|
101
|
+
# Returns an user information.
|
102
|
+
#
|
103
|
+
# @param [String] access_key The user access_key.
|
104
|
+
#
|
105
|
+
# @return [Hash] The requested user information.
|
106
|
+
#
|
107
|
+
# @raise [NotFound] If user was not found.
|
108
|
+
#
|
109
|
+
def get_user(access_key)
|
110
|
+
user = @conn.query("SELECT * FROM users WHERE access_key='#{access_key}'", symbolize_keys: true).first
|
111
|
+
raise NotFound, "No user found with access_key '#{access_key}'." unless user
|
112
|
+
user
|
113
|
+
end
|
114
|
+
|
115
|
+
# Delete a registered user.
|
116
|
+
#
|
117
|
+
# @param [String] access_key The user access_key.
|
118
|
+
#
|
119
|
+
# @return [hash] The deleted image metadata.
|
120
|
+
#
|
121
|
+
# @raise [NotFound] If user was not found.
|
122
|
+
#
|
123
|
+
def delete_user(access_key)
|
124
|
+
user = @conn.query("SELECT * FROM users WHERE access_key='#{access_key}'", symbolize_keys: true).first
|
125
|
+
raise NotFound, "No user found with access_key '#{access_key}'." unless user
|
126
|
+
@conn.query "DELETE FROM users WHERE access_key='#{access_key}'"
|
127
|
+
user
|
128
|
+
end
|
129
|
+
|
130
|
+
# Delete all images records.
|
131
|
+
#
|
132
|
+
def delete_all!
|
133
|
+
@conn.query "DELETE FROM users"
|
134
|
+
end
|
135
|
+
|
136
|
+
# Create a new user record for the given information.
|
137
|
+
#
|
138
|
+
# @param [Hash] user The user information.
|
139
|
+
#
|
140
|
+
# @return [Hash] The already added user information.
|
141
|
+
#
|
142
|
+
# @raise [Invalid] If user information validation fails.
|
143
|
+
# @raise [ConflictError] If an access_key was already taken.
|
144
|
+
#
|
145
|
+
def post_user(user)
|
146
|
+
validate_data_post user
|
147
|
+
exists = @conn.query("SELECT * FROM users WHERE access_key='#{user[:access_key]}'", symbolize_keys: true).first
|
148
|
+
raise ConflictError, "The access_key '#{user[:access_key]}' was already taken." if exists
|
149
|
+
|
150
|
+
set_protected_post user
|
151
|
+
keys_values = to_sql_insert(user)
|
152
|
+
@conn.query "INSERT INTO users #{keys_values[0]} VALUES #{keys_values[1]}"
|
153
|
+
self.get_user(user[:access_key])
|
154
|
+
end
|
155
|
+
|
156
|
+
# Update an user information.
|
157
|
+
#
|
158
|
+
# @param [String] access_key The user access_key.
|
159
|
+
# @param [Hash] update The user information update.
|
160
|
+
#
|
161
|
+
# @return [BSON::OrderedHash] The updated user information.
|
162
|
+
#
|
163
|
+
# @raise [Invalid] If user information validation fails.
|
164
|
+
# @raise [ConflictError] If an access_key was already taken.
|
165
|
+
# @raise [NotFound] If user was not found.
|
166
|
+
#
|
167
|
+
def put_user(access_key, update)
|
168
|
+
validate_data_put update
|
169
|
+
user = @conn.query("SELECT * FROM users WHERE access_key='#{access_key}'", symbolize_keys: true).first
|
170
|
+
raise NotFound, "No user found with access_key '#{access_key}'." unless user
|
171
|
+
|
172
|
+
if update[:access_key]
|
173
|
+
exists = @conn.query("SELECT * FROM users WHERE access_key='#{update[:access_key]}'", symbolize_keys: true).first
|
174
|
+
raise ConflictError, "The access_key '#{update[:access_key]}' was already taken." if exists
|
175
|
+
end
|
176
|
+
|
177
|
+
set_protected_put update
|
178
|
+
@conn.query "UPDATE users SET #{to_sql_update(update)} WHERE access_key='#{access_key}'"
|
179
|
+
self.get_user(update[:access_key] || access_key)
|
180
|
+
end
|
181
|
+
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
data/lib/auth/cli.rb
ADDED
@@ -0,0 +1,333 @@
|
|
1
|
+
require 'open-uri'
|
2
|
+
require 'logger'
|
3
|
+
require 'optparse'
|
4
|
+
require 'fileutils'
|
5
|
+
require 'rack'
|
6
|
+
|
7
|
+
module Visor
|
8
|
+
module Auth
|
9
|
+
class CLI
|
10
|
+
|
11
|
+
attr_reader :app, :cli_name, :argv, :options,
|
12
|
+
:port, :host, :env, :command, :parser
|
13
|
+
|
14
|
+
# Available commands
|
15
|
+
COMMANDS = %w[start stop restart status clean]
|
16
|
+
# Commands that wont load options from the config file
|
17
|
+
NO_CONF_COMMANDS = %w[stop status]
|
18
|
+
# Default config files directories to look at
|
19
|
+
DEFAULT_DIR = File.expand_path('~/.visor')
|
20
|
+
# Default host address
|
21
|
+
DEFAULT_HOST = '0.0.0.0'
|
22
|
+
# Default port
|
23
|
+
DEFAULT_PORT = 4566
|
24
|
+
# Default application environment
|
25
|
+
DEFAULT_ENV = :production
|
26
|
+
|
27
|
+
# Initialize a CLI
|
28
|
+
#
|
29
|
+
def initialize(app, cli_name, argv=ARGV)
|
30
|
+
@app = app
|
31
|
+
@cli_name = cli_name
|
32
|
+
@argv = argv
|
33
|
+
@options = default_opts
|
34
|
+
@parser = parser
|
35
|
+
@command = parse!
|
36
|
+
end
|
37
|
+
|
38
|
+
def default_opts
|
39
|
+
{debug: false,
|
40
|
+
foreground: false,
|
41
|
+
no_proxy: false,
|
42
|
+
environment: DEFAULT_ENV}
|
43
|
+
end
|
44
|
+
|
45
|
+
# OptionParser parser
|
46
|
+
#
|
47
|
+
def parser
|
48
|
+
OptionParser.new do |opts|
|
49
|
+
opts.banner = "Usage: #{cli_name} [OPTIONS] COMMAND"
|
50
|
+
|
51
|
+
opts.separator ""
|
52
|
+
opts.separator "Commands:"
|
53
|
+
opts.separator " start start the server"
|
54
|
+
opts.separator " stop stop the server"
|
55
|
+
opts.separator " restart restart the server"
|
56
|
+
opts.separator " status current server status"
|
57
|
+
|
58
|
+
opts.separator ""
|
59
|
+
opts.separator "Options:"
|
60
|
+
|
61
|
+
opts.on("-c", "--config FILE", "Load a custom configuration file") do |file|
|
62
|
+
options[:config] = File.expand_path(file)
|
63
|
+
end
|
64
|
+
opts.on("-o", "--host HOST", "listen on HOST (default: #{DEFAULT_HOST})") do |host|
|
65
|
+
options[:host] = host.to_s
|
66
|
+
end
|
67
|
+
opts.on("-p", "--port PORT", "use PORT (default: #{DEFAULT_PORT})") do |port|
|
68
|
+
options[:port] = port.to_i
|
69
|
+
end
|
70
|
+
opts.on("-x", "--no-proxy", "ignore proxy settings if any") do
|
71
|
+
options[:no_proxy] = true
|
72
|
+
end
|
73
|
+
opts.on("-e", "--env ENVIRONMENT", "use ENVIRONMENT for defaults (default: #{DEFAULT_ENV})") do |env|
|
74
|
+
options[:environment] = env.to_sym
|
75
|
+
end
|
76
|
+
opts.on("-F", "--foreground", "don't daemonize, run in the foreground") do
|
77
|
+
options[:foreground] = true
|
78
|
+
end
|
79
|
+
|
80
|
+
opts.separator ""
|
81
|
+
opts.separator "Common options:"
|
82
|
+
|
83
|
+
opts.on_tail("-d", "--debug", "Set debugging on (with foreground only)") do
|
84
|
+
options[:debug] = true
|
85
|
+
end
|
86
|
+
opts.on_tail("-h", "--help", "Show this message") do
|
87
|
+
puts opts
|
88
|
+
exit
|
89
|
+
end
|
90
|
+
opts.on_tail('-v', '--version', "Show version") do
|
91
|
+
puts "VISoR Auth Server v#{Visor::Auth::VERSION}"
|
92
|
+
exit
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Parse the current shell arguments and run the command.
|
98
|
+
# Exits on error.
|
99
|
+
#
|
100
|
+
def run!
|
101
|
+
if command.nil?
|
102
|
+
abort @parser.to_s
|
103
|
+
elsif COMMANDS.include?(command)
|
104
|
+
run_command
|
105
|
+
else
|
106
|
+
abort "Unknown command: #{command}. Available commands: #{COMMANDS.join(', ')}"
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# Execute the command
|
111
|
+
#
|
112
|
+
def run_command
|
113
|
+
unless NO_CONF_COMMANDS.include?(command)
|
114
|
+
@conf = load_conf_file
|
115
|
+
@host = options[:host] || @conf[:bind_host] || DEFAULT_HOST
|
116
|
+
@port = options[:port] || @conf[:bind_port] || DEFAULT_PORT
|
117
|
+
@env = options[:environment]
|
118
|
+
end
|
119
|
+
|
120
|
+
case command
|
121
|
+
when 'start' then start
|
122
|
+
when 'stop' then stop
|
123
|
+
when 'restart' then restart
|
124
|
+
when 'status' then status
|
125
|
+
else clean
|
126
|
+
end
|
127
|
+
exit 0
|
128
|
+
end
|
129
|
+
|
130
|
+
# Remove all files created by the daemon.
|
131
|
+
#
|
132
|
+
def clean
|
133
|
+
begin
|
134
|
+
FileUtils.rm(pid_file) rescue Errno::ENOENT
|
135
|
+
end
|
136
|
+
begin
|
137
|
+
FileUtils.rm(url_file) rescue Errno::ENOENT
|
138
|
+
end
|
139
|
+
put_and_log :warn, "Removed all files created by server start"
|
140
|
+
end
|
141
|
+
|
142
|
+
# Restart server
|
143
|
+
#
|
144
|
+
def restart
|
145
|
+
@restart = true
|
146
|
+
stop
|
147
|
+
sleep 0.1 while running?
|
148
|
+
start
|
149
|
+
end
|
150
|
+
|
151
|
+
# Display current server status
|
152
|
+
#
|
153
|
+
def status
|
154
|
+
if running?
|
155
|
+
STDERR.puts "#{cli_name} is running PID: #{fetch_pid} URL: #{fetch_url}"
|
156
|
+
else
|
157
|
+
STDERR.puts "#{cli_name} is not running."
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
# Stop the server
|
162
|
+
#
|
163
|
+
def stop
|
164
|
+
begin
|
165
|
+
pid = File.read(pid_file)
|
166
|
+
put_and_log :warn, "Stopping #{cli_name} with PID: #{pid.to_i} Signal: INT"
|
167
|
+
Process.kill(:INT, pid.to_i)
|
168
|
+
File.delete(url_file)
|
169
|
+
rescue
|
170
|
+
put_and_log :warn, "Cannot stop #{cli_name}, is it running?"
|
171
|
+
exit! 1
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
# Start the server
|
176
|
+
#
|
177
|
+
def start
|
178
|
+
FileUtils.mkpath(DEFAULT_DIR)
|
179
|
+
begin
|
180
|
+
is_it_running?
|
181
|
+
can_use_port?
|
182
|
+
write_url
|
183
|
+
launch!
|
184
|
+
rescue => e
|
185
|
+
put_and_log :warn, "ERROR starting #{cli_name}: #{e}"
|
186
|
+
exit! 1
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
# Launch the server
|
191
|
+
#
|
192
|
+
def launch!
|
193
|
+
put_and_log :info, "Starting #{cli_name} at #{host}:#{port}"
|
194
|
+
debug_settings
|
195
|
+
|
196
|
+
Rack::Server.start(app: app,
|
197
|
+
Host: host,
|
198
|
+
Port: port,
|
199
|
+
environment: get_env,
|
200
|
+
daemonize: daemonize?,
|
201
|
+
pid: pid_file)
|
202
|
+
end
|
203
|
+
|
204
|
+
protected
|
205
|
+
|
206
|
+
def is_it_running?
|
207
|
+
if files_exist?(pid_file, url_file)
|
208
|
+
if running?
|
209
|
+
put_and_log :warn, "'#{cli_name}' is already running at #{fetch_url}"
|
210
|
+
exit! 1
|
211
|
+
else
|
212
|
+
clean
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
def running?
|
218
|
+
begin
|
219
|
+
Process.kill 0, fetch_pid
|
220
|
+
true
|
221
|
+
rescue Errno::ESRCH
|
222
|
+
false
|
223
|
+
rescue Errno::EPERM
|
224
|
+
true
|
225
|
+
rescue
|
226
|
+
false
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
def can_use_port?
|
231
|
+
unless port_open?
|
232
|
+
put_and_log :warn, "Port #{port} already in use. Please try other."
|
233
|
+
exit! 1
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
def port_open?
|
238
|
+
begin
|
239
|
+
options[:no_proxy] ? open(url, proxy: nil) : open(url)
|
240
|
+
false
|
241
|
+
rescue OpenURI::HTTPError #TODO: quick-fix, try solve this
|
242
|
+
false
|
243
|
+
rescue Errno::ECONNREFUSED
|
244
|
+
true
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
def daemonize?
|
249
|
+
!options[:foreground]
|
250
|
+
end
|
251
|
+
|
252
|
+
def get_env
|
253
|
+
env == 'development' ? env : 'deployment'
|
254
|
+
end
|
255
|
+
|
256
|
+
def logger
|
257
|
+
@conf ||= load_conf_file
|
258
|
+
@logger ||=
|
259
|
+
begin
|
260
|
+
log = options[:foreground] ? Logger.new(STDERR) : Visor::Common::Config.build_logger(:visor_auth)
|
261
|
+
conf_level = @conf[:log_level] == 'INFO' ? 1 : 0
|
262
|
+
log.level = options[:debug] ? 0 : conf_level
|
263
|
+
log.formatter = Proc.new {|s, t, n, msg| "[#{t.strftime("%Y-%m-%d %H:%M:%S")}] #{s} - #{msg}\n"}
|
264
|
+
log
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
def put_and_log(level, msg)
|
269
|
+
STDERR.puts msg
|
270
|
+
logger.send level, msg
|
271
|
+
end
|
272
|
+
|
273
|
+
def parse!
|
274
|
+
parser.parse! argv
|
275
|
+
argv.shift
|
276
|
+
end
|
277
|
+
|
278
|
+
def debug_settings
|
279
|
+
logger.debug "Configurations loaded from #{@conf[:file]}:"
|
280
|
+
logger.debug "***************************************************"
|
281
|
+
@conf.each { |k, v| logger.debug "#{k}: #{v}" }
|
282
|
+
logger.debug "***************************************************"
|
283
|
+
|
284
|
+
logger.debug "Configurations passed from #{cli_name} CLI:"
|
285
|
+
logger.debug "***************************************************"
|
286
|
+
options.each { |k, v| logger.debug "#{k}: #{v}" if options[k] != default_opts[k] }
|
287
|
+
logger.debug "***************************************************"
|
288
|
+
end
|
289
|
+
|
290
|
+
def files_exist?(*files)
|
291
|
+
files.each { |file| return false unless File.exists?(File.expand_path(file)) }
|
292
|
+
true
|
293
|
+
end
|
294
|
+
|
295
|
+
def write_url
|
296
|
+
File.open(url_file, 'w') { |f| f << url }
|
297
|
+
end
|
298
|
+
|
299
|
+
def load_conf_file
|
300
|
+
Visor::Common::Config.load_config(:visor_auth, options[:config])
|
301
|
+
end
|
302
|
+
|
303
|
+
def safe_cli_name
|
304
|
+
cli_name.gsub('-', '_')
|
305
|
+
end
|
306
|
+
|
307
|
+
def fetch_pid
|
308
|
+
IO.read(pid_file).to_i
|
309
|
+
rescue
|
310
|
+
nil
|
311
|
+
end
|
312
|
+
|
313
|
+
def fetch_url
|
314
|
+
IO.read(url_file).split('//').last
|
315
|
+
rescue
|
316
|
+
nil
|
317
|
+
end
|
318
|
+
|
319
|
+
def pid_file
|
320
|
+
File.join(DEFAULT_DIR, "#{safe_cli_name}.pid")
|
321
|
+
end
|
322
|
+
|
323
|
+
def url_file
|
324
|
+
File.join(DEFAULT_DIR, "#{safe_cli_name}.url")
|
325
|
+
end
|
326
|
+
|
327
|
+
def url
|
328
|
+
"http://#{host}:#{port}"
|
329
|
+
end
|
330
|
+
|
331
|
+
end
|
332
|
+
end
|
333
|
+
end
|
data/lib/auth/client.rb
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'net/https'
|
3
|
+
require 'uri'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
module Visor
|
7
|
+
module Auth
|
8
|
+
|
9
|
+
# The Client API for the VISoR Auth.
|
10
|
+
#
|
11
|
+
# After Instantiate a Client object its possible to directly interact with the auth server and its
|
12
|
+
# database backend.
|
13
|
+
#
|
14
|
+
class Client
|
15
|
+
|
16
|
+
include Visor::Common::Exception
|
17
|
+
|
18
|
+
configs = Common::Config.load_config :visor_auth
|
19
|
+
|
20
|
+
DEFAULT_HOST = configs[:bind_host] || '0.0.0.0'
|
21
|
+
DEFAULT_PORT = configs[:bind_port] || 4566
|
22
|
+
|
23
|
+
attr_reader :host, :port, :ssl
|
24
|
+
|
25
|
+
def initialize(opts = {})
|
26
|
+
@host = opts[:host] || DEFAULT_HOST
|
27
|
+
@port = opts[:port] || DEFAULT_PORT
|
28
|
+
@ssl = opts[:ssl] || false
|
29
|
+
end
|
30
|
+
|
31
|
+
def get_users(query={})
|
32
|
+
str = build_query(query)
|
33
|
+
request = Net::HTTP::Get.new("/users#{str}")
|
34
|
+
do_request(request)
|
35
|
+
end
|
36
|
+
|
37
|
+
def get_user(access_key)
|
38
|
+
request = Net::HTTP::Get.new("/users/#{access_key}")
|
39
|
+
do_request(request)
|
40
|
+
end
|
41
|
+
|
42
|
+
def post_user(info)
|
43
|
+
request = Net::HTTP::Post.new('/users')
|
44
|
+
request.body = prepare_body(info)
|
45
|
+
do_request(request)
|
46
|
+
end
|
47
|
+
|
48
|
+
def put_user(access_key, info)
|
49
|
+
request = Net::HTTP::Put.new("/users/#{access_key}")
|
50
|
+
request.body = prepare_body(info)
|
51
|
+
do_request(request)
|
52
|
+
end
|
53
|
+
|
54
|
+
def delete_user(access_key)
|
55
|
+
request = Net::HTTP::Delete.new("/users/#{access_key}")
|
56
|
+
do_request(request)
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
# Parses a response body with the JSON parser and extracts and returns a single
|
62
|
+
# key value from it if defined, otherwise returns all the body.
|
63
|
+
#
|
64
|
+
# @param key (nil) [Symbol] The hash key to extract the wanted value.
|
65
|
+
# @param response [Net::HTTPResponse] The response which contains the body to parse.
|
66
|
+
#
|
67
|
+
# @return [String, Hash] If key is provided and exists on the response body, them return
|
68
|
+
# its value, otherwise return all the body hash.
|
69
|
+
#
|
70
|
+
def parse(key=nil, response)
|
71
|
+
parsed = JSON.parse(response.body, symbolize_names: true)
|
72
|
+
key ? parsed[key] : parsed
|
73
|
+
end
|
74
|
+
|
75
|
+
# Generate a valid URI query string from key/value pairs of the given hash.
|
76
|
+
#
|
77
|
+
# @param opts [Hash] The hash with the key/value pairs to generate query from.
|
78
|
+
#
|
79
|
+
# @return [String] The generated query in the form of "?k=v&k1=v1".
|
80
|
+
#
|
81
|
+
def build_query(h)
|
82
|
+
(h.nil? or h.empty?) ? '' : '?' + URI.encode_www_form(h)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Fill common header keys before each request. This sets the 'User-Agent' and 'Accept'
|
86
|
+
# headers for every request and additionally sets the 'content-type' header
|
87
|
+
# for POST and PUT requests.
|
88
|
+
#
|
89
|
+
# @param request [Net::HTTPResponse] The request which will be modified in its headers.
|
90
|
+
#
|
91
|
+
def prepare_headers(request)
|
92
|
+
request['User-Agent'] = 'VISoR image server'
|
93
|
+
request['Accept'] = 'application/json'
|
94
|
+
request['content-type'] = 'application/json' if ['POST', 'PUT'].include?(request.method)
|
95
|
+
end
|
96
|
+
|
97
|
+
# Generate a valid JSON request body for POST and PUT requests.
|
98
|
+
# It generates a JSON object encapsulated inside a :image key and then returns it.
|
99
|
+
#
|
100
|
+
# @param hash [Hash] The hash with the key/value pairs to generate a JSON object from.
|
101
|
+
#
|
102
|
+
# @return [Hash] If an :image key is already present in the hash, it just returns the plain
|
103
|
+
# JSON object, otherwise, encapsulate the hash inside a :image key and returns it.
|
104
|
+
#
|
105
|
+
def prepare_body(hash)
|
106
|
+
hash.has_key?(:user) ? hash.to_json : {user: hash}.to_json
|
107
|
+
end
|
108
|
+
|
109
|
+
# Process requests by preparing its headers, launch them and assert or raise their response.
|
110
|
+
#
|
111
|
+
# @param request [Net::HTTPResponse] The request which will be launched.
|
112
|
+
#
|
113
|
+
# @return [String, Hash] If an error is raised, then it parses and returns its message,
|
114
|
+
# otherwise it properly parse and return the response body.
|
115
|
+
#
|
116
|
+
# @raise [NotFound] If required image was not found (on a GET, PUT or DELETE request).
|
117
|
+
# @raise [Invalid] If image meta validation fails (on a POST or PUT request).
|
118
|
+
#
|
119
|
+
def do_request(request)
|
120
|
+
prepare_headers(request)
|
121
|
+
response = http_or_https.request(request)
|
122
|
+
case response
|
123
|
+
when Net::HTTPNotFound then
|
124
|
+
raise NotFound, parse(:message, response)
|
125
|
+
when Net::HTTPBadRequest then
|
126
|
+
raise Invalid, parse(:message, response)
|
127
|
+
when Net::HTTPConflict then
|
128
|
+
raise ConflictError, parse(:message, response)
|
129
|
+
else
|
130
|
+
parse(:user, response) or parse(:users, response)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# Generate a new HTTP or HTTPS connection based on initialization parameters.
|
135
|
+
#
|
136
|
+
# @return [Net::HTTP] A HTTP or HTTPS (not done yet) connection ready to use.
|
137
|
+
#
|
138
|
+
def http_or_https
|
139
|
+
if @ssl
|
140
|
+
#TODO: ssl connection
|
141
|
+
#https://github.com/augustl/net-http-cheat-sheet/blob/master/ssl_and_https.rb
|
142
|
+
else
|
143
|
+
Net::HTTP.new(@host, @port)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|