Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
base.py605 linesDownload Raw Back to clients
1# -*- coding: utf-8 -*-2"""3oauthlib.oauth2.rfc67494~~~~~~~~~~~~~~~~~~~~~~~5 6This module is an implementation of various logic needed7for consuming OAuth 2.0 RFC6749.8"""9import base6410import hashlib11import re12import secrets13import time14import warnings15 16from oauthlib.common import generate_token17from oauthlib.oauth2.rfc6749 import tokens18from oauthlib.oauth2.rfc6749.errors import (19    InsecureTransportError, TokenExpiredError,20)21from oauthlib.oauth2.rfc6749.parameters import (22    parse_token_response, prepare_token_request,23    prepare_token_revocation_request,24)25from oauthlib.oauth2.rfc6749.utils import is_secure_transport26 27AUTH_HEADER = 'auth_header'28URI_QUERY = 'query'29BODY = 'body'30 31FORM_ENC_HEADERS = {32    'Content-Type': 'application/x-www-form-urlencoded'33}34 35 36class Client:37    """Base OAuth2 client responsible for access token management.38 39    This class also acts as a generic interface providing methods common to all40    client types such as ``prepare_authorization_request`` and41    ``prepare_token_revocation_request``. The ``prepare_x_request`` methods are42    the recommended way of interacting with clients (as opposed to the abstract43    prepare uri/body/etc methods). They are recommended over the older set44    because they are easier to use (more consistent) and add a few additional45    security checks, such as HTTPS and state checking.46 47    Some of these methods require further implementation only provided by the48    specific purpose clients such as49    :py:class:`oauthlib.oauth2.MobileApplicationClient` and thus you should always50    seek to use the client class matching the OAuth workflow you need. For51    Python, this is usually :py:class:`oauthlib.oauth2.WebApplicationClient`.52 53    """54    refresh_token_key = 'refresh_token'55 56    def __init__(self, client_id,57                 default_token_placement=AUTH_HEADER,58                 token_type='Bearer',59                 access_token=None,60                 refresh_token=None,61                 mac_key=None,62                 mac_algorithm=None,63                 token=None,64                 scope=None,65                 state=None,66                 redirect_url=None,67                 state_generator=generate_token,68                 code_verifier=None,69                 code_challenge=None,70                 code_challenge_method=None,71                 **kwargs):72        """Initialize a client with commonly used attributes.73 74        :param client_id: Client identifier given by the OAuth provider upon75        registration.76 77        :param default_token_placement: Tokens can be supplied in the Authorization78        header (default), the URL query component (``query``) or the request79        body (``body``).80 81        :param token_type: OAuth 2 token type. Defaults to Bearer. Change this82        if you specify the ``access_token`` parameter and know it is of a83        different token type, such as a MAC, JWT or SAML token. Can84        also be supplied as ``token_type`` inside the ``token`` dict parameter.85 86        :param access_token: An access token (string) used to authenticate87        requests to protected resources. Can also be supplied inside the88        ``token`` dict parameter.89 90        :param refresh_token: A refresh token (string) used to refresh expired91        tokens. Can also be supplied inside the ``token`` dict parameter.92 93        :param mac_key: Encryption key used with MAC tokens.94 95        :param mac_algorithm:  Hashing algorithm for MAC tokens.96 97        :param token: A dict of token attributes such as ``access_token``,98        ``token_type`` and ``expires_at``.99 100        :param scope: A list of default scopes to request authorization for.101 102        :param state: A CSRF protection string used during authorization.103 104        :param redirect_url: The redirection endpoint on the client side to which105        the user returns after authorization.106 107        :param state_generator: A no argument state generation callable. Defaults108        to :py:meth:`oauthlib.common.generate_token`.109 110        :param code_verifier: PKCE parameter. A cryptographically random string that is used to correlate the111        authorization request to the token request.112 113        :param code_challenge: PKCE parameter. A challenge derived from the code verifier that is sent in the114        authorization request, to be verified against later.115 116        :param code_challenge_method: PKCE parameter. A method that was used to derive code challenge.117        Defaults to "plain" if not present in the request.118        """119 120        self.client_id = client_id121        self.default_token_placement = default_token_placement122        self.token_type = token_type123        self.access_token = access_token124        self.refresh_token = refresh_token125        self.mac_key = mac_key126        self.mac_algorithm = mac_algorithm127        self.token = token or {}128        self.scope = scope129        self.state_generator = state_generator130        self.state = state131        self.redirect_url = redirect_url132        self.code_verifier = code_verifier133        self.code_challenge = code_challenge134        self.code_challenge_method = code_challenge_method135        self.code = None136        self.expires_in = None137        self._expires_at = None138        self.populate_token_attributes(self.token)139 140    @property141    def token_types(self):142        """Supported token types and their respective methods143 144        Additional tokens can be supported by extending this dictionary.145 146        The Bearer token spec is stable and safe to use.147 148        The MAC token spec is not yet stable and support for MAC tokens149        is experimental and currently matching version 00 of the spec.150        """151        return {152            'Bearer': self._add_bearer_token,153            'MAC': self._add_mac_token154        }155 156    def prepare_request_uri(self, *args, **kwargs):157        """Abstract method used to create request URIs."""158        raise NotImplementedError("Must be implemented by inheriting classes.")159 160    def prepare_request_body(self, *args, **kwargs):161        """Abstract method used to create request bodies."""162        raise NotImplementedError("Must be implemented by inheriting classes.")163 164    def parse_request_uri_response(self, *args, **kwargs):165        """Abstract method used to parse redirection responses."""166        raise NotImplementedError("Must be implemented by inheriting classes.")167 168    def add_token(self, uri, http_method='GET', body=None, headers=None,169                  token_placement=None, **kwargs):170        """Add token to the request uri, body or authorization header.171 172        The access token type provides the client with the information173        required to successfully utilize the access token to make a protected174        resource request (along with type-specific attributes).  The client175        MUST NOT use an access token if it does not understand the token176        type.177 178        For example, the "bearer" token type defined in179        [`I-D.ietf-oauth-v2-bearer`_] is utilized by simply including the access180        token string in the request:181 182        .. code-block:: http183 184            GET /resource/1 HTTP/1.1185            Host: example.com186            Authorization: Bearer mF_9.B5f-4.1JqM187 188        while the "mac" token type defined in [`I-D.ietf-oauth-v2-http-mac`_] is189        utilized by issuing a MAC key together with the access token which is190        used to sign certain components of the HTTP requests:191 192        .. code-block:: http193 194            GET /resource/1 HTTP/1.1195            Host: example.com196            Authorization: MAC id="h480djs93hd8",197                                nonce="274312:dj83hs9s",198                                mac="kDZvddkndxvhGRXZhvuDjEWhGeE="199 200        .. _`I-D.ietf-oauth-v2-bearer`: https://tools.ietf.org/html/rfc6749#section-12.2201        .. _`I-D.ietf-oauth-v2-http-mac`: https://tools.ietf.org/html/rfc6749#section-12.2202        """203        if not is_secure_transport(uri):204            raise InsecureTransportError()205 206        token_placement = token_placement or self.default_token_placement207 208        case_insensitive_token_types = {209            k.lower(): v for k, v in self.token_types.items()}210        if not self.token_type.lower() in case_insensitive_token_types:211            raise ValueError("Unsupported token type: %s" % self.token_type)212 213        if not (self.access_token or self.token.get('access_token')):214            raise ValueError("Missing access token.")215 216        if self._expires_at and self._expires_at < time.time():217            raise TokenExpiredError()218 219        return case_insensitive_token_types[self.token_type.lower()](uri, http_method, body,220                                                                     headers, token_placement, **kwargs)221 222    def prepare_authorization_request(self, authorization_url, state=None,223                                      redirect_url=None, scope=None, **kwargs):224        """Prepare the authorization request.225 226        This is the first step in many OAuth flows in which the user is227        redirected to a certain authorization URL. This method adds228        required parameters to the authorization URL.229 230        :param authorization_url: Provider authorization endpoint URL.231        :param state: CSRF protection string. Will be automatically created if232            not provided. The generated state is available via the ``state``233            attribute. Clients should verify that the state is unchanged and234            present in the authorization response. This verification is done235            automatically if using the ``authorization_response`` parameter236            with ``prepare_token_request``.237        :param redirect_url: Redirect URL to which the user will be returned238            after authorization. Must be provided unless previously setup with239            the provider. If provided then it must also be provided in the240            token request.241        :param scope: List of scopes to request. Must be equal to242            or a subset of the scopes granted when obtaining the refresh243            token. If none is provided, the ones provided in the constructor are244            used.245        :param kwargs: Additional parameters to included in the request.246        :returns: The prepared request tuple with (url, headers, body).247        """248        if not is_secure_transport(authorization_url):249            raise InsecureTransportError()250 251        self.state = state or self.state_generator()252        self.redirect_url = redirect_url or self.redirect_url253        # do not assign scope to self automatically anymore254        scope = self.scope if scope is None else scope255        auth_url = self.prepare_request_uri(256            authorization_url, redirect_uri=self.redirect_url,257            scope=scope, state=self.state, **kwargs)258        return auth_url, FORM_ENC_HEADERS, ''259 260    def prepare_token_request(self, token_url, authorization_response=None,261                              redirect_url=None, state=None, body='', **kwargs):262        """Prepare a token creation request.263 264        Note that these requests usually require client authentication, either265        by including client_id or a set of provider specific authentication266        credentials.267 268        :param token_url: Provider token creation endpoint URL.269        :param authorization_response: The full redirection URL string, i.e.270            the location to which the user was redirected after successful271            authorization. Used to mine credentials needed to obtain a token272            in this step, such as authorization code.273        :param redirect_url: The redirect_url supplied with the authorization274            request (if there was one).275        :param state:276        :param body: Existing request body (URL encoded string) to embed parameters277                     into. This may contain extra parameters. Default ''.278        :param kwargs: Additional parameters to included in the request.279        :returns: The prepared request tuple with (url, headers, body).280        """281        if not is_secure_transport(token_url):282            raise InsecureTransportError()283 284        state = state or self.state285        if authorization_response:286            self.parse_request_uri_response(287                authorization_response, state=state)288        self.redirect_url = redirect_url or self.redirect_url289        body = self.prepare_request_body(body=body,290                                         redirect_uri=self.redirect_url, **kwargs)291 292        return token_url, FORM_ENC_HEADERS, body293 294    def prepare_refresh_token_request(self, token_url, refresh_token=None,295                                      body='', scope=None, **kwargs):296        """Prepare an access token refresh request.297 298        Expired access tokens can be replaced by new access tokens without299        going through the OAuth dance if the client obtained a refresh token.300        This refresh token and authentication credentials can be used to301        obtain a new access token, and possibly a new refresh token.302 303        :param token_url: Provider token refresh endpoint URL.304        :param refresh_token: Refresh token string.305        :param body: Existing request body (URL encoded string) to embed parameters306            into. This may contain extra parameters. Default ''.307        :param scope: List of scopes to request. Must be equal to308            or a subset of the scopes granted when obtaining the refresh309            token. If none is provided, the ones provided in the constructor are310            used.311        :param kwargs: Additional parameters to included in the request.312        :returns: The prepared request tuple with (url, headers, body).313        """314        if not is_secure_transport(token_url):315            raise InsecureTransportError()316 317        # do not assign scope to self automatically anymore318        scope = self.scope if scope is None else scope319        body = self.prepare_refresh_body(body=body,320                                         refresh_token=refresh_token, scope=scope, **kwargs)321        return token_url, FORM_ENC_HEADERS, body322 323    def prepare_token_revocation_request(self, revocation_url, token,324                                         token_type_hint="access_token", body='', callback=None, **kwargs):325        """Prepare a token revocation request.326 327        :param revocation_url: Provider token revocation endpoint URL.328        :param token: The access or refresh token to be revoked (string).329        :param token_type_hint: ``"access_token"`` (default) or330            ``"refresh_token"``. This is optional and if you wish to not pass it you331            must provide ``token_type_hint=None``.332        :param body:333        :param callback: A jsonp callback such as ``package.callback`` to be invoked334            upon receiving the response. Not that it should not include a () suffix.335        :param kwargs: Additional parameters to included in the request.336        :returns: The prepared request tuple with (url, headers, body).337 338        Note that JSONP request may use GET requests as the parameters will339        be added to the request URL query as opposed to the request body.340 341        An example of a revocation request342 343        .. code-block:: http344 345            POST /revoke HTTP/1.1346            Host: server.example.com347            Content-Type: application/x-www-form-urlencoded348            Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW349 350            token=45ghiukldjahdnhzdauz&token_type_hint=refresh_token351 352        An example of a jsonp revocation request353 354        .. code-block:: http355 356            GET /revoke?token=agabcdefddddafdd&callback=package.myCallback HTTP/1.1357            Host: server.example.com358            Content-Type: application/x-www-form-urlencoded359            Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW360 361        and an error response362 363        .. code-block:: javascript364 365            package.myCallback({"error":"unsupported_token_type"});366 367        Note that these requests usually require client credentials, client_id in368        the case for public clients and provider specific authentication369        credentials for confidential clients.370        """371        if not is_secure_transport(revocation_url):372            raise InsecureTransportError()373 374        return prepare_token_revocation_request(revocation_url, token,375                                                token_type_hint=token_type_hint, body=body, callback=callback,376                                                **kwargs)377 378    def parse_request_body_response(self, body, scope=None, **kwargs):379        """Parse the JSON response body.380 381        If the access token request is valid and authorized, the382        authorization server issues an access token as described in383        `Section 5.1`_.  A refresh token SHOULD NOT be included.  If the request384        failed client authentication or is invalid, the authorization server385        returns an error response as described in `Section 5.2`_.386 387        :param body: The response body from the token request.388        :param scope: Scopes originally requested. If none is provided, the ones389            provided in the constructor are used.390        :return: Dictionary of token parameters.391        :raises: Warning if scope has changed. :py:class:`oauthlib.oauth2.errors.OAuth2Error`392            if response is invalid.393 394        These response are json encoded and could easily be parsed without395        the assistance of OAuthLib. However, there are a few subtle issues396        to be aware of regarding the response which are helpfully addressed397        through the raising of various errors.398 399        A successful response should always contain400 401        **access_token**402                The access token issued by the authorization server. Often403                a random string.404 405        **token_type**406            The type of the token issued as described in `Section 7.1`_.407            Commonly ``Bearer``.408 409        While it is not mandated it is recommended that the provider include410 411        **expires_in**412            The lifetime in seconds of the access token.  For413            example, the value "3600" denotes that the access token will414            expire in one hour from the time the response was generated.415            If omitted, the authorization server SHOULD provide the416            expiration time via other means or document the default value.417 418         **scope**419            Providers may supply this in all responses but are required to only420            if it has changed since the authorization request.421 422        .. _`Section 5.1`: https://tools.ietf.org/html/rfc6749#section-5.1423        .. _`Section 5.2`: https://tools.ietf.org/html/rfc6749#section-5.2424        .. _`Section 7.1`: https://tools.ietf.org/html/rfc6749#section-7.1425        """426        scope = self.scope if scope is None else scope427        self.token = parse_token_response(body, scope=scope)428        self.populate_token_attributes(self.token)429        return self.token430 431    def prepare_refresh_body(self, body='', refresh_token=None, scope=None, **kwargs):432        """Prepare an access token request, using a refresh token.433 434        If the authorization server issued a refresh token to the client, the435        client makes a refresh request to the token endpoint by adding the436        following parameters using the `application/x-www-form-urlencoded`437        format in the HTTP request entity-body:438 439        :param refresh_token: REQUIRED.  The refresh token issued to the client.440        :param scope:  OPTIONAL.  The scope of the access request as described by441            Section 3.3.  The requested scope MUST NOT include any scope442            not originally granted by the resource owner, and if omitted is443            treated as equal to the scope originally granted by the444            resource owner. Note that if none is provided, the ones provided445            in the constructor are used if any.446        """447        refresh_token = refresh_token or self.refresh_token448        scope = self.scope if scope is None else scope449        return prepare_token_request(self.refresh_token_key, body=body, scope=scope,450                                     refresh_token=refresh_token, **kwargs)451 452    def _add_bearer_token(self, uri, http_method='GET', body=None,453                          headers=None, token_placement=None):454        """Add a bearer token to the request uri, body or authorization header."""455        if token_placement == AUTH_HEADER:456            headers = tokens.prepare_bearer_headers(self.access_token, headers)457 458        elif token_placement == URI_QUERY:459            uri = tokens.prepare_bearer_uri(self.access_token, uri)460 461        elif token_placement == BODY:462            body = tokens.prepare_bearer_body(self.access_token, body)463 464        else:465            raise ValueError("Invalid token placement.")466        return uri, headers, body467 468    def create_code_verifier(self, length):469        """Create PKCE **code_verifier** used in computing **code_challenge**. 470        See `RFC7636 Section 4.1`_471 472        :param length: REQUIRED. The length of the code_verifier.473 474        The client first creates a code verifier, "code_verifier", for each475        OAuth 2.0 [RFC6749] Authorization Request, in the following manner:476 477        .. code-block:: text478 479               code_verifier = high-entropy cryptographic random STRING using the480               unreserved characters [A-Z] / [a-z] / [0-9] / "-" / "." / "_" / "~"481               from Section 2.3 of [RFC3986], with a minimum length of 43 characters482               and a maximum length of 128 characters.483 484        .. _`RFC7636 Section 4.1`: https://tools.ietf.org/html/rfc7636#section-4.1485        """486        code_verifier = None487 488        if not length >= 43:489            raise ValueError("Length must be greater than or equal to 43")490 491        if not length <= 128:492            raise ValueError("Length must be less than or equal to 128")493 494        allowed_characters = re.compile('^[A-Zaa-z0-9-._~]')495        code_verifier = secrets.token_urlsafe(length)496 497        if not re.search(allowed_characters, code_verifier):498            raise ValueError("code_verifier contains invalid characters")499 500        self.code_verifier = code_verifier501 502        return code_verifier503 504    def create_code_challenge(self, code_verifier, code_challenge_method=None):505        """Create PKCE **code_challenge** derived from the  **code_verifier**.506        See `RFC7636 Section 4.2`_507 508        :param code_verifier: REQUIRED. The **code_verifier** generated from `create_code_verifier()`.509        :param code_challenge_method: OPTIONAL. The method used to derive the **code_challenge**. Acceptable values include `S256`. DEFAULT is `plain`.510 511               The client then creates a code challenge derived from the code512               verifier by using one of the following transformations on the code513               verifier::514 515                   plain516                      code_challenge = code_verifier517                   S256518                      code_challenge = BASE64URL-ENCODE(SHA256(ASCII(code_verifier)))519 520               If the client is capable of using `S256`, it MUST use `S256`, as521               `S256` is Mandatory To Implement (MTI) on the server.  Clients are522               permitted to use `plain` only if they cannot support `S256` for some523               technical reason and know via out-of-band configuration that the524               server supports `plain`.525 526               The plain transformation is for compatibility with existing527               deployments and for constrained environments that can't use the S256 transformation.528 529        .. _`RFC7636 Section 4.2`: https://tools.ietf.org/html/rfc7636#section-4.2530        """531        code_challenge = None532 533        if code_verifier == None:534            raise ValueError("Invalid code_verifier")535 536        if code_challenge_method == None:537            code_challenge_method = "plain"538            self.code_challenge_method = code_challenge_method539            code_challenge = code_verifier540            self.code_challenge = code_challenge541 542        if code_challenge_method == "S256":543            h = hashlib.sha256()544            h.update(code_verifier.encode(encoding='ascii'))545            sha256_val = h.digest()546            code_challenge = bytes.decode(base64.urlsafe_b64encode(sha256_val))547            # replace '+' with '-', '/' with '_', and remove trailing '='548            code_challenge = code_challenge.replace("+", "-").replace("/", "_").replace("=", "")549            self.code_challenge = code_challenge550 551        return code_challenge552 553    def _add_mac_token(self, uri, http_method='GET', body=None,554                       headers=None, token_placement=AUTH_HEADER, ext=None, **kwargs):555        """Add a MAC token to the request authorization header.556 557        Warning: MAC token support is experimental as the spec is not yet stable.558        """559        if token_placement != AUTH_HEADER:560            raise ValueError("Invalid token placement.")561 562        headers = tokens.prepare_mac_header(self.access_token, uri,563                                            self.mac_key, http_method, headers=headers, body=body, ext=ext,564                                            hash_algorithm=self.mac_algorithm, **kwargs)565        return uri, headers, body566 567    def _populate_attributes(self, response):568        warnings.warn("Please switch to the public method "569                      "populate_token_attributes.", DeprecationWarning)570        return self.populate_token_attributes(response)571 572    def populate_code_attributes(self, response):573        """Add attributes from an auth code response to self."""574 575        if 'code' in response:576            self.code = response.get('code')577 578    def populate_token_attributes(self, response):579        """Add attributes from a token exchange response to self."""580 581        if 'access_token' in response:582            self.access_token = response.get('access_token')583 584        if 'refresh_token' in response:585            self.refresh_token = response.get('refresh_token')586 587        if 'token_type' in response:588            self.token_type = response.get('token_type')589 590        if 'expires_in' in response:591            self.expires_in = response.get('expires_in')592            self._expires_at = time.time() + int(self.expires_in)593 594        if 'expires_at' in response:595            try:596                self._expires_at = int(response.get('expires_at'))597            except:598                self._expires_at = None599 600        if 'mac_key' in response:601            self.mac_key = response.get('mac_key')602 603        if 'mac_algorithm' in response:604            self.mac_algorithm = response.get('mac_algorithm')605 
codekingpro/portable-devtools · Team Ai