Skip to content

Instantly share code, notes, and snippets.

@thiezn
Last active July 27, 2024 11:17
Show Gist options
  • Save thiezn/eeb78dcdc3902cdb2f33f9050d6d429d to your computer and use it in GitHub Desktop.
Save thiezn/eeb78dcdc3902cdb2f33f9050d6d429d to your computer and use it in GitHub Desktop.

Revisions

  1. thiezn revised this gist Nov 16, 2021. 1 changed file with 3 additions and 3 deletions.
    6 changes: 3 additions & 3 deletions hackerone_programs.py
    Original file line number Diff line number Diff line change
    @@ -235,7 +235,7 @@ def _url(self, endpoint) -> str:

    def list_programs(self) -> Set[HackerOneProgram]:
    """Retrieve a list of programs."""
    endpoint = "/programs"
    endpoint = "programs"

    programs = set()

    @@ -259,7 +259,7 @@ def list_programs(self) -> Set[HackerOneProgram]:

    def get_program(self, program_handle) -> HackerOneProgram:
    """Retrieve a program by handle."""
    endpoint = f"/programs/{program_handle}"
    endpoint = f"programs/{program_handle}"
    response = self._get(endpoint)

    return HackerOneProgram.load_from_dict(response)
    @@ -270,7 +270,7 @@ def get_assets(self, program_handle) -> Set[HackerOneAsset]:
    This is a helper function to return only the assets on a program. Useful
    when you have retrieved a list of programs as this doesn't include assets.
    """
    endpoint = f"/programs/{program_handle}"
    endpoint = f"programs/{program_handle}"
    response = self._get(endpoint)

    try:
  2. thiezn revised this gist Oct 29, 2021. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion hackerone_programs.py
    Original file line number Diff line number Diff line change
    @@ -60,7 +60,7 @@ class HackerOneAssetType(Enum):
    HARDWARE = "HARDWARE"
    OTHER_APK = "OTHER_APK"
    OTHER_IPA = "OTHER_IPA"

    TESTFLIGHT = "TESTFLIGHT"

    @dataclass
    class HackerOneAsset:
  3. thiezn created this gist Jul 18, 2021.
    294 changes: 294 additions & 0 deletions hackerone_programs.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,294 @@
    #!/usr/bin/env python3

    """Interact with HackerOne Hacker API.
    First generate an API token through the Hackerone website and initialize the class:
    >>> username = "YOUR_USER_NAME"
    >>> token = "GENERATE_AN_API_TOKEN_THROUGH_HACKERONE_WEBSITE"
    >>> session = HackerOneSession(username, token)
    To retrieve a single program
    >>> hackerone_program = session.get_program("security")
    >>> print(hackerone_program)
    <HackerOneProgram HackerOne, 16 assets>
    >>> print(hackerone_program.program.offers_bounties)
    True
    To list all programs run the following. Note it will take some time to retrieve
    all programs. Please be concious of your fellow hackers and limit the amount of
    API calls you make.
    Note that when listing programs, the assets won't be returned. Use the get_program() or
    get_assets() API call for this.
    >>> all_programs = session.list_programs()
    >>> for program in all_programs:
    >>> print(program)
    <HackerOneProgram Node.js third-party modules, 0 assets>
    <HackerOneProgram Internet Freedom (IBB), 0 assets>
    ...truncated output...
    >>> for asset in session.list_assets(all_programs[0]):
    >>> print(asset)
    <HackerOneAsset URL api.example.com>
    """

    import requests
    from dataclasses import dataclass
    from datetime import datetime

    from typing import Optional, Set
    from enum import Enum


    class HackerOneAssetType(Enum):
    """Class representing known types in HackerOne assets."""

    URL = "URL"
    OTHER = "OTHER"
    GOOGLE_PLAY_APP_ID = "GOOGLE_PLAY_APP_ID"
    APPLE_STORE_APP_ID = "APPLE_STORE_APP_ID"
    WINDOWS_APP_STORE_APP_ID = "WINDOWS_APP_STORE_APP_ID"
    CIDR = "CIDR"
    SOURCE_CODE = "SOURCE_CODE"
    DOWNLOADABLE_EXECUTABLES = "DOWNLOADABLE_EXECUTABLES"
    HARDWARE = "HARDWARE"
    OTHER_APK = "OTHER_APK"
    OTHER_IPA = "OTHER_IPA"


    @dataclass
    class HackerOneAsset:
    """Class representing an asset of a HackerOne Program."""

    id: str

    type: HackerOneAssetType
    identifier: str
    eligible_for_bounty: bool
    eligible_for_submission: bool
    max_severity: str
    created_at: datetime
    updated_at: datetime

    instuction: Optional[str]
    reference: Optional[str]
    confidentiality_requirement: Optional[str]
    integrity_requirement: Optional[str]
    availability_requirement: Optional[str]

    def __repr__(self) -> str:
    """Pretty representation of class instance."""
    return f"<HackerOneAsset {self.type} {len(self.identifier)}>"

    @classmethod
    def load_from_dict(cls, asset_dict: dict):
    """Initialize class instance from Dictionary object."""
    return cls(
    asset_dict["id"],
    HackerOneAssetType(asset_dict["attributes"]["asset_type"]),
    asset_dict["attributes"]["asset_identifier"],
    asset_dict["attributes"]["eligible_for_bounty"],
    asset_dict["attributes"]["eligible_for_submission"],
    asset_dict["attributes"]["max_severity"],
    datetime.fromisoformat(asset_dict["attributes"]["created_at"].rstrip("Z")),
    datetime.fromisoformat(asset_dict["attributes"]["updated_at"].rstrip("Z")),
    asset_dict["attributes"].get("instruction"),
    asset_dict["attributes"].get("reference"),
    asset_dict["attributes"].get("confidentiality_requirement"),
    asset_dict["attributes"].get("integrity_requirement"),
    asset_dict["attributes"].get("availability_requirement"),
    )

    def __hash__(self):
    """Allow for use in Python Sets."""
    return hash(self.id)

    def __eq__(self, other):
    """Compare two class instances."""
    if other.id == self.id:
    return True
    return False


    @dataclass
    class HackerOneProgram:
    """Class representing a single HackerOne Program."""

    id: str

    # Program attributes
    handle: str
    name: str
    currency: str
    profile_picture: str
    submission_state: str
    triage_active: str
    state: str
    started_accepting_at: datetime
    number_of_reports_for_user: int
    number_of_valid_reports_for_user: int
    bounty_earned_for_user: float
    last_invitation_accepted_at_for_user: Optional[str]
    bookmarked: bool
    allows_bounty_splitting: bool
    offers_bounties: bool

    # Assets
    assets: Set[HackerOneAsset]

    def __repr__(self) -> str:
    """Pretty representation of class instance."""
    return f"<HackerOneProgram {self.name}, {len(self.assets)} assets>"

    @property
    def program_url(self) -> str:
    """The URL to the program on HackerOne."""
    return f"https://hackerone.com/{self.handle}?type=team"

    @classmethod
    def load_from_dict(cls, program_dict: dict):
    """Initialize class instance from Dictionary object."""

    try:
    assets = {
    HackerOneAsset.load_from_dict(asset)
    for asset in program_dict["relationships"]["structured_scopes"]["data"]
    }
    except KeyError:
    # When listing programs the assets are not returned.
    assets = set()

    return cls(
    program_dict["id"],
    program_dict["attributes"]["handle"],
    program_dict["attributes"]["name"],
    program_dict["attributes"]["currency"],
    program_dict["attributes"]["profile_picture"],
    program_dict["attributes"]["submission_state"],
    program_dict["attributes"]["triage_active"],
    program_dict["attributes"]["state"],
    datetime.fromisoformat(
    program_dict["attributes"]["started_accepting_at"].rstrip("Z")
    ),
    program_dict["attributes"]["number_of_reports_for_user"],
    program_dict["attributes"]["number_of_valid_reports_for_user"],
    program_dict["attributes"]["bounty_earned_for_user"],
    program_dict["attributes"]["last_invitation_accepted_at_for_user"],
    program_dict["attributes"]["bookmarked"],
    program_dict["attributes"]["allows_bounty_splitting"],
    program_dict["attributes"]["offers_bounties"],
    assets,
    )

    def __hash__(self):
    """Allow for use in Python Sets."""
    return hash(self.id)

    def __eq__(self, other):
    """Compare two class instances."""
    if other.id == self.id:
    return True
    return False


    class HackerOneSession:
    """Class to interact with the Hacker API of HackerOne."""

    def __init__(self, username, token, version="v1"):
    self._session = requests.session()
    self.version = version

    headers = {
    "Content-Type": "application/json",
    "Accept": "application/json",
    "Hello": "HackerOne!",
    }
    self._session.auth = (username, token)
    self._session.headers.update(headers)

    def _process_response(self, response):
    """Process HTTP response returned from API."""

    if not response.ok:
    # TODO: Shall we sleep and retry on 'response.status_code == 429'?
    raise IOError(f"HTTP {response.status_code} {response.request.url}")

    return response.json()

    def _get(self, endpoint, params: dict = None):
    """Retrieve a HTTP GET endpoint."""
    url = self._url(endpoint)
    response = self._session.get(url, params=params)

    return self._process_response(response)

    def _url(self, endpoint) -> str:
    """Generate full API url."""
    url = f"https://api.hackerone.com/{self.version}/hackers/{endpoint}"
    return url

    def list_programs(self) -> Set[HackerOneProgram]:
    """Retrieve a list of programs."""
    endpoint = "/programs"

    programs = set()

    page_number = 1
    while True:
    response = self._get(endpoint, params={"page[number]": page_number})

    if not response["links"].get("next") or not response.get("data"):
    break
    else:
    page_number += 1

    programs.update(
    [
    HackerOneProgram.load_from_dict(program)
    for program in response["data"]
    ]
    )

    return programs

    def get_program(self, program_handle) -> HackerOneProgram:
    """Retrieve a program by handle."""
    endpoint = f"/programs/{program_handle}"
    response = self._get(endpoint)

    return HackerOneProgram.load_from_dict(response)

    def get_assets(self, program_handle) -> Set[HackerOneAsset]:
    """Get the assets of given program.
    This is a helper function to return only the assets on a program. Useful
    when you have retrieved a list of programs as this doesn't include assets.
    """
    endpoint = f"/programs/{program_handle}"
    response = self._get(endpoint)

    try:
    assets = {
    HackerOneAsset.load_from_dict(asset)
    for asset in response["relationships"]["structured_scopes"]["data"]
    }
    except KeyError:
    # When listing programs the assets are not returned.
    assets = set()

    return assets


    if __name__ == "__main__":
    from getpass import getpass

    username = input("Hackerone username: ").strip()
    token = getpass(f"{username} token: ").strip()
    session = HackerOneSession(username, token)
    print(session.get_program("security"))