From 3b240da77b3b1484d2dac29e8af13083aa8ad9ba Mon Sep 17 00:00:00 2001 From: Fred Reimer Date: Mon, 13 Apr 2026 13:03:34 -0400 Subject: [PATCH] Handle legacy service config record title on startup --- .../service/config/file_handler.py | 24 +++++++++----- unit-tests/service/test_service_config.py | 31 ++++++++++++++++++- 2 files changed, 46 insertions(+), 9 deletions(-) diff --git a/keepercommander/service/config/file_handler.py b/keepercommander/service/config/file_handler.py index e7b4a798a..88419194d 100644 --- a/keepercommander/service/config/file_handler.py +++ b/keepercommander/service/config/file_handler.py @@ -17,6 +17,13 @@ from ..decorators.logging import logger from ..util.exceptions import ValidationError + +SERVICE_CONFIG_RECORD_TITLES = ( + 'Commander Service Mode Config', + 'Commander Service Mode', +) + + class ConfigFormatHandler: def __init__(self, config_dir: Path, messages: Dict, validation_messages: Dict): self.config_dir = config_dir @@ -50,13 +57,14 @@ def get_config_format(self, save_type: str = None) -> str: from ..core.globals import get_current_params if params := get_current_params(): - if self.cli_handler.download_config_from_vault(params, 'Commander Service Mode Config', self.config_dir): - if json_path.exists(): - self.encrypt_config_file(json_path, self.config_dir) - return 'json' - if yaml_path.exists(): - self.encrypt_config_file(yaml_path, self.config_dir) - return 'yaml' + for title in SERVICE_CONFIG_RECORD_TITLES: + if self.cli_handler.download_config_from_vault(params, title, self.config_dir): + if json_path.exists(): + self.encrypt_config_file(json_path, self.config_dir) + return 'json' + if yaml_path.exists(): + self.encrypt_config_file(yaml_path, self.config_dir) + return 'yaml' return self._get_format_input() if save_type == 'create' else 'json' @@ -195,4 +203,4 @@ def decrypt_config_file(encrypted_content: bytes, config_dir: Path) -> str: hashed_key = sha256(private_key.encode('utf-8')).digest() return decrypt_aes_v2(encrypted_content, hashed_key).decode('utf-8') except Exception as e: - raise ValidationError(f"Failed to decrypt configuration file: {str(e)}") \ No newline at end of file + raise ValidationError(f"Failed to decrypt configuration file: {str(e)}") diff --git a/unit-tests/service/test_service_config.py b/unit-tests/service/test_service_config.py index 4a99d6b12..6fc0941d4 100644 --- a/unit-tests/service/test_service_config.py +++ b/unit-tests/service/test_service_config.py @@ -1,9 +1,12 @@ import sys if sys.version_info >= (3, 8): import unittest + import tempfile + from pathlib import Path from unittest.mock import patch, MagicMock import json from keepercommander.params import KeeperParams + from keepercommander.service.config.file_handler import ConfigFormatHandler from keepercommander.service.config.service_config import ServiceConfig from keepercommander.service.util.exceptions import ValidationError @@ -65,6 +68,32 @@ def test_save_config_io_error(self): with self.assertRaises(ValidationError): self.service_config.save_config(self.test_config) + + def test_get_config_format_falls_back_to_legacy_vault_title(self): + """Test config recovery from the legacy Service Mode record title.""" + with tempfile.TemporaryDirectory() as temp_dir: + config_dir = Path(temp_dir) + handler = ConfigFormatHandler(config_dir, {}, {}) + params = MagicMock(spec=KeeperParams) + download_calls = [] + + def download_side_effect(_params, title, _config_dir): + download_calls.append(title) + if title == 'Commander Service Mode': + (config_dir / 'service_config.json').write_text('{}') + return True + return False + + handler.cli_handler = MagicMock() + handler.cli_handler.download_config_from_vault.side_effect = download_side_effect + + with patch('keepercommander.service.core.globals.get_current_params', return_value=params), \ + patch.object(handler, 'encrypt_config_file') as mock_encrypt: + format_type = handler.get_config_format() + + self.assertEqual(format_type, 'json') + self.assertEqual(download_calls, ['Commander Service Mode Config', 'Commander Service Mode']) + mock_encrypt.assert_called_once_with(config_dir / 'service_config.json', config_dir) @unittest.skip @patch('pathlib.Path.exists') @@ -133,4 +162,4 @@ def test_update_or_add_record(self, mock_record_handler): self.service_config.update_or_add_record(params) mock_record_handler.update_or_add_record.assert_called_once_with( params, self.service_config.title, self.service_config.config_path - ) \ No newline at end of file + )