dbi-dbrc 1.3.0 → 1.7.0

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -1,14 +1,14 @@
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
8
  desc "Create the dbi-dbrc gem"
9
9
  task :create => [:clean] do
10
10
  require 'rubygems/package'
11
- spec = eval(IO.read('dbi-dbrc.gemspec'))
11
+ spec = Gem::Specification.load('dbi-dbrc.gemspec')
12
12
  spec.signing_key = File.join(Dir.home, '.ssh', 'gem-private_key.pem')
13
13
  Gem::Package.build(spec)
14
14
  end
@@ -20,23 +20,7 @@ namespace :gem do
20
20
  end
21
21
  end
22
22
 
23
- namespace :test do
24
- Rake::TestTask.new(:all) do |t|
25
- t.warning = true
26
- t.verbose = true
27
- end
28
-
29
- Rake::TestTask.new(:xml) do |t|
30
- t.warning = true
31
- t.verbose = true
32
- t.test_files = FileList['test/test_dbi_dbrc_xml.rb']
33
- end
34
-
35
- Rake::TestTask.new(:yml) do |t|
36
- t.warning = true
37
- t.verbose = true
38
- t.test_files = FileList['test/test_dbi_dbrc_yml.rb']
39
- end
40
- end
23
+ desc "Run the test suite"
24
+ RSpec::Core::RakeTask.new(:spec)
41
25
 
42
- task :default => 'test:all'
26
+ task :default => :spec
@@ -1,21 +1,26 @@
1
1
  -----BEGIN CERTIFICATE-----
2
- MIIDcDCCAligAwIBAgIBATANBgkqhkiG9w0BAQUFADA/MREwDwYDVQQDDAhkamJl
2
+ MIIEcDCCAtigAwIBAgIBATANBgkqhkiG9w0BAQsFADA/MREwDwYDVQQDDAhkamJl
3
3
  cmc5NjEVMBMGCgmSJomT8ixkARkWBWdtYWlsMRMwEQYKCZImiZPyLGQBGRYDY29t
4
- MB4XDTE1MDkwMjIwNDkxOFoXDTE2MDkwMTIwNDkxOFowPzERMA8GA1UEAwwIZGpi
4
+ MB4XDTE4MDMxODE1MjIwN1oXDTI4MDMxNTE1MjIwN1owPzERMA8GA1UEAwwIZGpi
5
5
  ZXJnOTYxFTATBgoJkiaJk/IsZAEZFgVnbWFpbDETMBEGCgmSJomT8ixkARkWA2Nv
6
- bTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMyTkvXqRp6hLs9eoJOS
7
- Hmi8kRYbq9Vkf15/hMxJpotYMgJVHHWrmDcC5Dye2PbnXjTkKf266Zw0PtT9h+lI
8
- S3ts9HO+vaCFSMwFFZmnWJSpQ3CNw2RcHxjWkk9yF7imEM8Kz9ojhiDXzBetdV6M
9
- gr0lV/alUr7TNVBDngbXEfTWscyXh1qd7xZ4EcOdsDktCe5G45N/o3662tPQvJsi
10
- FOF0CM/KuBsa/HL1/eoEmF4B3EKIRfTHrQ3hu20Kv3RJ88QM4ec2+0dd97uX693O
11
- zv6981fyEg+aXLkxrkViM/tz2qR2ZE0jPhHTREPYeMEgptRkTmWSKAuLVWrJEfgl
12
- DtkCAwEAAaN3MHUwCQYDVR0TBAIwADALBgNVHQ8EBAMCBLAwHQYDVR0OBBYEFEwe
13
- nn6bfJADmuIDiMSOzedOrL+xMB0GA1UdEQQWMBSBEmRqYmVyZzk2QGdtYWlsLmNv
14
- bTAdBgNVHRIEFjAUgRJkamJlcmc5NkBnbWFpbC5jb20wDQYJKoZIhvcNAQEFBQAD
15
- ggEBAHmNOCWoDVD75zHFueY0viwGDVP1BNGFC+yXcb7u2GlK+nEMCORqzURbYPf7
16
- tL+/hzmePIRz7i30UM//64GI1NLv9jl7nIwjhPpXpf7/lu2I9hOTsvwSumb5UiKC
17
- /sqBxI3sfj9pr79Wpv4MuikX1XPik7Ncb7NPsJPw06Lvyc3Hkg5X2XpPtLtS+Gr2
18
- wKJnmzb5rIPS1cmsqv0M9LPWflzfwoZ/SpnmhagP+g05p8bRNKjZSA2iImM/GyYZ
19
- EJYzxdPOrx2n6NYR3Hk+vHP0U7UBSveI6+qx+ndQYaeyCn+GRX2PKS9h66YF/Q1V
20
- tGSHgAmcLlkdGgan182qsE/4kKM=
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
21
26
  -----END CERTIFICATE-----
data/dbi-dbrc.gemspec CHANGED
@@ -2,22 +2,33 @@ require 'rubygems'
2
2
 
3
3
  Gem::Specification.new do |spec|
4
4
  spec.name = 'dbi-dbrc'
5
- spec.version = '1.3.0'
5
+ spec.version = '1.7.0'
6
6
  spec.author = 'Daniel Berger'
7
7
  spec.email = 'djberg96@gmail.com'
8
- spec.license = 'Apache 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 = ['certs/djberg96_pub.pem']
13
+ spec.cert_chain = Dir['certs/*']
14
14
 
15
- spec.extra_rdoc_files = ['README', 'CHANGES', 'MANIFEST']
15
+ spec.add_dependency('gpgme', '~> 2.0')
16
16
 
17
- spec.add_dependency('sys-admin')
18
- 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
+ }
19
29
 
20
30
  if File::ALT_SEPARATOR
31
+ spec.add_dependency('sys-admin')
21
32
  spec.add_dependency('win32-file-attributes')
22
33
  spec.add_dependency('win32-dir')
23
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.3.0'
20
+ VERSION = '1.7.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,21 @@ 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
237
-
238
- raise Error, err
239
- 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
- end
251
-
252
- # A subclass of DBRC designed to handle .dbrc files in XML format. The
253
- # public methods of this class are identical to DBRC.
254
- class XML < DBRC
255
- require "rexml/document"
256
- include REXML
257
-
258
- private
259
-
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
235
+ break
267
236
  end
268
- fields.each{ |field|
269
- 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}"
278
- end
279
- end
280
-
281
- # A subclass of DBRC designed to handle .dbrc files in YAML format. The
282
- # public methods of this class are identical to DBRC.
283
- class YML < DBRC
284
- require "yaml"
285
-
286
- private
237
+ ensure
238
+ fh.close if fh && fh.respond_to?(:close)
239
+ end
287
240
 
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|
292
- 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}"
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
305
246
  end
306
247
  end
307
248
  end
249
+
250
+ require_relative 'dbrc/xml'
251
+ require_relative 'dbrc/yaml'
252
+ require_relative 'dbrc/json'