crewai-core 1.14.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- crewai_core-1.14.5/.gitignore +33 -0
- crewai_core-1.14.5/PKG-INFO +31 -0
- crewai_core-1.14.5/README.md +8 -0
- crewai_core-1.14.5/pyproject.toml +38 -0
- crewai_core-1.14.5/src/crewai_core/__init__.py +1 -0
- crewai_core-1.14.5/src/crewai_core/auth/__init__.py +24 -0
- crewai_core-1.14.5/src/crewai_core/auth/constants.py +8 -0
- crewai_core-1.14.5/src/crewai_core/auth/oauth2.py +186 -0
- crewai_core-1.14.5/src/crewai_core/auth/providers/__init__.py +1 -0
- crewai_core-1.14.5/src/crewai_core/auth/providers/auth0.py +40 -0
- crewai_core-1.14.5/src/crewai_core/auth/providers/base_provider.py +46 -0
- crewai_core-1.14.5/src/crewai_core/auth/providers/entra_id.py +49 -0
- crewai_core-1.14.5/src/crewai_core/auth/providers/keycloak.py +38 -0
- crewai_core-1.14.5/src/crewai_core/auth/providers/okta.py +48 -0
- crewai_core-1.14.5/src/crewai_core/auth/providers/workos.py +36 -0
- crewai_core-1.14.5/src/crewai_core/auth/token.py +17 -0
- crewai_core-1.14.5/src/crewai_core/auth/utils.py +71 -0
- crewai_core-1.14.5/src/crewai_core/constants.py +22 -0
- crewai_core-1.14.5/src/crewai_core/lock_store.py +89 -0
- crewai_core-1.14.5/src/crewai_core/paths.py +26 -0
- crewai_core-1.14.5/src/crewai_core/plus_api.py +232 -0
- crewai_core-1.14.5/src/crewai_core/printer.py +103 -0
- crewai_core-1.14.5/src/crewai_core/project.py +109 -0
- crewai_core-1.14.5/src/crewai_core/py.typed +0 -0
- crewai_core-1.14.5/src/crewai_core/settings.py +215 -0
- crewai_core-1.14.5/src/crewai_core/telemetry.py +272 -0
- crewai_core-1.14.5/src/crewai_core/token_manager.py +151 -0
- crewai_core-1.14.5/src/crewai_core/tool_credentials.py +56 -0
- crewai_core-1.14.5/src/crewai_core/user_data.py +91 -0
- crewai_core-1.14.5/src/crewai_core/version.py +194 -0
- crewai_core-1.14.5/tests/__init__.py +0 -0
- crewai_core-1.14.5/tests/test_smoke.py +96 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
.DS_Store
|
|
2
|
+
.pytest_cache
|
|
3
|
+
__pycache__
|
|
4
|
+
dist/
|
|
5
|
+
.env
|
|
6
|
+
assets/*
|
|
7
|
+
.idea
|
|
8
|
+
test/
|
|
9
|
+
docs_crew/
|
|
10
|
+
chroma.sqlite3
|
|
11
|
+
old_en.json
|
|
12
|
+
db/
|
|
13
|
+
test.py
|
|
14
|
+
rc-tests/*
|
|
15
|
+
*.pkl
|
|
16
|
+
temp/*
|
|
17
|
+
.vscode/*
|
|
18
|
+
crew_tasks_output.json
|
|
19
|
+
.codesight
|
|
20
|
+
.mypy_cache
|
|
21
|
+
.ruff_cache
|
|
22
|
+
.venv
|
|
23
|
+
test_flow.html
|
|
24
|
+
crewairules.mdc
|
|
25
|
+
plan.md
|
|
26
|
+
conceptual_plan.md
|
|
27
|
+
build_image
|
|
28
|
+
chromadb-*.lock
|
|
29
|
+
.claude
|
|
30
|
+
.crewai/memory
|
|
31
|
+
blogs/*
|
|
32
|
+
secrets/*
|
|
33
|
+
UNKNOWN.egg-info/
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: crewai-core
|
|
3
|
+
Version: 1.14.5
|
|
4
|
+
Summary: Shared utilities for CrewAI — version, paths, user-data, telemetry, printer.
|
|
5
|
+
Project-URL: Homepage, https://crewai.com
|
|
6
|
+
Project-URL: Documentation, https://docs.crewai.com
|
|
7
|
+
Project-URL: Repository, https://github.com/crewAIInc/crewAI
|
|
8
|
+
Author-email: "Greyson R. LaLonde" <greyson@crewai.com>
|
|
9
|
+
Requires-Python: <3.14,>=3.10
|
|
10
|
+
Requires-Dist: appdirs~=1.4.4
|
|
11
|
+
Requires-Dist: cryptography>=42.0
|
|
12
|
+
Requires-Dist: httpx~=0.28.1
|
|
13
|
+
Requires-Dist: opentelemetry-api~=1.34.0
|
|
14
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-http~=1.34.0
|
|
15
|
+
Requires-Dist: opentelemetry-sdk~=1.34.0
|
|
16
|
+
Requires-Dist: packaging>=23.0
|
|
17
|
+
Requires-Dist: portalocker~=2.7.0
|
|
18
|
+
Requires-Dist: pydantic<2.13,>=2.11.9
|
|
19
|
+
Requires-Dist: pyjwt<3,>=2.9.0
|
|
20
|
+
Requires-Dist: rich>=13.7.1
|
|
21
|
+
Requires-Dist: tomli~=2.0.2
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
# crewai-core
|
|
25
|
+
|
|
26
|
+
Shared utilities used by both `crewai` and `crewai-cli`: version lookup, storage
|
|
27
|
+
paths, user-data helpers, telemetry, and the printer.
|
|
28
|
+
|
|
29
|
+
This package is a leaf — it has no dependency on the `crewai` framework — and is
|
|
30
|
+
pulled in transitively by `crewai` and `crewai-cli`. End users do not normally
|
|
31
|
+
install it directly.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# crewai-core
|
|
2
|
+
|
|
3
|
+
Shared utilities used by both `crewai` and `crewai-cli`: version lookup, storage
|
|
4
|
+
paths, user-data helpers, telemetry, and the printer.
|
|
5
|
+
|
|
6
|
+
This package is a leaf — it has no dependency on the `crewai` framework — and is
|
|
7
|
+
pulled in transitively by `crewai` and `crewai-cli`. End users do not normally
|
|
8
|
+
install it directly.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "crewai-core"
|
|
3
|
+
dynamic = ["version"]
|
|
4
|
+
description = "Shared utilities for CrewAI — version, paths, user-data, telemetry, printer."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "Greyson R. LaLonde", email = "greyson@crewai.com" }
|
|
8
|
+
]
|
|
9
|
+
requires-python = ">=3.10, <3.14"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"appdirs~=1.4.4",
|
|
12
|
+
"cryptography>=42.0",
|
|
13
|
+
"httpx~=0.28.1",
|
|
14
|
+
"packaging>=23.0",
|
|
15
|
+
"portalocker~=2.7.0",
|
|
16
|
+
"pyjwt>=2.9.0,<3",
|
|
17
|
+
"pydantic>=2.11.9,<2.13",
|
|
18
|
+
"rich>=13.7.1",
|
|
19
|
+
"opentelemetry-api~=1.34.0",
|
|
20
|
+
"opentelemetry-sdk~=1.34.0",
|
|
21
|
+
"opentelemetry-exporter-otlp-proto-http~=1.34.0",
|
|
22
|
+
"tomli~=2.0.2",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
[project.urls]
|
|
26
|
+
Homepage = "https://crewai.com"
|
|
27
|
+
Documentation = "https://docs.crewai.com"
|
|
28
|
+
Repository = "https://github.com/crewAIInc/crewAI"
|
|
29
|
+
|
|
30
|
+
[build-system]
|
|
31
|
+
requires = ["hatchling"]
|
|
32
|
+
build-backend = "hatchling.build"
|
|
33
|
+
|
|
34
|
+
[tool.hatch.version]
|
|
35
|
+
path = "src/crewai_core/__init__.py"
|
|
36
|
+
|
|
37
|
+
[tool.hatch.build.targets.wheel]
|
|
38
|
+
packages = ["src/crewai_core"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.14.5"
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""OAuth2 authentication primitives — shared by crewai and crewai-cli."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from crewai_core.auth.oauth2 import (
|
|
6
|
+
AuthenticationCommand as AuthenticationCommand,
|
|
7
|
+
Oauth2Settings as Oauth2Settings,
|
|
8
|
+
ProviderFactory as ProviderFactory,
|
|
9
|
+
)
|
|
10
|
+
from crewai_core.auth.token import (
|
|
11
|
+
AuthError as AuthError,
|
|
12
|
+
get_auth_token as get_auth_token,
|
|
13
|
+
)
|
|
14
|
+
from crewai_core.auth.utils import validate_jwt_token as validate_jwt_token
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"AuthError",
|
|
19
|
+
"AuthenticationCommand",
|
|
20
|
+
"Oauth2Settings",
|
|
21
|
+
"ProviderFactory",
|
|
22
|
+
"get_auth_token",
|
|
23
|
+
"validate_jwt_token",
|
|
24
|
+
]
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
"""OAuth2 device-flow authentication for the CrewAI platform."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import time
|
|
6
|
+
from typing import TYPE_CHECKING, Any, TypeVar, cast
|
|
7
|
+
import webbrowser
|
|
8
|
+
|
|
9
|
+
import httpx
|
|
10
|
+
from pydantic import BaseModel, Field
|
|
11
|
+
from rich.console import Console
|
|
12
|
+
|
|
13
|
+
from crewai_core.auth.utils import validate_jwt_token
|
|
14
|
+
from crewai_core.settings import Settings
|
|
15
|
+
from crewai_core.token_manager import TokenManager
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
console = Console()
|
|
19
|
+
|
|
20
|
+
TOauth2Settings = TypeVar("TOauth2Settings", bound="Oauth2Settings")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class Oauth2Settings(BaseModel):
|
|
24
|
+
"""OAuth2 provider configuration."""
|
|
25
|
+
|
|
26
|
+
provider: str = Field(
|
|
27
|
+
description="OAuth2 provider used for authentication (e.g., workos, okta, auth0)."
|
|
28
|
+
)
|
|
29
|
+
client_id: str = Field(
|
|
30
|
+
description="OAuth2 client ID issued by the provider, used during authentication requests."
|
|
31
|
+
)
|
|
32
|
+
domain: str = Field(
|
|
33
|
+
description="OAuth2 provider's domain (e.g., your-org.auth0.com) used for issuing tokens."
|
|
34
|
+
)
|
|
35
|
+
audience: str | None = Field(
|
|
36
|
+
description="OAuth2 audience value, typically used to identify the target API or resource.",
|
|
37
|
+
default=None,
|
|
38
|
+
)
|
|
39
|
+
extra: dict[str, Any] = Field(
|
|
40
|
+
description="Extra configuration for the OAuth2 provider.",
|
|
41
|
+
default={},
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
@classmethod
|
|
45
|
+
def from_settings(cls: type[TOauth2Settings]) -> TOauth2Settings:
|
|
46
|
+
"""Build an ``Oauth2Settings`` instance from the persisted CrewAI settings."""
|
|
47
|
+
settings = Settings()
|
|
48
|
+
|
|
49
|
+
return cls(
|
|
50
|
+
provider=settings.oauth2_provider,
|
|
51
|
+
domain=settings.oauth2_domain,
|
|
52
|
+
client_id=settings.oauth2_client_id,
|
|
53
|
+
audience=settings.oauth2_audience,
|
|
54
|
+
extra=settings.oauth2_extra,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
if TYPE_CHECKING:
|
|
59
|
+
from crewai_core.auth.providers.base_provider import BaseProvider
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class ProviderFactory:
|
|
63
|
+
"""Factory for resolving the configured OAuth2 provider."""
|
|
64
|
+
|
|
65
|
+
@classmethod
|
|
66
|
+
def from_settings(
|
|
67
|
+
cls: type["ProviderFactory"], # noqa: UP037
|
|
68
|
+
settings: Oauth2Settings | None = None,
|
|
69
|
+
) -> "BaseProvider": # noqa: UP037
|
|
70
|
+
"""Create a provider instance from settings, importing the module dynamically."""
|
|
71
|
+
settings = settings or Oauth2Settings.from_settings()
|
|
72
|
+
|
|
73
|
+
import importlib
|
|
74
|
+
|
|
75
|
+
module = importlib.import_module(
|
|
76
|
+
f"crewai_core.auth.providers.{settings.provider.lower()}"
|
|
77
|
+
)
|
|
78
|
+
provider = getattr(
|
|
79
|
+
module,
|
|
80
|
+
f"{''.join(word.capitalize() for word in settings.provider.split('_'))}Provider",
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
return cast("BaseProvider", provider(settings))
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class AuthenticationCommand:
|
|
87
|
+
"""Drives the OAuth2 device-flow login against the configured provider."""
|
|
88
|
+
|
|
89
|
+
def __init__(self) -> None:
|
|
90
|
+
self.token_manager = TokenManager()
|
|
91
|
+
self.oauth2_provider = ProviderFactory.from_settings()
|
|
92
|
+
|
|
93
|
+
def login(self) -> None:
|
|
94
|
+
"""Sign in to the CrewAI platform via the OAuth2 device flow."""
|
|
95
|
+
console.print("Signing in to CrewAI AMP...\n", style="bold blue")
|
|
96
|
+
|
|
97
|
+
device_code_data = self._get_device_code()
|
|
98
|
+
self._display_auth_instructions(device_code_data)
|
|
99
|
+
|
|
100
|
+
return self._poll_for_token(device_code_data)
|
|
101
|
+
|
|
102
|
+
def _get_device_code(self) -> dict[str, Any]:
|
|
103
|
+
"""Request a device code from the provider."""
|
|
104
|
+
device_code_payload = {
|
|
105
|
+
"client_id": self.oauth2_provider.get_client_id(),
|
|
106
|
+
"scope": " ".join(self.oauth2_provider.get_oauth_scopes()),
|
|
107
|
+
"audience": self.oauth2_provider.get_audience(),
|
|
108
|
+
}
|
|
109
|
+
response = httpx.post(
|
|
110
|
+
url=self.oauth2_provider.get_authorize_url(),
|
|
111
|
+
data=device_code_payload,
|
|
112
|
+
timeout=20,
|
|
113
|
+
)
|
|
114
|
+
response.raise_for_status()
|
|
115
|
+
return cast(dict[str, Any], response.json())
|
|
116
|
+
|
|
117
|
+
def _display_auth_instructions(self, device_code_data: dict[str, str]) -> None:
|
|
118
|
+
"""Print and open the verification URL the user must visit."""
|
|
119
|
+
verification_uri = device_code_data.get(
|
|
120
|
+
"verification_uri_complete", device_code_data.get("verification_uri", "")
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
console.print("1. Navigate to: ", verification_uri)
|
|
124
|
+
console.print("2. Enter the following code: ", device_code_data["user_code"])
|
|
125
|
+
webbrowser.open(verification_uri)
|
|
126
|
+
|
|
127
|
+
def _poll_for_token(self, device_code_data: dict[str, Any]) -> None:
|
|
128
|
+
"""Poll the token endpoint until authentication completes or times out."""
|
|
129
|
+
token_payload = {
|
|
130
|
+
"grant_type": "urn:ietf:params:oauth:grant-type:device_code",
|
|
131
|
+
"device_code": device_code_data["device_code"],
|
|
132
|
+
"client_id": self.oauth2_provider.get_client_id(),
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
console.print("\nWaiting for authentication... ", style="bold blue", end="")
|
|
136
|
+
|
|
137
|
+
attempts = 0
|
|
138
|
+
while True and attempts < 10:
|
|
139
|
+
response = httpx.post(
|
|
140
|
+
self.oauth2_provider.get_token_url(), data=token_payload, timeout=30
|
|
141
|
+
)
|
|
142
|
+
token_data = response.json()
|
|
143
|
+
|
|
144
|
+
if response.status_code == 200:
|
|
145
|
+
self._validate_and_save_token(token_data)
|
|
146
|
+
|
|
147
|
+
console.print(
|
|
148
|
+
"Success!",
|
|
149
|
+
style="bold green",
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
self._post_login()
|
|
153
|
+
|
|
154
|
+
console.print("\n[bold green]Welcome to CrewAI AMP![/bold green]\n")
|
|
155
|
+
return
|
|
156
|
+
|
|
157
|
+
if token_data["error"] not in ("authorization_pending", "slow_down"):
|
|
158
|
+
raise httpx.HTTPError(
|
|
159
|
+
token_data.get("error_description") or token_data.get("error")
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
time.sleep(device_code_data["interval"])
|
|
163
|
+
attempts += 1
|
|
164
|
+
|
|
165
|
+
console.print(
|
|
166
|
+
"Timeout: Failed to get the token. Please try again.", style="bold red"
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
def _validate_and_save_token(self, token_data: dict[str, Any]) -> None:
|
|
170
|
+
"""Validate the JWT and persist it via the token manager."""
|
|
171
|
+
jwt_token = token_data["access_token"]
|
|
172
|
+
issuer = self.oauth2_provider.get_issuer()
|
|
173
|
+
jwt_token_data = {
|
|
174
|
+
"jwt_token": jwt_token,
|
|
175
|
+
"jwks_url": self.oauth2_provider.get_jwks_url(),
|
|
176
|
+
"issuer": issuer,
|
|
177
|
+
"audience": self.oauth2_provider.get_audience(),
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
decoded_token = validate_jwt_token(**jwt_token_data)
|
|
181
|
+
|
|
182
|
+
expires_at = decoded_token.get("exp", 0)
|
|
183
|
+
self.token_manager.save_tokens(jwt_token, expires_at)
|
|
184
|
+
|
|
185
|
+
def _post_login(self) -> None:
|
|
186
|
+
"""Hook called after a successful login. Override to extend behavior."""
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""OAuth2 authentication providers."""
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""Auth0 OAuth2 provider."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from crewai_core.auth.providers.base_provider import BaseProvider
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Auth0Provider(BaseProvider):
|
|
9
|
+
"""Auth0 OAuth2 provider implementation."""
|
|
10
|
+
|
|
11
|
+
def get_authorize_url(self) -> str:
|
|
12
|
+
return f"https://{self._get_domain()}/oauth/device/code"
|
|
13
|
+
|
|
14
|
+
def get_token_url(self) -> str:
|
|
15
|
+
return f"https://{self._get_domain()}/oauth/token"
|
|
16
|
+
|
|
17
|
+
def get_jwks_url(self) -> str:
|
|
18
|
+
return f"https://{self._get_domain()}/.well-known/jwks.json"
|
|
19
|
+
|
|
20
|
+
def get_issuer(self) -> str:
|
|
21
|
+
return f"https://{self._get_domain()}/"
|
|
22
|
+
|
|
23
|
+
def get_audience(self) -> str:
|
|
24
|
+
if self.settings.audience is None:
|
|
25
|
+
raise ValueError(
|
|
26
|
+
"Audience is required. Please set it in the configuration."
|
|
27
|
+
)
|
|
28
|
+
return self.settings.audience
|
|
29
|
+
|
|
30
|
+
def get_client_id(self) -> str:
|
|
31
|
+
if self.settings.client_id is None:
|
|
32
|
+
raise ValueError(
|
|
33
|
+
"Client ID is required. Please set it in the configuration."
|
|
34
|
+
)
|
|
35
|
+
return self.settings.client_id
|
|
36
|
+
|
|
37
|
+
def _get_domain(self) -> str:
|
|
38
|
+
if self.settings.domain is None:
|
|
39
|
+
raise ValueError("Domain is required. Please set it in the configuration.")
|
|
40
|
+
return self.settings.domain
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""Base OAuth2 provider interface."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from abc import ABC, abstractmethod
|
|
6
|
+
|
|
7
|
+
from crewai_core.auth.oauth2 import Oauth2Settings
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class BaseProvider(ABC):
|
|
11
|
+
"""Abstract base class for OAuth2 providers."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, settings: Oauth2Settings):
|
|
14
|
+
self.settings = settings
|
|
15
|
+
|
|
16
|
+
@abstractmethod
|
|
17
|
+
def get_authorize_url(self) -> str:
|
|
18
|
+
"""Return the authorization endpoint URL."""
|
|
19
|
+
|
|
20
|
+
@abstractmethod
|
|
21
|
+
def get_token_url(self) -> str:
|
|
22
|
+
"""Return the token endpoint URL."""
|
|
23
|
+
|
|
24
|
+
@abstractmethod
|
|
25
|
+
def get_jwks_url(self) -> str:
|
|
26
|
+
"""Return the JWKS endpoint URL."""
|
|
27
|
+
|
|
28
|
+
@abstractmethod
|
|
29
|
+
def get_issuer(self) -> str:
|
|
30
|
+
"""Return the OAuth issuer identifier."""
|
|
31
|
+
|
|
32
|
+
@abstractmethod
|
|
33
|
+
def get_audience(self) -> str:
|
|
34
|
+
"""Return the OAuth audience identifier."""
|
|
35
|
+
|
|
36
|
+
@abstractmethod
|
|
37
|
+
def get_client_id(self) -> str:
|
|
38
|
+
"""Return the OAuth client identifier."""
|
|
39
|
+
|
|
40
|
+
def get_required_fields(self) -> list[str]:
|
|
41
|
+
"""Return provider-specific keys required inside ``Oauth2Settings.extra``."""
|
|
42
|
+
return []
|
|
43
|
+
|
|
44
|
+
def get_oauth_scopes(self) -> list[str]:
|
|
45
|
+
"""Return the OAuth scopes to request."""
|
|
46
|
+
return ["openid", "profile", "email"]
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""Entra ID (Azure AD) OAuth2 provider."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import cast
|
|
6
|
+
|
|
7
|
+
from crewai_core.auth.providers.base_provider import BaseProvider
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class EntraIdProvider(BaseProvider):
|
|
11
|
+
"""Entra ID (Azure AD) OAuth2 provider implementation."""
|
|
12
|
+
|
|
13
|
+
def get_authorize_url(self) -> str:
|
|
14
|
+
return f"{self._base_url()}/oauth2/v2.0/devicecode"
|
|
15
|
+
|
|
16
|
+
def get_token_url(self) -> str:
|
|
17
|
+
return f"{self._base_url()}/oauth2/v2.0/token"
|
|
18
|
+
|
|
19
|
+
def get_jwks_url(self) -> str:
|
|
20
|
+
return f"{self._base_url()}/discovery/v2.0/keys"
|
|
21
|
+
|
|
22
|
+
def get_issuer(self) -> str:
|
|
23
|
+
return f"{self._base_url()}/v2.0"
|
|
24
|
+
|
|
25
|
+
def get_audience(self) -> str:
|
|
26
|
+
if self.settings.audience is None:
|
|
27
|
+
raise ValueError(
|
|
28
|
+
"Audience is required. Please set it in the configuration."
|
|
29
|
+
)
|
|
30
|
+
return self.settings.audience
|
|
31
|
+
|
|
32
|
+
def get_client_id(self) -> str:
|
|
33
|
+
if self.settings.client_id is None:
|
|
34
|
+
raise ValueError(
|
|
35
|
+
"Client ID is required. Please set it in the configuration."
|
|
36
|
+
)
|
|
37
|
+
return self.settings.client_id
|
|
38
|
+
|
|
39
|
+
def get_oauth_scopes(self) -> list[str]:
|
|
40
|
+
return [
|
|
41
|
+
*super().get_oauth_scopes(),
|
|
42
|
+
*cast(str, self.settings.extra.get("scope", "")).split(),
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
def get_required_fields(self) -> list[str]:
|
|
46
|
+
return ["scope"]
|
|
47
|
+
|
|
48
|
+
def _base_url(self) -> str:
|
|
49
|
+
return f"https://login.microsoftonline.com/{self.settings.domain}"
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Keycloak OAuth2 provider."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from crewai_core.auth.providers.base_provider import BaseProvider
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class KeycloakProvider(BaseProvider):
|
|
9
|
+
"""Keycloak OAuth2 provider implementation."""
|
|
10
|
+
|
|
11
|
+
def get_authorize_url(self) -> str:
|
|
12
|
+
return f"{self._oauth2_base_url()}/realms/{self.settings.extra.get('realm')}/protocol/openid-connect/auth/device"
|
|
13
|
+
|
|
14
|
+
def get_token_url(self) -> str:
|
|
15
|
+
return f"{self._oauth2_base_url()}/realms/{self.settings.extra.get('realm')}/protocol/openid-connect/token"
|
|
16
|
+
|
|
17
|
+
def get_jwks_url(self) -> str:
|
|
18
|
+
return f"{self._oauth2_base_url()}/realms/{self.settings.extra.get('realm')}/protocol/openid-connect/certs"
|
|
19
|
+
|
|
20
|
+
def get_issuer(self) -> str:
|
|
21
|
+
return f"{self._oauth2_base_url()}/realms/{self.settings.extra.get('realm')}"
|
|
22
|
+
|
|
23
|
+
def get_audience(self) -> str:
|
|
24
|
+
return self.settings.audience or "no-audience-provided"
|
|
25
|
+
|
|
26
|
+
def get_client_id(self) -> str:
|
|
27
|
+
if self.settings.client_id is None:
|
|
28
|
+
raise ValueError(
|
|
29
|
+
"Client ID is required. Please set it in the configuration."
|
|
30
|
+
)
|
|
31
|
+
return self.settings.client_id
|
|
32
|
+
|
|
33
|
+
def get_required_fields(self) -> list[str]:
|
|
34
|
+
return ["realm"]
|
|
35
|
+
|
|
36
|
+
def _oauth2_base_url(self) -> str:
|
|
37
|
+
domain = self.settings.domain.removeprefix("https://").removeprefix("http://")
|
|
38
|
+
return f"https://{domain}"
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""Okta OAuth2 provider."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from crewai_core.auth.providers.base_provider import BaseProvider
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class OktaProvider(BaseProvider):
|
|
9
|
+
"""Okta OAuth2 provider implementation."""
|
|
10
|
+
|
|
11
|
+
def get_authorize_url(self) -> str:
|
|
12
|
+
return f"{self._oauth2_base_url()}/v1/device/authorize"
|
|
13
|
+
|
|
14
|
+
def get_token_url(self) -> str:
|
|
15
|
+
return f"{self._oauth2_base_url()}/v1/token"
|
|
16
|
+
|
|
17
|
+
def get_jwks_url(self) -> str:
|
|
18
|
+
return f"{self._oauth2_base_url()}/v1/keys"
|
|
19
|
+
|
|
20
|
+
def get_issuer(self) -> str:
|
|
21
|
+
return self._oauth2_base_url().removesuffix("/oauth2")
|
|
22
|
+
|
|
23
|
+
def get_audience(self) -> str:
|
|
24
|
+
if self.settings.audience is None:
|
|
25
|
+
raise ValueError(
|
|
26
|
+
"Audience is required. Please set it in the configuration."
|
|
27
|
+
)
|
|
28
|
+
return self.settings.audience
|
|
29
|
+
|
|
30
|
+
def get_client_id(self) -> str:
|
|
31
|
+
if self.settings.client_id is None:
|
|
32
|
+
raise ValueError(
|
|
33
|
+
"Client ID is required. Please set it in the configuration."
|
|
34
|
+
)
|
|
35
|
+
return self.settings.client_id
|
|
36
|
+
|
|
37
|
+
def get_required_fields(self) -> list[str]:
|
|
38
|
+
return ["authorization_server_name", "using_org_auth_server"]
|
|
39
|
+
|
|
40
|
+
def _oauth2_base_url(self) -> str:
|
|
41
|
+
using_org_auth_server = self.settings.extra.get("using_org_auth_server", False)
|
|
42
|
+
|
|
43
|
+
if using_org_auth_server:
|
|
44
|
+
base_url = f"https://{self.settings.domain}/oauth2"
|
|
45
|
+
else:
|
|
46
|
+
base_url = f"https://{self.settings.domain}/oauth2/{self.settings.extra.get('authorization_server_name', 'default')}"
|
|
47
|
+
|
|
48
|
+
return f"{base_url}"
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""WorkOS OAuth2 provider."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from crewai_core.auth.providers.base_provider import BaseProvider
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class WorkosProvider(BaseProvider):
|
|
9
|
+
"""WorkOS OAuth2 provider implementation."""
|
|
10
|
+
|
|
11
|
+
def get_authorize_url(self) -> str:
|
|
12
|
+
return f"https://{self._get_domain()}/oauth2/device_authorization"
|
|
13
|
+
|
|
14
|
+
def get_token_url(self) -> str:
|
|
15
|
+
return f"https://{self._get_domain()}/oauth2/token"
|
|
16
|
+
|
|
17
|
+
def get_jwks_url(self) -> str:
|
|
18
|
+
return f"https://{self._get_domain()}/oauth2/jwks"
|
|
19
|
+
|
|
20
|
+
def get_issuer(self) -> str:
|
|
21
|
+
return f"https://{self._get_domain()}"
|
|
22
|
+
|
|
23
|
+
def get_audience(self) -> str:
|
|
24
|
+
return self.settings.audience or ""
|
|
25
|
+
|
|
26
|
+
def get_client_id(self) -> str:
|
|
27
|
+
if self.settings.client_id is None:
|
|
28
|
+
raise ValueError(
|
|
29
|
+
"Client ID is required. Please set it in the configuration."
|
|
30
|
+
)
|
|
31
|
+
return self.settings.client_id
|
|
32
|
+
|
|
33
|
+
def _get_domain(self) -> str:
|
|
34
|
+
if self.settings.domain is None:
|
|
35
|
+
raise ValueError("Domain is required. Please set it in the configuration.")
|
|
36
|
+
return self.settings.domain
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Authentication token retrieval."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from crewai_core.token_manager import TokenManager
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class AuthError(Exception):
|
|
9
|
+
"""Raised when authentication fails."""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def get_auth_token() -> str:
|
|
13
|
+
"""Return the saved authentication token; raise ``AuthError`` if missing."""
|
|
14
|
+
access_token = TokenManager().get_token()
|
|
15
|
+
if not access_token:
|
|
16
|
+
raise AuthError("No token found, make sure you are logged in")
|
|
17
|
+
return access_token
|