diff --git a/github/Installation.py b/github/Installation.py index 618219e3..217ed867 100644 --- a/github/Installation.py +++ b/github/Installation.py @@ -35,6 +35,7 @@ import github.PaginatedList import github.Plan import github.Repository import github.UserKey +from github.Auth import AppAuth from . import Consts @@ -46,6 +47,16 @@ class Installation(github.GithubObject.NonCompletableGithubObject): This class represents Installations. The reference can be found here https://docs.github.com/en/rest/reference/apps#installations """ + def __init__(self, requester, headers, attributes, completed): + super().__init__(requester, headers, attributes, completed) + + auth = self._requester.auth if self._requester is not None else None + # Usually, an Installation is created from a Requester with App authentication + if isinstance(auth, AppAuth): + # But the installation has to authenticate as an installation (e.g. for get_repos()) + auth = auth.get_installation_auth(self.id, requester=self._requester) + self._requester = self._requester.withAuth(auth) + def __repr__(self): return self.get__repr__({"id": self._id.value}) diff --git a/github/Requester.py b/github/Requester.py index 182346a4..01782f1a 100644 --- a/github/Requester.py +++ b/github/Requester.py @@ -343,6 +343,10 @@ class Requester: def base_url(self): return self.__base_url + @property + def auth(self): + return self.__auth + def withAuth(self, auth): """ Create a new requester instance with identical configuration but the given authentication method. diff --git a/github/Requester.pyi b/github/Requester.pyi index cc38a8c3..9d381816 100644 --- a/github/Requester.pyi +++ b/github/Requester.pyi @@ -141,6 +141,8 @@ class Requester: ) -> None: ... @property def base_url(self) -> str: ... + @property + def auth(self) -> Optional[Auth]: ... def withAuth(self, auth: Optional[Auth]) -> Requester: ... def _initializeDebugFeature(self) -> None: ... def check_me(self, obj: GithubObject) -> None: ... diff --git a/tests/Installation.py b/tests/Installation.py new file mode 100644 index 00000000..f4ea1c09 --- /dev/null +++ b/tests/Installation.py @@ -0,0 +1,46 @@ +############################ Copyrights and license ############################ +# # +# Copyright 2023 Enrico Minack # +# # +# 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 . # +# # +################################################################################ + +import github +from github.Auth import AppAuth + +from . import Framework, GithubIntegration + + +class Installation(Framework.BasicTestCase): + def setUp(self): + super().setUp() + app_id = 36541767 + private_key = GithubIntegration.PRIVATE_KEY + self.auth = AppAuth(app_id, private_key) + self.integration = github.GithubIntegration(auth=self.auth) + self.installations = list(self.integration.get_installations()) + + def testGetRepos(self): + self.assertEqual(len(self.installations), 1) + installation = self.installations[0] + + repos = list(installation.get_repos()) + self.assertEqual(len(repos), 2) + self.assertListEqual( + [repo.full_name for repo in repos], ["EnricoMi/sandbox", "EnricoMi/python"] + ) diff --git a/tests/ReplayData/Installation.setUp.txt b/tests/ReplayData/Installation.setUp.txt new file mode 100644 index 00000000..8de8a570 --- /dev/null +++ b/tests/ReplayData/Installation.setUp.txt @@ -0,0 +1,11 @@ +https +GET +api.github.com +None +/app/installations +{'Accept': 'application/vnd.github.machine-man-preview+json', 'Authorization': 'Bearer jwt_removed', 'User-Agent': 'PyGithub/Python'} +None +200 +[('Server', 'GitHub.com'), ('Date', 'Thu, 08 Jun 2023 07:39:24 GMT'), ('Content-Type', 'application/json; charset=utf-8'), ('Transfer-Encoding', 'chunked'), ('Cache-Control', 'public, max-age=60, s-maxage=60'), ('Vary', 'Accept, Accept-Encoding, Accept, X-Requested-With'), ('ETag', 'W/"b272db57fded7547562b4a357681c6c84054e8fb5169b32206e833bbe7f542e5"'), ('X-GitHub-Media-Type', 'github.v3; param=machine-man-preview; format=json'), ('x-github-api-version-selected', '2022-11-28'), ('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'), ('Content-Security-Policy', "default-src 'none'"), ('Content-Encoding', 'gzip'), ('X-GitHub-Request-Id', 'D58E:1978:151E09E:1565462:648185AC')] +[{"id":36541767,"account":{"login":"EnricoMi","id":44700269,"node_id":"MDQ6VXNlcjQ0NzAwMjY5","avatar_url":"https://avatars.githubusercontent.com/u/44700269?v=4","gravatar_id":"","url":"https://api.github.com/users/EnricoMi","html_url":"https://github.com/EnricoMi","followers_url":"https://api.github.com/users/EnricoMi/followers","following_url":"https://api.github.com/users/EnricoMi/following{/other_user}","gists_url":"https://api.github.com/users/EnricoMi/gists{/gist_id}","starred_url":"https://api.github.com/users/EnricoMi/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/EnricoMi/subscriptions","organizations_url":"https://api.github.com/users/EnricoMi/orgs","repos_url":"https://api.github.com/users/EnricoMi/repos","events_url":"https://api.github.com/users/EnricoMi/events{/privacy}","received_events_url":"https://api.github.com/users/EnricoMi/received_events","type":"User","site_admin":false},"repository_selection":"selected","access_tokens_url":"https://api.github.com/app/installations/36541767/access_tokens","repositories_url":"https://api.github.com/installation/repositories","html_url":"https://github.com/settings/installations/36541767","app_id":319953,"app_slug":"publish-test-results","target_id":44700269,"target_type":"User","permissions":{"checks":"write","issues":"read","contents":"read","metadata":"read","pull_requests":"write"},"events":[],"created_at":"2023-04-17T16:18:05.000Z","updated_at":"2023-06-08T07:38:12.000Z","single_file_name":null,"has_multiple_single_files":false,"single_file_paths":[],"suspended_by":null,"suspended_at":null}] + diff --git a/tests/ReplayData/Installation.testGetRepos.txt b/tests/ReplayData/Installation.testGetRepos.txt new file mode 100644 index 00000000..66a8b6a6 --- /dev/null +++ b/tests/ReplayData/Installation.testGetRepos.txt @@ -0,0 +1,22 @@ +https +POST +api.github.com +None +/app/installations/36541767/access_tokens +{'Accept': 'application/vnd.github.machine-man-preview+json', 'Authorization': 'Bearer jwt_removed', 'User-Agent': 'PyGithub/Python', 'Content-Type': 'application/json'} +{"permissions": {}} +201 +[('Server', 'GitHub.com'), ('Date', 'Thu, 08 Jun 2023 07:39:25 GMT'), ('Content-Type', 'application/json; charset=utf-8'), ('Content-Length', '231'), ('Cache-Control', 'public, max-age=60, s-maxage=60'), ('Vary', 'Accept, Accept-Encoding, Accept, X-Requested-With'), ('ETag', '"783fdaa90e16f1fd718449dd5b21c99024d0821e33012e2610a8446495847795"'), ('X-GitHub-Media-Type', 'github.v3; param=machine-man-preview; format=json'), ('x-github-api-version-selected', '2022-11-28'), ('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'), ('Content-Security-Policy', "default-src 'none'"), ('X-GitHub-Request-Id', 'D590:F2E4:13B6DDD:13FE18A:648185AC')] +{"token":"private_token_removed","expires_at":"2023-06-08T08:39:25Z","permissions":{"metadata":"read"},"repository_selection":"selected"} + +https +GET +api.github.com +None +/installation/repositories +{'Accept': 'application/vnd.github.machine-man-preview+json', 'Authorization': 'token private_token_removed', 'User-Agent': 'PyGithub/Python'} +None +200 +[('Server', 'GitHub.com'), ('Date', 'Thu, 08 Jun 2023 07:39:25 GMT'), ('Content-Type', 'application/json; charset=utf-8'), ('Transfer-Encoding', 'chunked'), ('Cache-Control', 'private, max-age=60, s-maxage=60'), ('Vary', 'Accept, Authorization, Cookie, X-GitHub-OTP, Accept-Encoding, Accept, X-Requested-With'), ('ETag', 'W/"7feb4668c0366b8d93e1fff0a4795d4cd0279f0d185056839c07521f2740640b"'), ('X-GitHub-Media-Type', 'github.v3; param=machine-man-preview; format=json'), ('x-github-api-version-selected', '2022-11-28'), ('X-RateLimit-Limit', '5000'), ('X-RateLimit-Remaining', '4998'), ('X-RateLimit-Reset', '1686213555'), ('X-RateLimit-Used', '2'), ('X-RateLimit-Resource', 'core'), ('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'), ('Content-Security-Policy', "default-src 'none'"), ('Content-Encoding', 'gzip'), ('X-GitHub-Request-Id', 'D594:1C3A:14AB318:14F270C:648185AD')] +{"total_count":2,"repository_selection":"selected","repositories":[{"id":418546277,"node_id":"R_kgDOGPKCZQ","name":"sandbox","full_name":"EnricoMi/sandbox","private":true,"owner":{"login":"EnricoMi","id":44700269,"node_id":"MDQ6VXNlcjQ0NzAwMjY5","avatar_url":"https://avatars.githubusercontent.com/u/44700269?v=4","gravatar_id":"","url":"https://api.github.com/users/EnricoMi","html_url":"https://github.com/EnricoMi","followers_url":"https://api.github.com/users/EnricoMi/followers","following_url":"https://api.github.com/users/EnricoMi/following{/other_user}","gists_url":"https://api.github.com/users/EnricoMi/gists{/gist_id}","starred_url":"https://api.github.com/users/EnricoMi/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/EnricoMi/subscriptions","organizations_url":"https://api.github.com/users/EnricoMi/orgs","repos_url":"https://api.github.com/users/EnricoMi/repos","events_url":"https://api.github.com/users/EnricoMi/events{/privacy}","received_events_url":"https://api.github.com/users/EnricoMi/received_events","type":"User","site_admin":false},"html_url":"https://github.com/EnricoMi/sandbox","description":null,"fork":false,"url":"https://api.github.com/repos/EnricoMi/sandbox","forks_url":"https://api.github.com/repos/EnricoMi/sandbox/forks","keys_url":"https://api.github.com/repos/EnricoMi/sandbox/keys{/key_id}","collaborators_url":"https://api.github.com/repos/EnricoMi/sandbox/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/EnricoMi/sandbox/teams","hooks_url":"https://api.github.com/repos/EnricoMi/sandbox/hooks","issue_events_url":"https://api.github.com/repos/EnricoMi/sandbox/issues/events{/number}","events_url":"https://api.github.com/repos/EnricoMi/sandbox/events","assignees_url":"https://api.github.com/repos/EnricoMi/sandbox/assignees{/user}","branches_url":"https://api.github.com/repos/EnricoMi/sandbox/branches{/branch}","tags_url":"https://api.github.com/repos/EnricoMi/sandbox/tags","blobs_url":"https://api.github.com/repos/EnricoMi/sandbox/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/EnricoMi/sandbox/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/EnricoMi/sandbox/git/refs{/sha}","trees_url":"https://api.github.com/repos/EnricoMi/sandbox/git/trees{/sha}","statuses_url":"https://api.github.com/repos/EnricoMi/sandbox/statuses/{sha}","languages_url":"https://api.github.com/repos/EnricoMi/sandbox/languages","stargazers_url":"https://api.github.com/repos/EnricoMi/sandbox/stargazers","contributors_url":"https://api.github.com/repos/EnricoMi/sandbox/contributors","subscribers_url":"https://api.github.com/repos/EnricoMi/sandbox/subscribers","subscription_url":"https://api.github.com/repos/EnricoMi/sandbox/subscription","commits_url":"https://api.github.com/repos/EnricoMi/sandbox/commits{/sha}","git_commits_url":"https://api.github.com/repos/EnricoMi/sandbox/git/commits{/sha}","comments_url":"https://api.github.com/repos/EnricoMi/sandbox/comments{/number}","issue_comment_url":"https://api.github.com/repos/EnricoMi/sandbox/issues/comments{/number}","contents_url":"https://api.github.com/repos/EnricoMi/sandbox/contents/{+path}","compare_url":"https://api.github.com/repos/EnricoMi/sandbox/compare/{base}...{head}","merges_url":"https://api.github.com/repos/EnricoMi/sandbox/merges","archive_url":"https://api.github.com/repos/EnricoMi/sandbox/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/EnricoMi/sandbox/downloads","issues_url":"https://api.github.com/repos/EnricoMi/sandbox/issues{/number}","pulls_url":"https://api.github.com/repos/EnricoMi/sandbox/pulls{/number}","milestones_url":"https://api.github.com/repos/EnricoMi/sandbox/milestones{/number}","notifications_url":"https://api.github.com/repos/EnricoMi/sandbox/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/EnricoMi/sandbox/labels{/name}","releases_url":"https://api.github.com/repos/EnricoMi/sandbox/releases{/id}","deployments_url":"https://api.github.com/repos/EnricoMi/sandbox/deployments","created_at":"2021-10-18T14:50:05Z","updated_at":"2022-03-14T20:59:56Z","pushed_at":"2023-05-11T13:00:52Z","git_url":"git://github.com/EnricoMi/sandbox.git","ssh_url":"git@github.com:EnricoMi/sandbox.git","clone_url":"https://github.com/EnricoMi/sandbox.git","svn_url":"https://github.com/EnricoMi/sandbox","homepage":null,"size":4616,"stargazers_count":0,"watchers_count":0,"language":"HTML","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"has_discussions":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":0,"license":null,"allow_forking":true,"is_template":false,"web_commit_signoff_required":false,"topics":[],"visibility":"private","forks":0,"open_issues":0,"watchers":0,"default_branch":"main","permissions":{"admin":false,"maintain":false,"push":false,"triage":false,"pull":false}},{"id":224264786,"node_id":"MDEwOlJlcG9zaXRvcnkyMjQyNjQ3ODY=","name":"python","full_name":"EnricoMi/python","private":false,"owner":{"login":"EnricoMi","id":44700269,"node_id":"MDQ6VXNlcjQ0NzAwMjY5","avatar_url":"https://avatars.githubusercontent.com/u/44700269?v=4","gravatar_id":"","url":"https://api.github.com/users/EnricoMi","html_url":"https://github.com/EnricoMi","followers_url":"https://api.github.com/users/EnricoMi/followers","following_url":"https://api.github.com/users/EnricoMi/following{/other_user}","gists_url":"https://api.github.com/users/EnricoMi/gists{/gist_id}","starred_url":"https://api.github.com/users/EnricoMi/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/EnricoMi/subscriptions","organizations_url":"https://api.github.com/users/EnricoMi/orgs","repos_url":"https://api.github.com/users/EnricoMi/repos","events_url":"https://api.github.com/users/EnricoMi/events{/privacy}","received_events_url":"https://api.github.com/users/EnricoMi/received_events","type":"User","site_admin":false},"html_url":"https://github.com/EnricoMi/python","description":"Miscellaneous Python code","fork":false,"url":"https://api.github.com/repos/EnricoMi/python","forks_url":"https://api.github.com/repos/EnricoMi/python/forks","keys_url":"https://api.github.com/repos/EnricoMi/python/keys{/key_id}","collaborators_url":"https://api.github.com/repos/EnricoMi/python/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/EnricoMi/python/teams","hooks_url":"https://api.github.com/repos/EnricoMi/python/hooks","issue_events_url":"https://api.github.com/repos/EnricoMi/python/issues/events{/number}","events_url":"https://api.github.com/repos/EnricoMi/python/events","assignees_url":"https://api.github.com/repos/EnricoMi/python/assignees{/user}","branches_url":"https://api.github.com/repos/EnricoMi/python/branches{/branch}","tags_url":"https://api.github.com/repos/EnricoMi/python/tags","blobs_url":"https://api.github.com/repos/EnricoMi/python/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/EnricoMi/python/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/EnricoMi/python/git/refs{/sha}","trees_url":"https://api.github.com/repos/EnricoMi/python/git/trees{/sha}","statuses_url":"https://api.github.com/repos/EnricoMi/python/statuses/{sha}","languages_url":"https://api.github.com/repos/EnricoMi/python/languages","stargazers_url":"https://api.github.com/repos/EnricoMi/python/stargazers","contributors_url":"https://api.github.com/repos/EnricoMi/python/contributors","subscribers_url":"https://api.github.com/repos/EnricoMi/python/subscribers","subscription_url":"https://api.github.com/repos/EnricoMi/python/subscription","commits_url":"https://api.github.com/repos/EnricoMi/python/commits{/sha}","git_commits_url":"https://api.github.com/repos/EnricoMi/python/git/commits{/sha}","comments_url":"https://api.github.com/repos/EnricoMi/python/comments{/number}","issue_comment_url":"https://api.github.com/repos/EnricoMi/python/issues/comments{/number}","contents_url":"https://api.github.com/repos/EnricoMi/python/contents/{+path}","compare_url":"https://api.github.com/repos/EnricoMi/python/compare/{base}...{head}","merges_url":"https://api.github.com/repos/EnricoMi/python/merges","archive_url":"https://api.github.com/repos/EnricoMi/python/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/EnricoMi/python/downloads","issues_url":"https://api.github.com/repos/EnricoMi/python/issues{/number}","pulls_url":"https://api.github.com/repos/EnricoMi/python/pulls{/number}","milestones_url":"https://api.github.com/repos/EnricoMi/python/milestones{/number}","notifications_url":"https://api.github.com/repos/EnricoMi/python/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/EnricoMi/python/labels{/name}","releases_url":"https://api.github.com/repos/EnricoMi/python/releases{/id}","deployments_url":"https://api.github.com/repos/EnricoMi/python/deployments","created_at":"2019-11-26T18:58:06Z","updated_at":"2021-12-04T16:06:43Z","pushed_at":"2023-05-30T07:57:26Z","git_url":"git://github.com/EnricoMi/python.git","ssh_url":"git@github.com:EnricoMi/python.git","clone_url":"https://github.com/EnricoMi/python.git","svn_url":"https://github.com/EnricoMi/python","homepage":null,"size":276,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":true,"has_projects":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"has_discussions":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":12,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit","node_id":"MDc6TGljZW5zZTEz"},"allow_forking":true,"is_template":false,"web_commit_signoff_required":false,"topics":[],"visibility":"public","forks":0,"open_issues":12,"watchers":0,"default_branch":"master","permissions":{"admin":false,"maintain":false,"push":false,"triage":false,"pull":false}}]} +