Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes15kdownloads
common.py434 linesDownload Raw Back to oauthlib
1"""2oauthlib.common3~~~~~~~~~~~~~~4 5This module provides data structures and utilities common6to all implementations of OAuth.7"""8import collections9import datetime10import logging11import re12import time13import urllib.parse as urlparse14from urllib.parse import (15    quote as _quote, unquote as _unquote, urlencode as _urlencode,16)17 18from . import get_debug19 20try:21    from secrets import SystemRandom, randbits22except ImportError:23    from random import SystemRandom, getrandbits as randbits24 25UNICODE_ASCII_CHARACTER_SET = ('abcdefghijklmnopqrstuvwxyz'26                               'ABCDEFGHIJKLMNOPQRSTUVWXYZ'27                               '0123456789')28 29CLIENT_ID_CHARACTER_SET = (r' !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMN'30                           'OPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}')31 32SANITIZE_PATTERN = re.compile(r'([^&;]*(?:password|token)[^=]*=)[^&;]+', re.IGNORECASE)33INVALID_HEX_PATTERN = re.compile(r'%[^0-9A-Fa-f]|%[0-9A-Fa-f][^0-9A-Fa-f]')34 35always_safe = ('ABCDEFGHIJKLMNOPQRSTUVWXYZ'36               'abcdefghijklmnopqrstuvwxyz'37               '0123456789_.-')38 39log = logging.getLogger('oauthlib')40 41 42# 'safe' must be bytes (Python 2.6 requires bytes, other versions allow either)43def quote(s, safe=b'/'):44    s = s.encode('utf-8') if isinstance(s, str) else s45    s = _quote(s, safe)46    # PY3 always returns unicode.  PY2 may return either, depending on whether47    # it had to modify the string.48    if isinstance(s, bytes):49        s = s.decode('utf-8')50    return s51 52 53def unquote(s):54    s = _unquote(s)55    # PY3 always returns unicode.  PY2 seems to always return what you give it,56    # which differs from quote's behavior.  Just to be safe, make sure it is57    # unicode before we return.58    if isinstance(s, bytes):59        s = s.decode('utf-8')60    return s61 62 63def urlencode(params):64    utf8_params = encode_params_utf8(params)65    urlencoded = _urlencode(utf8_params)66    if isinstance(urlencoded, str):67        return urlencoded68    else:69        return urlencoded.decode("utf-8")70 71 72def encode_params_utf8(params):73    """Ensures that all parameters in a list of 2-element tuples are encoded to74    bytestrings using UTF-875    """76    encoded = []77    for k, v in params:78        encoded.append((79            k.encode('utf-8') if isinstance(k, str) else k,80            v.encode('utf-8') if isinstance(v, str) else v))81    return encoded82 83 84def decode_params_utf8(params):85    """Ensures that all parameters in a list of 2-element tuples are decoded to86    unicode using UTF-8.87    """88    decoded = []89    for k, v in params:90        decoded.append((91            k.decode('utf-8') if isinstance(k, bytes) else k,92            v.decode('utf-8') if isinstance(v, bytes) else v))93    return decoded94 95 96urlencoded = set(always_safe) | set('=&;:%+~,*@!()/?\'$')97 98 99def urldecode(query):100    """Decode a query string in x-www-form-urlencoded format into a sequence101    of two-element tuples.102 103    Unlike urlparse.parse_qsl(..., strict_parsing=True) urldecode will enforce104    correct formatting of the query string by validation. If validation fails105    a ValueError will be raised. urllib.parse_qsl will only raise errors if106    any of name-value pairs omits the equals sign.107    """108    # Check if query contains invalid characters109    if query and not set(query) <= urlencoded:110        error = ("Error trying to decode a non urlencoded string. "111                 "Found invalid characters: %s "112                 "in the string: '%s'. "113                 "Please ensure the request/response body is "114                 "x-www-form-urlencoded.")115        raise ValueError(error % (set(query) - urlencoded, query))116 117    # Check for correctly hex encoded values using a regular expression118    # All encoded values begin with % followed by two hex characters119    # correct = %00, %A0, %0A, %FF120    # invalid = %G0, %5H, %PO121    if INVALID_HEX_PATTERN.search(query):122        raise ValueError('Invalid hex encoding in query string.')123 124    # We want to allow queries such as "c2" whereas urlparse.parse_qsl125    # with the strict_parsing flag will not.126    params = urlparse.parse_qsl(query, keep_blank_values=True)127 128    # unicode all the things129    return decode_params_utf8(params)130 131 132def extract_params(raw):133    """Extract parameters and return them as a list of 2-tuples.134 135    Will successfully extract parameters from urlencoded query strings,136    dicts, or lists of 2-tuples. Empty strings/dicts/lists will return an137    empty list of parameters. Any other input will result in a return138    value of None.139    """140    if isinstance(raw, (bytes, str)):141        try:142            params = urldecode(raw)143        except ValueError:144            params = None145    elif hasattr(raw, '__iter__'):146        try:147            dict(raw)148        except ValueError:149            params = None150        except TypeError:151            params = None152        else:153            params = list(raw.items() if isinstance(raw, dict) else raw)154            params = decode_params_utf8(params)155    else:156        params = None157 158    return params159 160 161def generate_nonce():162    """Generate pseudorandom nonce that is unlikely to repeat.163 164    Per `section 3.3`_ of the OAuth 1 RFC 5849 spec.165    Per `section 3.2.1`_ of the MAC Access Authentication spec.166 167    A random 64-bit number is appended to the epoch timestamp for both168    randomness and to decrease the likelihood of collisions.169 170    .. _`section 3.2.1`: https://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01#section-3.2.1171    .. _`section 3.3`: https://tools.ietf.org/html/rfc5849#section-3.3172    """173    return str(str(randbits(64)) + generate_timestamp())174 175 176def generate_timestamp():177    """Get seconds since epoch (UTC).178 179    Per `section 3.3`_ of the OAuth 1 RFC 5849 spec.180    Per `section 3.2.1`_ of the MAC Access Authentication spec.181 182    .. _`section 3.2.1`: https://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01#section-3.2.1183    .. _`section 3.3`: https://tools.ietf.org/html/rfc5849#section-3.3184    """185    return str(int(time.time()))186 187 188def generate_token(length=30, chars=UNICODE_ASCII_CHARACTER_SET):189    """Generates a non-guessable OAuth token190 191    OAuth (1 and 2) does not specify the format of tokens except that they192    should be strings of random characters. Tokens should not be guessable193    and entropy when generating the random characters is important. Which is194    why SystemRandom is used instead of the default random.choice method.195    """196    rand = SystemRandom()197    return ''.join(rand.choice(chars) for x in range(length))198 199 200def generate_signed_token(private_pem, request):201    import jwt  # noqa: PLC0415202 203    now = datetime.datetime.utcnow()204 205    claims = {206        'scope': request.scope,207        'exp': now + datetime.timedelta(seconds=request.expires_in)208    }209 210    claims.update(request.claims)211 212    token = jwt.encode(claims, private_pem, 'RS256')213    token = to_unicode(token, "UTF-8")214 215    return token216 217 218def verify_signed_token(public_pem, token):219    import jwt  # noqa: PLC0415220 221    return jwt.decode(token, public_pem, algorithms=['RS256'])222 223 224def generate_client_id(length=30, chars=CLIENT_ID_CHARACTER_SET):225    """Generates an OAuth client_id226 227    OAuth 2 specify the format of client_id in228    https://tools.ietf.org/html/rfc6749#appendix-A.229    """230    return generate_token(length, chars)231 232 233def add_params_to_qs(query, params):234    """Extend a query with a list of two-tuples."""235    if isinstance(params, dict):236        params = params.items()237    queryparams = urlparse.parse_qsl(query, keep_blank_values=True)238    queryparams.extend(params)239    return urlencode(queryparams)240 241 242def add_params_to_uri(uri, params, fragment=False):243    """Add a list of two-tuples to the uri query components."""244    sch, net, path, par, query, fra = urlparse.urlparse(uri)245    if fragment:246        fra = add_params_to_qs(fra, params)247    else:248        query = add_params_to_qs(query, params)249    return urlparse.urlunparse((sch, net, path, par, query, fra))250 251 252def safe_string_equals(a, b):253    """ Near-constant time string comparison.254 255    Used in order to avoid timing attacks on sensitive information such256    as secret keys during request verification (`rootLabs`_).257 258    .. _`rootLabs`: http://rdist.root.org/2010/01/07/timing-independent-array-comparison/259 260    """261    if len(a) != len(b):262        return False263 264    result = 0265    for x, y in zip(a, b):266        result |= ord(x) ^ ord(y)267    return result == 0268 269 270def to_unicode(data, encoding='UTF-8'):271    """Convert a number of different types of objects to unicode."""272    if isinstance(data, str):273        return data274 275    if isinstance(data, bytes):276        return str(data, encoding=encoding)277 278    if hasattr(data, '__iter__'):279        try:280            dict(data)281        except TypeError:282            pass283        except ValueError:284            # Assume it's a one dimensional data structure285            return (to_unicode(i, encoding) for i in data)286        else:287            # We support 2.6 which lacks dict comprehensions288            if hasattr(data, 'items'):289                data = data.items()290            return {to_unicode(k, encoding): to_unicode(v, encoding) for k, v in data}291 292    return data293 294 295class CaseInsensitiveDict(dict):296 297    """Basic case insensitive dict with strings only keys."""298 299    proxy = {}300 301    def __init__(self, data):302        self.proxy = {k.lower(): k for k in data}303        for k in data:304            self[k] = data[k]305 306    def __contains__(self, k):307        return k.lower() in self.proxy308 309    def __delitem__(self, k):310        key = self.proxy[k.lower()]311        super().__delitem__(key)312        del self.proxy[k.lower()]313 314    def __getitem__(self, k):315        key = self.proxy[k.lower()]316        return super().__getitem__(key)317 318    def get(self, k, default=None):319        return self[k] if k in self else default  # noqa: SIM401320 321    def __setitem__(self, k, v):322        super().__setitem__(k, v)323        self.proxy[k.lower()] = k324 325    def update(self, *args, **kwargs):326        super().update(*args, **kwargs)327        for k in dict(*args, **kwargs):328            self.proxy[k.lower()] = k329 330 331class Request:332 333    """A malleable representation of a signable HTTP request.334 335    Body argument may contain any data, but parameters will only be decoded if336    they are one of:337 338    * urlencoded query string339    * dict340    * list of 2-tuples341 342    Anything else will be treated as raw body data to be passed through343    unmolested.344    """345 346    def __init__(self, uri, http_method='GET', body=None, headers=None,347                 encoding='utf-8'):348        # Convert to unicode using encoding if given, else assume unicode349        def encode(x):350            return to_unicode(x, encoding) if encoding else x351 352        self.uri = encode(uri)353        self.http_method = encode(http_method)354        self.headers = CaseInsensitiveDict(encode(headers or {}))355        self.body = encode(body)356        self.decoded_body = extract_params(self.body)357        self.oauth_params = []358        self.validator_log = {}359 360        self._params = {361            "access_token": None,362            "client": None,363            "client_id": None,364            "client_secret": None,365            "code": None,366            "code_challenge": None,367            "code_challenge_method": None,368            "code_verifier": None,369            "extra_credentials": None,370            "grant_type": None,371            "redirect_uri": None,372            "refresh_token": None,373            "request_token": None,374            "response_type": None,375            "scope": None,376            "scopes": None,377            "state": None,378            "token": None,379            "user": None,380            "token_type_hint": None,381 382            # OpenID Connect383            "response_mode": None,384            "nonce": None,385            "display": None,386            "prompt": None,387            "claims": None,388            "max_age": None,389            "ui_locales": None,390            "id_token_hint": None,391            "login_hint": None,392            "acr_values": None393        }394        self._params.update(dict(urldecode(self.uri_query)))395        self._params.update(dict(self.decoded_body or []))396 397    def __getattr__(self, name):398        if name in self._params:399            return self._params[name]400        else:401            raise AttributeError(name)402 403    def __repr__(self):404        if not get_debug():405            return "<oauthlib.Request SANITIZED>"406        body = self.body407        headers = self.headers.copy()408        if body:409            body = SANITIZE_PATTERN.sub('\1<SANITIZED>', str(body))410        if 'Authorization' in headers:411            headers['Authorization'] = '<SANITIZED>'412        return '<oauthlib.Request url="{}", http_method="{}", headers="{}", body="{}">'.format(413            self.uri, self.http_method, headers, body)414 415    @property416    def uri_query(self):417        return urlparse.urlparse(self.uri).query418 419    @property420    def uri_query_params(self):421        if not self.uri_query:422            return []423        return urlparse.parse_qsl(self.uri_query, keep_blank_values=True,424                                  strict_parsing=True)425 426    @property427    def duplicate_params(self):428        seen_keys = collections.defaultdict(int)429        all_keys = (p[0]430                    for p in (self.decoded_body or []) + self.uri_query_params)431        for k in all_keys:432            seen_keys[k] += 1433        return [k for k, c in seen_keys.items() if c > 1]434 
codekingpro/portable-devtools · Team Ai