From 0554936180bcaa8fe4afb3dd1dc81734cc268cd4 Mon Sep 17 00:00:00 2001 From: GuyP <105154237+guyp-descope@users.noreply.github.com> Date: Fri, 21 Oct 2022 15:25:05 +0300 Subject: [PATCH] =?UTF-8?q?1.=20adjust=20email=5Fvalidator=20for=20the=20n?= =?UTF-8?q?ew=20version=20(disable=20call=20for=20dns=20q=E2=80=A6=20(#65)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 1. adjust email_validator for the new version 1.3.0 (disable call for dns queries). 2. adjust the tests for method that changed from get to post * fix definition of argument (list[str]) to be compatible with python 3.7, 3.8 * set allow_redirect=False to all Post calls --- descope/auth.py | 5 +++-- descope/descope_client.py | 18 ++++++++++-------- liccheck.ini | 2 +- poetry.lock | 20 ++++++++++---------- requirements.txt | 4 ++-- tests/test_auth.py | 4 ++-- tests/test_descope_client.py | 20 ++++++++++---------- tests/test_magiclink.py | 3 +++ tests/test_oauth.py | 2 +- tests/test_saml.py | 2 +- tests/test_totp.py | 1 + tests/test_webauthn.py | 6 ++++++ 12 files changed, 50 insertions(+), 37 deletions(-) diff --git a/descope/auth.py b/descope/auth.py index f103f099..211441a8 100644 --- a/descope/auth.py +++ b/descope/auth.py @@ -91,6 +91,7 @@ def do_post(self, uri: str, body: dict, pswd: str = None) -> requests.Response: f"{self.base_url}{uri}", headers=self._get_default_headers(pswd), data=json.dumps(body), + allow_redirects=False, verify=self.secure, ) if not response.ok: @@ -129,7 +130,7 @@ def verify_delivery_method( if not user.get("email", None): user["email"] = identifier try: - validate_email(user["email"]) + validate_email(email=user["email"], check_deliverability=False) return True except EmailNotValidError: return False @@ -190,7 +191,7 @@ def validate_email(email: str): ) try: - validate_email(email) + validate_email(email=email, check_deliverability=False) except EmailNotValidError as ex: raise AuthException( 400, ERROR_TYPE_INVALID_ARGUMENT, f"Invalid email address: {ex}" diff --git a/descope/descope_client.py b/descope/descope_client.py index ca6312c9..af0ac27c 100644 --- a/descope/descope_client.py +++ b/descope/descope_client.py @@ -1,3 +1,5 @@ +from typing import List + import requests from descope.auth import Auth # noqa: F401 @@ -53,21 +55,21 @@ def saml(self): def webauthn(self): return self._webauthn - def validate_permissions(self, jwt_response: dict, permissions: list[str]) -> bool: + def validate_permissions(self, jwt_response: dict, permissions: List[str]) -> bool: """ Validate that a jwt_response has been granted the specified permissions. For a multi-tenant environment use validate_tenant_permissions function Args: jwt_response (dict): The jwt_response object which includes all JWT claims information - permissions (list[str]): List of permissions to validate for this jwt_response + permissions (List[str]): List of permissions to validate for this jwt_response Return value (bool): returns true if all permissions granted; false if at least one permission not granted """ return self.validate_tenant_permissions(jwt_response, "", permissions) def validate_tenant_permissions( - self, jwt_response: dict, tenant: str, permissions: list[str] + self, jwt_response: dict, tenant: str, permissions: List[str] ) -> bool: """ Validate that a jwt_response has been granted the specified permissions on the specified tenant. @@ -76,7 +78,7 @@ def validate_tenant_permissions( Args: jwt_response (dict): The jwt_response object which includes all JWT claims information tenant (str): TenantId - permissions (list[str]): List of permissions to validate for this jwt_response + permissions (List[str]): List of permissions to validate for this jwt_response Return value (bool): returns true if all permissions granted; false if at least one permission not granted """ @@ -99,21 +101,21 @@ def validate_tenant_permissions( return False return True - def validate_roles(self, jwt_response: dict, roles: list[str]) -> bool: + def validate_roles(self, jwt_response: dict, roles: List[str]) -> bool: """ Validate that a jwt_response has been granted the specified roles. For a multi-tenant environment use validate_tenant_roles function Args: jwt_response (dict): The jwt_response object which includes all JWT claims information - roles (list[str]): List of roles to validate for this jwt_response + roles (List[str]): List of roles to validate for this jwt_response Return value (bool): returns true if all roles granted; false if at least one role not granted """ return self.validate_tenant_roles(jwt_response, "", roles) def validate_tenant_roles( - self, jwt_response: dict, tenant: str, roles: list[str] + self, jwt_response: dict, tenant: str, roles: List[str] ) -> bool: """ Validate that a jwt_response has been granted the specified roles on the specified tenant. @@ -122,7 +124,7 @@ def validate_tenant_roles( Args: jwt_response (dict): The jwt_response object which includes all JWT claims information tenant (str): TenantId - roles (list[str]): List of roles to validate for this jwt_response + roles (List[str]): List of roles to validate for this jwt_response Return value (bool): returns true if all roles granted; false if at least one role not granted """ diff --git a/liccheck.ini b/liccheck.ini index b5470bcb..c8af4cbd 100644 --- a/liccheck.ini +++ b/liccheck.ini @@ -29,7 +29,7 @@ unauthorized_licenses: # Apache-2.0 license coverage: 6.3.3 # CC0 1.0 Universal (CC0 1.0) Public Domain Dedication license -email-validator: 1.2.1 +email-validator: 1.3.0 #Public Domain (filelock package is dependency of filelock << virtualenv << pre-commit) filelock:>=3.4.1 #Mozilla Public License 2.0 (MPL 2.0) (certifi package is dependency of requests diff --git a/poetry.lock b/poetry.lock index fac9366b..a4c5dad3 100644 --- a/poetry.lock +++ b/poetry.lock @@ -10,7 +10,7 @@ python-versions = ">=3.5" dev = ["cloudpickle", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy (>=0.900,!=0.940)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "sphinx", "sphinx-notfound-page", "zope.interface"] docs = ["furo", "sphinx", "sphinx-notfound-page", "zope.interface"] tests = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "zope.interface"] -tests-no-zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins"] +tests_no_zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins"] [[package]] name = "black" @@ -71,7 +71,7 @@ optional = false python-versions = ">=3.6.0" [package.extras] -unicode-backport = ["unicodedata2"] +unicode_backport = ["unicodedata2"] [[package]] name = "click" @@ -272,9 +272,9 @@ python-versions = ">=3.6.1,<4.0" [package.extras] colors = ["colorama (>=0.4.3,<0.5.0)"] -pipfile-deprecated-finder = ["pipreqs", "requirementslib"] +pipfile_deprecated_finder = ["pipreqs", "requirementslib"] plugins = ["setuptools"] -requirements-deprecated-finder = ["pip-api", "pipreqs"] +requirements_deprecated_finder = ["pip-api", "pipreqs"] [[package]] name = "itsdangerous" @@ -455,7 +455,7 @@ optional = false python-versions = ">=3.6" [[package]] -name = "pyjwt" +name = "PyJWT" version = "2.5.0" description = "JSON Web Token implementation in Python" category = "main" @@ -539,7 +539,7 @@ urllib3 = ">=1.21.1,<1.27" [package.extras] socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] +use_chardet_on_py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "semantic-version" @@ -555,7 +555,7 @@ doc = ["Sphinx", "sphinx-rtd-theme"] [[package]] name = "setuptools" -version = "65.4.1" +version = "65.5.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" category = "dev" optional = false @@ -1010,7 +1010,7 @@ pyflakes = [ {file = "pyflakes-2.5.0-py2.py3-none-any.whl", hash = "sha256:4579f67d887f804e67edb544428f264b7b24f435b263c4614f384135cea553d2"}, {file = "pyflakes-2.5.0.tar.gz", hash = "sha256:491feb020dca48ccc562a8c0cbe8df07ee13078df59813b83959cbdada312ea3"}, ] -pyjwt = [ +PyJWT = [ {file = "PyJWT-2.5.0-py3-none-any.whl", hash = "sha256:8d82e7087868e94dd8d7d418e5088ce64f7daab4b36db654cbaedb46f9d1ca80"}, {file = "PyJWT-2.5.0.tar.gz", hash = "sha256:e77ab89480905d86998442ac5788f35333fa85f65047a534adc38edf3c88fc3b"}, ] @@ -1077,8 +1077,8 @@ semantic-version = [ {file = "semantic_version-2.10.0.tar.gz", hash = "sha256:bdabb6d336998cbb378d4b9db3a4b56a1e3235701dc05ea2690d9a997ed5041c"}, ] setuptools = [ - {file = "setuptools-65.4.1-py3-none-any.whl", hash = "sha256:1b6bdc6161661409c5f21508763dc63ab20a9ac2f8ba20029aaaa7fdb9118012"}, - {file = "setuptools-65.4.1.tar.gz", hash = "sha256:3050e338e5871e70c72983072fe34f6032ae1cdeeeb67338199c2f74e083a80e"}, + {file = "setuptools-65.5.0-py3-none-any.whl", hash = "sha256:f62ea9da9ed6289bfe868cd6845968a2c854d1427f8548d52cae02a42b4f0356"}, + {file = "setuptools-65.5.0.tar.gz", hash = "sha256:512e5536220e38146176efb833d4a62aa726b7bbff82cfbc8ba9eaa3996e0b17"}, ] toml = [ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, diff --git a/requirements.txt b/requirements.txt index 1711d07f..80da7a3f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -99,7 +99,7 @@ cryptography==38.0.1 ; python_version >= "3.7" and python_version < "4.0" \ dnspython==2.2.1 ; python_version >= "3.7" and python_version < "4.0" \ --hash=sha256:0f7569a4a6ff151958b64304071d370daa3243d15941a7beedf0c9fe5105603e \ --hash=sha256:a851e51367fb93e9e1361732c1d60dab63eff98712e503ea7d92e6eccb109b4f -email-validator==1.3.0; python_version >= "3.7" and python_version < "4.0" \ +email-validator==1.3.0 ; python_version >= "3.7" and python_version < "4.0" \ --hash=sha256:553a66f8be2ec2dea641ae1d3f29017ab89e9d603d4a25cdaac39eefa283d769 \ --hash=sha256:816073f2a7cffef786b29928f58ec16cdac42710a53bb18aa94317e3e145ec5c idna==3.4 ; python_version >= "3.7" and python_version < "4" \ @@ -108,7 +108,7 @@ idna==3.4 ; python_version >= "3.7" and python_version < "4" \ pycparser==2.21 ; python_version >= "3.7" and python_version < "4.0" \ --hash=sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9 \ --hash=sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206 -PyJWT==2.5.0; python_version >= "3.7" and python_version < "4.0" \ +pyjwt==2.5.0 ; python_version >= "3.7" and python_version < "4.0" \ --hash=sha256:8d82e7087868e94dd8d7d418e5088ce64f7daab4b36db654cbaedb46f9d1ca80 \ --hash=sha256:e77ab89480905d86998442ac5788f35333fa85f65047a534adc38edf3c88fc3b requests==2.28.1 ; python_version >= "3.7" and python_version < "4" \ diff --git a/tests/test_auth.py b/tests/test_auth.py index 7a31db73..e2dda636 100644 --- a/tests/test_auth.py +++ b/tests/test_auth.py @@ -231,7 +231,7 @@ def test_refresh_token(self): auth = Auth(self.dummy_project_id, self.public_key_dict) # Test fail flow - with patch("requests.get") as mock_request: + with patch("requests.post") as mock_request: mock_request.return_value.ok = False self.assertRaises( AuthException, @@ -244,7 +244,7 @@ def test_exchange_access_key(self): auth = Auth(self.dummy_project_id, self.public_key_dict) # Test fail flow - with patch("requests.get") as mock_request: + with patch("requests.post") as mock_request: mock_request.return_value.ok = False self.assertRaises( AuthException, diff --git a/tests/test_descope_client.py b/tests/test_descope_client.py index e05fb2ad..e3c55f14 100644 --- a/tests/test_descope_client.py +++ b/tests/test_descope_client.py @@ -59,13 +59,13 @@ def test_logout(self): self.assertRaises(AuthException, client.logout, None) # Test failed flow - with patch("requests.get") as mock_get: - mock_get.return_value.ok = False + with patch("requests.post") as mock_post: + mock_post.return_value.ok = False self.assertRaises(AuthException, client.logout, dummy_refresh_token) # Test success flow - with patch("requests.get") as mock_get: - mock_get.return_value.ok = True + with patch("requests.post") as mock_post: + mock_post.return_value.ok = True self.assertIsNotNone(client.logout(dummy_refresh_token)) def test_me(self): @@ -273,7 +273,7 @@ def test_expired_token(self): new_session_token = "eyJhbGciOiJFUzM4NCIsImtpZCI6IlAyQ3VDOXl2MlVHdEdJMW84NGdDWkViOXFFUVciLCJ0eXAiOiJKV1QifQ.eyJkcm4iOiJEUyIsImV4cCI6MjQ5MzA2MTQxNSwiaWF0IjoxNjU5NjQzMDYxLCJpc3MiOiJQMkN1Qzl5djJVR3RHSTFvODRnQ1pFYjlxRVFXIiwic3ViIjoiVTJDdUNQdUpnUFdIR0I1UDRHbWZidVBHaEdWbSJ9.gMalOv1GhqYVsfITcOc7Jv_fibX1Iof6AFy2KCVmyHmU2KwATT6XYXsHjBFFLq262Pg-LS1IX9f_DV3ppzvb1pSY4ccsP6WDGd1vJpjp3wFBP9Sji6WXL0SCCJUFIyJR" valid_refresh_token = "eyJhbGciOiJFUzM4NCIsImtpZCI6IlAyQ3VDOXl2MlVHdEdJMW84NGdDWkViOXFFUVciLCJ0eXAiOiJKV1QifQ.eyJkcm4iOiJEU1IiLCJleHAiOjIyNjQ0NDMwNjEsImlhdCI6MTY1OTY0MzA2MSwiaXNzIjoiUDJDdUM5eXYyVUd0R0kxbzg0Z0NaRWI5cUVRVyIsInN1YiI6IlUyQ3VDUHVKZ1BXSEdCNVA0R21mYnVQR2hHVm0ifQ.mRo9FihYMR3qnQT06Mj3CJ5X0uTCEcXASZqfLLUv0cPCLBtBqYTbuK-ZRDnV4e4N6zGCNX2a3jjpbyqbViOxICCNSxJsVb-sdsSujtEXwVMsTTLnpWmNsMbOUiKmoME0" expired_token = "eyJhbGciOiJFUzM4NCIsImtpZCI6IlAyQ3VDOXl2MlVHdEdJMW84NGdDWkViOXFFUVciLCJ0eXAiOiJKV1QifQ.eyJkcm4iOiJEUyIsImV4cCI6MTY1OTY0NDI5OCwiaWF0IjoxNjU5NjQ0Mjk3LCJpc3MiOiJQMkN1Qzl5djJVR3RHSTFvODRnQ1pFYjlxRVFXIiwic3ViIjoiVTJDdUNQdUpnUFdIR0I1UDRHbWZidVBHaEdWbSJ9.wBuOnIQI_z3SXOszqsWCg8ilOPdE5ruWYHA3jkaeQ3uX9hWgCTd69paFajc-xdMYbqlIF7JHji7T9oVmkCUJvDNgRZRZO9boMFANPyXitLOK4aX3VZpMJBpFxdrWV3GE" - with patch("requests.get") as mock_request: + with patch("requests.post") as mock_request: my_mock_response = mock.Mock() my_mock_response.ok = True my_mock_response.json.return_value = {"sessionJwt": new_session_token} @@ -363,7 +363,7 @@ def test_validate_permissions(self): client.validate_tenant_permissions(jwt_response, "t1", ["Perm 2"]) ) - jwt_response = {"tenants": {"t1": {"roles": "Perm 1"}}} + jwt_response = {"tenants": {"t1": {"permissions": "Perm 1"}}} self.assertTrue( client.validate_tenant_permissions(jwt_response, "t1", ["Perm 1"]) ) @@ -395,11 +395,11 @@ def test_validate_roles(self): jwt_response = {"tenants": {"t1": {}}} self.assertFalse(client.validate_tenant_roles(jwt_response, "t1", ["Perm 2"])) - jwt_response = {"tenants": {"t1": {"roles": "Perm 1"}}} - self.assertTrue(client.validate_tenant_roles(jwt_response, "t1", ["Perm 1"])) - self.assertFalse(client.validate_tenant_roles(jwt_response, "t1", ["Perm 2"])) + jwt_response = {"tenants": {"t1": {"roles": "Role 1"}}} + self.assertTrue(client.validate_tenant_roles(jwt_response, "t1", ["Role 1"])) + self.assertFalse(client.validate_tenant_roles(jwt_response, "t1", ["Role 2"])) self.assertFalse( - client.validate_tenant_roles(jwt_response, "t1", ["Perm 1", "Perm 2"]) + client.validate_tenant_roles(jwt_response, "t1", ["Role 1", "Role 2"]) ) diff --git a/tests/test_magiclink.py b/tests/test_magiclink.py index 3cea55d8..e8ee5ff7 100644 --- a/tests/test_magiclink.py +++ b/tests/test_magiclink.py @@ -258,6 +258,7 @@ def test_sign_in_cross_device(self): "crossDevice": True, } ), + allow_redirects=False, verify=True, ) self.assertEqual(res["pendingRef"], "aaaa") @@ -291,6 +292,7 @@ def test_sign_up_cross_device(self): "email": "dummy@dummy.com", } ), + allow_redirects=False, verify=True, ) self.assertEqual(res["pendingRef"], "aaaa") @@ -321,6 +323,7 @@ def test_sign_up_or_in_cross_device(self): "crossDevice": True, } ), + allow_redirects=False, verify=True, ) diff --git a/tests/test_oauth.py b/tests/test_oauth.py index b6b60a1e..96a6da5e 100644 --- a/tests/test_oauth.py +++ b/tests/test_oauth.py @@ -80,7 +80,7 @@ def test_oauth_start(self): ) def test_compose_exchange_params(self): - self.assertEqual(Auth._compose_exchange_params("c1"), {"code": "c1"}) + self.assertEqual(Auth._compose_exchange_body("c1"), {"code": "c1"}) def test_exchange_token(self): oauth = OAuth(Auth(self.dummy_project_id, self.public_key_dict)) diff --git a/tests/test_saml.py b/tests/test_saml.py index e32c8f0c..af812ecd 100644 --- a/tests/test_saml.py +++ b/tests/test_saml.py @@ -62,7 +62,7 @@ def test_saml_start(self): ) def test_compose_exchange_params(self): - self.assertEqual(Auth._compose_exchange_params("c1"), {"code": "c1"}) + self.assertEqual(Auth._compose_exchange_body("c1"), {"code": "c1"}) def test_exchange_token(self): saml = SAML(Auth(self.dummy_project_id, self.public_key_dict)) diff --git a/tests/test_totp.py b/tests/test_totp.py index f2ba8c43..f82f9bf1 100644 --- a/tests/test_totp.py +++ b/tests/test_totp.py @@ -123,6 +123,7 @@ def test_update_user(self): "Authorization": f"Bearer {self.dummy_project_id}:{valid_jwt_token}", }, data=json.dumps({"externalId": "dummy@dummy.com"}), + allow_redirects=False, verify=True, ) self.assertEqual(res, valid_response) diff --git a/tests/test_webauthn.py b/tests/test_webauthn.py index a042dbff..c6684343 100644 --- a/tests/test_webauthn.py +++ b/tests/test_webauthn.py @@ -99,6 +99,7 @@ def test_sign_up_start(self): data=json.dumps( {"user": {"externalId": "id1"}, "origin": "https://example.com"} ), + allow_redirects=False, verify=True, ) self.assertEqual(res, valid_response) @@ -145,6 +146,7 @@ def test_sign_up_finish(self): "Authorization": f"Bearer {self.dummy_project_id}", }, data=json.dumps({"transactionId": "t01", "response": "response01"}), + allow_redirects=False, verify=True, ) self.assertIsNotNone(webauthn.sign_up_finish("t01", "response01")) @@ -191,6 +193,7 @@ def test_sign_in_start(self): "Authorization": f"Bearer {self.dummy_project_id}", }, data=json.dumps({"externalId": "id1", "origin": "https://example.com"}), + allow_redirects=False, verify=True, ) self.assertEqual(res, valid_response), @@ -230,6 +233,7 @@ def test_sign_in_finish(self): "Authorization": f"Bearer {self.dummy_project_id}", }, data=json.dumps({"transactionId": "t01", "response": "response01"}), + allow_redirects=False, verify=True, ) self.assertIsNotNone(webauthn.sign_up_finish("t01", "response01")) @@ -298,6 +302,7 @@ def test_update_start(self): data=json.dumps( {"externalId": "dummy@dummy.com", "origin": "https://example.com"} ), + allow_redirects=False, verify=True, ) self.assertEqual(res, valid_response) @@ -336,6 +341,7 @@ def test_update_finish(self): "Authorization": f"Bearer {self.dummy_project_id}", }, data=json.dumps({"transactionId": "t01", "response": "response01"}), + allow_redirects=False, verify=True, ) self.assertIsNotNone(webauthn.sign_up_finish("t01", "response01"))