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