Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
credentials.py637 linesDownload Raw Back to oauth2
1# Copyright 2016 Google LLC2#3# Licensed under the Apache License, Version 2.0 (the "License");4# you may not use this file except in compliance with the License.5# You may obtain a copy of the License at6#7#      http://www.apache.org/licenses/LICENSE-2.08#9# Unless required by applicable law or agreed to in writing, software10# distributed under the License is distributed on an "AS IS" BASIS,11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.12# See the License for the specific language governing permissions and13# limitations under the License.14 15"""OAuth 2.0 Credentials.16 17This module provides credentials based on OAuth 2.0 access and refresh tokens.18These credentials usually access resources on behalf of a user (resource19owner).20 21Specifically, this is intended to use access tokens acquired using the22`Authorization Code grant`_ and can refresh those tokens using a23optional `refresh token`_.24 25Obtaining the initial access and refresh token is outside of the scope of this26module. Consult `rfc6749 section 4.1`_ for complete details on the27Authorization Code grant flow.28 29.. _Authorization Code grant: https://tools.ietf.org/html/rfc6749#section-1.3.130.. _refresh token: https://tools.ietf.org/html/rfc6749#section-631.. _rfc6749 section 4.1: https://tools.ietf.org/html/rfc6749#section-4.132"""33 34from datetime import datetime35import io36import json37import logging38import warnings39 40from google.auth import _cloud_sdk41from google.auth import _helpers42from google.auth import credentials43from google.auth import exceptions44from google.auth import metrics45from google.oauth2 import reauth46 47_LOGGER = logging.getLogger(__name__)48 49 50# The Google OAuth 2.0 token endpoint. Used for authorized user credentials.51_GOOGLE_OAUTH2_TOKEN_ENDPOINT = "https://oauth2.googleapis.com/token"52 53 54class Credentials(credentials.ReadOnlyScoped, credentials.CredentialsWithQuotaProject):55    """Credentials using OAuth 2.0 access and refresh tokens.56 57    The credentials are considered immutable except the tokens and the token58    expiry, which are updated after refresh. If you want to modify the quota59    project, use :meth:`with_quota_project` or ::60 61        credentials = credentials.with_quota_project('myproject-123')62 63    Reauth is disabled by default. To enable reauth, set the64    `enable_reauth_refresh` parameter to True in the constructor. Note that65    reauth feature is intended for gcloud to use only.66    If reauth is enabled, `pyu2f` dependency has to be installed in order to use security67    key reauth feature. Dependency can be installed via `pip install pyu2f` or `pip install68    google-auth[reauth]`.69    """70 71    def __init__(72        self,73        token,74        refresh_token=None,75        id_token=None,76        token_uri=None,77        client_id=None,78        client_secret=None,79        scopes=None,80        default_scopes=None,81        quota_project_id=None,82        expiry=None,83        rapt_token=None,84        refresh_handler=None,85        enable_reauth_refresh=False,86        granted_scopes=None,87        trust_boundary=None,88        universe_domain=credentials.DEFAULT_UNIVERSE_DOMAIN,89        account=None,90    ):91        """92        Args:93            token (Optional(str)): The OAuth 2.0 access token. Can be None94                if refresh information is provided.95            refresh_token (str): The OAuth 2.0 refresh token. If specified,96                credentials can be refreshed.97            id_token (str): The Open ID Connect ID Token.98            token_uri (str): The OAuth 2.0 authorization server's token99                endpoint URI. Must be specified for refresh, can be left as100                None if the token can not be refreshed.101            client_id (str): The OAuth 2.0 client ID. Must be specified for102                refresh, can be left as None if the token can not be refreshed.103            client_secret(str): The OAuth 2.0 client secret. Must be specified104                for refresh, can be left as None if the token can not be105                refreshed.106            scopes (Sequence[str]): The scopes used to obtain authorization.107                This parameter is used by :meth:`has_scopes`. OAuth 2.0108                credentials can not request additional scopes after109                authorization. The scopes must be derivable from the refresh110                token if refresh information is provided (e.g. The refresh111                token scopes are a superset of this or contain a wild card112                scope like 'https://www.googleapis.com/auth/any-api').113            default_scopes (Sequence[str]): Default scopes passed by a114                Google client library. Use 'scopes' for user-defined scopes.115            quota_project_id (Optional[str]): The project ID used for quota and billing.116                This project may be different from the project used to117                create the credentials.118            rapt_token (Optional[str]): The reauth Proof Token.119            refresh_handler (Optional[Callable[[google.auth.transport.Request, Sequence[str]], [str, datetime]]]):120                A callable which takes in the HTTP request callable and the list of121                OAuth scopes and when called returns an access token string for the122                requested scopes and its expiry datetime. This is useful when no123                refresh tokens are provided and tokens are obtained by calling124                some external process on demand. It is particularly useful for125                retrieving downscoped tokens from a token broker.126            enable_reauth_refresh (Optional[bool]): Whether reauth refresh flow127                should be used. This flag is for gcloud to use only.128            granted_scopes (Optional[Sequence[str]]): The scopes that were consented/granted by the user.129                This could be different from the requested scopes and it could be empty if granted130                and requested scopes were same.131            trust_boundary (str): String representation of trust boundary meta.132            universe_domain (Optional[str]): The universe domain. The default133                universe domain is googleapis.com.134            account (Optional[str]): The account associated with the credential.135        """136        super(Credentials, self).__init__()137        self.token = token138        self.expiry = expiry139        self._refresh_token = refresh_token140        self._id_token = id_token141        self._scopes = scopes142        self._default_scopes = default_scopes143        self._granted_scopes = granted_scopes144        self._token_uri = token_uri145        self._client_id = client_id146        self._client_secret = client_secret147        self._quota_project_id = quota_project_id148        self._rapt_token = rapt_token149        self.refresh_handler = refresh_handler150        self._enable_reauth_refresh = enable_reauth_refresh151        self._trust_boundary = trust_boundary152        self._universe_domain = universe_domain or credentials.DEFAULT_UNIVERSE_DOMAIN153        self._account = account or ""154 155    def __getstate__(self):156        """A __getstate__ method must exist for the __setstate__ to be called157        This is identical to the default implementation.158        See https://docs.python.org/3.7/library/pickle.html#object.__setstate__159        """160        state_dict = self.__dict__.copy()161        # Remove _refresh_handler function as there are limitations pickling and162        # unpickling certain callables (lambda, functools.partial instances)163        # because they need to be importable.164        # Instead, the refresh_handler setter should be used to repopulate this.165        if "_refresh_handler" in state_dict:166            del state_dict["_refresh_handler"]167 168        if "_refresh_worker" in state_dict:169            del state_dict["_refresh_worker"]170        return state_dict171 172    def __setstate__(self, d):173        """Credentials pickled with older versions of the class do not have174        all the attributes."""175        self.token = d.get("token")176        self.expiry = d.get("expiry")177        self._refresh_token = d.get("_refresh_token")178        self._id_token = d.get("_id_token")179        self._scopes = d.get("_scopes")180        self._default_scopes = d.get("_default_scopes")181        self._granted_scopes = d.get("_granted_scopes")182        self._token_uri = d.get("_token_uri")183        self._client_id = d.get("_client_id")184        self._client_secret = d.get("_client_secret")185        self._quota_project_id = d.get("_quota_project_id")186        self._rapt_token = d.get("_rapt_token")187        self._enable_reauth_refresh = d.get("_enable_reauth_refresh")188        self._trust_boundary = d.get("_trust_boundary")189        self._universe_domain = (190            d.get("_universe_domain") or credentials.DEFAULT_UNIVERSE_DOMAIN191        )192        # The refresh_handler setter should be used to repopulate this.193        self._refresh_handler = None194        self._refresh_worker = None195        self._use_non_blocking_refresh = d.get("_use_non_blocking_refresh", False)196        self._account = d.get("_account", "")197 198    @property199    def refresh_token(self):200        """Optional[str]: The OAuth 2.0 refresh token."""201        return self._refresh_token202 203    @property204    def scopes(self):205        """Optional[str]: The OAuth 2.0 permission scopes."""206        return self._scopes207 208    @property209    def granted_scopes(self):210        """Optional[Sequence[str]]: The OAuth 2.0 permission scopes that were granted by the user."""211        return self._granted_scopes212 213    @property214    def token_uri(self):215        """Optional[str]: The OAuth 2.0 authorization server's token endpoint216        URI."""217        return self._token_uri218 219    @property220    def id_token(self):221        """Optional[str]: The Open ID Connect ID Token.222 223        Depending on the authorization server and the scopes requested, this224        may be populated when credentials are obtained and updated when225        :meth:`refresh` is called. This token is a JWT. It can be verified226        and decoded using :func:`google.oauth2.id_token.verify_oauth2_token`.227        """228        return self._id_token229 230    @property231    def client_id(self):232        """Optional[str]: The OAuth 2.0 client ID."""233        return self._client_id234 235    @property236    def client_secret(self):237        """Optional[str]: The OAuth 2.0 client secret."""238        return self._client_secret239 240    @property241    def requires_scopes(self):242        """False: OAuth 2.0 credentials have their scopes set when243        the initial token is requested and can not be changed."""244        return False245 246    @property247    def rapt_token(self):248        """Optional[str]: The reauth Proof Token."""249        return self._rapt_token250 251    @property252    def refresh_handler(self):253        """Returns the refresh handler if available.254 255        Returns:256           Optional[Callable[[google.auth.transport.Request, Sequence[str]], [str, datetime]]]:257               The current refresh handler.258        """259        return self._refresh_handler260 261    @refresh_handler.setter262    def refresh_handler(self, value):263        """Updates the current refresh handler.264 265        Args:266            value (Optional[Callable[[google.auth.transport.Request, Sequence[str]], [str, datetime]]]):267                The updated value of the refresh handler.268 269        Raises:270            TypeError: If the value is not a callable or None.271        """272        if not callable(value) and value is not None:273            raise TypeError("The provided refresh_handler is not a callable or None.")274        self._refresh_handler = value275 276    @property277    def account(self):278        """str: The user account associated with the credential. If the account is unknown an empty string is returned."""279        return self._account280 281    @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject)282    def with_quota_project(self, quota_project_id):283 284        return self.__class__(285            self.token,286            refresh_token=self.refresh_token,287            id_token=self.id_token,288            token_uri=self.token_uri,289            client_id=self.client_id,290            client_secret=self.client_secret,291            scopes=self.scopes,292            default_scopes=self.default_scopes,293            granted_scopes=self.granted_scopes,294            quota_project_id=quota_project_id,295            rapt_token=self.rapt_token,296            enable_reauth_refresh=self._enable_reauth_refresh,297            trust_boundary=self._trust_boundary,298            universe_domain=self._universe_domain,299            account=self._account,300        )301 302    @_helpers.copy_docstring(credentials.CredentialsWithTokenUri)303    def with_token_uri(self, token_uri):304 305        return self.__class__(306            self.token,307            refresh_token=self.refresh_token,308            id_token=self.id_token,309            token_uri=token_uri,310            client_id=self.client_id,311            client_secret=self.client_secret,312            scopes=self.scopes,313            default_scopes=self.default_scopes,314            granted_scopes=self.granted_scopes,315            quota_project_id=self.quota_project_id,316            rapt_token=self.rapt_token,317            enable_reauth_refresh=self._enable_reauth_refresh,318            trust_boundary=self._trust_boundary,319            universe_domain=self._universe_domain,320            account=self._account,321        )322 323    def with_account(self, account):324        """Returns a copy of these credentials with a modified account.325 326        Args:327            account (str): The account to set328 329        Returns:330            google.oauth2.credentials.Credentials: A new credentials instance.331        """332 333        return self.__class__(334            self.token,335            refresh_token=self.refresh_token,336            id_token=self.id_token,337            token_uri=self._token_uri,338            client_id=self.client_id,339            client_secret=self.client_secret,340            scopes=self.scopes,341            default_scopes=self.default_scopes,342            granted_scopes=self.granted_scopes,343            quota_project_id=self.quota_project_id,344            rapt_token=self.rapt_token,345            enable_reauth_refresh=self._enable_reauth_refresh,346            trust_boundary=self._trust_boundary,347            universe_domain=self._universe_domain,348            account=account,349        )350 351    @_helpers.copy_docstring(credentials.CredentialsWithUniverseDomain)352    def with_universe_domain(self, universe_domain):353 354        return self.__class__(355            self.token,356            refresh_token=self.refresh_token,357            id_token=self.id_token,358            token_uri=self._token_uri,359            client_id=self.client_id,360            client_secret=self.client_secret,361            scopes=self.scopes,362            default_scopes=self.default_scopes,363            granted_scopes=self.granted_scopes,364            quota_project_id=self.quota_project_id,365            rapt_token=self.rapt_token,366            enable_reauth_refresh=self._enable_reauth_refresh,367            trust_boundary=self._trust_boundary,368            universe_domain=universe_domain,369            account=self._account,370        )371 372    def _metric_header_for_usage(self):373        return metrics.CRED_TYPE_USER374 375    @_helpers.copy_docstring(credentials.Credentials)376    def refresh(self, request):377        if self._universe_domain != credentials.DEFAULT_UNIVERSE_DOMAIN:378            raise exceptions.RefreshError(379                "User credential refresh is only supported in the default "380                "googleapis.com universe domain, but the current universe "381                "domain is {}. If you created the credential with an access "382                "token, it's likely that the provided token is expired now, "383                "please update your code with a valid token.".format(384                    self._universe_domain385                )386            )387 388        scopes = self._scopes if self._scopes is not None else self._default_scopes389        # Use refresh handler if available and no refresh token is390        # available. This is useful in general when tokens are obtained by calling391        # some external process on demand. It is particularly useful for retrieving392        # downscoped tokens from a token broker.393        if self._refresh_token is None and self.refresh_handler:394            token, expiry = self.refresh_handler(request, scopes=scopes)395            # Validate returned data.396            if not isinstance(token, str):397                raise exceptions.RefreshError(398                    "The refresh_handler returned token is not a string."399                )400            if not isinstance(expiry, datetime):401                raise exceptions.RefreshError(402                    "The refresh_handler returned expiry is not a datetime object."403                )404            if _helpers.utcnow() >= expiry - _helpers.REFRESH_THRESHOLD:405                raise exceptions.RefreshError(406                    "The credentials returned by the refresh_handler are "407                    "already expired."408                )409            self.token = token410            self.expiry = expiry411            return412 413        if (414            self._refresh_token is None415            or self._token_uri is None416            or self._client_id is None417            or self._client_secret is None418        ):419            raise exceptions.RefreshError(420                "The credentials do not contain the necessary fields need to "421                "refresh the access token. You must specify refresh_token, "422                "token_uri, client_id, and client_secret."423            )424 425        (426            access_token,427            refresh_token,428            expiry,429            grant_response,430            rapt_token,431        ) = reauth.refresh_grant(432            request,433            self._token_uri,434            self._refresh_token,435            self._client_id,436            self._client_secret,437            scopes=scopes,438            rapt_token=self._rapt_token,439            enable_reauth_refresh=self._enable_reauth_refresh,440        )441 442        self.token = access_token443        self.expiry = expiry444        self._refresh_token = refresh_token445        self._id_token = grant_response.get("id_token")446        self._rapt_token = rapt_token447 448        if scopes and "scope" in grant_response:449            requested_scopes = frozenset(scopes)450            self._granted_scopes = grant_response["scope"].split()451            granted_scopes = frozenset(self._granted_scopes)452            scopes_requested_but_not_granted = requested_scopes - granted_scopes453            if scopes_requested_but_not_granted:454                # User might be presented with unbundled scopes at the time of455                # consent. So it is a valid scenario to not have all the requested456                # scopes as part of granted scopes but log a warning in case the457                # developer wants to debug the scenario.458                _LOGGER.warning(459                    "Not all requested scopes were granted by the "460                    "authorization server, missing scopes {}.".format(461                        ", ".join(scopes_requested_but_not_granted)462                    )463                )464 465    @classmethod466    def from_authorized_user_info(cls, info, scopes=None):467        """Creates a Credentials instance from parsed authorized user info.468 469        Args:470            info (Mapping[str, str]): The authorized user info in Google471                format.472            scopes (Sequence[str]): Optional list of scopes to include in the473                credentials.474 475        Returns:476            google.oauth2.credentials.Credentials: The constructed477                credentials.478 479        Raises:480            ValueError: If the info is not in the expected format.481        """482        keys_needed = set(("refresh_token", "client_id", "client_secret"))483        missing = keys_needed.difference(info.keys())484 485        if missing:486            raise ValueError(487                "Authorized user info was not in the expected format, missing "488                "fields {}.".format(", ".join(missing))489            )490 491        # access token expiry (datetime obj); auto-expire if not saved492        expiry = info.get("expiry")493        if expiry:494            expiry = datetime.strptime(495                expiry.rstrip("Z").split(".")[0], "%Y-%m-%dT%H:%M:%S"496            )497        else:498            expiry = _helpers.utcnow() - _helpers.REFRESH_THRESHOLD499 500        # process scopes, which needs to be a seq501        if scopes is None and "scopes" in info:502            scopes = info.get("scopes")503            if isinstance(scopes, str):504                scopes = scopes.split(" ")505 506        return cls(507            token=info.get("token"),508            refresh_token=info.get("refresh_token"),509            token_uri=_GOOGLE_OAUTH2_TOKEN_ENDPOINT,  # always overrides510            scopes=scopes,511            client_id=info.get("client_id"),512            client_secret=info.get("client_secret"),513            quota_project_id=info.get("quota_project_id"),  # may not exist514            expiry=expiry,515            rapt_token=info.get("rapt_token"),  # may not exist516            trust_boundary=info.get("trust_boundary"),  # may not exist517            universe_domain=info.get("universe_domain"),  # may not exist518            account=info.get("account", ""),  # may not exist519        )520 521    @classmethod522    def from_authorized_user_file(cls, filename, scopes=None):523        """Creates a Credentials instance from an authorized user json file.524 525        Args:526            filename (str): The path to the authorized user json file.527            scopes (Sequence[str]): Optional list of scopes to include in the528                credentials.529 530        Returns:531            google.oauth2.credentials.Credentials: The constructed532                credentials.533 534        Raises:535            ValueError: If the file is not in the expected format.536        """537        with io.open(filename, "r", encoding="utf-8") as json_file:538            data = json.load(json_file)539            return cls.from_authorized_user_info(data, scopes)540 541    def to_json(self, strip=None):542        """Utility function that creates a JSON representation of a Credentials543        object.544 545        Args:546            strip (Sequence[str]): Optional list of members to exclude from the547                                   generated JSON.548 549        Returns:550            str: A JSON representation of this instance. When converted into551            a dictionary, it can be passed to from_authorized_user_info()552            to create a new credential instance.553        """554        prep = {555            "token": self.token,556            "refresh_token": self.refresh_token,557            "token_uri": self.token_uri,558            "client_id": self.client_id,559            "client_secret": self.client_secret,560            "scopes": self.scopes,561            "rapt_token": self.rapt_token,562            "universe_domain": self._universe_domain,563            "account": self._account,564        }565        if self.expiry:  # flatten expiry timestamp566            prep["expiry"] = self.expiry.isoformat() + "Z"567 568        # Remove empty entries (those which are None)569        prep = {k: v for k, v in prep.items() if v is not None}570 571        # Remove entries that explicitely need to be removed572        if strip is not None:573            prep = {k: v for k, v in prep.items() if k not in strip}574 575        return json.dumps(prep)576 577 578class UserAccessTokenCredentials(credentials.CredentialsWithQuotaProject):579    """Access token credentials for user account.580 581    Obtain the access token for a given user account or the current active582    user account with the ``gcloud auth print-access-token`` command.583 584    Args:585        account (Optional[str]): Account to get the access token for. If not586            specified, the current active account will be used.587        quota_project_id (Optional[str]): The project ID used for quota588            and billing.589    """590 591    def __init__(self, account=None, quota_project_id=None):592        warnings.warn(593            "UserAccessTokenCredentials is deprecated, please use "594            "google.oauth2.credentials.Credentials instead. To use "595            "that credential type, simply run "596            "`gcloud auth application-default login` and let the "597            "client libraries pick up the application default credentials."598        )599        super(UserAccessTokenCredentials, self).__init__()600        self._account = account601        self._quota_project_id = quota_project_id602 603    def with_account(self, account):604        """Create a new instance with the given account.605 606        Args:607            account (str): Account to get the access token for.608 609        Returns:610            google.oauth2.credentials.UserAccessTokenCredentials: The created611                credentials with the given account.612        """613        return self.__class__(account=account, quota_project_id=self._quota_project_id)614 615    @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject)616    def with_quota_project(self, quota_project_id):617        return self.__class__(account=self._account, quota_project_id=quota_project_id)618 619    def refresh(self, request):620        """Refreshes the access token.621 622        Args:623            request (google.auth.transport.Request): This argument is required624                by the base class interface but not used in this implementation,625                so just set it to `None`.626 627        Raises:628            google.auth.exceptions.UserAccessTokenError: If the access token629                refresh failed.630        """631        self.token = _cloud_sdk.get_auth_access_token(self._account)632 633    @_helpers.copy_docstring(credentials.Credentials)634    def before_request(self, request, method, url, headers):635        self.refresh(request)636        self.apply(headers)637 
codekingpro/portable-devtools · Team Ai