lib/dbi/dbrc.rb in dbi-dbrc-1.5.0 vs lib/dbi/dbrc.rb in dbi-dbrc-1.6.0
- old
+ new
@@ -1,45 +1,44 @@
+# frozen_string_literal: true
+
if File::ALT_SEPARATOR
require 'win32/dir'
require 'win32/file/attributes'
require 'win32/process'
- require 'sys/admin'
else
require 'etc'
+ require 'gpgme'
end
-
# The DBI module serves as a namespace only.
module DBI
-
# The DBRC class encapsulates a database resource config file.
class DBRC
-
# This error is raised if anything fails trying to read the config file.
class Error < StandardError; end
# The version of the dbi-dbrc library
- VERSION = '1.5.0'.freeze
+ VERSION = '1.6.0'
WINDOWS = File::ALT_SEPARATOR # :no-doc:
# The database or host to be connected to.
attr_accessor :database
- alias :db :database
- alias :db= :database=
- alias :host :database
- alias :host= :database=
+ alias db database
+ alias db= database=
+ alias host database
+ alias host= database=
# The user name used for the database or host connection.
attr_accessor :user
# The password associated with the database or host.
attr_accessor :password
- alias :passwd :password
- alias :passwd= :password=
+ alias passwd password
+ alias passwd= password=
# The driver associated with the database. This is used to internally to
# construct the DSN.
attr_accessor :driver
@@ -47,18 +46,18 @@
attr_accessor :dsn
# The maximum number of reconnects a program should make before giving up.
attr_accessor :maximum_reconnects
- alias :max_reconn :maximum_reconnects
- alias :max_reconn= :maximum_reconnects=
+ alias max_reconn maximum_reconnects
+ alias max_reconn= maximum_reconnects=
# The timeout, in seconds, for each connection attempt.
attr_accessor :timeout
- alias :time_out :timeout
- alias :time_out= :timeout=
+ alias time_out timeout
+ alias time_out= timeout=
# The interval, in seconds, between each connection attempt.
attr_accessor :interval
# The directory where the .dbrc file is stored.
@@ -76,20 +75,23 @@
# If a directory is passed as the third argument, then DBRC will look
# in that directory, instead of the default directory, for the .dbrc
# file.
#
# If an entry cannot be found for the database, or database plus user
- # combination, then a Error is raised. If the .dbrc file cannot be
+ # combination, then a Error is raised. If the .dbrc file cannot be
# found, or is setup improperly with regards to permissions or properties
# then a DBI::DBRC::Error is raised.
#
# See the README for the rules regarding .dbrc files and permissions.
#
# Note that this library can also be used as a general password
# storage mechanism. In that case simply treat the 'database' as the
# host name, and ignore the DBI::DBRC#dsn and DBI::DBRC#driver methods.
#
+ # On unixy systems you can GPG encrypt the file, and this library will
+ # decrypt it for you based on the +gpg_options+ that you pass.
+ #
# Examples:
#
# # Find the first match for 'some_database'
# DBI::DBRC.new('some_database')
#
@@ -97,18 +99,15 @@
# DBI::DBRC.new('some_database', 'foo_user')
#
# # Find the first match for 'foo_user@some_database' under /usr/local
# DBI::DBRC.new('some_database', 'foo_usr', '/usr/local')
#
- def initialize(database, user=nil, dbrc_dir=nil)
+ # # Pass along a GPG password to decrypt the file.
+ # DBI::DBRC.new('some_database', 'foo_usr', '/usr/local', :gpg_options => {:password => 'xxx'})
+ #
+ def initialize(database, user = nil, dbrc_dir = Dir.home, gpg_options = nil)
if dbrc_dir.nil?
- if WINDOWS
- home = Sys::Admin.get_user(Process.uid, :localaccount => true).dir
- else
- home = Dir.home(Etc.getpwuid.name)
- end
-
# Default to the app data directory on Windows, or root on Unix, if
# no home dir can be found.
if home.nil?
if WINDOWS
home = Dir::APPDATA
@@ -143,47 +142,49 @@
if WINDOWS && File.encrypted?(@dbrc_file)
file_was_encrypted = true
File.decrypt(@dbrc_file)
end
- parse_dbrc_config_file()
- validate_data()
- convert_numeric_strings()
- create_dsn_string()
- ensure
- if WINDOWS && file_was_encrypted
- File.encrypt(@dbrc_file)
+ if gpg_options
+ require 'gpgme'
+ require 'stringio'
+ crypto = GPGME::Crypto.new(gpg_options)
+ stringio = crypto.decrypt(File.open(@dbrc_file))
+ parse_dbrc_config_file(StringIO.new(stringio.read))
+ else
+ parse_dbrc_config_file
end
+
+ validate_data
+ convert_numeric_strings
+ create_dsn_string
+ ensure
+ File.encrypt(@dbrc_file) if WINDOWS && file_was_encrypted
end
end
# Inspection of the DBI::DBRC object. This is identical to the standard
# Ruby Object#inspect, except that the password field is filtered.
#
def inspect
- str = instance_variables.map{ |iv|
+ str = instance_variables.map do |iv|
if iv == '@password'
"#{iv}=[FILTERED]"
else
"#{iv}=#{instance_variable_get(iv).inspect}"
end
- }.join(', ')
+ end.join(', ')
- "#<#{self.class}:0x#{(self.object_id*2).to_s(16)} " << str << ">"
+ "#<#{self.class}:0x#{(object_id * 2).to_s(16)} " + str + '>'
end
private
# Ensure that the user/password has been set
def validate_data
- unless @user
- raise Error, "no user found associated with #{@database}"
- end
-
- unless @password
- raise Error, "password not defined for #{@user}@#{@database}"
- end
+ raise Error, "no user found associated with #{@database}" unless @user
+ raise Error, "password not defined for #{@user}@#{@database}" unless @password
end
# Converts strings that should be numbers into actual numbers
def convert_numeric_strings
@interval = @interval.to_i if @interval
@@ -195,116 +196,111 @@
def create_dsn_string
@dsn = "dbi:#{@driver}:#{@database}" if @driver
end
# Check ownership and permissions
- def check_file(file=@dbrc_file)
- File.open(file){ |f|
+ def check_file(file = @dbrc_file)
+ File.open(file) do |f|
# Permissions must be set to 600 or better on Unix systems.
# Must be hidden on Win32 systems.
if WINDOWS
- unless File.hidden?(file)
- raise Error, "The .dbrc file must be hidden"
- end
+ raise Error, 'The .dbrc file must be hidden' unless File.hidden?(file)
else
- unless (f.stat.mode & 077) == 0
- raise Error, "Bad .dbrc file permissions"
- end
+ raise Error, 'Bad .dbrc file permissions' unless (f.stat.mode & 0o77) == 0
end
# Only the owner may use it
- unless f.stat.owned?
- raise Error, "Not owner of .dbrc file"
- end
- }
+ raise Error, 'Not owner of .dbrc file' unless f.stat.owned?
+ end
end
# Parse the text out of the .dbrc file. This is the only method you
# need to redefine if writing your own config handler.
- def parse_dbrc_config_file(file=@dbrc_file)
- File.foreach(file){ |line|
- next if line =~ /^#/ # Ignore comments
- db, user, pwd, driver, timeout, max, interval = line.split
+ def parse_dbrc_config_file(file = @dbrc_file)
+ begin
+ fh = file.is_a?(StringIO) ? file : File.open(file)
- next unless @database == db
+ fh.each_line do |line|
+ next if line =~ /^#/ # Ignore comments
+ db, user, pwd, driver, timeout, max, interval = line.split
- if @user
- next unless @user == user
+ next unless @database == db
+ next if @user && @user != user
+
+ @user = user
+ @password = pwd
+ @driver = driver
+ @timeout = timeout
+ @maximum_reconnects = max
+ @interval = interval
+ break
end
+ ensure
+ fh.close if fh && fh.respond_to?(:close)
+ end
- @user = user
- @password = pwd
- @driver = driver
- @timeout = timeout
- @maximum_reconnects = max
- @interval = interval
- return
- }
-
- # If we reach here it means the database and/or user wasn't found
if @user
- err = "no record found for #{@user}@#{@database}"
+ raise Error, "no record found for #{@user}@#{@database}" unless @user
else
- err = "no record found for #{@database}"
+ raise Error, "no record found for #{@database}" unless @database
end
-
- raise Error, err
end
-
end
# A subclass of DBRC designed to handle .dbrc files in XML format. The
# public methods of this class are identical to DBRC.
class DBRC::XML < DBRC
require 'rexml/document' # Good enough for small files
- include REXML
private
- def parse_dbrc_config_file(file=@dbrc_file)
- doc = Document.new(File.new(file))
- fields = %w/user password driver interval timeout maximum_reconnects/
- doc.elements.each("/dbrc/database"){ |element|
- next unless element.attributes["name"] == database
- if @user
- next unless element.elements["user"].text == @user
- end
- fields.each{ |field|
+ def parse_dbrc_config_file(file = @dbrc_file)
+ file = file.is_a?(StringIO) ? file : File.new(file)
+ doc = REXML::Document.new(file)
+
+ fields = %w[user password driver interval timeout maximum_reconnects]
+
+ doc.elements.each('/dbrc/database') do |element|
+ next unless element.attributes['name'] == database
+ next if @user && @user != element.elements['user'].text
+
+ fields.each do |field|
val = element.elements[field]
- unless val.nil?
- send("#{field}=",val.text)
- end
- }
- return
- }
- # If we reach here it means the database and/or user wasn't found
- raise Error, "No record found for #{@user}@#{@database}"
+ send("#{field}=", val.text) unless val.nil?
+ end
+
+ break
+ end
+
+ raise Error, "No record found for #{@user}@#{@database}" unless @user && @database
end
end
# A subclass of DBRC designed to handle .dbrc files in YAML format. The
# public methods of this class are identical to DBRC.
class DBRC::YML < DBRC
require 'yaml'
private
- def parse_dbrc_config_file(file=@dbrc_file)
- config = YAML.load(File.open(file))
- config.each{ |hash|
- hash.each{ |db,info|
+ def parse_dbrc_config_file(file = @dbrc_file)
+ fh = file.is_a?(StringIO) ? file : File.open(file)
+ config = YAML.safe_load(fh)
+
+ config.each do |hash|
+ hash.each do |db, info|
next unless db == @database
- next unless @user == info["user"] if @user
- @user = info["user"]
- @password = info["password"]
- @driver = info["driver"]
- @interval = info["interval"]
- @timeout = info["timeout"]
- @maximum_reconnects = info["maximum_reconnects"]
- return
- }
- }
- # If we reach this point, it means the database wasn't found
- raise Error, "No entry found for #{@user}@#{@database}"
+ next if @user && @user != info['user']
+ @user = info['user']
+ @password = info['password']
+ @driver = info['driver']
+ @interval = info['interval']
+ @timeout = info['timeout']
+ @maximum_reconnects = info['maximum_reconnects']
+ break
+ end
+ end
+
+ raise Error, "No entry found for #{@user}@#{@database}" unless @user && @database
end
end
end