midb 1.1.1 → 2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d5ef9483dfada39d3e9f8bccc1c44e1e418a49b1
4
- data.tar.gz: 954831b1336e3d3402279f2d5b1cd49f99850d50
3
+ metadata.gz: 644f4e1de9aa34f81c03df7a4632ed0c809c2347
4
+ data.tar.gz: ba52f668994e64f7425ed9d001dd86083c3dec7e
5
5
  SHA512:
6
- metadata.gz: 50e11179acc6827bb9fc7ece5f5ecaf4b7938dac3838d82601d2c496a62fd5f6f4fa00fbab474c927a1c925e7aec4ede32603542256627c806e020c7d8010bbb
7
- data.tar.gz: e148bf8571b0f54652a79945f92df92ebde4ab67e3898acc4541f1c34342b93fb6fa9d20f8d006e3eb9f44a4f0a9145293e1706e227f993dcceed68d1d50aee5
6
+ metadata.gz: d05f26de94df71818137d79bef6cb23b79e9ad68d4fa3c853494959d75e670177d8b0065ae24a34fd767c145a5066df37548a666c912f58dfd54d12ff540063a
7
+ data.tar.gz: 82b54f559776bcb5a7dc5bc19a437e8a804c12703cde686bdc57f37048d85a70ea0e8ab0967ea954e96d7905f5b6270455cbf1a02641cdfa0b1f59467daf2cd6
@@ -1,6 +1,6 @@
1
1
  require 'sqlite3'
2
2
  require 'mysql2'
3
-
3
+ require 'midb/errors_view'
4
4
  module MIDB
5
5
  module API
6
6
  # @author unrar
@@ -38,14 +38,19 @@ module MIDB
38
38
  #
39
39
  # @return [SQLite3::Database, Mysql2::Client] A resource referencing to the database
40
40
  def connect()
41
- # Connect to an SQLite3 database
42
- if @engine == :sqlite3
43
- sq = SQLite3::Database.open("./db/#{@db}.db")
44
- sq.results_as_hash = true
45
- return sq
46
- # Connect to a MySQL database
47
- elsif @engine == :mysql
48
- return Mysql2::Client.new(:host => @host, :username => @uname, :password => @pwd, :database => @db)
41
+ begin
42
+ # Connect to an SQLite3 database
43
+ if @engine == :sqlite3
44
+ sq = SQLite3::Database.open("./db/#{@db}.db")
45
+ sq.results_as_hash = true
46
+ return sq
47
+ # Connect to a MySQL database
48
+ elsif @engine == :mysql
49
+ return Mysql2::Client.new(:host => @host, :username => @uname, :password => @pwd, :database => @db)
50
+ end
51
+ rescue
52
+ MIDB::Interface::Errors.exception(:database_error, $!)
53
+ return false
49
54
  end
50
55
  end
51
56
 
@@ -56,10 +61,15 @@ module MIDB
56
61
  #
57
62
  # @return [Array, Hash] Returns an array of hashes for SQLite3 or a hash for MySQL
58
63
  def query(res, query)
59
- if @engine == :sqlite3
60
- return res.execute(query)
61
- elsif @engine == :mysql
62
- return res.query(query)
64
+ begin
65
+ if @engine == :sqlite3
66
+ return res.execute(query)
67
+ elsif @engine == :mysql
68
+ return res.query(query)
69
+ end
70
+ rescue
71
+ MIDB::Interface::Errors.exception(:query_error, $!)
72
+ return false
63
73
  end
64
74
  end
65
75
 
@@ -25,6 +25,14 @@ module MIDB
25
25
  end
26
26
  abort("Fatal error: #{errmsg}")
27
27
  end
28
+ def self.exception(exc, more="")
29
+ excmsg = case exc
30
+ when :database_error then "An error occurred when trying to connect to the database. #{more}"
31
+ when :query_error then "An error occurred when trying to query the database. #{more}"
32
+ else "Unknown exception: #{exc.to_s} #{more}"
33
+ end
34
+ puts "(exception)\t#{excmsg}"
35
+ end
28
36
  end
29
37
  end
30
38
  end
@@ -31,6 +31,19 @@ module MIDB
31
31
  # @return [Object] MIDB::API::Hooks instance
32
32
  attr_accessor :args, :config, :db, :http_status, :port, :hooks
33
33
 
34
+ # Convert an on/off string to a boolean
35
+ #
36
+ # @param text [String] Text to convert to bool
37
+ def to_bool(text)
38
+ if text == "on" || text == "true" || text == "yes"
39
+ return true
40
+ elsif text == "off" || text == "false" || text == "no"
41
+ return false
42
+ else
43
+ return nil
44
+ end
45
+ end
46
+
34
47
  # Constructor for this controller.
35
48
  #
36
49
  # @param args [Array<String>] Arguments passed to the binary.
@@ -92,12 +105,20 @@ module MIDB
92
105
  @config["serves"] = []
93
106
  @config["status"] = :asleep # The server is initially asleep
94
107
  @config["apikey"] = "midb-api" # This should be changed, it's the private API key
108
+ # Optional API key for GET methods (void by default)
109
+ @config["apigetkey"] = nil
95
110
  @config["dbengine"] = :sqlite3 # SQLite is the default engine
96
111
  # Default DB configuration for MySQL and other engines
97
112
  @config["dbhost"] = "localhost"
98
113
  @config["dbport"] = 3306
99
114
  @config["dbuser"] = "nobody"
100
115
  @config["dbpassword"] = "openaccess"
116
+ # New settings - privacy
117
+ @config["privacyget"] = false
118
+ @config["privacypost"] = true
119
+ @config["privacyput"] = true
120
+ @config["privacydelete"] = true
121
+
101
122
  File.open(".midb.yaml", 'w') do |l|
102
123
  l.write @config.to_yaml
103
124
  end
@@ -155,6 +176,30 @@ module MIDB
155
176
  when "key"
156
177
  @config["apikey"] = setter if set
157
178
  MIDB::Interface::Server.out_config(:apikey, @config)
179
+ when "getkey"
180
+ if setter == "nil"
181
+ @config["apigetkey"] = nil if set
182
+ else
183
+ @config["apigetkey"] = setter if set
184
+ end
185
+ MIDB::Interface::Server.out_config(:apigetkey, @config)
186
+ end
187
+ when "privacy"
188
+ case subcmd
189
+ when "get"
190
+ @config["privacyget"] = self.to_bool(setter) if set
191
+ MIDB::Interface::Server.out_config(:privacyget, @config)
192
+ when "post"
193
+ @config["privacypost"] = self.to_bool(setter) if set
194
+ MIDB::Interface::Server.out_config(:privacypost, @config)
195
+ when "put"
196
+ @config["privacyput"] = self.to_bool(setter) if set
197
+ MIDB::Interface::Server.out_config(:privacyput, @config)
198
+ when "delete"
199
+ @config["privacydelete"] = self.to_bool(setter) if set
200
+ MIDB::Interface::Server.out_config(:privacydelete, @config)
201
+ else
202
+ MIDB::Interface::Errors.die(:syntax)
158
203
  end
159
204
  else
160
205
  MIDB::Interface::Errors.die(:syntax)
@@ -247,10 +247,12 @@ module MIDB
247
247
  dbe = MIDB::API::Dbengine.new(@engine.config, @db)
248
248
  dblink = dbe.connect()
249
249
  rows = dbe.query(dblink, "SELECT * FROM #{self.get_structure.values[0].split('/')[0]} WHERE id=#{id};")
250
+ if rows == false
251
+ return MIDB::Interface::Server.json_error(400, "Bad Request")
252
+ end
250
253
  if dbe.length(rows) > 0
251
254
  rows.each do |row|
252
255
  jso[row["id"]] = self.get_structure
253
-
254
256
  self.get_structure.each do |name, dbi|
255
257
  table = dbi.split("/")[0]
256
258
  field = dbi.split("/")[1]
@@ -263,6 +265,9 @@ module MIDB
263
265
  else
264
266
  query = dbe.query(dblink, "SELECT #{field} from #{table} WHERE id=#{row['id']};")
265
267
  end
268
+ if query == false
269
+ return MIDB::Interface::Server.json_error(400, "Bad Request")
270
+ end
266
271
  jso[row["id"]][name] = dbe.length(query) > 0 ? dbe.extract(query,field) : "unknown"
267
272
  jso[row["id"]][name] = @hooks.format_field(name, jso[row["id"]][name])
268
273
  end
@@ -284,10 +289,124 @@ module MIDB
284
289
  dbe = MIDB::API::Dbengine.new(@engine.config, @db)
285
290
  dblink = dbe.connect()
286
291
  rows = dbe.query(dblink, "SELECT * FROM #{self.get_structure.values[0].split('/')[0]};")
292
+ if rows == false
293
+ return MIDB::Interface::Server.json_error(400, "Bad Request")
294
+ end
295
+ # Iterate over all rows of this table
296
+ rows.each do |row|
297
+ jso[row["id"]] = self.get_structure
298
+ self.get_structure.each do |name, dbi|
299
+ table = dbi.split("/")[0]
300
+ field = dbi.split("/")[1]
301
+ # Must-match relations ("table2/field/table2-field->row-field")
302
+ if dbi.split("/").length > 2
303
+ match = dbi.split("/")[2]
304
+ matching_field = match.split("->")[0]
305
+ row_field = match.split("->")[1]
306
+ query = dbe.query(dblink, "SELECT #{field} FROM #{table} WHERE #{matching_field}=#{row[row_field]};")
307
+ else
308
+ query = dbe.query(dblink, "SELECT #{field} from #{table} WHERE id=#{row['id']};")
309
+ end
310
+ if query == false
311
+ return MIDB::Interface::Server.json_error(400, "Bad Request")
312
+ end
313
+ jso[row["id"]][name] = dbe.length(query) > 0 ? dbe.extract(query,field) : "unknown"
314
+ jso[row["id"]][name] = @hooks.format_field(name, jso[row["id"]][name])
315
+ end
316
+ end
317
+ @hooks.after_get_all_entries(dbe.length(rows))
318
+ return jso
319
+ end
320
+
321
+
322
+ # Get all the entries from the database belonging to a column
323
+ def get_column_entries(column)
324
+ jso = Hash.new()
325
+ jss = self.get_structure()
326
+ db_column = nil
327
+ # Is the column recognized?
328
+ if jss.has_key? column then
329
+ db_column = jss[column].split("/")[1]
330
+ else
331
+ return MIDB::Interface::Server.json_error(400, "Bad Request")
332
+ end
333
+
334
+ # Connect to database
335
+ dbe = MIDB::API::Dbengine.new(@engine.config, @db)
336
+ dblink = dbe.connect()
337
+ rows = dbe.query(dblink, "SELECT * FROM #{self.get_structure.values[0].split('/')[0]};")
338
+ if rows == false
339
+ return MIDB::Interface::Server.json_error(400, "Bad Request")
340
+ end
341
+ # Iterate over all rows of this table
342
+ rows.each do |row|
343
+
344
+ name = column
345
+ dbi = jss[name]
346
+ table = dbi.split("/")[0]
347
+ field = dbi.split("/")[1]
348
+ # Must-match relations ("table2/field/table2-field->row-field")
349
+ if dbi.split("/").length > 2
350
+ match = dbi.split("/")[2]
351
+ matching_field = match.split("->")[0]
352
+ row_field = match.split("->")[1]
353
+ query = dbe.query(dblink, "SELECT #{field} FROM #{table} WHERE #{matching_field}=#{row[row_field]};")
354
+ else
355
+ query = dbe.query(dblink, "SELECT #{field} from #{table} WHERE id=#{row['id']};")
356
+ end
357
+ if query == false
358
+ return MIDB::Interface::Server.json_error(400, "Bad Request")
359
+ end
360
+ jso[row["id"]] = {}
361
+ jso[row["id"]][name] = dbe.length(query) > 0 ? dbe.extract(query,field) : "unknown"
362
+ jso[row["id"]][name] = @hooks.format_field(name, jso[row["id"]][name])
363
+ end
364
+ @hooks.after_get_all_entries(dbe.length(rows))
365
+ return jso
366
+ end
287
367
 
368
+ # Get all the entries from the database belonging to a column matching a pattern
369
+ def get_matching_rows(column, pattern)
370
+ jso = Hash.new()
371
+ jss = self.get_structure()
372
+ db_column = nil
373
+ # Is the column recognized?
374
+ if jss.has_key? column then
375
+ db_column = jss[column].split("/")[1]
376
+ else
377
+ return MIDB::Interface::Server.json_error(400, "Bad Request")
378
+ end
379
+
380
+ # Connect to database
381
+ dbe = MIDB::API::Dbengine.new(@engine.config, @db)
382
+ dblink = dbe.connect()
383
+ rows = dbe.query(dblink, "SELECT * FROM #{self.get_structure.values[0].split('/')[0]};")
384
+ if rows == false
385
+ return MIDB::Interface::Server.json_error(400, "Bad Request")
386
+ end
288
387
  # Iterate over all rows of this table
289
388
  rows.each do |row|
290
- # Replace the "id" in the given JSON with the actual ID and expand it with the fields
389
+ # Does this row match?
390
+ bufd = jss[column]
391
+ b_table = bufd.split("/")[0]
392
+ b_field = bufd.split("/")[1]
393
+ # The column is in another table, let's find it
394
+ if bufd.split("/").length > 2
395
+ b_match = bufd.split("/")[2]
396
+ b_m_field = b_match.split("->")[0]
397
+ b_r_field = b_match.split("->")[1]
398
+
399
+ bquery = dbe.query(dblink, "SELECT #{b_field} FROM #{b_table} WHERE (#{b_m_field}=#{row[b_r_field]} AND #{db_column} LIKE '%#{pattern}%');")
400
+ else
401
+ # It's in the same main table, let's see if it matches
402
+ bquery = dbe.query(dblink, "SELECT #{b_field} FROM #{b_table} WHERE (id=#{row['id']} AND #{db_column} LIKE '%#{pattern}%');")
403
+ end
404
+
405
+ # Unless the query has been successful (thus this row matches), skip to the next row
406
+ unless dbe.length(bquery) > 0
407
+ next
408
+ end
409
+
291
410
  jso[row["id"]] = self.get_structure
292
411
 
293
412
  self.get_structure.each do |name, dbi|
@@ -302,11 +421,14 @@ module MIDB
302
421
  else
303
422
  query = dbe.query(dblink, "SELECT #{field} from #{table} WHERE id=#{row['id']};")
304
423
  end
424
+ if query == false
425
+ next
426
+ end
305
427
  jso[row["id"]][name] = dbe.length(query) > 0 ? dbe.extract(query,field) : "unknown"
306
428
  jso[row["id"]][name] = @hooks.format_field(name, jso[row["id"]][name])
307
429
  end
308
430
  end
309
- @hooks.after_get_all_entries(rows.length)
431
+ @hooks.after_get_all_entries(dbe.length(rows))
310
432
  return jso
311
433
  end
312
434
  end
@@ -47,6 +47,7 @@ module MIDB
47
47
  when :no_auth then ">> No authentication header - sending a 401 error."
48
48
  when :auth_success then ">> Successfully authenticated the request."
49
49
  when :bootstrap then "> Successfully bootstraped!"
50
+ when :fetch then ">> Fetching response... [#{info}]"
50
51
  end
51
52
  puts msg
52
53
  end
@@ -56,16 +57,11 @@ module MIDB
56
57
  # @param what [Symbol] What to show the config for.
57
58
  # @param cnf [Array<String>] The array for the config.
58
59
  def self.out_config(what, cnf)
59
- msg = case what
60
- when :dbengine then "Database engine: #{cnf['dbengine']}."
61
- when :dbhost then "Database server host: #{cnf['dbhost']}."
62
- when :dbport then "Database server port: #{cnf['dbport']}."
63
- when :dbuser then "Database server user: #{cnf['dbuser']}."
64
- when :dbpassword then "Database server password: #{cnf['dbpassword']}."
65
- when :apikey then "Private API key: #{cnf['apikey']}"
66
- else "Error??"
67
- end
68
- puts msg
60
+ if cnf.has_key? what.to_s
61
+ puts "#{what.to_s} set to #{cnf[what.to_s]}"
62
+ else
63
+ puts "Unknown setting."
64
+ end
69
65
  end
70
66
 
71
67
  # Shows the help
@@ -27,6 +27,12 @@ module MIDB
27
27
  # @return [Object] MIDB::API::Hooks instance
28
28
  attr_accessor :config, :db, :http_status, :hooks
29
29
 
30
+ # Handle an unauthorized request
31
+ def unauth_request
32
+ @http_status = "401 Unauthorized"
33
+ MIDB::Interface::Server.info(:no_auth)
34
+ MIDB::Interface::Server.json_error(401, "Unauthorized").to_json
35
+ end
30
36
 
31
37
  # Constructor
32
38
  #
@@ -72,7 +78,11 @@ module MIDB
72
78
 
73
79
  # Endpoint syntax: ["", FILE, ID, (ACTION)]
74
80
  endpoint = request[1].split("/")
75
- ep_file = endpoint[1]
81
+ if endpoint.length >= 2
82
+ ep_file = endpoint[1].split("?")[0]
83
+ else
84
+ ep_file = ""
85
+ end
76
86
 
77
87
  method = request[0]
78
88
  endpoints = [] # Valid endpoints
@@ -92,35 +102,74 @@ module MIDB
92
102
  # Create the model
93
103
  dbop = MIDB::API::Model.new(ep, @db, self)
94
104
  # Analyze the request and pass it to the model
95
- if method == "GET"
96
- case endpoint.length
97
- when 2
98
- # No ID has been specified. Return all the entries
99
- # Pass it to the model and get the JSON
100
- response_json = dbop.get_all_entries().to_json
101
- when 3
102
- # An ID has been specified. Should it exist, return all of its entries.
103
- response_json = dbop.get_entries(endpoint[2]).to_json
104
- end
105
+ # Is the method accepted?
106
+ accepted_methods = ["GET", "POST", "PUT", "DELETE"]
107
+ unless accepted_methods.include? method
108
+ @http_status = "405 Method Not Allowed"
109
+ response_json = MIDB::Interface::Server.json_error(405, "Method Not Allowed").to_json
105
110
  else
106
- # An action has been specified. We're going to need HTTP authentification here.
107
- MIDB::Interface::Server.info(:auth_required)
108
-
109
- if (not headers.has_key? "Authentication") ||
110
- (not MIDB::API::Security.check?(headers["Authentication"], data, @config["apikey"]))
111
- @http_status = "401 Unauthorized"
112
- response_json = MIDB::Interface::Server.json_error(401, "Unauthorized").to_json
113
- MIDB::Interface::Server.info(:no_auth)
111
+ # Do we need authentication?
112
+ auth_req = false
113
+ unauthenticated = false
114
+ if @config["privacy#{method.downcase}"] == true
115
+ MIDB::Interface::Server.info(:auth_required)
116
+ auth_req = true
117
+
118
+ # For GET and DELETE requests, the object of the digest is the endpoint
119
+ if (method == "GET") || (method == "DELETE")
120
+ data = ep_file
121
+ end
114
122
 
123
+ # If it's a GET request and we have a different key for GET methods...
124
+ if (@config["apigetkey"] != nil) && (method == "GET")
125
+ unauthenticated = (not headers.has_key? "Authentication") ||
126
+ (not MIDB::API::Security.check?(headers["Authentication"], data, @config["apigetkey"]))
127
+ else
128
+ unauthenticated = (not headers.has_key? "Authentication") ||
129
+ (not MIDB::API::Security.check?(headers["Authentication"], data, @config["apikey"]))
130
+ end
131
+ end
132
+ # Proceed to handle the request
133
+ if unauthenticated
134
+ response_json = self.unauth_request
135
+ puts ">> has header: #{headers.has_key? "Authentication"}"
115
136
  else
116
- MIDB::Interface::Server.info(:auth_success)
117
- if method == "POST"
137
+ MIDB::Interface::Server.info(:auth_success) if (not unauthenticated) && auth_req
138
+ if method == "GET"
139
+ case endpoint.length
140
+ when 2
141
+ # No ID has been specified. Return all the entries
142
+ # Pass it to the model and get the JSON
143
+ MIDB::Interface::Server.info(:fetch, "get_all_entries()")
144
+ response_json = dbop.get_all_entries().to_json
145
+ when 3
146
+ # This regular expression checks if it contains an integer
147
+ if /\A[-+]?\d+\z/ === endpoint[2]
148
+ # An ID has been specified. Should it exist, return all of its entries.
149
+ MIDB::Interface::Server.info(:fetch, "get_entries(#{endpoint[2]})")
150
+ response_json = dbop.get_entries(endpoint[2].to_i).to_json
151
+ else
152
+ # A row has been specified, but no pattern
153
+ MIDB::Interface::Server.info(:fetch, "get_column_entries(#{endpoint[2]})")
154
+ response_json = dbop.get_column_entries(endpoint[2]).to_json
155
+ end
156
+ when 4
157
+ if (endpoint[2].is_a? String) && (endpoint[3].is_a? String) then
158
+ # A row and a pattern have been specified
159
+ MIDB::Interface::Server.info(:fetch, "get_matching_rows(#{endpoint[2]}, #{endpoint[3]})")
160
+ response_json = dbop.get_matching_rows(endpoint[2], endpoint[3]).to_json
161
+ end
162
+ end
163
+ elsif method == "POST"
164
+ MIDB::Interface::Server.info(:fetch, "post(#{data})")
118
165
  response_json = dbop.post(data).to_json
119
166
  else
120
167
  if endpoint.length >= 3
121
168
  if method == "DELETE"
169
+ MIDB::Interface::Server.info(:fetch, "delete(#{endpoint[2]})")
122
170
  response_json = dbop.delete(endpoint[2]).to_json
123
171
  elsif method == "PUT"
172
+ MIDB::Interface::Server.info(:fetch, "put(#{endpoint[2]}, data)")
124
173
  response_json = dbop.put(endpoint[2], data).to_json
125
174
  end
126
175
  else
@@ -129,7 +178,7 @@ module MIDB
129
178
  end
130
179
  end
131
180
  end
132
- end
181
+ end
133
182
  MIDB::Interface::Server.info(:response, response_json)
134
183
  # Return the results via HTTP
135
184
  socket.print "HTTP/1.1 #{@http_status}\r\n" +
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: midb
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.1
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - unrar
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-11-20 00:00:00.000000000 Z
11
+ date: 2016-03-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: mysql2