# Copyright (c) 2020 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details. # frozen_string_literal: true cs__scoped_require 'contrast/utils/timer' cs__scoped_require 'contrast/utils/object_share' cs__scoped_require 'contrast/utils/gemfile_reader' cs__scoped_require 'contrast/components/interface' module Contrast module Utils # Utilities for getting inventory information from the application class InventoryUtil include Contrast::Components::Interface access_component :logging # 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 DATABASE = 'Database' ADAPTER = 'adapter' HOST = 'host' PORT = 'port' DATABASE_LOWER = 'database' DEFAULT = 'default' LOCALHOST = 'localhost' def self.inventory_class class_path Contrast::Utils::GemfileReader.instance.map_class(class_path) rescue StandardError => e logger.error('Unable to inventory module', e, path: class_path) end 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::Utils::InventoryUtil.active_record_config arr = build_from_db_config(hash_or_str) return unless arr&.any? activity_or_update.technologies[DATABASE] = true 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 next if a.vendor.empty? activity_or_update.technologies[a.vendor] = true 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_LOWER] || 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