class HackerOne::Client::Report

Constants

RESOLVED_STATES
SEVERITY_RATINGS
STATES
STATES_REQUIRING_STATE_CHANGE_MESSAGE

Public Class Methods

add_on_state_change_hook(proc) click to toggle source
# File lib/hackerone/client/report.rb, line 46
def add_on_state_change_hook(proc)
  on_state_change_hooks << proc
end
clear_on_state_change_hooks() click to toggle source
# File lib/hackerone/client/report.rb, line 50
def clear_on_state_change_hooks
  @on_state_change_hooks = []
end
new(report) click to toggle source
# File lib/hackerone/client/report.rb, line 59
def initialize(report)
  @report = report
end
on_state_change_hooks() click to toggle source
# File lib/hackerone/client/report.rb, line 54
def on_state_change_hooks
  @on_state_change_hooks ||= []
end

Public Instance Methods

activities() click to toggle source
# File lib/hackerone/client/report.rb, line 152
def activities
  if ships = relationships.fetch(:activities, {}).fetch(:data, [])
    ships.map do |activity_data|
      Activities.build(activity_data)
    end
  end
end
add_comment(message, internal: true) click to toggle source

Add a comment to a report. By default, internal comments will be added.

id: the ID of the report message: the content of the comment that will be created internal: “team only” comment (true, default) or “all participants”

# File lib/hackerone/client/report.rb, line 294
def add_comment(message, internal: true)
  fail ArgumentError, "message is required" if message.blank?

  body = {
    type: "activity-comment",
    attributes: {
      message: message,
      internal: internal
    }
  }

  response_json = make_post_request("reports/#{id}/activities", request_body: body)
  HackerOne::Client::Activities.build(response_json)
end
add_report_reference(reference) click to toggle source
Idempotent: Add a report reference to a project

id: the ID of the report state: value for the reference (e.g. issue number or relative path to cross-repo issue)

returns an HackerOne::Client::Report object or raises an error if no report is found.

# File lib/hackerone/client/report.rb, line 264
def add_report_reference(reference)
  body = {
    type: "issue-tracker-reference-id",
    attributes: {
      reference: reference
    }
  }

  response_json = make_post_request("reports/#{id}/issue_tracker_reference_id", request_body: body)
  @report = response_json[:relationships][:report][:data]
  self
end
assign_to_group(name) click to toggle source
# File lib/hackerone/client/report.rb, line 327
def assign_to_group(name)
  group = program.find_group(name)
  _assign_to(group.id, :group)
end
assign_to_user(name) click to toggle source
# File lib/hackerone/client/report.rb, line 322
def assign_to_user(name)
  member = program.find_member(name)
  _assign_to(member.user.id, :user)
end
assignee() click to toggle source
# File lib/hackerone/client/report.rb, line 98
def assignee
  if assignee_relationship = relationships[:assignee]
    HackerOne::Client::User.new(assignee_relationship[:data])
  else
    nil
  end
end
attachments() click to toggle source
# File lib/hackerone/client/report.rb, line 146
def attachments
  @attachments ||= relationships.fetch(:attachments, {})
      .fetch(:data, [])
      .map { |attachment| HackerOne::Client::Attachment.new(attachment) }
end
award_bounty(message:, amount:, bonus_amount: nil) click to toggle source
# File lib/hackerone/client/report.rb, line 164
def award_bounty(message:, amount:, bonus_amount: nil)
  request_body = {
    message: message,
    amount: amount,
    bonus_amount: bonus_amount
  }

  response_body = make_post_request(
    "reports/#{id}/bounties",
    request_body: request_body
  )
  Bounty.new(response_body)
end
award_swag(message:) click to toggle source
# File lib/hackerone/client/report.rb, line 178
def award_swag(message:)
  request_body = {
    message: message
  }

  response_body = make_post_request(
    "reports/#{id}/swags",
    request_body: request_body
  )
  Swag.new(response_body, program)
end
classification_label() click to toggle source
# File lib/hackerone/client/report.rb, line 137
def classification_label
  weakness.to_owasp
end
created_at() click to toggle source
# File lib/hackerone/client/report.rb, line 71
def created_at
  attributes[:created_at]
end
id() click to toggle source
# File lib/hackerone/client/report.rb, line 63
def id
  @report[:id]
end
issue_tracker_reference_id() click to toggle source
# File lib/hackerone/client/report.rb, line 79
def issue_tracker_reference_id
  attributes[:issue_tracker_reference_id]
end
issue_tracker_reference_url() click to toggle source
# File lib/hackerone/client/report.rb, line 75
def issue_tracker_reference_url
  attributes[:issue_tracker_reference_url]
end
lock!() click to toggle source
# File lib/hackerone/client/report.rb, line 309
def lock!
  unless RESOLVED_STATES.include? self.state.to_sym
    raise ArgumentError, "Report must be closed before locking"
  end

  body = {
    type: "activity-comments-closed"
  }

  response_json = make_put_request("reports/#{id}/close_comments", request_body: body)
  HackerOne::Client::Activities.build(response_json)
end
payment_total() click to toggle source
# File lib/hackerone/client/report.rb, line 106
def payment_total
  payments.reduce(0) { |total, payment| total + payment_amount(payment) }
end
program() click to toggle source
# File lib/hackerone/client/report.rb, line 160
def program
  @program || Program.find(relationships[:program][:data][:attributes][:handle])
end
reporter() click to toggle source
# File lib/hackerone/client/report.rb, line 91
def reporter
  relationships
    .fetch(:reporter, {})
    .fetch(:data, {})
    .fetch(:attributes, {})
end
risk() click to toggle source

Excludes reports where the payout amount is 0 indicating swag-only or no payout for the issue supplied

# File lib/hackerone/client/report.rb, line 116
def risk
  case payment_total
  when HackerOne::Client.low_range || DEFAULT_LOW_RANGE
    "low"
  when HackerOne::Client.medium_range || DEFAULT_MEDIUM_RANGE
    "medium"
  when HackerOne::Client.high_range || DEFAULT_HIGH_RANGE
    "high"
  when HackerOne::Client.critical_range || DEFAULT_CRITICAL_RANGE
    "critical"
  end
end
severity() click to toggle source
# File lib/hackerone/client/report.rb, line 83
def severity
  attributes[:severity]
end
state() click to toggle source
# File lib/hackerone/client/report.rb, line 87
def state
  attributes[:state]
end
state_change(state, message = nil, attributes = {}) click to toggle source
Idempotent: change the state of a report. See STATES for valid values.

id: the ID of the report state: the state in which the report is to be put in

returns an HackerOne::Client::Report object or raises an error if no report is found.

# File lib/hackerone/client/report.rb, line 228
def state_change(state, message = nil, attributes = {})
  raise ArgumentError, "state (#{state}) must be one of #{STATES}" unless STATES.include?(state)

  old_state = self.state
  body = {
    type: "state-change",
    attributes: {
      state: state
    }
  }

  body[:attributes] = body[:attributes].reverse_merge(attributes)

  if message
    body[:attributes][:message] = message
  elsif STATES_REQUIRING_STATE_CHANGE_MESSAGE.include?(state)
    fail ArgumentError, "State #{state} requires a message. No message was supplied."
  else
    # message is in theory optional, but a value appears to be required.
    body[:attributes][:message] = ""
  end
  response_json = make_post_request("reports/#{id}/state_changes", request_body: body)
  @report = response_json
  self.class.on_state_change_hooks.each do |hook|
    hook.call(self, old_state.to_s, state.to_s)
  end
  self
end
structured_scope() click to toggle source
# File lib/hackerone/client/report.rb, line 110
def structured_scope
  StructuredScope.new(relationships[:structured_scope].fetch(:data, {}))
end
suggest_bounty(message:, amount:, bonus_amount: nil) click to toggle source
# File lib/hackerone/client/report.rb, line 207
def suggest_bounty(message:, amount:, bonus_amount: nil)
  request_body = {
    message: message,
    amount: amount,
    bonus_amount: bonus_amount
  }

  response_body = make_post_request(
    "reports/#{id}/bounty_suggestions",
    request_body: request_body
  )
  Activities.build(response_body)
end
summary() click to toggle source
# File lib/hackerone/client/report.rb, line 129
def summary
  attributes[:vulnerability_information]
end
title() click to toggle source
# File lib/hackerone/client/report.rb, line 67
def title
  attributes[:title]
end
triage(reference) click to toggle source
Idempotent: add the issue reference and put the report into the "triage" state.

id: the ID of the report state: value for the reference (e.g. issue number or relative path to cross-repo issue)

returns an HackerOne::Client::Report object or raises an error if no report is found.

# File lib/hackerone/client/report.rb, line 284
def triage(reference)
  add_report_reference(reference)
  state_change(:triaged)
end
unassign() click to toggle source
# File lib/hackerone/client/report.rb, line 332
def unassign
  _assign_to(nil, :nobody)
end
update_severity(rating:) click to toggle source
# File lib/hackerone/client/report.rb, line 190
def update_severity(rating:)
  raise ArgumentError, "Invalid severity rating" unless SEVERITY_RATINGS.include?(rating.to_s)

  request_body = {
    type: "severity",
    attributes: {
      rating: rating
    }
  }
  response_body = make_post_request(
    "reports/#{id}/severities",
    request_body: request_body
  )
  @report[:attributes][:severity] = { rating: rating }
  Activities.build(response_body)
end
weakness() click to toggle source
# File lib/hackerone/client/report.rb, line 133
def weakness
  @weakness ||= Weakness.new(relationships.fetch(:weakness, {}).fetch(:data, {}).fetch(:attributes, {}))
end
writeup_classification() click to toggle source

Bounty writeups just use the key, and not the label value.

# File lib/hackerone/client/report.rb, line 142
def writeup_classification
  classification_label.split("-").first
end

Private Instance Methods

_assign_to(assignee_id, assignee_type) click to toggle source
# File lib/hackerone/client/report.rb, line 358
def _assign_to(assignee_id, assignee_type)
  request_body = {
    type: assignee_type,
  }
  request_body[:id] = assignee_id if assignee_id

  response = HackerOne::Client::Api.hackerone_api_connection.put do |req|
    req.headers["Content-Type"] = "application/json"
    req.url "reports/#{id}/assignee"
    req.body = { data: request_body }.to_json
  end
  unless response.success?
    fail("Unable to assign report #{id} to #{assignee_type} with id '#{assignee_id}'. Response status: #{response.status}, body: #{response.body}")
  end

  @report = parse_response response
end
attributes() click to toggle source
# File lib/hackerone/client/report.rb, line 346
def attributes
  @report[:attributes]
end
payment_amount(payment) click to toggle source
# File lib/hackerone/client/report.rb, line 342
def payment_amount(payment)
  payment.bounty_amount
end
payments() click to toggle source
# File lib/hackerone/client/report.rb, line 338
def payments
  activities.select { |activity| activity.is_a? Activities::BountyAwarded }
end
relationships() click to toggle source
# File lib/hackerone/client/report.rb, line 350
def relationships
  @report[:relationships]
end
vulnerability_types() click to toggle source
# File lib/hackerone/client/report.rb, line 354
def vulnerability_types
  relationships.fetch(:vulnerability_types, {}).fetch(:data, [])
end