diff --git a/keepercommander/__init__.py b/keepercommander/__init__.py index ded16103e..0c9cce431 100644 --- a/keepercommander/__init__.py +++ b/keepercommander/__init__.py @@ -10,4 +10,4 @@ # Contact: commander@keepersecurity.com # -__version__ = '18.0.10' +__version__ = '18.0.11' diff --git a/keepercommander/auth/console_ui.py b/keepercommander/auth/console_ui.py index eca6bf3b4..0af2ccf58 100644 --- a/keepercommander/auth/console_ui.py +++ b/keepercommander/auth/console_ui.py @@ -387,7 +387,11 @@ def on_sso_redirect(self, step): token = pyperclip.paste() except: token = '' - logging.warning('Failed to paste from clipboard') + if len(token) < 10: + try: + token = input(f'{Fore.GREEN}Paste SSO Token: {Fore.RESET}').strip() + except KeyboardInterrupt: + token = '' else: if len(token) < 10: logging.warning(f'Unsupported menu option: {token}') diff --git a/keepercommander/importer/imp_exp.py b/keepercommander/importer/imp_exp.py index 38703a9dc..48b29176f 100644 --- a/keepercommander/importer/imp_exp.py +++ b/keepercommander/importer/imp_exp.py @@ -1796,7 +1796,7 @@ def value_to_token(value): # type: (any) -> str return str(value) -def tokenize_record_key(record): # type: (ImportRecord) -> Iterator[str] +def tokenize_record_key(record, folder): # type: (ImportRecord, str) -> Iterator[str] """ Turn a record-to-import into an iterable of str's for hashing. This is really about import --update. @@ -1807,6 +1807,7 @@ def tokenize_record_key(record): # type: (ImportRecord) -> Iterator[str] yield f'$title:{record.title or ""}' yield f'$login:{record.login or ""}' yield f'$url:{record.login_url or ""}' + yield f'$folder:{folder}' if record_type in {'', 'login'}: return @@ -2071,7 +2072,7 @@ def prepare_record_add_or_update(update_flag, params, records): If a 100% match is found for a record, then just skip requesting anything; it doesn't need to be changed. Otherwise import the record, risking creating an almost-duplicate. If update_flag is True: - if a unique field match (on title, login, and url) is found, then request a change in password only. + if a unique field match (on title, login, url, and folder) is found, then request a change in password only. """ preexisting_entire_record_hash = {} preexisting_partial_record_hash = {} @@ -2081,8 +2082,13 @@ def prepare_record_add_or_update(update_flag, params, records): record_hash = build_record_hash(tokenize_full_import_record(import_record)) preexisting_entire_record_hash[record_hash] = record_uid if update_flag: - record_hash = build_record_hash(tokenize_record_key(import_record)) - preexisting_partial_record_hash[record_hash] = record_uid + folders = [get_folder_path(params, x) for x in find_folders(params, record_uid)] + folders = [x for x in folders if x] + if len(folders) == 0: + folders.append(record_uid) + for folder in folders: + record_hash = build_record_hash(tokenize_record_key(import_record, folder)) + preexisting_partial_record_hash[record_hash] = record_uid else: pass @@ -2128,15 +2134,26 @@ def prepare_record_add_or_update(update_flag, params, records): record_to_import.append(import_record) continue elif update_flag: - record_hash = build_record_hash(tokenize_record_key(import_record)) - if record_hash in preexisting_partial_record_hash: - record_uid = preexisting_partial_record_hash[record_hash] - if import_record.uid: - external_lookup[import_record.uid] = record_uid - if record_uid not in record_uid_to_update: - record_uid_to_update.add(record_uid) - import_record.uid = record_uid - record_to_import.append(import_record) + folders = [] + if import_record.folders: + folders.extend([x.get_folder_path() for x in import_record.folders]) + folders = [x for x in folders if x] + if len(folders) == 0: + folders.append('') + found = False + for folder in folders: + record_hash = build_record_hash(tokenize_record_key(import_record, folder)) + if record_hash in preexisting_partial_record_hash: + record_uid = preexisting_partial_record_hash[record_hash] + if import_record.uid: + external_lookup[import_record.uid] = record_uid + if record_uid not in record_uid_to_update: + record_uid_to_update.add(record_uid) + import_record.uid = record_uid + record_to_import.append(import_record) + found = True + break + if found: continue record_uid = utils.generate_uid() diff --git a/keepercommander/vault.py b/keepercommander/vault.py index 0fa854f82..52a16489f 100644 --- a/keepercommander/vault.py +++ b/keepercommander/vault.py @@ -475,8 +475,7 @@ def export_schedule_field(value): # type: (dict) -> Optional[str] cron = value.get('cron') if isinstance(cron, str): comps = [x for x in cron.split(' ') if x] - if len(comps) >= 6: - comps = comps[1:6] + if comps: return ' '.join(comps) return '' diff --git a/unit-tests/test_importer.py b/unit-tests/test_importer.py index 19217776a..aaa203611 100644 --- a/unit-tests/test_importer.py +++ b/unit-tests/test_importer.py @@ -230,3 +230,13 @@ def test_schedule_parser(self): self.assertEqual(sc.get('type'), 'MONTHLY_BY_WEEKDAY') self.assertEqual(sc.get('weekday'), 'WEDNESDAY') self.assertEqual(sc.get('occurrence'), 'SECOND') + + def test_export_schedule_field_preserves_quartz_cron_seconds(self): + schedule = {"type": "CRON", "cron": "1 5 1 * * ?", "tz": "Etc/UTC"} + self.assertEqual(vault.TypedField.export_schedule_field(schedule), '1 5 1 * * ?') + + schedule = {"type": "CRON", "cron": "0 5 1 * * ?"} + self.assertEqual(vault.TypedField.export_schedule_field(schedule), '0 5 1 * * ?') + + schedule = {"type": "CRON", "cron": "5 1 * * ?"} + self.assertEqual(vault.TypedField.export_schedule_field(schedule), '5 1 * * ?')