diff --git a/lib/crewai-tools/src/crewai_tools/tools/crewai_platform_tools/crewai_platform_action_tool.py b/lib/crewai-tools/src/crewai_tools/tools/crewai_platform_tools/crewai_platform_action_tool.py index c848cfd21..098256940 100644 --- a/lib/crewai-tools/src/crewai_tools/tools/crewai_platform_tools/crewai_platform_action_tool.py +++ b/lib/crewai-tools/src/crewai_tools/tools/crewai_platform_tools/crewai_platform_action_tool.py @@ -1,5 +1,5 @@ """Crewai Enterprise Tools.""" - +import os import json import re from typing import Any, Optional, Union, cast, get_origin @@ -432,7 +432,11 @@ class CrewAIPlatformActionTool(BaseTool): payload = cleaned_kwargs response = requests.post( - url=api_url, headers=headers, json=payload, timeout=60 + url=api_url, + headers=headers, + json=payload, + timeout=60, + verify=os.environ.get("CREWAI_FACTORY", "false").lower() != "true", ) data = response.json() diff --git a/lib/crewai-tools/src/crewai_tools/tools/crewai_platform_tools/crewai_platform_tool_builder.py b/lib/crewai-tools/src/crewai_tools/tools/crewai_platform_tools/crewai_platform_tool_builder.py index 3bf9cfc7e..564637189 100644 --- a/lib/crewai-tools/src/crewai_tools/tools/crewai_platform_tools/crewai_platform_tool_builder.py +++ b/lib/crewai-tools/src/crewai_tools/tools/crewai_platform_tools/crewai_platform_tool_builder.py @@ -1,5 +1,5 @@ from typing import Any - +import os from crewai.tools import BaseTool import requests @@ -37,6 +37,7 @@ class CrewaiPlatformToolBuilder: headers=headers, timeout=30, params={"apps": ",".join(self._apps)}, + verify=os.environ.get("CREWAI_FACTORY", "false").lower() != "true", ) response.raise_for_status() except Exception: diff --git a/lib/crewai-tools/tests/tools/crewai_platform_tools/test_crewai_platform_action_tool.py b/lib/crewai-tools/tests/tools/crewai_platform_tools/test_crewai_platform_action_tool.py index 6f1df9e8a..5bbbb3f91 100644 --- a/lib/crewai-tools/tests/tools/crewai_platform_tools/test_crewai_platform_action_tool.py +++ b/lib/crewai-tools/tests/tools/crewai_platform_tools/test_crewai_platform_action_tool.py @@ -1,4 +1,6 @@ from typing import Union, get_args, get_origin +from unittest.mock import patch, Mock +import os from crewai_tools.tools.crewai_platform_tools.crewai_platform_action_tool import ( CrewAIPlatformActionTool, @@ -249,3 +251,109 @@ class TestSchemaProcessing: result_type = tool._process_schema_type(test_schema, "TestFieldAllOfMixed") assert result_type is str + +class TestCrewAIPlatformActionToolVerify: + """Test suite for SSL verification behavior based on CREWAI_FACTORY environment variable""" + + def setup_method(self): + self.action_schema = { + "function": { + "name": "test_action", + "parameters": { + "properties": { + "test_param": { + "type": "string", + "description": "Test parameter" + } + }, + "required": [] + } + } + } + + def create_test_tool(self): + return CrewAIPlatformActionTool( + description="Test action tool", + action_name="test_action", + action_schema=self.action_schema + ) + + @patch.dict("os.environ", {"CREWAI_PLATFORM_INTEGRATION_TOKEN": "test_token"}, clear=True) + @patch("crewai_tools.tools.crewai_platform_tools.crewai_platform_action_tool.requests.post") + def test_run_with_ssl_verification_default(self, mock_post): + """Test that _run uses SSL verification by default when CREWAI_FACTORY is not set""" + mock_response = Mock() + mock_response.ok = True + mock_response.json.return_value = {"result": "success"} + mock_post.return_value = mock_response + + tool = self.create_test_tool() + tool._run(test_param="test_value") + + mock_post.assert_called_once() + call_args = mock_post.call_args + assert call_args.kwargs["verify"] is True + + @patch.dict("os.environ", {"CREWAI_PLATFORM_INTEGRATION_TOKEN": "test_token", "CREWAI_FACTORY": "false"}, clear=True) + @patch("crewai_tools.tools.crewai_platform_tools.crewai_platform_action_tool.requests.post") + def test_run_with_ssl_verification_factory_false(self, mock_post): + """Test that _run uses SSL verification when CREWAI_FACTORY is 'false'""" + mock_response = Mock() + mock_response.ok = True + mock_response.json.return_value = {"result": "success"} + mock_post.return_value = mock_response + + tool = self.create_test_tool() + tool._run(test_param="test_value") + + mock_post.assert_called_once() + call_args = mock_post.call_args + assert call_args.kwargs["verify"] is True + + @patch.dict("os.environ", {"CREWAI_PLATFORM_INTEGRATION_TOKEN": "test_token", "CREWAI_FACTORY": "FALSE"}, clear=True) + @patch("crewai_tools.tools.crewai_platform_tools.crewai_platform_action_tool.requests.post") + def test_run_with_ssl_verification_factory_false_uppercase(self, mock_post): + """Test that _run uses SSL verification when CREWAI_FACTORY is 'FALSE' (case-insensitive)""" + mock_response = Mock() + mock_response.ok = True + mock_response.json.return_value = {"result": "success"} + mock_post.return_value = mock_response + + tool = self.create_test_tool() + tool._run(test_param="test_value") + + mock_post.assert_called_once() + call_args = mock_post.call_args + assert call_args.kwargs["verify"] is True + + @patch.dict("os.environ", {"CREWAI_PLATFORM_INTEGRATION_TOKEN": "test_token", "CREWAI_FACTORY": "true"}, clear=True) + @patch("crewai_tools.tools.crewai_platform_tools.crewai_platform_action_tool.requests.post") + def test_run_without_ssl_verification_factory_true(self, mock_post): + """Test that _run disables SSL verification when CREWAI_FACTORY is 'true'""" + mock_response = Mock() + mock_response.ok = True + mock_response.json.return_value = {"result": "success"} + mock_post.return_value = mock_response + + tool = self.create_test_tool() + tool._run(test_param="test_value") + + mock_post.assert_called_once() + call_args = mock_post.call_args + assert call_args.kwargs["verify"] is False + + @patch.dict("os.environ", {"CREWAI_PLATFORM_INTEGRATION_TOKEN": "test_token", "CREWAI_FACTORY": "TRUE"}, clear=True) + @patch("crewai_tools.tools.crewai_platform_tools.crewai_platform_action_tool.requests.post") + def test_run_without_ssl_verification_factory_true_uppercase(self, mock_post): + """Test that _run disables SSL verification when CREWAI_FACTORY is 'TRUE' (case-insensitive)""" + mock_response = Mock() + mock_response.ok = True + mock_response.json.return_value = {"result": "success"} + mock_post.return_value = mock_response + + tool = self.create_test_tool() + tool._run(test_param="test_value") + + mock_post.assert_called_once() + call_args = mock_post.call_args + assert call_args.kwargs["verify"] is False diff --git a/lib/crewai-tools/tests/tools/crewai_platform_tools/test_crewai_platform_tool_builder.py b/lib/crewai-tools/tests/tools/crewai_platform_tools/test_crewai_platform_tool_builder.py index 7e6453fd4..880312d44 100644 --- a/lib/crewai-tools/tests/tools/crewai_platform_tools/test_crewai_platform_tool_builder.py +++ b/lib/crewai-tools/tests/tools/crewai_platform_tools/test_crewai_platform_tool_builder.py @@ -258,3 +258,98 @@ class TestCrewaiPlatformToolBuilder(unittest.TestCase): assert "simple_string" in description_text assert "nested_object" in description_text assert "array_prop" in description_text + + + +class TestCrewaiPlatformToolBuilderVerify(unittest.TestCase): + """Test suite for SSL verification behavior in CrewaiPlatformToolBuilder""" + + @patch.dict("os.environ", {"CREWAI_PLATFORM_INTEGRATION_TOKEN": "test_token"}, clear=True) + @patch( + "crewai_tools.tools.crewai_platform_tools.crewai_platform_tool_builder.requests.get" + ) + def test_fetch_actions_with_ssl_verification_default(self, mock_get): + """Test that _fetch_actions uses SSL verification by default when CREWAI_FACTORY is not set""" + mock_response = Mock() + mock_response.raise_for_status.return_value = None + mock_response.json.return_value = {"actions": {}} + mock_get.return_value = mock_response + + builder = CrewaiPlatformToolBuilder(apps=["github"]) + builder._fetch_actions() + + mock_get.assert_called_once() + call_args = mock_get.call_args + assert call_args.kwargs["verify"] is True + + @patch.dict("os.environ", {"CREWAI_PLATFORM_INTEGRATION_TOKEN": "test_token", "CREWAI_FACTORY": "false"}, clear=True) + @patch( + "crewai_tools.tools.crewai_platform_tools.crewai_platform_tool_builder.requests.get" + ) + def test_fetch_actions_with_ssl_verification_factory_false(self, mock_get): + """Test that _fetch_actions uses SSL verification when CREWAI_FACTORY is 'false'""" + mock_response = Mock() + mock_response.raise_for_status.return_value = None + mock_response.json.return_value = {"actions": {}} + mock_get.return_value = mock_response + + builder = CrewaiPlatformToolBuilder(apps=["github"]) + builder._fetch_actions() + + mock_get.assert_called_once() + call_args = mock_get.call_args + assert call_args.kwargs["verify"] is True + + @patch.dict("os.environ", {"CREWAI_PLATFORM_INTEGRATION_TOKEN": "test_token", "CREWAI_FACTORY": "FALSE"}, clear=True) + @patch( + "crewai_tools.tools.crewai_platform_tools.crewai_platform_tool_builder.requests.get" + ) + def test_fetch_actions_with_ssl_verification_factory_false_uppercase(self, mock_get): + """Test that _fetch_actions uses SSL verification when CREWAI_FACTORY is 'FALSE' (case-insensitive)""" + mock_response = Mock() + mock_response.raise_for_status.return_value = None + mock_response.json.return_value = {"actions": {}} + mock_get.return_value = mock_response + + builder = CrewaiPlatformToolBuilder(apps=["github"]) + builder._fetch_actions() + + mock_get.assert_called_once() + call_args = mock_get.call_args + assert call_args.kwargs["verify"] is True + + @patch.dict("os.environ", {"CREWAI_PLATFORM_INTEGRATION_TOKEN": "test_token", "CREWAI_FACTORY": "true"}, clear=True) + @patch( + "crewai_tools.tools.crewai_platform_tools.crewai_platform_tool_builder.requests.get" + ) + def test_fetch_actions_without_ssl_verification_factory_true(self, mock_get): + """Test that _fetch_actions disables SSL verification when CREWAI_FACTORY is 'true'""" + mock_response = Mock() + mock_response.raise_for_status.return_value = None + mock_response.json.return_value = {"actions": {}} + mock_get.return_value = mock_response + + builder = CrewaiPlatformToolBuilder(apps=["github"]) + builder._fetch_actions() + + mock_get.assert_called_once() + call_args = mock_get.call_args + assert call_args.kwargs["verify"] is False + + @patch.dict("os.environ", {"CREWAI_PLATFORM_INTEGRATION_TOKEN": "test_token", "CREWAI_FACTORY": "TRUE"}, clear=True) + @patch( + "crewai_tools.tools.crewai_platform_tools.crewai_platform_tool_builder.requests.get" + ) + def test_fetch_actions_without_ssl_verification_factory_true_uppercase(self, mock_get): + """Test that _fetch_actions disables SSL verification when CREWAI_FACTORY is 'TRUE' (case-insensitive)""" + mock_response = Mock() + mock_response.raise_for_status.return_value = None + mock_response.json.return_value = {"actions": {}} + mock_get.return_value = mock_response + + builder = CrewaiPlatformToolBuilder(apps=["github"]) + builder._fetch_actions() + + mock_get.assert_called_once() + call_args = mock_get.call_args + assert call_args.kwargs["verify"] is False diff --git a/lib/crewai/src/crewai/cli/plus_api.py b/lib/crewai/src/crewai/cli/plus_api.py index 5d7141179..62f34095b 100644 --- a/lib/crewai/src/crewai/cli/plus_api.py +++ b/lib/crewai/src/crewai/cli/plus_api.py @@ -1,6 +1,6 @@ from typing import Any from urllib.parse import urljoin - +import os import requests from crewai.cli.config import Settings @@ -33,9 +33,7 @@ class PlusAPI: if settings.org_uuid: self.headers["X-Crewai-Organization-Id"] = settings.org_uuid - self.base_url = ( - str(settings.enterprise_base_url) or DEFAULT_CREWAI_ENTERPRISE_URL - ) + self.base_url = os.getenv("CREWAI_PLUS_URL") or str(settings.enterprise_base_url) or DEFAULT_CREWAI_ENTERPRISE_URL def _make_request( self, method: str, endpoint: str, **kwargs: Any diff --git a/lib/crewai/tests/cli/test_plus_api.py b/lib/crewai/tests/cli/test_plus_api.py index 937d023a7..0a8946c2b 100644 --- a/lib/crewai/tests/cli/test_plus_api.py +++ b/lib/crewai/tests/cli/test_plus_api.py @@ -1,7 +1,7 @@ +import os import unittest from unittest.mock import ANY, MagicMock, patch -from crewai.cli.constants import DEFAULT_CREWAI_ENTERPRISE_URL from crewai.cli.plus_api import PlusAPI @@ -35,7 +35,7 @@ class TestPlusAPI(unittest.TestCase): ): mock_make_request.assert_called_once_with( method, - f"{DEFAULT_CREWAI_ENTERPRISE_URL}{endpoint}", + f"{os.getenv('CREWAI_PLUS_URL')}{endpoint}", headers={ "Authorization": ANY, "Content-Type": ANY, @@ -53,7 +53,7 @@ class TestPlusAPI(unittest.TestCase): ): mock_settings = MagicMock() mock_settings.org_uuid = self.org_uuid - mock_settings.enterprise_base_url = DEFAULT_CREWAI_ENTERPRISE_URL + mock_settings.enterprise_base_url = os.getenv('CREWAI_PLUS_URL') mock_settings_class.return_value = mock_settings # re-initialize Client self.api = PlusAPI(self.api_key) @@ -84,7 +84,7 @@ class TestPlusAPI(unittest.TestCase): def test_get_agent_with_org_uuid(self, mock_make_request, mock_settings_class): mock_settings = MagicMock() mock_settings.org_uuid = self.org_uuid - mock_settings.enterprise_base_url = DEFAULT_CREWAI_ENTERPRISE_URL + mock_settings.enterprise_base_url = os.getenv('CREWAI_PLUS_URL') mock_settings_class.return_value = mock_settings # re-initialize Client self.api = PlusAPI(self.api_key) @@ -115,7 +115,7 @@ class TestPlusAPI(unittest.TestCase): def test_get_tool_with_org_uuid(self, mock_make_request, mock_settings_class): mock_settings = MagicMock() mock_settings.org_uuid = self.org_uuid - mock_settings.enterprise_base_url = DEFAULT_CREWAI_ENTERPRISE_URL + mock_settings.enterprise_base_url = os.getenv('CREWAI_PLUS_URL') mock_settings_class.return_value = mock_settings # re-initialize Client self.api = PlusAPI(self.api_key) @@ -163,7 +163,7 @@ class TestPlusAPI(unittest.TestCase): def test_publish_tool_with_org_uuid(self, mock_make_request, mock_settings_class): mock_settings = MagicMock() mock_settings.org_uuid = self.org_uuid - mock_settings.enterprise_base_url = DEFAULT_CREWAI_ENTERPRISE_URL + mock_settings.enterprise_base_url = os.getenv('CREWAI_PLUS_URL') mock_settings_class.return_value = mock_settings # re-initialize Client self.api = PlusAPI(self.api_key) @@ -320,6 +320,7 @@ class TestPlusAPI(unittest.TestCase): ) @patch("crewai.cli.plus_api.Settings") + @patch.dict(os.environ, {"CREWAI_PLUS_URL": ""}) def test_custom_base_url(self, mock_settings_class): mock_settings = MagicMock() mock_settings.enterprise_base_url = "https://custom-url.com/api" @@ -329,3 +330,11 @@ class TestPlusAPI(unittest.TestCase): custom_api.base_url, "https://custom-url.com/api", ) + + @patch.dict(os.environ, {"CREWAI_PLUS_URL": "https://custom-url-from-env.com"}) + def test_custom_base_url_from_env(self): + custom_api = PlusAPI("test_key") + self.assertEqual( + custom_api.base_url, + "https://custom-url-from-env.com", + )