google-adk-extras 0.1.1__py3-none-any.whl → 0.2.3__py3-none-any.whl
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.
- google_adk_extras/__init__.py +31 -1
- google_adk_extras/adk_builder.py +1030 -0
- google_adk_extras/artifacts/__init__.py +25 -12
- google_adk_extras/artifacts/base_custom_artifact_service.py +148 -11
- google_adk_extras/artifacts/local_folder_artifact_service.py +133 -13
- google_adk_extras/artifacts/s3_artifact_service.py +135 -19
- google_adk_extras/artifacts/sql_artifact_service.py +109 -10
- google_adk_extras/credentials/__init__.py +34 -0
- google_adk_extras/credentials/base_custom_credential_service.py +113 -0
- google_adk_extras/credentials/github_oauth2_credential_service.py +213 -0
- google_adk_extras/credentials/google_oauth2_credential_service.py +216 -0
- google_adk_extras/credentials/http_basic_auth_credential_service.py +388 -0
- google_adk_extras/credentials/jwt_credential_service.py +345 -0
- google_adk_extras/credentials/microsoft_oauth2_credential_service.py +250 -0
- google_adk_extras/credentials/x_oauth2_credential_service.py +240 -0
- google_adk_extras/custom_agent_loader.py +156 -0
- google_adk_extras/enhanced_adk_web_server.py +137 -0
- google_adk_extras/enhanced_fastapi.py +470 -0
- google_adk_extras/enhanced_runner.py +38 -0
- google_adk_extras/memory/__init__.py +30 -13
- google_adk_extras/memory/base_custom_memory_service.py +37 -5
- google_adk_extras/memory/sql_memory_service.py +105 -19
- google_adk_extras/memory/yaml_file_memory_service.py +115 -22
- google_adk_extras/sessions/__init__.py +29 -13
- google_adk_extras/sessions/base_custom_session_service.py +133 -11
- google_adk_extras/sessions/sql_session_service.py +127 -16
- google_adk_extras/sessions/yaml_file_session_service.py +122 -14
- google_adk_extras-0.2.3.dist-info/METADATA +302 -0
- google_adk_extras-0.2.3.dist-info/RECORD +37 -0
- google_adk_extras/py.typed +0 -0
- google_adk_extras-0.1.1.dist-info/METADATA +0 -175
- google_adk_extras-0.1.1.dist-info/RECORD +0 -25
- {google_adk_extras-0.1.1.dist-info → google_adk_extras-0.2.3.dist-info}/WHEEL +0 -0
- {google_adk_extras-0.1.1.dist-info → google_adk_extras-0.2.3.dist-info}/licenses/LICENSE +0 -0
- {google_adk_extras-0.1.1.dist-info → google_adk_extras-0.2.3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,388 @@
|
|
1
|
+
"""HTTP Basic Auth credential service implementation."""
|
2
|
+
|
3
|
+
from typing import Optional, Dict
|
4
|
+
import logging
|
5
|
+
import base64
|
6
|
+
|
7
|
+
from google.adk.auth.credential_service.session_state_credential_service import SessionStateCredentialService
|
8
|
+
from google.adk.auth.credential_service.base_credential_service import CallbackContext
|
9
|
+
from google.adk.auth import AuthConfig, AuthCredential, AuthCredentialTypes
|
10
|
+
from google.adk.auth.auth_credential import HttpAuth, HttpCredentials
|
11
|
+
from fastapi.openapi.models import HTTPBase
|
12
|
+
|
13
|
+
from .base_custom_credential_service import BaseCustomCredentialService
|
14
|
+
|
15
|
+
logger = logging.getLogger(__name__)
|
16
|
+
|
17
|
+
|
18
|
+
class HTTPBasicAuthCredentialService(BaseCustomCredentialService):
|
19
|
+
"""HTTP Basic Auth credential service for username/password authentication.
|
20
|
+
|
21
|
+
This service manages HTTP Basic Authentication credentials, encoding username
|
22
|
+
and password combinations for API authentication.
|
23
|
+
|
24
|
+
Args:
|
25
|
+
username: The username for basic authentication.
|
26
|
+
password: The password for basic authentication.
|
27
|
+
realm: Optional realm parameter for HTTP Basic Auth.
|
28
|
+
use_session_state: If True, stores credentials in session state. If False,
|
29
|
+
uses in-memory storage. Default is True for persistence.
|
30
|
+
|
31
|
+
Example:
|
32
|
+
```python
|
33
|
+
credential_service = HTTPBasicAuthCredentialService(
|
34
|
+
username="api_user",
|
35
|
+
password="api_password",
|
36
|
+
realm="API Access"
|
37
|
+
)
|
38
|
+
await credential_service.initialize()
|
39
|
+
|
40
|
+
# Use with Runner
|
41
|
+
runner = Runner(
|
42
|
+
agent=agent,
|
43
|
+
session_service=session_service,
|
44
|
+
credential_service=credential_service,
|
45
|
+
app_name="my_app"
|
46
|
+
)
|
47
|
+
```
|
48
|
+
|
49
|
+
Security Note:
|
50
|
+
Basic Auth transmits credentials in base64 encoding, which is not encryption.
|
51
|
+
Always use HTTPS when using Basic Auth to protect credentials in transit.
|
52
|
+
"""
|
53
|
+
|
54
|
+
def __init__(
|
55
|
+
self,
|
56
|
+
username: str,
|
57
|
+
password: str,
|
58
|
+
realm: Optional[str] = None,
|
59
|
+
use_session_state: bool = True
|
60
|
+
):
|
61
|
+
"""Initialize the HTTP Basic Auth credential service.
|
62
|
+
|
63
|
+
Args:
|
64
|
+
username: Username for basic authentication.
|
65
|
+
password: Password for basic authentication.
|
66
|
+
realm: Optional realm for HTTP Basic Auth.
|
67
|
+
use_session_state: Whether to use session state for credential storage.
|
68
|
+
"""
|
69
|
+
super().__init__()
|
70
|
+
self.username = username
|
71
|
+
self.password = password
|
72
|
+
self.realm = realm
|
73
|
+
self.use_session_state = use_session_state
|
74
|
+
|
75
|
+
# Underlying credential service for storage
|
76
|
+
if use_session_state:
|
77
|
+
self._storage_service = SessionStateCredentialService()
|
78
|
+
else:
|
79
|
+
from google.adk.auth.credential_service.in_memory_credential_service import InMemoryCredentialService
|
80
|
+
self._storage_service = InMemoryCredentialService()
|
81
|
+
|
82
|
+
async def _initialize_impl(self) -> None:
|
83
|
+
"""Initialize the HTTP Basic Auth credential service.
|
84
|
+
|
85
|
+
Validates the username and password configuration.
|
86
|
+
|
87
|
+
Raises:
|
88
|
+
ValueError: If username or password is missing.
|
89
|
+
"""
|
90
|
+
if not self.username:
|
91
|
+
raise ValueError("Username is required for HTTP Basic Auth")
|
92
|
+
if not self.password:
|
93
|
+
raise ValueError("Password is required for HTTP Basic Auth")
|
94
|
+
|
95
|
+
logger.info(f"Initialized HTTP Basic Auth credential service for user: {self.username}")
|
96
|
+
|
97
|
+
def encode_basic_auth(self, username: str, password: str) -> str:
|
98
|
+
"""Encode username and password for HTTP Basic Auth.
|
99
|
+
|
100
|
+
Args:
|
101
|
+
username: The username to encode.
|
102
|
+
password: The password to encode.
|
103
|
+
|
104
|
+
Returns:
|
105
|
+
str: Base64 encoded credentials in format "Basic <encoded>".
|
106
|
+
"""
|
107
|
+
credentials = f"{username}:{password}"
|
108
|
+
encoded_credentials = base64.b64encode(credentials.encode('utf-8')).decode('ascii')
|
109
|
+
return f"Basic {encoded_credentials}"
|
110
|
+
|
111
|
+
def decode_basic_auth(self, auth_header: str) -> tuple[str, str]:
|
112
|
+
"""Decode HTTP Basic Auth header to extract username and password.
|
113
|
+
|
114
|
+
Args:
|
115
|
+
auth_header: The Authorization header value.
|
116
|
+
|
117
|
+
Returns:
|
118
|
+
tuple[str, str]: Tuple of (username, password).
|
119
|
+
|
120
|
+
Raises:
|
121
|
+
ValueError: If the auth header is invalid.
|
122
|
+
"""
|
123
|
+
if not auth_header.startswith("Basic "):
|
124
|
+
raise ValueError("Invalid Basic Auth header format")
|
125
|
+
|
126
|
+
encoded_credentials = auth_header[6:] # Remove "Basic " prefix
|
127
|
+
try:
|
128
|
+
credentials = base64.b64decode(encoded_credentials).decode('utf-8')
|
129
|
+
username, password = credentials.split(':', 1)
|
130
|
+
return username, password
|
131
|
+
except Exception as e:
|
132
|
+
raise ValueError(f"Failed to decode Basic Auth credentials: {e}")
|
133
|
+
|
134
|
+
def create_auth_config(self) -> AuthConfig:
|
135
|
+
"""Create an AuthConfig for HTTP Basic Authentication.
|
136
|
+
|
137
|
+
Returns:
|
138
|
+
AuthConfig: Configured auth config for HTTP Basic Auth.
|
139
|
+
"""
|
140
|
+
self._check_initialized()
|
141
|
+
|
142
|
+
# Create HTTP Basic auth scheme
|
143
|
+
auth_scheme = HTTPBase(scheme="basic")
|
144
|
+
|
145
|
+
# Create HTTP Basic credential
|
146
|
+
auth_credential = AuthCredential(
|
147
|
+
auth_type=AuthCredentialTypes.HTTP,
|
148
|
+
http=HttpAuth(
|
149
|
+
scheme="basic",
|
150
|
+
credentials=HttpCredentials(
|
151
|
+
username=self.username,
|
152
|
+
password=self.password
|
153
|
+
)
|
154
|
+
)
|
155
|
+
)
|
156
|
+
|
157
|
+
return AuthConfig(
|
158
|
+
auth_scheme=auth_scheme,
|
159
|
+
raw_auth_credential=auth_credential
|
160
|
+
)
|
161
|
+
|
162
|
+
async def load_credential(
|
163
|
+
self,
|
164
|
+
auth_config: AuthConfig,
|
165
|
+
callback_context: CallbackContext,
|
166
|
+
) -> Optional[AuthCredential]:
|
167
|
+
"""Load HTTP Basic Auth credential from storage.
|
168
|
+
|
169
|
+
Args:
|
170
|
+
auth_config: The auth config containing credential key information.
|
171
|
+
callback_context: The current callback context.
|
172
|
+
|
173
|
+
Returns:
|
174
|
+
Optional[AuthCredential]: The stored credential or None if not found.
|
175
|
+
"""
|
176
|
+
self._check_initialized()
|
177
|
+
return await self._storage_service.load_credential(auth_config, callback_context)
|
178
|
+
|
179
|
+
async def save_credential(
|
180
|
+
self,
|
181
|
+
auth_config: AuthConfig,
|
182
|
+
callback_context: CallbackContext,
|
183
|
+
) -> None:
|
184
|
+
"""Save HTTP Basic Auth credential to storage.
|
185
|
+
|
186
|
+
Args:
|
187
|
+
auth_config: The auth config containing the credential to save.
|
188
|
+
callback_context: The current callback context.
|
189
|
+
"""
|
190
|
+
self._check_initialized()
|
191
|
+
await self._storage_service.save_credential(auth_config, callback_context)
|
192
|
+
|
193
|
+
logger.info(f"Saved HTTP Basic Auth credential for user {callback_context._invocation_context.user_id}")
|
194
|
+
|
195
|
+
def validate_credentials(self, test_username: str, test_password: str) -> bool:
|
196
|
+
"""Validate if provided credentials match the configured ones.
|
197
|
+
|
198
|
+
Args:
|
199
|
+
test_username: Username to validate.
|
200
|
+
test_password: Password to validate.
|
201
|
+
|
202
|
+
Returns:
|
203
|
+
bool: True if credentials match, False otherwise.
|
204
|
+
"""
|
205
|
+
self._check_initialized()
|
206
|
+
return self.username == test_username and self.password == test_password
|
207
|
+
|
208
|
+
def get_auth_header(self) -> str:
|
209
|
+
"""Get the Authorization header value for HTTP Basic Auth.
|
210
|
+
|
211
|
+
Returns:
|
212
|
+
str: The complete Authorization header value.
|
213
|
+
"""
|
214
|
+
self._check_initialized()
|
215
|
+
return self.encode_basic_auth(self.username, self.password)
|
216
|
+
|
217
|
+
def get_credential_info(self) -> Dict[str, str]:
|
218
|
+
"""Get information about the configured credentials (without passwords).
|
219
|
+
|
220
|
+
Returns:
|
221
|
+
Dict[str, str]: Credential information (excluding sensitive data).
|
222
|
+
"""
|
223
|
+
self._check_initialized()
|
224
|
+
|
225
|
+
info = {
|
226
|
+
"username": self.username,
|
227
|
+
"auth_type": "HTTP Basic Auth",
|
228
|
+
"password_set": bool(self.password)
|
229
|
+
}
|
230
|
+
|
231
|
+
if self.realm:
|
232
|
+
info["realm"] = self.realm
|
233
|
+
|
234
|
+
return info
|
235
|
+
|
236
|
+
|
237
|
+
class HTTPBasicAuthWithCredentialsService(BaseCustomCredentialService):
|
238
|
+
"""HTTP Basic Auth service that accepts multiple username/password pairs.
|
239
|
+
|
240
|
+
This variant allows managing multiple sets of credentials, useful for
|
241
|
+
scenarios where different users or contexts require different credentials.
|
242
|
+
|
243
|
+
Args:
|
244
|
+
credentials: Dictionary mapping usernames to passwords.
|
245
|
+
default_username: Default username to use if not specified.
|
246
|
+
realm: Optional realm parameter for HTTP Basic Auth.
|
247
|
+
use_session_state: If True, stores credentials in session state. If False,
|
248
|
+
uses in-memory storage. Default is True for persistence.
|
249
|
+
|
250
|
+
Example:
|
251
|
+
```python
|
252
|
+
credential_service = HTTPBasicAuthWithCredentialsService(
|
253
|
+
credentials={
|
254
|
+
"admin": "admin_password",
|
255
|
+
"user1": "user1_password",
|
256
|
+
"api_client": "api_secret"
|
257
|
+
},
|
258
|
+
default_username="api_client"
|
259
|
+
)
|
260
|
+
await credential_service.initialize()
|
261
|
+
```
|
262
|
+
"""
|
263
|
+
|
264
|
+
def __init__(
|
265
|
+
self,
|
266
|
+
credentials: Dict[str, str],
|
267
|
+
default_username: Optional[str] = None,
|
268
|
+
realm: Optional[str] = None,
|
269
|
+
use_session_state: bool = True
|
270
|
+
):
|
271
|
+
"""Initialize the multi-credential HTTP Basic Auth service.
|
272
|
+
|
273
|
+
Args:
|
274
|
+
credentials: Dictionary of username -> password mappings.
|
275
|
+
default_username: Default username to use.
|
276
|
+
realm: Optional realm for HTTP Basic Auth.
|
277
|
+
use_session_state: Whether to use session state for credential storage.
|
278
|
+
"""
|
279
|
+
super().__init__()
|
280
|
+
self.credentials = credentials.copy()
|
281
|
+
self.default_username = default_username or (list(credentials.keys())[0] if credentials else None)
|
282
|
+
self.realm = realm
|
283
|
+
self.use_session_state = use_session_state
|
284
|
+
|
285
|
+
# Underlying credential service for storage
|
286
|
+
if use_session_state:
|
287
|
+
self._storage_service = SessionStateCredentialService()
|
288
|
+
else:
|
289
|
+
from google.adk.auth.credential_service.in_memory_credential_service import InMemoryCredentialService
|
290
|
+
self._storage_service = InMemoryCredentialService()
|
291
|
+
|
292
|
+
async def _initialize_impl(self) -> None:
|
293
|
+
"""Initialize the multi-credential HTTP Basic Auth service.
|
294
|
+
|
295
|
+
Validates that credentials are provided and default username exists.
|
296
|
+
|
297
|
+
Raises:
|
298
|
+
ValueError: If configuration is invalid.
|
299
|
+
"""
|
300
|
+
if not self.credentials:
|
301
|
+
raise ValueError("At least one username/password pair is required")
|
302
|
+
if self.default_username and self.default_username not in self.credentials:
|
303
|
+
raise ValueError(f"Default username '{self.default_username}' not found in credentials")
|
304
|
+
if not self.default_username:
|
305
|
+
raise ValueError("Default username is required when multiple credentials are provided")
|
306
|
+
|
307
|
+
logger.info(f"Initialized HTTP Basic Auth service with {len(self.credentials)} credential sets")
|
308
|
+
|
309
|
+
def create_auth_config(self, username: Optional[str] = None) -> AuthConfig:
|
310
|
+
"""Create an AuthConfig for HTTP Basic Authentication.
|
311
|
+
|
312
|
+
Args:
|
313
|
+
username: Username to use. If None, uses default_username.
|
314
|
+
|
315
|
+
Returns:
|
316
|
+
AuthConfig: Configured auth config for HTTP Basic Auth.
|
317
|
+
|
318
|
+
Raises:
|
319
|
+
ValueError: If username is not found in credentials.
|
320
|
+
"""
|
321
|
+
self._check_initialized()
|
322
|
+
|
323
|
+
username = username or self.default_username
|
324
|
+
if username not in self.credentials:
|
325
|
+
raise ValueError(f"Username '{username}' not found in credentials")
|
326
|
+
|
327
|
+
password = self.credentials[username]
|
328
|
+
|
329
|
+
# Create HTTP Basic auth scheme
|
330
|
+
auth_scheme = HTTPBase(scheme="basic")
|
331
|
+
|
332
|
+
# Create HTTP Basic credential
|
333
|
+
auth_credential = AuthCredential(
|
334
|
+
auth_type=AuthCredentialTypes.HTTP,
|
335
|
+
http=HttpAuth(
|
336
|
+
scheme="basic",
|
337
|
+
credentials=HttpCredentials(
|
338
|
+
username=username,
|
339
|
+
password=password
|
340
|
+
)
|
341
|
+
)
|
342
|
+
)
|
343
|
+
|
344
|
+
return AuthConfig(
|
345
|
+
auth_scheme=auth_scheme,
|
346
|
+
raw_auth_credential=auth_credential
|
347
|
+
)
|
348
|
+
|
349
|
+
async def load_credential(
|
350
|
+
self,
|
351
|
+
auth_config: AuthConfig,
|
352
|
+
callback_context: CallbackContext,
|
353
|
+
) -> Optional[AuthCredential]:
|
354
|
+
"""Load HTTP Basic Auth credential from storage.
|
355
|
+
|
356
|
+
Args:
|
357
|
+
auth_config: The auth config containing credential key information.
|
358
|
+
callback_context: The current callback context.
|
359
|
+
|
360
|
+
Returns:
|
361
|
+
Optional[AuthCredential]: The stored credential or None if not found.
|
362
|
+
"""
|
363
|
+
self._check_initialized()
|
364
|
+
return await self._storage_service.load_credential(auth_config, callback_context)
|
365
|
+
|
366
|
+
async def save_credential(
|
367
|
+
self,
|
368
|
+
auth_config: AuthConfig,
|
369
|
+
callback_context: CallbackContext,
|
370
|
+
) -> None:
|
371
|
+
"""Save HTTP Basic Auth credential to storage.
|
372
|
+
|
373
|
+
Args:
|
374
|
+
auth_config: The auth config containing the credential to save.
|
375
|
+
callback_context: The current callback context.
|
376
|
+
"""
|
377
|
+
self._check_initialized()
|
378
|
+
await self._storage_service.save_credential(auth_config, callback_context)
|
379
|
+
|
380
|
+
logger.info(f"Saved HTTP Basic Auth credential for user {callback_context._invocation_context.user_id}")
|
381
|
+
|
382
|
+
def get_available_usernames(self) -> list[str]:
|
383
|
+
"""Get list of available usernames.
|
384
|
+
|
385
|
+
Returns:
|
386
|
+
list[str]: List of configured usernames.
|
387
|
+
"""
|
388
|
+
return list(self.credentials.keys())
|