# Copyright (c) 2021 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details. # frozen_string_literal: true require 'contrast/utils/timer' require 'contrast/utils/object_share' require 'contrast/components/logger' module Contrast module Agent module Inventory # Methods used for parsing database connection configurations # for getting inventory information from the application module DatabaseConfig extend Contrast::Components::Logger::InstanceMethods # TeamServer only accepts certain values for ArchitectureComponents. # DO NOT CHANGE THIS! AC_TYPE_DB = 'db' # TeamServer only accepts certain values for FlowMap Services. # DO NOT CHANGE THIS ADAPTER = 'adapter' HOST = 'host' PORT = 'port' DATABASE = 'database' DEFAULT = 'default' LOCALHOST = 'localhost' def self.active_record_config return @_active_record_config if instance_variable_defined?(:@_active_record_config) @_active_record_config = ActiveRecord::Base.connection_config rescue nil # rubocop:disable Style/RescueModifier end def self.append_db_config(activity_or_update, hash_or_str = Contrast::Agent::Inventory::DatabaseConfig.active_record_config) arr = build_from_db_config(hash_or_str) return unless arr&.any? arr.each do |a| next unless a if activity_or_update.is_a?(Contrast::Api::Dtm::Activity) activity_or_update.architectures << a else activity_or_update.components << a end end rescue StandardError => e logger.error('Unable to append db config', e) nil end def self.build_from_db_config hash_or_str return unless hash_or_str if hash_or_str.is_a?(Hash) build_from_db_hash(hash_or_str) else build_from_db_string(hash_or_str.to_s) end end def self.build_from_db_hash hash ac = Contrast::Api::Dtm::ArchitectureComponent.new ac.vendor = hash[:adapter] || hash[ADAPTER] || Contrast::Utils::ObjectShare::EMPTY_STRING ac.remote_host = host_from_hash(hash) ac.remote_port = port_from_hash(hash) ac.type = AC_TYPE_DB ac.url = hash[:database] || hash[DATABASE] || DEFAULT [ac] end def self.host_from_hash hash hash[:host] || hash[HOST] || Contrast::Utils::ObjectShare::EMPTY_STRING end def self.port_from_hash hash p = hash[:port] || hash[PORT] || Contrast::Utils::ObjectShare::EMPTY_STRING p.to_i end # Examples: # mongodb://[user:pass@]host1[:port1][,host2[:port2],[,hostN[:portN]]][/[database][?options]] # postgresql://scott:tiger@localhost/mydatabase # mysql+mysqlconnector://scott:tiger@localhost/foo def self.build_from_db_string str adapter, hosts, database = split_connection_str(str) acs = [] hosts.split(Contrast::Utils::ObjectShare::COMMA).map do |s| host, port = s.split(Contrast::Utils::ObjectShare::COLON) ac = Contrast::Api::Dtm::ArchitectureComponent.new ac.vendor = Contrast::Utils::StringUtils.force_utf8(adapter) ac.remote_host = Contrast::Utils::StringUtils.force_utf8(host) ac.remote_port = port.to_i ac.type = AC_TYPE_DB ac.url = Contrast::Utils::StringUtils.force_utf8(database) acs << ac end acs end def self.split_connection_str str adapter, str = str.split(Contrast::Utils::ObjectShare::COLON_SLASH_SLASH) _auth, str = str.split(Contrast::Utils::ObjectShare::AT) # Not currently used # user, pass = auth.split(Contrast::Utils::ObjectShare::COLON) hosts, db_and_options = str.split(Contrast::Utils::ObjectShare::SLASH) hosts << LOCALHOST if hosts.empty? database, _options = db_and_options.split(Contrast::Utils::ObjectShare::QUESTION_MARK) [adapter, hosts, database] end end end end end