mirror of
https://github.com/status-im/PyGithub.git
synced 2026-09-01 11:21:16 +00:00
Support full GitHub app authentication (#1986)
* Support full GitHub app authentication Refactor GithubIntegration class and add test case for app authentication Add permissions and repository properties in InstallationAuthorization Set JWT_EXPIRY=60 by default in GithubIntegration constructor * Modify existing testcases for GithubIntegration as per the framework and add missing tests * Provide installation ID for creating the access token instead of getting the first installation * Add optional permissions support for installation access token * Add lock around app authentication * Keep compatibility for importing GithubIntegration from MainClass * Group app authentication parameters in a class Co-authored-by: Malik Ammar Akbar <malikammar.akbar@pfizer.com> Co-authored-by: Enrico Minack <github@enrico.minack.dev>
This commit is contained in:
co-authored by
Malik Ammar Akbar
Enrico Minack
parent
7cf3dfc18e
commit
5e27c10a31
@@ -0,0 +1,41 @@
|
||||
############################ Copyrights and license ############################
|
||||
# #
|
||||
# Copyright 2023 Denis Blanchette <denisblanchette@gmail.com> #
|
||||
# #
|
||||
# This file is part of PyGithub. #
|
||||
# http://pygithub.readthedocs.io/ #
|
||||
# #
|
||||
# PyGithub is free software: you can redistribute it and/or modify it under #
|
||||
# the terms of the GNU Lesser General Public License as published by the Free #
|
||||
# Software Foundation, either version 3 of the License, or (at your option) #
|
||||
# any later version. #
|
||||
# #
|
||||
# PyGithub is distributed in the hope that it will be useful, but WITHOUT ANY #
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS #
|
||||
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more #
|
||||
# details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU Lesser General Public License #
|
||||
# along with PyGithub. If not, see <http://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
################################################################################
|
||||
|
||||
|
||||
class AppAuthentication:
|
||||
def __init__(
|
||||
self,
|
||||
app_id,
|
||||
private_key,
|
||||
installation_id,
|
||||
token_permissions=None,
|
||||
):
|
||||
assert isinstance(app_id, (int, str)), app_id
|
||||
assert isinstance(private_key, str)
|
||||
assert isinstance(installation_id, int), installation_id
|
||||
assert token_permissions is None or isinstance(
|
||||
token_permissions, dict
|
||||
), token_permissions
|
||||
self.app_id = app_id
|
||||
self.private_key = private_key
|
||||
self.installation_id = installation_id
|
||||
self.token_permissions = token_permissions
|
||||
@@ -0,0 +1,10 @@
|
||||
from typing import Optional, Dict, Union
|
||||
|
||||
class AppAuthentication:
|
||||
def __init__(
|
||||
self,
|
||||
app_id: Union[int, str],
|
||||
private_key: str,
|
||||
installation_id: int,
|
||||
token_permissions: Optional[Dict[str, str]] = ...,
|
||||
): ...
|
||||
@@ -131,3 +131,20 @@ deploymentStatusEnhancementsPreview = "application/vnd.github.flash-preview+json
|
||||
|
||||
# https://developer.github.com/changes/2019-12-03-internal-visibility-changes/
|
||||
repoVisibilityPreview = "application/vnd.github.nebula-preview+json"
|
||||
|
||||
DEFAULT_BASE_URL = "https://api.github.com"
|
||||
DEFAULT_STATUS_URL = "https://status.github.com"
|
||||
# As of 2018-05-17, Github imposes a 10s limit for completion of API requests.
|
||||
# Thus, the timeout should be slightly > 10s to account for network/front-end
|
||||
# latency.
|
||||
DEFAULT_TIMEOUT = 15
|
||||
DEFAULT_PER_PAGE = 30
|
||||
|
||||
# JWT expiry in seconds. Could be set for max 600 seconds (10 minutes).
|
||||
# https://docs.github.com/en/developers/apps/building-github-apps/authenticating-with-github-apps#authenticating-as-a-github-app
|
||||
DEFAULT_JWT_EXPIRY = 300
|
||||
MIN_JWT_EXPIRY = 15
|
||||
MAX_JWT_EXPIRY = 600
|
||||
# https://docs.github.com/en/developers/apps/building-github-apps/authenticating-with-github-apps#generating-a-json-web-token-jwt
|
||||
# "The time the JWT was created. To protect against clock drift, we recommend you set this 60 seconds in the past."
|
||||
DEFAULT_JWT_ISSUED_AT = -60
|
||||
|
||||
@@ -0,0 +1,199 @@
|
||||
import time
|
||||
|
||||
import deprecated
|
||||
import jwt
|
||||
|
||||
from github import Consts
|
||||
from github.GithubException import GithubException
|
||||
from github.Installation import Installation
|
||||
from github.InstallationAuthorization import InstallationAuthorization
|
||||
from github.PaginatedList import PaginatedList
|
||||
from github.Requester import Requester
|
||||
|
||||
|
||||
class GithubIntegration:
|
||||
"""
|
||||
Main class to obtain tokens for a GitHub integration.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
integration_id,
|
||||
private_key,
|
||||
base_url=Consts.DEFAULT_BASE_URL,
|
||||
jwt_expiry=Consts.DEFAULT_JWT_EXPIRY,
|
||||
jwt_issued_at=Consts.DEFAULT_JWT_ISSUED_AT,
|
||||
):
|
||||
"""
|
||||
:param integration_id: int
|
||||
:param private_key: string
|
||||
:param base_url: string
|
||||
:param jwt_expiry: int. Expiry of the JWT used to get the information about this integration.
|
||||
The default expiration is in 5 minutes and is capped at 10 minutes according to GitHub documentation
|
||||
https://docs.github.com/en/developers/apps/building-github-apps/authenticating-with-github-apps#generating-a-json-web-token-jwt
|
||||
:param jwt_issued_at: int. Number of seconds, relative to now, to set for the "iat" (issued at) parameter.
|
||||
The default value is -60 to protect against clock drift
|
||||
"""
|
||||
assert isinstance(integration_id, (int, str)), integration_id
|
||||
assert isinstance(private_key, str), "supplied private key should be a string"
|
||||
assert isinstance(base_url, str), base_url
|
||||
assert isinstance(jwt_expiry, int), jwt_expiry
|
||||
assert Consts.MIN_JWT_EXPIRY <= jwt_expiry <= Consts.MAX_JWT_EXPIRY, jwt_expiry
|
||||
assert isinstance(jwt_issued_at, int)
|
||||
|
||||
self.base_url = base_url
|
||||
self.integration_id = integration_id
|
||||
self.private_key = private_key
|
||||
self.jwt_expiry = jwt_expiry
|
||||
self.jwt_issued_at = jwt_issued_at
|
||||
self.__requester = Requester(
|
||||
login_or_token=None,
|
||||
password=None,
|
||||
jwt=self.create_jwt(),
|
||||
app_auth=None,
|
||||
base_url=self.base_url,
|
||||
timeout=Consts.DEFAULT_TIMEOUT,
|
||||
user_agent="PyGithub/Python",
|
||||
per_page=Consts.DEFAULT_PER_PAGE,
|
||||
verify=True,
|
||||
retry=None,
|
||||
pool_size=None,
|
||||
)
|
||||
|
||||
def _get_headers(self):
|
||||
"""
|
||||
Get headers for the requests.
|
||||
|
||||
:return: dict
|
||||
"""
|
||||
return {
|
||||
"Authorization": f"Bearer {self.create_jwt()}",
|
||||
"Accept": Consts.mediaTypeIntegrationPreview,
|
||||
"User-Agent": "PyGithub/Python",
|
||||
}
|
||||
|
||||
def _get_installed_app(self, url):
|
||||
"""
|
||||
Get installation for the given URL.
|
||||
|
||||
:param url: str
|
||||
:rtype: :class:`github.Installation.Installation`
|
||||
"""
|
||||
headers, response = self.__requester.requestJsonAndCheck(
|
||||
"GET", url, headers=self._get_headers()
|
||||
)
|
||||
|
||||
return Installation(
|
||||
requester=self.__requester,
|
||||
headers=headers,
|
||||
attributes=response,
|
||||
completed=True,
|
||||
)
|
||||
|
||||
def create_jwt(self):
|
||||
"""
|
||||
Create a signed JWT
|
||||
https://docs.github.com/en/developers/apps/building-github-apps/authenticating-with-github-apps#authenticating-as-a-github-app
|
||||
|
||||
:return string:
|
||||
"""
|
||||
now = int(time.time())
|
||||
payload = {
|
||||
"iat": now + self.jwt_issued_at,
|
||||
"exp": now + self.jwt_expiry,
|
||||
"iss": self.integration_id,
|
||||
}
|
||||
encrypted = jwt.encode(payload, key=self.private_key, algorithm="RS256")
|
||||
|
||||
if isinstance(encrypted, bytes):
|
||||
encrypted = encrypted.decode("utf-8")
|
||||
|
||||
return encrypted
|
||||
|
||||
def get_access_token(self, installation_id, permissions=None):
|
||||
"""
|
||||
:calls: `POST /app/installations/{installation_id}/access_tokens <https://docs.github.com/en/rest/apps/apps#create-an-installation-access-token-for-an-app>`
|
||||
:param installation_id: int
|
||||
:param permissions: dict
|
||||
:return: :class:`github.InstallationAuthorization.InstallationAuthorization`
|
||||
"""
|
||||
if permissions is None:
|
||||
permissions = {}
|
||||
|
||||
if not isinstance(permissions, dict):
|
||||
raise GithubException(
|
||||
status=400, data={"message": "Invalid permissions"}, headers=None
|
||||
)
|
||||
|
||||
body = {"permissions": permissions}
|
||||
headers, response = self.__requester.requestJsonAndCheck(
|
||||
"POST",
|
||||
f"/app/installations/{installation_id}/access_tokens",
|
||||
input=body,
|
||||
)
|
||||
|
||||
return InstallationAuthorization(
|
||||
requester=self.__requester,
|
||||
headers=headers,
|
||||
attributes=response,
|
||||
completed=True,
|
||||
)
|
||||
|
||||
@deprecated.deprecated("Use get_repo_installation")
|
||||
def get_installation(self, owner, repo):
|
||||
"""
|
||||
Deprecated by get_repo_installation
|
||||
|
||||
:calls: `GET /repos/{owner}/{repo}/installation <https://docs.github.com/en/rest/reference/apps#get-a-repository-installation-for-the-authenticated-app>`
|
||||
:param owner: str
|
||||
:param repo: str
|
||||
:rtype: :class:`github.Installation.Installation`
|
||||
"""
|
||||
return self._get_installed_app(url=f"/repos/{owner}/{repo}/installation")
|
||||
|
||||
def get_installations(self):
|
||||
"""
|
||||
:calls: GET /app/installations <https://docs.github.com/en/rest/reference/apps#list-installations-for-the-authenticated-app>
|
||||
:rtype: :class:`github.PaginatedList.PaginatedList[github.Installation.Installation]`
|
||||
"""
|
||||
return PaginatedList(
|
||||
contentClass=Installation,
|
||||
requester=self.__requester,
|
||||
firstUrl="/app/installations",
|
||||
firstParams=None,
|
||||
headers=self._get_headers(),
|
||||
list_item="installations",
|
||||
)
|
||||
|
||||
def get_org_installation(self, org):
|
||||
"""
|
||||
:calls: `GET /orgs/{org}/installation <https://docs.github.com/en/rest/apps/apps#get-an-organization-installation-for-the-authenticated-app>`
|
||||
:param org: str
|
||||
:rtype: :class:`github.Installation.Installation`
|
||||
"""
|
||||
return self._get_installed_app(url=f"/orgs/{org}/installation")
|
||||
|
||||
def get_repo_installation(self, owner, repo):
|
||||
"""
|
||||
:calls: `GET /repos/{owner}/{repo}/installation <https://docs.github.com/en/rest/reference/apps#get-a-repository-installation-for-the-authenticated-app>`
|
||||
:param owner: str
|
||||
:param repo: str
|
||||
:rtype: :class:`github.Installation.Installation`
|
||||
"""
|
||||
return self._get_installed_app(url=f"/repos/{owner}/{repo}/installation")
|
||||
|
||||
def get_user_installation(self, username):
|
||||
"""
|
||||
:calls: `GET /users/{username}/installation <https://docs.github.com/en/rest/apps/apps#get-a-user-installation-for-the-authenticated-app>`
|
||||
:param username: str
|
||||
:rtype: :class:`github.Installation.Installation`
|
||||
"""
|
||||
return self._get_installed_app(url=f"/users/{username}/installation")
|
||||
|
||||
def get_app_installation(self, installation_id):
|
||||
"""
|
||||
:calls: `GET /app/installations/{installation_id} <https://docs.github.com/en/rest/apps/apps#get-an-installation-for-the-authenticated-app>`
|
||||
:param installation_id: int
|
||||
:rtype: :class:`github.Installation.Installation`
|
||||
"""
|
||||
return self._get_installed_app(url=f"/app/installations/{installation_id}")
|
||||
@@ -0,0 +1,34 @@
|
||||
from typing import Union, Optional, Dict
|
||||
|
||||
from github.Installation import Installation
|
||||
from github.InstallationAuthorization import InstallationAuthorization
|
||||
from github.PaginatedList import PaginatedList
|
||||
from github.Requester import Requester
|
||||
|
||||
class GithubIntegration:
|
||||
integration_id: Union[int, str] = ...
|
||||
private_key: str = ...
|
||||
base_url: str = ...
|
||||
jwt_expiry: int = ...
|
||||
jwt_issued_at: int = ...
|
||||
__requester: Requester = ...
|
||||
def __init__(
|
||||
self,
|
||||
integration_id: Union[int, str],
|
||||
private_key: str,
|
||||
base_url: str = ...,
|
||||
jwt_expiry: int = ...,
|
||||
jwt_issued_at: int = ...,
|
||||
) -> None: ...
|
||||
def _get_installed_app(self, url: str) -> Installation: ...
|
||||
def _get_headers(self) -> Dict[str, str]: ...
|
||||
def create_jwt(self, expiration: int = ...) -> str: ...
|
||||
def get_access_token(
|
||||
self, installation_id: int, permissions: Optional[Dict[str, str]] = ...
|
||||
) -> InstallationAuthorization: ...
|
||||
def get_app_installation(self, installation_id: int) -> Installation: ...
|
||||
def get_installation(self, owner: str, repo: str) -> Installation: ...
|
||||
def get_installations(self) -> PaginatedList[Installation]: ...
|
||||
def get_org_installation(self, org: str) -> Installation: ...
|
||||
def get_repo_installation(self, owner: str, repo: str) -> Installation: ...
|
||||
def get_user_installation(self, username: str) -> Installation: ...
|
||||
@@ -57,10 +57,26 @@ class InstallationAuthorization(github.GithubObject.NonCompletableGithubObject):
|
||||
"""
|
||||
return self._on_behalf_of.value
|
||||
|
||||
@property
|
||||
def permissions(self):
|
||||
"""
|
||||
:type: dict
|
||||
"""
|
||||
return self._permissions.value
|
||||
|
||||
@property
|
||||
def repository_selection(self):
|
||||
"""
|
||||
:type: string
|
||||
"""
|
||||
return self._repository_selection.value
|
||||
|
||||
def _initAttributes(self):
|
||||
self._token = github.GithubObject.NotSet
|
||||
self._expires_at = github.GithubObject.NotSet
|
||||
self._on_behalf_of = github.GithubObject.NotSet
|
||||
self._permissions = github.GithubObject.NotSet
|
||||
self._repository_selection = github.GithubObject.NotSet
|
||||
|
||||
def _useAttributes(self, attributes):
|
||||
if "token" in attributes: # pragma no branch
|
||||
@@ -71,3 +87,9 @@ class InstallationAuthorization(github.GithubObject.NonCompletableGithubObject):
|
||||
self._on_behalf_of = self._makeClassAttribute(
|
||||
github.NamedUser.NamedUser, attributes["on_behalf_of"]
|
||||
)
|
||||
if "permissions" in attributes: # pragma no branch
|
||||
self._permissions = self._makeDictAttribute(attributes["permissions"])
|
||||
if "repository_selection" in attributes: # pragma no branch
|
||||
self._repository_selection = self._makeStringAttribute(
|
||||
attributes["repository_selection"]
|
||||
)
|
||||
|
||||
@@ -14,3 +14,7 @@ class InstallationAuthorization(NonCompletableGithubObject):
|
||||
def on_behalf_of(self) -> NamedUser: ...
|
||||
@property
|
||||
def token(self) -> str: ...
|
||||
@property
|
||||
def permissions(self) -> dict: ...
|
||||
@property
|
||||
def repository_selection(self) -> str: ...
|
||||
|
||||
+14
-113
@@ -49,10 +49,7 @@
|
||||
|
||||
import datetime
|
||||
import pickle
|
||||
import time
|
||||
|
||||
import jwt
|
||||
import requests
|
||||
import urllib3
|
||||
|
||||
import github.ApplicationOAuth
|
||||
@@ -68,24 +65,13 @@ from . import (
|
||||
AuthenticatedUser,
|
||||
Consts,
|
||||
GithubApp,
|
||||
GithubException,
|
||||
GitignoreTemplate,
|
||||
HookDescription,
|
||||
Installation,
|
||||
InstallationAuthorization,
|
||||
RateLimit,
|
||||
Repository,
|
||||
)
|
||||
from .Requester import Requester
|
||||
|
||||
DEFAULT_BASE_URL = "https://api.github.com"
|
||||
DEFAULT_STATUS_URL = "https://status.github.com"
|
||||
# As of 2018-05-17, Github imposes a 10s limit for completion of API requests.
|
||||
# Thus, the timeout should be slightly > 10s to account for network/front-end
|
||||
# latency.
|
||||
DEFAULT_TIMEOUT = 15
|
||||
DEFAULT_PER_PAGE = 30
|
||||
|
||||
|
||||
class Github:
|
||||
"""
|
||||
@@ -97,10 +83,11 @@ class Github:
|
||||
login_or_token=None,
|
||||
password=None,
|
||||
jwt=None,
|
||||
base_url=DEFAULT_BASE_URL,
|
||||
timeout=DEFAULT_TIMEOUT,
|
||||
app_auth=None,
|
||||
base_url=Consts.DEFAULT_BASE_URL,
|
||||
timeout=Consts.DEFAULT_TIMEOUT,
|
||||
user_agent="PyGithub/Python",
|
||||
per_page=DEFAULT_PER_PAGE,
|
||||
per_page=Consts.DEFAULT_PER_PAGE,
|
||||
verify=True,
|
||||
retry=None,
|
||||
pool_size=None,
|
||||
@@ -108,6 +95,8 @@ class Github:
|
||||
"""
|
||||
:param login_or_token: string
|
||||
:param password: string
|
||||
:param jwt: string
|
||||
:param app_auth: github.AppAuthentication
|
||||
:param base_url: string
|
||||
:param timeout: integer
|
||||
:param user_agent: string
|
||||
@@ -125,14 +114,16 @@ class Github:
|
||||
assert user_agent is None or isinstance(user_agent, str), user_agent
|
||||
assert (
|
||||
retry is None
|
||||
or isinstance(retry, (int))
|
||||
or isinstance(retry, (urllib3.util.Retry))
|
||||
)
|
||||
assert pool_size is None or isinstance(pool_size, (int)), pool_size
|
||||
or isinstance(retry, int)
|
||||
or isinstance(retry, urllib3.util.Retry)
|
||||
), retry
|
||||
assert pool_size is None or isinstance(pool_size, int), pool_size
|
||||
|
||||
self.__requester = Requester(
|
||||
login_or_token,
|
||||
password,
|
||||
jwt,
|
||||
app_auth,
|
||||
base_url,
|
||||
timeout,
|
||||
user_agent,
|
||||
@@ -786,95 +777,5 @@ class Github:
|
||||
return GithubApp.GithubApp(self.__requester, headers, data, completed=True)
|
||||
|
||||
|
||||
class GithubIntegration:
|
||||
"""
|
||||
Main class to obtain tokens for a GitHub integration.
|
||||
"""
|
||||
|
||||
def __init__(self, integration_id, private_key, base_url=DEFAULT_BASE_URL):
|
||||
"""
|
||||
:param base_url: string
|
||||
:param integration_id: int
|
||||
:param private_key: string
|
||||
"""
|
||||
self.base_url = base_url
|
||||
self.integration_id = integration_id
|
||||
self.private_key = private_key
|
||||
assert isinstance(base_url, str), base_url
|
||||
|
||||
def create_jwt(self, expiration=60):
|
||||
"""
|
||||
Creates a signed JWT, valid for 60 seconds by default.
|
||||
The expiration can be extended beyond this, to a maximum of 600 seconds.
|
||||
|
||||
:param expiration: int
|
||||
:return string:
|
||||
"""
|
||||
now = int(time.time())
|
||||
payload = {"iat": now, "exp": now + expiration, "iss": self.integration_id}
|
||||
encrypted = jwt.encode(payload, key=self.private_key, algorithm="RS256")
|
||||
|
||||
if isinstance(encrypted, bytes):
|
||||
encrypted = encrypted.decode("utf-8")
|
||||
|
||||
return encrypted
|
||||
|
||||
def get_access_token(self, installation_id, user_id=None):
|
||||
"""
|
||||
Get an access token for the given installation id.
|
||||
POSTs https://api.github.com/app/installations/<installation_id>/access_tokens
|
||||
:param user_id: int
|
||||
:param installation_id: int
|
||||
:return: :class:`github.InstallationAuthorization.InstallationAuthorization`
|
||||
"""
|
||||
body = {}
|
||||
if user_id:
|
||||
body = {"user_id": user_id}
|
||||
response = requests.post(
|
||||
f"{self.base_url}/app/installations/{installation_id}/access_tokens",
|
||||
headers={
|
||||
"Authorization": f"Bearer {self.create_jwt()}",
|
||||
"Accept": Consts.mediaTypeIntegrationPreview,
|
||||
"User-Agent": "PyGithub/Python",
|
||||
},
|
||||
json=body,
|
||||
)
|
||||
|
||||
if response.status_code == 201:
|
||||
return InstallationAuthorization.InstallationAuthorization(
|
||||
requester=None, # not required, this is a NonCompletableGithubObject
|
||||
headers={}, # not required, this is a NonCompletableGithubObject
|
||||
attributes=response.json(),
|
||||
completed=True,
|
||||
)
|
||||
elif response.status_code == 403:
|
||||
raise GithubException.BadCredentialsException(
|
||||
status=response.status_code, data=response.text
|
||||
)
|
||||
elif response.status_code == 404:
|
||||
raise GithubException.UnknownObjectException(
|
||||
status=response.status_code, data=response.text
|
||||
)
|
||||
raise GithubException.GithubException(
|
||||
status=response.status_code, data=response.text
|
||||
)
|
||||
|
||||
def get_installation(self, owner, repo):
|
||||
"""
|
||||
:calls: `GET /repos/{owner}/{repo}/installation <https://docs.github.com/en/rest/reference/apps#get-a-repository-installation-for-the-authenticated-app>`_
|
||||
:param owner: str
|
||||
:param repo: str
|
||||
:rtype: :class:`github.Installation.Installation`
|
||||
"""
|
||||
headers = {
|
||||
"Authorization": f"Bearer {self.create_jwt()}",
|
||||
"Accept": Consts.mediaTypeIntegrationPreview,
|
||||
"User-Agent": "PyGithub/Python",
|
||||
}
|
||||
|
||||
response = requests.get(
|
||||
f"{self.base_url}/repos/{owner}/{repo}/installation",
|
||||
headers=headers,
|
||||
)
|
||||
response_dict = response.json()
|
||||
return Installation.Installation(None, headers, response_dict, True)
|
||||
# Retrocompatibility
|
||||
GithubIntegration = github.GithubIntegration
|
||||
|
||||
+5
-16
@@ -2,6 +2,7 @@ from datetime import datetime
|
||||
from io import BytesIO
|
||||
from typing import Any, Dict, List, Optional, Tuple, Type, TypeVar, Union, overload
|
||||
|
||||
from github.AppAuthentication import AppAuthentication
|
||||
from github.AuthenticatedUser import AuthenticatedUser
|
||||
from github.Commit import Commit
|
||||
from github.ContentFile import ContentFile
|
||||
@@ -10,8 +11,6 @@ from github.Gist import Gist
|
||||
from github.GithubObject import GithubObject, _NotSetType
|
||||
from github.GitignoreTemplate import GitignoreTemplate
|
||||
from github.HookDescription import HookDescription
|
||||
from github.Installation import Installation
|
||||
from github.InstallationAuthorization import InstallationAuthorization
|
||||
from github.Issue import Issue
|
||||
from github.License import License
|
||||
from github.NamedUser import NamedUser
|
||||
@@ -32,10 +31,9 @@ class Github:
|
||||
login_or_token: Optional[str] = ...,
|
||||
password: Optional[str] = ...,
|
||||
jwt: Optional[str] = ...,
|
||||
app_auth: Optional[AppAuthentication] = ...,
|
||||
base_url: str = ...,
|
||||
timeout: int = ...,
|
||||
client_id: Optional[str] = ...,
|
||||
client_secret: Optional[str] = ...,
|
||||
user_agent: str = ...,
|
||||
per_page: int = ...,
|
||||
verify: bool = ...,
|
||||
@@ -67,7 +65,6 @@ class Github:
|
||||
def get_gitignore_templates(self) -> List[str]: ...
|
||||
def get_hook(self, name: str) -> HookDescription: ...
|
||||
def get_hooks(self) -> List[HookDescription]: ...
|
||||
def get_installation(self, id: int) -> Installation: ...
|
||||
def get_license(self, key: Union[str, _NotSetType] = ...) -> License: ...
|
||||
def get_licenses(self) -> PaginatedList[License]: ...
|
||||
def get_organization(self, login: str) -> Organization: ...
|
||||
@@ -87,7 +84,9 @@ class Github:
|
||||
@overload
|
||||
def get_user(self, login: _NotSetType = ...) -> AuthenticatedUser: ...
|
||||
@overload
|
||||
def get_user(self, login: str) -> NamedUser: ...
|
||||
def get_user(
|
||||
self, login: Union[str, _NotSetType] = ...
|
||||
) -> Union[NamedUser, AuthenticatedUser]: ...
|
||||
def get_user_by_id(self, user_id: int) -> NamedUser: ...
|
||||
def get_users(
|
||||
self, since: Union[int, _NotSetType] = ...
|
||||
@@ -139,13 +138,3 @@ class Github:
|
||||
order: Union[str, _NotSetType] = ...,
|
||||
**qualifiers: Any
|
||||
) -> PaginatedList[NamedUser]: ...
|
||||
|
||||
class GithubIntegration:
|
||||
def __init__(
|
||||
self, integration_id: Union[int, str], private_key: str, base_url: str = ...
|
||||
) -> None: ...
|
||||
def create_jwt(self, expiration: int = ...) -> str: ...
|
||||
def get_access_token(
|
||||
self, installation_id: int, user_id: Optional[int] = ...
|
||||
) -> InstallationAuthorization: ...
|
||||
def get_installation(self, owner: str, repo: str) -> Installation: ...
|
||||
|
||||
+49
-1
@@ -51,6 +51,7 @@
|
||||
################################################################################
|
||||
|
||||
import base64
|
||||
import datetime
|
||||
import json
|
||||
import logging
|
||||
import mimetypes
|
||||
@@ -59,10 +60,14 @@ import re
|
||||
import time
|
||||
import urllib
|
||||
from io import IOBase
|
||||
from multiprocessing import RLock
|
||||
|
||||
import requests
|
||||
|
||||
from . import Consts, GithubException
|
||||
from . import Consts, GithubException, GithubIntegration
|
||||
|
||||
# For App authentication, time remaining before token expiration to request a new one
|
||||
ACCESS_TOKEN_REFRESH_THRESHOLD_SECONDS = 20
|
||||
|
||||
|
||||
class RequestsResponse:
|
||||
@@ -294,6 +299,7 @@ class Requester:
|
||||
login_or_token,
|
||||
password,
|
||||
jwt,
|
||||
app_auth,
|
||||
base_url,
|
||||
timeout,
|
||||
user_agent,
|
||||
@@ -304,6 +310,11 @@ class Requester:
|
||||
):
|
||||
self._initializeDebugFeature()
|
||||
|
||||
self.__installation_authorization = None
|
||||
self.__app_auth = app_auth
|
||||
|
||||
self.__auth_lock = RLock()
|
||||
|
||||
if password is not None:
|
||||
login = login_or_token
|
||||
b64 = (
|
||||
@@ -317,6 +328,8 @@ class Requester:
|
||||
self.__authorizationHeader = f"token {token}"
|
||||
elif jwt is not None:
|
||||
self.__authorizationHeader = f"Bearer {jwt}"
|
||||
elif self.__app_auth is not None:
|
||||
self._refresh_token()
|
||||
else:
|
||||
self.__authorizationHeader = None
|
||||
|
||||
@@ -349,6 +362,40 @@ class Requester:
|
||||
self.__userAgent = user_agent
|
||||
self.__verify = verify
|
||||
|
||||
def _must_refresh_token(self) -> bool:
|
||||
"""Check if it is time to refresh the API token gotten from the GitHub app installation"""
|
||||
if not self.__installation_authorization:
|
||||
return False
|
||||
return (
|
||||
self.__installation_authorization.expires_at
|
||||
< datetime.datetime.utcnow()
|
||||
+ datetime.timedelta(seconds=ACCESS_TOKEN_REFRESH_THRESHOLD_SECONDS)
|
||||
)
|
||||
|
||||
def _get_installation_authorization(self):
|
||||
assert self.__app_auth is not None
|
||||
integration = GithubIntegration.GithubIntegration(
|
||||
self.__app_auth.app_id, self.__app_auth.private_key
|
||||
)
|
||||
return integration.get_access_token(
|
||||
self.__app_auth.installation_id,
|
||||
permissions=self.__app_auth.token_permissions,
|
||||
)
|
||||
|
||||
def _refresh_token_if_needed(self) -> None:
|
||||
"""Get a new access token from the GitHub app installation if the one we have is about to expire"""
|
||||
if not self.__installation_authorization:
|
||||
return
|
||||
with self.__auth_lock:
|
||||
if self._must_refresh_token():
|
||||
logging.debug("Refreshing access token")
|
||||
self._refresh_token()
|
||||
|
||||
def _refresh_token(self) -> None:
|
||||
"""In the context of a GitHub app, refresh the access token"""
|
||||
self.__installation_authorization = self._get_installation_authorization()
|
||||
self.__authorizationHeader = f"token {self.__installation_authorization.token}"
|
||||
|
||||
def requestJsonAndCheck(self, verb, url, parameters=None, headers=None, input=None):
|
||||
return self.__check(
|
||||
*self.requestJson(
|
||||
@@ -578,6 +625,7 @@ class Requester:
|
||||
return status, responseHeaders, output
|
||||
|
||||
def __authenticate(self, url, requestHeaders, parameters):
|
||||
self._refresh_token_if_needed()
|
||||
if self.__authorizationHeader is not None:
|
||||
requestHeaders["Authorization"] = self.__authorizationHeader
|
||||
|
||||
|
||||
+20
-12
@@ -4,7 +4,9 @@ from typing import Any, Callable, Dict, Iterator, Optional, Tuple, Union
|
||||
|
||||
from requests.models import Response
|
||||
|
||||
from github.AppAuthentication import AppAuthentication
|
||||
from github.GithubObject import GithubObject
|
||||
from github.InstallationAuthorization import InstallationAuthorization
|
||||
|
||||
from urllib3.util import Retry
|
||||
|
||||
@@ -47,6 +49,8 @@ class HTTPSRequestsConnectionClass:
|
||||
) -> None: ...
|
||||
|
||||
class Requester:
|
||||
__installation_authorization: Optional[InstallationAuthorization] = ...
|
||||
__app_auth: Optional[AppAuthentication] = ...
|
||||
def DEBUG_ON_RESPONSE(
|
||||
self, statusCode: int, responseHeader: Dict[str, str], data: str
|
||||
) -> None: ...
|
||||
@@ -54,7 +58,7 @@ class Requester:
|
||||
def __check(
|
||||
self,
|
||||
status: int,
|
||||
responseHeader: Dict[str, Any],
|
||||
responseHeaders: Dict[str, Any],
|
||||
output: str,
|
||||
) -> Tuple[Dict[str, Any], Dict[str, Any]]: ...
|
||||
def __addParametersToUrl(
|
||||
@@ -65,7 +69,7 @@ class Requester:
|
||||
def __authenticate(
|
||||
self,
|
||||
url: str,
|
||||
responseHeader: Dict[str, Any],
|
||||
requestHeaders: Dict[str, Any],
|
||||
parameters: Dict[str, Any],
|
||||
) -> None: ...
|
||||
def __customConnection(
|
||||
@@ -88,37 +92,37 @@ class Requester:
|
||||
requestHeaders: Dict[str, str],
|
||||
input: Optional[str],
|
||||
status: Optional[int],
|
||||
responseHeader: Dict[str, Any],
|
||||
responseHeaders: Dict[str, Any],
|
||||
output: Optional[str],
|
||||
) -> None: ...
|
||||
def __makeAbsoluteUrl(self, url: str) -> str: ...
|
||||
def __structuredFromJson(self, data: str) -> Optional[Dict[str, Any]]: ...
|
||||
def __requestEncode(
|
||||
self,
|
||||
cnx: Union[HTTPRequestsConnectionClass, HTTPSRequestsConnectionClass],
|
||||
verb: str,
|
||||
url: str,
|
||||
parameters: Dict[str, str] = ...,
|
||||
headers: Dict[str, str] = ...,
|
||||
input: Optional[str] = ...,
|
||||
encode: Callable[[str], str] = ...,
|
||||
parameters: Dict[str, str],
|
||||
requestHeaders: Dict[str, str],
|
||||
input: Optional[str],
|
||||
encode: Callable[[str], str],
|
||||
) -> Tuple[int, Dict[str, Any], str]: ...
|
||||
def __requestRaw(
|
||||
self,
|
||||
cnx: Union[HTTPRequestsConnectionClass, HTTPSRequestsConnectionClass],
|
||||
verb: str,
|
||||
url: str,
|
||||
parameters: Dict[str, str] = ...,
|
||||
headers: Dict[str, str] = ...,
|
||||
input: Optional[str] = ...,
|
||||
requestHeaders: Dict[str, str],
|
||||
input: Optional[str],
|
||||
) -> Tuple[int, Dict[str, Any], str]: ...
|
||||
def __init__(
|
||||
self,
|
||||
login_or_token: Optional[str],
|
||||
password: Optional[str],
|
||||
jwt: Optional[str],
|
||||
app_auth: Optional[AppAuthentication],
|
||||
base_url: str,
|
||||
timeout: int,
|
||||
client_id: Optional[str],
|
||||
client_secret: Optional[str],
|
||||
user_agent: str,
|
||||
per_page: int,
|
||||
verify: bool,
|
||||
@@ -127,6 +131,10 @@ class Requester:
|
||||
) -> None: ...
|
||||
def _initializeDebugFeature(self) -> None: ...
|
||||
def check_me(self, obj: GithubObject) -> None: ...
|
||||
def _must_refresh_token(self) -> bool: ...
|
||||
def _get_installation_authorization(self) -> InstallationAuthorization: ...
|
||||
def _refresh_token_if_needed(self) -> None: ...
|
||||
def _refresh_token(self) -> None: ...
|
||||
@classmethod
|
||||
def injectConnectionClasses(
|
||||
cls, httpConnectionClass: Callable, httpsConnectionClass: Callable
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
from typing import Any, Dict, List, Union
|
||||
|
||||
from github.GithubObject import NonCompletableGithubObject
|
||||
|
||||
class SelfHostedActionsRunner(NonCompletableGithubObject):
|
||||
def __repr__(self) -> str: ...
|
||||
def _initAttributes(self) -> None: ...
|
||||
def _useAttributes(self, attributes: Dict[str, Any]) -> None: ...
|
||||
@property
|
||||
def id(self) -> int: ...
|
||||
@property
|
||||
def name(self) -> str: ...
|
||||
@property
|
||||
def os(self) -> str: ...
|
||||
@property
|
||||
def status(self) -> str: ...
|
||||
@property
|
||||
def busy(self) -> bool: ...
|
||||
def labels(self) -> List[Dict[str, Union[str, int]]]: ...
|
||||
from typing import Any, Dict, List, Union
|
||||
|
||||
from github.GithubObject import NonCompletableGithubObject
|
||||
|
||||
class SelfHostedActionsRunner(NonCompletableGithubObject):
|
||||
def __repr__(self) -> str: ...
|
||||
def _initAttributes(self) -> None: ...
|
||||
def _useAttributes(self, attributes: Dict[str, Any]) -> None: ...
|
||||
@property
|
||||
def id(self) -> int: ...
|
||||
@property
|
||||
def name(self) -> str: ...
|
||||
@property
|
||||
def os(self) -> str: ...
|
||||
@property
|
||||
def status(self) -> str: ...
|
||||
@property
|
||||
def busy(self) -> bool: ...
|
||||
def labels(self) -> List[Dict[str, Union[str, int]]]: ...
|
||||
|
||||
+4
-1
@@ -35,6 +35,7 @@ like :class:`github.NamedUser.NamedUser` or :class:`github.Repository.Repository
|
||||
All classes inherit from :class:`github.GithubObject.GithubObject`.
|
||||
"""
|
||||
__all__ = [
|
||||
"AppAuthentication",
|
||||
"BadAttributeException",
|
||||
"BadCredentialsException",
|
||||
"BadUserAgentException",
|
||||
@@ -53,7 +54,9 @@ __all__ = [
|
||||
|
||||
import logging
|
||||
|
||||
from github.MainClass import Github, GithubIntegration
|
||||
from github.AppAuthentication import AppAuthentication
|
||||
from github.GithubIntegration import GithubIntegration
|
||||
from github.MainClass import Github
|
||||
|
||||
from .GithubException import (
|
||||
BadAttributeException,
|
||||
|
||||
+3
-2
@@ -1,11 +1,12 @@
|
||||
from github.AppAuthentication import AppAuthentication as AppAuthentication
|
||||
from github.GithubIntegration import GithubIntegration as GithubIntegration
|
||||
from github.MainClass import Github as Github
|
||||
from github.MainClass import GithubIntegration as GithubIntegration
|
||||
|
||||
from .GithubException import BadAttributeException as BadAttributeException
|
||||
from .GithubException import BadCredentialsException as BadCredentialsException
|
||||
from .GithubException import BadUserAgentException as BadUserAgentException
|
||||
from .GithubException import GithubException as GithubException
|
||||
from .GithubException import IncompletableObject as IncompleteableObject
|
||||
from .GithubException import IncompletableObject as IncompletableObject
|
||||
from .GithubException import RateLimitExceededException as RateLimitExceededException
|
||||
from .GithubException import TwoFactorException as TwoFactorException
|
||||
from .GithubException import UnknownObjectException as UnknownObjectException
|
||||
|
||||
@@ -52,6 +52,16 @@ class Authentication(Framework.BasicTestCase):
|
||||
g = github.Github(user_agent="PyGithubTester")
|
||||
self.assertEqual(g.get_user("jacquev6").name, "Vincent Jacques")
|
||||
|
||||
def testAppAuthentication(self):
|
||||
g = github.Github(
|
||||
app_auth=github.AppAuthentication(
|
||||
app_id=self.app_id,
|
||||
private_key=self.app_private_key,
|
||||
installation_id=29782936,
|
||||
),
|
||||
)
|
||||
self.assertEqual(g.get_user("ammarmallik").name, "Ammar Akbar")
|
||||
|
||||
def testAuthorizationHeaderWithLogin(self):
|
||||
# See special case in Framework.fixAuthorizationHeader
|
||||
g = github.Github("fake_login", "fake_password")
|
||||
|
||||
@@ -46,6 +46,24 @@ from urllib3.util import Url # type: ignore
|
||||
|
||||
import github
|
||||
|
||||
APP_PRIVATE_KEY = """
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIICXAIBAAKBgQC+5ePolLv6VcWLp2f17g6r6vHl+eoLuodOOfUl8JK+MVmvXbPa
|
||||
xDy0SS0pQhwTOMtB0VdSt++elklDCadeokhEoGDQp411o+kiOhzLxfakp/kewf4U
|
||||
HJnu4M/A2nHmxXVe2lzYnZvZHX5BM4SJo5PGdr0Ue2JtSXoAtYr6qE9maQIDAQAB
|
||||
AoGAFhOJ7sy8jG+837Clcihso+8QuHLVYTPaD+7d7dxLbBlS8NfaQ9Nr3cGUqm/N
|
||||
xV9NCjiGa7d/y4w/vrPwGh6UUsA+CvndwDgBd0S3WgIdWvAvHM8wKgNh/GBLLzhT
|
||||
Bg9BouRUzcT1MjAnkGkWqqCAgN7WrCSUMLt57TNleNWfX90CQQDjvVKTT3pOiavD
|
||||
3YcLxwkyeGd0VMvKiS4nV0XXJ97cGXs2GpOGXldstDTnF5AnB6PbukdFLHpsx4sW
|
||||
Hft3LRWnAkEA1pY15ke08wX6DZVXy7zuQ2izTrWSGySn7B41pn55dlKpttjHeutA
|
||||
3BEQKTFvMhBCphr8qST7Wf1SR9FgO0tFbwJAEhHji2yy96hUyKW7IWQZhrem/cP8
|
||||
p4Va9CQolnnDZRNgg1p4eiDiLu3dhLiJ547joXuWTBbLX/Y1Qvv+B+a74QJBAMCW
|
||||
O3WbMZlS6eK6//rIa4ZwN00SxDg8I8FUM45jwBsjgVGrKQz2ilV3sutlhIiH82kk
|
||||
m1Iq8LMJGYl/LkDJA10CQBV1C+Xu3ukknr7C4A/4lDCa6Xb27cr1HanY7i89A+Ab
|
||||
eatdM6f/XVqWp8uPT9RggUV9TjppJobYGT2WrWJMkYw=
|
||||
-----END RSA PRIVATE KEY-----
|
||||
"""
|
||||
|
||||
|
||||
def readLine(file_):
|
||||
line = file_.readline()
|
||||
@@ -268,6 +286,8 @@ class BasicTestCase(unittest.TestCase):
|
||||
self.password = GithubCredentials.password
|
||||
self.oauth_token = GithubCredentials.oauth_token
|
||||
self.jwt = GithubCredentials.jwt
|
||||
self.app_id = GithubCredentials.app_id
|
||||
self.app_private_key = GithubCredentials.app_private_key
|
||||
else:
|
||||
github.Requester.Requester.injectConnectionClasses(
|
||||
lambda ignored, *args, **kwds: ReplayingHttpConnection(
|
||||
@@ -281,6 +301,8 @@ class BasicTestCase(unittest.TestCase):
|
||||
self.password = "password"
|
||||
self.oauth_token = "oauth_token"
|
||||
self.jwt = "jwt"
|
||||
self.app_id = 123456
|
||||
self.app_private_key = APP_PRIVATE_KEY
|
||||
|
||||
httpretty.enable(allow_net_connect=False)
|
||||
|
||||
|
||||
+174
-140
@@ -1,15 +1,15 @@
|
||||
import datetime
|
||||
import json
|
||||
import sys
|
||||
import time # NOQA
|
||||
import unittest
|
||||
|
||||
import jwt
|
||||
import requests # NOQA
|
||||
|
||||
from github.GithubObject import GithubObject
|
||||
import github
|
||||
|
||||
private_key = """
|
||||
from . import Framework
|
||||
|
||||
APP_ID = 243473
|
||||
PRIVATE_KEY = """
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIICXAIBAAKBgQC+5ePolLv6VcWLp2f17g6r6vHl+eoLuodOOfUl8JK+MVmvXbPa
|
||||
xDy0SS0pQhwTOMtB0VdSt++elklDCadeokhEoGDQp411o+kiOhzLxfakp/kewf4U
|
||||
@@ -26,8 +26,7 @@ m1Iq8LMJGYl/LkDJA10CQBV1C+Xu3ukknr7C4A/4lDCa6Xb27cr1HanY7i89A+Ab
|
||||
eatdM6f/XVqWp8uPT9RggUV9TjppJobYGT2WrWJMkYw=
|
||||
-----END RSA PRIVATE KEY-----
|
||||
"""
|
||||
|
||||
public_key = """
|
||||
PUBLIC_KEY = """
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC+5ePolLv6VcWLp2f17g6r6vHl
|
||||
+eoLuodOOfUl8JK+MVmvXbPaxDy0SS0pQhwTOMtB0VdSt++elklDCadeokhEoGDQ
|
||||
@@ -37,154 +36,189 @@ e2JtSXoAtYr6qE9maQIDAQAB
|
||||
"""
|
||||
|
||||
|
||||
class GithubIntegration(unittest.TestCase):
|
||||
class GithubIntegration(Framework.BasicTestCase):
|
||||
def setUp(self):
|
||||
# This flag ask requester to do some checking,
|
||||
# for debug and test purpose. But
|
||||
# `InstallationAuthorization.InstallationAuthorization` is a
|
||||
# `NonCompletableGithubObject`, it does not have requester.
|
||||
# So the check is not needed.
|
||||
# see `GithubIntegration.get_access_token`
|
||||
self.origin_check_after_init_flag = GithubObject.CHECK_AFTER_INIT_FLAG
|
||||
GithubObject.setCheckAfterInitFlag(False)
|
||||
|
||||
self.origin_time = sys.modules["time"].time
|
||||
sys.modules["time"].time = lambda: 1550055331.7435968
|
||||
|
||||
class Mock:
|
||||
def __init__(self):
|
||||
self.args = tuple()
|
||||
self.kwargs = dict()
|
||||
|
||||
@property
|
||||
def status_code(self):
|
||||
return 201
|
||||
|
||||
def json(self):
|
||||
return json.loads(self.text)
|
||||
|
||||
@property
|
||||
def text(self):
|
||||
return (
|
||||
'{"token": "v1.ce63424bc55028318325caac4f4c3a5378ca0038",'
|
||||
'"expires_at": "2019-02-13T11:10:38Z"}'
|
||||
)
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
return self
|
||||
|
||||
self.origin_request_post = sys.modules["requests"].post
|
||||
self.mock = Mock()
|
||||
sys.modules["requests"].post = self.mock
|
||||
|
||||
class GetMock:
|
||||
def __init__(self):
|
||||
self.args = tuple()
|
||||
self.kwargs = dict()
|
||||
self.calls = []
|
||||
|
||||
@property
|
||||
def status_code(self):
|
||||
return 201
|
||||
|
||||
def json(self):
|
||||
return json.loads(self.text)
|
||||
|
||||
@property
|
||||
def text(self):
|
||||
return (
|
||||
'{"id":111111,"account":{"login":"foo","id":11111111,'
|
||||
'"node_id":"foobar",'
|
||||
'"avatar_url":"https://avatars3.githubusercontent.com/u/11111111?v=4",'
|
||||
'"gravatar_id":"","url":"https://api.github.com/users/foo",'
|
||||
'"html_url":"https://github.com/foo",'
|
||||
'"followers_url":"https://api.github.com/users/foo/followers",'
|
||||
'"following_url":"https://api.github.com/users/foo/following{/other_user}",'
|
||||
'"gists_url":"https://api.github.com/users/foo/gists{/gist_id}",'
|
||||
'"starred_url":"https://api.github.com/users/foo/starred{/owner}{/repo}",'
|
||||
'"subscriptions_url":"https://api.github.com/users/foo/subscriptions",'
|
||||
'"organizations_url":"https://api.github.com/users/foo/orgs",'
|
||||
'"repos_url":"https://api.github.com/users/foo/repos",'
|
||||
'"events_url":"https://api.github.com/users/foo/events{/privacy}",'
|
||||
'"received_events_url":"https://api.github.com/users/foo/received_events",'
|
||||
'"type":"Organization","site_admin":false},"repository_selection":"all",'
|
||||
'"access_tokens_url":"https://api.github.com/app/installations/111111/access_tokens",'
|
||||
'"repositories_url":"https://api.github.com/installation/repositories",'
|
||||
'"html_url":"https://github.com/organizations/foo/settings/installations/111111",'
|
||||
'"app_id":11111,"target_id":11111111,"target_type":"Organization",'
|
||||
'"permissions":{"issues":"write","pull_requests":"write","statuses":"write","contents":"read",'
|
||||
'"metadata":"read"},"events":["pull_request","release"],"created_at":"2019-04-17T16:10:37.000Z",'
|
||||
'"updated_at":"2019-05-03T06:27:48.000Z","single_file_name":null}'
|
||||
)
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
self.calls.append((args, kwargs))
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
return self
|
||||
|
||||
self.origin_request_get = sys.modules["requests"].get
|
||||
self.get_mock = GetMock()
|
||||
sys.modules["requests"].get = self.get_mock
|
||||
super().setUp()
|
||||
self.org_installation_id = 30614487
|
||||
self.repo_installation_id = 30614431
|
||||
self.user_installation_id = 30614431
|
||||
|
||||
def testCreateJWT(self):
|
||||
from github import GithubIntegration
|
||||
|
||||
integration = GithubIntegration(25216, private_key)
|
||||
token = integration.create_jwt()
|
||||
self.origin_time = sys.modules["time"].time
|
||||
sys.modules["time"].time = lambda: 1550055331.7435968
|
||||
github_integration = github.GithubIntegration(
|
||||
integration_id=APP_ID, private_key=PRIVATE_KEY
|
||||
)
|
||||
token = github_integration.create_jwt()
|
||||
payload = jwt.decode(
|
||||
token,
|
||||
key=public_key,
|
||||
key=PUBLIC_KEY,
|
||||
algorithms=["RS256"],
|
||||
options={"verify_exp": False},
|
||||
)
|
||||
self.assertDictEqual(
|
||||
payload, {"iat": 1550055331, "exp": 1550055391, "iss": 25216}
|
||||
payload, {"iat": 1550055271, "exp": 1550055631, "iss": APP_ID}
|
||||
)
|
||||
sys.modules["time"].time = self.origin_time
|
||||
|
||||
def testGetInstallations(self):
|
||||
github_integration = github.GithubIntegration(
|
||||
integration_id=APP_ID, private_key=PRIVATE_KEY
|
||||
)
|
||||
installations = github_integration.get_installations()
|
||||
|
||||
self.assertEqual(len(list(installations)), 2)
|
||||
self.assertEqual(installations[0].id, self.org_installation_id)
|
||||
self.assertEqual(installations[1].id, self.repo_installation_id)
|
||||
|
||||
def testGetAccessToken(self):
|
||||
from github import GithubIntegration
|
||||
|
||||
integration = GithubIntegration(25216, private_key)
|
||||
auth_obj = integration.get_access_token(664281)
|
||||
self.assertEqual(
|
||||
self.mock.args[0],
|
||||
"https://api.github.com/app/installations/664281/access_tokens",
|
||||
)
|
||||
self.assertEqual(auth_obj.token, "v1.ce63424bc55028318325caac4f4c3a5378ca0038")
|
||||
self.assertEqual(
|
||||
auth_obj.expires_at, datetime.datetime(2019, 2, 13, 11, 10, 38)
|
||||
)
|
||||
self.assertEqual(
|
||||
repr(auth_obj), "InstallationAuthorization(expires_at=2019-02-13 11:10:38)"
|
||||
github_integration = github.GithubIntegration(
|
||||
integration_id=APP_ID, private_key=PRIVATE_KEY
|
||||
)
|
||||
|
||||
def test_get_installation(self):
|
||||
from github import GithubIntegration
|
||||
|
||||
integr = GithubIntegration("11111", private_key)
|
||||
inst = integr.get_installation("foo", "bar")
|
||||
self.assertEqual(
|
||||
self.get_mock.calls[0][0],
|
||||
("https://api.github.com/repos/foo/bar/installation",),
|
||||
# Get repo installation access token
|
||||
repo_installation_authorization = github_integration.get_access_token(
|
||||
self.repo_installation_id
|
||||
)
|
||||
self.assertEqual(inst.id, 111111)
|
||||
|
||||
def test_get_installation_custom_base_url(self):
|
||||
from github import GithubIntegration
|
||||
|
||||
integr = GithubIntegration("11111", private_key, base_url="https://corp.com/v3")
|
||||
inst = integr.get_installation("foo", "bar")
|
||||
self.assertEqual(
|
||||
self.get_mock.calls[0][0],
|
||||
("https://corp.com/v3/repos/foo/bar/installation",),
|
||||
repo_installation_authorization.token,
|
||||
"ghs_1llwuELtXN5HDOB99XhpcTXdJxbOuF0ZlSmj",
|
||||
)
|
||||
self.assertDictEqual(
|
||||
repo_installation_authorization.permissions,
|
||||
{"issues": "read", "metadata": "read"},
|
||||
)
|
||||
self.assertEqual(
|
||||
repo_installation_authorization.repository_selection, "selected"
|
||||
)
|
||||
self.assertEqual(inst.id, 111111)
|
||||
|
||||
def tearDown(self):
|
||||
GithubObject.setCheckAfterInitFlag(self.origin_check_after_init_flag)
|
||||
sys.modules["time"].time = self.origin_time
|
||||
sys.modules["requests"].post = self.origin_request_post
|
||||
sys.modules["requests"].get = self.origin_request_get
|
||||
# Get org installation access token
|
||||
org_installation_authorization = github_integration.get_access_token(
|
||||
self.org_installation_id
|
||||
)
|
||||
self.assertEqual(
|
||||
org_installation_authorization.token,
|
||||
"ghs_V0xygF8yACXSDz5FM65QWV1BT2vtxw0cbgPw",
|
||||
)
|
||||
org_permissions = {
|
||||
"administration": "write",
|
||||
"issues": "write",
|
||||
"metadata": "read",
|
||||
"organization_administration": "read",
|
||||
}
|
||||
self.assertDictEqual(
|
||||
org_installation_authorization.permissions, org_permissions
|
||||
)
|
||||
self.assertEqual(
|
||||
org_installation_authorization.repository_selection, "selected"
|
||||
)
|
||||
|
||||
# Get user installation access token
|
||||
user_installation_authorization = github_integration.get_access_token(
|
||||
self.user_installation_id
|
||||
)
|
||||
self.assertEqual(
|
||||
user_installation_authorization.token,
|
||||
"ghs_1llwuELtXN5HDOB99XhpcTXdJxbOuF0ZlSmj",
|
||||
)
|
||||
self.assertDictEqual(
|
||||
user_installation_authorization.permissions,
|
||||
{"issues": "read", "metadata": "read"},
|
||||
)
|
||||
self.assertEqual(
|
||||
user_installation_authorization.repository_selection, "selected"
|
||||
)
|
||||
|
||||
def testGetUserInstallation(self):
|
||||
github_integration = github.GithubIntegration(
|
||||
integration_id=APP_ID, private_key=PRIVATE_KEY
|
||||
)
|
||||
installation = github_integration.get_user_installation(username="ammarmallik")
|
||||
|
||||
self.assertEqual(installation.id, self.user_installation_id)
|
||||
|
||||
def testGetOrgInstallation(self):
|
||||
github_integration = github.GithubIntegration(
|
||||
integration_id=APP_ID, private_key=PRIVATE_KEY
|
||||
)
|
||||
installation = github_integration.get_org_installation(org="GithubApp-Test-Org")
|
||||
|
||||
self.assertEqual(installation.id, self.org_installation_id)
|
||||
|
||||
def testGetRepoInstallation(self):
|
||||
github_integration = github.GithubIntegration(
|
||||
integration_id=APP_ID, private_key=PRIVATE_KEY
|
||||
)
|
||||
installation = github_integration.get_repo_installation(
|
||||
owner="ammarmallik", repo="test-runner"
|
||||
)
|
||||
|
||||
self.assertEqual(installation.id, self.repo_installation_id)
|
||||
|
||||
def testGetAppInstallation(self):
|
||||
github_integration = github.GithubIntegration(
|
||||
integration_id=APP_ID, private_key=PRIVATE_KEY
|
||||
)
|
||||
installation = github_integration.get_app_installation(
|
||||
installation_id=self.org_installation_id
|
||||
)
|
||||
|
||||
self.assertEqual(installation.id, self.org_installation_id)
|
||||
|
||||
def testGetInstallationNotFound(self):
|
||||
github_integration = github.GithubIntegration(
|
||||
integration_id=APP_ID, private_key=PRIVATE_KEY
|
||||
)
|
||||
with self.assertRaises(github.UnknownObjectException) as raisedexp:
|
||||
github_integration.get_org_installation(org="GithubApp-Test-Org-404")
|
||||
|
||||
self.assertEqual(raisedexp.exception.status, 404)
|
||||
|
||||
def testGetInstallationWithExpiredJWT(self):
|
||||
github_integration = github.GithubIntegration(
|
||||
integration_id=APP_ID, private_key=PRIVATE_KEY
|
||||
)
|
||||
with self.assertRaises(github.GithubException) as raisedexp:
|
||||
github_integration.get_org_installation(org="GithubApp-Test-Org")
|
||||
|
||||
self.assertEqual(raisedexp.exception.status, 401)
|
||||
|
||||
def testGetAccessTokenWithExpiredJWT(self):
|
||||
github_integration = github.GithubIntegration(
|
||||
integration_id=APP_ID, private_key=PRIVATE_KEY
|
||||
)
|
||||
with self.assertRaises(github.GithubException) as raisedexp:
|
||||
github_integration.get_access_token(self.repo_installation_id)
|
||||
|
||||
self.assertEqual(raisedexp.exception.status, 401)
|
||||
|
||||
def testGetAccessTokenForNoInstallation(self):
|
||||
github_integration = github.GithubIntegration(
|
||||
integration_id=APP_ID, private_key=PRIVATE_KEY
|
||||
)
|
||||
with self.assertRaises(github.UnknownObjectException) as raisedexp:
|
||||
github_integration.get_access_token(40432121)
|
||||
|
||||
self.assertEqual(raisedexp.exception.status, 404)
|
||||
|
||||
def testGetAccessTokenWithInvalidPermissions(self):
|
||||
github_integration = github.GithubIntegration(
|
||||
integration_id=APP_ID, private_key=PRIVATE_KEY
|
||||
)
|
||||
with self.assertRaises(github.GithubException) as raisedexp:
|
||||
github_integration.get_access_token(
|
||||
self.repo_installation_id, permissions={"test-permissions": "read"}
|
||||
)
|
||||
|
||||
self.assertEqual(raisedexp.exception.status, 422)
|
||||
|
||||
def testGetAccessTokenWithInvalidData(self):
|
||||
github_integration = github.GithubIntegration(
|
||||
integration_id=APP_ID, private_key=PRIVATE_KEY
|
||||
)
|
||||
with self.assertRaises(github.GithubException) as raisedexp:
|
||||
github_integration.get_access_token(
|
||||
self.repo_installation_id, permissions="invalid_data"
|
||||
)
|
||||
|
||||
self.assertEqual(raisedexp.exception.status, 400)
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
https
|
||||
POST
|
||||
api.github.com
|
||||
None
|
||||
/app/installations/29782936/access_tokens
|
||||
{'Authorization': 'Bearer jwt_removed', 'User-Agent': 'PyGithub/Python', 'Content-Type': 'application/json'}
|
||||
{"permissions": {}}
|
||||
201
|
||||
[('status', '201 CREATED'), ('server', 'Github.com'), ('date', 'Mon, 24 Oct 2022 23:11:45 GMT'), ('content-type', 'application/json; charset=utf-8'), ('connection', 'keep-alive'), ('content-length', '1962'), ('etag', 'W/"b11a1c9caabe35f1de0a13e597a3022d27d2bff0694c2ccb5a65edc3b4d18837"'), ('cache-control', 'public, max-age=60, s-maxage=60'), ('vary', 'Accept'), ('x-github-media-type', 'github.v3; format=json'), ('access-control-expose-headers', 'ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset'), ('access-control-allow-origin', '*'), ('strict-transport-security', 'max-age=31536000; includeSubdomains; preload'), ('x-frame-options', 'deny'), ('x-content-type-options', 'nosniff'), ('x-xss-protection', '0'), ('referrer-policy', 'origin-when-cross-origin, strict-origin-when-cross-origin'), ('x-github-request-id', "E475:53DD:8B7A89E:11E38A79:63571BB0"), ('vary', 'Accept-Encoding, Accept, X-Requested-With'), ('content-security-policy', "default-src 'none'")]
|
||||
{"token":"ghs_1llwuELtXN5HDOB99XhpcTXdJxbOuF0ZlSmj", "expires_at":"2024-11-25T01:00:02Z", "permissions":{"issues":"read","metadata":"read"}, "repository_selection":"selected"}
|
||||
|
||||
https
|
||||
GET
|
||||
api.github.com
|
||||
None
|
||||
/users/ammarmallik
|
||||
{'Authorization': 'token private_token_removed', 'User-Agent': 'PyGithub/Python'}
|
||||
None
|
||||
200
|
||||
[('status', '200 OK'), ('x-ratelimit-remaining', '57'), ('content-length', '1338'), ('server', 'GitHub.com'), ('connection', 'keep-alive'), ('x-ratelimit-limit', '60'), ('etag', 'W/"e5e65462690241eb7d9bc213cc00b3df2c75984d29a36b2ee8e151deaf4f3981"'), ('date', 'Tue, 25 Oct 2022 02:01:06 GMT'), ('x-ratelimit-reset', '1666666583'), ('content-type', 'application/json; charset=utf-8'), ('x-ratelimit-resource', 'core'), ('x-ratelimit-used', '3'), ('accept-ranges', 'bytes'), ('x-github-request-id', 'D8C1:7CE1:C3DF20:D546B4:63574361'), ('content-security-policy', "default-src 'none'"), ('referrer-policy', 'origin-when-cross-origin, strict-origin-when-cross-origin'), ('x-xss-protection', '0'), ('x-content-type-options', 'nosniff'), ('x-frame-options', 'deny'), ('strict-transport-security', 'max-age=31536000; includeSubdomains; preload'), ('access-control-allow-origin', '*'), ('access-control-expose-headers', 'ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset'), ('x-github-media-type', 'github.v3; format=jsom'), ('last-modified', 'Mon, 24 Oct 2022 20:26:22 GMT'), ('cache-control', 'public, max-age=60, s-maxage=60'), ('vary', 'Accept, Accept-Encoding, Accept, X-Requested-With')]
|
||||
{"login":"ammarmallik","id":29196434,"node_id":"MDQ6VXNlcjI5MTk2NDM0","avatar_url":"https://avatars.githubusercontent.com/u/29196434?v=4","gravatar_id":"","url":"https://api.github.com/users/ammarmallik","html_url":"https://github.com/ammarmallik","followers_url":"https://api.github.com/users/ammarmallik/followers","following_url":"https://api.github.com/users/ammarmallik/following{/other_user}","gists_url":"https://api.github.com/users/ammarmallik/gists{/gist_id}","starred_url":"https://api.github.com/users/ammarmallik/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ammarmallik/subscriptions","organizations_url":"https://api.github.com/users/ammarmallik/orgs","repos_url":"https://api.github.com/users/ammarmallik/repos","events_url":"https://api.github.com/users/ammarmallik/events{/privacy}","received_events_url":"https://api.github.com/users/ammarmallik/received_events","type":"User","site_admin":false,"name":"Ammar Akbar","company":null,"blog":"","location":"Lahore","email":null,"hireable":true,"bio":null,"twitter_username":null,"public_repos":14,"public_gists":2,"followers":0,"following":3,"created_at":"2017-06-05T09:42:01Z","updated_at":"2022-10-24T20:26:22Z"}
|
||||
@@ -0,0 +1,32 @@
|
||||
https
|
||||
POST
|
||||
api.github.com
|
||||
None
|
||||
/app/installations/30614431/access_tokens
|
||||
{'Authorization': 'Bearer jwt_removed', 'User-Agent': 'PyGithub/Python', 'Content-Type': 'application/json'}
|
||||
{"permissions": {}}
|
||||
201
|
||||
[('status', '201 CREATED'), ('server', 'Github.com'), ('date', 'Mon, 24 Oct 2022 23:11:45 GMT'), ('content-type', 'application/json; charset=utf-8'), ('connection', 'keep-alive'), ('content-length', '1962'), ('etag', 'W/"b11a1c9caabe35f1de0a13e597a3022d27d2bff0694c2ccb5a65edc3b4d18837"'), ('cache-control', 'public, max-age=60, s-maxage=60'), ('vary', 'Accept'), ('x-github-media-type', 'github.v3; format=json'), ('access-control-expose-headers', 'ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset'), ('access-control-allow-origin', '*'), ('strict-transport-security', 'max-age=31536000; includeSubdomains; preload'), ('x-frame-options', 'deny'), ('x-content-type-options', 'nosniff'), ('x-xss-protection', '0'), ('referrer-policy', 'origin-when-cross-origin, strict-origin-when-cross-origin'), ('x-github-request-id', "E475:53DD:8B7A89E:11E38A79:63571BB0"), ('vary', 'Accept-Encoding, Accept, X-Requested-With'), ('content-security-policy', "default-src 'none'")]
|
||||
{"token":"ghs_1llwuELtXN5HDOB99XhpcTXdJxbOuF0ZlSmj", "expires_at":"2024-11-25T01:00:02Z", "permissions":{"issues":"read","metadata":"read"}, "repository_selection":"selected"}
|
||||
|
||||
https
|
||||
POST
|
||||
api.github.com
|
||||
None
|
||||
/app/installations/30614487/access_tokens
|
||||
{'Authorization': 'Bearer jwt_removed', 'User-Agent': 'PyGithub/Python', 'Content-Type': 'application/json'}
|
||||
{"permissions": {}}
|
||||
201
|
||||
[('status', '201 CREATED'), ('server', 'Github.com'), ('date', 'Mon, 24 Oct 2022 23:11:45 GMT'), ('content-type', 'application/json; charset=utf-8'), ('connection', 'keep-alive'), ('content-length', '1962'), ('etag', 'W/"b11a1c9caabe35f1de0a13e597a3022d27d2bff0694c2ccb5a65edc3b4d18837"'), ('cache-control', 'public, max-age=60, s-maxage=60'), ('vary', 'Accept'), ('x-github-media-type', 'github.v3; format=json'), ('access-control-expose-headers', 'ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset'), ('access-control-allow-origin', '*'), ('strict-transport-security', 'max-age=31536000; includeSubdomains; preload'), ('x-frame-options', 'deny'), ('x-content-type-options', 'nosniff'), ('x-xss-protection', '0'), ('referrer-policy', 'origin-when-cross-origin, strict-origin-when-cross-origin'), ('x-github-request-id', "E475:53DD:8B7A89E:11E38A79:63571BB0"), ('vary', 'Accept-Encoding, Accept, X-Requested-With'), ('content-security-policy', "default-src 'none'")]
|
||||
{"token":"ghs_V0xygF8yACXSDz5FM65QWV1BT2vtxw0cbgPw", "expires_at":"2024-11-25T01:00:02Z", "permissions":{"organization_administration":"read","administration":"write","issues":"write","metadata":"read"}, "repository_selection":"selected"}
|
||||
|
||||
https
|
||||
POST
|
||||
api.github.com
|
||||
None
|
||||
/app/installations/30614431/access_tokens
|
||||
{'Authorization': 'Bearer jwt_removed', 'User-Agent': 'PyGithub/Python', 'Content-Type': 'application/json'}
|
||||
{"permissions": {}}
|
||||
201
|
||||
[('status', '201 CREATED'), ('server', 'Github.com'), ('date', 'Mon, 24 Oct 2022 23:11:45 GMT'), ('content-type', 'application/json; charset=utf-8'), ('connection', 'keep-alive'), ('content-length', '1962'), ('etag', 'W/"b11a1c9caabe35f1de0a13e597a3022d27d2bff0694c2ccb5a65edc3b4d18837"'), ('cache-control', 'public, max-age=60, s-maxage=60'), ('vary', 'Accept'), ('x-github-media-type', 'github.v3; format=json'), ('access-control-expose-headers', 'ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset'), ('access-control-allow-origin', '*'), ('strict-transport-security', 'max-age=31536000; includeSubdomains; preload'), ('x-frame-options', 'deny'), ('x-content-type-options', 'nosniff'), ('x-xss-protection', '0'), ('referrer-policy', 'origin-when-cross-origin, strict-origin-when-cross-origin'), ('x-github-request-id', "E475:53DD:8B7A89E:11E38A79:63571BB0"), ('vary', 'Accept-Encoding, Accept, X-Requested-With'), ('content-security-policy', "default-src 'none'")]
|
||||
{"token":"ghs_1llwuELtXN5HDOB99XhpcTXdJxbOuF0ZlSmj", "expires_at":"2024-11-25T01:00:02Z", "permissions":{"issues":"read","metadata":"read"}, "repository_selection":"selected"}
|
||||
@@ -0,0 +1,11 @@
|
||||
https
|
||||
POST
|
||||
api.github.com
|
||||
None
|
||||
/app/installations/40432121/access_tokens
|
||||
{'Authorization': 'Bearer jwt_removed', 'User-Agent': 'PyGithub/Python', 'Content-Type': 'application/json'}
|
||||
{"permissions": {}}
|
||||
404
|
||||
[('status', '404 NOT FOUND'), ('server', 'Github.com'), ('date', 'Mon, 24 Oct 2022 23:11:45 GMT'), ('content-type', 'application/json; charset=utf-8'), ('connection', 'keep-alive'), ('content-length', '1962'), ('etag', '"b11a1c9caabe35f1de0a13e597a3022d27d2bff0694c2ccb5a65edc3b4d18837"'), ('cache-control', 'public, max-age=60, s-maxage=60'), ('vary', 'Accept'), ('x-github-media-type', 'github.v3; format=json'), ('access-control-expose-headers', 'ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset'), ('access-control-allow-origin', '*'), ('strict-transport-security', 'max-age=31536000; includeSubdomains; preload'), ('x-frame-options', 'deny'), ('x-content-type-options', 'nosniff'), ('x-xss-protection', '0'), ('referrer-policy', 'origin-when-cross-origin, strict-origin-when-cross-origin'), ('x-github-request-id', "E475:53DD:8B7A89E:11E38A79:63571BB0"), ('vary', 'Accept-Encoding, Accept, X-Requested-With'), ('content-security-policy', "default-src 'none'")]
|
||||
{"message":"Not Found","documentation_url":"https://docs.github.com/rest/reference/apps#get-a-repository-installation-for-the-authenticated-app"}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
https
|
||||
POST
|
||||
api.github.com
|
||||
None
|
||||
/app/installations/30614431/access_tokens
|
||||
{'Authorization': 'Bearer jwt_removed', 'User-Agent': 'PyGithub/Python', 'Content-Type': 'application/json'}
|
||||
{"permissions": {}}
|
||||
401
|
||||
[('status', '401 UNAUTHORIZED'), ('server', 'Github.com'), ('date', 'Mon, 24 Oct 2022 23:11:45 GMT'), ('content-type', 'application/json; charset=utf-8'), ('connection', 'keep-alive'), ('content-length', '1962'), ('etag', 'W/"b11a1c9caabe35f1de0a13e597a3022d27d2bff0694c2ccb5a65edc3b4d18837"'), ('cache-control', 'public, max-age=60, s-maxage=60'), ('vary', 'Accept'), ('x-github-media-type', 'github.v3; format=json'), ('access-control-expose-headers', 'ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset'), ('access-control-allow-origin', '*'), ('strict-transport-security', 'max-age=31536000; includeSubdomains; preload'), ('x-frame-options', 'deny'), ('x-content-type-options', 'nosniff'), ('x-xss-protection', '0'), ('referrer-policy', 'origin-when-cross-origin, strict-origin-when-cross-origin'), ('x-github-request-id', "E475:53DD:8B7A89E:11E38A79:63571BB0"), ('vary', 'Accept-Encoding, Accept, X-Requested-With'), ('content-security-policy', "default-src 'none'")]
|
||||
{"message":"'Expiration time' claim ('exp') must be a numeric value representing the future time at which the assertion expires","documentation_url":"https://docs.github.com/rest"}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
https
|
||||
POST
|
||||
api.github.com
|
||||
None
|
||||
/app/installations/30614431/access_tokens
|
||||
{'Authorization': 'Bearer jwt_removed', 'User-Agent': 'PyGithub/Python', 'Content-Type': 'application/json'}
|
||||
{"permissions": "invalid_data"}
|
||||
400
|
||||
[('status', '400 BAD REQUEST'), ('server', 'Github.com'), ('date', 'Mon, 24 Oct 2022 23:11:45 GMT'), ('content-type', 'application/json; charset=utf-8'), ('connection', 'keep-alive'), ('content-length', '1962'), ('etag', 'W/"b11a1c9caabe35f1de0a13e597a3022d27d2bff0694c2ccb5a65edc3b4d18837"'), ('cache-control', 'public, max-age=60, s-maxage=60'), ('vary', 'Accept'), ('x-github-media-type', 'github.v3; format=json'), ('access-control-expose-headers', 'ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset'), ('access-control-allow-origin', '*'), ('strict-transport-security', 'max-age=31536000; includeSubdomains; preload'), ('x-frame-options', 'deny'), ('x-content-type-options', 'nosniff'), ('x-xss-protection', '0'), ('referrer-policy', 'origin-when-cross-origin, strict-origin-when-cross-origin'), ('x-github-request-id', "E475:53DD:8B7A89E:11E38A79:63571BB0"), ('vary', 'Accept-Encoding, Accept, X-Requested-With'), ('content-security-policy', "default-src 'none'")]
|
||||
{"message":"Problems parsing JSON","documentation_url":"https://docs.github.com/rest/reference/apps#create-an-installation-access-token-for-an-app"}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
https
|
||||
POST
|
||||
api.github.com
|
||||
None
|
||||
/app/installations/30614431/access_tokens
|
||||
{'Authorization': 'Bearer jwt_removed', 'User-Agent': 'PyGithub/Python', 'Content-Type': 'application/json'}
|
||||
{"permissions": {"test-permissions": "read"}}
|
||||
422
|
||||
[('status', '422 UNPROCESSABLE ENTITY'), ('server', 'Github.com'), ('date', 'Mon, 24 Oct 2022 23:11:45 GMT'), ('content-type', 'application/json; charset=utf-8'), ('connection', 'keep-alive'), ('content-length', '1962'), ('etag', 'W/"b11a1c9caabe35f1de0a13e597a3022d27d2bff0694c2ccb5a65edc3b4d18837"'), ('cache-control', 'public, max-age=60, s-maxage=60'), ('vary', 'Accept'), ('x-github-media-type', 'github.v3; format=json'), ('access-control-expose-headers', 'ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset'), ('access-control-allow-origin', '*'), ('strict-transport-security', 'max-age=31536000; includeSubdomains; preload'), ('x-frame-options', 'deny'), ('x-content-type-options', 'nosniff'), ('x-xss-protection', '0'), ('referrer-policy', 'origin-when-cross-origin, strict-origin-when-cross-origin'), ('x-github-request-id', "E475:53DD:8B7A89E:11E38A79:63571BB0"), ('vary', 'Accept-Encoding, Accept, X-Requested-With'), ('content-security-policy', "default-src 'none'")]
|
||||
{"message":"The permissions requested are not granted to this installation.","documentation_url":"https://docs.github.com/rest/reference/apps#create-an-installation-access-token-for-an-app"}
|
||||
@@ -0,0 +1,11 @@
|
||||
https
|
||||
GET
|
||||
api.github.com
|
||||
None
|
||||
/app/installations/30614487
|
||||
{'Authorization': 'Bearer jwt_removed', 'User-Agent': 'PyGithub/Python', 'Accept': 'application/vnd.github.machine-man-preview+json'}
|
||||
None
|
||||
200
|
||||
[('status', '200 OK'), ('server', 'Github.com'), ('date', 'Mon, 24 Oct 2022 23:11:45 GMT'), ('content-type', 'application/json; charset=utf-8'), ('connection', 'keep-alive'), ('content-length', '1962'), ('etag', '"b11a1c9caabe35f1de0a13e597a3022d27d2bff0694c2ccb5a65edc3b4d18837"'), ('cache-control', 'public, max-age=60, s-maxage=60'), ('vary', 'Accept'), ('x-github-media-type', 'github.v3; format=json'), ('access-control-expose-headers', 'ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset'), ('access-control-allow-origin', '*'), ('strict-transport-security', 'max-age=31536000; includeSubdomains; preload'), ('x-frame-options', 'deny'), ('x-content-type-options', 'nosniff'), ('x-xss-protection', '0'), ('referrer-policy', 'origin-when-cross-origin, strict-origin-when-cross-origin'), ('x-github-request-id', "E475:53DD:8B7A89E:11E38A79:63571BB0"), ('vary', 'Accept-Encoding, Accept, X-Requested-With'), ('content-security-policy', "default-src 'none'")]
|
||||
{"id":30614487,"account":{"login":"GithubApp-Test-Org","id":116723333,"node_id":"O_kgDOBvUOhQ","avatar_url":"https://avatars.githubusercontent.com/u/116723333?v=4","gravatar_id":"","url":"https://api.github.com/users/GithubApp-Test-Org","html_url":"https://github.com/GithubApp-Test-Org","followers_url":"https://api.github.com/users/GithubApp-Test-Org/followers","following_url":"https://api.github.com/users/GithubApp-Test-Org/following{/other_user}","gists_url":"https://api.github.com/users/GithubApp-Test-Org/gists{/gist_id}","starred_url":"https://api.github.com/users/GithubApp-Test-Org/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/GithubApp-Test-Org/subscriptions","organizations_url":"https://api.github.com/users/GithubApp-Test-Org/orgs","repos_url":"https://api.github.com/users/GithubApp-Test-Org/repos","events_url":"https://api.github.com/users/GithubApp-Test-Org/events{/privacy}","received_events_url":"https://api.github.com/users/GithubApp-Test-Org/received_events","type":"Organization","site_admin":false},"repository_selection":"selected","access_tokens_url":"https://api.github.com/app/installations/30614487/access_tokens","repositories_url":"https://api.github.com/installation/repositories","html_url":"https://github.com/organizations/GithubApp-Test-Org/settings/installations/30614487","app_id":243473,"app_slug":"gh-actions-test-ammar","target_id":116723333,"target_type":"Organization","permissions":{"issues":"write","metadata":"read","administration":"write","organization_administration":"read"},"events":[],"created_at":"2022-10-26T11:15:21.000Z","updated_at":"2022-10-26T11:36:34.000Z","single_file_name":null,"has_multiple_single_files":false,"single_file_paths":[],"suspended_by":null,"suspended_at":null}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
https
|
||||
GET
|
||||
api.github.com
|
||||
None
|
||||
/orgs/GithubApp-Test-Org-404/installation
|
||||
{'Authorization': 'Bearer jwt_removed', 'User-Agent': 'PyGithub/Python', 'Accept': 'application/vnd.github.machine-man-preview+json'}
|
||||
None
|
||||
404
|
||||
[('status', '404 NOT FOUND'), ('server', 'Github.com'), ('date', 'Mon, 24 Oct 2022 23:11:45 GMT'), ('content-type', 'application/json; charset=utf-8'), ('connection', 'keep-alive'), ('content-length', '1962'), ('etag', '"b11a1c9caabe35f1de0a13e597a3022d27d2bff0694c2ccb5a65edc3b4d18837"'), ('cache-control', 'public, max-age=60, s-maxage=60'), ('vary', 'Accept'), ('x-github-media-type', 'github.v3; format=json'), ('access-control-expose-headers', 'ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset'), ('access-control-allow-origin', '*'), ('strict-transport-security', 'max-age=31536000; includeSubdomains; preload'), ('x-frame-options', 'deny'), ('x-content-type-options', 'nosniff'), ('x-xss-protection', '0'), ('referrer-policy', 'origin-when-cross-origin, strict-origin-when-cross-origin'), ('x-github-request-id', "E475:53DD:8B7A89E:11E38A79:63571BB0"), ('vary', 'Accept-Encoding, Accept, X-Requested-With'), ('content-security-policy', "default-src 'none'")]
|
||||
{"message":"Not Found","documentation_url":"https://docs.github.com/rest/reference/apps#get-a-repository-installation-for-the-authenticated-app"}
|
||||
@@ -0,0 +1,11 @@
|
||||
https
|
||||
GET
|
||||
api.github.com
|
||||
None
|
||||
/orgs/GithubApp-Test-Org/installation
|
||||
{'Authorization': 'Bearer jwt_removed', 'User-Agent': 'PyGithub/Python', 'Accept': 'application/vnd.github.machine-man-preview+json'}
|
||||
None
|
||||
401
|
||||
[('status', '401 UNAUTHORIZED'), ('server', 'Github.com'), ('date', 'Mon, 24 Oct 2022 23:11:45 GMT'), ('content-type', 'application/json; charset=utf-8'), ('connection', 'keep-alive'), ('content-length', '1962'), ('etag', 'W/"b11a1c9caabe35f1de0a13e597a3022d27d2bff0694c2ccb5a65edc3b4d18837"'), ('cache-control', 'public, max-age=60, s-maxage=60'), ('vary', 'Accept'), ('x-github-media-type', 'github.v3; format=json'), ('access-control-expose-headers', 'ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset'), ('access-control-allow-origin', '*'), ('strict-transport-security', 'max-age=31536000; includeSubdomains; preload'), ('x-frame-options', 'deny'), ('x-content-type-options', 'nosniff'), ('x-xss-protection', '0'), ('referrer-policy', 'origin-when-cross-origin, strict-origin-when-cross-origin'), ('x-github-request-id', "E475:53DD:8B7A89E:11E38A79:63571BB0"), ('vary', 'Accept-Encoding, Accept, X-Requested-With'), ('content-security-policy', "default-src 'none'")]
|
||||
{"message":"'Expiration time' claim ('exp') must be a numeric value representing the future time at which the assertion expires","documentation_url":"https://docs.github.com/rest"}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
https
|
||||
GET
|
||||
api.github.com
|
||||
None
|
||||
/app/installations
|
||||
{'Authorization': 'Bearer jwt_removed', 'User-Agent': 'PyGithub/Python', 'Accept': 'application/vnd.github.machine-man-preview+json'}
|
||||
None
|
||||
200
|
||||
[('status', '200 OK'), ('server', 'Github.com'), ('date', 'Mon, 24 Oct 2022 23:11:45 GMT'), ('content-type', 'application/json; charset=utf-8'), ('connection', 'keep-alive'), ('content-length', '1962'), ('etag', '"b11a1c9caabe35f1de0a13e597a3022d27d2bff0694c2ccb5a65edc3b4d18837"'), ('cache-control', 'public, max-age=60, s-maxage=60'), ('vary', 'Accept'), ('x-github-media-type', 'github.v3; format=json'), ('access-control-expose-headers', 'ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset'), ('access-control-allow-origin', '*'), ('strict-transport-security', 'max-age=31536000; includeSubdomains; preload'), ('x-frame-options', 'deny'), ('x-content-type-options', 'nosniff'), ('x-xss-protection', '0'), ('referrer-policy', 'origin-when-cross-origin, strict-origin-when-cross-origin'), ('x-github-request-id', "E475:53DD:8B7A89E:11E38A79:63571BB0"), ('vary', 'Accept-Encoding, Accept, X-Requested-With'), ('content-security-policy', "default-src 'none'")]
|
||||
[{"id":30614487,"account":{"login":"GithubApp-Test-Org","id":116723333,"node_id":"O_kgDOBvUOhQ","avatar_url":"https://avatars.githubusercontent.com/u/116723333?v=4","gravatar_id":"","url":"https://api.github.com/users/GithubApp-Test-Org","html_url":"https://github.com/GithubApp-Test-Org","followers_url":"https://api.github.com/users/GithubApp-Test-Org/followers","following_url":"https://api.github.com/users/GithubApp-Test-Org/following{/other_user}","gists_url":"https://api.github.com/users/GithubApp-Test-Org/gists{/gist_id}","starred_url":"https://api.github.com/users/GithubApp-Test-Org/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/GithubApp-Test-Org/subscriptions","organizations_url":"https://api.github.com/users/GithubApp-Test-Org/orgs","repos_url":"https://api.github.com/users/GithubApp-Test-Org/repos","events_url":"https://api.github.com/users/GithubApp-Test-Org/events{/privacy}","received_events_url":"https://api.github.com/users/GithubApp-Test-Org/received_events","type":"Organization","site_admin":false},"repository_selection":"selected","access_tokens_url":"https://api.github.com/app/installations/30614487/access_tokens","repositories_url":"https://api.github.com/installation/repositories","html_url":"https://github.com/organizations/GithubApp-Test-Org/settings/installations/30614487","app_id":243473,"app_slug":"gh-actions-test-ammar","target_id":116723333,"target_type":"Organization","permissions":{"issues":"write","metadata":"read","administration":"write","organization_administration":"read"},"events":[],"created_at":"2022-10-26T11:15:21.000Z","updated_at":"2022-10-26T11:36:34.000Z","single_file_name":null,"has_multiple_single_files":false,"single_file_paths":[],"suspended_by":null,"suspended_at":null},{"id":30614431,"account":{"login":"ammarmallik","id":29196434,"node_id":"MDQ6VXNlcjI5MTk2NDM0","avatar_url":"https://avatars.githubusercontent.com/u/29196434?v=4","gravatar_id":"","url":"https://api.github.com/users/ammarmallik","html_url":"https://github.com/ammarmallik","followers_url":"https://api.github.com/users/ammarmallik/followers","following_url":"https://api.github.com/users/ammarmallik/following{/other_user}","gists_url":"https://api.github.com/users/ammarmallik/gists{/gist_id}","starred_url":"https://api.github.com/users/ammarmallik/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ammarmallik/subscriptions","organizations_url":"https://api.github.com/users/ammarmallik/orgs","repos_url":"https://api.github.com/users/ammarmallik/repos","events_url":"https://api.github.com/users/ammarmallik/events{/privacy}","received_events_url":"https://api.github.com/users/ammarmallik/received_events","type":"User","site_admin":false},"repository_selection":"selected","access_tokens_url":"https://api.github.com/app/installations/30614431/access_tokens","repositories_url":"https://api.github.com/installation/repositories","html_url":"https://github.com/settings/installations/30614431","app_id":243473,"app_slug":"gh-actions-test-ammar","target_id":29196434,"target_type":"User","permissions":{"issues":"write","metadata":"read","administration":"write"},"events":[],"created_at":"2022-10-26T11:13:03.000Z","updated_at":"2022-10-26T11:13:03.000Z","single_file_name":null,"has_multiple_single_files":false,"single_file_paths":[],"suspended_by":null,"suspended_at":null}]
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
https
|
||||
GET
|
||||
api.github.com
|
||||
None
|
||||
/orgs/GithubApp-Test-Org/installation
|
||||
{'Authorization': 'Bearer jwt_removed', 'User-Agent': 'PyGithub/Python', 'Accept': 'application/vnd.github.machine-man-preview+json'}
|
||||
None
|
||||
200
|
||||
[('status', '200 OK'), ('server', 'Github.com'), ('date', 'Mon, 24 Oct 2022 23:11:45 GMT'), ('content-type', 'application/json; charset=utf-8'), ('connection', 'keep-alive'), ('content-length', '1962'), ('etag', '"b11a1c9caabe35f1de0a13e597a3022d27d2bff0694c2ccb5a65edc3b4d18837"'), ('cache-control', 'public, max-age=60, s-maxage=60'), ('vary', 'Accept'), ('x-github-media-type', 'github.v3; format=json'), ('access-control-expose-headers', 'ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset'), ('access-control-allow-origin', '*'), ('strict-transport-security', 'max-age=31536000; includeSubdomains; preload'), ('x-frame-options', 'deny'), ('x-content-type-options', 'nosniff'), ('x-xss-protection', '0'), ('referrer-policy', 'origin-when-cross-origin, strict-origin-when-cross-origin'), ('x-github-request-id', "E475:53DD:8B7A89E:11E38A79:63571BB0"), ('vary', 'Accept-Encoding, Accept, X-Requested-With'), ('content-security-policy', "default-src 'none'")]
|
||||
{"id":30614487,"account":{"login":"GithubApp-Test-Org","id":116723333,"node_id":"O_kgDOBvUOhQ","avatar_url":"https://avatars.githubusercontent.com/u/116723333?v=4","gravatar_id":"","url":"https://api.github.com/users/GithubApp-Test-Org","html_url":"https://github.com/GithubApp-Test-Org","followers_url":"https://api.github.com/users/GithubApp-Test-Org/followers","following_url":"https://api.github.com/users/GithubApp-Test-Org/following{/other_user}","gists_url":"https://api.github.com/users/GithubApp-Test-Org/gists{/gist_id}","starred_url":"https://api.github.com/users/GithubApp-Test-Org/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/GithubApp-Test-Org/subscriptions","organizations_url":"https://api.github.com/users/GithubApp-Test-Org/orgs","repos_url":"https://api.github.com/users/GithubApp-Test-Org/repos","events_url":"https://api.github.com/users/GithubApp-Test-Org/events{/privacy}","received_events_url":"https://api.github.com/users/GithubApp-Test-Org/received_events","type":"Organization","site_admin":false},"repository_selection":"selected","access_tokens_url":"https://api.github.com/app/installations/30614487/access_tokens","repositories_url":"https://api.github.com/installation/repositories","html_url":"https://github.com/organizations/GithubApp-Test-Org/settings/installations/30614487","app_id":243473,"app_slug":"gh-actions-test-ammar","target_id":116723333,"target_type":"Organization","permissions":{"issues":"write","metadata":"read","administration":"write","organization_administration":"read"},"events":[],"created_at":"2022-10-26T11:15:21.000Z","updated_at":"2022-10-26T11:36:34.000Z","single_file_name":null,"has_multiple_single_files":false,"single_file_paths":[],"suspended_by":null,"suspended_at":null}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
https
|
||||
GET
|
||||
api.github.com
|
||||
None
|
||||
/repos/ammarmallik/test-runner/installation
|
||||
{'Authorization': 'Bearer jwt_removed', 'User-Agent': 'PyGithub/Python', 'Accept': 'application/vnd.github.machine-man-preview+json'}
|
||||
None
|
||||
200
|
||||
[('status', '200 OK'), ('server', 'Github.com'), ('date', 'Mon, 24 Oct 2022 23:11:45 GMT'), ('content-type', 'application/json; charset=utf-8'), ('connection', 'keep-alive'), ('content-length', '1962'), ('etag', '"b11a1c9caabe35f1de0a13e597a3022d27d2bff0694c2ccb5a65edc3b4d18837"'), ('cache-control', 'public, max-age=60, s-maxage=60'), ('vary', 'Accept'), ('x-github-media-type', 'github.v3; format=json'), ('access-control-expose-headers', 'ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset'), ('access-control-allow-origin', '*'), ('strict-transport-security', 'max-age=31536000; includeSubdomains; preload'), ('x-frame-options', 'deny'), ('x-content-type-options', 'nosniff'), ('x-xss-protection', '0'), ('referrer-policy', 'origin-when-cross-origin, strict-origin-when-cross-origin'), ('x-github-request-id', "E475:53DD:8B7A89E:11E38A79:63571BB0"), ('vary', 'Accept-Encoding, Accept, X-Requested-With'), ('content-security-policy', "default-src 'none'")]
|
||||
{"id":30614431,"account":{"login":"ammarmallik","id":29196434,"node_id":"MDQ6VXNlcjI5MTk2NDM0","avatar_url":"https://avatars.githubusercontent.com/u/29196434?v=4","gravatar_id":"","url":"https://api.github.com/users/ammarmallik","html_url":"https://github.com/ammarmallik","followers_url":"https://api.github.com/users/ammarmallik/followers","following_url":"https://api.github.com/users/ammarmallik/following{/other_user}","gists_url":"https://api.github.com/users/ammarmallik/gists{/gist_id}","starred_url":"https://api.github.com/users/ammarmallik/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ammarmallik/subscriptions","organizations_url":"https://api.github.com/users/ammarmallik/orgs","repos_url":"https://api.github.com/users/ammarmallik/repos","events_url":"https://api.github.com/users/ammarmallik/events{/privacy}","received_events_url":"https://api.github.com/users/ammarmallik/received_events","type":"User","site_admin":false},"repository_selection":"selected","access_tokens_url":"https://api.github.com/app/installations/30614431/access_tokens","repositories_url":"https://api.github.com/installation/repositories","html_url":"https://github.com/settings/installations/30614431","app_id":243473,"app_slug":"gh-actions-test-ammar","target_id":29196434,"target_type":"User","permissions":{"issues":"write","metadata":"read","administration":"write"},"events":[],"created_at":"2022-10-26T11:13:03.000Z","updated_at":"2022-10-26T11:13:03.000Z","single_file_name":null,"has_multiple_single_files":false,"single_file_paths":[],"suspended_by":null,"suspended_at":null}
|
||||
@@ -0,0 +1,10 @@
|
||||
https
|
||||
GET
|
||||
api.github.com
|
||||
None
|
||||
/users/ammarmallik/installation
|
||||
{'Authorization': 'Bearer jwt_removed', 'User-Agent': 'PyGithub/Python', 'Accept': 'application/vnd.github.machine-man-preview+json'}
|
||||
None
|
||||
200
|
||||
[('status', '200 OK'), ('server', 'Github.com'), ('date', 'Mon, 24 Oct 2022 23:11:45 GMT'), ('content-type', 'application/json; charset=utf-8'), ('connection', 'keep-alive'), ('content-length', '1962'), ('etag', '"b11a1c9caabe35f1de0a13e597a3022d27d2bff0694c2ccb5a65edc3b4d18837"'), ('cache-control', 'public, max-age=60, s-maxage=60'), ('vary', 'Accept'), ('x-github-media-type', 'github.v3; format=json'), ('access-control-expose-headers', 'ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset'), ('access-control-allow-origin', '*'), ('strict-transport-security', 'max-age=31536000; includeSubdomains; preload'), ('x-frame-options', 'deny'), ('x-content-type-options', 'nosniff'), ('x-xss-protection', '0'), ('referrer-policy', 'origin-when-cross-origin, strict-origin-when-cross-origin'), ('x-github-request-id', "E475:53DD:8B7A89E:11E38A79:63571BB0"), ('vary', 'Accept-Encoding, Accept, X-Requested-With'), ('content-security-policy', "default-src 'none'")]
|
||||
{"id":30614431,"account":{"login":"ammarmallik","id":29196434,"node_id":"MDQ6VXNlcjI5MTk2NDM0","avatar_url":"https://avatars.githubusercontent.com/u/29196434?v=4","gravatar_id":"","url":"https://api.github.com/users/ammarmallik","html_url":"https://github.com/ammarmallik","followers_url":"https://api.github.com/users/ammarmallik/followers","following_url":"https://api.github.com/users/ammarmallik/following{/other_user}","gists_url":"https://api.github.com/users/ammarmallik/gists{/gist_id}","starred_url":"https://api.github.com/users/ammarmallik/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ammarmallik/subscriptions","organizations_url":"https://api.github.com/users/ammarmallik/orgs","repos_url":"https://api.github.com/users/ammarmallik/repos","events_url":"https://api.github.com/users/ammarmallik/events{/privacy}","received_events_url":"https://api.github.com/users/ammarmallik/received_events","type":"User","site_admin":false},"repository_selection":"selected","access_tokens_url":"https://api.github.com/app/installations/30614431/access_tokens","repositories_url":"https://api.github.com/installation/repositories","html_url":"https://github.com/settings/installations/30614431","app_id":243473,"app_slug":"gh-actions-test-ammar","target_id":29196434,"target_type":"User","permissions":{"issues":"write","metadata":"read","administration":"write"},"events":[],"created_at":"2022-10-26T11:13:03.000Z","updated_at":"2022-10-26T11:13:03.000Z","single_file_name":null,"has_multiple_single_files":false,"single_file_paths":[],"suspended_by":null,"suspended_at":null}
|
||||
Reference in New Issue
Block a user