/ tests / Authentication.py
Authentication.py
  1  ############################ Copyrights and license ############################
  2  #                                                                              #
  3  # Copyright 2012 Vincent Jacques <vincent@vincent-jacques.net>                 #
  4  # Copyright 2012 Zearin <zearin@gonk.net>                                      #
  5  # Copyright 2013 Vincent Jacques <vincent@vincent-jacques.net>                 #
  6  # Copyright 2014 Vincent Jacques <vincent@vincent-jacques.net>                 #
  7  # Copyright 2016 Peter Buckley <dx-pbuckley@users.noreply.github.com>          #
  8  # Copyright 2018 Steve Kowalik <steven@wedontsleep.org>                        #
  9  # Copyright 2018 sfdye <tsfdye@gmail.com>                                      #
 10  #                                                                              #
 11  # This file is part of PyGithub.                                               #
 12  # http://pygithub.readthedocs.io/                                              #
 13  #                                                                              #
 14  # PyGithub is free software: you can redistribute it and/or modify it under    #
 15  # the terms of the GNU Lesser General Public License as published by the Free  #
 16  # Software Foundation, either version 3 of the License, or (at your option)    #
 17  # any later version.                                                           #
 18  #                                                                              #
 19  # PyGithub is distributed in the hope that it will be useful, but WITHOUT ANY  #
 20  # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS    #
 21  # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more #
 22  # details.                                                                     #
 23  #                                                                              #
 24  # You should have received a copy of the GNU Lesser General Public License     #
 25  # along with PyGithub. If not, see <http://www.gnu.org/licenses/>.             #
 26  #                                                                              #
 27  ################################################################################
 28  
 29  import os
 30  from datetime import datetime, timezone
 31  from tempfile import NamedTemporaryFile
 32  from unittest import mock
 33  
 34  import jwt
 35  
 36  import github
 37  
 38  from . import Framework
 39  from .GithubIntegration import APP_ID, PRIVATE_KEY, PUBLIC_KEY
 40  
 41  
 42  class Authentication(Framework.BasicTestCase):
 43      def testNoAuthentication(self):
 44          g = github.Github()
 45          self.assertEqual(g.get_user("jacquev6").name, "Vincent Jacques")
 46  
 47      def testBasicAuthentication(self):
 48          with self.assertWarns(DeprecationWarning) as warning:
 49              g = github.Github(self.login.login, self.login.password)
 50          self.assertEqual(g.get_user("jacquev6").name, "Vincent Jacques")
 51          self.assertWarning(
 52              warning,
 53              "Arguments login_or_token and password are deprecated, please use auth=github.Auth.Login(...) instead",
 54          )
 55  
 56      def testOAuthAuthentication(self):
 57          with self.assertWarns(DeprecationWarning) as warning:
 58              g = github.Github(self.oauth_token.token)
 59          self.assertEqual(g.get_user("jacquev6").name, "Vincent Jacques")
 60          self.assertWarning(
 61              warning,
 62              "Argument login_or_token is deprecated, please use auth=github.Auth.Token(...) instead",
 63          )
 64  
 65      def testJWTAuthentication(self):
 66          with self.assertWarns(DeprecationWarning) as warning:
 67              g = github.Github(jwt=self.jwt.token)
 68          self.assertEqual(g.get_user("jacquev6").name, "Vincent Jacques")
 69          self.assertWarning(
 70              warning,
 71              "Argument jwt is deprecated, please use auth=github.Auth.AppAuth(...) or "
 72              "auth=github.Auth.AppAuthToken(...) instead",
 73          )
 74  
 75      def testAppAuthentication(self):
 76          with self.assertWarns(DeprecationWarning) as warning:
 77              app_auth = github.AppAuthentication(
 78                  app_id=self.app_auth.app_id,
 79                  private_key=self.app_auth.private_key,
 80                  installation_id=29782936,
 81              )
 82              g = github.Github(app_auth=app_auth)
 83          self.assertEqual(g.get_user("ammarmallik").name, "Ammar Akbar")
 84          self.assertWarnings(
 85              warning,
 86              "Call to deprecated class AppAuthentication. (Use github.Auth.AppInstallationAuth instead)",
 87              "Argument app_auth is deprecated, please use auth=github.Auth.AppInstallationAuth(...) instead",
 88          )
 89  
 90      def testLoginAuthentication(self):
 91          # test data copied from testBasicAuthentication to test parity
 92          g = github.Github(auth=self.login)
 93          self.assertEqual(g.get_user("jacquev6").name, "Vincent Jacques")
 94  
 95      def testTokenAuthentication(self):
 96          # test data copied from testOAuthAuthentication to test parity
 97          g = github.Github(auth=self.oauth_token)
 98          self.assertEqual(g.get_user("jacquev6").name, "Vincent Jacques")
 99  
100      def testAppAuthTokenAuthentication(self):
101          # test data copied from testJWTAuthentication to test parity
102          g = github.Github(auth=self.jwt)
103          self.assertEqual(g.get_user("jacquev6").name, "Vincent Jacques")
104  
105      def testAppAuthAuthentication(self):
106          # test data copied from testAppAuthentication to test parity
107          g = github.Github(auth=self.app_auth.get_installation_auth(29782936))
108          self.assertEqual(g.get_user("ammarmallik").name, "Ammar Akbar")
109  
110      def assert_requester_args(self, g, expected_requester):
111          expected_args = expected_requester.kwargs
112          expected_args.pop("auth")
113  
114          auth_args = g._Github__requester.auth.requester.kwargs
115          auth_args.pop("auth")
116  
117          self.assertEqual(expected_args, auth_args)
118  
119          auth_integration_args = (
120              g._Github__requester.auth._AppInstallationAuth__integration._GithubIntegration__requester.kwargs
121          )
122          auth_integration_args.pop("auth")
123  
124          self.assertEqual(expected_args, auth_integration_args)
125  
126      def testAppAuthAuthenticationWithGithubRequesterArgs(self):
127          # test that Requester arguments given to github.Github are passed to auth and auth.__integration
128          g = github.Github(
129              auth=self.app_auth.get_installation_auth(29782936),
130              base_url="https://base.net/",
131              timeout=60,
132              user_agent="agent",
133              per_page=100,
134              verify="cert",
135              retry=999,
136              pool_size=10,
137              seconds_between_requests=100,
138              seconds_between_writes=1000,
139          )
140  
141          self.assert_requester_args(g, g._Github__requester)
142  
143      def testAppAuthAuthenticationWithGithubIntegrationRequesterArgs(self):
144          # test that Requester arguments given to github.GithubIntegration are passed to auth and auth.__integration
145          gi = github.GithubIntegration(
146              auth=self.app_auth,
147              base_url="https://base.net/",
148              timeout=60,
149              user_agent="agent",
150              per_page=100,
151              verify="cert",
152              retry=999,
153              pool_size=10,
154              seconds_between_requests=100,
155              seconds_between_writes=1000,
156          )
157  
158          self.assert_requester_args(gi.get_github_for_installation(29782936), gi._GithubIntegration__requester)
159  
160      def testAppInstallationAuthAuthentication(self):
161          # test data copied from testAppAuthentication to test parity
162          installation_auth = github.Auth.AppInstallationAuth(self.app_auth, 29782936)
163          g = github.Github(auth=installation_auth)
164  
165          # test token expiry
166          # token expires 2024-11-25 01:00:02
167          token = installation_auth.token
168          self.assertFalse(installation_auth._is_expired)
169          self.assertEqual(
170              installation_auth._AppInstallationAuth__installation_authorization.expires_at,
171              datetime(2024, 11, 25, 1, 0, 2, tzinfo=timezone.utc),
172          )
173  
174          # forward the clock so token expires
175          with mock.patch("github.Auth.datetime") as dt:
176              # just before expiry
177              dt.now = mock.Mock(return_value=datetime(2024, 11, 25, 0, 59, 3, tzinfo=timezone.utc))
178              self.assertFalse(installation_auth._is_expired)
179  
180              # just after expiry
181              dt.now = mock.Mock(return_value=datetime(2024, 11, 25, 1, 0, 3, tzinfo=timezone.utc))
182              self.assertTrue(installation_auth._is_expired)
183  
184              # expect refreshing the token
185              refreshed_token = installation_auth.token
186              self.assertNotEqual(refreshed_token, token)
187              self.assertFalse(installation_auth._is_expired)
188              self.assertEqual(
189                  installation_auth._AppInstallationAuth__installation_authorization.expires_at,
190                  datetime(2025, 11, 25, 1, 0, 2, tzinfo=timezone.utc),
191              )
192  
193          # use the token
194          self.assertEqual(g.get_user("ammarmallik").name, "Ammar Akbar")
195          self.assertEqual(g.get_repo("PyGithub/PyGithub").full_name, "PyGithub/PyGithub")
196  
197      def testAppInstallationAuthAuthenticationRequesterArgs(self):
198          installation_auth = github.Auth.AppInstallationAuth(self.app_auth, 29782936)
199          github.Github(
200              auth=installation_auth,
201          )
202  
203      def testAppUserAuthentication(self):
204          client_id = "removed client id"
205          client_secret = "removed client secret"
206          refresh_token = "removed refresh token"
207  
208          g = github.Github()
209          app = g.get_oauth_application(client_id, client_secret)
210          with mock.patch("github.AccessToken.datetime") as dt:
211              dt.now = mock.Mock(return_value=datetime(2023, 6, 7, 12, 0, 0, 123, tzinfo=timezone.utc))
212              token = app.refresh_access_token(refresh_token)
213          self.assertEqual(token.token, "fresh access token")
214          self.assertEqual(token.type, "bearer")
215          self.assertEqual(token.scope, "")
216          self.assertEqual(token.expires_in, 28800)
217          self.assertEqual(
218              token.expires_at,
219              datetime(2023, 6, 7, 20, 0, 0, 123, tzinfo=timezone.utc),
220          )
221          self.assertEqual(token.refresh_token, "fresh refresh token")
222          self.assertEqual(token.refresh_expires_in, 15811200)
223          self.assertEqual(
224              token.refresh_expires_at,
225              datetime(2023, 12, 7, 12, 0, 0, 123, tzinfo=timezone.utc),
226          )
227  
228          auth = app.get_app_user_auth(token)
229          with mock.patch("github.Auth.datetime") as dt:
230              dt.now = mock.Mock(return_value=datetime(2023, 6, 7, 20, 0, 0, 123, tzinfo=timezone.utc))
231              self.assertEqual(auth._is_expired, False)
232              self.assertEqual(auth.token, "fresh access token")
233          self.assertEqual(auth.token_type, "bearer")
234          self.assertEqual(auth.refresh_token, "fresh refresh token")
235  
236          # expire auth token
237          with mock.patch("github.Auth.datetime") as dt:
238              dt.now = mock.Mock(return_value=datetime(2023, 6, 7, 20, 0, 1, 123, tzinfo=timezone.utc))
239              self.assertEqual(auth._is_expired, True)
240              self.assertEqual(auth.token, "another access token")
241              self.assertEqual(auth._is_expired, False)
242          self.assertEqual(auth.token_type, "bearer")
243          self.assertEqual(auth.refresh_token, "another refresh token")
244  
245          g = github.Github(auth=auth)
246          user = g.get_user()
247          self.assertEqual(user.login, "EnricoMi")
248  
249      def testNetrcAuth(self):
250          with NamedTemporaryFile("wt", delete=False) as tmp:
251              # write temporary netrc file
252              tmp.write("machine api.github.com\n")
253              tmp.write("login github-user\n")
254              tmp.write("password github-password\n")
255              tmp.close()
256  
257              auth = github.Auth.NetrcAuth()
258              with mock.patch.dict(os.environ, {"NETRC": tmp.name}):
259                  github.Github(auth=auth)
260  
261              self.assertEqual(auth.login, "github-user")
262              self.assertEqual(auth.password, "github-password")
263              self.assertEqual(auth.token, "Z2l0aHViLXVzZXI6Z2l0aHViLXBhc3N3b3Jk")
264              self.assertEqual(auth.token_type, "Basic")
265  
266      def testNetrcAuthFails(self):
267          # provide an empty netrc file to make sure this test does not find one
268          with NamedTemporaryFile("wt", delete=False) as tmp:
269              tmp.close()
270              auth = github.Auth.NetrcAuth()
271              with mock.patch.dict(os.environ, {"NETRC": tmp.name}):
272                  with self.assertRaises(RuntimeError) as exc:
273                      github.Github(auth=auth)
274                  self.assertEqual(exc.exception.args, ("Could not get credentials from netrc for host api.github.com",))
275  
276      def testCreateJWT(self):
277          auth = github.Auth.AppAuth(APP_ID, PRIVATE_KEY)
278  
279          with mock.patch("github.Auth.time") as t:
280              t.time = mock.Mock(return_value=1550055331.7435968)
281              token = auth.create_jwt()
282  
283          payload = jwt.decode(
284              token,
285              key=PUBLIC_KEY,
286              algorithms=["RS256"],
287              options={"verify_exp": False},
288          )
289          self.assertDictEqual(payload, {"iat": 1550055271, "exp": 1550055631, "iss": APP_ID})
290  
291      def testCreateJWTWithExpiration(self):
292          auth = github.Auth.AppAuth(APP_ID, PRIVATE_KEY, jwt_expiry=120, jwt_issued_at=-30)
293  
294          with mock.patch("github.Auth.time") as t:
295              t.time = mock.Mock(return_value=1550055331.7435968)
296              token = auth.create_jwt(60)
297  
298          payload = jwt.decode(
299              token,
300              key=PUBLIC_KEY,
301              algorithms=["RS256"],
302              options={"verify_exp": False},
303          )
304          self.assertDictEqual(payload, {"iat": 1550055301, "exp": 1550055391, "iss": APP_ID})
305  
306      def testUserAgent(self):
307          g = github.Github(user_agent="PyGithubTester")
308          self.assertEqual(g.get_user("jacquev6").name, "Vincent Jacques")
309  
310      def testAuthorizationHeaderWithLogin(self):
311          # See special case in Framework.fixAuthorizationHeader
312          g = github.Github(auth=github.Auth.Login("fake_login", "fake_password"))
313          with self.assertRaises(github.GithubException):
314              g.get_user().name
315  
316      def testAuthorizationHeaderWithToken(self):
317          # See special case in Framework.fixAuthorizationHeader
318          g = github.Github(auth=github.Auth.Token("ZmFrZV9sb2dpbjpmYWtlX3Bhc3N3b3Jk"))
319          with self.assertRaises(github.GithubException):
320              g.get_user().name