EasyRecon/Hunt3r

View on GitHub
backend/lib/platforms/hackerone.rb

Summary

Maintainability
A
2 hrs
Test Coverage
class Hackerone
  def self.get_jwt(platform)
    # There is no JWT on hackerone, use this method to make sure the login is valid
    response = api_request(platform, 'https://api.hackerone.com/v1/hackers/me/reports')
    return unless response.code == 200

    platform.update(jwt: platform.password)
    platform.jwt
  end

  def self.get_username(platform)
    jwt = get_jwt(platform)
    return if jwt.nil?

    platform.hunter_username
  end

  def self.update_programs(platform, page_id = 1)
    get_jwt(platform)

    programs_infos = get_programs_infos(platform, page_id)
    return if programs_infos.nil?

    parse_programs(programs_infos[:programs], platform)
    update_programs(platform, page_id + 1) if programs_infos[:next_page]
  end

  def self.get_programs_infos(platform, page_id)
    response = api_request(platform, "https://api.hackerone.com/v1/hackers/programs?page%5Bnumber%5D=#{page_id}")
    if response&.code == 429
      sleep 65
      get_programs_infos(platform, page_id)
    end
    return unless response.code == 200

    json_body = JSON.parse(response.body)
    { next_page: json_body['links']['next'], programs: json_body['data'] }
  end

  def self.parse_programs(programs, platform)
    programs.each do |program|
      next unless program['attributes']['submission_state'] == 'open'

      slug = program['attributes']['handle']

      # In case it is not yet present in the database we add the program
      if Program.find_by(slug: slug).nil?
        vdp = !program['attributes']['offers_bounties']
        name = program['attributes']['handle']

        Program.create(name: name, slug: slug, vdp: vdp, platform_id: platform.id)
      end

      # And in any case we update the scopes of the program
      update_scopes(platform, slug)
    end
  end

  def self.update_scopes(platform, slug)
    scope_infos = get_scope_infos(platform, slug)
    return if scope_infos.nil? || scope_infos.empty?

    parse_scopes(scope_infos, slug, platform)
  end

  def self.get_scope_infos(platform, slug)
    response = api_request(platform, "https://api.hackerone.com/v1/hackers/programs/#{slug}")
    if response&.code == 429
      sleep 65
      get_scope_infos(platform, slug)
    end
    return unless response.code == 200

    response_json = JSON.parse(response.body)
    response_json['relationships']['structured_scopes']['data']
  end

  def self.parse_scopes(scopes, slug, platform)
    program = Program.find_by(slug: slug)
    scopes.each do |scope|
      next unless scope['attributes']['eligible_for_submission']

      endpoint = scope['attributes']['asset_identifier']
      type = scope['attributes']['asset_type']
      next unless type == 'URL'
      next unless Scope.find_by(scope: endpoint).nil?

      scope = Scope.new(scope: endpoint, scope_type: type, program_id: program.id)
      scope.normalize(platform)
    end
  end

  def self.get_reports(platform)
    response = api_request(platform, 'https://api.hackerone.com/v1/hackers/me/reports')
    return unless response.code == 200

    reports = JSON.parse(response.body)['data']
    parse_reports(reports, platform)
  end

  def self.parse_reports(reports, platform)
    reports.each do |report|

      report_infos = {
        report_id: report['id'],
        report_title: report['attributes']['title'],
        report_date: Date.parse(report['attributes']['created_at']).strftime('%Y-%m-%d'),
        report_status: report['attributes']['state'],
        severity: report['relationships']['severity']['data']['attributes']['rating'],
        currency: 'USD',
        collab: false
      }

      if report['attributes']['bounty_awarded_at']
        report_infos[:reward] = get_report_reward(platform, report_infos[:report_id])
      end

      current_report = PlatformStat.find_by(report_id: report_infos[:report_id])
      if current_report.nil?
        report_infos[:platform_id] = platform.id
        PlatformStat.create(report_infos)
      else
        current_report.update(report_infos)
      end
    end
  end

  def self.get_report_reward(platform, report_id)
    response = api_request(platform, "https://api.hackerone.com/v1/hackers/reports/#{report_id}")
    return unless response.code == 200

    report_activities = JSON.parse(response.body)['data']['relationships']['activities']['data']

    reward = 0
    report_activities.each do |activity|
      next unless activity['type'] == 'activity-bounty-awarded'

      reward += activity['attributes']['bounty_amount'].to_f
      reward += activity['attributes']['bonus_amount'].to_f
    end

    reward
  end

  def self.api_request(platform, url)
    Typhoeus::Request.get(
      url,
      userpwd: "#{platform.hunter_username}:#{platform.password}",
      headers: { 'Accept': 'application/json' }
    )
  end
end