From 8f73b7e5fc7d9bb7e110c2d5a34034be71d235b5 Mon Sep 17 00:00:00 2001 From: CentOS Sources Date: Mar 28 2023 09:36:08 +0000 Subject: import python-oauthlib-3.1.1-5.el9 --- diff --git a/SOURCES/0002-Rip-out-the-rest-of-RSA.patch b/SOURCES/0002-Rip-out-the-rest-of-RSA.patch new file mode 100644 index 0000000..0296695 --- /dev/null +++ b/SOURCES/0002-Rip-out-the-rest-of-RSA.patch @@ -0,0 +1,328 @@ +diff -up oauthlib-3.1.1/oauthlib/oauth1/__init__.py.orig oauthlib-3.1.1/oauthlib/oauth1/__init__.py +--- oauthlib-3.1.1/oauthlib/oauth1/__init__.py.orig 2022-07-12 14:00:51.468041694 +0200 ++++ oauthlib-3.1.1/oauthlib/oauth1/__init__.py 2022-07-12 14:02:06.102946935 +0200 +@@ -10,8 +10,6 @@ from .rfc5849 import (SIGNATURE_HMAC, + SIGNATURE_HMAC_SHA1, + SIGNATURE_HMAC_SHA256, + SIGNATURE_HMAC_SHA512, +- SIGNATURE_RSA_SHA256, +- SIGNATURE_RSA_SHA512, + SIGNATURE_PLAINTEXT) + from .rfc5849 import SIGNATURE_TYPE_AUTH_HEADER, SIGNATURE_TYPE_QUERY + from .rfc5849 import SIGNATURE_TYPE_BODY +diff -up oauthlib-3.1.1/oauthlib/oauth1/rfc5849/endpoints/base.py.orig oauthlib-3.1.1/oauthlib/oauth1/rfc5849/endpoints/base.py +--- oauthlib-3.1.1/oauthlib/oauth1/rfc5849/endpoints/base.py.orig 2022-07-13 16:45:37.104370084 +0200 ++++ oauthlib-3.1.1/oauthlib/oauth1/rfc5849/endpoints/base.py 2022-07-12 14:17:46.689355274 +0200 +@@ -180,27 +180,11 @@ class BaseEndpoint: + description='Invalid nonce format.') + + def _check_signature(self, request, is_token_request=False): +- # ---- RSA-SHA1 is not allowed ------ +- if request.signature_method == SIGNATURE_RSA_SHA1: +- raise ValueError("Using RSA-SHA1 is deprecated, use HMAC-SHA1 or a stronger RSA-SHA***") +- +- # ---- RSA Signature verification ---- +- if request.signature_method == SIGNATURE_RSA_SHA256 or \ +- request.signature_method == SIGNATURE_RSA_SHA512: +- # RSA-based signature method +- +- # The server verifies the signature per `[RFC3447] section 8.2.2`_ +- # .. _`[RFC3447] section 8.2.2`: https://tools.ietf.org/html/rfc3447#section-8.2.1 +- +- rsa_key = self.request_validator.get_rsa_key( +- request.client_key, request) +- +- if request.signature_method == SIGNATURE_RSA_SHA256: +- valid_signature = signature.verify_rsa_sha256(request, rsa_key) +- elif request.signature_method == SIGNATURE_RSA_SHA512: +- valid_signature = signature.verify_rsa_sha512(request, rsa_key) +- else: +- valid_signature = False ++ # ---- RSA-SHA is not allowed ------ ++ if request.signature_method in (SIGNATURE_RSA_SHA1, ++ SIGNATURE_RSA_SHA256, ++ SIGNATURE_RSA_SHA512): ++ raise ValueError("Using RSA-SHA is deprecated, use HMAC-SHA") + + # ---- HMAC or Plaintext Signature verification ---- + else: +diff -up oauthlib-3.1.1/oauthlib/oauth1/rfc5849/__init__.py.orig oauthlib-3.1.1/oauthlib/oauth1/rfc5849/__init__.py +--- oauthlib-3.1.1/oauthlib/oauth1/rfc5849/__init__.py.orig 2022-07-13 16:45:37.103370073 +0200 ++++ oauthlib-3.1.1/oauthlib/oauth1/rfc5849/__init__.py 2022-07-12 14:05:31.087433182 +0200 +@@ -78,7 +78,7 @@ class Client: + SIGNATURE_HMAC_SHA1: signature.sign_hmac_sha1_with_client, + SIGNATURE_HMAC_SHA256: signature.sign_hmac_sha256_with_client, + SIGNATURE_HMAC_SHA512: signature.sign_hmac_sha512_with_client, +- # sign_rsa_sha1_with_client actually points out to a dummy method ++ # sign_rsa_shaXYZ_with_client actually points out to a dummy method + # that just throws an exception + SIGNATURE_RSA_SHA1: signature.sign_rsa_sha1_with_client, + SIGNATURE_RSA_SHA256: signature.sign_rsa_sha256_with_client, +diff -up oauthlib-3.1.1/oauthlib/oauth1/rfc5849/signature.py.orig oauthlib-3.1.1/oauthlib/oauth1/rfc5849/signature.py +--- oauthlib-3.1.1/oauthlib/oauth1/rfc5849/signature.py.orig 2022-07-13 16:45:37.104370084 +0200 ++++ oauthlib-3.1.1/oauthlib/oauth1/rfc5849/signature.py 2022-08-10 12:05:45.642421443 +0200 +@@ -559,16 +559,17 @@ def _get_jwt_rsa_algorithm(hash_algorith + # Not in cache: instantiate a new RSAAlgorithm + + # PyJWT has some nice pycrypto/cryptography abstractions +- import jwt.algorithms as jwt_algorithms +- m = { +- 'SHA-256': jwt_algorithms.hashes.SHA256, +- 'SHA-512': jwt_algorithms.hashes.SHA512, +- } +- v = jwt_algorithms.RSAAlgorithm(m[hash_algorithm_name]) ++ # import jwt.algorithms as jwt_algorithms ++ #m = { ++ # 'SHA-256': jwt_algorithms.hashes.SHA256, ++ # 'SHA-512': jwt_algorithms.hashes.SHA512, ++ #} ++ #v = jwt_algorithms.RSAAlgorithm(m[hash_algorithm_name]) + +- _jwt_rsa[hash_algorithm_name] = v # populate cache ++ #_jwt_rsa[hash_algorithm_name] = v # populate cache + +- return v ++ #return v ++ return None + + + def _prepare_key_plus(alg, keystr): +@@ -612,6 +613,7 @@ def _sign_rsa(hash_algorithm_name: str, + """ + + # Get the implementation of RSA-hash ++ raise ValueError('Invalid signature method.') + + alg = _get_jwt_rsa_algorithm(hash_algorithm_name) + +diff -up oauthlib-3.1.1/tests/oauth1/rfc5849/test_client.py.orig oauthlib-3.1.1/tests/oauth1/rfc5849/test_client.py +--- oauthlib-3.1.1/tests/oauth1/rfc5849/test_client.py.orig 2022-07-13 16:45:37.104370084 +0200 ++++ oauthlib-3.1.1/tests/oauth1/rfc5849/test_client.py 2022-08-09 11:46:07.105962442 +0200 +@@ -2,7 +2,7 @@ + from oauthlib.common import Request + from oauthlib.oauth1 import ( + SIGNATURE_HMAC_SHA1, SIGNATURE_HMAC_SHA256, SIGNATURE_PLAINTEXT, +- SIGNATURE_RSA_SHA256, SIGNATURE_TYPE_BODY, SIGNATURE_TYPE_QUERY, ++ SIGNATURE_TYPE_BODY, SIGNATURE_TYPE_QUERY, + ) + from oauthlib.oauth1.rfc5849 import Client + +@@ -74,14 +74,6 @@ class ClientConstructorTests(TestCase): + self.assertEqual(Client.SIGNATURE_METHODS[SIGNATURE_HMAC_SHA256], + client.SIGNATURE_METHODS[client.signature_method]) + +- def test_rsa(self): +- client = Client('client_key', signature_method=SIGNATURE_RSA_SHA256) +- # instance is using the correct signer method +- self.assertEqual(Client.SIGNATURE_METHODS[SIGNATURE_RSA_SHA256], +- client.SIGNATURE_METHODS[client.signature_method]) +- # don't need an RSA key to instantiate +- self.assertIsNone(client.rsa_key) +- + + class SignatureMethodTest(TestCase): + +@@ -104,35 +96,6 @@ class SignatureMethodTest(TestCase): + 'oauth_signature="JzgJWBxX664OiMW3WE4MEjtYwOjI%2FpaUWHqtdHe68Es%3D"') + self.assertEqual(h['Authorization'], correct) + +- def test_rsa_method(self): +- private_key = ( +- "-----BEGIN RSA PRIVATE KEY-----\nMIICXgIBAAKBgQDk1/bxy" +- "S8Q8jiheHeYYp/4rEKJopeQRRKKpZI4s5i+UPwVpupG\nAlwXWfzXw" +- "SMaKPAoKJNdu7tqKRniqst5uoHXw98gj0x7zamu0Ck1LtQ4c7pFMVa" +- "h\n5IYGhBi2E9ycNS329W27nJPWNCbESTu7snVlG8V8mfvGGg3xNjT" +- "MO7IdrwIDAQAB\nAoGBAOQ2KuH8S5+OrsL4K+wfjoCi6MfxCUyqVU9" +- "GxocdM1m30WyWRFMEz2nKJ8fR\np3vTD4w8yplTOhcoXdQZl0kRoaD" +- "zrcYkm2VvJtQRrX7dKFT8dR8D/Tr7dNQLOXfC\nDY6xveQczE7qt7V" +- "k7lp4FqmxBsaaEuokt78pOOjywZoInjZhAkEA9wz3zoZNT0/i\nrf6" +- "qv2qTIeieUB035N3dyw6f1BGSWYaXSuerDCD/J1qZbAPKKhyHZbVaw" +- "Ft3UMhe\n542UftBaxQJBAO0iJy1I8GQjGnS7B3yvyH3CcLYGy296+" +- "XO/2xKp/d/ty1OIeovx\nC60pLNwuFNF3z9d2GVQAdoQ89hUkOtjZL" +- "eMCQQD0JO6oPHUeUjYT+T7ImAv7UKVT\nSuy30sKjLzqoGw1kR+wv7" +- "C5PeDRvscs4wa4CW9s6mjSrMDkDrmCLuJDtmf55AkEA\nkmaMg2PNr" +- "jUR51F0zOEFycaaqXbGcFwe1/xx9zLmHzMDXd4bsnwt9kk+fe0hQzV" +- "S\nJzatanQit3+feev1PN3QewJAWv4RZeavEUhKv+kLe95Yd0su7lT" +- "LVduVgh4v5yLT\nGa6FHdjGPcfajt+nrpB1n8UQBEH9ZxniokR/IPv" +- "dMlxqXA==\n-----END RSA PRIVATE KEY-----" +- ) +- client = Client('client_key', signature_method=SIGNATURE_RSA_SHA256, +- rsa_key=private_key, timestamp='1234567890', nonce='abc') +- u, h, b = client.sign('http://example.com') +- correct = ('OAuth oauth_nonce="abc", oauth_timestamp="1234567890", ' +- 'oauth_version="1.0", oauth_signature_method="RSA-SHA256", ' +- 'oauth_consumer_key="client_key", ' +- 'oauth_signature="hJE2IGqCn3bw7ecu6psnsImrvERhTd667aIENzWbzdRGxEWwvAwJvWWCffD8P0Ox9IEu3gKD%2FzYdr36tBhW%2FMvdFsOAr4F41ojznv1urY6%2FD9FRs1py9dYuj1vdFYFUzziMBDv2w2emidDk8PqfHT1we5%2FIcH%2FKNCjMbkQgxsqE%3D"') +- self.assertEqual(h['Authorization'], correct) +- + def test_plaintext_method(self): + client = Client('client_key', + signature_method=SIGNATURE_PLAINTEXT, +@@ -151,10 +114,6 @@ class SignatureMethodTest(TestCase): + client = Client('client_key', signature_method='invalid') + self.assertRaises(ValueError, client.sign, 'http://example.com') + +- def test_rsa_no_key(self): +- client = Client('client_key', signature_method=SIGNATURE_RSA_SHA256) +- self.assertRaises(ValueError, client.sign, 'http://example.com') +- + def test_register_method(self): + Client.register_signature_method('PIZZA', + lambda base_string, client: 'PIZZA') +diff -up oauthlib-3.1.1/tests/oauth1/rfc5849/test_signatures.py.orig oauthlib-3.1.1/tests/oauth1/rfc5849/test_signatures.py +--- oauthlib-3.1.1/tests/oauth1/rfc5849/test_signatures.py.orig 2022-07-13 16:45:37.104370084 +0200 ++++ oauthlib-3.1.1/tests/oauth1/rfc5849/test_signatures.py 2022-08-09 11:55:56.834032943 +0200 +@@ -657,129 +657,38 @@ GLYT3Jw1Lfb1bbuck9Y0JsRJO7uydWUbxXyZ+8Ya + + def test_sign_rsa_sha256_with_client(self): + """ +- Test sign and verify with RSA-SHA256. +- """ +- self.assertEqual( +- self.expected_signature_rsa_sha256, +- sign_rsa_sha256_with_client(self.eg_signature_base_string, +- self.rsa_private_client)) +- self.assertTrue(verify_rsa_sha256( +- MockRequest('POST', +- 'http://example.com/request', +- self.eg_params, +- self.expected_signature_rsa_sha256), +- self.rsa_public_client.rsa_key)) +- +- def test_sign_rsa_sha512_with_client(self): +- """ +- Test sign and verify with RSA-SHA512. +- """ +- self.assertEqual( +- self.expected_signature_rsa_sha512, +- sign_rsa_sha512_with_client(self.eg_signature_base_string, +- self.rsa_private_client)) +- self.assertTrue(verify_rsa_sha512( +- MockRequest('POST', +- 'http://example.com/request', +- self.eg_params, +- self.expected_signature_rsa_sha512), +- self.rsa_public_client.rsa_key)) +- +- def test_rsa_false_positives(self): +- """ +- Test verify_rsa-* functions will correctly detect invalid signatures. ++ Test sign and verify with RSA-SHA256 throws an exception. + """ ++ self.assertRaises(ValueError, ++ sign_rsa_sha256_with_client, ++ self.eg_signature_base_string, ++ self.rsa_private_client) + +- another_client = MockClient(rsa_key=''' +------BEGIN RSA PRIVATE KEY----- +-MIICXQIBAAKBgQDZcD/1OZNJJ6Y3QZM16Z+O7fkD9kTIQuT2BfpAOUvDfxzYhVC9 +-TNmSDHCQhr+ClutyolBk5jTE1/FXFUuHoPsTrkI7KQFXPP834D4gnSY9jrAiUJHe +-DVF6wXNuS7H4Ueh16YPjUxgLLRh/nn/JSEj98gsw+7DP01OWMfWS99S7eQIDAQAB +-AoGBALsQZRXVyK7BG7CiC8HwEcNnXDpaXmZjlpNKJTenk1THQMvONd4GBZAuf5D3 +-PD9fE4R1u/ByVKecmBaxTV+L0TRQfD8K/nbQe0SKRQIkLI2ymLJKC/eyw5iTKT0E +-+BS6wYpVd+mfcqgvpHOYpUmz9X8k/eOa7uslFmvt+sDb5ZcBAkEA+++SRqqUxFEG +-s/ZWAKw9p5YgkeVUOYVUwyAeZ97heySrjVzg1nZ6v6kv7iOPi9KOEpaIGPW7x1K/ +-uQuSt4YEqQJBANzyNqZTTPpv7b/R8ABFy0YMwPVNt3b1GOU1Xxl6iuhH2WcHuueo +-UB13JHoZCMZ7hsEqieEz6uteUjdRzRPKclECQFNhVK4iop3emzNQYeJTHwyp+RmQ +-JrHq2MTDioyiDUouNsDQbnFMQQ/RtNVB265Q/0hTnbN1ELLFRkK9+87VghECQQC9 +-hacLFPk6+TffCp3sHfI3rEj4Iin1iFhKhHWGzW7JwJfjoOXaQK44GDLZ6Q918g+t +-MmgDHR2tt8KeYTSgfU+BAkBcaVF91EQ7VXhvyABNYjeYP7lU7orOgdWMa/zbLXSU +-4vLsK1WOmwPY9zsXpPkilqszqcru4gzlG462cSbEdAW9 +------END RSA PRIVATE KEY----- +-''') +- +- for functions in [ +- (sign_rsa_sha256_with_client, verify_rsa_sha256), +- (sign_rsa_sha512_with_client, verify_rsa_sha512), +- ]: +- signing_function = functions[0] +- verify_function = functions[1] +- +- good_signature = \ +- signing_function(self.eg_signature_base_string, +- self.rsa_private_client) +- +- bad_signature_on_different_value = \ +- signing_function('wrong value signed', self.rsa_private_client) +- +- bad_signature_produced_by_different_private_key = \ +- signing_function(self.eg_signature_base_string, another_client) +- +- self.assertTrue(verify_function( +- MockRequest('POST', +- 'http://example.com/request', +- self.eg_params, +- good_signature), +- self.rsa_public_client.rsa_key)) +- +- for bad_signature in [ +- '', +- 'ZG9uJ3QgdHJ1c3QgbWUK', # random base64 encoded value +- 'altérer', # value with a non-ASCII character in it +- bad_signature_on_different_value, +- bad_signature_produced_by_different_private_key, +- ]: +- self.assertFalse(verify_function( +- MockRequest('POST', +- 'http://example.com/request', +- self.eg_params, +- bad_signature), +- self.rsa_public_client.rsa_key)) ++ self.assertRaises(ValueError, ++ verify_rsa_sha1, ++ MockRequest('POST', ++ 'http://example.com/request', ++ self.eg_params, ++ self.expected_signature_rsa_sha256), ++ self.rsa_public_client.rsa_key) + +- def test_rsa_bad_keys(self): ++ def test_sign_rsa_sha512_with_client(self): + """ +- Testing RSA sign and verify with bad key values produces errors. +- +- This test is useful for coverage tests, since it runs the code branches +- that deal with error situations. ++ Test sign and verify with RSA-SHA512 throws an exception. + """ +- +- # Signing needs a private key +- +- for bad_value in [None, '', 'foobar']: +- self.assertRaises(ValueError, +- sign_rsa_sha256_with_client, +- self.eg_signature_base_string, +- MockClient(rsa_key=bad_value)) +- +- self.assertRaises(AttributeError, +- sign_rsa_sha256_with_client, ++ self.assertRaises(ValueError, ++ sign_rsa_sha512_with_client, + self.eg_signature_base_string, +- self.rsa_public_client) # public key doesn't sign +- +- # Verify needs a public key ++ self.rsa_private_client) + +- for bad_value in [None, '', 'foobar', self.rsa_private_client.rsa_key]: +- self.assertRaises(TypeError, +- verify_rsa_sha256, +- MockRequest('POST', +- 'http://example.com/request', +- self.eg_params, +- self.expected_signature_rsa_sha256), +- MockClient(rsa_key=bad_value)) ++ self.assertRaises(ValueError, ++ verify_rsa_sha1, ++ MockRequest('POST', ++ 'http://example.com/request', ++ self.eg_params, ++ self.expected_signature_rsa_sha512), ++ self.rsa_public_client.rsa_key) + +- # For completeness, this text could repeat the above for RSA-SHA256 and +- # RSA-SHA512 signing and verification functions. + + def test_rsa_jwt_algorithm_cache(self): + # Tests cache of RSAAlgorithm objects is implemented correctly. diff --git a/SOURCES/0003-IPV6-regex-redirect_uri.patch b/SOURCES/0003-IPV6-regex-redirect_uri.patch new file mode 100644 index 0000000..641cd42 --- /dev/null +++ b/SOURCES/0003-IPV6-regex-redirect_uri.patch @@ -0,0 +1,95 @@ +diff -up oauthlib-3.1.1/oauthlib/uri_validate.py.orig oauthlib-3.1.1/oauthlib/uri_validate.py +--- oauthlib-3.1.1/oauthlib/uri_validate.py.orig 2021-06-01 21:11:24.000000000 +0200 ++++ oauthlib-3.1.1/oauthlib/uri_validate.py 2022-10-24 14:42:42.147180444 +0200 +@@ -66,7 +66,7 @@ IPv4address = r"%(dec_octet)s \. %(dec_o + ) + + # IPv6address +-IPv6address = r"([A-Fa-f0-9:]+:+)+[A-Fa-f0-9]+" ++IPv6address = r"([A-Fa-f0-9:]+[:$])[A-Fa-f0-9]{1,4}" + + # IPvFuture = "v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" ) + IPvFuture = r"v %(HEXDIG)s+ \. (?: %(unreserved)s | %(sub_delims)s | : )+" % locals() +diff -up oauthlib-3.1.1/tests/test_uri_validate.py.orig oauthlib-3.1.1/tests/test_uri_validate.py +--- oauthlib-3.1.1/tests/test_uri_validate.py.orig 2021-06-01 21:11:24.000000000 +0200 ++++ oauthlib-3.1.1/tests/test_uri_validate.py 2022-10-24 14:44:26.180296911 +0200 +@@ -1,4 +1,4 @@ +-import oauthlib ++import unittest + from oauthlib.uri_validate import is_absolute_uri + + from tests.unittest import TestCase +@@ -7,7 +7,6 @@ from tests.unittest import TestCase + class UriValidateTest(TestCase): + + def test_is_absolute_uri(self): +- + self.assertIsNotNone(is_absolute_uri('schema://example.com/path')) + self.assertIsNotNone(is_absolute_uri('https://example.com/path')) + self.assertIsNotNone(is_absolute_uri('https://example.com')) +@@ -17,17 +16,64 @@ class UriValidateTest(TestCase): + self.assertIsNotNone(is_absolute_uri('http://example.com')) + self.assertIsNotNone(is_absolute_uri('http://example.com/path')) + self.assertIsNotNone(is_absolute_uri('http://example.com:80/path')) +- self.assertIsNotNone(is_absolute_uri('com.example.bundle.id:/')) ++ ++ def test_query(self): ++ self.assertIsNotNone(is_absolute_uri('http://example.com:80/path?foo')) ++ self.assertIsNotNone(is_absolute_uri('http://example.com:80/path?foo=bar')) ++ self.assertIsNotNone(is_absolute_uri('http://example.com:80/path?foo=bar&fruit=banana')) ++ ++ def test_fragment_forbidden(self): ++ self.assertIsNone(is_absolute_uri('http://example.com:80/path#foo')) ++ self.assertIsNone(is_absolute_uri('http://example.com:80/path#foo=bar')) ++ self.assertIsNone(is_absolute_uri('http://example.com:80/path#foo=bar&fruit=banana')) ++ ++ def test_combined_forbidden(self): ++ self.assertIsNone(is_absolute_uri('http://example.com:80/path?foo#bar')) ++ self.assertIsNone(is_absolute_uri('http://example.com:80/path?foo&bar#fruit')) ++ self.assertIsNone(is_absolute_uri('http://example.com:80/path?foo=1&bar#fruit=banana')) ++ self.assertIsNone(is_absolute_uri('http://example.com:80/path?foo=1&bar=2#fruit=banana&bar=foo')) ++ ++ def test_custom_scheme(self): ++ self.assertIsNotNone(is_absolute_uri('com.example.bundle.id://')) ++ ++ def test_ipv6_bracket(self): + self.assertIsNotNone(is_absolute_uri('http://[::1]:38432/path')) + self.assertIsNotNone(is_absolute_uri('http://[::1]/path')) + self.assertIsNotNone(is_absolute_uri('http://[fd01:0001::1]/path')) + self.assertIsNotNone(is_absolute_uri('http://[fd01:1::1]/path')) + self.assertIsNotNone(is_absolute_uri('http://[0123:4567:89ab:cdef:0123:4567:89ab:cdef]/path')) ++ self.assertIsNotNone(is_absolute_uri('http://[0123:4567:89ab:cdef:0123:4567:89ab:cdef]:8080/path')) ++ ++ @unittest.skip("ipv6 edge-cases not supported") ++ def test_ipv6_edge_cases(self): ++ self.assertIsNotNone(is_absolute_uri('http://2001:db8::')) ++ self.assertIsNotNone(is_absolute_uri('http://::1234:5678')) ++ self.assertIsNotNone(is_absolute_uri('http://2001:db8::1234:5678')) ++ self.assertIsNotNone(is_absolute_uri('http://2001:db8:3333:4444:5555:6666:7777:8888')) ++ self.assertIsNotNone(is_absolute_uri('http://2001:db8:3333:4444:CCCC:DDDD:EEEE:FFFF')) ++ self.assertIsNotNone(is_absolute_uri('http://0123:4567:89ab:cdef:0123:4567:89ab:cdef/path')) ++ self.assertIsNotNone(is_absolute_uri('http://::')) ++ self.assertIsNotNone(is_absolute_uri('http://2001:0db8:0001:0000:0000:0ab9:C0A8:0102')) ++ ++ @unittest.skip("ipv6 dual ipv4 not supported") ++ def test_ipv6_dual(self): ++ self.assertIsNotNone(is_absolute_uri('http://2001:db8:3333:4444:5555:6666:1.2.3.4')) ++ self.assertIsNotNone(is_absolute_uri('http://::11.22.33.44')) ++ self.assertIsNotNone(is_absolute_uri('http://2001:db8::123.123.123.123')) ++ self.assertIsNotNone(is_absolute_uri('http://::1234:5678:91.123.4.56')) ++ self.assertIsNotNone(is_absolute_uri('http://::1234:5678:1.2.3.4')) ++ self.assertIsNotNone(is_absolute_uri('http://2001:db8::1234:5678:5.6.7.8')) ++ ++ def test_ipv4(self): + self.assertIsNotNone(is_absolute_uri('http://127.0.0.1:38432/')) + self.assertIsNotNone(is_absolute_uri('http://127.0.0.1:38432/')) + self.assertIsNotNone(is_absolute_uri('http://127.1:38432/')) + ++ def test_failures(self): + self.assertIsNone(is_absolute_uri('http://example.com:notaport/path')) + self.assertIsNone(is_absolute_uri('wrong')) + self.assertIsNone(is_absolute_uri('http://[:1]:38432/path')) + self.assertIsNone(is_absolute_uri('http://[abcd:efgh::1]/')) ++ ++ def test_malitious_semicolon(self): ++ self.assertIsNone(is_absolute_uri('http://[::::::::::::::::::::::::::]/path')) diff --git a/SOURCES/0004-IPV6-parsing-signature.patch b/SOURCES/0004-IPV6-parsing-signature.patch new file mode 100644 index 0000000..56b51b1 --- /dev/null +++ b/SOURCES/0004-IPV6-parsing-signature.patch @@ -0,0 +1,240 @@ +From 49294a6a7cb6e9ece1c1814d629e2d9e497180fa Mon Sep 17 00:00:00 2001 +From: Dariusz Smigiel +Date: Thu, 19 May 2022 09:41:59 -0700 +Subject: [PATCH 1/4] OAuth1: Allow IPv6 addresses being parsed by signature + +This PR addresses issue with incorrectly parsing IPv6 address, +described here: https://github.com/oauthlib/oauthlib/issues/817 +--- + oauthlib/oauth1/rfc5849/signature.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/oauthlib/oauth1/rfc5849/signature.py b/oauthlib/oauth1/rfc5849/signature.py +index a370ccd6..424393b6 100644 +--- a/oauthlib/oauth1/rfc5849/signature.py ++++ b/oauthlib/oauth1/rfc5849/signature.py +@@ -173,7 +173,7 @@ def base_string_uri(uri: str, host: str = None) -> str: + if ':' in netloc: + # Contains a colon ":", so try to parse as "host:port" + +- hostname, port_str = netloc.split(':', 1) ++ hostname, port_str = netloc.rsplit(':', 1) + + if len(hostname) == 0: + raise ValueError('missing host') # error: netloc was ":port" or ":" + +From d05c388078b45285ac4a012c568a5e2d56556a34 Mon Sep 17 00:00:00 2001 +From: Dariusz Smigiel +Date: Wed, 15 Jun 2022 09:26:20 -0700 +Subject: [PATCH 2/4] Removed dependency on split + +--- + oauthlib/oauth1/rfc5849/signature.py | 68 +++++++++++++++---------- + tests/oauth1/rfc5849/test_signatures.py | 21 +++++++- + 2 files changed, 60 insertions(+), 29 deletions(-) + +diff --git a/oauthlib/oauth1/rfc5849/signature.py b/oauthlib/oauth1/rfc5849/signature.py +index 424393b6..70447852 100644 +--- a/oauthlib/oauth1/rfc5849/signature.py ++++ b/oauthlib/oauth1/rfc5849/signature.py +@@ -37,6 +37,7 @@ + import binascii + import hashlib + import hmac ++import ipaddress + import logging + import warnings + +@@ -131,7 +132,14 @@ def base_string_uri(uri: str, host: str = None) -> str: + raise ValueError('uri must be a string.') + + # FIXME: urlparse does not support unicode +- scheme, netloc, path, params, query, fragment = urlparse.urlparse(uri) ++ output = urlparse.urlparse(uri) ++ scheme = output.scheme ++ hostname = output.hostname ++ port = output.port ++ path = output.path ++ params = output.params ++ query = output.query ++ fragment = output.fragment + + # The scheme, authority, and path of the request resource URI `RFC3986` + # are included by constructing an "http" or "https" URI representing +@@ -153,13 +161,22 @@ def base_string_uri(uri: str, host: str = None) -> str: + + # 1. The scheme and host MUST be in lowercase. + scheme = scheme.lower() +- netloc = netloc.lower() + # Note: if ``host`` is used, it will be converted to lowercase below ++ if hostname is not None: ++ hostname = hostname.lower() + + # 2. The host and port values MUST match the content of the HTTP + # request "Host" header field. + if host is not None: +- netloc = host.lower() # override value in uri with provided host ++ # NOTE: override value in uri with provided host ++ # Host argument is equal to netloc. It means it's missing scheme. ++ # Add it back, before parsing. ++ ++ host = host.lower() ++ host = f"{scheme}://{host}" ++ output = urlparse.urlparse(host) ++ hostname = output.hostname ++ port = output.port + + # 3. The port MUST be included if it is not the default port for the + # scheme, and MUST be excluded if it is the default. Specifically, +@@ -170,33 +187,28 @@ def base_string_uri(uri: str, host: str = None) -> str: + # .. _`RFC2616`: https://tools.ietf.org/html/rfc2616 + # .. _`RFC2818`: https://tools.ietf.org/html/rfc2818 + +- if ':' in netloc: +- # Contains a colon ":", so try to parse as "host:port" +- +- hostname, port_str = netloc.rsplit(':', 1) +- +- if len(hostname) == 0: +- raise ValueError('missing host') # error: netloc was ":port" or ":" ++ if hostname is None: ++ raise ValueError('missing host') + +- if len(port_str) == 0: +- netloc = hostname # was "host:", so just use the host part +- else: +- try: +- port_num = int(port_str) # try to parse into an integer number +- except ValueError: +- raise ValueError('port is not an integer') +- +- if port_num <= 0 or 65535 < port_num: +- raise ValueError('port out of range') # 16-bit unsigned ints +- if (scheme, port_num) in (('http', 80), ('https', 443)): +- netloc = hostname # default port for scheme: exclude port num +- else: +- netloc = hostname + ':' + str(port_num) # use hostname:port ++ # NOTE: Try guessing if we're dealing with IP or hostname ++ try: ++ hostname = ipaddress.ip_address(hostname) ++ except ValueError: ++ pass ++ ++ 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 ++ if (scheme, port) in (('http', 80), ('https', 443)): ++ netloc = hostname # default port for scheme: exclude port num ++ elif port: ++ netloc = f"{hostname}:{port}" # use hostname:port + else: +- # Does not contain a colon, so entire value must be the hostname +- +- if len(netloc) == 0: +- raise ValueError('missing host') # error: netloc was empty string ++ netloc = hostname + + v = urlparse.urlunparse((scheme, netloc, path, params, '', '')) + +diff --git a/tests/oauth1/rfc5849/test_signatures.py b/tests/oauth1/rfc5849/test_signatures.py +index 3e84f24b..e737e68b 100644 +--- a/tests/oauth1/rfc5849/test_signatures.py ++++ b/tests/oauth1/rfc5849/test_signatures.py +@@ -239,6 +239,26 @@ def test_base_string_uri(self): + 'http://override.example.com/path', + base_string_uri('http:///path', 'OVERRIDE.example.com')) + ++ # ---------------- ++ # Host: valid host allows for IPv4 and IPv6 ++ ++ self.assertEqual( ++ 'https://192.168.0.1/', ++ base_string_uri('https://192.168.0.1') ++ ) ++ self.assertEqual( ++ 'https://192.168.0.1:13000/', ++ base_string_uri('https://192.168.0.1:13000') ++ ) ++ self.assertEqual( ++ 'https://[123:db8:fd00:1000::5]:13000/', ++ base_string_uri('https://[123:db8:fd00:1000::5]:13000') ++ ) ++ self.assertEqual( ++ 'https://[123:db8:fd00:1000::5]/', ++ base_string_uri('https://[123:db8:fd00:1000::5]') ++ ) ++ + # ---------------- + # Port: default ports always excluded; non-default ports always included + +@@ -339,7 +359,6 @@ def test_base_string_uri(self): + self.assertRaises(ValueError, base_string_uri, 'http://:8080') + + # Port is not a valid TCP/IP port number +- self.assertRaises(ValueError, base_string_uri, 'http://eg.com:0') + self.assertRaises(ValueError, base_string_uri, 'http://eg.com:-1') + self.assertRaises(ValueError, base_string_uri, 'http://eg.com:65536') + self.assertRaises(ValueError, base_string_uri, 'http://eg.com:3.14') + +From ed0cb63945c4a5940b185823809693b7f97989ad Mon Sep 17 00:00:00 2001 +From: Dariusz Smigiel +Date: Wed, 15 Jun 2022 10:20:29 -0700 +Subject: [PATCH 3/4] Removed unused query and fragment + +--- + oauthlib/oauth1/rfc5849/signature.py | 2 -- + 1 file changed, 2 deletions(-) + +diff --git a/oauthlib/oauth1/rfc5849/signature.py b/oauthlib/oauth1/rfc5849/signature.py +index 70447852..7e8044a9 100644 +--- a/oauthlib/oauth1/rfc5849/signature.py ++++ b/oauthlib/oauth1/rfc5849/signature.py +@@ -138,8 +138,6 @@ def base_string_uri(uri: str, host: str = None) -> str: + port = output.port + path = output.path + params = output.params +- query = output.query +- fragment = output.fragment + + # The scheme, authority, and path of the request resource URI `RFC3986` + # are included by constructing an "http" or "https" URI representing + +From 9aa45aaff0cdeab258d18c025cf66e9bdba529c0 Mon Sep 17 00:00:00 2001 +From: Dariusz Smigiel +Date: Mon, 27 Jun 2022 07:20:06 -0700 +Subject: [PATCH 4/4] Restored test for port 0. + +--- + oauthlib/oauth1/rfc5849/signature.py | 2 +- + tests/oauth1/rfc5849/test_signatures.py | 1 + + 2 files changed, 2 insertions(+), 1 deletion(-) + +diff --git a/oauthlib/oauth1/rfc5849/signature.py b/oauthlib/oauth1/rfc5849/signature.py +index 862c3f3c..9cb1a517 100644 +--- a/oauthlib/oauth1/rfc5849/signature.py ++++ b/oauthlib/oauth1/rfc5849/signature.py +@@ -198,7 +198,7 @@ def base_string_uri(uri: str, host: str = None) -> str: + elif isinstance(hostname, ipaddress.IPv4Address): + hostname = f"{hostname}" + +- if port is not None and not (0 <= port <= 65535): ++ if port is not None and not (0 < port <= 65535): + raise ValueError('port out of range') # 16-bit unsigned ints + if (scheme, port) in (('http', 80), ('https', 443)): + netloc = hostname # default port for scheme: exclude port num +diff --git a/tests/oauth1/rfc5849/test_signatures.py b/tests/oauth1/rfc5849/test_signatures.py +index f0e18093..2d4735ea 100644 +--- a/tests/oauth1/rfc5849/test_signatures.py ++++ b/tests/oauth1/rfc5849/test_signatures.py +@@ -348,6 +348,7 @@ def test_base_string_uri(self): + self.assertRaises(ValueError, base_string_uri, 'http://:8080') + + # Port is not a valid TCP/IP port number ++ self.assertRaises(ValueError, base_string_uri, 'http://eg.com:0') + self.assertRaises(ValueError, base_string_uri, 'http://eg.com:-1') + self.assertRaises(ValueError, base_string_uri, 'http://eg.com:65536') + self.assertRaises(ValueError, base_string_uri, 'http://eg.com:3.14') diff --git a/SPECS/python-oauthlib.spec b/SPECS/python-oauthlib.spec index 81cace1..5c56415 100644 --- a/SPECS/python-oauthlib.spec +++ b/SPECS/python-oauthlib.spec @@ -2,7 +2,7 @@ Name: python-oauthlib Version: 3.1.1 -Release: 2%{?dist} +Release: 5%{?dist} Summary: An implementation of the OAuth request-signing logic License: BSD @@ -10,6 +10,9 @@ URL: https://github.com/oauthlib/oauthlib Source0: https://github.com/oauthlib/oauthlib/archive/v%{version}/%{modname}-%{version}.tar.gz Patch0001: 0001-Rip-out-RSA-SHA1.patch +Patch0002: 0002-Rip-out-the-rest-of-RSA.patch +Patch0003: 0003-IPV6-regex-redirect_uri.patch +Patch0004: 0004-IPV6-parsing-signature.patch BuildArch: noarch @@ -31,7 +34,6 @@ BuildRequires: python3-devel BuildRequires: python3-setuptools BuildRequires: python3-pytest -BuildRequires: python3-blinker BuildRequires: python3-cryptography >= 1.4.0 %description -n python3-oauthlib @@ -43,8 +45,7 @@ library, write a thin veneer on top of OAuthLib and get OAuth support for very little effort. %prep -%setup -q -n %{modname}-%{version} -%patch0001 -p1 +%autosetup -n %{modname}-%{version} -p1 # python-unittest2 is now provided by "python" package and python-unittest is retired # adapt setup.py to reflect this fact downstream @@ -61,7 +62,12 @@ rm -rf %{modname}.egg-info %check echo 'import pytest; __getattr__ = lambda _: pytest.skip("this test needs jwt")' > jwt.py -%pytest -rs --ignore tests/oauth2/rfc6749/clients/test_service_application.py +%pytest -rs --ignore tests/oauth2/rfc6749/clients/test_service_application.py \ + --ignore tests/oauth2/rfc6749/clients/test_web_application.py \ + --ignore tests/oauth2/rfc6749/clients/test_mobile_application.py \ + --ignore tests/oauth2/rfc6749/clients/test_legacy_application.py \ + --ignore tests/oauth2/rfc6749/clients/test_backend_application.py \ + --ignore tests/oauth2/rfc6749/test_parameters.py rm jwt.py %files -n python3-oauthlib @@ -71,6 +77,18 @@ rm jwt.py %{python3_sitelib}/%{modname}-%{version}-* %changelog +* Thu Nov 10 2022 TomasHalman - 3.1.1-5 +- RFC5849 oauth1 signature base_string_uri doesn't parse IPv6 addresses + Resolves: rhbz#2133805 + +* Mon Oct 24 2022 TomasHalman - 3.1.1-4 +- Resolves: rhbz#2133805 - fix for CVE-2022-36087 + +* Tue Aug 9 2022 TomasHalman - 3.1.1-3 +- Remove RSA support +- Remove build dependency on blinker + Resolves: rhbz#1984046 - python-oauthlib depends on jwt for RSA + * Tue Aug 10 2021 Mohan Boddu - 3.1.1-2 - Rebuilt for IMA sigs, glibc 2.34, aarch64 flags Related: rhbz#1991688