mirror of
https://github.com/Tautulli/Tautulli.git
synced 2025-07-05 20:51:15 -07:00
Bump requests-oauthlib from 1.3.1 to 2.0.0 (#2293)
* Bump requests-oauthlib from 1.3.1 to 2.0.0 Bumps [requests-oauthlib](https://github.com/requests/requests-oauthlib) from 1.3.1 to 2.0.0. - [Release notes](https://github.com/requests/requests-oauthlib/releases) - [Changelog](https://github.com/requests/requests-oauthlib/blob/master/HISTORY.rst) - [Commits](https://github.com/requests/requests-oauthlib/compare/v1.3.1...v2.0.0) --- updated-dependencies: - dependency-name: requests-oauthlib dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> * Update requests-oauthlib==2.0.0 --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> [skip ci]
This commit is contained in:
parent
452a4afdcf
commit
0d1d2a3e6b
60 changed files with 2414 additions and 2291 deletions
4
lib/charset_normalizer/__main__.py
Normal file
4
lib/charset_normalizer/__main__.py
Normal file
|
@ -0,0 +1,4 @@
|
|||
from .cli import cli_detect
|
||||
|
||||
if __name__ == "__main__":
|
||||
cli_detect()
|
File diff suppressed because it is too large
Load diff
|
@ -4,8 +4,13 @@ from collections import Counter
|
|||
from functools import lru_cache
|
||||
from typing import Counter as TypeCounter, Dict, List, Optional, Tuple
|
||||
|
||||
from .assets import FREQUENCIES
|
||||
from .constant import KO_NAMES, LANGUAGE_SUPPORTED_COUNT, TOO_SMALL_SEQUENCE, ZH_NAMES
|
||||
from .constant import (
|
||||
FREQUENCIES,
|
||||
KO_NAMES,
|
||||
LANGUAGE_SUPPORTED_COUNT,
|
||||
TOO_SMALL_SEQUENCE,
|
||||
ZH_NAMES,
|
||||
)
|
||||
from .md import is_suspiciously_successive_range
|
||||
from .models import CoherenceMatches
|
||||
from .utils import (
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
from .__main__ import cli_detect, query_yes_no
|
||||
|
||||
__all__ = (
|
||||
"cli_detect",
|
||||
"query_yes_no",
|
||||
)
|
File diff suppressed because it is too large
Load diff
|
@ -9,7 +9,8 @@ from .constant import (
|
|||
)
|
||||
from .utils import (
|
||||
is_accentuated,
|
||||
is_ascii,
|
||||
is_arabic,
|
||||
is_arabic_isolated_form,
|
||||
is_case_variable,
|
||||
is_cjk,
|
||||
is_emoticon,
|
||||
|
@ -128,8 +129,9 @@ class TooManyAccentuatedPlugin(MessDetectorPlugin):
|
|||
|
||||
@property
|
||||
def ratio(self) -> float:
|
||||
if self._character_count == 0 or self._character_count < 8:
|
||||
if self._character_count < 8:
|
||||
return 0.0
|
||||
|
||||
ratio_of_accentuation: float = self._accentuated_count / self._character_count
|
||||
return ratio_of_accentuation if ratio_of_accentuation >= 0.35 else 0.0
|
||||
|
||||
|
@ -234,16 +236,13 @@ class SuspiciousRange(MessDetectorPlugin):
|
|||
|
||||
@property
|
||||
def ratio(self) -> float:
|
||||
if self._character_count == 0:
|
||||
if self._character_count <= 24:
|
||||
return 0.0
|
||||
|
||||
ratio_of_suspicious_range_usage: float = (
|
||||
self._suspicious_successive_range_count * 2
|
||||
) / self._character_count
|
||||
|
||||
if ratio_of_suspicious_range_usage < 0.1:
|
||||
return 0.0
|
||||
|
||||
return ratio_of_suspicious_range_usage
|
||||
|
||||
|
||||
|
@ -296,7 +295,11 @@ class SuperWeirdWordPlugin(MessDetectorPlugin):
|
|||
self._is_current_word_bad = True
|
||||
# Word/Buffer ending with an upper case accentuated letter are so rare,
|
||||
# that we will consider them all as suspicious. Same weight as foreign_long suspicious.
|
||||
if is_accentuated(self._buffer[-1]) and self._buffer[-1].isupper():
|
||||
if (
|
||||
is_accentuated(self._buffer[-1])
|
||||
and self._buffer[-1].isupper()
|
||||
and all(_.isupper() for _ in self._buffer) is False
|
||||
):
|
||||
self._foreign_long_count += 1
|
||||
self._is_current_word_bad = True
|
||||
if buffer_length >= 24 and self._foreign_long_watch:
|
||||
|
@ -419,7 +422,7 @@ class ArchaicUpperLowerPlugin(MessDetectorPlugin):
|
|||
|
||||
return
|
||||
|
||||
if self._current_ascii_only is True and is_ascii(character) is False:
|
||||
if self._current_ascii_only is True and character.isascii() is False:
|
||||
self._current_ascii_only = False
|
||||
|
||||
if self._last_alpha_seen is not None:
|
||||
|
@ -455,6 +458,34 @@ class ArchaicUpperLowerPlugin(MessDetectorPlugin):
|
|||
return self._successive_upper_lower_count_final / self._character_count
|
||||
|
||||
|
||||
class ArabicIsolatedFormPlugin(MessDetectorPlugin):
|
||||
def __init__(self) -> None:
|
||||
self._character_count: int = 0
|
||||
self._isolated_form_count: int = 0
|
||||
|
||||
def reset(self) -> None: # pragma: no cover
|
||||
self._character_count = 0
|
||||
self._isolated_form_count = 0
|
||||
|
||||
def eligible(self, character: str) -> bool:
|
||||
return is_arabic(character)
|
||||
|
||||
def feed(self, character: str) -> None:
|
||||
self._character_count += 1
|
||||
|
||||
if is_arabic_isolated_form(character):
|
||||
self._isolated_form_count += 1
|
||||
|
||||
@property
|
||||
def ratio(self) -> float:
|
||||
if self._character_count < 8:
|
||||
return 0.0
|
||||
|
||||
isolated_form_usage: float = self._isolated_form_count / self._character_count
|
||||
|
||||
return isolated_form_usage
|
||||
|
||||
|
||||
@lru_cache(maxsize=1024)
|
||||
def is_suspiciously_successive_range(
|
||||
unicode_range_a: Optional[str], unicode_range_b: Optional[str]
|
||||
|
@ -522,6 +553,8 @@ def is_suspiciously_successive_range(
|
|||
return False
|
||||
if "Forms" in unicode_range_a or "Forms" in unicode_range_b:
|
||||
return False
|
||||
if unicode_range_a == "Basic Latin" or unicode_range_b == "Basic Latin":
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
|
|
@ -54,16 +54,19 @@ class CharsetMatch:
|
|||
|
||||
# Below 1% difference --> Use Coherence
|
||||
if chaos_difference < 0.01 and coherence_difference > 0.02:
|
||||
# When having a tough decision, use the result that decoded as many multi-byte as possible.
|
||||
if chaos_difference == 0.0 and self.coherence == other.coherence:
|
||||
return self.multi_byte_usage > other.multi_byte_usage
|
||||
return self.coherence > other.coherence
|
||||
elif chaos_difference < 0.01 and coherence_difference <= 0.02:
|
||||
# When having a difficult decision, use the result that decoded as many multi-byte as possible.
|
||||
# preserve RAM usage!
|
||||
if len(self._payload) >= TOO_BIG_SEQUENCE:
|
||||
return self.chaos < other.chaos
|
||||
return self.multi_byte_usage > other.multi_byte_usage
|
||||
|
||||
return self.chaos < other.chaos
|
||||
|
||||
@property
|
||||
def multi_byte_usage(self) -> float:
|
||||
return 1.0 - len(str(self)) / len(self.raw)
|
||||
return 1.0 - (len(str(self)) / len(self.raw))
|
||||
|
||||
def __str__(self) -> str:
|
||||
# Lazy Str Loading
|
||||
|
|
|
@ -32,6 +32,8 @@ def is_accentuated(character: str) -> bool:
|
|||
or "WITH DIAERESIS" in description
|
||||
or "WITH CIRCUMFLEX" in description
|
||||
or "WITH TILDE" in description
|
||||
or "WITH MACRON" in description
|
||||
or "WITH RING ABOVE" in description
|
||||
)
|
||||
|
||||
|
||||
|
@ -69,15 +71,6 @@ def is_latin(character: str) -> bool:
|
|||
return "LATIN" in description
|
||||
|
||||
|
||||
@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION)
|
||||
def is_ascii(character: str) -> bool:
|
||||
try:
|
||||
character.encode("ascii")
|
||||
except UnicodeEncodeError:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION)
|
||||
def is_punctuation(character: str) -> bool:
|
||||
character_category: str = unicodedata.category(character)
|
||||
|
@ -105,7 +98,7 @@ def is_symbol(character: str) -> bool:
|
|||
if character_range is None:
|
||||
return False
|
||||
|
||||
return "Forms" in character_range
|
||||
return "Forms" in character_range and character_category != "Lo"
|
||||
|
||||
|
||||
@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION)
|
||||
|
@ -115,7 +108,7 @@ def is_emoticon(character: str) -> bool:
|
|||
if character_range is None:
|
||||
return False
|
||||
|
||||
return "Emoticons" in character_range
|
||||
return "Emoticons" in character_range or "Pictographs" in character_range
|
||||
|
||||
|
||||
@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION)
|
||||
|
@ -133,12 +126,6 @@ def is_case_variable(character: str) -> bool:
|
|||
return character.islower() != character.isupper()
|
||||
|
||||
|
||||
def is_private_use_only(character: str) -> bool:
|
||||
character_category: str = unicodedata.category(character)
|
||||
|
||||
return character_category == "Co"
|
||||
|
||||
|
||||
@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION)
|
||||
def is_cjk(character: str) -> bool:
|
||||
try:
|
||||
|
@ -189,6 +176,26 @@ def is_thai(character: str) -> bool:
|
|||
return "THAI" in character_name
|
||||
|
||||
|
||||
@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION)
|
||||
def is_arabic(character: str) -> bool:
|
||||
try:
|
||||
character_name = unicodedata.name(character)
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
return "ARABIC" in character_name
|
||||
|
||||
|
||||
@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION)
|
||||
def is_arabic_isolated_form(character: str) -> bool:
|
||||
try:
|
||||
character_name = unicodedata.name(character)
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
return "ARABIC" in character_name and "ISOLATED FORM" in character_name
|
||||
|
||||
|
||||
@lru_cache(maxsize=len(UNICODE_RANGES_COMBINED))
|
||||
def is_unicode_range_secondary(range_name: str) -> bool:
|
||||
return any(keyword in range_name for keyword in UNICODE_SECONDARY_RANGE_KEYWORD)
|
||||
|
@ -205,7 +212,7 @@ def is_unprintable(character: str) -> bool:
|
|||
)
|
||||
|
||||
|
||||
def any_specified_encoding(sequence: bytes, search_zone: int = 4096) -> Optional[str]:
|
||||
def any_specified_encoding(sequence: bytes, search_zone: int = 8192) -> Optional[str]:
|
||||
"""
|
||||
Extract using ASCII-only decoder any specified encoding in the first n-bytes.
|
||||
"""
|
||||
|
|
|
@ -2,5 +2,5 @@
|
|||
Expose version
|
||||
"""
|
||||
|
||||
__version__ = "3.2.0"
|
||||
__version__ = "3.3.2"
|
||||
VERSION = __version__.split(".")
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from .core import encode, decode, alabel, ulabel, IDNAError
|
||||
import codecs
|
||||
import re
|
||||
from typing import Tuple, Optional
|
||||
from typing import Any, Tuple, Optional
|
||||
|
||||
_unicode_dots_re = re.compile('[\u002e\u3002\uff0e\uff61]')
|
||||
|
||||
|
@ -26,24 +26,24 @@ class Codec(codecs.Codec):
|
|||
return decode(data), len(data)
|
||||
|
||||
class IncrementalEncoder(codecs.BufferedIncrementalEncoder):
|
||||
def _buffer_encode(self, data: str, errors: str, final: bool) -> Tuple[str, int]: # type: ignore
|
||||
def _buffer_encode(self, data: str, errors: str, final: bool) -> Tuple[bytes, int]:
|
||||
if errors != 'strict':
|
||||
raise IDNAError('Unsupported error handling \"{}\"'.format(errors))
|
||||
|
||||
if not data:
|
||||
return "", 0
|
||||
return b'', 0
|
||||
|
||||
labels = _unicode_dots_re.split(data)
|
||||
trailing_dot = ''
|
||||
trailing_dot = b''
|
||||
if labels:
|
||||
if not labels[-1]:
|
||||
trailing_dot = '.'
|
||||
trailing_dot = b'.'
|
||||
del labels[-1]
|
||||
elif not final:
|
||||
# Keep potentially unfinished label until the next call
|
||||
del labels[-1]
|
||||
if labels:
|
||||
trailing_dot = '.'
|
||||
trailing_dot = b'.'
|
||||
|
||||
result = []
|
||||
size = 0
|
||||
|
@ -54,18 +54,21 @@ class IncrementalEncoder(codecs.BufferedIncrementalEncoder):
|
|||
size += len(label)
|
||||
|
||||
# Join with U+002E
|
||||
result_str = '.'.join(result) + trailing_dot # type: ignore
|
||||
result_bytes = b'.'.join(result) + trailing_dot
|
||||
size += len(trailing_dot)
|
||||
return result_str, size
|
||||
return result_bytes, size
|
||||
|
||||
class IncrementalDecoder(codecs.BufferedIncrementalDecoder):
|
||||
def _buffer_decode(self, data: str, errors: str, final: bool) -> Tuple[str, int]: # type: ignore
|
||||
def _buffer_decode(self, data: Any, errors: str, final: bool) -> Tuple[str, int]:
|
||||
if errors != 'strict':
|
||||
raise IDNAError('Unsupported error handling \"{}\"'.format(errors))
|
||||
|
||||
if not data:
|
||||
return ('', 0)
|
||||
|
||||
if not isinstance(data, str):
|
||||
data = str(data, 'ascii')
|
||||
|
||||
labels = _unicode_dots_re.split(data)
|
||||
trailing_dot = ''
|
||||
if labels:
|
||||
|
@ -99,14 +102,17 @@ class StreamReader(Codec, codecs.StreamReader):
|
|||
pass
|
||||
|
||||
|
||||
def getregentry() -> codecs.CodecInfo:
|
||||
# Compatibility as a search_function for codecs.register()
|
||||
def search_function(name: str) -> Optional[codecs.CodecInfo]:
|
||||
if name != 'idna2008':
|
||||
return None
|
||||
return codecs.CodecInfo(
|
||||
name='idna',
|
||||
encode=Codec().encode, # type: ignore
|
||||
decode=Codec().decode, # type: ignore
|
||||
name=name,
|
||||
encode=Codec().encode,
|
||||
decode=Codec().decode,
|
||||
incrementalencoder=IncrementalEncoder,
|
||||
incrementaldecoder=IncrementalDecoder,
|
||||
streamwriter=StreamWriter,
|
||||
streamreader=StreamReader,
|
||||
)
|
||||
|
||||
codecs.register(search_function)
|
||||
|
|
|
@ -318,7 +318,7 @@ def uts46_remap(domain: str, std3_rules: bool = True, transitional: bool = False
|
|||
status = uts46row[1]
|
||||
replacement = None # type: Optional[str]
|
||||
if len(uts46row) == 3:
|
||||
replacement = uts46row[2] # type: ignore
|
||||
replacement = uts46row[2]
|
||||
if (status == 'V' or
|
||||
(status == 'D' and not transitional) or
|
||||
(status == '3' and not std3_rules and replacement is None)):
|
||||
|
@ -338,9 +338,9 @@ def uts46_remap(domain: str, std3_rules: bool = True, transitional: bool = False
|
|||
|
||||
|
||||
def encode(s: Union[str, bytes, bytearray], strict: bool = False, uts46: bool = False, std3_rules: bool = False, transitional: bool = False) -> bytes:
|
||||
if isinstance(s, (bytes, bytearray)):
|
||||
if not isinstance(s, str):
|
||||
try:
|
||||
s = s.decode('ascii')
|
||||
s = str(s, 'ascii')
|
||||
except UnicodeDecodeError:
|
||||
raise IDNAError('should pass a unicode string to the function rather than a byte string.')
|
||||
if uts46:
|
||||
|
@ -372,8 +372,8 @@ def encode(s: Union[str, bytes, bytearray], strict: bool = False, uts46: bool =
|
|||
|
||||
def decode(s: Union[str, bytes, bytearray], strict: bool = False, uts46: bool = False, std3_rules: bool = False) -> str:
|
||||
try:
|
||||
if isinstance(s, (bytes, bytearray)):
|
||||
s = s.decode('ascii')
|
||||
if not isinstance(s, str):
|
||||
s = str(s, 'ascii')
|
||||
except UnicodeDecodeError:
|
||||
raise IDNAError('Invalid ASCII in A-label')
|
||||
if uts46:
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# This file is automatically generated by tools/idna-data
|
||||
|
||||
__version__ = '15.0.0'
|
||||
__version__ = '15.1.0'
|
||||
scripts = {
|
||||
'Greek': (
|
||||
0x37000000374,
|
||||
|
@ -59,6 +59,7 @@ scripts = {
|
|||
0x2b7400002b81e,
|
||||
0x2b8200002cea2,
|
||||
0x2ceb00002ebe1,
|
||||
0x2ebf00002ee5e,
|
||||
0x2f8000002fa1e,
|
||||
0x300000003134b,
|
||||
0x31350000323b0,
|
||||
|
@ -1834,7 +1835,6 @@ codepoint_classes = {
|
|||
0xa7d50000a7d6,
|
||||
0xa7d70000a7d8,
|
||||
0xa7d90000a7da,
|
||||
0xa7f20000a7f5,
|
||||
0xa7f60000a7f8,
|
||||
0xa7fa0000a828,
|
||||
0xa82c0000a82d,
|
||||
|
@ -1907,9 +1907,7 @@ codepoint_classes = {
|
|||
0x1060000010737,
|
||||
0x1074000010756,
|
||||
0x1076000010768,
|
||||
0x1078000010786,
|
||||
0x10787000107b1,
|
||||
0x107b2000107bb,
|
||||
0x1078000010781,
|
||||
0x1080000010806,
|
||||
0x1080800010809,
|
||||
0x1080a00010836,
|
||||
|
@ -2134,6 +2132,7 @@ codepoint_classes = {
|
|||
0x2b7400002b81e,
|
||||
0x2b8200002cea2,
|
||||
0x2ceb00002ebe1,
|
||||
0x2ebf00002ee5e,
|
||||
0x300000003134b,
|
||||
0x31350000323b0,
|
||||
),
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
__version__ = '3.4'
|
||||
__version__ = '3.6'
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -12,7 +12,7 @@ import logging
|
|||
from logging import NullHandler
|
||||
|
||||
__author__ = 'The OAuthlib Community'
|
||||
__version__ = '3.2.0'
|
||||
__version__ = '3.2.2'
|
||||
|
||||
logging.getLogger('oauthlib').addHandler(NullHandler())
|
||||
|
||||
|
|
|
@ -18,11 +18,9 @@ from urllib.parse import (
|
|||
from . import get_debug
|
||||
|
||||
try:
|
||||
from secrets import randbits
|
||||
from secrets import SystemRandom
|
||||
from secrets import SystemRandom, randbits
|
||||
except ImportError:
|
||||
from random import getrandbits as randbits
|
||||
from random import SystemRandom
|
||||
from random import SystemRandom, getrandbits as randbits
|
||||
|
||||
UNICODE_ASCII_CHARACTER_SET = ('abcdefghijklmnopqrstuvwxyz'
|
||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
||||
|
|
|
@ -5,24 +5,19 @@ oauthlib.oauth1
|
|||
This module is a wrapper for the most recent implementation of OAuth 1.0 Client
|
||||
and Server classes.
|
||||
"""
|
||||
from .rfc5849 import Client
|
||||
from .rfc5849 import (SIGNATURE_HMAC,
|
||||
SIGNATURE_HMAC_SHA1,
|
||||
SIGNATURE_HMAC_SHA256,
|
||||
SIGNATURE_HMAC_SHA512,
|
||||
SIGNATURE_RSA,
|
||||
SIGNATURE_RSA_SHA1,
|
||||
SIGNATURE_RSA_SHA256,
|
||||
SIGNATURE_RSA_SHA512,
|
||||
SIGNATURE_PLAINTEXT)
|
||||
from .rfc5849 import SIGNATURE_TYPE_AUTH_HEADER, SIGNATURE_TYPE_QUERY
|
||||
from .rfc5849 import SIGNATURE_TYPE_BODY
|
||||
from .rfc5849 import (
|
||||
SIGNATURE_HMAC, SIGNATURE_HMAC_SHA1, SIGNATURE_HMAC_SHA256,
|
||||
SIGNATURE_HMAC_SHA512, SIGNATURE_PLAINTEXT, SIGNATURE_RSA,
|
||||
SIGNATURE_RSA_SHA1, SIGNATURE_RSA_SHA256, SIGNATURE_RSA_SHA512,
|
||||
SIGNATURE_TYPE_AUTH_HEADER, SIGNATURE_TYPE_BODY, SIGNATURE_TYPE_QUERY,
|
||||
Client,
|
||||
)
|
||||
from .rfc5849.endpoints import (
|
||||
AccessTokenEndpoint, AuthorizationEndpoint, RequestTokenEndpoint,
|
||||
ResourceEndpoint, SignatureOnlyEndpoint, WebApplicationServer,
|
||||
)
|
||||
from .rfc5849.errors import (
|
||||
InsecureTransportError, InvalidClientError, InvalidRequestError,
|
||||
InvalidSignatureMethodError, OAuth1Error,
|
||||
)
|
||||
from .rfc5849.request_validator import RequestValidator
|
||||
from .rfc5849.endpoints import RequestTokenEndpoint, AuthorizationEndpoint
|
||||
from .rfc5849.endpoints import AccessTokenEndpoint, ResourceEndpoint
|
||||
from .rfc5849.endpoints import SignatureOnlyEndpoint, WebApplicationServer
|
||||
from .rfc5849.errors import (InsecureTransportError,
|
||||
InvalidClientError,
|
||||
InvalidRequestError,
|
||||
InvalidSignatureMethodError,
|
||||
OAuth1Error)
|
||||
|
|
|
@ -11,12 +11,11 @@ import time
|
|||
from oauthlib.common import CaseInsensitiveDict, Request, generate_token
|
||||
|
||||
from .. import (
|
||||
CONTENT_TYPE_FORM_URLENCODED,
|
||||
SIGNATURE_HMAC_SHA1, SIGNATURE_HMAC_SHA256, SIGNATURE_HMAC_SHA512,
|
||||
SIGNATURE_RSA_SHA1, SIGNATURE_RSA_SHA256, SIGNATURE_RSA_SHA512,
|
||||
SIGNATURE_PLAINTEXT,
|
||||
SIGNATURE_TYPE_AUTH_HEADER, SIGNATURE_TYPE_BODY,
|
||||
SIGNATURE_TYPE_QUERY, errors, signature, utils)
|
||||
CONTENT_TYPE_FORM_URLENCODED, SIGNATURE_HMAC_SHA1, SIGNATURE_HMAC_SHA256,
|
||||
SIGNATURE_HMAC_SHA512, SIGNATURE_PLAINTEXT, SIGNATURE_RSA_SHA1,
|
||||
SIGNATURE_RSA_SHA256, SIGNATURE_RSA_SHA512, SIGNATURE_TYPE_AUTH_HEADER,
|
||||
SIGNATURE_TYPE_BODY, SIGNATURE_TYPE_QUERY, errors, signature, utils,
|
||||
)
|
||||
|
||||
|
||||
class BaseEndpoint:
|
||||
|
|
|
@ -152,7 +152,7 @@ class RequestTokenEndpoint(BaseEndpoint):
|
|||
request.client_key = self.request_validator.dummy_client
|
||||
|
||||
# Note that `realm`_ is only used in authorization headers and how
|
||||
# it should be interepreted is not included in the OAuth spec.
|
||||
# it should be interpreted is not included in the OAuth spec.
|
||||
# However they could be seen as a scope or realm to which the
|
||||
# client has access and as such every client should be checked
|
||||
# to ensure it is authorized access to that scope or realm.
|
||||
|
@ -164,7 +164,7 @@ class RequestTokenEndpoint(BaseEndpoint):
|
|||
# workflow where a client requests access to a specific realm.
|
||||
# This first step (obtaining request token) need not require a realm
|
||||
# and can then be identified by checking the require_resource_owner
|
||||
# flag and abscence of realm.
|
||||
# flag and absence of realm.
|
||||
#
|
||||
# Clients obtaining an access token will not supply a realm and it will
|
||||
# not be checked. Instead the previously requested realm should be
|
||||
|
|
|
@ -113,7 +113,7 @@ class ResourceEndpoint(BaseEndpoint):
|
|||
request.resource_owner_key = self.request_validator.dummy_access_token
|
||||
|
||||
# Note that `realm`_ is only used in authorization headers and how
|
||||
# it should be interepreted is not included in the OAuth spec.
|
||||
# it should be interpreted is not included in the OAuth spec.
|
||||
# However they could be seen as a scope or realm to which the
|
||||
# client has access and as such every client should be checked
|
||||
# to ensure it is authorized access to that scope or realm.
|
||||
|
@ -125,7 +125,7 @@ class ResourceEndpoint(BaseEndpoint):
|
|||
# workflow where a client requests access to a specific realm.
|
||||
# This first step (obtaining request token) need not require a realm
|
||||
# and can then be identified by checking the require_resource_owner
|
||||
# flag and abscence of realm.
|
||||
# flag and absence of realm.
|
||||
#
|
||||
# Clients obtaining an access token will not supply a realm and it will
|
||||
# not be checked. Instead the previously requested realm should be
|
||||
|
|
|
@ -19,7 +19,7 @@ class RequestValidator:
|
|||
Methods used to check the format of input parameters. Common tests include
|
||||
length, character set, membership, range or pattern. These tests are
|
||||
referred to as `whitelisting or blacklisting`_. Whitelisting is better
|
||||
but blacklisting can be usefull to spot malicious activity.
|
||||
but blacklisting can be useful to spot malicious activity.
|
||||
The following have methods a default implementation:
|
||||
|
||||
- check_client_key
|
||||
|
@ -443,7 +443,7 @@ class RequestValidator:
|
|||
:type request: oauthlib.common.Request
|
||||
:returns: None
|
||||
|
||||
Per `Section 2.3`__ of the spec:
|
||||
Per `Section 2.3`_ of the spec:
|
||||
|
||||
"The server MUST (...) ensure that the temporary
|
||||
credentials have not expired or been used before."
|
||||
|
@ -831,7 +831,7 @@ class RequestValidator:
|
|||
"""Associate an authorization verifier with a request token.
|
||||
|
||||
:param token: A request token string.
|
||||
:param verifier A dictionary containing the oauth_verifier and
|
||||
:param verifier: A dictionary containing the oauth_verifier and
|
||||
oauth_token
|
||||
:param request: OAuthlib request.
|
||||
:type request: oauthlib.common.Request
|
||||
|
|
|
@ -37,15 +37,15 @@ should have no impact on properly behaving programs.
|
|||
import binascii
|
||||
import hashlib
|
||||
import hmac
|
||||
import ipaddress
|
||||
import logging
|
||||
import urllib.parse as urlparse
|
||||
import warnings
|
||||
|
||||
from oauthlib.common import extract_params, safe_string_equals, urldecode
|
||||
import urllib.parse as urlparse
|
||||
|
||||
from . import utils
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
@ -131,7 +131,12 @@ def base_string_uri(uri: str, host: str = None) -> str:
|
|||
raise ValueError('uri must be a string.')
|
||||
|
||||
# FIXME: urlparse does not support unicode
|
||||
scheme, netloc, path, params, query, fragment = urlparse.urlparse(uri)
|
||||
output = urlparse.urlparse(uri)
|
||||
scheme = output.scheme
|
||||
hostname = output.hostname
|
||||
port = output.port
|
||||
path = output.path
|
||||
params = output.params
|
||||
|
||||
# The scheme, authority, and path of the request resource URI `RFC3986`
|
||||
# are included by constructing an "http" or "https" URI representing
|
||||
|
@ -153,13 +158,22 @@ def base_string_uri(uri: str, host: str = None) -> str:
|
|||
|
||||
# 1. The scheme and host MUST be in lowercase.
|
||||
scheme = scheme.lower()
|
||||
netloc = netloc.lower()
|
||||
# Note: if ``host`` is used, it will be converted to lowercase below
|
||||
if hostname is not None:
|
||||
hostname = hostname.lower()
|
||||
|
||||
# 2. The host and port values MUST match the content of the HTTP
|
||||
# request "Host" header field.
|
||||
if host is not None:
|
||||
netloc = host.lower() # override value in uri with provided host
|
||||
# NOTE: override value in uri with provided host
|
||||
# Host argument is equal to netloc. It means it's missing scheme.
|
||||
# Add it back, before parsing.
|
||||
|
||||
host = host.lower()
|
||||
host = f"{scheme}://{host}"
|
||||
output = urlparse.urlparse(host)
|
||||
hostname = output.hostname
|
||||
port = output.port
|
||||
|
||||
# 3. The port MUST be included if it is not the default port for the
|
||||
# scheme, and MUST be excluded if it is the default. Specifically,
|
||||
|
@ -170,33 +184,28 @@ def base_string_uri(uri: str, host: str = None) -> str:
|
|||
# .. _`RFC2616`: https://tools.ietf.org/html/rfc2616
|
||||
# .. _`RFC2818`: https://tools.ietf.org/html/rfc2818
|
||||
|
||||
if ':' in netloc:
|
||||
# Contains a colon ":", so try to parse as "host:port"
|
||||
if hostname is None:
|
||||
raise ValueError('missing host')
|
||||
|
||||
hostname, port_str = netloc.split(':', 1)
|
||||
# NOTE: Try guessing if we're dealing with IP or hostname
|
||||
try:
|
||||
hostname = ipaddress.ip_address(hostname)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
if len(hostname) == 0:
|
||||
raise ValueError('missing host') # error: netloc was ":port" or ":"
|
||||
if isinstance(hostname, ipaddress.IPv6Address):
|
||||
hostname = f"[{hostname}]"
|
||||
elif isinstance(hostname, ipaddress.IPv4Address):
|
||||
hostname = f"{hostname}"
|
||||
|
||||
if len(port_str) == 0:
|
||||
netloc = hostname # was "host:", so just use the host part
|
||||
else:
|
||||
try:
|
||||
port_num = int(port_str) # try to parse into an integer number
|
||||
except ValueError:
|
||||
raise ValueError('port is not an integer')
|
||||
|
||||
if port_num <= 0 or 65535 < port_num:
|
||||
raise ValueError('port out of range') # 16-bit unsigned ints
|
||||
if (scheme, port_num) in (('http', 80), ('https', 443)):
|
||||
netloc = hostname # default port for scheme: exclude port num
|
||||
else:
|
||||
netloc = hostname + ':' + str(port_num) # use hostname:port
|
||||
if port is not None and not (0 < port <= 65535):
|
||||
raise ValueError('port out of range') # 16-bit unsigned ints
|
||||
if (scheme, port) in (('http', 80), ('https', 443)):
|
||||
netloc = hostname # default port for scheme: exclude port num
|
||||
elif port:
|
||||
netloc = f"{hostname}:{port}" # use hostname:port
|
||||
else:
|
||||
# Does not contain a colon, so entire value must be the hostname
|
||||
|
||||
if len(netloc) == 0:
|
||||
raise ValueError('missing host') # error: netloc was empty string
|
||||
netloc = hostname
|
||||
|
||||
v = urlparse.urlunparse((scheme, netloc, path, params, '', ''))
|
||||
|
||||
|
|
|
@ -39,7 +39,7 @@ class BackendApplicationClient(Client):
|
|||
format per `Appendix B`_ in the HTTP request entity-body:
|
||||
|
||||
:param body: Existing request body (URL encoded string) to embed parameters
|
||||
into. This may contain extra paramters. Default ''.
|
||||
into. This may contain extra parameters. Default ''.
|
||||
:param scope: The scope of the access request as described by
|
||||
`Section 3.3`_.
|
||||
|
||||
|
|
|
@ -6,12 +6,12 @@ oauthlib.oauth2.rfc6749
|
|||
This module is an implementation of various logic needed
|
||||
for consuming OAuth 2.0 RFC6749.
|
||||
"""
|
||||
import base64
|
||||
import hashlib
|
||||
import re
|
||||
import secrets
|
||||
import time
|
||||
import warnings
|
||||
import secrets
|
||||
import re
|
||||
import hashlib
|
||||
import base64
|
||||
|
||||
from oauthlib.common import generate_token
|
||||
from oauthlib.oauth2.rfc6749 import tokens
|
||||
|
@ -228,26 +228,21 @@ class Client:
|
|||
required parameters to the authorization URL.
|
||||
|
||||
:param authorization_url: Provider authorization endpoint URL.
|
||||
|
||||
:param state: CSRF protection string. Will be automatically created if
|
||||
not provided. The generated state is available via the ``state``
|
||||
attribute. Clients should verify that the state is unchanged and
|
||||
present in the authorization response. This verification is done
|
||||
automatically if using the ``authorization_response`` parameter
|
||||
with ``prepare_token_request``.
|
||||
|
||||
not provided. The generated state is available via the ``state``
|
||||
attribute. Clients should verify that the state is unchanged and
|
||||
present in the authorization response. This verification is done
|
||||
automatically if using the ``authorization_response`` parameter
|
||||
with ``prepare_token_request``.
|
||||
:param redirect_url: Redirect URL to which the user will be returned
|
||||
after authorization. Must be provided unless previously setup with
|
||||
the provider. If provided then it must also be provided in the
|
||||
token request.
|
||||
|
||||
after authorization. Must be provided unless previously setup with
|
||||
the provider. If provided then it must also be provided in the
|
||||
token request.
|
||||
:param scope: List of scopes to request. Must be equal to
|
||||
or a subset of the scopes granted when obtaining the refresh
|
||||
token. If none is provided, the ones provided in the constructor are
|
||||
used.
|
||||
|
||||
or a subset of the scopes granted when obtaining the refresh
|
||||
token. If none is provided, the ones provided in the constructor are
|
||||
used.
|
||||
:param kwargs: Additional parameters to included in the request.
|
||||
|
||||
:returns: The prepared request tuple with (url, headers, body).
|
||||
"""
|
||||
if not is_secure_transport(authorization_url):
|
||||
|
@ -271,22 +266,16 @@ class Client:
|
|||
credentials.
|
||||
|
||||
:param token_url: Provider token creation endpoint URL.
|
||||
|
||||
:param authorization_response: The full redirection URL string, i.e.
|
||||
the location to which the user was redirected after successfull
|
||||
authorization. Used to mine credentials needed to obtain a token
|
||||
in this step, such as authorization code.
|
||||
|
||||
the location to which the user was redirected after successful
|
||||
authorization. Used to mine credentials needed to obtain a token
|
||||
in this step, such as authorization code.
|
||||
:param redirect_url: The redirect_url supplied with the authorization
|
||||
request (if there was one).
|
||||
|
||||
request (if there was one).
|
||||
:param state:
|
||||
|
||||
:param body: Existing request body (URL encoded string) to embed parameters
|
||||
into. This may contain extra paramters. Default ''.
|
||||
|
||||
into. This may contain extra parameters. Default ''.
|
||||
:param kwargs: Additional parameters to included in the request.
|
||||
|
||||
:returns: The prepared request tuple with (url, headers, body).
|
||||
"""
|
||||
if not is_secure_transport(token_url):
|
||||
|
@ -312,19 +301,14 @@ class Client:
|
|||
obtain a new access token, and possibly a new refresh token.
|
||||
|
||||
:param token_url: Provider token refresh endpoint URL.
|
||||
|
||||
:param refresh_token: Refresh token string.
|
||||
|
||||
:param body: Existing request body (URL encoded string) to embed parameters
|
||||
into. This may contain extra paramters. Default ''.
|
||||
|
||||
into. This may contain extra parameters. Default ''.
|
||||
:param scope: List of scopes to request. Must be equal to
|
||||
or a subset of the scopes granted when obtaining the refresh
|
||||
token. If none is provided, the ones provided in the constructor are
|
||||
used.
|
||||
|
||||
or a subset of the scopes granted when obtaining the refresh
|
||||
token. If none is provided, the ones provided in the constructor are
|
||||
used.
|
||||
:param kwargs: Additional parameters to included in the request.
|
||||
|
||||
:returns: The prepared request tuple with (url, headers, body).
|
||||
"""
|
||||
if not is_secure_transport(token_url):
|
||||
|
@ -341,20 +325,14 @@ class Client:
|
|||
"""Prepare a token revocation request.
|
||||
|
||||
:param revocation_url: Provider token revocation endpoint URL.
|
||||
|
||||
:param token: The access or refresh token to be revoked (string).
|
||||
|
||||
:param token_type_hint: ``"access_token"`` (default) or
|
||||
``"refresh_token"``. This is optional and if you wish to not pass it you
|
||||
must provide ``token_type_hint=None``.
|
||||
|
||||
``"refresh_token"``. This is optional and if you wish to not pass it you
|
||||
must provide ``token_type_hint=None``.
|
||||
:param body:
|
||||
|
||||
:param callback: A jsonp callback such as ``package.callback`` to be invoked
|
||||
upon receiving the response. Not that it should not include a () suffix.
|
||||
|
||||
upon receiving the response. Not that it should not include a () suffix.
|
||||
:param kwargs: Additional parameters to included in the request.
|
||||
|
||||
:returns: The prepared request tuple with (url, headers, body).
|
||||
|
||||
Note that JSONP request may use GET requests as the parameters will
|
||||
|
@ -362,7 +340,7 @@ class Client:
|
|||
|
||||
An example of a revocation request
|
||||
|
||||
.. code-block: http
|
||||
.. code-block:: http
|
||||
|
||||
POST /revoke HTTP/1.1
|
||||
Host: server.example.com
|
||||
|
@ -373,7 +351,7 @@ class Client:
|
|||
|
||||
An example of a jsonp revocation request
|
||||
|
||||
.. code-block: http
|
||||
.. code-block:: http
|
||||
|
||||
GET /revoke?token=agabcdefddddafdd&callback=package.myCallback HTTP/1.1
|
||||
Host: server.example.com
|
||||
|
@ -382,9 +360,9 @@ class Client:
|
|||
|
||||
and an error response
|
||||
|
||||
.. code-block: http
|
||||
.. code-block:: javascript
|
||||
|
||||
package.myCallback({"error":"unsupported_token_type"});
|
||||
package.myCallback({"error":"unsupported_token_type"});
|
||||
|
||||
Note that these requests usually require client credentials, client_id in
|
||||
the case for public clients and provider specific authentication
|
||||
|
@ -408,9 +386,10 @@ class Client:
|
|||
|
||||
:param body: The response body from the token request.
|
||||
:param scope: Scopes originally requested. If none is provided, the ones
|
||||
provided in the constructor are used.
|
||||
provided in the constructor are used.
|
||||
:return: Dictionary of token parameters.
|
||||
:raises: Warning if scope has changed. OAuth2Error if response is invalid.
|
||||
:raises: Warning if scope has changed. :py:class:`oauthlib.oauth2.errors.OAuth2Error`
|
||||
if response is invalid.
|
||||
|
||||
These response are json encoded and could easily be parsed without
|
||||
the assistance of OAuthLib. However, there are a few subtle issues
|
||||
|
@ -436,7 +415,7 @@ class Client:
|
|||
If omitted, the authorization server SHOULD provide the
|
||||
expiration time via other means or document the default value.
|
||||
|
||||
**scope**
|
||||
**scope**
|
||||
Providers may supply this in all responses but are required to only
|
||||
if it has changed since the authorization request.
|
||||
|
||||
|
@ -454,20 +433,16 @@ class Client:
|
|||
|
||||
If the authorization server issued a refresh token to the client, the
|
||||
client makes a refresh request to the token endpoint by adding the
|
||||
following parameters using the "application/x-www-form-urlencoded"
|
||||
following parameters using the `application/x-www-form-urlencoded`
|
||||
format in the HTTP request entity-body:
|
||||
|
||||
grant_type
|
||||
REQUIRED. Value MUST be set to "refresh_token".
|
||||
refresh_token
|
||||
REQUIRED. The refresh token issued to the client.
|
||||
scope
|
||||
OPTIONAL. The scope of the access request as described by
|
||||
Section 3.3. The requested scope MUST NOT include any scope
|
||||
not originally granted by the resource owner, and if omitted is
|
||||
treated as equal to the scope originally granted by the
|
||||
resource owner. Note that if none is provided, the ones provided
|
||||
in the constructor are used if any.
|
||||
:param refresh_token: REQUIRED. The refresh token issued to the client.
|
||||
:param scope: OPTIONAL. The scope of the access request as described by
|
||||
Section 3.3. The requested scope MUST NOT include any scope
|
||||
not originally granted by the resource owner, and if omitted is
|
||||
treated as equal to the scope originally granted by the
|
||||
resource owner. Note that if none is provided, the ones provided
|
||||
in the constructor are used if any.
|
||||
"""
|
||||
refresh_token = refresh_token or self.refresh_token
|
||||
scope = self.scope if scope is None else scope
|
||||
|
@ -492,18 +467,21 @@ class Client:
|
|||
|
||||
def create_code_verifier(self, length):
|
||||
"""Create PKCE **code_verifier** used in computing **code_challenge**.
|
||||
See `RFC7636 Section 4.1`_
|
||||
|
||||
:param length: REQUIRED. The length of the code_verifier.
|
||||
:param length: REQUIRED. The length of the code_verifier.
|
||||
|
||||
The client first creates a code verifier, "code_verifier", for each
|
||||
OAuth 2.0 [RFC6749] Authorization Request, in the following manner:
|
||||
The client first creates a code verifier, "code_verifier", for each
|
||||
OAuth 2.0 [RFC6749] Authorization Request, in the following manner:
|
||||
|
||||
code_verifier = high-entropy cryptographic random STRING using the
|
||||
unreserved characters [A-Z] / [a-z] / [0-9] / "-" / "." / "_" / "~"
|
||||
from Section 2.3 of [RFC3986], with a minimum length of 43 characters
|
||||
and a maximum length of 128 characters.
|
||||
|
||||
.. _`Section 4.1`: https://tools.ietf.org/html/rfc7636#section-4.1
|
||||
.. code-block:: text
|
||||
|
||||
code_verifier = high-entropy cryptographic random STRING using the
|
||||
unreserved characters [A-Z] / [a-z] / [0-9] / "-" / "." / "_" / "~"
|
||||
from Section 2.3 of [RFC3986], with a minimum length of 43 characters
|
||||
and a maximum length of 128 characters.
|
||||
|
||||
.. _`RFC7636 Section 4.1`: https://tools.ietf.org/html/rfc7636#section-4.1
|
||||
"""
|
||||
code_verifier = None
|
||||
|
||||
|
@ -525,33 +503,30 @@ class Client:
|
|||
|
||||
def create_code_challenge(self, code_verifier, code_challenge_method=None):
|
||||
"""Create PKCE **code_challenge** derived from the **code_verifier**.
|
||||
See `RFC7636 Section 4.2`_
|
||||
|
||||
:param code_verifier: REQUIRED. The **code_verifier** generated from create_code_verifier().
|
||||
:param code_challenge_method: OPTIONAL. The method used to derive the **code_challenge**. Acceptable
|
||||
values include "S256". DEFAULT is "plain".
|
||||
:param code_verifier: REQUIRED. The **code_verifier** generated from `create_code_verifier()`.
|
||||
:param code_challenge_method: OPTIONAL. The method used to derive the **code_challenge**. Acceptable values include `S256`. DEFAULT is `plain`.
|
||||
|
||||
|
||||
The client then creates a code challenge derived from the code
|
||||
The client then creates a code challenge derived from the code
|
||||
verifier by using one of the following transformations on the code
|
||||
verifier:
|
||||
verifier::
|
||||
|
||||
plain
|
||||
code_challenge = code_verifier
|
||||
plain
|
||||
code_challenge = code_verifier
|
||||
S256
|
||||
code_challenge = BASE64URL-ENCODE(SHA256(ASCII(code_verifier)))
|
||||
|
||||
S256
|
||||
code_challenge = BASE64URL-ENCODE(SHA256(ASCII(code_verifier)))
|
||||
|
||||
If the client is capable of using "S256", it MUST use "S256", as
|
||||
"S256" is Mandatory To Implement (MTI) on the server. Clients are
|
||||
permitted to use "plain" only if they cannot support "S256" for some
|
||||
If the client is capable of using `S256`, it MUST use `S256`, as
|
||||
`S256` is Mandatory To Implement (MTI) on the server. Clients are
|
||||
permitted to use `plain` only if they cannot support `S256` for some
|
||||
technical reason and know via out-of-band configuration that the
|
||||
server supports "plain".
|
||||
server supports `plain`.
|
||||
|
||||
The plain transformation is for compatibility with existing
|
||||
deployments and for constrained environments that can't use the S256
|
||||
transformation.
|
||||
deployments and for constrained environments that can't use the S256 transformation.
|
||||
|
||||
.. _`Section 4.2`: https://tools.ietf.org/html/rfc7636#section-4.2
|
||||
.. _`RFC7636 Section 4.2`: https://tools.ietf.org/html/rfc7636#section-4.2
|
||||
"""
|
||||
code_challenge = None
|
||||
|
||||
|
|
|
@ -49,7 +49,7 @@ class LegacyApplicationClient(Client):
|
|||
:param username: The resource owner username.
|
||||
:param password: The resource owner password.
|
||||
:param body: Existing request body (URL encoded string) to embed parameters
|
||||
into. This may contain extra paramters. Default ''.
|
||||
into. This may contain extra parameters. Default ''.
|
||||
:param scope: The scope of the access request as described by
|
||||
`Section 3.3`_.
|
||||
:param include_client_id: `True` to send the `client_id` in the
|
||||
|
|
|
@ -55,7 +55,7 @@ class MobileApplicationClient(Client):
|
|||
using the "application/x-www-form-urlencoded" format, per `Appendix B`_:
|
||||
|
||||
:param redirect_uri: OPTIONAL. The redirect URI must be an absolute URI
|
||||
and it should have been registerd with the OAuth
|
||||
and it should have been registered with the OAuth
|
||||
provider prior to use. As described in `Section 3.1.2`_.
|
||||
|
||||
:param scope: OPTIONAL. The scope of the access request as described by
|
||||
|
|
|
@ -31,7 +31,7 @@ class ServiceApplicationClient(Client):
|
|||
|
||||
def __init__(self, client_id, private_key=None, subject=None, issuer=None,
|
||||
audience=None, **kwargs):
|
||||
"""Initalize a JWT client with defaults for implicit use later.
|
||||
"""Initialize a JWT client with defaults for implicit use later.
|
||||
|
||||
:param client_id: Client identifier given by the OAuth provider upon
|
||||
registration.
|
||||
|
@ -99,7 +99,7 @@ class ServiceApplicationClient(Client):
|
|||
:param extra_claims: A dict of additional claims to include in the JWT.
|
||||
|
||||
:param body: Existing request body (URL encoded string) to embed parameters
|
||||
into. This may contain extra paramters. Default ''.
|
||||
into. This may contain extra parameters. Default ''.
|
||||
|
||||
:param scope: The scope of the access request.
|
||||
|
||||
|
|
|
@ -49,7 +49,7 @@ class WebApplicationClient(Client):
|
|||
using the "application/x-www-form-urlencoded" format, per `Appendix B`_:
|
||||
|
||||
:param redirect_uri: OPTIONAL. The redirect URI must be an absolute URI
|
||||
and it should have been registerd with the OAuth
|
||||
and it should have been registered with the OAuth
|
||||
provider prior to use. As described in `Section 3.1.2`_.
|
||||
|
||||
:param scope: OPTIONAL. The scope of the access request as described by
|
||||
|
@ -117,7 +117,7 @@ class WebApplicationClient(Client):
|
|||
values MUST be identical.
|
||||
|
||||
:param body: Existing request body (URL encoded string) to embed parameters
|
||||
into. This may contain extra paramters. Default ''.
|
||||
into. This may contain extra parameters. Default ''.
|
||||
|
||||
:param include_client_id: `True` (default) to send the `client_id` in the
|
||||
body of the upstream request. This is required
|
||||
|
|
|
@ -86,9 +86,9 @@ class IntrospectEndpoint(BaseEndpoint):
|
|||
an HTTP POST request with parameters sent as
|
||||
"application/x-www-form-urlencoded".
|
||||
|
||||
token REQUIRED. The string value of the token.
|
||||
* token REQUIRED. The string value of the token.
|
||||
* token_type_hint OPTIONAL.
|
||||
|
||||
token_type_hint OPTIONAL.
|
||||
A hint about the type of the token submitted for
|
||||
introspection. The protected resource MAY pass this parameter to
|
||||
help the authorization server optimize the token lookup. If the
|
||||
|
@ -96,11 +96,9 @@ class IntrospectEndpoint(BaseEndpoint):
|
|||
extend its search across all of its supported token types. An
|
||||
authorization server MAY ignore this parameter, particularly if it
|
||||
is able to detect the token type automatically.
|
||||
* access_token: An Access Token as defined in [`RFC6749`],
|
||||
`section 1.4`_
|
||||
|
||||
* refresh_token: A Refresh Token as defined in [`RFC6749`],
|
||||
`section 1.5`_
|
||||
* access_token: An Access Token as defined in [`RFC6749`], `section 1.4`_
|
||||
* refresh_token: A Refresh Token as defined in [`RFC6749`], `section 1.5`_
|
||||
|
||||
The introspection endpoint MAY accept other OPTIONAL
|
||||
parameters to provide further context to the query. For
|
||||
|
|
|
@ -10,7 +10,7 @@ import copy
|
|||
import json
|
||||
import logging
|
||||
|
||||
from .. import grant_types
|
||||
from .. import grant_types, utils
|
||||
from .authorization import AuthorizationEndpoint
|
||||
from .base import BaseEndpoint, catch_errors_and_unavailability
|
||||
from .introspect import IntrospectEndpoint
|
||||
|
@ -68,7 +68,7 @@ class MetadataEndpoint(BaseEndpoint):
|
|||
raise ValueError("key {} is a mandatory metadata.".format(key))
|
||||
|
||||
elif is_issuer:
|
||||
if not array[key].startswith("https"):
|
||||
if not utils.is_secure_transport(array[key]):
|
||||
raise ValueError("key {}: {} must be an HTTPS URL".format(key, array[key]))
|
||||
if "?" in array[key] or "&" in array[key] or "#" in array[key]:
|
||||
raise ValueError("key {}: {} must not contain query or fragment components".format(key, array[key]))
|
||||
|
|
|
@ -42,7 +42,7 @@ class RevocationEndpoint(BaseEndpoint):
|
|||
|
||||
|
||||
The authorization server responds with HTTP status code 200 if the
|
||||
token has been revoked sucessfully or if the client submitted an
|
||||
token has been revoked successfully or if the client submitted an
|
||||
invalid token.
|
||||
|
||||
Note: invalid tokens do not cause an error response since the client
|
||||
|
@ -95,7 +95,7 @@ class RevocationEndpoint(BaseEndpoint):
|
|||
submitted for revocation. Clients MAY pass this parameter in order to
|
||||
help the authorization server to optimize the token lookup. If the
|
||||
server is unable to locate the token using the given hint, it MUST
|
||||
extend its search accross all of its supported token types. An
|
||||
extend its search across all of its supported token types. An
|
||||
authorization server MAY ignore this parameter, particularly if it is
|
||||
able to detect the token type automatically. This specification
|
||||
defines two such values:
|
||||
|
|
|
@ -10,7 +10,6 @@ import logging
|
|||
from oauthlib import common
|
||||
|
||||
from .. import errors
|
||||
from ..utils import is_secure_transport
|
||||
from .base import GrantTypeBase
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
@ -547,20 +546,3 @@ class AuthorizationCodeGrant(GrantTypeBase):
|
|||
if challenge_method in self._code_challenge_methods:
|
||||
return self._code_challenge_methods[challenge_method](verifier, challenge)
|
||||
raise NotImplementedError('Unknown challenge_method %s' % challenge_method)
|
||||
|
||||
def _create_cors_headers(self, request):
|
||||
"""If CORS is allowed, create the appropriate headers."""
|
||||
if 'origin' not in request.headers:
|
||||
return {}
|
||||
|
||||
origin = request.headers['origin']
|
||||
if not is_secure_transport(origin):
|
||||
log.debug('Origin "%s" is not HTTPS, CORS not allowed.', origin)
|
||||
return {}
|
||||
elif not self.request_validator.is_origin_allowed(
|
||||
request.client_id, origin, request):
|
||||
log.debug('Invalid origin "%s", CORS not allowed.', origin)
|
||||
return {}
|
||||
else:
|
||||
log.debug('Valid origin "%s", injecting CORS headers.', origin)
|
||||
return {'Access-Control-Allow-Origin': origin}
|
||||
|
|
|
@ -10,6 +10,7 @@ from oauthlib.oauth2.rfc6749 import errors, utils
|
|||
from oauthlib.uri_validate import is_absolute_uri
|
||||
|
||||
from ..request_validator import RequestValidator
|
||||
from ..utils import is_secure_transport
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
@ -248,3 +249,20 @@ class GrantTypeBase:
|
|||
raise errors.MissingRedirectURIError(request=request)
|
||||
if not is_absolute_uri(request.redirect_uri):
|
||||
raise errors.InvalidRedirectURIError(request=request)
|
||||
|
||||
def _create_cors_headers(self, request):
|
||||
"""If CORS is allowed, create the appropriate headers."""
|
||||
if 'origin' not in request.headers:
|
||||
return {}
|
||||
|
||||
origin = request.headers['origin']
|
||||
if not is_secure_transport(origin):
|
||||
log.debug('Origin "%s" is not HTTPS, CORS not allowed.', origin)
|
||||
return {}
|
||||
elif not self.request_validator.is_origin_allowed(
|
||||
request.client_id, origin, request):
|
||||
log.debug('Invalid origin "%s", CORS not allowed.', origin)
|
||||
return {}
|
||||
else:
|
||||
log.debug('Valid origin "%s", injecting CORS headers.', origin)
|
||||
return {'Access-Control-Allow-Origin': origin}
|
||||
|
|
|
@ -69,6 +69,7 @@ class RefreshTokenGrant(GrantTypeBase):
|
|||
|
||||
log.debug('Issuing new token to client id %r (%r), %r.',
|
||||
request.client_id, request.client, token)
|
||||
headers.update(self._create_cors_headers(request))
|
||||
return headers, json.dumps(token), 200
|
||||
|
||||
def validate_token_request(self, request):
|
||||
|
|
|
@ -45,7 +45,7 @@ def prepare_grant_uri(uri, client_id, response_type, redirect_uri=None,
|
|||
back to the client. The parameter SHOULD be used for
|
||||
preventing cross-site request forgery as described in
|
||||
`Section 10.12`_.
|
||||
:param code_challenge: PKCE paramater. A challenge derived from the
|
||||
:param code_challenge: PKCE parameter. A challenge derived from the
|
||||
code_verifier that is sent in the authorization
|
||||
request, to be verified against later.
|
||||
:param code_challenge_method: PKCE parameter. A method that was used to derive the
|
||||
|
|
|
@ -191,6 +191,7 @@ class RequestValidator:
|
|||
claims associated, or `None` in case the token is unknown.
|
||||
|
||||
Below the list of registered claims you should be interested in:
|
||||
|
||||
- scope : space-separated list of scopes
|
||||
- client_id : client identifier
|
||||
- username : human-readable identifier for the resource owner
|
||||
|
@ -204,10 +205,10 @@ class RequestValidator:
|
|||
- jti : string identifier for the token
|
||||
|
||||
Note that most of them are coming directly from JWT RFC. More details
|
||||
can be found in `Introspect Claims`_ or `_JWT Claims`_.
|
||||
can be found in `Introspect Claims`_ or `JWT Claims`_.
|
||||
|
||||
The implementation can use *token_type_hint* to improve lookup
|
||||
efficency, but must fallback to other types to be compliant with RFC.
|
||||
efficiency, but must fallback to other types to be compliant with RFC.
|
||||
|
||||
The dict of claims is added to request.token after this method.
|
||||
|
||||
|
@ -443,6 +444,7 @@ class RequestValidator:
|
|||
- request.user
|
||||
- request.scopes
|
||||
- request.claims (if given)
|
||||
|
||||
OBS! The request.user attribute should be set to the resource owner
|
||||
associated with this authorization code. Similarly request.scopes
|
||||
must also be set.
|
||||
|
@ -451,6 +453,7 @@ class RequestValidator:
|
|||
|
||||
If PKCE is enabled (see 'is_pkce_required' and 'save_authorization_code')
|
||||
you MUST set the following based on the information stored:
|
||||
|
||||
- request.code_challenge
|
||||
- request.code_challenge_method
|
||||
|
||||
|
@ -561,7 +564,7 @@ class RequestValidator:
|
|||
OBS! The validation should also set the user attribute of the request
|
||||
to a valid resource owner, i.e. request.user = username or similar. If
|
||||
not set you will be unable to associate a token with a user in the
|
||||
persistance method used (commonly, save_bearer_token).
|
||||
persistence method used (commonly, save_bearer_token).
|
||||
|
||||
:param username: Unicode username.
|
||||
:param password: Unicode password.
|
||||
|
@ -671,6 +674,7 @@ class RequestValidator:
|
|||
|
||||
Method is used by:
|
||||
- Authorization Code Grant
|
||||
- Refresh Token Grant
|
||||
|
||||
"""
|
||||
return False
|
||||
|
|
|
@ -257,6 +257,7 @@ def get_token_from_header(request):
|
|||
|
||||
|
||||
class TokenBase:
|
||||
__slots__ = ()
|
||||
|
||||
def __call__(self, request, refresh_token=False):
|
||||
raise NotImplementedError('Subclasses must implement this method.')
|
||||
|
|
|
@ -5,12 +5,11 @@ oauthlib.oauth2.rfc8628
|
|||
This module is an implementation of various logic needed
|
||||
for consuming and providing OAuth 2.0 Device Authorization RFC8628.
|
||||
"""
|
||||
|
||||
from oauthlib.common import add_params_to_uri
|
||||
from oauthlib.oauth2 import BackendApplicationClient, Client
|
||||
from oauthlib.oauth2.rfc6749.errors import InsecureTransportError
|
||||
from oauthlib.oauth2.rfc6749.parameters import prepare_token_request
|
||||
from oauthlib.oauth2.rfc6749.utils import is_secure_transport, list_to_scope
|
||||
from oauthlib.common import add_params_to_uri
|
||||
|
||||
|
||||
class DeviceClient(Client):
|
||||
|
@ -62,7 +61,7 @@ class DeviceClient(Client):
|
|||
body.
|
||||
|
||||
:param body: Existing request body (URL encoded string) to embed parameters
|
||||
into. This may contain extra paramters. Default ''.
|
||||
into. This may contain extra parameters. Default ''.
|
||||
:param scope: The scope of the access request as described by
|
||||
`Section 3.3`_.
|
||||
|
||||
|
@ -84,6 +83,8 @@ class DeviceClient(Client):
|
|||
>>> client.prepare_request_body(scope=['hello', 'world'])
|
||||
'grant_type=urn:ietf:params:oauth:grant-type:device_code&scope=hello+world'
|
||||
|
||||
.. _`Section 3.2.1`: https://datatracker.ietf.org/doc/html/rfc6749#section-3.2.1
|
||||
.. _`Section 3.3`: https://datatracker.ietf.org/doc/html/rfc6749#section-3.3
|
||||
.. _`Section 3.4`: https://datatracker.ietf.org/doc/html/rfc8628#section-3.4
|
||||
"""
|
||||
|
||||
|
|
|
@ -69,7 +69,7 @@ class UserInfoEndpoint(BaseEndpoint):
|
|||
5.3.1. UserInfo Request
|
||||
The Client sends the UserInfo Request using either HTTP GET or HTTP
|
||||
POST. The Access Token obtained from an OpenID Connect Authentication
|
||||
Request MUST be sent as a Bearer Token, per Section 2 of OAuth 2.0
|
||||
Request MUST be sent as a Bearer Token, per `Section 2`_ of OAuth 2.0
|
||||
Bearer Token Usage [RFC6750].
|
||||
|
||||
It is RECOMMENDED that the request use the HTTP GET method and the
|
||||
|
@ -77,21 +77,28 @@ class UserInfoEndpoint(BaseEndpoint):
|
|||
|
||||
The following is a non-normative example of a UserInfo Request:
|
||||
|
||||
GET /userinfo HTTP/1.1
|
||||
Host: server.example.com
|
||||
Authorization: Bearer SlAV32hkKG
|
||||
.. code-block:: http
|
||||
|
||||
GET /userinfo HTTP/1.1
|
||||
Host: server.example.com
|
||||
Authorization: Bearer SlAV32hkKG
|
||||
|
||||
5.3.3. UserInfo Error Response
|
||||
When an error condition occurs, the UserInfo Endpoint returns an Error
|
||||
Response as defined in Section 3 of OAuth 2.0 Bearer Token Usage
|
||||
Response as defined in `Section 3`_ of OAuth 2.0 Bearer Token Usage
|
||||
[RFC6750]. (HTTP errors unrelated to RFC 6750 are returned to the User
|
||||
Agent using the appropriate HTTP status code.)
|
||||
|
||||
The following is a non-normative example of a UserInfo Error Response:
|
||||
|
||||
HTTP/1.1 401 Unauthorized
|
||||
WWW-Authenticate: Bearer error="invalid_token",
|
||||
.. code-block:: http
|
||||
|
||||
HTTP/1.1 401 Unauthorized
|
||||
WWW-Authenticate: Bearer error="invalid_token",
|
||||
error_description="The Access Token expired"
|
||||
|
||||
.. _`Section 2`: https://datatracker.ietf.org/doc/html/rfc6750#section-2
|
||||
.. _`Section 3`: https://datatracker.ietf.org/doc/html/rfc6750#section-3
|
||||
"""
|
||||
if not self.bearer.validate_request(request):
|
||||
raise errors.InvalidTokenError()
|
||||
|
|
|
@ -8,7 +8,6 @@ from oauthlib.oauth2.rfc6749.errors import (
|
|||
ConsentRequired, InvalidRequestError, LoginRequired,
|
||||
)
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
|
|
@ -84,7 +84,7 @@ class AuthorizationTokenGrantDispatcher(Dispatcher):
|
|||
code = parameters.get('code', None)
|
||||
redirect_uri = parameters.get('redirect_uri', None)
|
||||
|
||||
# If code is not pressent fallback to `default_grant` which will
|
||||
# If code is not present fallback to `default_grant` which will
|
||||
# raise an error for the missing `code` in `create_token_response` step.
|
||||
if code:
|
||||
scopes = self.request_validator.get_authorization_code_scopes(client_id, code, redirect_uri, request)
|
||||
|
|
|
@ -4,7 +4,9 @@ authlib.openid.connect.core.tokens
|
|||
|
||||
This module contains methods for adding JWT tokens to requests.
|
||||
"""
|
||||
from oauthlib.oauth2.rfc6749.tokens import TokenBase, random_token_generator, get_token_from_header
|
||||
from oauthlib.oauth2.rfc6749.tokens import (
|
||||
TokenBase, get_token_from_header, random_token_generator,
|
||||
)
|
||||
|
||||
|
||||
class JWTToken(TokenBase):
|
||||
|
|
|
@ -66,7 +66,7 @@ IPv4address = r"%(dec_octet)s \. %(dec_octet)s \. %(dec_octet)s \. %(dec_octet)s
|
|||
)
|
||||
|
||||
# IPv6address
|
||||
IPv6address = r"([A-Fa-f0-9:]+:+)+[A-Fa-f0-9]+"
|
||||
IPv6address = r"([A-Fa-f0-9:]+[:$])[A-Fa-f0-9]{1,4}"
|
||||
|
||||
# IPvFuture = "v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" )
|
||||
IPvFuture = r"v %(HEXDIG)s+ \. (?: %(unreserved)s | %(sub_delims)s | : )+" % locals()
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# ruff: noqa: F401
|
||||
import logging
|
||||
|
||||
from .oauth1_auth import OAuth1
|
||||
|
@ -5,7 +6,7 @@ from .oauth1_session import OAuth1Session
|
|||
from .oauth2_auth import OAuth2
|
||||
from .oauth2_session import OAuth2Session, TokenUpdated
|
||||
|
||||
__version__ = "1.3.1"
|
||||
__version__ = "2.0.0"
|
||||
|
||||
import requests
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
# ruff: noqa: F401
|
||||
from .facebook import facebook_compliance_fix
|
||||
from .fitbit import fitbit_compliance_fix
|
||||
from .slack import slack_compliance_fix
|
||||
|
|
|
@ -1,14 +1,12 @@
|
|||
import json
|
||||
|
||||
from oauthlib.common import to_unicode
|
||||
|
||||
|
||||
def douban_compliance_fix(session):
|
||||
def fix_token_type(r):
|
||||
token = json.loads(r.text)
|
||||
token.setdefault("token_type", "Bearer")
|
||||
fixed_token = json.dumps(token)
|
||||
r._content = to_unicode(fixed_token).encode("utf-8")
|
||||
r._content = fixed_token.encode()
|
||||
return r
|
||||
|
||||
session._client_default_token_placement = "query"
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import json
|
||||
from oauthlib.common import to_unicode
|
||||
|
||||
|
||||
def ebay_compliance_fix(session):
|
||||
|
@ -13,7 +12,7 @@ def ebay_compliance_fix(session):
|
|||
if token.get("token_type") in ["Application Access Token", "User Access Token"]:
|
||||
token["token_type"] = "Bearer"
|
||||
fixed_token = json.dumps(token)
|
||||
response._content = to_unicode(fixed_token).encode("utf-8")
|
||||
response._content = fixed_token.encode()
|
||||
|
||||
return response
|
||||
|
||||
|
|
|
@ -1,11 +1,5 @@
|
|||
from json import dumps
|
||||
|
||||
try:
|
||||
from urlparse import parse_qsl
|
||||
except ImportError:
|
||||
from urllib.parse import parse_qsl
|
||||
|
||||
from oauthlib.common import to_unicode
|
||||
from urllib.parse import parse_qsl
|
||||
|
||||
|
||||
def facebook_compliance_fix(session):
|
||||
|
@ -26,7 +20,7 @@ def facebook_compliance_fix(session):
|
|||
if expires is not None:
|
||||
token["expires_in"] = expires
|
||||
token["token_type"] = "Bearer"
|
||||
r._content = to_unicode(dumps(token)).encode("UTF-8")
|
||||
r._content = dumps(token).encode()
|
||||
return r
|
||||
|
||||
session.register_compliance_hook("access_token_response", _compliance_fix)
|
||||
|
|
|
@ -8,8 +8,6 @@ MissingTokenError.
|
|||
|
||||
from json import loads, dumps
|
||||
|
||||
from oauthlib.common import to_unicode
|
||||
|
||||
|
||||
def fitbit_compliance_fix(session):
|
||||
def _missing_error(r):
|
||||
|
@ -17,7 +15,7 @@ def fitbit_compliance_fix(session):
|
|||
if "errors" in token:
|
||||
# Set the error to the first one we have
|
||||
token["error"] = token["errors"][0]["errorType"]
|
||||
r._content = to_unicode(dumps(token)).encode("UTF-8")
|
||||
r._content = dumps(token).encode()
|
||||
return r
|
||||
|
||||
session.register_compliance_hook("access_token_response", _missing_error)
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
try:
|
||||
from urlparse import urlparse, parse_qs
|
||||
except ImportError:
|
||||
from urllib.parse import urlparse, parse_qs
|
||||
from urllib.parse import urlparse, parse_qs
|
||||
|
||||
from oauthlib.common import add_params_to_uri
|
||||
|
||||
|
|
|
@ -1,21 +1,19 @@
|
|||
import json
|
||||
|
||||
from oauthlib.common import to_unicode
|
||||
|
||||
|
||||
def mailchimp_compliance_fix(session):
|
||||
def _null_scope(r):
|
||||
token = json.loads(r.text)
|
||||
if "scope" in token and token["scope"] is None:
|
||||
token.pop("scope")
|
||||
r._content = to_unicode(json.dumps(token)).encode("utf-8")
|
||||
r._content = json.dumps(token).encode()
|
||||
return r
|
||||
|
||||
def _non_zero_expiration(r):
|
||||
token = json.loads(r.text)
|
||||
if "expires_in" in token and token["expires_in"] == 0:
|
||||
token["expires_in"] = 3600
|
||||
r._content = to_unicode(json.dumps(token)).encode("utf-8")
|
||||
r._content = json.dumps(token).encode()
|
||||
return r
|
||||
|
||||
session.register_compliance_hook("access_token_response", _null_scope)
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
from json import dumps, loads
|
||||
import re
|
||||
|
||||
from oauthlib.common import to_unicode
|
||||
|
||||
|
||||
def plentymarkets_compliance_fix(session):
|
||||
def _to_snake_case(n):
|
||||
|
@ -22,7 +20,7 @@ def plentymarkets_compliance_fix(session):
|
|||
for k, v in token.items():
|
||||
fixed_token[_to_snake_case(k)] = v
|
||||
|
||||
r._content = to_unicode(dumps(fixed_token)).encode("UTF-8")
|
||||
r._content = dumps(fixed_token).encode()
|
||||
return r
|
||||
|
||||
session.register_compliance_hook("access_token_response", _compliance_fix)
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
try:
|
||||
from urlparse import urlparse, parse_qs
|
||||
except ImportError:
|
||||
from urllib.parse import urlparse, parse_qs
|
||||
from urllib.parse import urlparse, parse_qs
|
||||
|
||||
from oauthlib.common import add_params_to_uri
|
||||
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
from json import loads, dumps
|
||||
|
||||
from oauthlib.common import to_unicode
|
||||
|
||||
|
||||
def weibo_compliance_fix(session):
|
||||
def _missing_token_type(r):
|
||||
token = loads(r.text)
|
||||
token["token_type"] = "Bearer"
|
||||
r._content = to_unicode(dumps(token)).encode("UTF-8")
|
||||
r._content = dumps(token).encode()
|
||||
return r
|
||||
|
||||
session._client.default_token_placement = "query"
|
||||
|
|
|
@ -1,20 +1,15 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import logging
|
||||
|
||||
from oauthlib.common import extract_params
|
||||
from oauthlib.oauth1 import Client, SIGNATURE_HMAC, SIGNATURE_TYPE_AUTH_HEADER
|
||||
from oauthlib.oauth1 import SIGNATURE_TYPE_BODY
|
||||
from requests.compat import is_py3
|
||||
from requests.utils import to_native_string
|
||||
from requests.auth import AuthBase
|
||||
|
||||
CONTENT_TYPE_FORM_URLENCODED = "application/x-www-form-urlencoded"
|
||||
CONTENT_TYPE_MULTI_PART = "multipart/form-data"
|
||||
|
||||
if is_py3:
|
||||
unicode = str
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
@ -83,7 +78,7 @@ class OAuth1(AuthBase):
|
|||
or self.client.signature_type == SIGNATURE_TYPE_BODY
|
||||
):
|
||||
content_type = CONTENT_TYPE_FORM_URLENCODED
|
||||
if not isinstance(content_type, unicode):
|
||||
if not isinstance(content_type, str):
|
||||
content_type = content_type.decode("utf-8")
|
||||
|
||||
is_form_encoded = CONTENT_TYPE_FORM_URLENCODED in content_type
|
||||
|
@ -96,17 +91,17 @@ class OAuth1(AuthBase):
|
|||
if is_form_encoded:
|
||||
r.headers["Content-Type"] = CONTENT_TYPE_FORM_URLENCODED
|
||||
r.url, headers, r.body = self.client.sign(
|
||||
unicode(r.url), unicode(r.method), r.body or "", r.headers
|
||||
str(r.url), str(r.method), r.body or "", r.headers
|
||||
)
|
||||
elif self.force_include_body:
|
||||
# To allow custom clients to work on non form encoded bodies.
|
||||
r.url, headers, r.body = self.client.sign(
|
||||
unicode(r.url), unicode(r.method), r.body or "", r.headers
|
||||
str(r.url), str(r.method), r.body or "", r.headers
|
||||
)
|
||||
else:
|
||||
# Omit body data in the signing of non form-encoded requests
|
||||
r.url, headers, _ = self.client.sign(
|
||||
unicode(r.url), unicode(r.method), None, r.headers
|
||||
str(r.url), str(r.method), None, r.headers
|
||||
)
|
||||
|
||||
r.prepare_headers(headers)
|
||||
|
|
|
@ -1,9 +1,4 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
try:
|
||||
from urlparse import urlparse
|
||||
except ImportError:
|
||||
from urllib.parse import urlparse
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import logging
|
||||
|
||||
|
@ -85,7 +80,7 @@ class OAuth1Session(requests.Session):
|
|||
'https://api.twitter.com/oauth/authorize?oauth_token=sdf0o9823sjdfsdf&oauth_callback=https%3A%2F%2F127.0.0.1%2Fcallback'
|
||||
>>>
|
||||
>>> # Third step. Fetch the access token
|
||||
>>> redirect_response = raw_input('Paste the full redirect URL here.')
|
||||
>>> redirect_response = input('Paste the full redirect URL here.')
|
||||
>>> oauth_session.parse_authorization_response(redirect_response)
|
||||
{
|
||||
'oauth_token: 'kjerht2309u',
|
||||
|
@ -258,7 +253,7 @@ class OAuth1Session(requests.Session):
|
|||
return add_params_to_uri(url, kwargs.items())
|
||||
|
||||
def fetch_request_token(self, url, realm=None, **request_kwargs):
|
||||
r"""Fetch a request token.
|
||||
"""Fetch a request token.
|
||||
|
||||
This is the first step in the OAuth 1 workflow. A request token is
|
||||
obtained by making a signed post request to url. The token is then
|
||||
|
@ -267,7 +262,7 @@ class OAuth1Session(requests.Session):
|
|||
|
||||
:param url: The request token endpoint URL.
|
||||
:param realm: A list of realms to request access to.
|
||||
:param \*\*request_kwargs: Optional arguments passed to ''post''
|
||||
:param request_kwargs: Optional arguments passed to ''post''
|
||||
function in ''requests.Session''
|
||||
:returns: The response in dict format.
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
from __future__ import unicode_literals
|
||||
from oauthlib.oauth2 import WebApplicationClient, InsecureTransportError
|
||||
from oauthlib.oauth2 import is_secure_transport
|
||||
from requests.auth import AuthBase
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
import logging
|
||||
|
||||
from oauthlib.common import generate_token, urldecode
|
||||
|
@ -46,6 +44,7 @@ class OAuth2Session(requests.Session):
|
|||
token=None,
|
||||
state=None,
|
||||
token_updater=None,
|
||||
pkce=None,
|
||||
**kwargs
|
||||
):
|
||||
"""Construct a new OAuth 2 client session.
|
||||
|
@ -72,18 +71,23 @@ class OAuth2Session(requests.Session):
|
|||
set a TokenUpdated warning will be raised when a token
|
||||
has been refreshed. This warning will carry the token
|
||||
in its token argument.
|
||||
:param pkce: Set "S256" or "plain" to enable PKCE. Default is disabled.
|
||||
:param kwargs: Arguments to pass to the Session constructor.
|
||||
"""
|
||||
super(OAuth2Session, self).__init__(**kwargs)
|
||||
self._client = client or WebApplicationClient(client_id, token=token)
|
||||
self.token = token or {}
|
||||
self.scope = scope
|
||||
self._scope = scope
|
||||
self.redirect_uri = redirect_uri
|
||||
self.state = state or generate_token
|
||||
self._state = state
|
||||
self.auto_refresh_url = auto_refresh_url
|
||||
self.auto_refresh_kwargs = auto_refresh_kwargs or {}
|
||||
self.token_updater = token_updater
|
||||
self._pkce = pkce
|
||||
|
||||
if self._pkce not in ["S256", "plain", None]:
|
||||
raise AttributeError("Wrong value for {}(.., pkce={})".format(self.__class__, self._pkce))
|
||||
|
||||
# Ensure that requests doesn't do any automatic auth. See #278.
|
||||
# The default behavior can be re-enabled by setting auth to None.
|
||||
|
@ -95,8 +99,24 @@ class OAuth2Session(requests.Session):
|
|||
"access_token_response": set(),
|
||||
"refresh_token_response": set(),
|
||||
"protected_request": set(),
|
||||
"refresh_token_request": set(),
|
||||
"access_token_request": set(),
|
||||
}
|
||||
|
||||
@property
|
||||
def scope(self):
|
||||
"""By default the scope from the client is used, except if overridden"""
|
||||
if self._scope is not None:
|
||||
return self._scope
|
||||
elif self._client is not None:
|
||||
return self._client.scope
|
||||
else:
|
||||
return None
|
||||
|
||||
@scope.setter
|
||||
def scope(self, scope):
|
||||
self._scope = scope
|
||||
|
||||
def new_state(self):
|
||||
"""Generates a state string to be used in authorizations."""
|
||||
try:
|
||||
|
@ -161,6 +181,13 @@ class OAuth2Session(requests.Session):
|
|||
:return: authorization_url, state
|
||||
"""
|
||||
state = state or self.new_state()
|
||||
if self._pkce:
|
||||
self._code_verifier = self._client.create_code_verifier(43)
|
||||
kwargs["code_challenge_method"] = self._pkce
|
||||
kwargs["code_challenge"] = self._client.create_code_challenge(
|
||||
code_verifier=self._code_verifier,
|
||||
code_challenge_method=self._pkce
|
||||
)
|
||||
return (
|
||||
self._client.prepare_request_uri(
|
||||
url,
|
||||
|
@ -185,7 +212,7 @@ class OAuth2Session(requests.Session):
|
|||
force_querystring=False,
|
||||
timeout=None,
|
||||
headers=None,
|
||||
verify=True,
|
||||
verify=None,
|
||||
proxies=None,
|
||||
include_client_id=None,
|
||||
client_secret=None,
|
||||
|
@ -252,6 +279,13 @@ class OAuth2Session(requests.Session):
|
|||
"Please supply either code or " "authorization_response parameters."
|
||||
)
|
||||
|
||||
if self._pkce:
|
||||
if self._code_verifier is None:
|
||||
raise ValueError(
|
||||
"Code verifier is not found, authorization URL must be generated before"
|
||||
)
|
||||
kwargs["code_verifier"] = self._code_verifier
|
||||
|
||||
# Earlier versions of this library build an HTTPBasicAuth header out of
|
||||
# `username` and `password`. The RFC states, however these attributes
|
||||
# must be in the request body and not the header.
|
||||
|
@ -325,7 +359,7 @@ class OAuth2Session(requests.Session):
|
|||
|
||||
headers = headers or {
|
||||
"Accept": "application/json",
|
||||
"Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
}
|
||||
self.token = {}
|
||||
request_kwargs = {}
|
||||
|
@ -338,6 +372,12 @@ class OAuth2Session(requests.Session):
|
|||
else:
|
||||
raise ValueError("The method kwarg must be POST or GET.")
|
||||
|
||||
for hook in self.compliance_hook["access_token_request"]:
|
||||
log.debug("Invoking access_token_request hook %s.", hook)
|
||||
token_url, headers, request_kwargs = hook(
|
||||
token_url, headers, request_kwargs
|
||||
)
|
||||
|
||||
r = self.request(
|
||||
method=method,
|
||||
url=token_url,
|
||||
|
@ -388,7 +428,7 @@ class OAuth2Session(requests.Session):
|
|||
auth=None,
|
||||
timeout=None,
|
||||
headers=None,
|
||||
verify=True,
|
||||
verify=None,
|
||||
proxies=None,
|
||||
**kwargs
|
||||
):
|
||||
|
@ -426,9 +466,13 @@ class OAuth2Session(requests.Session):
|
|||
if headers is None:
|
||||
headers = {
|
||||
"Accept": "application/json",
|
||||
"Content-Type": ("application/x-www-form-urlencoded;charset=UTF-8"),
|
||||
"Content-Type": ("application/x-www-form-urlencoded"),
|
||||
}
|
||||
|
||||
for hook in self.compliance_hook["refresh_token_request"]:
|
||||
log.debug("Invoking refresh_token_request hook %s.", hook)
|
||||
token_url, headers, body = hook(token_url, headers, body)
|
||||
|
||||
r = self.post(
|
||||
token_url,
|
||||
data=dict(urldecode(body)),
|
||||
|
@ -450,7 +494,7 @@ class OAuth2Session(requests.Session):
|
|||
r = hook(r)
|
||||
|
||||
self.token = self._client.parse_request_body_response(r.text, scope=self.scope)
|
||||
if not "refresh_token" in self.token:
|
||||
if "refresh_token" not in self.token:
|
||||
log.debug("No new refresh token given. Re-using old.")
|
||||
self.token["refresh_token"] = refresh_token
|
||||
return self.token
|
||||
|
@ -464,6 +508,7 @@ class OAuth2Session(requests.Session):
|
|||
withhold_token=False,
|
||||
client_id=None,
|
||||
client_secret=None,
|
||||
files=None,
|
||||
**kwargs
|
||||
):
|
||||
"""Intercept all requests and add the OAuth 2 token if present."""
|
||||
|
@ -519,7 +564,7 @@ class OAuth2Session(requests.Session):
|
|||
log.debug("Supplying headers %s and data %s", headers, data)
|
||||
log.debug("Passing through key word arguments %s.", kwargs)
|
||||
return super(OAuth2Session, self).request(
|
||||
method, url, headers=headers, data=data, **kwargs
|
||||
method, url, headers=headers, data=data, files=files, **kwargs
|
||||
)
|
||||
|
||||
def register_compliance_hook(self, hook_type, hook):
|
||||
|
@ -529,6 +574,8 @@ class OAuth2Session(requests.Session):
|
|||
access_token_response invoked before token parsing.
|
||||
refresh_token_response invoked before refresh token parsing.
|
||||
protected_request invoked before making a request.
|
||||
access_token_request invoked before making a token fetch request.
|
||||
refresh_token_request invoked before making a refresh request.
|
||||
|
||||
If you find a new hook is needed please send a GitHub PR request
|
||||
or open an issue.
|
||||
|
|
|
@ -37,7 +37,7 @@ python-dateutil==2.9.0.post0
|
|||
python-twitter==3.5
|
||||
pytz==2024.1
|
||||
requests==2.31.0
|
||||
requests-oauthlib==1.3.1
|
||||
requests-oauthlib==2.0.0
|
||||
rumps==0.4.0; platform_system == "Darwin"
|
||||
simplejson==3.19.2
|
||||
six==1.16.0
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue