mirror of
https://github.com/Tautulli/Tautulli.git
synced 2025-08-20 13:23:24 -07:00
Merge branch 'nightly' into dependabot/pip/nightly/pyparsing-3.1.2
This commit is contained in:
commit
0e5f61c5ef
132 changed files with 7725 additions and 6087 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
|
||||||
|
|
||||||
if len(hostname) == 0:
|
|
||||||
raise ValueError('missing host') # error: netloc was ":port" or ":"
|
|
||||||
|
|
||||||
if len(port_str) == 0:
|
|
||||||
netloc = hostname # was "host:", so just use the host part
|
|
||||||
else:
|
|
||||||
try:
|
try:
|
||||||
port_num = int(port_str) # try to parse into an integer number
|
hostname = ipaddress.ip_address(hostname)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise ValueError('port is not an integer')
|
pass
|
||||||
|
|
||||||
if port_num <= 0 or 65535 < port_num:
|
if isinstance(hostname, ipaddress.IPv6Address):
|
||||||
|
hostname = f"[{hostname}]"
|
||||||
|
elif isinstance(hostname, ipaddress.IPv4Address):
|
||||||
|
hostname = f"{hostname}"
|
||||||
|
|
||||||
|
if port is not None and not (0 < port <= 65535):
|
||||||
raise ValueError('port out of range') # 16-bit unsigned ints
|
raise ValueError('port out of range') # 16-bit unsigned ints
|
||||||
if (scheme, port_num) in (('http', 80), ('https', 443)):
|
if (scheme, port) in (('http', 80), ('https', 443)):
|
||||||
netloc = hostname # default port for scheme: exclude port num
|
netloc = hostname # default port for scheme: exclude port num
|
||||||
|
elif port:
|
||||||
|
netloc = f"{hostname}:{port}" # use hostname:port
|
||||||
else:
|
else:
|
||||||
netloc = hostname + ':' + str(port_num) # use hostname:port
|
netloc = hostname
|
||||||
else:
|
|
||||||
# Does not contain a colon, so entire value must be the hostname
|
|
||||||
|
|
||||||
if len(netloc) == 0:
|
|
||||||
raise ValueError('missing host') # error: netloc was empty string
|
|
||||||
|
|
||||||
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,7 +360,7 @@ 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"});
|
||||||
|
|
||||||
|
@ -410,7 +388,8 @@ class Client:
|
||||||
: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
|
||||||
|
@ -454,15 +433,11 @@ 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
|
|
||||||
REQUIRED. The refresh token issued to the client.
|
|
||||||
scope
|
|
||||||
OPTIONAL. The scope of the access request as described by
|
|
||||||
Section 3.3. The requested scope MUST NOT include any scope
|
Section 3.3. The requested scope MUST NOT include any scope
|
||||||
not originally granted by the resource owner, and if omitted is
|
not originally granted by the resource owner, and if omitted is
|
||||||
treated as equal to the scope originally granted by the
|
treated as equal to the scope originally granted by the
|
||||||
|
@ -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-block:: text
|
||||||
|
|
||||||
code_verifier = high-entropy cryptographic random STRING using the
|
code_verifier = high-entropy cryptographic random STRING using the
|
||||||
unreserved characters [A-Z] / [a-z] / [0-9] / "-" / "." / "_" / "~"
|
unreserved characters [A-Z] / [a-z] / [0-9] / "-" / "." / "_" / "~"
|
||||||
from Section 2.3 of [RFC3986], with a minimum length of 43 characters
|
from Section 2.3 of [RFC3986], with a minimum length of 43 characters
|
||||||
and a maximum length of 128 characters.
|
and a maximum length of 128 characters.
|
||||||
|
|
||||||
.. _`Section 4.1`: https://tools.ietf.org/html/rfc7636#section-4.1
|
.. _`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
|
S256
|
||||||
code_challenge = BASE64URL-ENCODE(SHA256(ASCII(code_verifier)))
|
code_challenge = BASE64URL-ENCODE(SHA256(ASCII(code_verifier)))
|
||||||
|
|
||||||
If the client is capable of using "S256", it MUST use "S256", as
|
If the client is capable of using `S256`, it MUST use `S256`, as
|
||||||
"S256" is Mandatory To Implement (MTI) on the server. Clients are
|
`S256` is Mandatory To Implement (MTI) on the server. Clients are
|
||||||
permitted to use "plain" only if they cannot support "S256" for some
|
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:
|
||||||
|
|
||||||
|
.. code-block:: http
|
||||||
|
|
||||||
GET /userinfo HTTP/1.1
|
GET /userinfo HTTP/1.1
|
||||||
Host: server.example.com
|
Host: server.example.com
|
||||||
Authorization: Bearer SlAV32hkKG
|
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:
|
||||||
|
|
||||||
|
.. code-block:: http
|
||||||
|
|
||||||
HTTP/1.1 401 Unauthorized
|
HTTP/1.1 401 Unauthorized
|
||||||
WWW-Authenticate: Bearer error="invalid_token",
|
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,4 +1,4 @@
|
||||||
__version__ = "1.6.1"
|
__version__ = "2.0.0"
|
||||||
|
|
||||||
|
|
||||||
class MQTTException(Exception):
|
class MQTTException(Exception):
|
||||||
|
|
File diff suppressed because it is too large
Load diff
113
lib/paho/mqtt/enums.py
Normal file
113
lib/paho/mqtt/enums.py
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
import enum
|
||||||
|
|
||||||
|
|
||||||
|
class MQTTErrorCode(enum.IntEnum):
|
||||||
|
MQTT_ERR_AGAIN = -1
|
||||||
|
MQTT_ERR_SUCCESS = 0
|
||||||
|
MQTT_ERR_NOMEM = 1
|
||||||
|
MQTT_ERR_PROTOCOL = 2
|
||||||
|
MQTT_ERR_INVAL = 3
|
||||||
|
MQTT_ERR_NO_CONN = 4
|
||||||
|
MQTT_ERR_CONN_REFUSED = 5
|
||||||
|
MQTT_ERR_NOT_FOUND = 6
|
||||||
|
MQTT_ERR_CONN_LOST = 7
|
||||||
|
MQTT_ERR_TLS = 8
|
||||||
|
MQTT_ERR_PAYLOAD_SIZE = 9
|
||||||
|
MQTT_ERR_NOT_SUPPORTED = 10
|
||||||
|
MQTT_ERR_AUTH = 11
|
||||||
|
MQTT_ERR_ACL_DENIED = 12
|
||||||
|
MQTT_ERR_UNKNOWN = 13
|
||||||
|
MQTT_ERR_ERRNO = 14
|
||||||
|
MQTT_ERR_QUEUE_SIZE = 15
|
||||||
|
MQTT_ERR_KEEPALIVE = 16
|
||||||
|
|
||||||
|
|
||||||
|
class MQTTProtocolVersion(enum.IntEnum):
|
||||||
|
MQTTv31 = 3
|
||||||
|
MQTTv311 = 4
|
||||||
|
MQTTv5 = 5
|
||||||
|
|
||||||
|
|
||||||
|
class CallbackAPIVersion(enum.Enum):
|
||||||
|
"""Defined the arguments passed to all user-callback.
|
||||||
|
|
||||||
|
See each callbacks for details: `on_connect`, `on_connect_fail`, `on_disconnect`, `on_message`, `on_publish`,
|
||||||
|
`on_subscribe`, `on_unsubscribe`, `on_log`, `on_socket_open`, `on_socket_close`,
|
||||||
|
`on_socket_register_write`, `on_socket_unregister_write`
|
||||||
|
"""
|
||||||
|
VERSION1 = 1
|
||||||
|
"""The version used with paho-mqtt 1.x before introducing CallbackAPIVersion.
|
||||||
|
|
||||||
|
This version had different arguments depending if MQTTv5 or MQTTv3 was used. `Properties` & `ReasonCode` were missing
|
||||||
|
on some callback (apply only to MQTTv5).
|
||||||
|
|
||||||
|
This version is deprecated and will be removed in version 3.0.
|
||||||
|
"""
|
||||||
|
VERSION2 = 2
|
||||||
|
""" This version fix some of the shortcoming of previous version.
|
||||||
|
|
||||||
|
Callback have the same signature if using MQTTv5 or MQTTv3. `ReasonCode` are used in MQTTv3.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class MessageType(enum.IntEnum):
|
||||||
|
CONNECT = 0x10
|
||||||
|
CONNACK = 0x20
|
||||||
|
PUBLISH = 0x30
|
||||||
|
PUBACK = 0x40
|
||||||
|
PUBREC = 0x50
|
||||||
|
PUBREL = 0x60
|
||||||
|
PUBCOMP = 0x70
|
||||||
|
SUBSCRIBE = 0x80
|
||||||
|
SUBACK = 0x90
|
||||||
|
UNSUBSCRIBE = 0xA0
|
||||||
|
UNSUBACK = 0xB0
|
||||||
|
PINGREQ = 0xC0
|
||||||
|
PINGRESP = 0xD0
|
||||||
|
DISCONNECT = 0xE0
|
||||||
|
AUTH = 0xF0
|
||||||
|
|
||||||
|
|
||||||
|
class LogLevel(enum.IntEnum):
|
||||||
|
MQTT_LOG_INFO = 0x01
|
||||||
|
MQTT_LOG_NOTICE = 0x02
|
||||||
|
MQTT_LOG_WARNING = 0x04
|
||||||
|
MQTT_LOG_ERR = 0x08
|
||||||
|
MQTT_LOG_DEBUG = 0x10
|
||||||
|
|
||||||
|
|
||||||
|
class ConnackCode(enum.IntEnum):
|
||||||
|
CONNACK_ACCEPTED = 0
|
||||||
|
CONNACK_REFUSED_PROTOCOL_VERSION = 1
|
||||||
|
CONNACK_REFUSED_IDENTIFIER_REJECTED = 2
|
||||||
|
CONNACK_REFUSED_SERVER_UNAVAILABLE = 3
|
||||||
|
CONNACK_REFUSED_BAD_USERNAME_PASSWORD = 4
|
||||||
|
CONNACK_REFUSED_NOT_AUTHORIZED = 5
|
||||||
|
|
||||||
|
|
||||||
|
class _ConnectionState(enum.Enum):
|
||||||
|
MQTT_CS_NEW = enum.auto()
|
||||||
|
MQTT_CS_CONNECT_ASYNC = enum.auto()
|
||||||
|
MQTT_CS_CONNECTING = enum.auto()
|
||||||
|
MQTT_CS_CONNECTED = enum.auto()
|
||||||
|
MQTT_CS_CONNECTION_LOST = enum.auto()
|
||||||
|
MQTT_CS_DISCONNECTING = enum.auto()
|
||||||
|
MQTT_CS_DISCONNECTED = enum.auto()
|
||||||
|
|
||||||
|
|
||||||
|
class MessageState(enum.IntEnum):
|
||||||
|
MQTT_MS_INVALID = 0
|
||||||
|
MQTT_MS_PUBLISH = 1
|
||||||
|
MQTT_MS_WAIT_FOR_PUBACK = 2
|
||||||
|
MQTT_MS_WAIT_FOR_PUBREC = 3
|
||||||
|
MQTT_MS_RESEND_PUBREL = 4
|
||||||
|
MQTT_MS_WAIT_FOR_PUBREL = 5
|
||||||
|
MQTT_MS_RESEND_PUBCOMP = 6
|
||||||
|
MQTT_MS_WAIT_FOR_PUBCOMP = 7
|
||||||
|
MQTT_MS_SEND_PUBREC = 8
|
||||||
|
MQTT_MS_QUEUED = 9
|
||||||
|
|
||||||
|
|
||||||
|
class PahoClientMode(enum.IntEnum):
|
||||||
|
MQTT_CLIENT = 0
|
||||||
|
MQTT_BRIDGE = 1
|
|
@ -1,4 +1,4 @@
|
||||||
class MQTTMatcher(object):
|
class MQTTMatcher:
|
||||||
"""Intended to manage topic filters including wildcards.
|
"""Intended to manage topic filters including wildcards.
|
||||||
|
|
||||||
Internally, MQTTMatcher use a prefix tree (trie) to store
|
Internally, MQTTMatcher use a prefix tree (trie) to store
|
||||||
|
@ -6,7 +6,7 @@ class MQTTMatcher(object):
|
||||||
method to iterate efficiently over all filters that match
|
method to iterate efficiently over all filters that match
|
||||||
some topic name."""
|
some topic name."""
|
||||||
|
|
||||||
class Node(object):
|
class Node:
|
||||||
__slots__ = '_children', '_content'
|
__slots__ = '_children', '_content'
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
@ -33,8 +33,8 @@ class MQTTMatcher(object):
|
||||||
if node._content is None:
|
if node._content is None:
|
||||||
raise KeyError(key)
|
raise KeyError(key)
|
||||||
return node._content
|
return node._content
|
||||||
except KeyError:
|
except KeyError as ke:
|
||||||
raise KeyError(key)
|
raise KeyError(key) from ke
|
||||||
|
|
||||||
def __delitem__(self, key):
|
def __delitem__(self, key):
|
||||||
"""Delete the value associated with some topic filter :key"""
|
"""Delete the value associated with some topic filter :key"""
|
||||||
|
@ -46,8 +46,8 @@ class MQTTMatcher(object):
|
||||||
lst.append((parent, k, node))
|
lst.append((parent, k, node))
|
||||||
# TODO
|
# TODO
|
||||||
node._content = None
|
node._content = None
|
||||||
except KeyError:
|
except KeyError as ke:
|
||||||
raise KeyError(key)
|
raise KeyError(key) from ke
|
||||||
else: # cleanup
|
else: # cleanup
|
||||||
for parent, k, node in reversed(lst):
|
for parent, k, node in reversed(lst):
|
||||||
if node._children or node._content is not None:
|
if node._children or node._content is not None:
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
and Eclipse Distribution License v1.0 which accompany this distribution.
|
and Eclipse Distribution License v1.0 which accompany this distribution.
|
||||||
|
|
||||||
The Eclipse Public License is available at
|
The Eclipse Public License is available at
|
||||||
http://www.eclipse.org/legal/epl-v10.html
|
http://www.eclipse.org/legal/epl-v20.html
|
||||||
and the Eclipse Distribution License is available at
|
and the Eclipse Distribution License is available at
|
||||||
http://www.eclipse.org/org/documents/edl-v10.php.
|
http://www.eclipse.org/org/documents/edl-v10.php.
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ class PacketTypes:
|
||||||
# Dummy packet type for properties use - will delay only applies to will
|
# Dummy packet type for properties use - will delay only applies to will
|
||||||
WILLMESSAGE = 99
|
WILLMESSAGE = 99
|
||||||
|
|
||||||
Names = [ "reserved", \
|
Names = ( "reserved", \
|
||||||
"Connect", "Connack", "Publish", "Puback", "Pubrec", "Pubrel", \
|
"Connect", "Connack", "Publish", "Puback", "Pubrec", "Pubrel", \
|
||||||
"Pubcomp", "Subscribe", "Suback", "Unsubscribe", "Unsuback", \
|
"Pubcomp", "Subscribe", "Suback", "Unsubscribe", "Unsuback", \
|
||||||
"Pingreq", "Pingresp", "Disconnect", "Auth"]
|
"Pingreq", "Pingresp", "Disconnect", "Auth")
|
||||||
|
|
|
@ -1,23 +1,20 @@
|
||||||
"""
|
# *******************************************************************
|
||||||
*******************************************************************
|
# Copyright (c) 2017, 2019 IBM Corp.
|
||||||
Copyright (c) 2017, 2019 IBM Corp.
|
#
|
||||||
|
# All rights reserved. This program and the accompanying materials
|
||||||
All rights reserved. This program and the accompanying materials
|
# are made available under the terms of the Eclipse Public License v2.0
|
||||||
are made available under the terms of the Eclipse Public License v2.0
|
# and Eclipse Distribution License v1.0 which accompany this distribution.
|
||||||
and Eclipse Distribution License v1.0 which accompany this distribution.
|
#
|
||||||
|
# The Eclipse Public License is available at
|
||||||
The Eclipse Public License is available at
|
# http://www.eclipse.org/legal/epl-v20.html
|
||||||
http://www.eclipse.org/legal/epl-v10.html
|
# and the Eclipse Distribution License is available at
|
||||||
and the Eclipse Distribution License is available at
|
# http://www.eclipse.org/org/documents/edl-v10.php.
|
||||||
http://www.eclipse.org/org/documents/edl-v10.php.
|
#
|
||||||
|
# Contributors:
|
||||||
Contributors:
|
# Ian Craggs - initial implementation and/or documentation
|
||||||
Ian Craggs - initial implementation and/or documentation
|
# *******************************************************************
|
||||||
*******************************************************************
|
|
||||||
"""
|
|
||||||
|
|
||||||
import struct
|
import struct
|
||||||
import sys
|
|
||||||
|
|
||||||
from .packettypes import PacketTypes
|
from .packettypes import PacketTypes
|
||||||
|
|
||||||
|
@ -52,10 +49,8 @@ def readInt32(buf):
|
||||||
|
|
||||||
def writeUTF(data):
|
def writeUTF(data):
|
||||||
# data could be a string, or bytes. If string, encode into bytes with utf-8
|
# data could be a string, or bytes. If string, encode into bytes with utf-8
|
||||||
if sys.version_info[0] < 3:
|
if not isinstance(data, bytes):
|
||||||
data = bytearray(data, 'utf-8')
|
data = bytes(data, "utf-8")
|
||||||
else:
|
|
||||||
data = data if type(data) == type(b"") else bytes(data, "utf-8")
|
|
||||||
return writeInt16(len(data)) + data
|
return writeInt16(len(data)) + data
|
||||||
|
|
||||||
|
|
||||||
|
@ -100,19 +95,17 @@ class VariableByteIntegers: # Variable Byte Integer
|
||||||
def encode(x):
|
def encode(x):
|
||||||
"""
|
"""
|
||||||
Convert an integer 0 <= x <= 268435455 into multi-byte format.
|
Convert an integer 0 <= x <= 268435455 into multi-byte format.
|
||||||
Returns the buffer convered from the integer.
|
Returns the buffer converted from the integer.
|
||||||
"""
|
"""
|
||||||
assert 0 <= x <= 268435455
|
if not 0 <= x <= 268435455:
|
||||||
|
raise ValueError(f"Value {x!r} must be in range 0-268435455")
|
||||||
buffer = b''
|
buffer = b''
|
||||||
while 1:
|
while 1:
|
||||||
digit = x % 128
|
digit = x % 128
|
||||||
x //= 128
|
x //= 128
|
||||||
if x > 0:
|
if x > 0:
|
||||||
digit |= 0x80
|
digit |= 0x80
|
||||||
if sys.version_info[0] >= 3:
|
|
||||||
buffer += bytes([digit])
|
buffer += bytes([digit])
|
||||||
else:
|
|
||||||
buffer += bytes(chr(digit))
|
|
||||||
if x == 0:
|
if x == 0:
|
||||||
break
|
break
|
||||||
return buffer
|
return buffer
|
||||||
|
@ -139,14 +132,14 @@ class VariableByteIntegers: # Variable Byte Integer
|
||||||
return (value, bytes)
|
return (value, bytes)
|
||||||
|
|
||||||
|
|
||||||
class Properties(object):
|
class Properties:
|
||||||
"""MQTT v5.0 properties class.
|
"""MQTT v5.0 properties class.
|
||||||
|
|
||||||
See Properties.names for a list of accepted property names along with their numeric values.
|
See Properties.names for a list of accepted property names along with their numeric values.
|
||||||
|
|
||||||
See Properties.properties for the data type of each property.
|
See Properties.properties for the data type of each property.
|
||||||
|
|
||||||
Example of use:
|
Example of use::
|
||||||
|
|
||||||
publish_properties = Properties(PacketTypes.PUBLISH)
|
publish_properties = Properties(PacketTypes.PUBLISH)
|
||||||
publish_properties.UserProperty = ("a", "2")
|
publish_properties.UserProperty = ("a", "2")
|
||||||
|
@ -264,37 +257,33 @@ class Properties(object):
|
||||||
# the name could have spaces in, or not. Remove spaces before assignment
|
# the name could have spaces in, or not. Remove spaces before assignment
|
||||||
if name not in [aname.replace(' ', '') for aname in self.names.keys()]:
|
if name not in [aname.replace(' ', '') for aname in self.names.keys()]:
|
||||||
raise MQTTException(
|
raise MQTTException(
|
||||||
"Property name must be one of "+str(self.names.keys()))
|
f"Property name must be one of {self.names.keys()}")
|
||||||
# check that this attribute applies to the packet type
|
# check that this attribute applies to the packet type
|
||||||
if self.packetType not in self.properties[self.getIdentFromName(name)][1]:
|
if self.packetType not in self.properties[self.getIdentFromName(name)][1]:
|
||||||
raise MQTTException("Property %s does not apply to packet type %s"
|
raise MQTTException(f"Property {name} does not apply to packet type {PacketTypes.Names[self.packetType]}")
|
||||||
% (name, PacketTypes.Names[self.packetType]))
|
|
||||||
|
|
||||||
# Check for forbidden values
|
# Check for forbidden values
|
||||||
if type(value) != type([]):
|
if not isinstance(value, list):
|
||||||
if name in ["ReceiveMaximum", "TopicAlias"] \
|
if name in ["ReceiveMaximum", "TopicAlias"] \
|
||||||
and (value < 1 or value > 65535):
|
and (value < 1 or value > 65535):
|
||||||
|
|
||||||
raise MQTTException(
|
raise MQTTException(f"{name} property value must be in the range 1-65535")
|
||||||
"%s property value must be in the range 1-65535" % (name))
|
|
||||||
elif name in ["TopicAliasMaximum"] \
|
elif name in ["TopicAliasMaximum"] \
|
||||||
and (value < 0 or value > 65535):
|
and (value < 0 or value > 65535):
|
||||||
|
|
||||||
raise MQTTException(
|
raise MQTTException(f"{name} property value must be in the range 0-65535")
|
||||||
"%s property value must be in the range 0-65535" % (name))
|
|
||||||
elif name in ["MaximumPacketSize", "SubscriptionIdentifier"] \
|
elif name in ["MaximumPacketSize", "SubscriptionIdentifier"] \
|
||||||
and (value < 1 or value > 268435455):
|
and (value < 1 or value > 268435455):
|
||||||
|
|
||||||
raise MQTTException(
|
raise MQTTException(f"{name} property value must be in the range 1-268435455")
|
||||||
"%s property value must be in the range 1-268435455" % (name))
|
|
||||||
elif name in ["RequestResponseInformation", "RequestProblemInformation", "PayloadFormatIndicator"] \
|
elif name in ["RequestResponseInformation", "RequestProblemInformation", "PayloadFormatIndicator"] \
|
||||||
and (value != 0 and value != 1):
|
and (value != 0 and value != 1):
|
||||||
|
|
||||||
raise MQTTException(
|
raise MQTTException(
|
||||||
"%s property value must be 0 or 1" % (name))
|
f"{name} property value must be 0 or 1")
|
||||||
|
|
||||||
if self.allowsMultiple(name):
|
if self.allowsMultiple(name):
|
||||||
if type(value) != type([]):
|
if not isinstance(value, list):
|
||||||
value = [value]
|
value = [value]
|
||||||
if hasattr(self, name):
|
if hasattr(self, name):
|
||||||
value = object.__getattribute__(self, name) + value
|
value = object.__getattribute__(self, name) + value
|
||||||
|
@ -308,8 +297,7 @@ class Properties(object):
|
||||||
if hasattr(self, compressedName):
|
if hasattr(self, compressedName):
|
||||||
if not first:
|
if not first:
|
||||||
buffer += ", "
|
buffer += ", "
|
||||||
buffer += compressedName + " : " + \
|
buffer += f"{compressedName} : {getattr(self, compressedName)}"
|
||||||
str(getattr(self, compressedName))
|
|
||||||
first = False
|
first = False
|
||||||
buffer += "]"
|
buffer += "]"
|
||||||
return buffer
|
return buffer
|
||||||
|
@ -345,9 +333,6 @@ class Properties(object):
|
||||||
buffer = b""
|
buffer = b""
|
||||||
buffer += VariableByteIntegers.encode(identifier) # identifier
|
buffer += VariableByteIntegers.encode(identifier) # identifier
|
||||||
if type == self.types.index("Byte"): # value
|
if type == self.types.index("Byte"): # value
|
||||||
if sys.version_info[0] < 3:
|
|
||||||
buffer += chr(value)
|
|
||||||
else:
|
|
||||||
buffer += bytes([value])
|
buffer += bytes([value])
|
||||||
elif type == self.types.index("Two Byte Integer"):
|
elif type == self.types.index("Two Byte Integer"):
|
||||||
buffer += writeInt16(value)
|
buffer += writeInt16(value)
|
||||||
|
@ -412,8 +397,6 @@ class Properties(object):
|
||||||
return rc
|
return rc
|
||||||
|
|
||||||
def unpack(self, buffer):
|
def unpack(self, buffer):
|
||||||
if sys.version_info[0] < 3:
|
|
||||||
buffer = bytearray(buffer)
|
|
||||||
self.clear()
|
self.clear()
|
||||||
# deserialize properties into attributes from buffer received from network
|
# deserialize properties into attributes from buffer received from network
|
||||||
propslen, VBIlen = VariableByteIntegers.decode(buffer)
|
propslen, VBIlen = VariableByteIntegers.decode(buffer)
|
||||||
|
@ -433,6 +416,6 @@ class Properties(object):
|
||||||
compressedName = propname.replace(' ', '')
|
compressedName = propname.replace(' ', '')
|
||||||
if not self.allowsMultiple(compressedName) and hasattr(self, compressedName):
|
if not self.allowsMultiple(compressedName) and hasattr(self, compressedName):
|
||||||
raise MQTTException(
|
raise MQTTException(
|
||||||
"Property '%s' must not exist more than once" % property)
|
f"Property '{property}' must not exist more than once")
|
||||||
setattr(self, propname, value)
|
setattr(self, propname, value)
|
||||||
return self, propslen + VBIlen
|
return self, propslen + VBIlen
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
# and Eclipse Distribution License v1.0 which accompany this distribution.
|
# and Eclipse Distribution License v1.0 which accompany this distribution.
|
||||||
#
|
#
|
||||||
# The Eclipse Public License is available at
|
# The Eclipse Public License is available at
|
||||||
# http://www.eclipse.org/legal/epl-v10.html
|
# http://www.eclipse.org/legal/epl-v20.html
|
||||||
# and the Eclipse Distribution License is available at
|
# and the Eclipse Distribution License is available at
|
||||||
# http://www.eclipse.org/org/documents/edl-v10.php.
|
# http://www.eclipse.org/org/documents/edl-v10.php.
|
||||||
#
|
#
|
||||||
|
@ -18,20 +18,58 @@ of messages in a one-shot manner. In other words, they are useful for the
|
||||||
situation where you have a single/multiple messages you want to publish to a
|
situation where you have a single/multiple messages you want to publish to a
|
||||||
broker, then disconnect and nothing else is required.
|
broker, then disconnect and nothing else is required.
|
||||||
"""
|
"""
|
||||||
from __future__ import absolute_import
|
from __future__ import annotations
|
||||||
|
|
||||||
import collections
|
import collections
|
||||||
|
from collections.abc import Iterable
|
||||||
|
from typing import TYPE_CHECKING, Any, List, Tuple, Union
|
||||||
|
|
||||||
try:
|
from paho.mqtt.enums import CallbackAPIVersion
|
||||||
from collections.abc import Iterable
|
from paho.mqtt.properties import Properties
|
||||||
except ImportError:
|
from paho.mqtt.reasoncodes import ReasonCode
|
||||||
from collections import Iterable
|
|
||||||
|
|
||||||
from .. import mqtt
|
from .. import mqtt
|
||||||
from . import client as paho
|
from . import client as paho
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
try:
|
||||||
|
from typing import NotRequired, Required, TypedDict # type: ignore
|
||||||
|
except ImportError:
|
||||||
|
from typing_extensions import NotRequired, Required, TypedDict
|
||||||
|
|
||||||
def _do_publish(client):
|
try:
|
||||||
|
from typing import Literal
|
||||||
|
except ImportError:
|
||||||
|
from typing_extensions import Literal # type: ignore
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class AuthParameter(TypedDict, total=False):
|
||||||
|
username: Required[str]
|
||||||
|
password: NotRequired[str]
|
||||||
|
|
||||||
|
|
||||||
|
class TLSParameter(TypedDict, total=False):
|
||||||
|
ca_certs: Required[str]
|
||||||
|
certfile: NotRequired[str]
|
||||||
|
keyfile: NotRequired[str]
|
||||||
|
tls_version: NotRequired[int]
|
||||||
|
ciphers: NotRequired[str]
|
||||||
|
insecure: NotRequired[bool]
|
||||||
|
|
||||||
|
|
||||||
|
class MessageDict(TypedDict, total=False):
|
||||||
|
topic: Required[str]
|
||||||
|
payload: NotRequired[paho.PayloadType]
|
||||||
|
qos: NotRequired[int]
|
||||||
|
retain: NotRequired[bool]
|
||||||
|
|
||||||
|
MessageTuple = Tuple[str, paho.PayloadType, int, bool]
|
||||||
|
|
||||||
|
MessagesList = List[Union[MessageDict, MessageTuple]]
|
||||||
|
|
||||||
|
|
||||||
|
def _do_publish(client: paho.Client):
|
||||||
"""Internal function"""
|
"""Internal function"""
|
||||||
|
|
||||||
message = client._userdata.popleft()
|
message = client._userdata.popleft()
|
||||||
|
@ -44,21 +82,18 @@ def _do_publish(client):
|
||||||
raise TypeError('message must be a dict, tuple, or list')
|
raise TypeError('message must be a dict, tuple, or list')
|
||||||
|
|
||||||
|
|
||||||
def _on_connect(client, userdata, flags, rc):
|
def _on_connect(client: paho.Client, userdata: MessagesList, flags, reason_code, properties):
|
||||||
"""Internal callback"""
|
"""Internal v5 callback"""
|
||||||
#pylint: disable=invalid-name, unused-argument
|
if reason_code == 0:
|
||||||
|
|
||||||
if rc == 0:
|
|
||||||
if len(userdata) > 0:
|
if len(userdata) > 0:
|
||||||
_do_publish(client)
|
_do_publish(client)
|
||||||
else:
|
else:
|
||||||
raise mqtt.MQTTException(paho.connack_string(rc))
|
raise mqtt.MQTTException(paho.connack_string(reason_code))
|
||||||
|
|
||||||
def _on_connect_v5(client, userdata, flags, rc, properties):
|
|
||||||
"""Internal v5 callback"""
|
|
||||||
_on_connect(client, userdata, flags, rc)
|
|
||||||
|
|
||||||
def _on_publish(client, userdata, mid):
|
def _on_publish(
|
||||||
|
client: paho.Client, userdata: collections.deque[MessagesList], mid: int, reason_codes: ReasonCode, properties: Properties,
|
||||||
|
) -> None:
|
||||||
"""Internal callback"""
|
"""Internal callback"""
|
||||||
#pylint: disable=unused-argument
|
#pylint: disable=unused-argument
|
||||||
|
|
||||||
|
@ -68,16 +103,26 @@ def _on_publish(client, userdata, mid):
|
||||||
_do_publish(client)
|
_do_publish(client)
|
||||||
|
|
||||||
|
|
||||||
def multiple(msgs, hostname="localhost", port=1883, client_id="", keepalive=60,
|
def multiple(
|
||||||
will=None, auth=None, tls=None, protocol=paho.MQTTv311,
|
msgs: MessagesList,
|
||||||
transport="tcp", proxy_args=None):
|
hostname: str = "localhost",
|
||||||
|
port: int = 1883,
|
||||||
|
client_id: str = "",
|
||||||
|
keepalive: int = 60,
|
||||||
|
will: MessageDict | None = None,
|
||||||
|
auth: AuthParameter | None = None,
|
||||||
|
tls: TLSParameter | None = None,
|
||||||
|
protocol: int = paho.MQTTv311,
|
||||||
|
transport: Literal["tcp", "websockets"] = "tcp",
|
||||||
|
proxy_args: Any | None = None,
|
||||||
|
) -> None:
|
||||||
"""Publish multiple messages to a broker, then disconnect cleanly.
|
"""Publish multiple messages to a broker, then disconnect cleanly.
|
||||||
|
|
||||||
This function creates an MQTT client, connects to a broker and publishes a
|
This function creates an MQTT client, connects to a broker and publishes a
|
||||||
list of messages. Once the messages have been delivered, it disconnects
|
list of messages. Once the messages have been delivered, it disconnects
|
||||||
cleanly from the broker.
|
cleanly from the broker.
|
||||||
|
|
||||||
msgs : a list of messages to publish. Each message is either a dict or a
|
:param msgs: a list of messages to publish. Each message is either a dict or a
|
||||||
tuple.
|
tuple.
|
||||||
|
|
||||||
If a dict, only the topic must be present. Default values will be
|
If a dict, only the topic must be present. Default values will be
|
||||||
|
@ -94,30 +139,30 @@ def multiple(msgs, hostname="localhost", port=1883, client_id="", keepalive=60,
|
||||||
If a tuple, then it must be of the form:
|
If a tuple, then it must be of the form:
|
||||||
("<topic>", "<payload>", qos, retain)
|
("<topic>", "<payload>", qos, retain)
|
||||||
|
|
||||||
hostname : a string containing the address of the broker to connect to.
|
:param str hostname: the address of the broker to connect to.
|
||||||
Defaults to localhost.
|
Defaults to localhost.
|
||||||
|
|
||||||
port : the port to connect to the broker on. Defaults to 1883.
|
:param int port: the port to connect to the broker on. Defaults to 1883.
|
||||||
|
|
||||||
client_id : the MQTT client id to use. If "" or None, the Paho library will
|
:param str client_id: the MQTT client id to use. If "" or None, the Paho library will
|
||||||
generate a client id automatically.
|
generate a client id automatically.
|
||||||
|
|
||||||
keepalive : the keepalive timeout value for the client. Defaults to 60
|
:param int keepalive: the keepalive timeout value for the client. Defaults to 60
|
||||||
seconds.
|
seconds.
|
||||||
|
|
||||||
will : a dict containing will parameters for the client: will = {'topic':
|
:param will: a dict containing will parameters for the client: will = {'topic':
|
||||||
"<topic>", 'payload':"<payload">, 'qos':<qos>, 'retain':<retain>}.
|
"<topic>", 'payload':"<payload">, 'qos':<qos>, 'retain':<retain>}.
|
||||||
Topic is required, all other parameters are optional and will
|
Topic is required, all other parameters are optional and will
|
||||||
default to None, 0 and False respectively.
|
default to None, 0 and False respectively.
|
||||||
Defaults to None, which indicates no will should be used.
|
Defaults to None, which indicates no will should be used.
|
||||||
|
|
||||||
auth : a dict containing authentication parameters for the client:
|
:param auth: a dict containing authentication parameters for the client:
|
||||||
auth = {'username':"<username>", 'password':"<password>"}
|
auth = {'username':"<username>", 'password':"<password>"}
|
||||||
Username is required, password is optional and will default to None
|
Username is required, password is optional and will default to None
|
||||||
if not provided.
|
if not provided.
|
||||||
Defaults to None, which indicates no authentication is to be used.
|
Defaults to None, which indicates no authentication is to be used.
|
||||||
|
|
||||||
tls : a dict containing TLS configuration parameters for the client:
|
:param tls: a dict containing TLS configuration parameters for the client:
|
||||||
dict = {'ca_certs':"<ca_certs>", 'certfile':"<certfile>",
|
dict = {'ca_certs':"<ca_certs>", 'certfile':"<certfile>",
|
||||||
'keyfile':"<keyfile>", 'tls_version':"<tls_version>",
|
'keyfile':"<keyfile>", 'tls_version':"<tls_version>",
|
||||||
'ciphers':"<ciphers">, 'insecure':"<bool>"}
|
'ciphers':"<ciphers">, 'insecure':"<bool>"}
|
||||||
|
@ -128,23 +173,28 @@ def multiple(msgs, hostname="localhost", port=1883, client_id="", keepalive=60,
|
||||||
processed using the tls_set_context method.
|
processed using the tls_set_context method.
|
||||||
Defaults to None, which indicates that TLS should not be used.
|
Defaults to None, which indicates that TLS should not be used.
|
||||||
|
|
||||||
transport : set to "tcp" to use the default setting of transport which is
|
:param str transport: set to "tcp" to use the default setting of transport which is
|
||||||
raw TCP. Set to "websockets" to use WebSockets as the transport.
|
raw TCP. Set to "websockets" to use WebSockets as the transport.
|
||||||
proxy_args: a dictionary that will be given to the client.
|
|
||||||
|
:param proxy_args: a dictionary that will be given to the client.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not isinstance(msgs, Iterable):
|
if not isinstance(msgs, Iterable):
|
||||||
raise TypeError('msgs must be an iterable')
|
raise TypeError('msgs must be an iterable')
|
||||||
|
if len(msgs) == 0:
|
||||||
|
raise ValueError('msgs is empty')
|
||||||
|
|
||||||
|
client = paho.Client(
|
||||||
|
CallbackAPIVersion.VERSION2,
|
||||||
|
client_id=client_id,
|
||||||
|
userdata=collections.deque(msgs),
|
||||||
|
protocol=protocol,
|
||||||
|
transport=transport,
|
||||||
|
)
|
||||||
|
|
||||||
client = paho.Client(client_id=client_id, userdata=collections.deque(msgs),
|
client.enable_logger()
|
||||||
protocol=protocol, transport=transport)
|
|
||||||
|
|
||||||
client.on_publish = _on_publish
|
client.on_publish = _on_publish
|
||||||
if protocol == mqtt.client.MQTTv5:
|
client.on_connect = _on_connect # type: ignore
|
||||||
client.on_connect = _on_connect_v5
|
|
||||||
else:
|
|
||||||
client.on_connect = _on_connect
|
|
||||||
|
|
||||||
if proxy_args is not None:
|
if proxy_args is not None:
|
||||||
client.proxy_set(**proxy_args)
|
client.proxy_set(**proxy_args)
|
||||||
|
@ -164,7 +214,8 @@ def multiple(msgs, hostname="localhost", port=1883, client_id="", keepalive=60,
|
||||||
if tls is not None:
|
if tls is not None:
|
||||||
if isinstance(tls, dict):
|
if isinstance(tls, dict):
|
||||||
insecure = tls.pop('insecure', False)
|
insecure = tls.pop('insecure', False)
|
||||||
client.tls_set(**tls)
|
# mypy don't get that tls no longer contains the key insecure
|
||||||
|
client.tls_set(**tls) # type: ignore[misc]
|
||||||
if insecure:
|
if insecure:
|
||||||
# Must be set *after* the `client.tls_set()` call since it sets
|
# Must be set *after* the `client.tls_set()` call since it sets
|
||||||
# up the SSL context that `client.tls_insecure_set` alters.
|
# up the SSL context that `client.tls_insecure_set` alters.
|
||||||
|
@ -177,49 +228,62 @@ def multiple(msgs, hostname="localhost", port=1883, client_id="", keepalive=60,
|
||||||
client.loop_forever()
|
client.loop_forever()
|
||||||
|
|
||||||
|
|
||||||
def single(topic, payload=None, qos=0, retain=False, hostname="localhost",
|
def single(
|
||||||
port=1883, client_id="", keepalive=60, will=None, auth=None,
|
topic: str,
|
||||||
tls=None, protocol=paho.MQTTv311, transport="tcp", proxy_args=None):
|
payload: paho.PayloadType = None,
|
||||||
|
qos: int = 0,
|
||||||
|
retain: bool = False,
|
||||||
|
hostname: str = "localhost",
|
||||||
|
port: int = 1883,
|
||||||
|
client_id: str = "",
|
||||||
|
keepalive: int = 60,
|
||||||
|
will: MessageDict | None = None,
|
||||||
|
auth: AuthParameter | None = None,
|
||||||
|
tls: TLSParameter | None = None,
|
||||||
|
protocol: int = paho.MQTTv311,
|
||||||
|
transport: Literal["tcp", "websockets"] = "tcp",
|
||||||
|
proxy_args: Any | None = None,
|
||||||
|
) -> None:
|
||||||
"""Publish a single message to a broker, then disconnect cleanly.
|
"""Publish a single message to a broker, then disconnect cleanly.
|
||||||
|
|
||||||
This function creates an MQTT client, connects to a broker and publishes a
|
This function creates an MQTT client, connects to a broker and publishes a
|
||||||
single message. Once the message has been delivered, it disconnects cleanly
|
single message. Once the message has been delivered, it disconnects cleanly
|
||||||
from the broker.
|
from the broker.
|
||||||
|
|
||||||
topic : the only required argument must be the topic string to which the
|
:param str topic: the only required argument must be the topic string to which the
|
||||||
payload will be published.
|
payload will be published.
|
||||||
|
|
||||||
payload : the payload to be published. If "" or None, a zero length payload
|
:param payload: the payload to be published. If "" or None, a zero length payload
|
||||||
will be published.
|
will be published.
|
||||||
|
|
||||||
qos : the qos to use when publishing, default to 0.
|
:param int qos: the qos to use when publishing, default to 0.
|
||||||
|
|
||||||
retain : set the message to be retained (True) or not (False).
|
:param bool retain: set the message to be retained (True) or not (False).
|
||||||
|
|
||||||
hostname : a string containing the address of the broker to connect to.
|
:param str hostname: the address of the broker to connect to.
|
||||||
Defaults to localhost.
|
Defaults to localhost.
|
||||||
|
|
||||||
port : the port to connect to the broker on. Defaults to 1883.
|
:param int port: the port to connect to the broker on. Defaults to 1883.
|
||||||
|
|
||||||
client_id : the MQTT client id to use. If "" or None, the Paho library will
|
:param str client_id: the MQTT client id to use. If "" or None, the Paho library will
|
||||||
generate a client id automatically.
|
generate a client id automatically.
|
||||||
|
|
||||||
keepalive : the keepalive timeout value for the client. Defaults to 60
|
:param int keepalive: the keepalive timeout value for the client. Defaults to 60
|
||||||
seconds.
|
seconds.
|
||||||
|
|
||||||
will : a dict containing will parameters for the client: will = {'topic':
|
:param will: a dict containing will parameters for the client: will = {'topic':
|
||||||
"<topic>", 'payload':"<payload">, 'qos':<qos>, 'retain':<retain>}.
|
"<topic>", 'payload':"<payload">, 'qos':<qos>, 'retain':<retain>}.
|
||||||
Topic is required, all other parameters are optional and will
|
Topic is required, all other parameters are optional and will
|
||||||
default to None, 0 and False respectively.
|
default to None, 0 and False respectively.
|
||||||
Defaults to None, which indicates no will should be used.
|
Defaults to None, which indicates no will should be used.
|
||||||
|
|
||||||
auth : a dict containing authentication parameters for the client:
|
:param auth: a dict containing authentication parameters for the client:
|
||||||
auth = {'username':"<username>", 'password':"<password>"}
|
|
||||||
Username is required, password is optional and will default to None
|
Username is required, password is optional and will default to None
|
||||||
|
auth = {'username':"<username>", 'password':"<password>"}
|
||||||
if not provided.
|
if not provided.
|
||||||
Defaults to None, which indicates no authentication is to be used.
|
Defaults to None, which indicates no authentication is to be used.
|
||||||
|
|
||||||
tls : a dict containing TLS configuration parameters for the client:
|
:param tls: a dict containing TLS configuration parameters for the client:
|
||||||
dict = {'ca_certs':"<ca_certs>", 'certfile':"<certfile>",
|
dict = {'ca_certs':"<ca_certs>", 'certfile':"<certfile>",
|
||||||
'keyfile':"<keyfile>", 'tls_version':"<tls_version>",
|
'keyfile':"<keyfile>", 'tls_version':"<tls_version>",
|
||||||
'ciphers':"<ciphers">, 'insecure':"<bool>"}
|
'ciphers':"<ciphers">, 'insecure':"<bool>"}
|
||||||
|
@ -230,12 +294,13 @@ def single(topic, payload=None, qos=0, retain=False, hostname="localhost",
|
||||||
Alternatively, tls input can be an SSLContext object, which will be
|
Alternatively, tls input can be an SSLContext object, which will be
|
||||||
processed using the tls_set_context method.
|
processed using the tls_set_context method.
|
||||||
|
|
||||||
transport : set to "tcp" to use the default setting of transport which is
|
:param transport: set to "tcp" to use the default setting of transport which is
|
||||||
raw TCP. Set to "websockets" to use WebSockets as the transport.
|
raw TCP. Set to "websockets" to use WebSockets as the transport.
|
||||||
proxy_args: a dictionary that will be given to the client.
|
|
||||||
|
:param proxy_args: a dictionary that will be given to the client.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
msg = {'topic':topic, 'payload':payload, 'qos':qos, 'retain':retain}
|
msg: MessageDict = {'topic':topic, 'payload':payload, 'qos':qos, 'retain':retain}
|
||||||
|
|
||||||
multiple([msg], hostname, port, client_id, keepalive, will, auth, tls,
|
multiple([msg], hostname, port, client_id, keepalive, will, auth, tls,
|
||||||
protocol, transport, proxy_args)
|
protocol, transport, proxy_args)
|
||||||
|
|
0
lib/paho/mqtt/py.typed
Normal file
0
lib/paho/mqtt/py.typed
Normal file
|
@ -1,30 +1,31 @@
|
||||||
"""
|
# *******************************************************************
|
||||||
*******************************************************************
|
# Copyright (c) 2017, 2019 IBM Corp.
|
||||||
Copyright (c) 2017, 2019 IBM Corp.
|
#
|
||||||
|
# All rights reserved. This program and the accompanying materials
|
||||||
|
# are made available under the terms of the Eclipse Public License v2.0
|
||||||
|
# and Eclipse Distribution License v1.0 which accompany this distribution.
|
||||||
|
#
|
||||||
|
# The Eclipse Public License is available at
|
||||||
|
# http://www.eclipse.org/legal/epl-v20.html
|
||||||
|
# and the Eclipse Distribution License is available at
|
||||||
|
# http://www.eclipse.org/org/documents/edl-v10.php.
|
||||||
|
#
|
||||||
|
# Contributors:
|
||||||
|
# Ian Craggs - initial implementation and/or documentation
|
||||||
|
# *******************************************************************
|
||||||
|
|
||||||
All rights reserved. This program and the accompanying materials
|
import functools
|
||||||
are made available under the terms of the Eclipse Public License v2.0
|
import warnings
|
||||||
and Eclipse Distribution License v1.0 which accompany this distribution.
|
from typing import Any
|
||||||
|
|
||||||
The Eclipse Public License is available at
|
|
||||||
http://www.eclipse.org/legal/epl-v10.html
|
|
||||||
and the Eclipse Distribution License is available at
|
|
||||||
http://www.eclipse.org/org/documents/edl-v10.php.
|
|
||||||
|
|
||||||
Contributors:
|
|
||||||
Ian Craggs - initial implementation and/or documentation
|
|
||||||
*******************************************************************
|
|
||||||
"""
|
|
||||||
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from .packettypes import PacketTypes
|
from .packettypes import PacketTypes
|
||||||
|
|
||||||
|
|
||||||
class ReasonCodes:
|
@functools.total_ordering
|
||||||
|
class ReasonCode:
|
||||||
"""MQTT version 5.0 reason codes class.
|
"""MQTT version 5.0 reason codes class.
|
||||||
|
|
||||||
See ReasonCodes.names for a list of possible numeric values along with their
|
See ReasonCode.names for a list of possible numeric values along with their
|
||||||
names and the packets to which they apply.
|
names and the packets to which they apply.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
@ -135,10 +136,12 @@ class ReasonCodes:
|
||||||
|
|
||||||
Used when displaying the reason code.
|
Used when displaying the reason code.
|
||||||
"""
|
"""
|
||||||
assert identifier in self.names.keys(), identifier
|
if identifier not in self.names:
|
||||||
|
raise KeyError(identifier)
|
||||||
names = self.names[identifier]
|
names = self.names[identifier]
|
||||||
namelist = [name for name in names.keys() if packetType in names[name]]
|
namelist = [name for name in names.keys() if packetType in names[name]]
|
||||||
assert len(namelist) == 1
|
if len(namelist) != 1:
|
||||||
|
raise ValueError(f"Expected exactly one name, found {namelist!r}")
|
||||||
return namelist[0]
|
return namelist[0]
|
||||||
|
|
||||||
def getId(self, name):
|
def getId(self, name):
|
||||||
|
@ -148,22 +151,17 @@ class ReasonCodes:
|
||||||
Used when setting the reason code for a packetType
|
Used when setting the reason code for a packetType
|
||||||
check that only valid codes for the packet are set.
|
check that only valid codes for the packet are set.
|
||||||
"""
|
"""
|
||||||
identifier = None
|
|
||||||
for code in self.names.keys():
|
for code in self.names.keys():
|
||||||
if name in self.names[code].keys():
|
if name in self.names[code].keys():
|
||||||
if self.packetType in self.names[code][name]:
|
if self.packetType in self.names[code][name]:
|
||||||
identifier = code
|
return code
|
||||||
break
|
raise KeyError(f"Reason code name not found: {name}")
|
||||||
assert identifier is not None, name
|
|
||||||
return identifier
|
|
||||||
|
|
||||||
def set(self, name):
|
def set(self, name):
|
||||||
self.value = self.getId(name)
|
self.value = self.getId(name)
|
||||||
|
|
||||||
def unpack(self, buffer):
|
def unpack(self, buffer):
|
||||||
c = buffer[0]
|
c = buffer[0]
|
||||||
if sys.version_info[0] < 3:
|
|
||||||
c = ord(c)
|
|
||||||
name = self.__getName__(self.packetType, c)
|
name = self.__getName__(self.packetType, c)
|
||||||
self.value = self.getId(name)
|
self.value = self.getId(name)
|
||||||
return 1
|
return 1
|
||||||
|
@ -177,11 +175,26 @@ class ReasonCodes:
|
||||||
if isinstance(other, int):
|
if isinstance(other, int):
|
||||||
return self.value == other
|
return self.value == other
|
||||||
if isinstance(other, str):
|
if isinstance(other, str):
|
||||||
return self.value == str(self)
|
return other == str(self)
|
||||||
if isinstance(other, ReasonCodes):
|
if isinstance(other, ReasonCode):
|
||||||
return self.value == other.value
|
return self.value == other.value
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def __lt__(self, other):
|
||||||
|
if isinstance(other, int):
|
||||||
|
return self.value < other
|
||||||
|
if isinstance(other, ReasonCode):
|
||||||
|
return self.value < other.value
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
try:
|
||||||
|
packet_name = PacketTypes.Names[self.packetType]
|
||||||
|
except IndexError:
|
||||||
|
packet_name = "Unknown"
|
||||||
|
|
||||||
|
return f"ReasonCode({packet_name}, {self.getName()!r})"
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.getName()
|
return self.getName()
|
||||||
|
|
||||||
|
@ -190,3 +203,21 @@ class ReasonCodes:
|
||||||
|
|
||||||
def pack(self):
|
def pack(self):
|
||||||
return bytearray([self.value])
|
return bytearray([self.value])
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_failure(self) -> bool:
|
||||||
|
return self.value >= 0x80
|
||||||
|
|
||||||
|
|
||||||
|
class _CompatibilityIsInstance(type):
|
||||||
|
def __instancecheck__(self, other: Any) -> bool:
|
||||||
|
return isinstance(other, ReasonCode)
|
||||||
|
|
||||||
|
|
||||||
|
class ReasonCodes(ReasonCode, metaclass=_CompatibilityIsInstance):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
warnings.warn("ReasonCodes is deprecated, use ReasonCode (singular) instead",
|
||||||
|
category=DeprecationWarning,
|
||||||
|
stacklevel=2,
|
||||||
|
)
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
# and Eclipse Distribution License v1.0 which accompany this distribution.
|
# and Eclipse Distribution License v1.0 which accompany this distribution.
|
||||||
#
|
#
|
||||||
# The Eclipse Public License is available at
|
# The Eclipse Public License is available at
|
||||||
# http://www.eclipse.org/legal/epl-v10.html
|
# http://www.eclipse.org/legal/epl-v20.html
|
||||||
# and the Eclipse Distribution License is available at
|
# and the Eclipse Distribution License is available at
|
||||||
# http://www.eclipse.org/org/documents/edl-v10.php.
|
# http://www.eclipse.org/org/documents/edl-v10.php.
|
||||||
#
|
#
|
||||||
|
@ -18,16 +18,15 @@ to topics and retrieving messages. The two functions are simple(), which
|
||||||
returns one or messages matching a set of topics, and callback() which allows
|
returns one or messages matching a set of topics, and callback() which allows
|
||||||
you to pass a callback for processing of messages.
|
you to pass a callback for processing of messages.
|
||||||
"""
|
"""
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
from .. import mqtt
|
from .. import mqtt
|
||||||
from . import client as paho
|
from . import client as paho
|
||||||
|
|
||||||
|
|
||||||
def _on_connect_v5(client, userdata, flags, rc, properties):
|
def _on_connect(client, userdata, flags, reason_code, properties):
|
||||||
"""Internal callback"""
|
"""Internal callback"""
|
||||||
if rc != 0:
|
if reason_code != 0:
|
||||||
raise mqtt.MQTTException(paho.connack_string(rc))
|
raise mqtt.MQTTException(paho.connack_string(reason_code))
|
||||||
|
|
||||||
if isinstance(userdata['topics'], list):
|
if isinstance(userdata['topics'], list):
|
||||||
for topic in userdata['topics']:
|
for topic in userdata['topics']:
|
||||||
|
@ -35,10 +34,6 @@ def _on_connect_v5(client, userdata, flags, rc, properties):
|
||||||
else:
|
else:
|
||||||
client.subscribe(userdata['topics'], userdata['qos'])
|
client.subscribe(userdata['topics'], userdata['qos'])
|
||||||
|
|
||||||
def _on_connect(client, userdata, flags, rc):
|
|
||||||
"""Internal v5 callback"""
|
|
||||||
_on_connect_v5(client, userdata, flags, rc, None)
|
|
||||||
|
|
||||||
|
|
||||||
def _on_message_callback(client, userdata, message):
|
def _on_message_callback(client, userdata, message):
|
||||||
"""Internal callback"""
|
"""Internal callback"""
|
||||||
|
@ -77,40 +72,41 @@ def callback(callback, topics, qos=0, userdata=None, hostname="localhost",
|
||||||
to a list of topics. Incoming messages are processed by the user provided
|
to a list of topics. Incoming messages are processed by the user provided
|
||||||
callback. This is a blocking function and will never return.
|
callback. This is a blocking function and will never return.
|
||||||
|
|
||||||
callback : function of the form "on_message(client, userdata, message)" for
|
:param callback: function with the same signature as `on_message` for
|
||||||
processing the messages received.
|
processing the messages received.
|
||||||
|
|
||||||
topics : either a string containing a single topic to subscribe to, or a
|
:param topics: either a string containing a single topic to subscribe to, or a
|
||||||
list of topics to subscribe to.
|
list of topics to subscribe to.
|
||||||
|
|
||||||
qos : the qos to use when subscribing. This is applied to all topics.
|
:param int qos: the qos to use when subscribing. This is applied to all topics.
|
||||||
|
|
||||||
userdata : passed to the callback
|
:param userdata: passed to the callback
|
||||||
|
|
||||||
hostname : a string containing the address of the broker to connect to.
|
:param str hostname: the address of the broker to connect to.
|
||||||
Defaults to localhost.
|
Defaults to localhost.
|
||||||
|
|
||||||
port : the port to connect to the broker on. Defaults to 1883.
|
:param int port: the port to connect to the broker on. Defaults to 1883.
|
||||||
|
|
||||||
client_id : the MQTT client id to use. If "" or None, the Paho library will
|
:param str client_id: the MQTT client id to use. If "" or None, the Paho library will
|
||||||
generate a client id automatically.
|
generate a client id automatically.
|
||||||
|
|
||||||
keepalive : the keepalive timeout value for the client. Defaults to 60
|
:param int keepalive: the keepalive timeout value for the client. Defaults to 60
|
||||||
seconds.
|
seconds.
|
||||||
|
|
||||||
will : a dict containing will parameters for the client: will = {'topic':
|
:param will: a dict containing will parameters for the client: will = {'topic':
|
||||||
"<topic>", 'payload':"<payload">, 'qos':<qos>, 'retain':<retain>}.
|
"<topic>", 'payload':"<payload">, 'qos':<qos>, 'retain':<retain>}.
|
||||||
Topic is required, all other parameters are optional and will
|
Topic is required, all other parameters are optional and will
|
||||||
default to None, 0 and False respectively.
|
default to None, 0 and False respectively.
|
||||||
|
|
||||||
Defaults to None, which indicates no will should be used.
|
Defaults to None, which indicates no will should be used.
|
||||||
|
|
||||||
auth : a dict containing authentication parameters for the client:
|
:param auth: a dict containing authentication parameters for the client:
|
||||||
auth = {'username':"<username>", 'password':"<password>"}
|
auth = {'username':"<username>", 'password':"<password>"}
|
||||||
Username is required, password is optional and will default to None
|
Username is required, password is optional and will default to None
|
||||||
if not provided.
|
if not provided.
|
||||||
Defaults to None, which indicates no authentication is to be used.
|
Defaults to None, which indicates no authentication is to be used.
|
||||||
|
|
||||||
tls : a dict containing TLS configuration parameters for the client:
|
:param tls: a dict containing TLS configuration parameters for the client:
|
||||||
dict = {'ca_certs':"<ca_certs>", 'certfile':"<certfile>",
|
dict = {'ca_certs':"<ca_certs>", 'certfile':"<certfile>",
|
||||||
'keyfile':"<keyfile>", 'tls_version':"<tls_version>",
|
'keyfile':"<keyfile>", 'tls_version':"<tls_version>",
|
||||||
'ciphers':"<ciphers">, 'insecure':"<bool>"}
|
'ciphers':"<ciphers">, 'insecure':"<bool>"}
|
||||||
|
@ -121,17 +117,17 @@ def callback(callback, topics, qos=0, userdata=None, hostname="localhost",
|
||||||
processed using the tls_set_context method.
|
processed using the tls_set_context method.
|
||||||
Defaults to None, which indicates that TLS should not be used.
|
Defaults to None, which indicates that TLS should not be used.
|
||||||
|
|
||||||
transport : set to "tcp" to use the default setting of transport which is
|
:param str transport: set to "tcp" to use the default setting of transport which is
|
||||||
raw TCP. Set to "websockets" to use WebSockets as the transport.
|
raw TCP. Set to "websockets" to use WebSockets as the transport.
|
||||||
|
|
||||||
clean_session : a boolean that determines the client type. If True,
|
:param clean_session: a boolean that determines the client type. If True,
|
||||||
the broker will remove all information about this client
|
the broker will remove all information about this client
|
||||||
when it disconnects. If False, the client is a persistent
|
when it disconnects. If False, the client is a persistent
|
||||||
client and subscription information and queued messages
|
client and subscription information and queued messages
|
||||||
will be retained when the client disconnects.
|
will be retained when the client disconnects.
|
||||||
Defaults to True.
|
Defaults to True.
|
||||||
|
|
||||||
proxy_args: a dictionary that will be given to the client.
|
:param proxy_args: a dictionary that will be given to the client.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if qos < 0 or qos > 2:
|
if qos < 0 or qos > 2:
|
||||||
|
@ -143,13 +139,17 @@ def callback(callback, topics, qos=0, userdata=None, hostname="localhost",
|
||||||
'qos':qos,
|
'qos':qos,
|
||||||
'userdata':userdata}
|
'userdata':userdata}
|
||||||
|
|
||||||
client = paho.Client(client_id=client_id, userdata=callback_userdata,
|
client = paho.Client(
|
||||||
protocol=protocol, transport=transport,
|
paho.CallbackAPIVersion.VERSION2,
|
||||||
clean_session=clean_session)
|
client_id=client_id,
|
||||||
|
userdata=callback_userdata,
|
||||||
|
protocol=protocol,
|
||||||
|
transport=transport,
|
||||||
|
clean_session=clean_session,
|
||||||
|
)
|
||||||
|
client.enable_logger()
|
||||||
|
|
||||||
client.on_message = _on_message_callback
|
client.on_message = _on_message_callback
|
||||||
if protocol == mqtt.client.MQTTv5:
|
|
||||||
client.on_connect = _on_connect_v5
|
|
||||||
else:
|
|
||||||
client.on_connect = _on_connect
|
client.on_connect = _on_connect
|
||||||
|
|
||||||
if proxy_args is not None:
|
if proxy_args is not None:
|
||||||
|
@ -193,45 +193,45 @@ def simple(topics, qos=0, msg_count=1, retained=True, hostname="localhost",
|
||||||
to a list of topics. Once "msg_count" messages have been received, it
|
to a list of topics. Once "msg_count" messages have been received, it
|
||||||
disconnects cleanly from the broker and returns the messages.
|
disconnects cleanly from the broker and returns the messages.
|
||||||
|
|
||||||
topics : either a string containing a single topic to subscribe to, or a
|
:param topics: either a string containing a single topic to subscribe to, or a
|
||||||
list of topics to subscribe to.
|
list of topics to subscribe to.
|
||||||
|
|
||||||
qos : the qos to use when subscribing. This is applied to all topics.
|
:param int qos: the qos to use when subscribing. This is applied to all topics.
|
||||||
|
|
||||||
msg_count : the number of messages to retrieve from the broker.
|
:param int msg_count: the number of messages to retrieve from the broker.
|
||||||
if msg_count == 1 then a single MQTTMessage will be returned.
|
if msg_count == 1 then a single MQTTMessage will be returned.
|
||||||
if msg_count > 1 then a list of MQTTMessages will be returned.
|
if msg_count > 1 then a list of MQTTMessages will be returned.
|
||||||
|
|
||||||
retained : If set to True, retained messages will be processed the same as
|
:param bool retained: If set to True, retained messages will be processed the same as
|
||||||
non-retained messages. If set to False, retained messages will
|
non-retained messages. If set to False, retained messages will
|
||||||
be ignored. This means that with retained=False and msg_count=1,
|
be ignored. This means that with retained=False and msg_count=1,
|
||||||
the function will return the first message received that does
|
the function will return the first message received that does
|
||||||
not have the retained flag set.
|
not have the retained flag set.
|
||||||
|
|
||||||
hostname : a string containing the address of the broker to connect to.
|
:param str hostname: the address of the broker to connect to.
|
||||||
Defaults to localhost.
|
Defaults to localhost.
|
||||||
|
|
||||||
port : the port to connect to the broker on. Defaults to 1883.
|
:param int port: the port to connect to the broker on. Defaults to 1883.
|
||||||
|
|
||||||
client_id : the MQTT client id to use. If "" or None, the Paho library will
|
:param str client_id: the MQTT client id to use. If "" or None, the Paho library will
|
||||||
generate a client id automatically.
|
generate a client id automatically.
|
||||||
|
|
||||||
keepalive : the keepalive timeout value for the client. Defaults to 60
|
:param int keepalive: the keepalive timeout value for the client. Defaults to 60
|
||||||
seconds.
|
seconds.
|
||||||
|
|
||||||
will : a dict containing will parameters for the client: will = {'topic':
|
:param will: a dict containing will parameters for the client: will = {'topic':
|
||||||
"<topic>", 'payload':"<payload">, 'qos':<qos>, 'retain':<retain>}.
|
"<topic>", 'payload':"<payload">, 'qos':<qos>, 'retain':<retain>}.
|
||||||
Topic is required, all other parameters are optional and will
|
Topic is required, all other parameters are optional and will
|
||||||
default to None, 0 and False respectively.
|
default to None, 0 and False respectively.
|
||||||
Defaults to None, which indicates no will should be used.
|
Defaults to None, which indicates no will should be used.
|
||||||
|
|
||||||
auth : a dict containing authentication parameters for the client:
|
:param auth: a dict containing authentication parameters for the client:
|
||||||
auth = {'username':"<username>", 'password':"<password>"}
|
auth = {'username':"<username>", 'password':"<password>"}
|
||||||
Username is required, password is optional and will default to None
|
Username is required, password is optional and will default to None
|
||||||
if not provided.
|
if not provided.
|
||||||
Defaults to None, which indicates no authentication is to be used.
|
Defaults to None, which indicates no authentication is to be used.
|
||||||
|
|
||||||
tls : a dict containing TLS configuration parameters for the client:
|
:param tls: a dict containing TLS configuration parameters for the client:
|
||||||
dict = {'ca_certs':"<ca_certs>", 'certfile':"<certfile>",
|
dict = {'ca_certs':"<ca_certs>", 'certfile':"<certfile>",
|
||||||
'keyfile':"<keyfile>", 'tls_version':"<tls_version>",
|
'keyfile':"<keyfile>", 'tls_version':"<tls_version>",
|
||||||
'ciphers':"<ciphers">, 'insecure':"<bool>"}
|
'ciphers':"<ciphers">, 'insecure':"<bool>"}
|
||||||
|
@ -242,17 +242,20 @@ def simple(topics, qos=0, msg_count=1, retained=True, hostname="localhost",
|
||||||
processed using the tls_set_context method.
|
processed using the tls_set_context method.
|
||||||
Defaults to None, which indicates that TLS should not be used.
|
Defaults to None, which indicates that TLS should not be used.
|
||||||
|
|
||||||
transport : set to "tcp" to use the default setting of transport which is
|
:param protocol: the MQTT protocol version to use. Defaults to MQTTv311.
|
||||||
|
|
||||||
|
:param transport: set to "tcp" to use the default setting of transport which is
|
||||||
raw TCP. Set to "websockets" to use WebSockets as the transport.
|
raw TCP. Set to "websockets" to use WebSockets as the transport.
|
||||||
|
|
||||||
clean_session : a boolean that determines the client type. If True,
|
:param clean_session: a boolean that determines the client type. If True,
|
||||||
the broker will remove all information about this client
|
the broker will remove all information about this client
|
||||||
when it disconnects. If False, the client is a persistent
|
when it disconnects. If False, the client is a persistent
|
||||||
client and subscription information and queued messages
|
client and subscription information and queued messages
|
||||||
will be retained when the client disconnects.
|
will be retained when the client disconnects.
|
||||||
Defaults to True.
|
Defaults to True. If protocol is MQTTv50, clean_session
|
||||||
|
is ignored.
|
||||||
|
|
||||||
proxy_args: a dictionary that will be given to the client.
|
:param proxy_args: a dictionary that will be given to the client.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if msg_count < 1:
|
if msg_count < 1:
|
||||||
|
@ -265,6 +268,10 @@ def simple(topics, qos=0, msg_count=1, retained=True, hostname="localhost",
|
||||||
else:
|
else:
|
||||||
messages = []
|
messages = []
|
||||||
|
|
||||||
|
# Ignore clean_session if protocol is MQTTv50, otherwise Client will raise
|
||||||
|
if protocol == paho.MQTTv5:
|
||||||
|
clean_session = None
|
||||||
|
|
||||||
userdata = {'retained':retained, 'msg_count':msg_count, 'messages':messages}
|
userdata = {'retained':retained, 'msg_count':msg_count, 'messages':messages}
|
||||||
|
|
||||||
callback(_on_message_simple, topics, qos, userdata, hostname, port,
|
callback(_on_message_simple, topics, qos, userdata, hostname, port,
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
and Eclipse Distribution License v1.0 which accompany this distribution.
|
and Eclipse Distribution License v1.0 which accompany this distribution.
|
||||||
|
|
||||||
The Eclipse Public License is available at
|
The Eclipse Public License is available at
|
||||||
http://www.eclipse.org/legal/epl-v10.html
|
http://www.eclipse.org/legal/epl-v20.html
|
||||||
and the Eclipse Distribution License is available at
|
and the Eclipse Distribution License is available at
|
||||||
http://www.eclipse.org/org/documents/edl-v10.php.
|
http://www.eclipse.org/org/documents/edl-v10.php.
|
||||||
|
|
||||||
|
@ -16,14 +16,13 @@
|
||||||
*******************************************************************
|
*******************************************************************
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sys
|
|
||||||
|
|
||||||
|
|
||||||
class MQTTException(Exception):
|
class MQTTException(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class SubscribeOptions(object):
|
class SubscribeOptions:
|
||||||
"""The MQTT v5.0 subscribe options class.
|
"""The MQTT v5.0 subscribe options class.
|
||||||
|
|
||||||
The options are:
|
The options are:
|
||||||
|
@ -42,7 +41,13 @@ class SubscribeOptions(object):
|
||||||
RETAIN_SEND_ON_SUBSCRIBE, RETAIN_SEND_IF_NEW_SUB, RETAIN_DO_NOT_SEND = range(
|
RETAIN_SEND_ON_SUBSCRIBE, RETAIN_SEND_IF_NEW_SUB, RETAIN_DO_NOT_SEND = range(
|
||||||
0, 3)
|
0, 3)
|
||||||
|
|
||||||
def __init__(self, qos=0, noLocal=False, retainAsPublished=False, retainHandling=RETAIN_SEND_ON_SUBSCRIBE):
|
def __init__(
|
||||||
|
self,
|
||||||
|
qos: int = 0,
|
||||||
|
noLocal: bool = False,
|
||||||
|
retainAsPublished: bool = False,
|
||||||
|
retainHandling: int = RETAIN_SEND_ON_SUBSCRIBE,
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
qos: 0, 1 or 2. 0 is the default.
|
qos: 0, 1 or 2. 0 is the default.
|
||||||
noLocal: True or False. False is the default and corresponds to MQTT v3.1.1 behavior.
|
noLocal: True or False. False is the default and corresponds to MQTT v3.1.1 behavior.
|
||||||
|
@ -56,29 +61,27 @@ class SubscribeOptions(object):
|
||||||
self.noLocal = noLocal # bit 2
|
self.noLocal = noLocal # bit 2
|
||||||
self.retainAsPublished = retainAsPublished # bit 3
|
self.retainAsPublished = retainAsPublished # bit 3
|
||||||
self.retainHandling = retainHandling # bits 4 and 5: 0, 1 or 2
|
self.retainHandling = retainHandling # bits 4 and 5: 0, 1 or 2
|
||||||
assert self.QoS in [0, 1, 2]
|
if self.retainHandling not in (0, 1, 2):
|
||||||
assert self.retainHandling in [
|
raise AssertionError(f"Retain handling should be 0, 1 or 2, not {self.retainHandling}")
|
||||||
0, 1, 2], "Retain handling should be 0, 1 or 2"
|
if self.QoS not in (0, 1, 2):
|
||||||
|
raise AssertionError(f"QoS should be 0, 1 or 2, not {self.QoS}")
|
||||||
|
|
||||||
def __setattr__(self, name, value):
|
def __setattr__(self, name, value):
|
||||||
if name not in self.names:
|
if name not in self.names:
|
||||||
raise MQTTException(
|
raise MQTTException(
|
||||||
name + " Attribute name must be one of "+str(self.names))
|
f"{name} Attribute name must be one of {self.names}")
|
||||||
object.__setattr__(self, name, value)
|
object.__setattr__(self, name, value)
|
||||||
|
|
||||||
def pack(self):
|
def pack(self):
|
||||||
assert self.QoS in [0, 1, 2]
|
if self.retainHandling not in (0, 1, 2):
|
||||||
assert self.retainHandling in [
|
raise AssertionError(f"Retain handling should be 0, 1 or 2, not {self.retainHandling}")
|
||||||
0, 1, 2], "Retain handling should be 0, 1 or 2"
|
if self.QoS not in (0, 1, 2):
|
||||||
|
raise AssertionError(f"QoS should be 0, 1 or 2, not {self.QoS}")
|
||||||
noLocal = 1 if self.noLocal else 0
|
noLocal = 1 if self.noLocal else 0
|
||||||
retainAsPublished = 1 if self.retainAsPublished else 0
|
retainAsPublished = 1 if self.retainAsPublished else 0
|
||||||
data = [(self.retainHandling << 4) | (retainAsPublished << 3) |
|
data = [(self.retainHandling << 4) | (retainAsPublished << 3) |
|
||||||
(noLocal << 2) | self.QoS]
|
(noLocal << 2) | self.QoS]
|
||||||
if sys.version_info[0] >= 3:
|
return bytes(data)
|
||||||
buffer = bytes(data)
|
|
||||||
else:
|
|
||||||
buffer = bytearray(data)
|
|
||||||
return buffer
|
|
||||||
|
|
||||||
def unpack(self, buffer):
|
def unpack(self, buffer):
|
||||||
b0 = buffer[0]
|
b0 = buffer[0]
|
||||||
|
@ -86,10 +89,10 @@ class SubscribeOptions(object):
|
||||||
self.retainAsPublished = True if ((b0 >> 3) & 0x01) == 1 else False
|
self.retainAsPublished = True if ((b0 >> 3) & 0x01) == 1 else False
|
||||||
self.noLocal = True if ((b0 >> 2) & 0x01) == 1 else False
|
self.noLocal = True if ((b0 >> 2) & 0x01) == 1 else False
|
||||||
self.QoS = (b0 & 0x03)
|
self.QoS = (b0 & 0x03)
|
||||||
assert self.retainHandling in [
|
if self.retainHandling not in (0, 1, 2):
|
||||||
0, 1, 2], "Retain handling should be 0, 1 or 2, not %d" % self.retainHandling
|
raise AssertionError(f"Retain handling should be 0, 1 or 2, not {self.retainHandling}")
|
||||||
assert self.QoS in [
|
if self.QoS not in (0, 1, 2):
|
||||||
0, 1, 2], "QoS should be 0, 1 or 2, not %d" % self.QoS
|
raise AssertionError(f"QoS should be 0, 1 or 2, not {self.QoS}")
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# IANA versions like 2020a are not valid PEP 440 identifiers; the recommended
|
# IANA versions like 2020a are not valid PEP 440 identifiers; the recommended
|
||||||
# way to translate the version is to use YYYY.n where `n` is a 0-based index.
|
# way to translate the version is to use YYYY.n where `n` is a 0-based index.
|
||||||
__version__ = "2023.3"
|
__version__ = "2024.1"
|
||||||
|
|
||||||
# This exposes the original IANA version number.
|
# This exposes the original IANA version number.
|
||||||
IANA_VERSION = "2023c"
|
IANA_VERSION = "2024a"
|
||||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue