| 1281 | |
| 1282 | |
| 1283 | def execute(self, params, **kwargs): |
| 1284 | name = kwargs.get('record') |
| 1285 | if not name: |
| 1286 | self.get_parser().print_help() |
| 1287 | return |
| 1288 | |
| 1289 | emails = kwargs.get('email') or [] |
| 1290 | if not emails: |
| 1291 | raise CommandError('share-record', '\'email\' parameter is missing') |
| 1292 | |
| 1293 | from ..enforcement import MasterPasswordReentryEnforcer |
| 1294 | if not MasterPasswordReentryEnforcer.check_and_enforce(params, "record_level"): |
| 1295 | raise CommandError('share-record', 'Operation cancelled: Re-authentication failed') |
| 1296 | |
| 1297 | force = kwargs.get('force') is True |
| 1298 | action = kwargs.get('action') or 'grant' |
| 1299 | use_contacts = kwargs.get('contacts_only') |
| 1300 | |
| 1301 | def get_contact(user, contacts): |
| 1302 | get_username = lambda addr: next(iter(addr.split('@')), '').casefold() |
| 1303 | matches = [c for c in contacts if get_username(user) == get_username(c)] |
| 1304 | if len(matches) > 1: |
| 1305 | raise CommandError('share-record', 'More than 1 matching usernames found. Aborting') |
| 1306 | return next(iter(matches), None) |
| 1307 | |
| 1308 | if use_contacts: |
| 1309 | known_users = api.get_share_objects(params).get('users', {}) |
| 1310 | known_emails = [u.casefold() for u in known_users.keys()] |
| 1311 | is_unknown = lambda e: e.casefold() not in known_emails and is_email(e) |
| 1312 | unknowns = [e for e in emails if is_unknown(e)] |
| 1313 | if unknowns: |
| 1314 | username_map = {e: get_contact(e, known_users) for e in unknowns} |
| 1315 | table = [[k, v] for k, v in username_map.items()] |
| 1316 | logging.info(f'{len(unknowns)} unrecognized share recipient(s) and closest matching contact(s)') |
| 1317 | dump_report_data(table, ['Username', 'From Contacts']) |
| 1318 | confirmed = force or user_choice('\tReplace with known matching contact(s)?', 'yn', default='n') == 'y' |
| 1319 | if confirmed: |
| 1320 | good_emails = [e for e in emails if e not in unknowns] |
| 1321 | replacements = [e for e in username_map.values() if e] |
| 1322 | emails = [*good_emails, *replacements] |
| 1323 | |
| 1324 | if action == 'cancel': |
| 1325 | if not force: |
| 1326 | answer = base.user_choice( |
| 1327 | bcolors.FAIL + bcolors.BOLD + '\nALERT!\n' + bcolors.ENDC + 'This action cannot be undone.\n\n' + |
| 1328 | 'Do you want to cancel all shares with user(s): ' + ', '.join(emails) + ' ?', 'yn', 'n') |
| 1329 | else: |
| 1330 | answer = 'y' |
| 1331 | if answer.lower() in {'y', 'yes'}: |
| 1332 | for email in emails: |
| 1333 | rq = { |
| 1334 | 'command': 'cancel_share', |
| 1335 | 'to_email': email |
| 1336 | } |
| 1337 | try: |
| 1338 | api.communicate(params, rq) |
| 1339 | except KeeperApiError as kae: |
| 1340 | if kae.result_code == 'share_not_found': |