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,36 +312,60 @@ def fetch_zuliprc(zuliprc_path: str) -> None:
311
312
print (in_color ("red" , "\n Incorrect 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 ,
317
- login_id = login_id ,
321
+ to_path = zuliprc_path ,
322
+ key_path = zulip_key_path ,
318
323
api_key = api_key ,
324
+ login_id = login_id ,
319
325
server_url = preferred_realm_url ,
320
326
)
321
327
if not save_zuliprc_failure :
322
- print (f"Generated API key saved at { zuliprc_path } " )
328
+ print (f"Generated config file saved at { zuliprc_path } " )
323
329
else :
324
330
exit_with_error (save_zuliprc_failure )
325
331
326
332
327
333
def _write_zuliprc (
328
- to_path : str , * , login_id : str , api_key : str , server_url : str
334
+ to_path : str , * , key_path : str , login_id : str , api_key : str , server_url : str
329
335
) -> str :
330
336
"""
331
- Writes a zuliprc file, returning a non-empty error string on failure
332
- 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.
333
340
"""
341
+ zuliprc_created = False
342
+
334
343
try :
344
+ # Write zuliprc
335
345
with open (
336
346
os .open (to_path , os .O_CREAT | os .O_WRONLY | os .O_EXCL , 0o600 ), "w"
337
347
) as f :
338
- f .write (f"[api]\n email={ login_id } \n key={ api_key } \n site={ server_url } " )
348
+ f .write (
349
+ f"[api]\n email={ login_id } \n passcmd=cat zulip_key\n site={ server_url } "
350
+ )
351
+ zuliprc_created = True
352
+ # Write zulip_key
353
+ with open (
354
+ os .open (key_path , os .O_CREAT | os .O_WRONLY | os .O_EXCL , 0o600 ), "w"
355
+ ) as f :
356
+ f .write (api_key )
357
+
339
358
return ""
359
+
340
360
except FileExistsError :
341
- return f"zuliprc already exists at { to_path } "
361
+ filename = to_path if not zuliprc_created else key_path
362
+ return f"FileExistsError: { filename } already exists"
342
363
except OSError as ex :
343
- return f"{ ex .__class__ .__name__ } : zuliprc 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 } )"
344
369
345
370
346
371
def parse_zuliprc (zuliprc_str : str ) -> Dict [str , SettingData ]:
@@ -366,12 +391,12 @@ def parse_zuliprc(zuliprc_str: str) -> Dict[str, SettingData]:
366
391
in_color (
367
392
"red" ,
368
393
"ERROR: Please ensure your zuliprc is NOT publicly accessible:\n "
369
- " {0 }\n "
370
- "(it currently has permissions '{1 }')\n "
394
+ f " { zuliprc_path } \n "
395
+ f "(it currently has permissions '{ stat . filemode ( mode ) } ')\n "
371
396
"This can often be achieved with a command such as:\n "
372
- " chmod og-rwx {0 }\n "
397
+ f " chmod og-rwx { zuliprc_path } \n "
373
398
"Consider regenerating the [api] part of your zuliprc to ensure "
374
- "your account is secure." . format ( zuliprc_path , stat . filemode ( mode )) ,
399
+ "your account is secure." ,
375
400
)
376
401
)
377
402
sys .exit (1 )
@@ -687,8 +712,8 @@ def print_setting(setting: str, data: SettingData, suffix: str = "") -> None:
687
712
# Dump stats only after temporary file is closed (for Win NT+ case)
688
713
prof .dump_stats (profile_path )
689
714
print (
690
- "Profile data saved to {0 }.\n "
691
- "You can visualize it using e.g. `snakeviz {0 }`" . format ( profile_path )
715
+ f "Profile data saved to { profile_path } .\n "
716
+ f "You can visualize it using e.g. `snakeviz { profile_path } `"
692
717
)
693
718
694
719
sys .exit (1 )
0 commit comments