| 985 | |
| 986 | |
| 987 | def execute(self, params, **kwargs): |
| 988 | name = kwargs.get('record') |
| 989 | if not name: |
| 990 | self.get_parser().print_help() |
| 991 | return |
| 992 | |
| 993 | emails = kwargs.get('email') or [] |
| 994 | if not emails: |
| 995 | raise CommandError('share-record', '\'email\' parameter is missing') |
| 996 | |
| 997 | from ..enforcement import MasterPasswordReentryEnforcer |
| 998 | if not MasterPasswordReentryEnforcer.check_and_enforce(params, "record_level"): |
| 999 | raise CommandError('share-record', 'Operation cancelled: Re-authentication failed') |
| 1000 | |
| 1001 | force = kwargs.get('force') is True |
| 1002 | action = kwargs.get('action') or 'grant' |
| 1003 | use_contacts = kwargs.get('contacts_only') |
| 1004 | |
| 1005 | def get_contact(user, contacts): |
| 1006 | get_username = lambda addr: next(iter(addr.split('@')), '').casefold() |
| 1007 | matches = [c for c in contacts if get_username(user) == get_username(c)] |
| 1008 | if len(matches) > 1: |
| 1009 | raise CommandError('share-record', 'More than 1 matching usernames found. Aborting') |
| 1010 | return next(iter(matches), None) |
| 1011 | |
| 1012 | if use_contacts: |
| 1013 | known_users = api.get_share_objects(params).get('users', {}) |
| 1014 | known_emails = [u.casefold() for u in known_users.keys()] |
| 1015 | is_unknown = lambda e: e.casefold() not in known_emails and is_email(e) |
| 1016 | unknowns = [e for e in emails if is_unknown(e)] |
| 1017 | if unknowns: |
| 1018 | username_map = {e: get_contact(e, known_users) for e in unknowns} |
| 1019 | table = [[k, v] for k, v in username_map.items()] |
| 1020 | logging.info(f'{len(unknowns)} unrecognized share recipient(s) and closest matching contact(s)') |
| 1021 | dump_report_data(table, ['Username', 'From Contacts']) |
| 1022 | confirmed = force or user_choice('\tReplace with known matching contact(s)?', 'yn', default='n') == 'y' |
| 1023 | if confirmed: |
| 1024 | good_emails = [e for e in emails if e not in unknowns] |
| 1025 | replacements = [e for e in username_map.values() if e] |
| 1026 | emails = [*good_emails, *replacements] |
| 1027 | |
| 1028 | if action == 'cancel': |
| 1029 | if not force: |
| 1030 | answer = base.user_choice( |
| 1031 | bcolors.FAIL + bcolors.BOLD + '\nALERT!\n' + bcolors.ENDC + 'This action cannot be undone.\n\n' + |
| 1032 | 'Do you want to cancel all shares with user(s): ' + ', '.join(emails) + ' ?', 'yn', 'n') |
| 1033 | else: |
| 1034 | answer = 'y' |
| 1035 | if answer.lower() in {'y', 'yes'}: |
| 1036 | for email in emails: |
| 1037 | rq = { |
| 1038 | 'command': 'cancel_share', |
| 1039 | 'to_email': email |
| 1040 | } |
| 1041 | try: |
| 1042 | api.communicate(params, rq) |
| 1043 | except KeeperApiError as kae: |
| 1044 | if kae.result_code == 'share_not_found': |