Skip to content

Commit 2ed7a73

Browse files
committed
New reports in new exports can now import
MSP-9783 * Extracted import_report from monstrous import_msf_collateral; simplified and clarified approach * Updated report_report: includes all attrs provided vs subset, provides more helpful error message * Added report_artifact: adds child artifact for reports, handles various troublesome cases * Tested on all report types with a legion of option variants
1 parent 54346f3 commit 2ed7a73

File tree

1 file changed

+92
-52
lines changed

1 file changed

+92
-52
lines changed

lib/msf/core/db.rb

Lines changed: 92 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -2172,35 +2172,63 @@ def tasks(wspace=workspace)
21722172
end
21732173

21742174

2175-
#
2176-
# Find or create a task matching this type/data
2177-
#
2175+
# TODO This method does not attempt to find. It just creates
2176+
# a report based on the passed params.
21782177
def find_or_create_report(opts)
21792178
report_report(opts)
21802179
end
21812180

2181+
# Creates a Report based on passed parameters. Does not handle
2182+
# child artifacts.
2183+
# @param opts [Hash]
2184+
# @return [Integer] ID of created report
21822185
def report_report(opts)
21832186
return if not active
21842187
::ActiveRecord::Base.connection_pool.with_connection {
2185-
wspace = opts.delete(:workspace) || workspace
2186-
path = opts.delete(:path) || (raise RuntimeError, "A report :path is required")
21872188

2188-
ret = {}
2189-
user = opts.delete(:user)
2190-
options = opts.delete(:options)
2191-
rtype = opts.delete(:rtype)
2192-
report = wspace.reports.new
2193-
report.created_by = user
2194-
report.options = options
2195-
report.rtype = rtype
2196-
report.path = path
2197-
msf_import_timestamps(opts,report)
2198-
report.save!
2199-
2200-
ret[:task] = report
2189+
report = Report.new(opts)
2190+
unless report.valid?
2191+
errors = report.errors.full_messages.join('; ')
2192+
raise RuntimeError "Report to be imported is not valid: #{errors}"
2193+
end
2194+
report.state = :complete # Presume complete since it was exported
2195+
report.save
2196+
2197+
report.id
22012198
}
22022199
end
22032200

2201+
# Creates a ReportArtifact based on passed parameters.
2202+
# @param opts [Hash] of ReportArtifact attributes
2203+
def report_artifact(opts)
2204+
artifacts_dir = Report::ARTIFACT_DIR
2205+
tmp_path = opts[:file_path]
2206+
artifact_name = File.basename tmp_path
2207+
new_path = File.join(artifacts_dir, artifact_name)
2208+
2209+
unless File.exists? tmp_path
2210+
raise DBImportError 'Report artifact file to be imported does not exist.'
2211+
end
2212+
2213+
unless (File.directory?(artifacts_dir) && File.writable?(artifacts_dir))
2214+
raise DBImportError "Could not move report artifact file to #{artifacts_dir}."
2215+
end
2216+
2217+
if File.exists? new_path
2218+
unique_basename = "#{(Time.now.to_f*1000).to_i}_#{artifact_name}"
2219+
new_path = File.join(artifacts_dir, unique_basename)
2220+
end
2221+
2222+
FileUtils.copy(tmp_path, new_path)
2223+
opts[:file_path] = new_path
2224+
artifact = ReportArtifact.new(opts)
2225+
unless artifact.valid?
2226+
errors = artifact.errors.full_messages.join('; ')
2227+
raise RuntimeError "Artifact to be imported is not valid: #{errors}"
2228+
end
2229+
artifact.save
2230+
end
2231+
22042232
#
22052233
# This methods returns a list of all reports in the database
22062234
#
@@ -3793,43 +3821,55 @@ def import_msf_collateral(args={}, &block)
37933821

37943822
# Import Reports
37953823
doc.elements.each("/#{btag}/reports/report") do |report|
3796-
tmp = args[:ifd][:zip_tmp]
3797-
report_info = {}
3798-
report_info[:workspace] = args[:wspace]
3799-
# Should user be imported (original) or declared (the importing user)?
3800-
report_info[:user] = nils_for_nulls(report.elements["created-by"].text.to_s.strip)
3801-
report_info[:options] = nils_for_nulls(report.elements["options"].text.to_s.strip)
3802-
report_info[:rtype] = nils_for_nulls(report.elements["rtype"].text.to_s.strip)
3803-
report_info[:created_at] = nils_for_nulls(report.elements["created-at"].text.to_s.strip)
3804-
report_info[:updated_at] = nils_for_nulls(report.elements["updated-at"].text.to_s.strip)
3805-
report_info[:orig_path] = nils_for_nulls(report.elements["path"].text.to_s.strip)
3806-
report_info[:task] = args[:task]
3807-
report_info[:orig_path].gsub!(/^\./, tmp) if report_info[:orig_path]
3808-
3809-
# Only report a report if we actually have it.
3810-
# TODO: Copypasta. Seperate this out.
3811-
if ::File.exists? report_info[:orig_path]
3812-
reports_dir = ::File.join(basedir,"reports")
3813-
report_file = ::File.split(report_info[:orig_path]).last
3814-
if ::File.exists? reports_dir
3815-
unless (::File.directory?(reports_dir) && ::File.writable?(reports_dir))
3816-
raise DBImportError.new("Could not move files to #{reports_dir}")
3817-
end
3818-
else
3819-
::FileUtils.mkdir_p(reports_dir)
3820-
end
3821-
new_report = ::File.join(reports_dir,report_file)
3822-
report_info[:path] = new_report
3823-
if ::File.exists?(new_report)
3824-
::File.unlink new_report
3825-
else
3826-
report_report(report_info)
3827-
end
3828-
::FileUtils.copy(report_info[:orig_path], new_report)
3829-
yield(:msf_report, new_report) if block
3824+
import_report(report, args, basedir)
3825+
end
3826+
end
3827+
3828+
# @param report [REXML::Element] to be imported
3829+
# @param args [Hash]
3830+
# @param base_dir [String]
3831+
def import_report(report, args, base_dir)
3832+
tmp = args[:ifd][:zip_tmp]
3833+
report_info = {}
3834+
3835+
report.elements.each do |e|
3836+
node_name = e.name
3837+
node_value = e.text
3838+
3839+
# These need to be converted back to arrays:
3840+
array_attrs = %w|addresses file-formats options sections|
3841+
if array_attrs.member? node_name
3842+
node_value = JSON.parse(node_value)
38303843
end
3844+
# Don't restore these values:
3845+
skip_nodes = %w|id workspace-id artifacts|
3846+
next if skip_nodes.member? node_name
3847+
3848+
report_info[node_name.parameterize.underscore.to_sym] = node_value
38313849
end
3850+
# Use current workspace
3851+
report_info[:workspace_id] = args[:wspace].id
3852+
3853+
# Create report, need new ID to record artifacts
3854+
report_id = report_report(report_info)
38323855

3856+
# Handle artifacts
3857+
report.elements['artifacts'].elements.each do |artifact|
3858+
artifact_opts = {}
3859+
artifact.elements.each do |attr|
3860+
skip_nodes = %w|id accessed-at|
3861+
next if skip_nodes.member? attr.name
3862+
3863+
symboled_attr = attr.name.parameterize.underscore.to_sym
3864+
artifact_opts[symboled_attr] = attr.text
3865+
end
3866+
# Use new Report as parent
3867+
artifact_opts[:report_id] = report_id
3868+
# Update to full path
3869+
artifact_opts[:file_path].gsub!(/^\./, tmp)
3870+
3871+
report_artifact(artifact_opts)
3872+
end
38333873
end
38343874

38353875
# Convert the string "NULL" to actual nil

0 commit comments

Comments
 (0)