Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Asset and Organization resources #10

Merged
merged 10 commits into from
Aug 9, 2024
3 changes: 3 additions & 0 deletions lib/hackerone/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@
require "json"
require "active_support"
require "active_support/core_ext/numeric/time"
require "ostruct"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should not be needed, as Ostruct is bundled with Ruby: https://github.com/ruby/ostruct?tab=readme-ov-file#installation.

I don't see anything inherently wrong with this, so fine with the diff but it in theory it shouldn't be breaking anything currently.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't need this when I was testing this locally but then when I tried to use it in another context it crashed with an "openstruct is not defined" error. I'm not sure what the cause was, maybe a different ruby version that doesn't require it by default any more or something. So I feel like it's worth leaving it in to prevent someone else from running into that, and like you say I think it should be harmless.

require_relative "client/version"
require_relative "client/report"
require_relative "client/activity"
require_relative "client/program"
require_relative "client/organization"
require_relative "client/asset"
require_relative "client/reporter"
require_relative "client/member"
require_relative "client/user"
Expand Down
60 changes: 60 additions & 0 deletions lib/hackerone/client/asset.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# frozen_string_literal: true

module HackerOne
module Client
class Asset
include ResourceHelper

DELEGATES = [
:asset_type,
:identifier,
:description,
:coverage,
:max_severity,
:confidentiality_requirement,
:integrity_requirement,
:availability_requirement,
:created_at,
:updated_at,
:archived_at,
:reference,
:state,
]

delegate *DELEGATES, to: :attributes

attr_reader :organization

def initialize(asset, organization)
@asset = asset
@organization = organization
end

def id
@asset[:id]
end

def update(attributes:)
body = {
type: "asset",
attributes: attributes
}
make_put_request("organizations/#{organization.id}/assets/#{id}", request_body: body)
end

def programs
relationships.programs[:data].map { |p| Program.new(p) }
end

private

def relationships
OpenStruct.new(@asset[:relationships])
end

def attributes
OpenStruct.new(@asset[:attributes])
end
end
end
end
34 changes: 34 additions & 0 deletions lib/hackerone/client/organization.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# frozen_string_literal: true
rzhade3 marked this conversation as resolved.
Show resolved Hide resolved

module HackerOne
module Client
class Organization
include ResourceHelper

delegate :handle, :created_at, :updated_at, to: :attributes

def initialize(org)
@organization = org
end

def id
@organization[:id]
end

def assets(page_number: 1, page_size: 100)
make_get_request(
"organizations/#{id}/assets",
params: { page: { number: page_number, size: page_size } }
).map do |asset_data|
Asset.new(asset_data, self)
end
end

private

def attributes
OpenStruct.new(@organization[:attributes])
end
end
end
end
17 changes: 15 additions & 2 deletions lib/hackerone/client/program.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,15 @@ def find_group(groupname)
groups.find { |group| group.name == groupname }
end

def structured_scopes(page_number: 1, page_size: 100)
make_get_request(
"programs/#{id}/structured_scopes",
params: { page: { number: page_number, size: page_size } }
).map do |data|
StructuredScope.new(data, self)
end
end

def update_policy(policy:)
body = {
type: "program-policy",
Expand Down Expand Up @@ -83,8 +92,6 @@ def balance
BillingBalance.new(response_body).balance
end

private

def members
@members ||= relationships.members[:data].map { |member_data| Member.new(member_data) }
end
Expand All @@ -93,6 +100,12 @@ def groups
@groups ||= relationships.groups[:data].map { |group_data| Group.new(group_data) }
end

def organization
@organization ||= Organization.new(relationships.organization[:data])
end

private

def relationships
# Relationships are only included in the /programs/:id call,
# which is why we need to do a separate call here.
Expand Down
2 changes: 1 addition & 1 deletion lib/hackerone/client/report.rb
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ def payment_total
end

def structured_scope
StructuredScope.new(relationships[:structured_scope].fetch(:data, {}))
StructuredScope.new(relationships[:structured_scope].fetch(:data, {}), program)
end

# Excludes reports where the payout amount is 0 indicating swag-only or no
Expand Down
22 changes: 20 additions & 2 deletions lib/hackerone/client/structured_scope.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,42 @@
module HackerOne
module Client
class StructuredScope
include ResourceHelper

DELEGATES = [
:asset_identifier,
:asset_type,
:availability_requirement,
:confidentiality_requirement,
:eligible_for_bounty,
:eligible_for_submission,
:instruction
:instruction,
rzhade3 marked this conversation as resolved.
Show resolved Hide resolved
:integrity_requirement,
:max_severity,
:reference
]

delegate *DELEGATES, to: :attributes

def initialize(scope)
attr_reader :program

def initialize(scope, program = nil)
@program = program
@scope = scope
end

def id
@scope[:id]
end

def update(attributes:)
body = {
type: "structured-scope",
attributes: attributes
}
make_put_request("programs/#{program.id}/structured_scopes/#{id}", request_body: body)
end

private

def attributes
Expand Down
177 changes: 177 additions & 0 deletions spec/hackerone/client/asset_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
# frozen_string_literal: true

require "spec_helper"

RSpec.describe HackerOne::Client::Asset do
before(:all) do
ENV["HACKERONE_TOKEN_NAME"] = "foo"
ENV["HACKERONE_TOKEN"] = "bar"
end
before(:each) do
stub_request(:get, "https://api.hackerone.com/v1/programs/18969").
to_return(body: <<~JSON)
{
"data": {
"id": "18969",
"type": "program",
"attributes": {
"handle": "github",
"created_at": "2016-02-02T04:05:06.000Z",
"updated_at": "2016-02-02T04:05:06.000Z"
},
"relationships": {
"organization": {
"data": {
"id": "14",
"type": "organization",
"attributes": {
"handle": "api-example",
"created_at": "2016-02-02T04:05:06.000Z",
"updated_at": "2016-02-02T04:05:06.000Z"
}
}
}
}
}
}
JSON

stub_request(:get, "https://api.hackerone.com/v1/organizations/14/assets?page%5Bnumber%5D=1&page%5Bsize%5D=100").
to_return(body: <<~JSON2)
{
"data": [
{
"id": "2",
"type": "asset",
"attributes": {
"asset_type": "domain",
"domain_name": "hackerone.com",
"description": null,
"coverage": "untested",
"max_severity": "critical",
"confidentiality_requirement": "high",
"integrity_requirement": "high",
"availability_requirement": "high",
"created_at": "2016-02-02T04:05:06.000Z",
"updated_at": "2016-02-02T04:05:06.000Z",
"archived_at": "2017-02-02T04:05:06.000Z",
"reference": "reference",
"state": "confirmed"
},
"relationships": {
"asset_tags": {
"data": [
{
"id": "1",
"type": "asset-tag",
"attributes": {
"name": "test"
},
"relationships": {
"asset_tag_category": {
"data": {
"id": "2",
"type": "asset-tag-category",
"attributes": {
"name": "test"
}
}
}
}
}
]
},
"programs": {
"data": [
{
"id": "18969",
"type": "program",
"attributes": {
"handle": "github",
"name": "team name"
}
}
]
},
"attachments": {
"data": [
{
"id": "1337",
"type": "attachment",
"attributes": {
"expiring_url": "https://attachments.s3.amazonaws.com/G74PuDP6qdEdN2rpKNLkVwZF",
"created_at": "2016-02-02T04:05:06.000Z",
"file_name": "example.png",
"content_type": "image/png",
"file_size": 16115
}
}
]
}
}
}
],
"links": {}
}
JSON2
end

after(:each) do
# clear cached programs to prevent contaminatin between tests
HackerOne::Client::Program.instance_variable_set(:@my_programs, nil)
end

let(:program) do
VCR.use_cassette(:programs) do
HackerOne::Client::Program.find("github")
end
end

let(:organization) do
program.organization
end

let(:assets) do
organization.assets
end

let(:asset) { assets[0] }

it "returns a collection" do
expect(assets).to be_kind_of(Array)
expect(assets.size).to eq(1)
end

it "returns id" do
expect(asset.id).to be_present
expect(asset.id).to eq("2")
end

it "returns organization" do
expect(asset.organization).to be_present
expect(asset.organization.id).to eq("14")
end

it "returns programs" do
expect(asset.programs).to be_kind_of(Array)
expect(asset.programs.first.id).to eq("18969")
end

it "updates the asset" do
req = stub_request(:put, "https://api.hackerone.com/v1/organizations/14/assets/2").
with { |r|
r.body == <<~BODY.strip
{"data":{"type":"asset","attributes":{"description":"This is the new description"}}}
BODY
}.
to_return(body: "{}") # we are not using the response for now so not bothering to stub it properly

asset.update(
attributes: {
description: "This is the new description"
}
)

expect(req).to have_been_requested
end
end
Loading
Loading