/ github / ApplicationOAuth.py
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