From 170c1b7ce2b98811b01c9336c1c71c6f6e25fedf Mon Sep 17 00:00:00 2001 From: Blair Lunceford Date: Wed, 17 Feb 2021 12:13:30 -0700 Subject: [PATCH 1/4] Fix bug in supporting client id --- tests/test_client.py | 4 +-- tests/test_sso.py | 59 ++++++++++++++++++++++++++++---------- workos/utils/validation.py | 17 ++++++++--- 3 files changed, 59 insertions(+), 21 deletions(-) diff --git a/tests/test_client.py b/tests/test_client.py index 62a4d01b..4523a5aa 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -43,7 +43,7 @@ def test_initialize_sso_missing_client_id(self, set_api_key): message = str(ex) - assert "project_id" in message + assert "client_id" in message assert "api_key" not in message def test_initialize_sso_missing_api_key_and_project_id(self): @@ -52,7 +52,7 @@ def test_initialize_sso_missing_api_key_and_project_id(self): message = str(ex) - assert all(setting in message for setting in ("api_key", "project_id",)) + assert all(setting in message for setting in ("api_key", "client_id",)) def test_initialize_audit_trail_missing_api_key(self): with pytest.raises(ConfigurationException) as ex: diff --git a/tests/test_sso.py b/tests/test_sso.py index 40e3f0e5..64233255 100644 --- a/tests/test_sso.py +++ b/tests/test_sso.py @@ -11,8 +11,17 @@ class TestSSO(object): - @pytest.fixture(autouse=True) - def setup(self, set_api_key_and_project_id): + @pytest.fixture + def setup_with_client_id(self, set_api_key_and_client_id): + self.provider = ConnectionType.GoogleOAuth + self.customer_domain = "workos.com" + self.redirect_uri = "https://localhost/auth/callback" + self.state = json.dumps({"things": "with_stuff",}) + + self.sso = SSO() + + @pytest.fixture + def setup_with_project_id(self, set_api_key_and_project_id): self.provider = ConnectionType.GoogleOAuth self.customer_domain = "workos.com" self.redirect_uri = "https://localhost/auth/callback" @@ -91,14 +100,14 @@ def mock_connections(self): } def test_authorization_url_throws_value_error_with_missing_domain_and_provider( - self, + self, setup_with_client_id ): with pytest.raises(ValueError, match=r"Incomplete arguments.*"): self.sso.get_authorization_url( redirect_uri=self.redirect_uri, state=self.state ) - def test_authorization_url_throws_value_error_with_incorrect_provider_type(self): + def test_authorization_url_throws_value_error_with_incorrect_provider_type(self, setup_with_client_id): with pytest.raises( ValueError, match="'provider' must be of type ConnectionType" ): @@ -106,7 +115,7 @@ def test_authorization_url_throws_value_error_with_incorrect_provider_type(self) provider="foo", redirect_uri=self.redirect_uri, state=self.state ) - def test_authorization_url_has_expected_query_params_with_provider(self): + def test_authorization_url_has_expected_query_params_with_provider(self, setup_with_client_id): authorization_url = self.sso.get_authorization_url( provider=self.provider, redirect_uri=self.redirect_uri, state=self.state ) @@ -115,13 +124,13 @@ def test_authorization_url_has_expected_query_params_with_provider(self): assert dict(parse_qsl(parsed_url.query)) == { "provider": str(self.provider.value), - "client_id": workos.project_id, + "client_id": workos.client_id, "redirect_uri": self.redirect_uri, "response_type": RESPONSE_TYPE_CODE, "state": self.state, } - def test_authorization_url_has_expected_query_params_with_domain(self): + def test_authorization_url_has_expected_query_params_with_domain(self, setup_with_client_id): authorization_url = self.sso.get_authorization_url( domain=self.customer_domain, redirect_uri=self.redirect_uri, @@ -132,13 +141,13 @@ def test_authorization_url_has_expected_query_params_with_domain(self): assert dict(parse_qsl(parsed_url.query)) == { "domain": self.customer_domain, - "client_id": workos.project_id, + "client_id": workos.client_id, "redirect_uri": self.redirect_uri, "response_type": RESPONSE_TYPE_CODE, "state": self.state, } - def test_authorization_url_has_expected_query_params_with_domain_and_provider(self): + def test_authorization_url_has_expected_query_params_with_domain_and_provider(self, setup_with_client_id): authorization_url = self.sso.get_authorization_url( domain=self.customer_domain, provider=self.provider, @@ -151,14 +160,34 @@ def test_authorization_url_has_expected_query_params_with_domain_and_provider(se assert dict(parse_qsl(parsed_url.query)) == { "domain": self.customer_domain, "provider": str(self.provider.value), - "client_id": workos.project_id, + "client_id": workos.client_id, "redirect_uri": self.redirect_uri, "response_type": RESPONSE_TYPE_CODE, "state": self.state, } + def test_authorization_url_supports_project_id_with_deprecation_warning(self, setup_with_project_id): + with pytest.deprecated_call(): + authorization_url = self.sso.get_authorization_url( + domain=self.customer_domain, + provider=self.provider, + redirect_uri=self.redirect_uri, + state=self.state, + ) + + parsed_url = urlparse(authorization_url) + + assert dict(parse_qsl(parsed_url.query)) == { + "domain": self.customer_domain, + "provider": str(self.provider.value), + "client_id": workos.project_id, + "redirect_uri": self.redirect_uri, + "response_type": RESPONSE_TYPE_CODE, + "state": self.state, + } + def test_get_profile_returns_expected_workosprofile_object( - self, mock_profile, mock_request_method + self, setup_with_client_id, mock_profile, mock_request_method ): response_dict = { "profile": { @@ -185,7 +214,7 @@ def test_get_profile_returns_expected_workosprofile_object( assert profile.to_dict() == mock_profile - def test_create_connection(self, mock_request_method, mock_connection): + def test_create_connection(self, setup_with_client_id, mock_request_method, mock_connection): response_dict = { "object": "connection", "id": mock_connection["id"], @@ -208,7 +237,7 @@ def test_create_connection(self, mock_request_method, mock_connection): connection = self.sso.create_connection("draft_conn_id") assert connection == response_dict - def test_get_connection(self, mock_connection, mock_request_method): + def test_get_connection(self, setup_with_client_id, mock_connection, mock_request_method): mock_response = Response() mock_response.status_code = 200 mock_response.response_dict = mock_connection @@ -217,7 +246,7 @@ def test_get_connection(self, mock_connection, mock_request_method): assert response.status_code == 200 assert response.response_dict == mock_connection - def test_list_connections(self, mock_connections, mock_request_method): + def test_list_connections(self, setup_with_client_id, mock_connections, mock_request_method): mock_response = Response() mock_response.status_code = 200 mock_response.response_dict = mock_connections @@ -226,7 +255,7 @@ def test_list_connections(self, mock_connections, mock_request_method): assert response.status_code == 200 assert response.response_dict == mock_connections - def test_delete_connection(self, mock_request_method): + def test_delete_connection(self, setup_with_client_id, mock_request_method): mock_response = Response() mock_response.status_code = 200 mock_request_method("delete", mock_response, 200) diff --git a/workos/utils/validation.py b/workos/utils/validation.py index 7f4699e5..567ad718 100644 --- a/workos/utils/validation.py +++ b/workos/utils/validation.py @@ -14,7 +14,7 @@ DIRECTORY_SYNC_MODULE: ["api_key",], PASSWORDLESS_MODULE: ["api_key",], PORTAL_MODULE: ["api_key",], - SSO_MODULE: ["api_key", "project_id",], + SSO_MODULE: ["api_key", "client_id",], } @@ -23,9 +23,18 @@ def decorator(fn): @wraps(fn) def wrapper(*args, **kwargs): missing_settings = [] - for setting in REQUIRED_SETTINGS_FOR_MODULE[module_name]: - if not getattr(workos, setting, None): - missing_settings.append(setting) + + # Adding this to accept both client_id and project_id + # can remove once project_id is deprecated + if module_name == SSO_MODULE: + if not getattr(workos, "api_key", None): + missing_settings.append("api_key") + if not getattr(workos, "client_id", None) and not getattr(workos, "project_id", None): + missing_settings.append("client_id") + else: + for setting in REQUIRED_SETTINGS_FOR_MODULE[module_name]: + if not getattr(workos, setting, None): + missing_settings.append(setting) if missing_settings: raise ConfigurationException( From aed5e19f6d61f2c4c24ac96af43123168fb004c2 Mon Sep 17 00:00:00 2001 From: Blair Lunceford Date: Wed, 17 Feb 2021 12:20:17 -0700 Subject: [PATCH 2/4] Formatted files --- tests/test_sso.py | 44 +++++++++++++++++++++++++++++--------- workos/utils/validation.py | 25 ++++++++++++++++------ 2 files changed, 53 insertions(+), 16 deletions(-) diff --git a/tests/test_sso.py b/tests/test_sso.py index 64233255..9f57f77d 100644 --- a/tests/test_sso.py +++ b/tests/test_sso.py @@ -16,7 +16,11 @@ def setup_with_client_id(self, set_api_key_and_client_id): self.provider = ConnectionType.GoogleOAuth self.customer_domain = "workos.com" self.redirect_uri = "https://localhost/auth/callback" - self.state = json.dumps({"things": "with_stuff",}) + self.state = json.dumps( + { + "things": "with_stuff", + } + ) self.sso = SSO() @@ -25,7 +29,11 @@ def setup_with_project_id(self, set_api_key_and_project_id): self.provider = ConnectionType.GoogleOAuth self.customer_domain = "workos.com" self.redirect_uri = "https://localhost/auth/callback" - self.state = json.dumps({"things": "with_stuff",}) + self.state = json.dumps( + { + "things": "with_stuff", + } + ) self.sso = SSO() @@ -107,7 +115,9 @@ def test_authorization_url_throws_value_error_with_missing_domain_and_provider( redirect_uri=self.redirect_uri, state=self.state ) - def test_authorization_url_throws_value_error_with_incorrect_provider_type(self, setup_with_client_id): + def test_authorization_url_throws_value_error_with_incorrect_provider_type( + self, setup_with_client_id + ): with pytest.raises( ValueError, match="'provider' must be of type ConnectionType" ): @@ -115,7 +125,9 @@ def test_authorization_url_throws_value_error_with_incorrect_provider_type(self, provider="foo", redirect_uri=self.redirect_uri, state=self.state ) - def test_authorization_url_has_expected_query_params_with_provider(self, setup_with_client_id): + def test_authorization_url_has_expected_query_params_with_provider( + self, setup_with_client_id + ): authorization_url = self.sso.get_authorization_url( provider=self.provider, redirect_uri=self.redirect_uri, state=self.state ) @@ -130,7 +142,9 @@ def test_authorization_url_has_expected_query_params_with_provider(self, setup_w "state": self.state, } - def test_authorization_url_has_expected_query_params_with_domain(self, setup_with_client_id): + def test_authorization_url_has_expected_query_params_with_domain( + self, setup_with_client_id + ): authorization_url = self.sso.get_authorization_url( domain=self.customer_domain, redirect_uri=self.redirect_uri, @@ -147,7 +161,9 @@ def test_authorization_url_has_expected_query_params_with_domain(self, setup_wit "state": self.state, } - def test_authorization_url_has_expected_query_params_with_domain_and_provider(self, setup_with_client_id): + def test_authorization_url_has_expected_query_params_with_domain_and_provider( + self, setup_with_client_id + ): authorization_url = self.sso.get_authorization_url( domain=self.customer_domain, provider=self.provider, @@ -166,7 +182,9 @@ def test_authorization_url_has_expected_query_params_with_domain_and_provider(se "state": self.state, } - def test_authorization_url_supports_project_id_with_deprecation_warning(self, setup_with_project_id): + def test_authorization_url_supports_project_id_with_deprecation_warning( + self, setup_with_project_id + ): with pytest.deprecated_call(): authorization_url = self.sso.get_authorization_url( domain=self.customer_domain, @@ -214,7 +232,9 @@ def test_get_profile_returns_expected_workosprofile_object( assert profile.to_dict() == mock_profile - def test_create_connection(self, setup_with_client_id, mock_request_method, mock_connection): + def test_create_connection( + self, setup_with_client_id, mock_request_method, mock_connection + ): response_dict = { "object": "connection", "id": mock_connection["id"], @@ -237,7 +257,9 @@ def test_create_connection(self, setup_with_client_id, mock_request_method, mock connection = self.sso.create_connection("draft_conn_id") assert connection == response_dict - def test_get_connection(self, setup_with_client_id, mock_connection, mock_request_method): + def test_get_connection( + self, setup_with_client_id, mock_connection, mock_request_method + ): mock_response = Response() mock_response.status_code = 200 mock_response.response_dict = mock_connection @@ -246,7 +268,9 @@ def test_get_connection(self, setup_with_client_id, mock_connection, mock_reques assert response.status_code == 200 assert response.response_dict == mock_connection - def test_list_connections(self, setup_with_client_id, mock_connections, mock_request_method): + def test_list_connections( + self, setup_with_client_id, mock_connections, mock_request_method + ): mock_response = Response() mock_response.status_code = 200 mock_response.response_dict = mock_connections diff --git a/workos/utils/validation.py b/workos/utils/validation.py index 567ad718..dbf6a985 100644 --- a/workos/utils/validation.py +++ b/workos/utils/validation.py @@ -10,11 +10,22 @@ SSO_MODULE = "SSO" REQUIRED_SETTINGS_FOR_MODULE = { - AUDIT_TRAIL_MODULE: ["api_key",], - DIRECTORY_SYNC_MODULE: ["api_key",], - PASSWORDLESS_MODULE: ["api_key",], - PORTAL_MODULE: ["api_key",], - SSO_MODULE: ["api_key", "client_id",], + AUDIT_TRAIL_MODULE: [ + "api_key", + ], + DIRECTORY_SYNC_MODULE: [ + "api_key", + ], + PASSWORDLESS_MODULE: [ + "api_key", + ], + PORTAL_MODULE: [ + "api_key", + ], + SSO_MODULE: [ + "api_key", + "client_id", + ], } @@ -29,7 +40,9 @@ def wrapper(*args, **kwargs): if module_name == SSO_MODULE: if not getattr(workos, "api_key", None): missing_settings.append("api_key") - if not getattr(workos, "client_id", None) and not getattr(workos, "project_id", None): + if not getattr(workos, "client_id", None) and not getattr( + workos, "project_id", None + ): missing_settings.append("client_id") else: for setting in REQUIRED_SETTINGS_FOR_MODULE[module_name]: From b2533d216869c79b16761f098232ecfe67ac216c Mon Sep 17 00:00:00 2001 From: Blair Lunceford Date: Wed, 17 Feb 2021 13:06:48 -0700 Subject: [PATCH 3/4] Bump minor status --- workos/__about__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/workos/__about__.py b/workos/__about__.py index f53028ed..2781df44 100644 --- a/workos/__about__.py +++ b/workos/__about__.py @@ -12,7 +12,7 @@ __package_url__ = "https://github.com/workos-inc/workos-python" -__version__ = "0.8.3" +__version__ = "0.8.4" __author__ = "WorkOS" From 65c6ea67df3b3c35aca1665feb3920979798a741 Mon Sep 17 00:00:00 2001 From: Blair Lunceford Date: Wed, 17 Feb 2021 13:07:27 -0700 Subject: [PATCH 4/4] Formatted files with correct balck version --- tests/test_sso.py | 12 ++---------- workos/utils/validation.py | 21 +++++---------------- 2 files changed, 7 insertions(+), 26 deletions(-) diff --git a/tests/test_sso.py b/tests/test_sso.py index 9f57f77d..6e02f2d5 100644 --- a/tests/test_sso.py +++ b/tests/test_sso.py @@ -16,11 +16,7 @@ def setup_with_client_id(self, set_api_key_and_client_id): self.provider = ConnectionType.GoogleOAuth self.customer_domain = "workos.com" self.redirect_uri = "https://localhost/auth/callback" - self.state = json.dumps( - { - "things": "with_stuff", - } - ) + self.state = json.dumps({"things": "with_stuff",}) self.sso = SSO() @@ -29,11 +25,7 @@ def setup_with_project_id(self, set_api_key_and_project_id): self.provider = ConnectionType.GoogleOAuth self.customer_domain = "workos.com" self.redirect_uri = "https://localhost/auth/callback" - self.state = json.dumps( - { - "things": "with_stuff", - } - ) + self.state = json.dumps({"things": "with_stuff",}) self.sso = SSO() diff --git a/workos/utils/validation.py b/workos/utils/validation.py index dbf6a985..139be8f4 100644 --- a/workos/utils/validation.py +++ b/workos/utils/validation.py @@ -10,22 +10,11 @@ SSO_MODULE = "SSO" REQUIRED_SETTINGS_FOR_MODULE = { - AUDIT_TRAIL_MODULE: [ - "api_key", - ], - DIRECTORY_SYNC_MODULE: [ - "api_key", - ], - PASSWORDLESS_MODULE: [ - "api_key", - ], - PORTAL_MODULE: [ - "api_key", - ], - SSO_MODULE: [ - "api_key", - "client_id", - ], + AUDIT_TRAIL_MODULE: ["api_key",], + DIRECTORY_SYNC_MODULE: ["api_key",], + PASSWORDLESS_MODULE: ["api_key",], + PORTAL_MODULE: ["api_key",], + SSO_MODULE: ["api_key", "client_id",], }