|
5 | 5 | import os
|
6 | 6 | import platform
|
7 | 7 | import random
|
| 8 | +import shlex |
| 9 | +import subprocess |
8 | 10 | import sys
|
9 | 11 | import time
|
10 | 12 | import traceback
|
@@ -366,12 +368,15 @@ class MissingURLError(ZulipError):
|
366 | 368 | class UnrecoverableNetworkError(ZulipError):
|
367 | 369 | pass
|
368 | 370 |
|
| 371 | +class APIKeyRetrievalError(ZulipError): |
| 372 | + pass |
369 | 373 |
|
370 | 374 | class Client:
|
371 | 375 | def __init__(
|
372 | 376 | self,
|
373 | 377 | email: Optional[str] = None,
|
374 | 378 | api_key: Optional[str] = None,
|
| 379 | + passcmd: Optional[str] = None, |
375 | 380 | config_file: Optional[str] = None,
|
376 | 381 | verbose: bool = False,
|
377 | 382 | retry_on_errors: bool = True,
|
@@ -424,8 +429,29 @@ def __init__(
|
424 | 429 | config = ConfigParser()
|
425 | 430 | with open(config_file) as f:
|
426 | 431 | config.read_file(f, config_file)
|
427 |
| - if api_key is None: |
| 432 | + if api_key is None and config.has_option("api", "key"): |
428 | 433 | api_key = config.get("api", "key")
|
| 434 | + if passcmd is None and config.has_option("api", "passcmd"): |
| 435 | + passcmd = config.get("api", "passcmd") |
| 436 | + malicious_chars = {";", "|", "&", ">", "<", "`", "$", "\\", "\n", "\r"} |
| 437 | + if any(char in passcmd for char in malicious_chars): |
| 438 | + raise APIKeyRetrievalError( |
| 439 | + f"Invalid characters detected in passcmd: {passcmd!r}" |
| 440 | + ) |
| 441 | + try: |
| 442 | + cmd_parts = shlex.split(passcmd) |
| 443 | + except ValueError as err: |
| 444 | + raise APIKeyRetrievalError( |
| 445 | + f"Failed to parse passcmd '{passcmd}': {err!s}" |
| 446 | + ) from err |
| 447 | + try: |
| 448 | + result = subprocess.run(cmd_parts, capture_output=True, check=True) |
| 449 | + api_key = result.stdout.decode().strip() |
| 450 | + except subprocess.CalledProcessError as err: |
| 451 | + raise APIKeyRetrievalError( |
| 452 | + f'Failed to retrieve API key using passcmd "{passcmd}".' |
| 453 | + f"Command exited with return code {err.returncode}." |
| 454 | + ) from err |
429 | 455 | if email is None:
|
430 | 456 | email = config.get("api", "email")
|
431 | 457 | if site is None and config.has_option("api", "site"):
|
|
0 commit comments