|
4 | 4 |
|
5 | 5 | import argparse
|
6 | 6 | import configparser
|
| 7 | +import contextlib |
7 | 8 | import logging
|
8 | 9 | import os
|
9 | 10 | import stat
|
@@ -311,67 +312,60 @@ def fetch_zuliprc(zuliprc_path: str) -> None:
|
311 | 312 | print(in_color("red", "\nIncorrect Email(or Username) or Password!\n"))
|
312 | 313 | login_data = get_api_key(realm_url)
|
313 | 314 |
|
| 315 | + zulip_key_path = os.path.join( |
| 316 | + os.path.dirname(os.path.abspath(zuliprc_path)), "zulip_key" |
| 317 | + ) |
| 318 | + |
314 | 319 | preferred_realm_url, login_id, api_key = login_data
|
315 | 320 | save_zuliprc_failure = _write_zuliprc(
|
316 |
| - zuliprc_path, |
| 321 | + to_path=zuliprc_path, |
| 322 | + key_path=zulip_key_path, |
| 323 | + api_key=api_key, |
317 | 324 | login_id=login_id,
|
318 | 325 | server_url=preferred_realm_url,
|
319 | 326 | )
|
320 |
| - zulip_key_path = os.path.join( |
321 |
| - os.path.dirname(os.path.abspath(zuliprc_path)), "zulip_key" |
322 |
| - ) |
323 |
| - save_zulipkey_failure = _write_zulip_key(zulip_key_path, api_key=api_key) |
324 |
| - if not save_zulipkey_failure: |
325 |
| - print(f"Generated API key saved at {zulip_key_path}") |
326 |
| - else: |
327 |
| - exit_with_error(save_zuliprc_failure) |
328 |
| - |
329 | 327 | if not save_zuliprc_failure:
|
330 | 328 | print(f"Generated config file saved at {zuliprc_path}")
|
331 | 329 | else:
|
332 | 330 | exit_with_error(save_zuliprc_failure)
|
333 | 331 |
|
334 | 332 |
|
335 |
| -def _write_zuliprc(to_path: str, *, login_id: str, server_url: str) -> str: |
| 333 | +def _write_zuliprc( |
| 334 | + to_path: str, *, key_path: str, login_id: str, api_key: str, server_url: str |
| 335 | +) -> str: |
336 | 336 | """
|
337 |
| - Writes a zuliprc file, returning a non-empty error string on failure |
338 |
| - Only creates new private files; errors if file already exists |
| 337 | + Writes both zuliprc and zulip_key files securely. |
| 338 | + Ensures atomicity: if one file fails to write, cleans up the other. |
| 339 | + Returns an empty string on success, or a descriptive error message on failure. |
339 | 340 | """
|
| 341 | + zuliprc_created = False |
| 342 | + |
340 | 343 | try:
|
| 344 | + # Write zuliprc |
341 | 345 | with open(
|
342 | 346 | os.open(to_path, os.O_CREAT | os.O_WRONLY | os.O_EXCL, 0o600), "w"
|
343 | 347 | ) as f:
|
344 | 348 | f.write(
|
345 |
| - f"[api]\nemail={login_id}\n" |
346 |
| - f"# Fill the passcmd field with a command that outputs the API key.\n" |
347 |
| - f"# The API key is temporarily stored in the 'zulip_key' file at " |
348 |
| - f"{os.path.dirname(os.path.abspath(to_path))}." |
349 |
| - f"\n# After storing the key in a password manager, replace the cmd.\n" |
350 |
| - f"passcmd=cat zulip_key\nsite={server_url}" |
| 349 | + f"[api]\nemail={login_id}\npasscmd=cat zulip_key\nsite={server_url}" |
351 | 350 | )
|
352 |
| - return "" |
353 |
| - except FileExistsError: |
354 |
| - return f"zuliprc already exists at {to_path}" |
355 |
| - except OSError as ex: |
356 |
| - return f"{ex.__class__.__name__}: zuliprc could not be created at {to_path}" |
357 |
| - |
358 |
| - |
359 |
| -def _write_zulip_key(to_path: str, *, api_key: str) -> str: |
360 |
| - """ |
361 |
| - Writes a zulip_key file, returning a non-empty error string on failure |
362 |
| - Only creates new private files; errors if file already exists |
363 |
| - """ |
364 |
| - |
365 |
| - try: |
| 351 | + zuliprc_created = True |
| 352 | + # Write zulip_key |
366 | 353 | with open(
|
367 |
| - os.open(to_path, os.O_CREAT | os.O_WRONLY | os.O_EXCL, 0o600), "w" |
| 354 | + os.open(key_path, os.O_CREAT | os.O_WRONLY | os.O_EXCL, 0o600), "w" |
368 | 355 | ) as f:
|
369 | 356 | f.write(api_key)
|
| 357 | + |
370 | 358 | return ""
|
| 359 | + |
371 | 360 | except FileExistsError:
|
372 |
| - return f"zulip_key already exists at {to_path}" |
| 361 | + filename = to_path if not zuliprc_created else key_path |
| 362 | + return f"FileExistsError: {filename} already exists" |
373 | 363 | except OSError as ex:
|
374 |
| - return f"{ex.__class__.__name__}: zulip_key could not be created at {to_path}" |
| 364 | + if zuliprc_created: |
| 365 | + with contextlib.suppress(Exception): |
| 366 | + os.remove(to_path) |
| 367 | + filename = key_path if zuliprc_created else to_path |
| 368 | + return f"{ex.__class__.__name__}: could not create {filename} ({ex})" |
375 | 369 |
|
376 | 370 |
|
377 | 371 | def parse_zuliprc(zuliprc_str: str) -> Dict[str, SettingData]:
|
|
0 commit comments