Thanks to visit codestin.com
Credit goes to github.com

Skip to content
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
62 changes: 62 additions & 0 deletions docs/apps.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
:mod:`gidgethub.apps` --- Support for GitHub App
================================================

.. module:: gidgethub.apps

.. versionadded:: 4.1.0

This module is to help provide support for `GitHub Apps <https://developer.github.com/v3/apps/>`_.

Example on how you would obtain the access token for authenticating as a GitHub App installation::

from gidgethub.apps import get_installation_access_token

private_key = """-----BEGIN RSA PRIVATE KEY-----
zBgqFIin/uQEb0he006F9pNC6Kga0AMY5b0cCdZ4ge9qyFro2eVA
...
-----END RSA PRIVATE KEY-----
"""

access_token_response = await get_installation_access_token(
installation_id=123,
app_id=456,
private_key=private_key
)

data = gh.getitem("/rate_limit", oauth_token=access_token_response["token"])

.. coroutine:: get_installation_access_token(gh, *, installation_id, app_id, private_key)

Obtain a GitHub App's installation access token.

**installation_id** is the GitHub App installation's id.

**app_id** is the GitHub App's identifier.

**private_key** is the content of the GitHub App's private key (``.PEM`` format) file.

It returns the response from GitHub's
`Authenticating as an installation <https://developer.github.com/apps/building-github-apps/authenticating-with-github-apps/#authenticating-as-an-installation>`_ API endpoint.


.. function:: get_jwt(*, app_id, private_key)

Construct the JWT (JSON Web Token), that can be used to access endpoints
that require it.

Example::

from gidgethub.apps import get_jwt

private_key = """-----BEGIN RSA PRIVATE KEY-----
zBgqFIin/uQEb0he006F9pNC6Kga0AMY5b0cCdZ4ge9qyFro2eVA
...
-----END RSA PRIVATE KEY-----
"""

token = get_jwt(app_id=123, private_key=private_key)
data = gh.getitem(
"/app/installations",
jwt=token,
accept="application/vnd.github.machine-man-preview+json",
)
17 changes: 16 additions & 1 deletion docs/changelog.rst
Original file line number Diff line number Diff line change
@@ -1,14 +1,29 @@
Changelog
=========

4.1.0
'''''

.. note::
Under development

- Introduce :mod:`gidgethub.apps`.

- Introduce :meth:`gidgethub.apps.get_installation_access_token`
for obtaining an installation access token that can be used to authenticate as
a GitHub App installation. (`Issue #71 <https://github.com/brettcannon/gidgethub/issues/71>`_).

- Introduce :meth:`gidgethub.apps.get_jwt` for constructing the JSON Web Token
that can be used to access endpoints that require it.

4.0.0
'''''

- Add :meth:`gidgethub.abc.GitHubAPI.graphql` and related exceptions.
- Add :exc:`gidgethub.BadRequestUnknownError` when something other than JSON is
returned for a 422 response.
- Remove `gidgethub.treq`; tests were not passing and a request for help on
Twitter came back with no reponse (happy to add back if someone steps forward
Twitter came back with no response (happy to add back if someone steps forward
to help out).
- Remove `gidgethub.test` from the distribution.
- Introduce :mod:`gidgethub.actions`.
Expand Down
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ Contents
__init__
sansio
actions
apps
routing
abc
aiohttp
Expand Down
2 changes: 1 addition & 1 deletion gidgethub/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""An async GitHub API library"""
__version__ = "4.0.0"
__version__ = "4.1.0.dev1"

import http
from typing import Any
Expand Down
43 changes: 43 additions & 0 deletions gidgethub/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"""Support for GitHub Actions."""
from typing import cast, Any, Dict

import time
import jwt

from gidgethub.abc import GitHubAPI


def get_jwt(*, app_id: str, private_key: str) -> str:

Choose a reason for hiding this comment

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

In octomachinery, I used to have it as a function but then I implemented an object that represents a private key with fingerprint pinning and it turned out that it's handy to have it as a method:
https://github.com/sanitizers/octomachinery/blob/6b41c1df510f8bbf090ea290941261396e2cb559/octomachinery/github/models/private_key.py

"""Construct the JWT (JSON Web Token), used for GitHub App authentication."""
time_int = int(time.time())
payload = {"iat": time_int, "exp": time_int + (10 * 60), "iss": app_id}
encoded = jwt.encode(payload, private_key, algorithm="RS256")
bearer_token = encoded.decode("utf-8")

return bearer_token


async def get_installation_access_token(
gh: GitHubAPI, *, installation_id: str, app_id: str, private_key: str
) -> Dict[str, Any]:
"""Obtain a GitHub App's installation access token.
Return a dictionary containing access token and expiration time.
(https://developer.github.com/v3/apps/#create-a-new-installation-token)
"""
access_token_url = f"/app/installations/{installation_id}/access_tokens"
token = get_jwt(app_id=app_id, private_key=private_key)
response = await gh.post(
access_token_url,
data=b"",
jwt=token,
accept="application/vnd.github.machine-man-preview+json",
)
# example response
# {
# "token": "v1.1f699f1069f60xxx",
# "expires_at": "2016-07-11T22:14:10Z"
# }

return cast(Dict[str, Any], response)
6 changes: 5 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ build-backend = "flit.buildapi"
module = "gidgethub"
author = "Brett Cannon"
author-email = "[email protected]"
requires = ["uritemplate>=3.0.1"]
requires = [
"uritemplate>=3.0.1",
"PyJWT>=1.7.1",

Choose a reason for hiding this comment

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

It should've been PyJWT[crypto] as per their docs:
Screenshot_2020-04-19-08-52-35-032_com android chrome

Copy link
Collaborator

Choose a reason for hiding this comment

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

I opened #116 to track this.

"cryptography>=2.9"

Choose a reason for hiding this comment

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

It's not a direct dependency, why do you specify it here?

]
requires-python = ">=3.6"
license = "Apache"
keywords = "github sans-io async"
Expand Down
Empty file.
27 changes: 27 additions & 0 deletions tests/samples/rsa_key/test_rsa_key
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEA1HgzBfJv2cOjQryCwe8NEelriOTNFWKZUivevUrRhlqcmZJd
CvuCJRr+xCN+OmO8qwgJJR98feNujxVg+J9Ls3/UOA4HcF9nYH6aqVXELAE8Hk/A
Lvxi96ms1DDuAvQGaYZ+lANxlvxeQFOZSbjkz/9mh8aLeGKwqJLp3p+OhUBQpwvA
UAPg82+OUtgTW3nSljjeFr14B8qAneGSc/wl0ni++1SRZUXFSovzcqQOkla3W27r
rLfrD6LXgj/TsDs4vD1PnIm1zcVenKT7TfYI17bsG/O/Wecwz2Nl19pL7gDosNru
F3ogJWNq1Lyn/ijPQnkPLpZHyhvuiycYcI3DiQIDAQABAoIBAQCt9uzwBZ0HVGQs
lGULnUu6SsC9iXlR9TVMTpdFrij4NODb7Tc5cs0QzJWkytrjvB4Se7XhK3KnMLyp
cvu/Fc7J3fRJIVN98t+V5pOD6rGAxlIPD4Vv8z6lQcw8wQNgb6WAaZriXh93XJNf
YBO2hSj0FU5CBZLUsxmqLQBIQ6RR/OUGAvThShouE9K4N0vKB2UPOCu5U+d5zS3W
44Q5uatxYiSHBTYIZDN4u27Nfo5WA+GTvFyeNsO6tNNWlYfRHSBtnm6SZDY/5i4J
fxP2JY0waM81KRvuHTazY571lHM/TTvFDRUX5nvHIu7GToBKahfVLf26NJuTZYXR
5c09GAXBAoGBAO7a9M/dvS6eDhyESYyCjP6w61jD7UYJ1fudaYFrDeqnaQ857Pz4
BcKx3KMmLFiDvuMgnVVj8RToBGfMV0zP7sDnuFRJnWYcOeU8e2sWGbZmWGWzv0SD
+AhppSZThU4mJ8aa/tgsepCHkJnfoX+3wN7S9NfGhM8GDGxTHJwBpxINAoGBAOO4
ZVtn9QEblmCX/Q5ejInl43Y9nRsfTy9lB9Lp1cyWCJ3eep6lzT60K3OZGVOuSgKQ
vZ/aClMCMbqsAAG4fKBjREA6p7k4/qaMApHQum8APCh9WPsKLaavxko8ZDc41kZt
hgKyUs2XOhW/BLjmzqwGryidvOfszDwhH7rNVmRtAoGBALYGdvrSaRHVsbtZtRM3
imuuOCx1Y6U0abZOx9Cw3PIukongAxLlkL5G/XX36WOrQxWkDUK930OnbXQM7ZrD
+5dW/8p8L09Zw2VHKmb5eK7gYA1hZim4yJTgrdL/Y1+jBDz+cagcfWsXZMNfAZxr
VLh628x0pVF/sof67pqVR9UhAoGBAMcQiLoQ9GJVhW1HMBYBnQVnCyJv1gjBo+0g
emhrtVQ0y6+FrtdExVjNEzboXPWD5Hq9oKY+aswJnQM8HH1kkr16SU2EeN437pQU
zKI/PtqN8AjNGp3JVgLioYp/pHOJofbLA10UGcJTMpmT9ELWsVA8P55X1a1AmYDu
y9f2bFE5AoGAdjo95mB0LVYikNPa+NgyDwLotLqrueb9IviMmn6zKHCwiOXReqXD
X9slB8RA15uv56bmN04O//NyVFcgJ2ef169GZHiRFIgIy0Pl8LYkMhCYKKhyqM7g
xN+SqGqDTKDC22j00S7jcvCaa1qadn1qbdfukZ4NXv7E2d/LO0Y2Kkc=
-----END RSA PRIVATE KEY-----
50 changes: 50 additions & 0 deletions tests/test_apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
from unittest import mock

import importlib_resources
import jwt
import pytest

from gidgethub import apps
from .test_abc import MockGitHubAPI

from .samples import rsa_key as rsa_key_samples


class TestGitHubAppUtils:

"""Tests for GitHub App utilities."""

@mock.patch("time.time")
def test_get_jwt(self, time_mock):
app_id = 12345

time_mock.return_value = 1587069751.5588422

# test file copied from https://github.com/jpadilla/pyjwt/blob/master/tests/keys/testkey_rsa
private_key = importlib_resources.read_binary(rsa_key_samples, "test_rsa_key")

result = apps.get_jwt(app_id=app_id, private_key=private_key)
expected_payload = {
"iat": 1587069751,
"exp": 1587069751 + (10 * 60),
"iss": app_id,
}

assert result == jwt.encode(
expected_payload, private_key, algorithm="RS256"
).decode("utf-8")

@pytest.mark.asyncio
async def test_get_installation_access_token(self):
gh = MockGitHubAPI()
installation_id = 6789
app_id = 12345

private_key = importlib_resources.read_binary(rsa_key_samples, "test_rsa_key")

await apps.get_installation_access_token(
gh, installation_id=installation_id, app_id=app_id, private_key=private_key
)

assert gh.url == "https://api.github.com/app/installations/6789/access_tokens"
assert gh.body == b""
4 changes: 2 additions & 2 deletions tests/test_sansio.py
Original file line number Diff line number Diff line change
Expand Up @@ -419,7 +419,7 @@ def test_201(self):
"link": '<http://example.com>; test="unimportant"',
}
data = {
"id": 208045946,
"id": 208_045_946,
"url": "https://api.github.com/repos/octocat/Hello-World/labels/bug",
"name": "bug",
"color": "f29513",
Expand Down Expand Up @@ -505,7 +505,7 @@ def test_no_ratelimit(self):
"link": '<http://example.com>; test="unimportant"',
}
data = {
"id": 208045946,
"id": 208_045_946,
"url": "https://api.github.com/repos/octocat/Hello-World/labels/bug",
"name": "bug",
"color": "f29513",
Expand Down