dbi-dbrc 1.2.0 → 1.6.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.
data/Rakefile CHANGED
@@ -1,24 +1,16 @@
1
1
  require 'rake'
2
2
  require 'rake/clean'
3
- require 'rake/testtask'
3
+ require 'rspec/core/rake_task'
4
4
 
5
- CLEAN.include("**/*.gem", "**/*.rbc")
5
+ CLEAN.include("**/*.gem", "**/*.rbc", "**/*.lock")
6
6
 
7
7
  namespace :gem do
8
- desc "Remove any gem files."
9
- task :clean do
10
- Dir['*.gem'].each{ |f| File.delete(f) }
11
- end
12
-
13
8
  desc "Create the dbi-dbrc gem"
14
9
  task :create => [:clean] do
15
- spec = eval(IO.read('dbi-dbrc.gemspec'))
16
- if Gem::VERSION < "2.0"
17
- Gem::Builder.new(spec).build
18
- else
19
- require 'rubygems/package'
20
- Gem::Package.build(spec)
21
- end
10
+ require 'rubygems/package'
11
+ spec = Gem::Specification.load('dbi-dbrc.gemspec')
12
+ spec.signing_key = File.join(Dir.home, '.ssh', 'gem-private_key.pem')
13
+ Gem::Package.build(spec)
22
14
  end
23
15
 
24
16
  desc "Install the dbi-dbrc gem"
@@ -28,23 +20,7 @@ namespace :gem do
28
20
  end
29
21
  end
30
22
 
31
- namespace :test do
32
- Rake::TestTask.new(:all) do |t|
33
- t.warning = true
34
- t.verbose = true
35
- end
36
-
37
- Rake::TestTask.new(:xml) do |t|
38
- t.warning = true
39
- t.verbose = true
40
- t.test_files = FileList['test/test_dbi_dbrc_xml.rb']
41
- end
42
-
43
- Rake::TestTask.new(:yml) do |t|
44
- t.warning = true
45
- t.verbose = true
46
- t.test_files = FileList['test/test_dbi_dbrc_yml.rb']
47
- end
48
- end
23
+ desc "Run the test suite"
24
+ RSpec::Core::RakeTask.new(:spec)
49
25
 
50
- task :default => 'test:all'
26
+ task :default => :spec
@@ -0,0 +1,26 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIEcDCCAtigAwIBAgIBATANBgkqhkiG9w0BAQsFADA/MREwDwYDVQQDDAhkamJl
3
+ cmc5NjEVMBMGCgmSJomT8ixkARkWBWdtYWlsMRMwEQYKCZImiZPyLGQBGRYDY29t
4
+ MB4XDTE4MDMxODE1MjIwN1oXDTI4MDMxNTE1MjIwN1owPzERMA8GA1UEAwwIZGpi
5
+ ZXJnOTYxFTATBgoJkiaJk/IsZAEZFgVnbWFpbDETMBEGCgmSJomT8ixkARkWA2Nv
6
+ bTCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBALgfaroVM6CI06cxr0/h
7
+ A+j+pc8fgpRgBVmHFaFunq28GPC3IvW7Nvc3Y8SnAW7pP1EQIbhlwRIaQzJ93/yj
8
+ u95KpkP7tA9erypnV7dpzBkzNlX14ACaFD/6pHoXoe2ltBxk3CCyyzx70mTqJpph
9
+ 75IB03ni9a8yqn8pmse+s83bFJOAqddSj009sGPcQO+QOWiNxqYv1n5EHcvj2ebO
10
+ 6hN7YTmhx7aSia4qL/quc4DlIaGMWoAhvML7u1fmo53CYxkKskfN8MOecq2vfEmL
11
+ iLu+SsVVEAufMDDFMXMJlvDsviolUSGMSNRTujkyCcJoXKYYxZSNtIiyd9etI0X3
12
+ ctu0uhrFyrMZXCedutvXNjUolD5r9KGBFSWH1R9u2I3n3SAyFF2yzv/7idQHLJJq
13
+ 74BMnx0FIq6fCpu5slAipvxZ3ZkZpEXZFr3cIBtO1gFvQWW7E/Y3ijliWJS1GQFq
14
+ 058qERadHGu1yu1dojmFRo6W2KZvY9al2yIlbkpDrD5MYQIDAQABo3cwdTAJBgNV
15
+ HRMEAjAAMAsGA1UdDwQEAwIEsDAdBgNVHQ4EFgQUFZsMapgzJimzsbaBG2Tm8j5e
16
+ AzgwHQYDVR0RBBYwFIESZGpiZXJnOTZAZ21haWwuY29tMB0GA1UdEgQWMBSBEmRq
17
+ YmVyZzk2QGdtYWlsLmNvbTANBgkqhkiG9w0BAQsFAAOCAYEAW2tnYixXQtKxgGXq
18
+ /3iSWG2bLwvxS4go3srO+aRXZHrFUMlJ5W0mCxl03aazxxKTsVVpZD8QZxvK91OQ
19
+ h9zr9JBYqCLcCVbr8SkmYCi/laxIZxsNE5YI8cC8vvlLI7AMgSfPSnn/Epq1GjGY
20
+ 6L1iRcEDtanGCIvjqlCXO9+BmsnCfEVehqZkQHeYczA03tpOWb6pon2wzvMKSsKH
21
+ ks0ApVdstSLz1kzzAqem/uHdG9FyXdbTAwH1G4ZPv69sQAFAOCgAqYmdnzedsQtE
22
+ 1LQfaQrx0twO+CZJPcRLEESjq8ScQxWRRkfuh2VeR7cEU7L7KqT10mtUwrvw7APf
23
+ DYoeCY9KyjIBjQXfbj2ke5u1hZj94Fsq9FfbEQg8ygCgwThnmkTrrKEiMSs3alYR
24
+ ORVCZpRuCPpmC8qmqxUnARDArzucjaclkxjLWvCVHeFa9UP7K3Nl9oTjJNv+7/jM
25
+ WZs4eecIcUc4tKdHxcAJ0MO/Dkqq7hGaiHpwKY76wQ1+8xAh
26
+ -----END CERTIFICATE-----
data/dbi-dbrc.gemspec CHANGED
@@ -2,21 +2,33 @@ require 'rubygems'
2
2
 
3
3
  Gem::Specification.new do |spec|
4
4
  spec.name = 'dbi-dbrc'
5
- spec.version = '1.2.0'
5
+ spec.version = '1.6.0'
6
6
  spec.author = 'Daniel Berger'
7
7
  spec.email = 'djberg96@gmail.com'
8
- spec.license = 'Artistic 2.0'
8
+ spec.license = 'Apache-2.0'
9
9
  spec.summary = 'A simple way to avoid hard-coding passwords with DBI'
10
10
  spec.homepage = 'https://github.com/djberg96/dbi-dbrc'
11
11
  spec.files = Dir['**/*'].reject{ |f| f.include?('git') }
12
12
  spec.test_files = Dir['test/test*.rb']
13
+ spec.cert_chain = Dir['certs/*']
13
14
 
14
- spec.extra_rdoc_files = ['README', 'CHANGES', 'MANIFEST']
15
+ spec.add_dependency('gpgme', '~> 2.0')
15
16
 
16
- spec.add_dependency('sys-admin')
17
- spec.add_development_dependency('test-unit')
17
+ spec.add_development_dependency('rake')
18
+ spec.add_development_dependency('rspec', '~> 3.9')
19
+ spec.add_development_dependency('fakefs', '~> 1.3')
20
+
21
+ spec.metadata = {
22
+ 'homepage_uri' => 'https://github.com/djberg96/dbi-dbrc',
23
+ 'bug_tracker_uri' => 'https://github.com/djberg96/dbi-dbrc/issues',
24
+ 'changelog_uri' => 'https://github.com/djberg96/dbi-dbrc/blob/main/CHANGES.md',
25
+ 'documentation_uri' => 'https://github.com/djberg96/dbi-dbrc/wiki',
26
+ 'source_code_uri' => 'https://github.com/djberg96/dbi-dbrc',
27
+ 'wiki_uri' => 'https://github.com/djberg96/dbi-dbrc/wiki'
28
+ }
18
29
 
19
30
  if File::ALT_SEPARATOR
31
+ spec.add_dependency('sys-admin')
20
32
  spec.add_dependency('win32-file-attributes')
21
33
  spec.add_dependency('win32-dir')
22
34
  spec.add_dependency('win32-process')
@@ -1,12 +1,12 @@
1
1
  #######################################################################
2
- # test_xml.rb
2
+ # test_yml.rb
3
3
  #
4
- # Simple test script that uses the DBRC::XML subclass.
4
+ # Simple test script that uses the DBRC::YML subclass.
5
5
  #######################################################################
6
6
  if File.basename(Dir.pwd) == "yml"
7
- Dir.chdir "../.."
8
- $LOAD_PATH.unshift Dir.pwd + "/lib"
9
- Dir.chdir "examples/yml"
7
+ Dir.chdir "../.."
8
+ $LOAD_PATH.unshift Dir.pwd + "/lib"
9
+ Dir.chdir "examples/yml"
10
10
  end
11
11
 
12
12
  require "dbi/dbrc"
data/lib/dbi-dbrc.rb ADDED
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'dbi/dbrc'
data/lib/dbi/dbrc.rb CHANGED
@@ -1,34 +1,43 @@
1
+ # frozen_string_literal: true
2
+
1
3
  if File::ALT_SEPARATOR
2
4
  require 'win32/dir'
3
5
  require 'win32/file/attributes'
4
6
  require 'win32/process'
7
+ else
8
+ require 'etc'
9
+ require 'gpgme'
5
10
  end
6
11
 
7
- require 'sys/admin'
8
-
9
12
  # The DBI module serves as a namespace only.
10
13
  module DBI
11
-
12
14
  # The DBRC class encapsulates a database resource config file.
13
15
  class DBRC
14
-
15
16
  # This error is raised if anything fails trying to read the config file.
16
17
  class Error < StandardError; end
17
18
 
18
19
  # The version of the dbi-dbrc library
19
- VERSION = '1.2.0'
20
+ VERSION = '1.6.0'
20
21
 
21
- @@windows = File::ALT_SEPARATOR
22
+ WINDOWS = File::ALT_SEPARATOR # :no-doc:
22
23
 
23
24
  # The database or host to be connected to.
24
25
  attr_accessor :database
25
26
 
27
+ alias db database
28
+ alias db= database=
29
+ alias host database
30
+ alias host= database=
31
+
26
32
  # The user name used for the database or host connection.
27
33
  attr_accessor :user
28
34
 
29
35
  # The password associated with the database or host.
30
36
  attr_accessor :password
31
37
 
38
+ alias passwd password
39
+ alias passwd= password=
40
+
32
41
  # The driver associated with the database. This is used to internally to
33
42
  # construct the DSN.
34
43
  attr_accessor :driver
@@ -39,9 +48,15 @@ module DBI
39
48
  # The maximum number of reconnects a program should make before giving up.
40
49
  attr_accessor :maximum_reconnects
41
50
 
51
+ alias max_reconn maximum_reconnects
52
+ alias max_reconn= maximum_reconnects=
53
+
42
54
  # The timeout, in seconds, for each connection attempt.
43
55
  attr_accessor :timeout
44
56
 
57
+ alias time_out timeout
58
+ alias time_out= timeout=
59
+
45
60
  # The interval, in seconds, between each connection attempt.
46
61
  attr_accessor :interval
47
62
 
@@ -62,7 +77,7 @@ module DBI
62
77
  # file.
63
78
  #
64
79
  # If an entry cannot be found for the database, or database plus user
65
- # combination, then a Error is raised. If the .dbrc file cannot be
80
+ # combination, then a Error is raised. If the .dbrc file cannot be
66
81
  # found, or is setup improperly with regards to permissions or properties
67
82
  # then a DBI::DBRC::Error is raised.
68
83
  #
@@ -72,6 +87,9 @@ module DBI
72
87
  # storage mechanism. In that case simply treat the 'database' as the
73
88
  # host name, and ignore the DBI::DBRC#dsn and DBI::DBRC#driver methods.
74
89
  #
90
+ # On unixy systems you can GPG encrypt the file, and this library will
91
+ # decrypt it for you based on the +gpg_options+ that you pass.
92
+ #
75
93
  # Examples:
76
94
  #
77
95
  # # Find the first match for 'some_database'
@@ -83,23 +101,15 @@ module DBI
83
101
  # # Find the first match for 'foo_user@some_database' under /usr/local
84
102
  # DBI::DBRC.new('some_database', 'foo_usr', '/usr/local')
85
103
  #
86
- def initialize(database, user=nil, dbrc_dir=nil)
104
+ # # Pass along a GPG password to decrypt the file.
105
+ # DBI::DBRC.new('some_database', 'foo_usr', '/usr/local', :gpg_options => {:password => 'xxx'})
106
+ #
107
+ def initialize(database, user = nil, dbrc_dir = Dir.home, gpg_options = nil)
87
108
  if dbrc_dir.nil?
88
- uid = Process.uid
89
- home = ENV['HOME'] || ENV['USERPROFILE']
90
-
91
- if home.nil?
92
- if @@windows
93
- home ||= Sys::Admin.get_user(uid, :localaccount => true).dir
94
- else
95
- home ||= Sys::Admin.get_user(uid).dir
96
- end
97
- end
98
-
99
109
  # Default to the app data directory on Windows, or root on Unix, if
100
110
  # no home dir can be found.
101
111
  if home.nil?
102
- if @@windows
112
+ if WINDOWS
103
113
  home = Dir::APPDATA
104
114
  else
105
115
  home = '/'
@@ -107,6 +117,7 @@ module DBI
107
117
  end
108
118
 
109
119
  @dbrc_file = File.join(home, '.dbrc')
120
+ dbrc_dir = home
110
121
  else
111
122
  raise Error, 'bad directory' unless File.directory?(dbrc_dir)
112
123
  @dbrc_file = File.join(dbrc_dir, '.dbrc')
@@ -128,19 +139,26 @@ module DBI
128
139
  # Decrypt and re-encrypt the file if we're on MS Windows and the
129
140
  # file is encrypted.
130
141
  begin
131
- if @@windows && File.encrypted?(@dbrc_file)
142
+ if WINDOWS && File.encrypted?(@dbrc_file)
132
143
  file_was_encrypted = true
133
144
  File.decrypt(@dbrc_file)
134
145
  end
135
146
 
136
- parse_dbrc_config_file()
137
- validate_data()
138
- convert_numeric_strings()
139
- create_dsn_string()
140
- ensure
141
- if @@windows && file_was_encrypted
142
- File.encrypt(@dbrc_file)
147
+ if gpg_options
148
+ require 'gpgme'
149
+ require 'stringio'
150
+ crypto = GPGME::Crypto.new(gpg_options)
151
+ stringio = crypto.decrypt(File.open(@dbrc_file))
152
+ parse_dbrc_config_file(StringIO.new(stringio.read))
153
+ else
154
+ parse_dbrc_config_file
143
155
  end
156
+
157
+ validate_data
158
+ convert_numeric_strings
159
+ create_dsn_string
160
+ ensure
161
+ File.encrypt(@dbrc_file) if WINDOWS && file_was_encrypted
144
162
  end
145
163
  end
146
164
 
@@ -148,28 +166,23 @@ module DBI
148
166
  # Ruby Object#inspect, except that the password field is filtered.
149
167
  #
150
168
  def inspect
151
- str = instance_variables.map{ |iv|
169
+ str = instance_variables.map do |iv|
152
170
  if iv == '@password'
153
171
  "#{iv}=[FILTERED]"
154
172
  else
155
173
  "#{iv}=#{instance_variable_get(iv).inspect}"
156
174
  end
157
- }.join(', ')
175
+ end.join(', ')
158
176
 
159
- "#<#{self.class}:0x#{(self.object_id*2).to_s(16)} " << str << ">"
177
+ "#<#{self.class}:0x#{(object_id * 2).to_s(16)} " + str + '>'
160
178
  end
161
179
 
162
180
  private
163
181
 
164
182
  # Ensure that the user/password has been set
165
183
  def validate_data
166
- unless @user
167
- raise Error, "no user found associated with #{@database}"
168
- end
169
-
170
- unless @password
171
- raise Error, "password not defined for #{@user}@#{@database}"
172
- end
184
+ raise Error, "no user found associated with #{@database}" unless @user
185
+ raise Error, "password not defined for #{@user}@#{@database}" unless @password
173
186
  end
174
187
 
175
188
  # Converts strings that should be numbers into actual numbers
@@ -185,39 +198,33 @@ module DBI
185
198
  end
186
199
 
187
200
  # Check ownership and permissions
188
- def check_file(file=@dbrc_file)
189
- File.open(file){ |f|
201
+ def check_file(file = @dbrc_file)
202
+ File.open(file) do |f|
190
203
  # Permissions must be set to 600 or better on Unix systems.
191
204
  # Must be hidden on Win32 systems.
192
- if @@windows
193
- unless File.hidden?(file)
194
- raise Error, "The .dbrc file must be hidden"
195
- end
205
+ if WINDOWS
206
+ raise Error, 'The .dbrc file must be hidden' unless File.hidden?(file)
196
207
  else
197
- unless (f.stat.mode & 077) == 0
198
- raise Error, "Bad .dbrc file permissions"
199
- end
208
+ raise Error, 'Bad .dbrc file permissions' unless (f.stat.mode & 0o77) == 0
200
209
  end
201
210
 
202
211
  # Only the owner may use it
203
- unless f.stat.owned?
204
- raise Error, "Not owner of .dbrc file"
205
- end
206
- }
212
+ raise Error, 'Not owner of .dbrc file' unless f.stat.owned?
213
+ end
207
214
  end
208
215
 
209
216
  # Parse the text out of the .dbrc file. This is the only method you
210
217
  # need to redefine if writing your own config handler.
211
- def parse_dbrc_config_file(file=@dbrc_file)
212
- IO.foreach(file){ |line|
213
- next if line =~ /^#/ # Ignore comments
218
+ def parse_dbrc_config_file(file = @dbrc_file)
219
+ begin
220
+ fh = file.is_a?(StringIO) ? file : File.open(file)
221
+
222
+ fh.each_line do |line|
223
+ next if line =~ /^#/ # Ignore comments
214
224
  db, user, pwd, driver, timeout, max, interval = line.split
215
225
 
216
226
  next unless @database == db
217
-
218
- if @user
219
- next unless @user == user
220
- end
227
+ next if @user && @user != user
221
228
 
222
229
  @user = user
223
230
  @password = pwd
@@ -225,83 +232,75 @@ module DBI
225
232
  @timeout = timeout
226
233
  @maximum_reconnects = max
227
234
  @interval = interval
228
- return
229
- }
230
-
231
- # If we reach here it means the database and/or user wasn't found
232
- if @user
233
- err = "no record found for #{@user}@#{@database}"
234
- else
235
- err = "no record found for #{@database}"
236
- end
235
+ break
236
+ end
237
+ ensure
238
+ fh.close if fh && fh.respond_to?(:close)
239
+ end
237
240
 
238
- raise Error, err
241
+ if @user
242
+ raise Error, "no record found for #{@user}@#{@database}" unless @user
243
+ else
244
+ raise Error, "no record found for #{@database}" unless @database
245
+ end
239
246
  end
240
-
241
- alias_method(:db, :database)
242
- alias_method(:db=, :database=)
243
- alias_method(:passwd, :password)
244
- alias_method(:passwd=, :password=)
245
- alias_method(:max_reconn, :maximum_reconnects)
246
- alias_method(:max_reconn=, :maximum_reconnects=)
247
- alias_method(:time_out, :timeout)
248
- alias_method(:time_out=, :timeout=)
249
- alias_method(:host, :database)
250
247
  end
251
248
 
252
249
  # A subclass of DBRC designed to handle .dbrc files in XML format. The
253
250
  # public methods of this class are identical to DBRC.
254
- class XML < DBRC
255
- require "rexml/document"
256
- include REXML
251
+ class DBRC::XML < DBRC
252
+ require 'rexml/document' # Good enough for small files
257
253
 
258
254
  private
259
255
 
260
- def parse_dbrc_config_file(file=@dbrc_file)
261
- doc = Document.new(File.new(file))
262
- fields = %w/user password driver interval timeout maximum_reconnects/
263
- doc.elements.each("/dbrc/database"){ |element|
264
- next unless element.attributes["name"] == database
265
- if @user
266
- next unless element.elements["user"].text == @user
267
- end
268
- fields.each{ |field|
256
+ def parse_dbrc_config_file(file = @dbrc_file)
257
+ file = file.is_a?(StringIO) ? file : File.new(file)
258
+ doc = REXML::Document.new(file)
259
+
260
+ fields = %w[user password driver interval timeout maximum_reconnects]
261
+
262
+ doc.elements.each('/dbrc/database') do |element|
263
+ next unless element.attributes['name'] == database
264
+ next if @user && @user != element.elements['user'].text
265
+
266
+ fields.each do |field|
269
267
  val = element.elements[field]
270
- unless val.nil?
271
- send("#{field}=",val.text)
272
- end
273
- }
274
- return
275
- }
276
- # If we reach here it means the database and/or user wasn't found
277
- raise Error, "No record found for #{@user}@#{@database}"
268
+ send("#{field}=", val.text) unless val.nil?
269
+ end
270
+
271
+ break
272
+ end
273
+
274
+ raise Error, "No record found for #{@user}@#{@database}" unless @user && @database
278
275
  end
279
276
  end
280
277
 
281
278
  # A subclass of DBRC designed to handle .dbrc files in YAML format. The
282
279
  # public methods of this class are identical to DBRC.
283
- class YML < DBRC
284
- require "yaml"
280
+ class DBRC::YML < DBRC
281
+ require 'yaml'
285
282
 
286
283
  private
287
284
 
288
- def parse_dbrc_config_file(file=@dbrc_file)
289
- config = YAML.load(File.open(file))
290
- config.each{ |hash|
291
- hash.each{ |db,info|
285
+ def parse_dbrc_config_file(file = @dbrc_file)
286
+ fh = file.is_a?(StringIO) ? file : File.open(file)
287
+ config = YAML.safe_load(fh)
288
+
289
+ config.each do |hash|
290
+ hash.each do |db, info|
292
291
  next unless db == @database
293
- next unless @user == info["user"] if @user
294
- @user = info["user"]
295
- @password = info["password"]
296
- @driver = info["driver"]
297
- @interval = info["interval"]
298
- @timeout = info["timeout"]
299
- @maximum_reconnects = info["max_reconn"]
300
- return
301
- }
302
- }
303
- # If we reach this point, it means the database wasn't found
304
- raise Error, "No entry found for #{@user}@#{@database}"
292
+ next if @user && @user != info['user']
293
+ @user = info['user']
294
+ @password = info['password']
295
+ @driver = info['driver']
296
+ @interval = info['interval']
297
+ @timeout = info['timeout']
298
+ @maximum_reconnects = info['maximum_reconnects']
299
+ break
300
+ end
301
+ end
302
+
303
+ raise Error, "No entry found for #{@user}@#{@database}" unless @user && @database
305
304
  end
306
305
  end
307
306
  end