Skip to content

Commit

Permalink
Accept Client ID strings as app_id
Browse files Browse the repository at this point in the history
This includes saving integer IDs as `iss` strings in JWT.

Refs:
* PyGithub/PyGithub#3213
* PyGithub/PyGithub#3214
* jpadilla/pyjwt#1039
  • Loading branch information
webknjaz committed Feb 19, 2025
1 parent e09f3bc commit 0803e41
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 6 deletions.
42 changes: 37 additions & 5 deletions src/awx_plugins/credentials/github_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,16 +132,48 @@ class MaybeBaseURLKwarg(TypedDict, total=False):
base_url: str


GH_CLIENT_ID_TRAILER_LENGTH = 16
HEXADECIMAL_BASE = 16


def _is_intish(app_id_candidate: str | int) -> bool:
return isinstance(app_id_candidate, int) or app_id_candidate.isdigit()


def _is_client_id(client_id_candidate: str) -> bool:
client_id_prefix = 'Iv1.'
if not client_id_candidate.startswith(client_id_prefix):
return False

client_id_trailer = client_id_candidate.removeprefix(client_id_prefix)

if len(client_id_trailer) != GH_CLIENT_ID_TRAILER_LENGTH:
return False

try:
int(client_id_trailer, base=HEXADECIMAL_BASE)
except ValueError:
return False

return True


def _is_app_or_client_id(app_or_client_id_candidate: str | int) -> bool:
if _is_intish(app_or_client_id_candidate):
return True

assert not isinstance(app_or_client_id_candidate, int) # type narrowing
return _is_client_id(app_or_client_id_candidate)


def _validate_inputs(
app_id: str, install_id: int | str,
app_id: int | str, install_id: int | str,
) -> None:
if not app_id.isdigit():
if not _is_app_or_client_id(app_id):
raise ValueError(
f'Expected GitHub App ID to be an integer but got {app_id !r}',
'Expected GitHub App or Client ID to be an integer or a string '
f'starting with `Iv1.` followed by 16 hexadecimal digits, '
f'but got {app_id !r}',
)

if not _is_intish(install_id):
Expand All @@ -154,7 +186,7 @@ def _validate_inputs(
def extract_github_app_install_token( # noqa: WPS210
*,
github_api_url: str,
app_id: str,
app_id: int | str,
private_rsa_key: str,
install_id: int | str,
**_discarded_kwargs: Unpack[EmptyKwargs],
Expand All @@ -174,7 +206,7 @@ def extract_github_app_install_token( # noqa: WPS210
_validate_inputs(app_id, install_id)

auth = Auth.AppAuth(
app_id=int(app_id),
app_id=str(app_id),
private_key=private_rsa_key,
).get_installation_auth(installation_id=int(install_id))

Expand Down
52 changes: 51 additions & 1 deletion tests/github_app_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,50 @@ class AppInstallIds(TypedDict):
'app_id': 'invalid',
'install_id': '666',
},
"^Expected GitHub App ID to be an integer but got 'invalid'$",
'^Expected GitHub App or Client ID to be an integer or a string '
r'starting with `Iv1\.` followed by 16 hexadecimal digits, but got'
" 'invalid'$",
id='gh-app-id-broken-text',
),
pytest.param(
{
'app_id': 'Iv1.bbbbbbbbbbbbbbb',
'install_id': '666',
},
'^Expected GitHub App or Client ID to be an integer or a string '
r'starting with `Iv1\.` followed by 16 hexadecimal digits, but got'
" 'Iv1.bbbbbbbbbbbbbbb'$",
id='gh-app-id-client-id-not-enough-chars',
),
pytest.param(
{
'app_id': 'Iv1.bbbbbbbbbbbbbbbx',
'install_id': '666',
},
'^Expected GitHub App or Client ID to be an integer or a string '
r'starting with `Iv1\.` followed by 16 hexadecimal digits, but got'
" 'Iv1.bbbbbbbbbbbbbbbx'$",
id='gh-app-id-client-id-broken-hex',
),
pytest.param(
{
'app_id': 'Iv1.bbbbbbbbbbbbbbbbb',
'install_id': '666',
},
'^Expected GitHub App or Client ID to be an integer or a string '
r'starting with `Iv1\.` followed by 16 hexadecimal digits, but got'
" 'Iv1.bbbbbbbbbbbbbbbbb'$",
id='gh-app-id-client-id-too-many-chars',
),
pytest.param(
{
'app_id': 999,
'install_id': 'invalid',
},
'^Expected GitHub App Installation ID to be an integer '
"but got 'invalid'$",
id='gh-app-invalid-install-id-with-int-app-id',
),
pytest.param(
{
'app_id': '999',
Expand All @@ -37,6 +78,15 @@ class AppInstallIds(TypedDict):
"but got 'invalid'$",
id='gh-app-invalid-install-id-with-str-digit-app-id',
),
pytest.param(
{
'app_id': 'Iv1.cccccccccccccccc',
'install_id': 'invalid',
},
'^Expected GitHub App Installation ID to be an integer '
"but got 'invalid'$",
id='gh-app-invalid-install-id-with-client-id',
),
),
)
def test_github_app_invalid_args(
Expand Down

0 comments on commit 0803e41

Please sign in to comment.