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

fix: use GitHub App token when authed with GitHub App #103

Merged
merged 5 commits into from
Apr 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions .env-example
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
GH_TOKEN = " "
ORGANIZATION = "organization"
BATCH_SIZE = ""
BODY = ""
COMMIT_MESSAGE = ""
CREATED_AFTER_DATE = ""
DRY_RUN = ""
ENABLE_SECURITY_UPDATES = ""
EXEMPT_ECOSYSTEMS = ""
EXEMPT_REOPS = ""
FILTER_VISIBILITY = ""
GH_TOKEN = ""
GROUP_DEPENDENCIES = ""
ORGANIZATION = ""
PROJECT_ID = ""
REPOSITORY = ""
TITLE = ""
TYPE = ""

# GITHUB APP
GH_APP_ID = ""
Expand Down
27 changes: 27 additions & 0 deletions auth.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""This is the module that contains functions related to authenticating to GitHub with a personal access token."""

import github3
import requests


def auth_to_github(
Expand Down Expand Up @@ -41,3 +42,29 @@ def auth_to_github(
if not github_connection:
raise ValueError("Unable to authenticate to GitHub")
return github_connection # type: ignore


def get_github_app_installation_token(
gh_app_id: str, gh_app_private_key_bytes: bytes, gh_app_installation_id: str
) -> str | None:
"""
Get a GitHub App Installation token.

Args:
gh_app_id (str): the GitHub App ID
gh_app_private_key_bytes (bytes): the GitHub App Private Key
gh_app_installation_id (str): the GitHub App Installation ID

Returns:
str: the GitHub App token
"""
jwt_headers = github3.apps.create_jwt_headers(gh_app_private_key_bytes, gh_app_id)
url = f"https://api.github.com/app/installations/{gh_app_installation_id}/access_tokens"
Copy link
Member Author

Choose a reason for hiding this comment

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

core change of this PR


try:
response = requests.post(url, headers=jwt_headers, json=None, timeout=5)
response.raise_for_status()
except requests.exceptions.RequestException as e:
print(f"Request failed: {e}")
return None
return response.json().get("token")
6 changes: 2 additions & 4 deletions env.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,7 @@ def get_int_env_var(env_var_name: str) -> int | None:
return None


def get_env_vars(
test: bool = False,
) -> tuple[
def get_env_vars(test: bool = False) -> tuple[
str | None,
list[str],
int | None,
Expand Down Expand Up @@ -95,7 +93,7 @@ def get_env_vars(
exempt_ecosystems_list (list[str]): A list of package ecosystems to exempt from the action
"""
if not test:
# Load from .env file if it exists
# Load from .env file if it exists and not testing
dotenv_path = join(dirname(__file__), ".env")
load_dotenv(dotenv_path)

Expand Down
5 changes: 5 additions & 0 deletions evergreen.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ def main(): # pragma: no cover
token, gh_app_id, gh_app_installation_id, gh_app_private_key, ghe
)

if not token and gh_app_id and gh_app_installation_id and gh_app_private_key:
Copy link
Member Author

Choose a reason for hiding this comment

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

core change of this PR

token = auth.get_github_app_installation_token(
gh_app_id, gh_app_private_key, gh_app_installation_id
)

# If Project ID is set lookup the global project ID
if project_id:
# Check Organization is set as it is required for linking to a project
Expand Down
24 changes: 21 additions & 3 deletions test_auth.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Test cases for the auth module."""

import unittest
from unittest.mock import patch
from unittest.mock import MagicMock, patch

import auth

Expand All @@ -27,9 +27,9 @@ def test_auth_to_github_without_token(self):
Test the auth_to_github function when the token is not provided.
Expect a ValueError to be raised.
"""
with self.assertRaises(ValueError) as cm:
with self.assertRaises(ValueError) as context_manager:
auth.auth_to_github("", "", "", b"", "")
the_exception = cm.exception
the_exception = context_manager.exception
self.assertEqual(
str(the_exception),
"GH_TOKEN or the set of [GH_APP_ID, GH_APP_INSTALLATION_ID, GH_APP_PRIVATE_KEY] environment variables are not set",
Expand All @@ -45,6 +45,24 @@ def test_auth_to_github_with_ghe(self, mock_ghe):

self.assertEqual(result, "Authenticated to GitHub Enterprise")

@patch("github3.apps.create_jwt_headers", MagicMock(return_value="gh_token"))
@patch("requests.post")
def test_get_github_app_installation_token(self, mock_post):
"""
Test the get_github_app_installation_token function.
"""
dummy_token = "dummytoken"
mock_response = MagicMock()
mock_response.raise_for_status.return_value = None
mock_response.json.return_value = {"token": dummy_token}
mock_post.return_value = mock_response

result = auth.get_github_app_installation_token(
b"gh_private_token", "gh_app_id", "gh_installation_id"
)

self.assertEqual(result, dummy_token)


if __name__ == "__main__":
unittest.main()
2 changes: 1 addition & 1 deletion test_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ def setUp(self):
"GH_APP_ID",
"GH_APP_INSTALLATION_ID",
"GH_APP_PRIVATE_KEY",
"GH_TOKEN",
"GH_ENTERPRISE_URL",
"GH_TOKEN",
"GROUP_DEPENDENCIES",
"ORGANIZATION",
"PROJECT_ID",
Expand Down
Loading