From c6078d49c8c5f4a7fac55aafecb21b61ff816664 Mon Sep 17 00:00:00 2001 From: Jeff Gran Date: Tue, 25 Jun 2024 13:06:51 -0600 Subject: [PATCH 01/10] Add Asset and Organization resources --- lib/hackerone/client.rb | 2 + lib/hackerone/client/asset.rb | 62 ++++++++++++++++++++++++ lib/hackerone/client/organization.rb | 34 +++++++++++++ lib/hackerone/client/program.rb | 17 ++++++- lib/hackerone/client/report.rb | 2 +- lib/hackerone/client/structured_scope.rb | 21 +++++++- 6 files changed, 133 insertions(+), 5 deletions(-) create mode 100644 lib/hackerone/client/asset.rb create mode 100644 lib/hackerone/client/organization.rb diff --git a/lib/hackerone/client.rb b/lib/hackerone/client.rb index 444e607..f5f1f95 100644 --- a/lib/hackerone/client.rb +++ b/lib/hackerone/client.rb @@ -8,6 +8,8 @@ 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" diff --git a/lib/hackerone/client/asset.rb b/lib/hackerone/client/asset.rb new file mode 100644 index 0000000..aa06903 --- /dev/null +++ b/lib/hackerone/client/asset.rb @@ -0,0 +1,62 @@ +# 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 + + def initialize(organization_id, asset) + @organization_id = organization_id + @asset = asset + end + + def id + @asset[:id] + end + + def organization_id + @organization_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{ Program.new(_1) } + end + + private + + def relationships + OpenStruct.new(@asset[:relationships]) + end + + def attributes + OpenStruct.new(@asset[:attributes]) + end + end + end +end diff --git a/lib/hackerone/client/organization.rb b/lib/hackerone/client/organization.rb new file mode 100644 index 0000000..d6f761b --- /dev/null +++ b/lib/hackerone/client/organization.rb @@ -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 |data| + Asset.new(id, data) + end + end + + private + + def attributes + OpenStruct.new(@organization[:attributes]) + end + end + end +end diff --git a/lib/hackerone/client/program.rb b/lib/hackerone/client/program.rb index ec8f3df..846b686 100644 --- a/lib/hackerone/client/program.rb +++ b/lib/hackerone/client/program.rb @@ -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(id, data) + end + end + def update_policy(policy:) body = { type: "program-policy", @@ -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 @@ -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. diff --git a/lib/hackerone/client/report.rb b/lib/hackerone/client/report.rb index 391fce9..8c90f86 100644 --- a/lib/hackerone/client/report.rb +++ b/lib/hackerone/client/report.rb @@ -108,7 +108,7 @@ def payment_total end def structured_scope - StructuredScope.new(relationships[:structured_scope].fetch(:data, {})) + StructuredScope.new(relationships[:program][:data][:id], relationships[:structured_scope].fetch(:data, {})) end # Excludes reports where the payout amount is 0 indicating swag-only or no diff --git a/lib/hackerone/client/structured_scope.rb b/lib/hackerone/client/structured_scope.rb index c51ffda..28b5c85 100644 --- a/lib/hackerone/client/structured_scope.rb +++ b/lib/hackerone/client/structured_scope.rb @@ -3,17 +3,22 @@ module HackerOne module Client class StructuredScope + include ResourceHelper + DELEGATES = [ :asset_identifier, :asset_type, :eligible_for_bounty, :eligible_for_submission, - :instruction + :instruction, + :max_severity, + :reference ] delegate *DELEGATES, to: :attributes - def initialize(scope) + def initialize(program_id, scope) + @program_id = program_id @scope = scope end @@ -21,6 +26,18 @@ def id @scope[:id] end + def program_id + @program_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 From 3577b67adf332e94d76589cc2074c8247dce6e1c Mon Sep 17 00:00:00 2001 From: Jeff Gran Date: Wed, 26 Jun 2024 08:20:56 -0600 Subject: [PATCH 02/10] require ostruct --- lib/hackerone/client.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/hackerone/client.rb b/lib/hackerone/client.rb index f5f1f95..e0e41b4 100644 --- a/lib/hackerone/client.rb +++ b/lib/hackerone/client.rb @@ -4,6 +4,7 @@ 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" From 6818b41e39bb9859c6360025e69df3f67719c3a1 Mon Sep 17 00:00:00 2001 From: Jeff Gran <115882203+jeffgran-dox@users.noreply.github.com> Date: Fri, 5 Jul 2024 10:37:55 -0600 Subject: [PATCH 03/10] Use older ruby syntax for more compatibility Co-authored-by: Rahul Zhade --- lib/hackerone/client/asset.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/hackerone/client/asset.rb b/lib/hackerone/client/asset.rb index aa06903..99b9f3b 100644 --- a/lib/hackerone/client/asset.rb +++ b/lib/hackerone/client/asset.rb @@ -45,7 +45,7 @@ def update(attributes:) end def programs - relationships.programs[:data].map{ Program.new(_1) } + relationships.programs[:data].map{ |p| Program.new(p) } end private From 2021299666ba171a64256ce486ed13a29cbc5377 Mon Sep 17 00:00:00 2001 From: Jeff Gran Date: Fri, 12 Jul 2024 14:57:01 -0600 Subject: [PATCH 04/10] Updates from CR feedback --- lib/hackerone/client/asset.rb | 4 ++-- lib/hackerone/client/organization.rb | 4 ++-- lib/hackerone/client/program.rb | 2 +- lib/hackerone/client/report.rb | 2 +- lib/hackerone/client/structured_scope.rb | 15 ++++++++------- 5 files changed, 14 insertions(+), 13 deletions(-) diff --git a/lib/hackerone/client/asset.rb b/lib/hackerone/client/asset.rb index 99b9f3b..5b9db9e 100644 --- a/lib/hackerone/client/asset.rb +++ b/lib/hackerone/client/asset.rb @@ -23,9 +23,9 @@ class Asset delegate *DELEGATES, to: :attributes - def initialize(organization_id, asset) - @organization_id = organization_id + def initialize(asset, organization_id) @asset = asset + @organization_id = organization_id end def id diff --git a/lib/hackerone/client/organization.rb b/lib/hackerone/client/organization.rb index d6f761b..e215bc0 100644 --- a/lib/hackerone/client/organization.rb +++ b/lib/hackerone/client/organization.rb @@ -19,8 +19,8 @@ def assets(page_number: 1, page_size: 100) make_get_request( "organizations/#{id}/assets", params: { page: { number: page_number, size: page_size } } - ).map do |data| - Asset.new(id, data) + ).map do |asset_data| + Asset.new(asset_data, id) end end diff --git a/lib/hackerone/client/program.rb b/lib/hackerone/client/program.rb index 846b686..eb678ee 100644 --- a/lib/hackerone/client/program.rb +++ b/lib/hackerone/client/program.rb @@ -56,7 +56,7 @@ def structured_scopes(page_number: 1, page_size: 100) "programs/#{id}/structured_scopes", params: { page: { number: page_number, size: page_size } } ).map do |data| - StructuredScope.new(id, data) + StructuredScope.new(data, self) end end diff --git a/lib/hackerone/client/report.rb b/lib/hackerone/client/report.rb index 8c90f86..57c0e58 100644 --- a/lib/hackerone/client/report.rb +++ b/lib/hackerone/client/report.rb @@ -108,7 +108,7 @@ def payment_total end def structured_scope - StructuredScope.new(relationships[:program][:data][:id], 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 diff --git a/lib/hackerone/client/structured_scope.rb b/lib/hackerone/client/structured_scope.rb index 28b5c85..6556b34 100644 --- a/lib/hackerone/client/structured_scope.rb +++ b/lib/hackerone/client/structured_scope.rb @@ -8,17 +8,22 @@ class StructuredScope DELEGATES = [ :asset_identifier, :asset_type, + :availability_requirement, + :confidentiality_requirement, :eligible_for_bounty, :eligible_for_submission, :instruction, + :integrity_requirement, :max_severity, :reference ] delegate *DELEGATES, to: :attributes - def initialize(program_id, scope) - @program_id = program_id + attr_reader :program + + def initialize(scope, program = nil) + @program = program @scope = scope end @@ -26,16 +31,12 @@ def id @scope[:id] end - def program_id - @program_id - end - def update(attributes:) body = { type: "structured-scope", attributes: attributes } - make_put_request("programs/#{@program_id}/structured_scopes/#{id}", request_body: body) + make_put_request("programs/#{program.id}/structured_scopes/#{id}", request_body: body) end private From 30bdda1231ac30d7b713681c8dacdbc70d5cf2fa Mon Sep 17 00:00:00 2001 From: Jeff Gran Date: Fri, 12 Jul 2024 15:40:13 -0600 Subject: [PATCH 05/10] add asset spec --- lib/hackerone/client/asset.rb | 12 +- lib/hackerone/client/organization.rb | 2 +- spec/hackerone/client/asset_spec.rb | 174 +++++++++++++++++++++++++++ spec/spec_helper.rb | 1 + 4 files changed, 181 insertions(+), 8 deletions(-) create mode 100644 spec/hackerone/client/asset_spec.rb diff --git a/lib/hackerone/client/asset.rb b/lib/hackerone/client/asset.rb index 5b9db9e..42e6500 100644 --- a/lib/hackerone/client/asset.rb +++ b/lib/hackerone/client/asset.rb @@ -23,25 +23,23 @@ class Asset delegate *DELEGATES, to: :attributes - def initialize(asset, organization_id) + attr_reader :organization + + def initialize(asset, organization) @asset = asset - @organization_id = organization_id + @organization = organization end def id @asset[:id] end - def organization_id - @organization_id - end - def update(attributes:) body = { type: "asset", attributes: attributes } - make_put_request("organizations/#{@organization_id}/assets/#{id}", request_body: body) + make_put_request("organizations/#{organization.id}/assets/#{id}", request_body: body) end def programs diff --git a/lib/hackerone/client/organization.rb b/lib/hackerone/client/organization.rb index e215bc0..53d63c3 100644 --- a/lib/hackerone/client/organization.rb +++ b/lib/hackerone/client/organization.rb @@ -20,7 +20,7 @@ def assets(page_number: 1, page_size: 100) "organizations/#{id}/assets", params: { page: { number: page_number, size: page_size } } ).map do |asset_data| - Asset.new(asset_data, id) + Asset.new(asset_data, self) end end diff --git a/spec/hackerone/client/asset_spec.rb b/spec/hackerone/client/asset_spec.rb new file mode 100644 index 0000000..6f56a69 --- /dev/null +++ b/spec/hackerone/client/asset_spec.rb @@ -0,0 +1,174 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe HackerOne::Client::Asset do + let(:api) { HackerOne::Client::Api.new("github") } + + 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 + + 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 diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index baf3328..9372b1c 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -4,6 +4,7 @@ require "hackerone/client" require "pry" require "vcr" +require 'webmock/rspec' RSpec.configure do |config| config.example_status_persistence_file_path = ".rspec_status" From 623dcf7c60f7dc04510dbd28da2f0b6913222347 Mon Sep 17 00:00:00 2001 From: Jeff Gran Date: Fri, 12 Jul 2024 15:46:14 -0600 Subject: [PATCH 06/10] add program.structured_scopes spec --- spec/hackerone/client/program_spec.rb | 33 +++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/spec/hackerone/client/program_spec.rb b/spec/hackerone/client/program_spec.rb index 21fff5e..529e19f 100644 --- a/spec/hackerone/client/program_spec.rb +++ b/spec/hackerone/client/program_spec.rb @@ -51,6 +51,39 @@ end end + describe "structured_scopes" do + it "returns a list of structured scopes" do + stub_request(:get, "https://api.hackerone.com/v1/programs/18969/structured_scopes?page%5Bnumber%5D=1&page%5Bsize%5D=100"). + to_return(body: <<~JSON) +{ + "data": [ + { + "id": "57", + "type": "structured-scope", + "attributes": { + "asset_identifier": "api.example.com", + "asset_type": "URL", + "confidentiality_requirement": "high", + "integrity_requirement": "high", + "availability_requirement": "high", + "max_severity": "critical", + "created_at": "2015-02-02T04:05:06.000Z", + "updated_at": "2016-05-02T04:05:06.000Z", + "instruction": null, + "eligible_for_bounty": true, + "eligible_for_submission": true, + "reference": "H001001" + } + } + ], + "links": {} +} + JSON + expect(program.structured_scopes).to be_a(Array) + expect(program.structured_scopes.first).to be_a(HackerOne::Client::StructuredScope) + end + end + describe ".incremental_activities" do it "can traverse through the activities of a program" do incremental_activities = program.incremental_activities(updated_at_after: DateTime.new(2017, 12, 4, 15, 38), page_size: 3) From 915dddd98f55b2390f903d5d21b2b497fd693f08 Mon Sep 17 00:00:00 2001 From: Jeff Gran Date: Mon, 15 Jul 2024 08:53:19 -0600 Subject: [PATCH 07/10] Fix cross-contaminated specs --- spec/hackerone/client/asset_spec.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/spec/hackerone/client/asset_spec.rb b/spec/hackerone/client/asset_spec.rb index 6f56a69..b54ffc2 100644 --- a/spec/hackerone/client/asset_spec.rb +++ b/spec/hackerone/client/asset_spec.rb @@ -3,8 +3,6 @@ require "spec_helper" RSpec.describe HackerOne::Client::Asset do - let(:api) { HackerOne::Client::Api.new("github") } - before(:all) do ENV["HACKERONE_TOKEN_NAME"] = "foo" ENV["HACKERONE_TOKEN"] = "bar" @@ -118,6 +116,11 @@ 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") From f1899ccafa4040a871fd39b4e29944f887de61b2 Mon Sep 17 00:00:00 2001 From: Jeff Gran <115882203+jeffgran-dox@users.noreply.github.com> Date: Fri, 9 Aug 2024 10:06:57 -0600 Subject: [PATCH 08/10] Update spec/spec_helper.rb Co-authored-by: Rahul Zhade --- spec/spec_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 9372b1c..5be3eb4 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -4,7 +4,7 @@ require "hackerone/client" require "pry" require "vcr" -require 'webmock/rspec' +require "webmock/rspec" RSpec.configure do |config| config.example_status_persistence_file_path = ".rspec_status" From 25dbdec8fea482073f35d05edecdbb18a8c20aab Mon Sep 17 00:00:00 2001 From: Jeff Gran <115882203+jeffgran-dox@users.noreply.github.com> Date: Fri, 9 Aug 2024 10:07:08 -0600 Subject: [PATCH 09/10] Update spec/hackerone/client/asset_spec.rb Co-authored-by: Rahul Zhade --- spec/hackerone/client/asset_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/hackerone/client/asset_spec.rb b/spec/hackerone/client/asset_spec.rb index b54ffc2..32ca878 100644 --- a/spec/hackerone/client/asset_spec.rb +++ b/spec/hackerone/client/asset_spec.rb @@ -37,7 +37,7 @@ JSON stub_request(:get, "https://api.hackerone.com/v1/organizations/14/assets?page%5Bnumber%5D=1&page%5Bsize%5D=100"). - to_return(body:<<~JSON2) + to_return(body: <<~JSON2) { "data": [ { From 5f7ac10385405cc11bcd9a23b7a0cccf1e1671a3 Mon Sep 17 00:00:00 2001 From: Jeff Gran <115882203+jeffgran-dox@users.noreply.github.com> Date: Fri, 9 Aug 2024 10:07:17 -0600 Subject: [PATCH 10/10] Update lib/hackerone/client/asset.rb Co-authored-by: Rahul Zhade --- lib/hackerone/client/asset.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/hackerone/client/asset.rb b/lib/hackerone/client/asset.rb index 42e6500..966ba17 100644 --- a/lib/hackerone/client/asset.rb +++ b/lib/hackerone/client/asset.rb @@ -43,7 +43,7 @@ def update(attributes:) end def programs - relationships.programs[:data].map{ |p| Program.new(p) } + relationships.programs[:data].map { |p| Program.new(p) } end private