ApplicationOAuth.py
1 ############################ Copyrights and license ########################### 2 # # 3 # Copyright 2019 Rigas Papathanasopoulos <rigaspapas@gmail.com> # 4 # Copyright 2023 Enrico Minack <github@enrico.minack.dev> # 5 # # 6 # This file is part of PyGithub. # 7 # http://pygithub.readthedocs.io/ # 8 # # 9 # PyGithub is free software: you can redistribute it and/or modify it under # 10 # the terms of the GNU Lesser General Public License as published by the Free # 11 # Software Foundation, either version 3 of the License, or (at your option) # 12 # any later version. # 13 # # 14 # PyGithub is distributed in the hope that it will be useful, but WITHOUT ANY # 15 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # 16 # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more# 17 # details. # 18 # # 19 # You should have received a copy of the GNU Lesser General Public License # 20 # along with PyGithub. If not, see <http://www.gnu.org/licenses/>. # 21 # # 22 ############################################################################### 23 from __future__ import annotations 24 25 import urllib.parse 26 from typing import TYPE_CHECKING, Any 27 28 import github.AccessToken 29 import github.Auth 30 from github.GithubException import BadCredentialsException, GithubException 31 from github.GithubObject import Attribute, NonCompletableGithubObject, NotSet 32 from github.Requester import Requester 33 34 if TYPE_CHECKING: 35 from github.AccessToken import AccessToken 36 from github.Auth import AppUserAuth 37 38 39 class ApplicationOAuth(NonCompletableGithubObject): 40 """ 41 This class is used for identifying and authorizing users for Github Apps. 42 The reference can be found at https://docs.github.com/en/developers/apps/building-github-apps/identifying-and-authorizing-users-for-github-apps 43 """ 44 45 def _initAttributes(self) -> None: 46 self._client_id: Attribute[str] = NotSet 47 self._client_secret: Attribute[str] = NotSet 48 49 def __init__( 50 self, 51 requester: Requester, 52 headers: dict[str, Any], 53 attributes: Any, 54 completed: bool, 55 ) -> None: 56 # this object requires a request without authentication 57 requester = requester.withAuth(auth=None) 58 super().__init__(requester, headers, attributes, completed) 59 60 def __repr__(self) -> str: 61 return self.get__repr__({"client_id": self._client_id.value}) 62 63 @property 64 def client_id(self) -> str: 65 return self._client_id.value 66 67 @property 68 def client_secret(self) -> str: 69 return self._client_secret.value 70 71 def _useAttributes(self, attributes: dict[str, Any]) -> None: 72 if "client_id" in attributes: # pragma no branch 73 self._client_id = self._makeStringAttribute(attributes["client_id"]) 74 if "client_secret" in attributes: # pragma no branch 75 self._client_secret = self._makeStringAttribute(attributes["client_secret"]) 76 77 def get_login_url( 78 self, 79 redirect_uri: str | None = None, 80 state: str | None = None, 81 login: str | None = None, 82 ) -> str: 83 """Return the URL you need to redirect a user to in order to authorize your App.""" 84 parameters = {"client_id": self.client_id} 85 if redirect_uri is not None: 86 assert isinstance(redirect_uri, str), redirect_uri 87 parameters["redirect_uri"] = redirect_uri 88 if state is not None: 89 assert isinstance(state, str), state 90 parameters["state"] = state 91 if login is not None: 92 assert isinstance(login, str), login 93 parameters["login"] = login 94 95 query = urllib.parse.urlencode(parameters) 96 97 base_url = "https://github.com/login/oauth/authorize" 98 return f"{base_url}?{query}" 99 100 def get_access_token(self, code: str, state: str | None = None) -> AccessToken: 101 """ 102 :calls: `POST /login/oauth/access_token <https://docs.github.com/en/developers/apps/identifying-and-authorizing-users-for-github-apps>`_ 103 """ 104 assert isinstance(code, str), code 105 post_parameters = { 106 "code": code, 107 "client_id": self.client_id, 108 "client_secret": self.client_secret, 109 } 110 111 if state is not None: 112 post_parameters["state"] = state 113 114 headers, data = self._checkError( 115 *self._requester.requestJsonAndCheck( 116 "POST", 117 "https://github.com/login/oauth/access_token", 118 headers={"Accept": "application/json"}, 119 input=post_parameters, 120 ) 121 ) 122 123 return github.AccessToken.AccessToken( 124 requester=self._requester, 125 headers=headers, 126 attributes=data, 127 completed=False, 128 ) 129 130 def get_app_user_auth(self, token: AccessToken) -> AppUserAuth: 131 return github.Auth.AppUserAuth( 132 client_id=self.client_id, 133 client_secret=self.client_secret, 134 token=token.token, 135 token_type=token.type, 136 expires_at=token.expires_at, 137 refresh_token=token.refresh_token, 138 refresh_expires_at=token.refresh_expires_at, 139 requester=self._requester, 140 ) 141 142 def refresh_access_token(self, refresh_token: str) -> AccessToken: 143 """ 144 :calls: `POST /login/oauth/access_token <https://docs.github.com/en/developers/apps/identifying-and-authorizing-users-for-github-apps>`_ 145 :param refresh_token: string 146 """ 147 assert isinstance(refresh_token, str) 148 post_parameters = { 149 "client_id": self.client_id, 150 "client_secret": self.client_secret, 151 "grant_type": "refresh_token", 152 "refresh_token": refresh_token, 153 } 154 155 headers, data = self._checkError( 156 *self._requester.requestJsonAndCheck( 157 "POST", 158 "https://github.com/login/oauth/access_token", 159 headers={"Accept": "application/json"}, 160 input=post_parameters, 161 ) 162 ) 163 164 return github.AccessToken.AccessToken( 165 requester=self._requester, 166 headers=headers, 167 attributes=data, 168 completed=False, 169 ) 170 171 @staticmethod 172 def _checkError(headers: dict[str, Any], data: Any) -> tuple[dict[str, Any], Any]: 173 if isinstance(data, dict) and "error" in data: 174 if data["error"] == "bad_verification_code": 175 raise BadCredentialsException(200, data, headers) 176 raise GithubException(200, data, headers) 177 178 return headers, data