aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/python
diff options
context:
space:
mode:
authorrobot-piglet <robot-piglet@yandex-team.com>2024-04-04 14:31:37 +0300
committerrobot-piglet <robot-piglet@yandex-team.com>2024-04-04 14:41:27 +0300
commit8ca7961c8ff183dca63ff964f6142d24df5e03e8 (patch)
treea40fefbc0747d2a2e103dabdc392f6f13b628ab6 /contrib/python
parentdef02a26e8270f5177fd53cfcf8ce445e26bf583 (diff)
downloadydb-8ca7961c8ff183dca63ff964f6142d24df5e03e8.tar.gz
Intermediate changes
Diffstat (limited to 'contrib/python')
-rw-r--r--contrib/python/PyJWT/py2/.dist-info/METADATA115
-rw-r--r--contrib/python/PyJWT/py2/.dist-info/entry_points.txt3
-rw-r--r--contrib/python/PyJWT/py2/.dist-info/top_level.txt1
-rw-r--r--contrib/python/PyJWT/py2/jwt/__init__.py31
-rw-r--r--contrib/python/PyJWT/py2/jwt/__main__.py168
-rw-r--r--contrib/python/PyJWT/py2/jwt/algorithms.py403
-rw-r--r--contrib/python/PyJWT/py2/jwt/api_jws.py242
-rw-r--r--contrib/python/PyJWT/py2/jwt/api_jwt.py222
-rw-r--r--contrib/python/PyJWT/py2/jwt/compat.py68
-rw-r--r--contrib/python/PyJWT/py2/jwt/contrib/__init__.py0
-rw-r--r--contrib/python/PyJWT/py2/jwt/contrib/algorithms/__init__.py0
-rw-r--r--contrib/python/PyJWT/py2/jwt/contrib/algorithms/py_ecdsa.py60
-rw-r--r--contrib/python/PyJWT/py2/jwt/contrib/algorithms/pycrypto.py46
-rw-r--r--contrib/python/PyJWT/py2/jwt/exceptions.py59
-rw-r--r--contrib/python/PyJWT/py2/jwt/help.py61
-rw-r--r--contrib/python/PyJWT/py2/jwt/utils.py113
-rw-r--r--contrib/python/PyJWT/py2/ya.make43
-rw-r--r--contrib/python/cffi/py2/.dist-info/METADATA5
-rw-r--r--contrib/python/cffi/py2/.dist-info/entry_points.txt1
-rw-r--r--contrib/python/importlib-metadata/py3/.dist-info/METADATA3
-rw-r--r--contrib/python/importlib-metadata/py3/importlib_metadata/__init__.py18
-rw-r--r--contrib/python/importlib-metadata/py3/importlib_metadata/compat/__init__.py0
-rw-r--r--contrib/python/importlib-metadata/py3/importlib_metadata/compat/py39.py (renamed from contrib/python/importlib-metadata/py3/importlib_metadata/_py39compat.py)6
-rw-r--r--contrib/python/importlib-metadata/py3/ya.make5
-rw-r--r--contrib/python/lz4/py2/.dist-info/METADATA3
-rw-r--r--contrib/python/ruamel.yaml.clib/py2/.dist-info/METADATA1
-rw-r--r--contrib/python/ydb/py3/.dist-info/METADATA3
-rw-r--r--contrib/python/ydb/py3/ya.make3
-rw-r--r--contrib/python/ydb/py3/ydb/aio/iam.py109
-rw-r--r--contrib/python/ydb/py3/ydb/driver.py7
-rw-r--r--contrib/python/ydb/py3/ydb/iam/__init__.py1
-rw-r--r--contrib/python/ydb/py3/ydb/iam/auth.py151
-rw-r--r--contrib/python/ydb/py3/ydb/ydb_version.py2
33 files changed, 1903 insertions, 50 deletions
diff --git a/contrib/python/PyJWT/py2/.dist-info/METADATA b/contrib/python/PyJWT/py2/.dist-info/METADATA
new file mode 100644
index 0000000000..47ee558907
--- /dev/null
+++ b/contrib/python/PyJWT/py2/.dist-info/METADATA
@@ -0,0 +1,115 @@
+Metadata-Version: 2.1
+Name: PyJWT
+Version: 1.7.1
+Summary: JSON Web Token implementation in Python
+Home-page: http://github.com/jpadilla/pyjwt
+Author: Jose Padilla
+Author-email: hello@jpadilla.com
+License: MIT
+Keywords: jwt json web token security signing
+Platform: UNKNOWN
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: Natural Language :: English
+Classifier: License :: OSI Approved :: MIT License
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3.4
+Classifier: Programming Language :: Python :: 3.5
+Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3.7
+Classifier: Topic :: Utilities
+Provides-Extra: crypto
+Requires-Dist: cryptography (>=1.4) ; extra == 'crypto'
+Provides-Extra: flake8
+Requires-Dist: flake8 ; extra == 'flake8'
+Requires-Dist: flake8-import-order ; extra == 'flake8'
+Requires-Dist: pep8-naming ; extra == 'flake8'
+Provides-Extra: test
+Requires-Dist: pytest (<5.0.0,>=4.0.1) ; extra == 'test'
+Requires-Dist: pytest-cov (<3.0.0,>=2.6.0) ; extra == 'test'
+Requires-Dist: pytest-runner (<5.0.0,>=4.2) ; extra == 'test'
+
+PyJWT
+=====
+
+.. image:: https://travis-ci.com/jpadilla/pyjwt.svg?branch=master
+ :target: http://travis-ci.com/jpadilla/pyjwt?branch=master
+
+.. image:: https://ci.appveyor.com/api/projects/status/h8nt70aqtwhht39t?svg=true
+ :target: https://ci.appveyor.com/project/jpadilla/pyjwt
+
+.. image:: https://img.shields.io/pypi/v/pyjwt.svg
+ :target: https://pypi.python.org/pypi/pyjwt
+
+.. image:: https://coveralls.io/repos/jpadilla/pyjwt/badge.svg?branch=master
+ :target: https://coveralls.io/r/jpadilla/pyjwt?branch=master
+
+.. image:: https://readthedocs.org/projects/pyjwt/badge/?version=latest
+ :target: https://pyjwt.readthedocs.io
+
+A Python implementation of `RFC 7519 <https://tools.ietf.org/html/rfc7519>`_. Original implementation was written by `@progrium <https://github.com/progrium>`_.
+
+Sponsor
+-------
+
++--------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
+| |auth0-logo| | If you want to quickly add secure token-based authentication to Python projects, feel free to check Auth0's Python SDK and free plan at `auth0.com/overview <https://auth0.com/overview?utm_source=GHsponsor&utm_medium=GHsponsor&utm_campaign=pyjwt&utm_content=auth>`_. |
++--------------+-----------------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
+
+.. |auth0-logo| image:: https://user-images.githubusercontent.com/83319/31722733-de95bbde-b3ea-11e7-96bf-4f4e8f915588.png
+
+Installing
+----------
+
+Install with **pip**:
+
+.. code-block:: sh
+
+ $ pip install PyJWT
+
+
+Usage
+-----
+
+.. code:: python
+
+ >>> import jwt
+ >>> encoded = jwt.encode({'some': 'payload'}, 'secret', algorithm='HS256')
+ 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzb21lIjoicGF5bG9hZCJ9.4twFt5NiznN84AWoo1d7KO1T_yoc0Z6XOpOVswacPZg'
+
+ >>> jwt.decode(encoded, 'secret', algorithms=['HS256'])
+ {'some': 'payload'}
+
+
+Command line
+------------
+
+Usage::
+
+ pyjwt [options] INPUT
+
+Decoding examples::
+
+ pyjwt --key=secret decode TOKEN
+ pyjwt decode --no-verify TOKEN
+
+See more options executing ``pyjwt --help``.
+
+
+Documentation
+-------------
+
+View the full docs online at https://pyjwt.readthedocs.io/en/latest/
+
+
+Tests
+-----
+
+You can run tests from the project root after cloning with:
+
+.. code-block:: sh
+
+ $ python setup.py test
+
+
diff --git a/contrib/python/PyJWT/py2/.dist-info/entry_points.txt b/contrib/python/PyJWT/py2/.dist-info/entry_points.txt
new file mode 100644
index 0000000000..78717b2661
--- /dev/null
+++ b/contrib/python/PyJWT/py2/.dist-info/entry_points.txt
@@ -0,0 +1,3 @@
+[console_scripts]
+pyjwt = jwt.__main__:main
+
diff --git a/contrib/python/PyJWT/py2/.dist-info/top_level.txt b/contrib/python/PyJWT/py2/.dist-info/top_level.txt
new file mode 100644
index 0000000000..27ccc9bc3a
--- /dev/null
+++ b/contrib/python/PyJWT/py2/.dist-info/top_level.txt
@@ -0,0 +1 @@
+jwt
diff --git a/contrib/python/PyJWT/py2/jwt/__init__.py b/contrib/python/PyJWT/py2/jwt/__init__.py
new file mode 100644
index 0000000000..946983f022
--- /dev/null
+++ b/contrib/python/PyJWT/py2/jwt/__init__.py
@@ -0,0 +1,31 @@
+# -*- coding: utf-8 -*-
+# flake8: noqa
+
+"""
+JSON Web Token implementation
+
+Minimum implementation based on this spec:
+http://self-issued.info/docs/draft-jones-json-web-token-01.html
+"""
+
+
+__title__ = 'pyjwt'
+__version__ = '1.7.1'
+__author__ = 'José Padilla'
+__license__ = 'MIT'
+__copyright__ = 'Copyright 2015-2018 José Padilla'
+
+
+from .api_jwt import (
+ encode, decode, register_algorithm, unregister_algorithm,
+ get_unverified_header, PyJWT
+)
+from .api_jws import PyJWS
+from .exceptions import (
+ InvalidTokenError, DecodeError, InvalidAlgorithmError,
+ InvalidAudienceError, ExpiredSignatureError, ImmatureSignatureError,
+ InvalidIssuedAtError, InvalidIssuerError, ExpiredSignature,
+ InvalidAudience, InvalidIssuer, MissingRequiredClaimError,
+ InvalidSignatureError,
+ PyJWTError,
+)
diff --git a/contrib/python/PyJWT/py2/jwt/__main__.py b/contrib/python/PyJWT/py2/jwt/__main__.py
new file mode 100644
index 0000000000..bf50aabf4a
--- /dev/null
+++ b/contrib/python/PyJWT/py2/jwt/__main__.py
@@ -0,0 +1,168 @@
+#!/usr/bin/env python
+
+from __future__ import absolute_import, print_function
+
+import argparse
+import json
+import sys
+import time
+
+from . import DecodeError, __version__, decode, encode
+
+
+def encode_payload(args):
+ # Try to encode
+ if args.key is None:
+ raise ValueError('Key is required when encoding. See --help for usage.')
+
+ # Build payload object to encode
+ payload = {}
+
+ for arg in args.payload:
+ k, v = arg.split('=', 1)
+
+ # exp +offset special case?
+ if k == 'exp' and v[0] == '+' and len(v) > 1:
+ v = str(int(time.time()+int(v[1:])))
+
+ # Cast to integer?
+ if v.isdigit():
+ v = int(v)
+ else:
+ # Cast to float?
+ try:
+ v = float(v)
+ except ValueError:
+ pass
+
+ # Cast to true, false, or null?
+ constants = {'true': True, 'false': False, 'null': None}
+
+ if v in constants:
+ v = constants[v]
+
+ payload[k] = v
+
+ token = encode(
+ payload,
+ key=args.key,
+ algorithm=args.algorithm
+ )
+
+ return token.decode('utf-8')
+
+
+def decode_payload(args):
+ try:
+ if args.token:
+ token = args.token
+ else:
+ if sys.stdin.isatty():
+ token = sys.stdin.readline().strip()
+ else:
+ raise IOError('Cannot read from stdin: terminal not a TTY')
+
+ token = token.encode('utf-8')
+ data = decode(token, key=args.key, verify=args.verify)
+
+ return json.dumps(data)
+
+ except DecodeError as e:
+ raise DecodeError('There was an error decoding the token: %s' % e)
+
+
+def build_argparser():
+
+ usage = '''
+ Encodes or decodes JSON Web Tokens based on input.
+
+ %(prog)s [options] <command> [options] input
+
+ Decoding examples:
+
+ %(prog)s --key=secret decode json.web.token
+ %(prog)s decode --no-verify json.web.token
+
+ Encoding requires the key option and takes space separated key/value pairs
+ separated by equals (=) as input. Examples:
+
+ %(prog)s --key=secret encode iss=me exp=1302049071
+ %(prog)s --key=secret encode foo=bar exp=+10
+
+ The exp key is special and can take an offset to current Unix time.
+ '''
+
+ arg_parser = argparse.ArgumentParser(
+ prog='pyjwt',
+ usage=usage
+ )
+
+ arg_parser.add_argument(
+ '-v', '--version',
+ action='version',
+ version='%(prog)s ' + __version__
+ )
+
+ arg_parser.add_argument(
+ '--key',
+ dest='key',
+ metavar='KEY',
+ default=None,
+ help='set the secret key to sign with'
+ )
+
+ arg_parser.add_argument(
+ '--alg',
+ dest='algorithm',
+ metavar='ALG',
+ default='HS256',
+ help='set crypto algorithm to sign with. default=HS256'
+ )
+
+ subparsers = arg_parser.add_subparsers(
+ title='PyJWT subcommands',
+ description='valid subcommands',
+ help='additional help'
+ )
+
+ # Encode subcommand
+ encode_parser = subparsers.add_parser('encode', help='use to encode a supplied payload')
+
+ payload_help = """Payload to encode. Must be a space separated list of key/value
+ pairs separated by equals (=) sign."""
+
+ encode_parser.add_argument('payload', nargs='+', help=payload_help)
+ encode_parser.set_defaults(func=encode_payload)
+
+ # Decode subcommand
+ decode_parser = subparsers.add_parser('decode', help='use to decode a supplied JSON web token')
+ decode_parser.add_argument(
+ 'token',
+ help='JSON web token to decode.',
+ nargs='?')
+
+ decode_parser.add_argument(
+ '-n', '--no-verify',
+ action='store_false',
+ dest='verify',
+ default=True,
+ help='ignore signature and claims verification on decode'
+ )
+
+ decode_parser.set_defaults(func=decode_payload)
+
+ return arg_parser
+
+
+def main():
+ arg_parser = build_argparser()
+
+ try:
+ arguments = arg_parser.parse_args(sys.argv[1:])
+
+ output = arguments.func(arguments)
+
+ print(output)
+ except Exception as e:
+ print('There was an unforseen error: ', e)
+ arg_parser.print_help()
diff --git a/contrib/python/PyJWT/py2/jwt/algorithms.py b/contrib/python/PyJWT/py2/jwt/algorithms.py
new file mode 100644
index 0000000000..1343688341
--- /dev/null
+++ b/contrib/python/PyJWT/py2/jwt/algorithms.py
@@ -0,0 +1,403 @@
+import hashlib
+import hmac
+import json
+
+
+from .compat import constant_time_compare, string_types
+from .exceptions import InvalidKeyError
+from .utils import (
+ base64url_decode, base64url_encode, der_to_raw_signature,
+ force_bytes, force_unicode, from_base64url_uint, raw_to_der_signature,
+ to_base64url_uint
+)
+
+try:
+ from cryptography.hazmat.primitives import hashes
+ from cryptography.hazmat.primitives.serialization import (
+ load_pem_private_key, load_pem_public_key, load_ssh_public_key
+ )
+ from cryptography.hazmat.primitives.asymmetric.rsa import (
+ RSAPrivateKey, RSAPublicKey, RSAPrivateNumbers, RSAPublicNumbers,
+ rsa_recover_prime_factors, rsa_crt_dmp1, rsa_crt_dmq1, rsa_crt_iqmp
+ )
+ from cryptography.hazmat.primitives.asymmetric.ec import (
+ EllipticCurvePrivateKey, EllipticCurvePublicKey
+ )
+ from cryptography.hazmat.primitives.asymmetric import ec, padding
+ from cryptography.hazmat.backends import default_backend
+ from cryptography.exceptions import InvalidSignature
+
+ has_crypto = True
+except ImportError:
+ has_crypto = False
+
+requires_cryptography = set(['RS256', 'RS384', 'RS512', 'ES256', 'ES384',
+ 'ES521', 'ES512', 'PS256', 'PS384', 'PS512'])
+
+
+def get_default_algorithms():
+ """
+ Returns the algorithms that are implemented by the library.
+ """
+ default_algorithms = {
+ 'none': NoneAlgorithm(),
+ 'HS256': HMACAlgorithm(HMACAlgorithm.SHA256),
+ 'HS384': HMACAlgorithm(HMACAlgorithm.SHA384),
+ 'HS512': HMACAlgorithm(HMACAlgorithm.SHA512)
+ }
+
+ if has_crypto:
+ default_algorithms.update({
+ 'RS256': RSAAlgorithm(RSAAlgorithm.SHA256),
+ 'RS384': RSAAlgorithm(RSAAlgorithm.SHA384),
+ 'RS512': RSAAlgorithm(RSAAlgorithm.SHA512),
+ 'ES256': ECAlgorithm(ECAlgorithm.SHA256),
+ 'ES384': ECAlgorithm(ECAlgorithm.SHA384),
+ 'ES521': ECAlgorithm(ECAlgorithm.SHA512),
+ 'ES512': ECAlgorithm(ECAlgorithm.SHA512), # Backward compat for #219 fix
+ 'PS256': RSAPSSAlgorithm(RSAPSSAlgorithm.SHA256),
+ 'PS384': RSAPSSAlgorithm(RSAPSSAlgorithm.SHA384),
+ 'PS512': RSAPSSAlgorithm(RSAPSSAlgorithm.SHA512)
+ })
+
+ return default_algorithms
+
+
+class Algorithm(object):
+ """
+ The interface for an algorithm used to sign and verify tokens.
+ """
+ def prepare_key(self, key):
+ """
+ Performs necessary validation and conversions on the key and returns
+ the key value in the proper format for sign() and verify().
+ """
+ raise NotImplementedError
+
+ def sign(self, msg, key):
+ """
+ Returns a digital signature for the specified message
+ using the specified key value.
+ """
+ raise NotImplementedError
+
+ def verify(self, msg, key, sig):
+ """
+ Verifies that the specified digital signature is valid
+ for the specified message and key values.
+ """
+ raise NotImplementedError
+
+ @staticmethod
+ def to_jwk(key_obj):
+ """
+ Serializes a given RSA key into a JWK
+ """
+ raise NotImplementedError
+
+ @staticmethod
+ def from_jwk(jwk):
+ """
+ Deserializes a given RSA key from JWK back into a PublicKey or PrivateKey object
+ """
+ raise NotImplementedError
+
+
+class NoneAlgorithm(Algorithm):
+ """
+ Placeholder for use when no signing or verification
+ operations are required.
+ """
+ def prepare_key(self, key):
+ if key == '':
+ key = None
+
+ if key is not None:
+ raise InvalidKeyError('When alg = "none", key value must be None.')
+
+ return key
+
+ def sign(self, msg, key):
+ return b''
+
+ def verify(self, msg, key, sig):
+ return False
+
+
+class HMACAlgorithm(Algorithm):
+ """
+ Performs signing and verification operations using HMAC
+ and the specified hash function.
+ """
+ SHA256 = hashlib.sha256
+ SHA384 = hashlib.sha384
+ SHA512 = hashlib.sha512
+
+ def __init__(self, hash_alg):
+ self.hash_alg = hash_alg
+
+ def prepare_key(self, key):
+ key = force_bytes(key)
+
+ invalid_strings = [
+ b'-----BEGIN PUBLIC KEY-----',
+ b'-----BEGIN CERTIFICATE-----',
+ b'-----BEGIN RSA PUBLIC KEY-----',
+ b'ssh-rsa'
+ ]
+
+ if any([string_value in key for string_value in invalid_strings]):
+ raise InvalidKeyError(
+ 'The specified key is an asymmetric key or x509 certificate and'
+ ' should not be used as an HMAC secret.')
+
+ return key
+
+ @staticmethod
+ def to_jwk(key_obj):
+ return json.dumps({
+ 'k': force_unicode(base64url_encode(force_bytes(key_obj))),
+ 'kty': 'oct'
+ })
+
+ @staticmethod
+ def from_jwk(jwk):
+ obj = json.loads(jwk)
+
+ if obj.get('kty') != 'oct':
+ raise InvalidKeyError('Not an HMAC key')
+
+ return base64url_decode(obj['k'])
+
+ def sign(self, msg, key):
+ return hmac.new(key, msg, self.hash_alg).digest()
+
+ def verify(self, msg, key, sig):
+ return constant_time_compare(sig, self.sign(msg, key))
+
+
+if has_crypto:
+
+ class RSAAlgorithm(Algorithm):
+ """
+ Performs signing and verification operations using
+ RSASSA-PKCS-v1_5 and the specified hash function.
+ """
+ SHA256 = hashes.SHA256
+ SHA384 = hashes.SHA384
+ SHA512 = hashes.SHA512
+
+ def __init__(self, hash_alg):
+ self.hash_alg = hash_alg
+
+ def prepare_key(self, key):
+ if isinstance(key, RSAPrivateKey) or \
+ isinstance(key, RSAPublicKey):
+ return key
+
+ if isinstance(key, string_types):
+ key = force_bytes(key)
+
+ try:
+ if key.startswith(b'ssh-rsa'):
+ key = load_ssh_public_key(key, backend=default_backend())
+ else:
+ key = load_pem_private_key(key, password=None, backend=default_backend())
+ except ValueError:
+ key = load_pem_public_key(key, backend=default_backend())
+ else:
+ raise TypeError('Expecting a PEM-formatted key.')
+
+ return key
+
+ @staticmethod
+ def to_jwk(key_obj):
+ obj = None
+
+ if getattr(key_obj, 'private_numbers', None):
+ # Private key
+ numbers = key_obj.private_numbers()
+
+ obj = {
+ 'kty': 'RSA',
+ 'key_ops': ['sign'],
+ 'n': force_unicode(to_base64url_uint(numbers.public_numbers.n)),
+ 'e': force_unicode(to_base64url_uint(numbers.public_numbers.e)),
+ 'd': force_unicode(to_base64url_uint(numbers.d)),
+ 'p': force_unicode(to_base64url_uint(numbers.p)),
+ 'q': force_unicode(to_base64url_uint(numbers.q)),
+ 'dp': force_unicode(to_base64url_uint(numbers.dmp1)),
+ 'dq': force_unicode(to_base64url_uint(numbers.dmq1)),
+ 'qi': force_unicode(to_base64url_uint(numbers.iqmp))
+ }
+
+ elif getattr(key_obj, 'verify', None):
+ # Public key
+ numbers = key_obj.public_numbers()
+
+ obj = {
+ 'kty': 'RSA',
+ 'key_ops': ['verify'],
+ 'n': force_unicode(to_base64url_uint(numbers.n)),
+ 'e': force_unicode(to_base64url_uint(numbers.e))
+ }
+ else:
+ raise InvalidKeyError('Not a public or private key')
+
+ return json.dumps(obj)
+
+ @staticmethod
+ def from_jwk(jwk):
+ try:
+ obj = json.loads(jwk)
+ except ValueError:
+ raise InvalidKeyError('Key is not valid JSON')
+
+ if obj.get('kty') != 'RSA':
+ raise InvalidKeyError('Not an RSA key')
+
+ if 'd' in obj and 'e' in obj and 'n' in obj:
+ # Private key
+ if 'oth' in obj:
+ raise InvalidKeyError('Unsupported RSA private key: > 2 primes not supported')
+
+ other_props = ['p', 'q', 'dp', 'dq', 'qi']
+ props_found = [prop in obj for prop in other_props]
+ any_props_found = any(props_found)
+
+ if any_props_found and not all(props_found):
+ raise InvalidKeyError('RSA key must include all parameters if any are present besides d')
+
+ public_numbers = RSAPublicNumbers(
+ from_base64url_uint(obj['e']), from_base64url_uint(obj['n'])
+ )
+
+ if any_props_found:
+ numbers = RSAPrivateNumbers(
+ d=from_base64url_uint(obj['d']),
+ p=from_base64url_uint(obj['p']),
+ q=from_base64url_uint(obj['q']),
+ dmp1=from_base64url_uint(obj['dp']),
+ dmq1=from_base64url_uint(obj['dq']),
+ iqmp=from_base64url_uint(obj['qi']),
+ public_numbers=public_numbers
+ )
+ else:
+ d = from_base64url_uint(obj['d'])
+ p, q = rsa_recover_prime_factors(
+ public_numbers.n, d, public_numbers.e
+ )
+
+ numbers = RSAPrivateNumbers(
+ d=d,
+ p=p,
+ q=q,
+ dmp1=rsa_crt_dmp1(d, p),
+ dmq1=rsa_crt_dmq1(d, q),
+ iqmp=rsa_crt_iqmp(p, q),
+ public_numbers=public_numbers
+ )
+
+ return numbers.private_key(default_backend())
+ elif 'n' in obj and 'e' in obj:
+ # Public key
+ numbers = RSAPublicNumbers(
+ from_base64url_uint(obj['e']), from_base64url_uint(obj['n'])
+ )
+
+ return numbers.public_key(default_backend())
+ else:
+ raise InvalidKeyError('Not a public or private key')
+
+ def sign(self, msg, key):
+ return key.sign(msg, padding.PKCS1v15(), self.hash_alg())
+
+ def verify(self, msg, key, sig):
+ try:
+ key.verify(sig, msg, padding.PKCS1v15(), self.hash_alg())
+ return True
+ except InvalidSignature:
+ return False
+
+ class ECAlgorithm(Algorithm):
+ """
+ Performs signing and verification operations using
+ ECDSA and the specified hash function
+ """
+ SHA256 = hashes.SHA256
+ SHA384 = hashes.SHA384
+ SHA512 = hashes.SHA512
+
+ def __init__(self, hash_alg):
+ self.hash_alg = hash_alg
+
+ def prepare_key(self, key):
+ if isinstance(key, EllipticCurvePrivateKey) or \
+ isinstance(key, EllipticCurvePublicKey):
+ return key
+
+ if isinstance(key, string_types):
+ key = force_bytes(key)
+
+ # Attempt to load key. We don't know if it's
+ # a Signing Key or a Verifying Key, so we try
+ # the Verifying Key first.
+ try:
+ if key.startswith(b'ecdsa-sha2-'):
+ key = load_ssh_public_key(key, backend=default_backend())
+ else:
+ key = load_pem_public_key(key, backend=default_backend())
+ except ValueError:
+ key = load_pem_private_key(key, password=None, backend=default_backend())
+
+ else:
+ raise TypeError('Expecting a PEM-formatted key.')
+
+ return key
+
+ def sign(self, msg, key):
+ der_sig = key.sign(msg, ec.ECDSA(self.hash_alg()))
+
+ return der_to_raw_signature(der_sig, key.curve)
+
+ def verify(self, msg, key, sig):
+ try:
+ der_sig = raw_to_der_signature(sig, key.curve)
+ except ValueError:
+ return False
+
+ try:
+ key.verify(der_sig, msg, ec.ECDSA(self.hash_alg()))
+ return True
+ except InvalidSignature:
+ return False
+
+ class RSAPSSAlgorithm(RSAAlgorithm):
+ """
+ Performs a signature using RSASSA-PSS with MGF1
+ """
+
+ def sign(self, msg, key):
+ return key.sign(
+ msg,
+ padding.PSS(
+ mgf=padding.MGF1(self.hash_alg()),
+ salt_length=self.hash_alg.digest_size
+ ),
+ self.hash_alg()
+ )
+
+ def verify(self, msg, key, sig):
+ try:
+ key.verify(
+ sig,
+ msg,
+ padding.PSS(
+ mgf=padding.MGF1(self.hash_alg()),
+ salt_length=self.hash_alg.digest_size
+ ),
+ self.hash_alg()
+ )
+ return True
+ except InvalidSignature:
+ return False
diff --git a/contrib/python/PyJWT/py2/jwt/api_jws.py b/contrib/python/PyJWT/py2/jwt/api_jws.py
new file mode 100644
index 0000000000..a9354adb06
--- /dev/null
+++ b/contrib/python/PyJWT/py2/jwt/api_jws.py
@@ -0,0 +1,242 @@
+import binascii
+import json
+import warnings
+try:
+ # import required by mypy to perform type checking, not used for normal execution
+ from typing import Callable, Dict, List, Optional, Union # NOQA
+except ImportError:
+ pass
+
+from .algorithms import (
+ Algorithm, get_default_algorithms, has_crypto, requires_cryptography # NOQA
+)
+from .compat import Mapping, binary_type, string_types, text_type
+from .exceptions import (
+ DecodeError, InvalidAlgorithmError, InvalidSignatureError,
+ InvalidTokenError
+)
+from .utils import base64url_decode, base64url_encode, force_bytes, merge_dict
+
+
+class PyJWS(object):
+ header_typ = 'JWT'
+
+ def __init__(self, algorithms=None, options=None):
+ self._algorithms = get_default_algorithms()
+ self._valid_algs = (set(algorithms) if algorithms is not None
+ else set(self._algorithms))
+
+ # Remove algorithms that aren't on the whitelist
+ for key in list(self._algorithms.keys()):
+ if key not in self._valid_algs:
+ del self._algorithms[key]
+
+ if not options:
+ options = {}
+
+ self.options = merge_dict(self._get_default_options(), options)
+
+ @staticmethod
+ def _get_default_options():
+ return {
+ 'verify_signature': True
+ }
+
+ def register_algorithm(self, alg_id, alg_obj):
+ """
+ Registers a new Algorithm for use when creating and verifying tokens.
+ """
+ if alg_id in self._algorithms:
+ raise ValueError('Algorithm already has a handler.')
+
+ if not isinstance(alg_obj, Algorithm):
+ raise TypeError('Object is not of type `Algorithm`')
+
+ self._algorithms[alg_id] = alg_obj
+ self._valid_algs.add(alg_id)
+
+ def unregister_algorithm(self, alg_id):
+ """
+ Unregisters an Algorithm for use when creating and verifying tokens
+ Throws KeyError if algorithm is not registered.
+ """
+ if alg_id not in self._algorithms:
+ raise KeyError('The specified algorithm could not be removed'
+ ' because it is not registered.')
+
+ del self._algorithms[alg_id]
+ self._valid_algs.remove(alg_id)
+
+ def get_algorithms(self):
+ """
+ Returns a list of supported values for the 'alg' parameter.
+ """
+ return list(self._valid_algs)
+
+ def encode(self,
+ payload, # type: Union[Dict, bytes]
+ key, # type: str
+ algorithm='HS256', # type: str
+ headers=None, # type: Optional[Dict]
+ json_encoder=None # type: Optional[Callable]
+ ):
+ segments = []
+
+ if algorithm is None:
+ algorithm = 'none'
+
+ if algorithm not in self._valid_algs:
+ pass
+
+ # Header
+ header = {'typ': self.header_typ, 'alg': algorithm}
+
+ if headers:
+ self._validate_headers(headers)
+ header.update(headers)
+
+ json_header = force_bytes(
+ json.dumps(
+ header,
+ separators=(',', ':'),
+ cls=json_encoder
+ )
+ )
+
+ segments.append(base64url_encode(json_header))
+ segments.append(base64url_encode(payload))
+
+ # Segments
+ signing_input = b'.'.join(segments)
+ try:
+ alg_obj = self._algorithms[algorithm]
+ key = alg_obj.prepare_key(key)
+ signature = alg_obj.sign(signing_input, key)
+
+ except KeyError:
+ if not has_crypto and algorithm in requires_cryptography:
+ raise NotImplementedError(
+ "Algorithm '%s' could not be found. Do you have cryptography "
+ "installed?" % algorithm
+ )
+ else:
+ raise NotImplementedError('Algorithm not supported')
+
+ segments.append(base64url_encode(signature))
+
+ return b'.'.join(segments)
+
+ def decode(self,
+ jwt, # type: str
+ key='', # type: str
+ verify=True, # type: bool
+ algorithms=None, # type: List[str]
+ options=None, # type: Dict
+ **kwargs):
+
+ merged_options = merge_dict(self.options, options)
+ verify_signature = merged_options['verify_signature']
+
+ if verify_signature and not algorithms:
+ warnings.warn(
+ 'It is strongly recommended that you pass in a ' +
+ 'value for the "algorithms" argument when calling decode(). ' +
+ 'This argument will be mandatory in a future version.',
+ DeprecationWarning
+ )
+
+ payload, signing_input, header, signature = self._load(jwt)
+
+ if not verify:
+ warnings.warn('The verify parameter is deprecated. '
+ 'Please use verify_signature in options instead.',
+ DeprecationWarning, stacklevel=2)
+ elif verify_signature:
+ self._verify_signature(payload, signing_input, header, signature,
+ key, algorithms)
+
+ return payload
+
+ def get_unverified_header(self, jwt):
+ """Returns back the JWT header parameters as a dict()
+
+ Note: The signature is not verified so the header parameters
+ should not be fully trusted until signature verification is complete
+ """
+ headers = self._load(jwt)[2]
+ self._validate_headers(headers)
+
+ return headers
+
+ def _load(self, jwt):
+ if isinstance(jwt, text_type):
+ jwt = jwt.encode('utf-8')
+
+ if not issubclass(type(jwt), binary_type):
+ raise DecodeError("Invalid token type. Token must be a {0}".format(
+ binary_type))
+
+ try:
+ signing_input, crypto_segment = jwt.rsplit(b'.', 1)
+ header_segment, payload_segment = signing_input.split(b'.', 1)
+ except ValueError:
+ raise DecodeError('Not enough segments')
+
+ try:
+ header_data = base64url_decode(header_segment)
+ except (TypeError, binascii.Error):
+ raise DecodeError('Invalid header padding')
+
+ try:
+ header = json.loads(header_data.decode('utf-8'))
+ except ValueError as e:
+ raise DecodeError('Invalid header string: %s' % e)
+
+ if not isinstance(header, Mapping):
+ raise DecodeError('Invalid header string: must be a json object')
+
+ try:
+ payload = base64url_decode(payload_segment)
+ except (TypeError, binascii.Error):
+ raise DecodeError('Invalid payload padding')
+
+ try:
+ signature = base64url_decode(crypto_segment)
+ except (TypeError, binascii.Error):
+ raise DecodeError('Invalid crypto padding')
+
+ return (payload, signing_input, header, signature)
+
+ def _verify_signature(self, payload, signing_input, header, signature,
+ key='', algorithms=None):
+
+ alg = header.get('alg')
+
+ if algorithms is not None and alg not in algorithms:
+ raise InvalidAlgorithmError('The specified alg value is not allowed')
+
+ try:
+ alg_obj = self._algorithms[alg]
+ key = alg_obj.prepare_key(key)
+
+ if not alg_obj.verify(signing_input, key, signature):
+ raise InvalidSignatureError('Signature verification failed')
+
+ except KeyError:
+ raise InvalidAlgorithmError('Algorithm not supported')
+
+ def _validate_headers(self, headers):
+ if 'kid' in headers:
+ self._validate_kid(headers['kid'])
+
+ def _validate_kid(self, kid):
+ if not isinstance(kid, string_types):
+ raise InvalidTokenError('Key ID header parameter must be a string')
+
+
+_jws_global_obj = PyJWS()
+encode = _jws_global_obj.encode
+decode = _jws_global_obj.decode
+register_algorithm = _jws_global_obj.register_algorithm
+unregister_algorithm = _jws_global_obj.unregister_algorithm
+get_unverified_header = _jws_global_obj.get_unverified_header
diff --git a/contrib/python/PyJWT/py2/jwt/api_jwt.py b/contrib/python/PyJWT/py2/jwt/api_jwt.py
new file mode 100644
index 0000000000..85504acf93
--- /dev/null
+++ b/contrib/python/PyJWT/py2/jwt/api_jwt.py
@@ -0,0 +1,222 @@
+import json
+import warnings
+from calendar import timegm
+from datetime import datetime, timedelta
+try:
+ # import required by mypy to perform type checking, not used for normal execution
+ from typing import Callable, Dict, List, Optional, Union # NOQA
+except ImportError:
+ pass
+
+from .api_jws import PyJWS
+from .algorithms import Algorithm, get_default_algorithms # NOQA
+from .compat import Iterable, Mapping, string_types
+from .exceptions import (
+ DecodeError, ExpiredSignatureError, ImmatureSignatureError,
+ InvalidAudienceError, InvalidIssuedAtError,
+ InvalidIssuerError, MissingRequiredClaimError
+)
+from .utils import merge_dict
+
+
+class PyJWT(PyJWS):
+ header_type = 'JWT'
+
+ @staticmethod
+ def _get_default_options():
+ # type: () -> Dict[str, bool]
+ return {
+ 'verify_signature': True,
+ 'verify_exp': True,
+ 'verify_nbf': True,
+ 'verify_iat': True,
+ 'verify_aud': True,
+ 'verify_iss': True,
+ 'require_exp': False,
+ 'require_iat': False,
+ 'require_nbf': False
+ }
+
+ def encode(self,
+ payload, # type: Union[Dict, bytes]
+ key, # type: str
+ algorithm='HS256', # type: str
+ headers=None, # type: Optional[Dict]
+ json_encoder=None # type: Optional[Callable]
+ ):
+ # Check that we get a mapping
+ if not isinstance(payload, Mapping):
+ raise TypeError('Expecting a mapping object, as JWT only supports '
+ 'JSON objects as payloads.')
+
+ # Payload
+ for time_claim in ['exp', 'iat', 'nbf']:
+ # Convert datetime to a intDate value in known time-format claims
+ if isinstance(payload.get(time_claim), datetime):
+ payload[time_claim] = timegm(payload[time_claim].utctimetuple()) # type: ignore
+
+ json_payload = json.dumps(
+ payload,
+ separators=(',', ':'),
+ cls=json_encoder
+ ).encode('utf-8')
+
+ return super(PyJWT, self).encode(
+ json_payload, key, algorithm, headers, json_encoder
+ )
+
+ def decode(self,
+ jwt, # type: str
+ key='', # type: str
+ verify=True, # type: bool
+ algorithms=None, # type: List[str]
+ options=None, # type: Dict
+ **kwargs):
+
+ if verify and not algorithms:
+ warnings.warn(
+ 'It is strongly recommended that you pass in a ' +
+ 'value for the "algorithms" argument when calling decode(). ' +
+ 'This argument will be mandatory in a future version.',
+ DeprecationWarning
+ )
+
+ payload, _, _, _ = self._load(jwt)
+
+ if options is None:
+ options = {'verify_signature': verify}
+ else:
+ options.setdefault('verify_signature', verify)
+
+ decoded = super(PyJWT, self).decode(
+ jwt, key=key, algorithms=algorithms, options=options, **kwargs
+ )
+
+ try:
+ payload = json.loads(decoded.decode('utf-8'))
+ except ValueError as e:
+ raise DecodeError('Invalid payload string: %s' % e)
+ if not isinstance(payload, Mapping):
+ raise DecodeError('Invalid payload string: must be a json object')
+
+ if verify:
+ merged_options = merge_dict(self.options, options)
+ self._validate_claims(payload, merged_options, **kwargs)
+
+ return payload
+
+ def _validate_claims(self, payload, options, audience=None, issuer=None,
+ leeway=0, **kwargs):
+
+ if 'verify_expiration' in kwargs:
+ options['verify_exp'] = kwargs.get('verify_expiration', True)
+ warnings.warn('The verify_expiration parameter is deprecated. '
+ 'Please use verify_exp in options instead.',
+ DeprecationWarning)
+
+ if isinstance(leeway, timedelta):
+ leeway = leeway.total_seconds()
+
+ if not isinstance(audience, (string_types, type(None), Iterable)):
+ raise TypeError('audience must be a string, iterable, or None')
+
+ self._validate_required_claims(payload, options)
+
+ now = timegm(datetime.utcnow().utctimetuple())
+
+ if 'iat' in payload and options.get('verify_iat'):
+ self._validate_iat(payload, now, leeway)
+
+ if 'nbf' in payload and options.get('verify_nbf'):
+ self._validate_nbf(payload, now, leeway)
+
+ if 'exp' in payload and options.get('verify_exp'):
+ self._validate_exp(payload, now, leeway)
+
+ if options.get('verify_iss'):
+ self._validate_iss(payload, issuer)
+
+ if options.get('verify_aud'):
+ self._validate_aud(payload, audience)
+
+ def _validate_required_claims(self, payload, options):
+ if options.get('require_exp') and payload.get('exp') is None:
+ raise MissingRequiredClaimError('exp')
+
+ if options.get('require_iat') and payload.get('iat') is None:
+ raise MissingRequiredClaimError('iat')
+
+ if options.get('require_nbf') and payload.get('nbf') is None:
+ raise MissingRequiredClaimError('nbf')
+
+ def _validate_iat(self, payload, now, leeway):
+ try:
+ int(payload['iat'])
+ except ValueError:
+ raise InvalidIssuedAtError('Issued At claim (iat) must be an integer.')
+
+ def _validate_nbf(self, payload, now, leeway):
+ try:
+ nbf = int(payload['nbf'])
+ except ValueError:
+ raise DecodeError('Not Before claim (nbf) must be an integer.')
+
+ if nbf > (now + leeway):
+ raise ImmatureSignatureError('The token is not yet valid (nbf)')
+
+ def _validate_exp(self, payload, now, leeway):
+ try:
+ exp = int(payload['exp'])
+ except ValueError:
+ raise DecodeError('Expiration Time claim (exp) must be an'
+ ' integer.')
+
+ if exp < (now - leeway):
+ raise ExpiredSignatureError('Signature has expired')
+
+ def _validate_aud(self, payload, audience):
+ if audience is None and 'aud' not in payload:
+ return
+
+ if audience is not None and 'aud' not in payload:
+ # Application specified an audience, but it could not be
+ # verified since the token does not contain a claim.
+ raise MissingRequiredClaimError('aud')
+
+ if audience is None and 'aud' in payload:
+ # Application did not specify an audience, but
+ # the token has the 'aud' claim
+ raise InvalidAudienceError('Invalid audience')
+
+ audience_claims = payload['aud']
+
+ if isinstance(audience_claims, string_types):
+ audience_claims = [audience_claims]
+ if not isinstance(audience_claims, list):
+ raise InvalidAudienceError('Invalid claim format in token')
+ if any(not isinstance(c, string_types) for c in audience_claims):
+ raise InvalidAudienceError('Invalid claim format in token')
+
+ if isinstance(audience, string_types):
+ audience = [audience]
+
+ if not any(aud in audience_claims for aud in audience):
+ raise InvalidAudienceError('Invalid audience')
+
+ def _validate_iss(self, payload, issuer):
+ if issuer is None:
+ return
+
+ if 'iss' not in payload:
+ raise MissingRequiredClaimError('iss')
+
+ if payload['iss'] != issuer:
+ raise InvalidIssuerError('Invalid issuer')
+
+
+_jwt_global_obj = PyJWT()
+encode = _jwt_global_obj.encode
+decode = _jwt_global_obj.decode
+register_algorithm = _jwt_global_obj.register_algorithm
+unregister_algorithm = _jwt_global_obj.unregister_algorithm
+get_unverified_header = _jwt_global_obj.get_unverified_header
diff --git a/contrib/python/PyJWT/py2/jwt/compat.py b/contrib/python/PyJWT/py2/jwt/compat.py
new file mode 100644
index 0000000000..e79e258e56
--- /dev/null
+++ b/contrib/python/PyJWT/py2/jwt/compat.py
@@ -0,0 +1,68 @@
+"""
+The `compat` module provides support for backwards compatibility with older
+versions of python, and compatibility wrappers around optional packages.
+"""
+# flake8: noqa
+import hmac
+import struct
+import sys
+
+
+PY3 = sys.version_info[0] == 3
+
+
+if PY3:
+ text_type = str
+ binary_type = bytes
+else:
+ text_type = unicode
+ binary_type = str
+
+string_types = (text_type, binary_type)
+
+try:
+ # Importing ABCs from collections will be removed in PY3.8
+ from collections.abc import Iterable, Mapping
+except ImportError:
+ from collections import Iterable, Mapping
+
+try:
+ constant_time_compare = hmac.compare_digest
+except AttributeError:
+ # Fallback for Python < 2.7
+ def constant_time_compare(val1, val2):
+ """
+ Returns True if the two strings are equal, False otherwise.
+
+ The time taken is independent of the number of characters that match.
+ """
+ if len(val1) != len(val2):
+ return False
+
+ result = 0
+
+ for x, y in zip(val1, val2):
+ result |= ord(x) ^ ord(y)
+
+ return result == 0
+
+# Use int.to_bytes if it exists (Python 3)
+if getattr(int, 'to_bytes', None):
+ def bytes_from_int(val):
+ remaining = val
+ byte_length = 0
+
+ while remaining != 0:
+ remaining = remaining >> 8
+ byte_length += 1
+
+ return val.to_bytes(byte_length, 'big', signed=False)
+else:
+ def bytes_from_int(val):
+ buf = []
+ while val:
+ val, remainder = divmod(val, 256)
+ buf.append(remainder)
+
+ buf.reverse()
+ return struct.pack('%sB' % len(buf), *buf)
diff --git a/contrib/python/PyJWT/py2/jwt/contrib/__init__.py b/contrib/python/PyJWT/py2/jwt/contrib/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/contrib/python/PyJWT/py2/jwt/contrib/__init__.py
diff --git a/contrib/python/PyJWT/py2/jwt/contrib/algorithms/__init__.py b/contrib/python/PyJWT/py2/jwt/contrib/algorithms/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/contrib/python/PyJWT/py2/jwt/contrib/algorithms/__init__.py
diff --git a/contrib/python/PyJWT/py2/jwt/contrib/algorithms/py_ecdsa.py b/contrib/python/PyJWT/py2/jwt/contrib/algorithms/py_ecdsa.py
new file mode 100644
index 0000000000..bf0dea5ae2
--- /dev/null
+++ b/contrib/python/PyJWT/py2/jwt/contrib/algorithms/py_ecdsa.py
@@ -0,0 +1,60 @@
+# Note: This file is named py_ecdsa.py because import behavior in Python 2
+# would cause ecdsa.py to squash the ecdsa library that it depends upon.
+
+import hashlib
+
+import ecdsa
+
+from jwt.algorithms import Algorithm
+from jwt.compat import string_types, text_type
+
+
+class ECAlgorithm(Algorithm):
+ """
+ Performs signing and verification operations using
+ ECDSA and the specified hash function
+
+ This class requires the ecdsa package to be installed.
+
+ This is based off of the implementation in PyJWT 0.3.2
+ """
+ SHA256 = hashlib.sha256
+ SHA384 = hashlib.sha384
+ SHA512 = hashlib.sha512
+
+ def __init__(self, hash_alg):
+ self.hash_alg = hash_alg
+
+ def prepare_key(self, key):
+
+ if isinstance(key, ecdsa.SigningKey) or \
+ isinstance(key, ecdsa.VerifyingKey):
+ return key
+
+ if isinstance(key, string_types):
+ if isinstance(key, text_type):
+ key = key.encode('utf-8')
+
+ # Attempt to load key. We don't know if it's
+ # a Signing Key or a Verifying Key, so we try
+ # the Verifying Key first.
+ try:
+ key = ecdsa.VerifyingKey.from_pem(key)
+ except ecdsa.der.UnexpectedDER:
+ key = ecdsa.SigningKey.from_pem(key)
+
+ else:
+ raise TypeError('Expecting a PEM-formatted key.')
+
+ return key
+
+ def sign(self, msg, key):
+ return key.sign(msg, hashfunc=self.hash_alg,
+ sigencode=ecdsa.util.sigencode_string)
+
+ def verify(self, msg, key, sig):
+ try:
+ return key.verify(sig, msg, hashfunc=self.hash_alg,
+ sigdecode=ecdsa.util.sigdecode_string)
+ except AssertionError:
+ return False
diff --git a/contrib/python/PyJWT/py2/jwt/contrib/algorithms/pycrypto.py b/contrib/python/PyJWT/py2/jwt/contrib/algorithms/pycrypto.py
new file mode 100644
index 0000000000..e49cdbfe40
--- /dev/null
+++ b/contrib/python/PyJWT/py2/jwt/contrib/algorithms/pycrypto.py
@@ -0,0 +1,46 @@
+import Crypto.Hash.SHA256
+import Crypto.Hash.SHA384
+import Crypto.Hash.SHA512
+from Crypto.PublicKey import RSA
+from Crypto.Signature import PKCS1_v1_5
+
+from jwt.algorithms import Algorithm
+from jwt.compat import string_types, text_type
+
+
+class RSAAlgorithm(Algorithm):
+ """
+ Performs signing and verification operations using
+ RSASSA-PKCS-v1_5 and the specified hash function.
+
+ This class requires PyCrypto package to be installed.
+
+ This is based off of the implementation in PyJWT 0.3.2
+ """
+ SHA256 = Crypto.Hash.SHA256
+ SHA384 = Crypto.Hash.SHA384
+ SHA512 = Crypto.Hash.SHA512
+
+ def __init__(self, hash_alg):
+ self.hash_alg = hash_alg
+
+ def prepare_key(self, key):
+
+ if isinstance(key, RSA._RSAobj):
+ return key
+
+ if isinstance(key, string_types):
+ if isinstance(key, text_type):
+ key = key.encode('utf-8')
+
+ key = RSA.importKey(key)
+ else:
+ raise TypeError('Expecting a PEM- or RSA-formatted key.')
+
+ return key
+
+ def sign(self, msg, key):
+ return PKCS1_v1_5.new(key).sign(self.hash_alg.new(msg))
+
+ def verify(self, msg, key, sig):
+ return PKCS1_v1_5.new(key).verify(self.hash_alg.new(msg), sig)
diff --git a/contrib/python/PyJWT/py2/jwt/exceptions.py b/contrib/python/PyJWT/py2/jwt/exceptions.py
new file mode 100644
index 0000000000..2a6aa596ba
--- /dev/null
+++ b/contrib/python/PyJWT/py2/jwt/exceptions.py
@@ -0,0 +1,59 @@
+class PyJWTError(Exception):
+ """
+ Base class for all exceptions
+ """
+ pass
+
+
+class InvalidTokenError(PyJWTError):
+ pass
+
+
+class DecodeError(InvalidTokenError):
+ pass
+
+
+class InvalidSignatureError(DecodeError):
+ pass
+
+
+class ExpiredSignatureError(InvalidTokenError):
+ pass
+
+
+class InvalidAudienceError(InvalidTokenError):
+ pass
+
+
+class InvalidIssuerError(InvalidTokenError):
+ pass
+
+
+class InvalidIssuedAtError(InvalidTokenError):
+ pass
+
+
+class ImmatureSignatureError(InvalidTokenError):
+ pass
+
+
+class InvalidKeyError(PyJWTError):
+ pass
+
+
+class InvalidAlgorithmError(InvalidTokenError):
+ pass
+
+
+class MissingRequiredClaimError(InvalidTokenError):
+ def __init__(self, claim):
+ self.claim = claim
+
+ def __str__(self):
+ return 'Token is missing the "%s" claim' % self.claim
+
+
+# Compatibility aliases (deprecated)
+ExpiredSignature = ExpiredSignatureError
+InvalidAudience = InvalidAudienceError
+InvalidIssuer = InvalidIssuerError
diff --git a/contrib/python/PyJWT/py2/jwt/help.py b/contrib/python/PyJWT/py2/jwt/help.py
new file mode 100644
index 0000000000..55e39ebb27
--- /dev/null
+++ b/contrib/python/PyJWT/py2/jwt/help.py
@@ -0,0 +1,61 @@
+from __future__ import print_function
+
+import json
+import platform
+import sys
+
+from . import __version__ as pyjwt_version
+
+try:
+ import cryptography
+except ImportError:
+ cryptography = None
+
+try:
+ import ecdsa
+except ImportError:
+ ecdsa = None
+
+
+def info():
+ """
+ Generate information for a bug report.
+ Based on the requests package help utility module.
+ """
+ try:
+ platform_info = {"system": platform.system(), "release": platform.release()}
+ except IOError:
+ platform_info = {"system": "Unknown", "release": "Unknown"}
+
+ implementation = platform.python_implementation()
+
+ if implementation == "CPython":
+ implementation_version = platform.python_version()
+ elif implementation == "PyPy":
+ implementation_version = "%s.%s.%s" % (
+ sys.pypy_version_info.major,
+ sys.pypy_version_info.minor,
+ sys.pypy_version_info.micro,
+ )
+ if sys.pypy_version_info.releaselevel != "final":
+ implementation_version = "".join(
+ [implementation_version, sys.pypy_version_info.releaselevel]
+ )
+ else:
+ implementation_version = "Unknown"
+
+ return {
+ "platform": platform_info,
+ "implementation": {"name": implementation, "version": implementation_version},
+ "cryptography": {"version": getattr(cryptography, "__version__", "")},
+ "pyjwt": {"version": pyjwt_version},
+ }
+
+
+def main():
+ """Pretty-print the bug information as JSON."""
+ print(json.dumps(info(), sort_keys=True, indent=2))
+
+
+if __name__ == "__main__":
+ main()
diff --git a/contrib/python/PyJWT/py2/jwt/utils.py b/contrib/python/PyJWT/py2/jwt/utils.py
new file mode 100644
index 0000000000..b33c7a2d45
--- /dev/null
+++ b/contrib/python/PyJWT/py2/jwt/utils.py
@@ -0,0 +1,113 @@
+import base64
+import binascii
+import struct
+
+from .compat import binary_type, bytes_from_int, text_type
+
+try:
+ from cryptography.hazmat.primitives.asymmetric.utils import (
+ decode_dss_signature, encode_dss_signature
+ )
+except ImportError:
+ pass
+
+
+def force_unicode(value):
+ if isinstance(value, binary_type):
+ return value.decode('utf-8')
+ elif isinstance(value, text_type):
+ return value
+ else:
+ raise TypeError('Expected a string value')
+
+
+def force_bytes(value):
+ if isinstance(value, text_type):
+ return value.encode('utf-8')
+ elif isinstance(value, binary_type):
+ return value
+ else:
+ raise TypeError('Expected a string value')
+
+
+def base64url_decode(input):
+ if isinstance(input, text_type):
+ input = input.encode('ascii')
+
+ rem = len(input) % 4
+
+ if rem > 0:
+ input += b'=' * (4 - rem)
+
+ return base64.urlsafe_b64decode(input)
+
+
+def base64url_encode(input):
+ return base64.urlsafe_b64encode(input).replace(b'=', b'')
+
+
+def to_base64url_uint(val):
+ if val < 0:
+ raise ValueError('Must be a positive integer')
+
+ int_bytes = bytes_from_int(val)
+
+ if len(int_bytes) == 0:
+ int_bytes = b'\x00'
+
+ return base64url_encode(int_bytes)
+
+
+def from_base64url_uint(val):
+ if isinstance(val, text_type):
+ val = val.encode('ascii')
+
+ data = base64url_decode(val)
+
+ buf = struct.unpack('%sB' % len(data), data)
+ return int(''.join(["%02x" % byte for byte in buf]), 16)
+
+
+def merge_dict(original, updates):
+ if not updates:
+ return original
+
+ try:
+ merged_options = original.copy()
+ merged_options.update(updates)
+ except (AttributeError, ValueError) as e:
+ raise TypeError('original and updates must be a dictionary: %s' % e)
+
+ return merged_options
+
+
+def number_to_bytes(num, num_bytes):
+ padded_hex = '%0*x' % (2 * num_bytes, num)
+ big_endian = binascii.a2b_hex(padded_hex.encode('ascii'))
+ return big_endian
+
+
+def bytes_to_number(string):
+ return int(binascii.b2a_hex(string), 16)
+
+
+def der_to_raw_signature(der_sig, curve):
+ num_bits = curve.key_size
+ num_bytes = (num_bits + 7) // 8
+
+ r, s = decode_dss_signature(der_sig)
+
+ return number_to_bytes(r, num_bytes) + number_to_bytes(s, num_bytes)
+
+
+def raw_to_der_signature(raw_sig, curve):
+ num_bits = curve.key_size
+ num_bytes = (num_bits + 7) // 8
+
+ if len(raw_sig) != 2 * num_bytes:
+ raise ValueError('Invalid signature')
+
+ r = bytes_to_number(raw_sig[:num_bytes])
+ s = bytes_to_number(raw_sig[num_bytes:])
+
+ return encode_dss_signature(r, s)
diff --git a/contrib/python/PyJWT/py2/ya.make b/contrib/python/PyJWT/py2/ya.make
new file mode 100644
index 0000000000..57a9352fba
--- /dev/null
+++ b/contrib/python/PyJWT/py2/ya.make
@@ -0,0 +1,43 @@
+# Generated by devtools/yamaker (pypi).
+
+PY2_LIBRARY()
+
+VERSION(1.7.1)
+
+LICENSE(MIT)
+
+PEERDIR(
+ contrib/python/cryptography
+)
+
+NO_LINT()
+
+NO_CHECK_IMPORTS(
+ jwt.contrib.*
+)
+
+PY_SRCS(
+ TOP_LEVEL
+ jwt/__init__.py
+ jwt/__main__.py
+ jwt/algorithms.py
+ jwt/api_jws.py
+ jwt/api_jwt.py
+ jwt/compat.py
+ jwt/contrib/__init__.py
+ jwt/contrib/algorithms/__init__.py
+ jwt/contrib/algorithms/py_ecdsa.py
+ jwt/contrib/algorithms/pycrypto.py
+ jwt/exceptions.py
+ jwt/help.py
+ jwt/utils.py
+)
+
+RESOURCE_FILES(
+ PREFIX contrib/python/PyJWT/py2/
+ .dist-info/METADATA
+ .dist-info/entry_points.txt
+ .dist-info/top_level.txt
+)
+
+END()
diff --git a/contrib/python/cffi/py2/.dist-info/METADATA b/contrib/python/cffi/py2/.dist-info/METADATA
index 538e679147..abe2ac79bb 100644
--- a/contrib/python/cffi/py2/.dist-info/METADATA
+++ b/contrib/python/cffi/py2/.dist-info/METADATA
@@ -6,6 +6,7 @@ Home-page: http://cffi.readthedocs.org
Author: Armin Rigo, Maciej Fijalkowski
Author-email: python-cffi@googlegroups.com
License: MIT
+Platform: UNKNOWN
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 2
Classifier: Programming Language :: Python :: 2.7
@@ -18,10 +19,8 @@ Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Python :: Implementation :: PyPy
Classifier: License :: OSI Approved :: MIT License
-License-File: LICENSE
Requires-Dist: pycparser
-
CFFI
====
@@ -32,3 +31,5 @@ Contact
-------
`Mailing list <https://groups.google.com/forum/#!forum/python-cffi>`_
+
+
diff --git a/contrib/python/cffi/py2/.dist-info/entry_points.txt b/contrib/python/cffi/py2/.dist-info/entry_points.txt
index 4b0274f233..eee7e0fb1f 100644
--- a/contrib/python/cffi/py2/.dist-info/entry_points.txt
+++ b/contrib/python/cffi/py2/.dist-info/entry_points.txt
@@ -1,2 +1,3 @@
[distutils.setup_keywords]
cffi_modules = cffi.setuptools_ext:cffi_modules
+
diff --git a/contrib/python/importlib-metadata/py3/.dist-info/METADATA b/contrib/python/importlib-metadata/py3/.dist-info/METADATA
index 38ffc3a5f0..ec8a77c116 100644
--- a/contrib/python/importlib-metadata/py3/.dist-info/METADATA
+++ b/contrib/python/importlib-metadata/py3/.dist-info/METADATA
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: importlib_metadata
-Version: 7.0.2
+Version: 7.1.0
Summary: Read metadata from Python packages
Home-page: https://github.com/python/importlib_metadata
Author: Jason R. Coombs
@@ -32,6 +32,7 @@ Requires-Dist: packaging ; extra == 'testing'
Requires-Dist: pyfakefs ; extra == 'testing'
Requires-Dist: flufl.flake8 ; extra == 'testing'
Requires-Dist: pytest-perf >=0.9.2 ; extra == 'testing'
+Requires-Dist: jaraco.test >=5.4 ; extra == 'testing'
Requires-Dist: pytest-mypy ; (platform_python_implementation != "PyPy") and extra == 'testing'
Requires-Dist: importlib-resources >=1.3 ; (python_version < "3.9") and extra == 'testing'
diff --git a/contrib/python/importlib-metadata/py3/importlib_metadata/__init__.py b/contrib/python/importlib-metadata/py3/importlib_metadata/__init__.py
index a069efe55f..0ed3e8dfb5 100644
--- a/contrib/python/importlib-metadata/py3/importlib_metadata/__init__.py
+++ b/contrib/python/importlib-metadata/py3/importlib_metadata/__init__.py
@@ -3,7 +3,6 @@ from __future__ import annotations
import os
import re
import abc
-import csv
import sys
import json
import email
@@ -18,7 +17,8 @@ import itertools
import posixpath
import collections
-from . import _adapters, _meta, _py39compat
+from . import _adapters, _meta
+from .compat import py39
from ._collections import FreezableDefaultDict, Pair
from ._compat import (
NullFinder,
@@ -285,7 +285,7 @@ class EntryPoints(tuple):
Select entry points from self that match the
given parameters (typically group and/or name).
"""
- return EntryPoints(ep for ep in self if _py39compat.ep_matches(ep, **params))
+ return EntryPoints(ep for ep in self if py39.ep_matches(ep, **params))
@property
def names(self) -> Set[str]:
@@ -527,6 +527,10 @@ class Distribution(DeprecatedNonAbstract):
@pass_none
def make_files(lines):
+ # Delay csv import, since Distribution.files is not as widely used
+ # as other parts of importlib.metadata
+ import csv
+
return starmap(make_file, csv.reader(lines))
@pass_none
@@ -878,8 +882,9 @@ class MetadataPathFinder(NullFinder, DistributionFinder):
of Python that do not have a PathFinder find_distributions().
"""
+ @classmethod
def find_distributions(
- self, context=DistributionFinder.Context()
+ cls, context=DistributionFinder.Context()
) -> Iterable[PathDistribution]:
"""
Find distributions.
@@ -889,7 +894,7 @@ class MetadataPathFinder(NullFinder, DistributionFinder):
(or all names if ``None`` indicated) along the paths in the list
of directories ``context.path``.
"""
- found = self._search_paths(context.name, context.path)
+ found = cls._search_paths(context.name, context.path)
return map(PathDistribution, found)
@classmethod
@@ -900,6 +905,7 @@ class MetadataPathFinder(NullFinder, DistributionFinder):
path.search(prepared) for path in map(FastPath, paths)
)
+ @classmethod
def invalidate_caches(cls) -> None:
FastPath.__new__.cache_clear()
@@ -1054,7 +1060,7 @@ def version(distribution_name: str) -> str:
_unique = functools.partial(
unique_everseen,
- key=_py39compat.normalized_name,
+ key=py39.normalized_name,
)
"""
Wrapper for ``distributions`` to return unique distributions by name.
diff --git a/contrib/python/importlib-metadata/py3/importlib_metadata/compat/__init__.py b/contrib/python/importlib-metadata/py3/importlib_metadata/compat/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/contrib/python/importlib-metadata/py3/importlib_metadata/compat/__init__.py
diff --git a/contrib/python/importlib-metadata/py3/importlib_metadata/_py39compat.py b/contrib/python/importlib-metadata/py3/importlib_metadata/compat/py39.py
index fc6b82216f..1f15bd97e6 100644
--- a/contrib/python/importlib-metadata/py3/importlib_metadata/_py39compat.py
+++ b/contrib/python/importlib-metadata/py3/importlib_metadata/compat/py39.py
@@ -6,7 +6,7 @@ from typing import TYPE_CHECKING, Any, Optional
if TYPE_CHECKING: # pragma: no cover
# Prevent circular imports on runtime.
- from . import Distribution, EntryPoint
+ from .. import Distribution, EntryPoint
else:
Distribution = EntryPoint = Any
@@ -18,7 +18,7 @@ def normalized_name(dist: Distribution) -> Optional[str]:
try:
return dist._normalized_name
except AttributeError:
- from . import Prepared # -> delay to prevent circular imports.
+ from .. import Prepared # -> delay to prevent circular imports.
return Prepared.normalize(getattr(dist, "name", None) or dist.metadata['Name'])
@@ -30,7 +30,7 @@ def ep_matches(ep: EntryPoint, **params) -> bool:
try:
return ep.matches(**params)
except AttributeError:
- from . import EntryPoint # -> delay to prevent circular imports.
+ from .. import EntryPoint # -> delay to prevent circular imports.
# Reconstruct the EntryPoint object to make sure it is compatible.
return EntryPoint(ep.name, ep.value, ep.group).matches(**params)
diff --git a/contrib/python/importlib-metadata/py3/ya.make b/contrib/python/importlib-metadata/py3/ya.make
index 962408f997..00d69e1c37 100644
--- a/contrib/python/importlib-metadata/py3/ya.make
+++ b/contrib/python/importlib-metadata/py3/ya.make
@@ -2,7 +2,7 @@
PY3_LIBRARY()
-VERSION(7.0.2)
+VERSION(7.1.0)
LICENSE(Apache-2.0)
@@ -21,8 +21,9 @@ PY_SRCS(
importlib_metadata/_functools.py
importlib_metadata/_itertools.py
importlib_metadata/_meta.py
- importlib_metadata/_py39compat.py
importlib_metadata/_text.py
+ importlib_metadata/compat/__init__.py
+ importlib_metadata/compat/py39.py
importlib_metadata/diagnose.py
)
diff --git a/contrib/python/lz4/py2/.dist-info/METADATA b/contrib/python/lz4/py2/.dist-info/METADATA
index a896017502..1f76df926c 100644
--- a/contrib/python/lz4/py2/.dist-info/METADATA
+++ b/contrib/python/lz4/py2/.dist-info/METADATA
@@ -18,9 +18,10 @@ Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*
+Provides-Extra: docs
Provides-Extra: tests
Provides-Extra: flake8
-Provides-Extra: docs
+Requires-Dist: future
Provides-Extra: docs
Requires-Dist: sphinx (>=1.6.0); extra == 'docs'
Requires-Dist: sphinx-bootstrap-theme; extra == 'docs'
diff --git a/contrib/python/ruamel.yaml.clib/py2/.dist-info/METADATA b/contrib/python/ruamel.yaml.clib/py2/.dist-info/METADATA
index 6c952ac9d6..df2e2797e2 100644
--- a/contrib/python/ruamel.yaml.clib/py2/.dist-info/METADATA
+++ b/contrib/python/ruamel.yaml.clib/py2/.dist-info/METADATA
@@ -22,7 +22,6 @@ Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Description-Content-Type: text/x-rst
-
ruamel.yaml.clib
================
diff --git a/contrib/python/ydb/py3/.dist-info/METADATA b/contrib/python/ydb/py3/.dist-info/METADATA
index 71ddeb7b6f..d921ac7f1f 100644
--- a/contrib/python/ydb/py3/.dist-info/METADATA
+++ b/contrib/python/ydb/py3/.dist-info/METADATA
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: ydb
-Version: 3.8.1
+Version: 3.9.0
Summary: YDB Python SDK
Home-page: http://github.com/ydb-platform/ydb-python-sdk
Author: Yandex LLC
@@ -18,6 +18,7 @@ Requires-Dist: grpcio >=1.42.0
Requires-Dist: packaging
Requires-Dist: protobuf <5.0.0,>=3.13.0
Requires-Dist: aiohttp <4
+Requires-Dist: pyjwt ==2.8.0
Provides-Extra: yc
Requires-Dist: yandexcloud ; extra == 'yc'
diff --git a/contrib/python/ydb/py3/ya.make b/contrib/python/ydb/py3/ya.make
index 24ba22bc0f..b8611eae13 100644
--- a/contrib/python/ydb/py3/ya.make
+++ b/contrib/python/ydb/py3/ya.make
@@ -2,11 +2,12 @@
PY3_LIBRARY()
-VERSION(3.8.1)
+VERSION(3.9.0)
LICENSE(Apache-2.0)
PEERDIR(
+ contrib/python/PyJWT
contrib/python/aiohttp
contrib/python/grpcio
contrib/python/packaging
diff --git a/contrib/python/ydb/py3/ydb/aio/iam.py b/contrib/python/ydb/py3/ydb/aio/iam.py
index eab8faffe0..40622f8a9d 100644
--- a/contrib/python/ydb/py3/ydb/aio/iam.py
+++ b/contrib/python/ydb/py3/ydb/aio/iam.py
@@ -5,15 +5,19 @@ import abc
import logging
from ydb.iam import auth
from .credentials import AbstractExpiringTokenCredentials
+from ydb import issues
logger = logging.getLogger(__name__)
try:
- from yandex.cloud.iam.v1 import iam_token_service_pb2_grpc
- from yandex.cloud.iam.v1 import iam_token_service_pb2
import jwt
except ImportError:
jwt = None
+
+try:
+ from yandex.cloud.iam.v1 import iam_token_service_pb2_grpc
+ from yandex.cloud.iam.v1 import iam_token_service_pb2
+except ImportError:
iam_token_service_pb2_grpc = None
iam_token_service_pb2 = None
@@ -55,6 +59,51 @@ class TokenServiceCredentials(AbstractExpiringTokenCredentials):
IamTokenCredentials = TokenServiceCredentials
+class OAuth2JwtTokenExchangeCredentials(AbstractExpiringTokenCredentials, auth.BaseJWTCredentials):
+ def __init__(
+ self,
+ token_exchange_url,
+ account_id,
+ access_key_id,
+ private_key,
+ algorithm,
+ token_service_url,
+ subject=None,
+ ):
+ super(OAuth2JwtTokenExchangeCredentials, self).__init__()
+ auth.BaseJWTCredentials.__init__(
+ self, account_id, access_key_id, private_key, algorithm, token_service_url, subject
+ )
+ assert aiohttp is not None, "Install aiohttp library to use OAuth 2.0 token exchange credentials provider"
+ self._token_exchange_url = token_exchange_url
+
+ async def _make_token_request(self):
+ params = {
+ "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange",
+ "requested_token_type": "urn:ietf:params:oauth:token-type:access_token",
+ "subject_token": self._get_jwt(),
+ "subject_token_type": "urn:ietf:params:oauth:token-type:jwt",
+ }
+ headers = {"Content-Type": "application/x-www-form-urlencoded"}
+
+ timeout = aiohttp.ClientTimeout(total=2)
+ async with aiohttp.ClientSession(timeout=timeout) as session:
+ async with session.post(self._token_exchange_url, data=params, headers=headers) as response:
+ if response.status == 403:
+ raise issues.Unauthenticated(await response.text())
+ if response.status >= 500:
+ raise issues.Unavailable(await response.text())
+ if response.status >= 400:
+ raise issues.BadRequest(await response.text())
+ if response.status != 200:
+ raise issues.Error(await response.text())
+
+ response_json = await response.json()
+ access_token = response_json["access_token"]
+ expires_in = response_json["expires_in"]
+ return {"access_token": access_token, "expires_in": expires_in}
+
+
class JWTIamCredentials(TokenServiceCredentials, auth.BaseJWTCredentials):
def __init__(
self,
@@ -65,16 +114,39 @@ class JWTIamCredentials(TokenServiceCredentials, auth.BaseJWTCredentials):
iam_channel_credentials=None,
):
TokenServiceCredentials.__init__(self, iam_endpoint, iam_channel_credentials)
- auth.BaseJWTCredentials.__init__(self, account_id, access_key_id, private_key)
+ auth.BaseJWTCredentials.__init__(
+ self,
+ account_id,
+ access_key_id,
+ private_key,
+ auth.YANDEX_CLOUD_JWT_ALGORITHM,
+ auth.YANDEX_CLOUD_IAM_TOKEN_SERVICE_URL,
+ )
def _get_token_request(self):
- return iam_token_service_pb2.CreateIamTokenRequest(
- jwt=auth.get_jwt(
- self._account_id,
- self._access_key_id,
- self._private_key,
- self._jwt_expiration_timeout,
- )
+ return iam_token_service_pb2.CreateIamTokenRequest(jwt=self._get_jwt())
+
+
+class NebiusJWTIamCredentials(OAuth2JwtTokenExchangeCredentials):
+ def __init__(
+ self,
+ account_id,
+ access_key_id,
+ private_key,
+ token_exchange_url=None,
+ ):
+ url = token_exchange_url
+ if url is None:
+ url = auth.NEBIUS_CLOUD_IAM_TOKEN_EXCHANGE_URL
+ OAuth2JwtTokenExchangeCredentials.__init__(
+ self,
+ url,
+ account_id,
+ access_key_id,
+ private_key,
+ auth.NEBIUS_CLOUD_JWT_ALGORITHM,
+ auth.NEBIUS_CLOUD_IAM_TOKEN_SERVICE_AUDIENCE,
+ account_id,
)
@@ -130,3 +202,20 @@ class ServiceAccountCredentials(JWTIamCredentials):
iam_endpoint,
iam_channel_credentials,
)
+
+
+class NebiusServiceAccountCredentials(NebiusJWTIamCredentials):
+ def __init__(
+ self,
+ service_account_id,
+ access_key_id,
+ private_key,
+ iam_endpoint=None,
+ iam_channel_credentials=None,
+ ):
+ super(NebiusServiceAccountCredentials, self).__init__(
+ service_account_id,
+ access_key_id,
+ private_key,
+ iam_endpoint,
+ )
diff --git a/contrib/python/ydb/py3/ydb/driver.py b/contrib/python/ydb/py3/ydb/driver.py
index 89109b9b57..16bba15154 100644
--- a/contrib/python/ydb/py3/ydb/driver.py
+++ b/contrib/python/ydb/py3/ydb/driver.py
@@ -38,6 +38,13 @@ def credentials_from_env_variables(tracer=None):
return ydb.iam.ServiceAccountCredentials.from_file(service_account_key_file)
+ nebius_service_account_key_file = os.getenv("YDB_NEBIUS_SERVICE_ACCOUNT_KEY_FILE_CREDENTIALS")
+ if nebius_service_account_key_file is not None:
+ ctx.trace({"credentials.nebius_service_account_key_file": True})
+ import ydb.iam
+
+ return ydb.iam.NebiusServiceAccountCredentials.from_file(nebius_service_account_key_file)
+
anonymous_credetials = os.getenv("YDB_ANONYMOUS_CREDENTIALS", "0") == "1"
if anonymous_credetials:
ctx.trace({"credentials.anonymous": True})
diff --git a/contrib/python/ydb/py3/ydb/iam/__init__.py b/contrib/python/ydb/py3/ydb/iam/__init__.py
index 7167efe13e..cf835769db 100644
--- a/contrib/python/ydb/py3/ydb/iam/__init__.py
+++ b/contrib/python/ydb/py3/ydb/iam/__init__.py
@@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
from .auth import ServiceAccountCredentials # noqa
+from .auth import NebiusServiceAccountCredentials # noqa
from .auth import MetadataUrlCredentials # noqa
diff --git a/contrib/python/ydb/py3/ydb/iam/auth.py b/contrib/python/ydb/py3/ydb/iam/auth.py
index 82e7c9f6c8..852c0c28bb 100644
--- a/contrib/python/ydb/py3/ydb/iam/auth.py
+++ b/contrib/python/ydb/py3/ydb/iam/auth.py
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
-from ydb import credentials, tracing
+from ydb import credentials, tracing, issues
import grpc
import time
import abc
@@ -8,11 +8,14 @@ import json
import os
try:
- from yandex.cloud.iam.v1 import iam_token_service_pb2_grpc
- from yandex.cloud.iam.v1 import iam_token_service_pb2
import jwt
except ImportError:
jwt = None
+
+try:
+ from yandex.cloud.iam.v1 import iam_token_service_pb2_grpc
+ from yandex.cloud.iam.v1 import iam_token_service_pb2
+except ImportError:
iam_token_service_pb2_grpc = None
iam_token_service_pb2 = None
@@ -23,22 +26,32 @@ except ImportError:
DEFAULT_METADATA_URL = "http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token"
+YANDEX_CLOUD_IAM_TOKEN_SERVICE_URL = "https://iam.api.cloud.yandex.net/iam/v1/tokens"
+NEBIUS_CLOUD_IAM_TOKEN_SERVICE_AUDIENCE = "token-service.iam.new.nebiuscloud.net"
+NEBIUS_CLOUD_IAM_TOKEN_EXCHANGE_URL = "https://auth.new.nebiuscloud.net/oauth2/token/exchange"
+YANDEX_CLOUD_JWT_ALGORITHM = "PS256"
+NEBIUS_CLOUD_JWT_ALGORITHM = "RS256"
-def get_jwt(account_id, access_key_id, private_key, jwt_expiration_timeout):
+
+def get_jwt(account_id, access_key_id, private_key, jwt_expiration_timeout, algorithm, token_service_url, subject=None):
+ assert jwt is not None, "Install pyjwt library to use jwt tokens"
now = time.time()
now_utc = datetime.utcfromtimestamp(now)
exp_utc = datetime.utcfromtimestamp(now + jwt_expiration_timeout)
+ payload = {
+ "iss": account_id,
+ "aud": token_service_url,
+ "iat": now_utc,
+ "exp": exp_utc,
+ }
+ if subject is not None:
+ payload["sub"] = subject
return jwt.encode(
key=private_key,
- algorithm="PS256",
- headers={"typ": "JWT", "alg": "PS256", "kid": access_key_id},
- payload={
- "iss": account_id,
- "aud": "https://iam.api.cloud.yandex.net/iam/v1/tokens",
- "iat": now_utc,
- "exp": exp_utc,
- },
+ algorithm=algorithm,
+ headers={"typ": "JWT", "alg": algorithm, "kid": access_key_id},
+ payload=payload,
)
@@ -73,12 +86,15 @@ class TokenServiceCredentials(credentials.AbstractExpiringTokenCredentials):
class BaseJWTCredentials(abc.ABC):
- def __init__(self, account_id, access_key_id, private_key):
+ def __init__(self, account_id, access_key_id, private_key, algorithm, token_service_url, subject=None):
self._account_id = account_id
self._jwt_expiration_timeout = 60.0 * 60
self._token_expiration_timeout = 120
self._access_key_id = access_key_id
self._private_key = private_key
+ self._algorithm = algorithm
+ self._token_service_url = token_service_url
+ self._subject = subject
def set_token_expiration_timeout(self, value):
self._token_expiration_timeout = value
@@ -99,6 +115,64 @@ class BaseJWTCredentials(abc.ABC):
iam_channel_credentials=iam_channel_credentials,
)
+ def _get_jwt(self):
+ return get_jwt(
+ self._account_id,
+ self._access_key_id,
+ self._private_key,
+ self._jwt_expiration_timeout,
+ self._algorithm,
+ self._token_service_url,
+ self._subject,
+ )
+
+
+class OAuth2JwtTokenExchangeCredentials(credentials.AbstractExpiringTokenCredentials, BaseJWTCredentials):
+ def __init__(
+ self,
+ token_exchange_url,
+ account_id,
+ access_key_id,
+ private_key,
+ algorithm,
+ token_service_url,
+ subject=None,
+ tracer=None,
+ ):
+ BaseJWTCredentials.__init__(self, account_id, access_key_id, private_key, algorithm, token_service_url, subject)
+ super(OAuth2JwtTokenExchangeCredentials, self).__init__(tracer)
+ assert requests is not None, "Install requests library to use OAuth 2.0 token exchange credentials provider"
+ self._token_exchange_url = token_exchange_url
+
+ def _process_response_status_code(self, response):
+ if response.status_code == 403:
+ raise issues.Unauthenticated(response.content)
+ if response.status_code >= 500:
+ raise issues.Unavailable(response.content)
+ if response.status_code >= 400:
+ raise issues.BadRequest(response.content)
+ if response.status_code != 200:
+ raise issues.Error(response.content)
+
+ def _process_response(self, response):
+ self._process_response_status_code(response)
+ response_json = json.loads(response.content)
+ access_token = response_json["access_token"]
+ expires_in = response_json["expires_in"]
+ return {"access_token": access_token, "expires_in": expires_in}
+
+ @tracing.with_trace()
+ def _make_token_request(self):
+ params = {
+ "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange",
+ "requested_token_type": "urn:ietf:params:oauth:token-type:access_token",
+ "subject_token": self._get_jwt(),
+ "subject_token_type": "urn:ietf:params:oauth:token-type:jwt",
+ }
+ headers = {"Content-Type": "application/x-www-form-urlencoded"}
+ response = requests.post(self._token_exchange_url, data=params, headers=headers)
+ return self._process_response(response)
+
class JWTIamCredentials(TokenServiceCredentials, BaseJWTCredentials):
def __init__(
@@ -110,16 +184,34 @@ class JWTIamCredentials(TokenServiceCredentials, BaseJWTCredentials):
iam_channel_credentials=None,
):
TokenServiceCredentials.__init__(self, iam_endpoint, iam_channel_credentials)
- BaseJWTCredentials.__init__(self, account_id, access_key_id, private_key)
+ BaseJWTCredentials.__init__(
+ self, account_id, access_key_id, private_key, YANDEX_CLOUD_JWT_ALGORITHM, YANDEX_CLOUD_IAM_TOKEN_SERVICE_URL
+ )
def _get_token_request(self):
- return self._iam_token_service_pb2.CreateIamTokenRequest(
- jwt=get_jwt(
- self._account_id,
- self._access_key_id,
- self._private_key,
- self._jwt_expiration_timeout,
- )
+ return self._iam_token_service_pb2.CreateIamTokenRequest(jwt=self._get_jwt())
+
+
+class NebiusJWTIamCredentials(OAuth2JwtTokenExchangeCredentials):
+ def __init__(
+ self,
+ account_id,
+ access_key_id,
+ private_key,
+ token_exchange_url=None,
+ ):
+ url = token_exchange_url
+ if url is None:
+ url = NEBIUS_CLOUD_IAM_TOKEN_EXCHANGE_URL
+ OAuth2JwtTokenExchangeCredentials.__init__(
+ self,
+ url,
+ account_id,
+ access_key_id,
+ private_key,
+ NEBIUS_CLOUD_JWT_ALGORITHM,
+ NEBIUS_CLOUD_IAM_TOKEN_SERVICE_AUDIENCE,
+ account_id,
)
@@ -176,3 +268,20 @@ class ServiceAccountCredentials(JWTIamCredentials):
iam_endpoint,
iam_channel_credentials,
)
+
+
+class NebiusServiceAccountCredentials(NebiusJWTIamCredentials):
+ def __init__(
+ self,
+ service_account_id,
+ access_key_id,
+ private_key,
+ iam_endpoint=None,
+ iam_channel_credentials=None,
+ ):
+ super(NebiusServiceAccountCredentials, self).__init__(
+ service_account_id,
+ access_key_id,
+ private_key,
+ iam_endpoint,
+ )
diff --git a/contrib/python/ydb/py3/ydb/ydb_version.py b/contrib/python/ydb/py3/ydb/ydb_version.py
index a464e6dc07..10baa2a41a 100644
--- a/contrib/python/ydb/py3/ydb/ydb_version.py
+++ b/contrib/python/ydb/py3/ydb/ydb_version.py
@@ -1 +1 @@
-VERSION = "3.8.1"
+VERSION = "3.9.0"