Skip to content

Commit f448f7a

Browse files
feat: Add function to process the passcmd for API key retrieval.
1 parent 929e71e commit f448f7a

File tree

1 file changed

+35
-1
lines changed

1 file changed

+35
-1
lines changed

zulip/zulip/__init__.py

+35-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
import os
66
import platform
77
import random
8+
import shlex
9+
import subprocess
810
import sys
911
import time
1012
import traceback
@@ -366,12 +368,15 @@ class MissingURLError(ZulipError):
366368
class UnrecoverableNetworkError(ZulipError):
367369
pass
368370

371+
class APIKeyRetrievalError(ZulipError):
372+
pass
369373

370374
class Client:
371375
def __init__(
372376
self,
373377
email: Optional[str] = None,
374378
api_key: Optional[str] = None,
379+
passcmd: Optional[str] = None,
375380
config_file: Optional[str] = None,
376381
verbose: bool = False,
377382
retry_on_errors: bool = True,
@@ -424,8 +429,29 @@ def __init__(
424429
config = ConfigParser()
425430
with open(config_file) as f:
426431
config.read_file(f, config_file)
427-
if api_key is None:
432+
if api_key is None and config.has_option("api", "key"):
428433
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
429455
if email is None:
430456
email = config.get("api", "email")
431457
if site is None and config.has_option("api", "site"):
@@ -512,6 +538,14 @@ def __init__(
512538
self.feature_level: int = server_settings.get("zulip_feature_level", 0)
513539
assert self.zulip_version is not None
514540

541+
def get_api_key(self, passcmd: str) -> Optional[str]:
542+
# run the passcmd command and get the API key
543+
result = subprocess.run(passcmd.split(), capture_output=True, check=False)
544+
if result.returncode == 0:
545+
return result.stdout.decode().strip()
546+
else:
547+
raise RuntimeError("Error: Unable to retrieve API key.")
548+
515549
def ensure_session(self) -> None:
516550
# Check if the session has been created already, and return
517551
# immediately if so.

0 commit comments

Comments
 (0)