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