Skip to content

Commit

Permalink
Add Asset and Organization resources (#10)
Browse files Browse the repository at this point in the history
* Add Asset and Organization resources

* require ostruct

* Use older ruby syntax for more compatibility

Co-authored-by: Rahul Zhade <[email protected]>

* Updates from CR feedback

* add asset spec

* add program.structured_scopes spec

* Fix cross-contaminated specs

* Update spec/spec_helper.rb

Co-authored-by: Rahul Zhade <[email protected]>

* Update spec/hackerone/client/asset_spec.rb

Co-authored-by: Rahul Zhade <[email protected]>

* Update lib/hackerone/client/asset.rb

Co-authored-by: Rahul Zhade <[email protected]>

---------

Co-authored-by: Rahul Zhade <[email protected]>
  • Loading branch information
jeffgran-dox and rzhade3 authored Aug 9, 2024
1 parent cd44f29 commit 05dc74d
Show file tree
Hide file tree
Showing 9 changed files with 344 additions and 5 deletions.
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"
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

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,
: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

0 comments on commit 05dc74d

Please sign in to comment.