Class: NucleusApi

Inherits:
Object
  • Object
show all
Defined in:
lib/domain/project/api.rb,
lib/service/api/nucleus.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(base_url:, api_key:, download_dir:, archive_dir:) ⇒ NucleusApi

Returns a new instance of NucleusApi.



12
13
14
15
16
17
18
19
20
21
22
# File 'lib/service/api/nucleus.rb', line 12

def initialize(base_url:, api_key:, download_dir:, archive_dir:)
  @options = {
    headers: {
      'Accept' => 'application/json',
      'x-apikey' => api_key
    }
  }
  @base_url = base_url
  @download_dir = download_dir
  @archive_dir = archive_dir
end

Instance Attribute Details

#api_keyObject (readonly)

Returns the value of attribute api_key.



7
8
9
# File 'lib/service/api/nucleus.rb', line 7

def api_key
  @api_key
end

#archive_dirObject (readonly)

Returns the value of attribute archive_dir.



7
8
9
# File 'lib/service/api/nucleus.rb', line 7

def archive_dir
  @archive_dir
end

#base_urlObject (readonly)

Returns the value of attribute base_url.



7
8
9
# File 'lib/service/api/nucleus.rb', line 7

def base_url
  @base_url
end

#download_dirObject (readonly)

Returns the value of attribute download_dir.



7
8
9
# File 'lib/service/api/nucleus.rb', line 7

def download_dir
  @download_dir
end

Instance Method Details

#download(path) ⇒ Object



78
79
80
81
82
83
84
85
# File 'lib/service/api/nucleus.rb', line 78

def download(path)
  download_request = Typhoeus::Request.new(
    "#{base_url}#{path}",
    method: :get,
    headers: @options[:headers].merge('Accept' => 'application/octet-stream')
  )
  download_file(download_request)
end

#download_file(request) ⇒ Object



87
88
89
90
91
92
93
94
95
# File 'lib/service/api/nucleus.rb', line 87

def download_file(request)
  response = request.run
  if response.success?
    response.body
  else
    puts "Failed to download file: HTTP #{response.code}"
    nil
  end
end

#download_project_report(id, report_id, dir = nil) ⇒ Object



73
74
75
76
77
78
79
80
81
82
# File 'lib/domain/project/api.rb', line 73

def download_project_report(id, report_id, dir = nil)
  endpoint = [Project.url_path, id, 'reports', report_id, 'download'].join('/')
  file_content = download(endpoint)
  raise 'Download failed.' if file_content.nil?

  dir ||= download_dir

  filename ||= "#{dir}/project_#{id}_report_#{report_id}.xlsx"
  File.open(filename, 'wb') { |file| file.write(file_content) }
end

#extract_report_id(filename) ⇒ Object



84
85
86
87
# File 'lib/domain/project/api.rb', line 84

def extract_report_id(filename)
  match_data = filename.match(/project_\d+_report_(\d+)\.xlsx/)
  match_data[1] if match_data
end

#fetch(model_class) ⇒ Object



24
25
26
27
28
29
# File 'lib/service/api/nucleus.rb', line 24

def fetch(model_class)
  get(model_class.url_path).map do |data|
    # p data
    model_class.new(data)
  end
end

#fetch_project(id) ⇒ Object



12
13
14
15
# File 'lib/domain/project/api.rb', line 12

def fetch_project(id)
  endpoint = [Project.url_path, id].join('/')
  Project.new get(endpoint)
end

#fetch_project_report_details(spreadsheet, report_id:, tabsheet_name: 'Scan Data') ⇒ Object



89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/domain/project/api.rb', line 89

def fetch_project_report_details(spreadsheet, report_id:, tabsheet_name: 'Scan Data')
  xlsx = Roo::Excelx.new(spreadsheet)
  sheet = xlsx.sheet(tabsheet_name)
  headers = xlsx.row(1)
  details = []
  row_id = 1
  total_rows = sheet.last_row - 1
  return [] if total_rows.zero?

  progress_bar = ProgressBar.create(
    title: "Fetching #{total_rows} details",
    total: total_rows,
    format: '%a %B %p%% %t'
  )

  xlsx.each_row_streaming(offset: 1) do |row|
    values = row.map(&:value)
    hash = Hash[headers.zip(values)]
    hash['report_id'] = report_id
    hash['row_id'] = row_id
    details << ProjectReportDetail.new(hash)
    row_id += 1
    progress_bar.increment
  end
  details
end

#fetch_project_report_findings(spreadsheet, report_id:) ⇒ Object



116
117
118
119
120
# File 'lib/domain/project/api.rb', line 116

def fetch_project_report_findings(spreadsheet, report_id:)
  ProjectReportFinding::SEVERITIES.flat_map do |severity|
    fetch_project_report_vulns(spreadsheet, report_id:, tabsheet_name: severity)
  end
end

#fetch_project_report_vulns(spreadsheet, report_id:, tabsheet_name:) ⇒ Object



122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/domain/project/api.rb', line 122

def fetch_project_report_vulns(spreadsheet, report_id:, tabsheet_name:)
  xlsx = Roo::Excelx.new(spreadsheet)
  return [] unless xlsx.sheets.include?(tabsheet_name)

  sheet = xlsx.sheet(tabsheet_name)
  headers = xlsx.row(1)
  total_rows = sheet.last_row - 1
  return [] if total_rows.zero?

  findings = []
  progress_bar = ProgressBar.create(
    title: "Fetching #{total_rows} #{tabsheet_name} findings",
    total: total_rows,
    format: '%a %B %p%% %t'
  )

  xlsx.each_row_streaming(offset: 1) do |row|
    values = row.map(&:value)
    hash = Hash[headers.zip(values)]
    hash['severity'] = tabsheet_name
    hash['report_id'] = report_id
    findings << ProjectReportFinding.new(hash)
    progress_bar.increment
  end
  findings
end

#fetch_project_reports(id, start: 0, limit: 1) ⇒ Object



67
68
69
70
71
# File 'lib/domain/project/api.rb', line 67

def fetch_project_reports(id, start: 0, limit: 1)
  endpoint = [Project.url_path, id, 'reports'].join('/')
  reports = get(endpoint, { start:, limit: })
  reports.map { |report| ProjectReport.new(report.merge('project_id' => id)) }
end

#fetch_projectsObject



8
9
10
# File 'lib/domain/project/api.rb', line 8

def fetch_projects
  fetch(Project)
end

#find_all_project_report_by(id, report_name:, since: nil) ⇒ Object



26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/domain/project/api.rb', line 26

def find_all_project_report_by(
  id,
  report_name:,
  since: nil
)
  since ||= Date.new(2024, 1, 1)
  condition = lambda do |report|
    report.name == report_name
  end
  reports = []
  find_project_report_if(id, condition) do |report|
    return reports if Date.parse(report.created_on) < since

    reports << report
  end
  reports
end

#find_first_project_report_name(id, name) ⇒ Object

Return the first project report with the correct name



18
19
20
21
22
23
24
# File 'lib/domain/project/api.rb', line 18

def find_first_project_report_name(id, name)
  condition = ->(report) { report.name == name }
  find_project_report_if(id, condition) do |report|
    return report
  end
  nil
end

#find_project_report_if(id, condition) ⇒ Object



44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/domain/project/api.rb', line 44

def find_project_report_if(id, condition)
  start = 0
  limit = 50
  found_reports = []
  loop do
    reports = fetch_project_reports(id, start:, limit:)
    break if reports.empty?

    reports.each do |report|
      # puts "#{report.created_on}"
      next unless condition.call(report)

      if block_given?
        yield(report)
      else
        found_reports << report
      end
    end
    start += limit
  end
  found_reports unless block_given?
end

#get(path, params = {}) ⇒ Object

Generic GET request



32
33
34
35
36
37
38
39
40
# File 'lib/service/api/nucleus.rb', line 32

def get(path, params = {})
  request = Typhoeus::Request.new(
    "#{base_url}#{path}",
    method: :get,
    headers: @options[:headers],
    params:
  )
  run_request(request)
end

#post(path, body) ⇒ Object

Generic POST request



43
44
45
46
47
48
49
50
51
# File 'lib/service/api/nucleus.rb', line 43

def post(path, body)
  request = Typhoeus::Request.new(
    "#{base_url}#{path}",
    method: :post,
    headers: @options[:headers],
    body: body.to_json
  )
  run_request(request)
end

#put(path, body) ⇒ Object

Generic PUT request



54
55
56
57
58
59
60
61
62
# File 'lib/service/api/nucleus.rb', line 54

def put(path, body)
  request = Typhoeus::Request.new(
    "#{base_url}#{path}",
    method: :put,
    headers: @options[:headers],
    body: body.to_json
  )
  run_request(request)
end

#run_request(request) ⇒ Object

Runs the request and handles the response



65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/service/api/nucleus.rb', line 65

def run_request(request)
  response = request.run
  if response.success?
    JSON.parse(response.body)
  elsif response.timed_out?
    { error: 'Request timed out' }
  elsif response.code == 0
    { error: response.return_message }
  else
    { error: 'HTTP request failed', status_code: response.code }
  end
end