Skip to content

Commit a1acb67

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

File tree

1 file changed

+34
-1
lines changed

1 file changed

+34
-1
lines changed

zulip/zulip/__init__.py

+34-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
import os
66
import platform
77
import random
8+
import subprocess
9+
import shlex
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,28 @@ 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 e:
444+
raise APIKeyRetrievalError(
445+
f"Failed to parse passcmd '{passcmd}': {str(e)}")
446+
try:
447+
result = subprocess.run(cmd_parts, capture_output=True, check=True)
448+
api_key = result.stdout.decode().strip()
449+
except subprocess.CalledProcessError as e:
450+
raise APIKeyRetrievalError(
451+
f"Failed to retrieve API key using passcmd '{passcmd}'. "
452+
f"Command exited with return code {e.returncode}."
453+
)
429454
if email is None:
430455
email = config.get("api", "email")
431456
if site is None and config.has_option("api", "site"):
@@ -512,6 +537,14 @@ def __init__(
512537
self.feature_level: int = server_settings.get("zulip_feature_level", 0)
513538
assert self.zulip_version is not None
514539

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

0 commit comments

Comments
 (0)