diff options
Diffstat (limited to 'src/cerberus/integrations/chrome.py')
| -rw-r--r-- | src/cerberus/integrations/chrome.py | 135 |
1 files changed, 135 insertions, 0 deletions
diff --git a/src/cerberus/integrations/chrome.py b/src/cerberus/integrations/chrome.py new file mode 100644 index 0000000..ddc6acc --- /dev/null +++ b/src/cerberus/integrations/chrome.py @@ -0,0 +1,135 @@ +"""Chrome/Chromium password export integration for Cerberus.""" + +import csv +import json +import sqlite3 +import shutil +import tempfile +from pathlib import Path +from typing import List, Optional, Dict, Any + +from ..core.models import PasswordEntry +from . import BaseIntegration, register_integration, IntegrationError + +class ChromeIntegration(BaseIntegration): + """Integration with Chrome/Chromium password exports.""" + + def __init__(self, export_path: Optional[Path] = None): + """Initialize the Chrome integration. + + Args: + export_path: Path to Chrome passwords CSV export + """ + super().__init__() + self.export_path = export_path + + def connect(self, export_path: Optional[Path] = None, **kwargs) -> bool: + """Load the Chrome passwords export file. + + Args: + export_path: Path to the Chrome passwords CSV export + + Returns: + bool: True if the export file exists and is accessible + """ + if export_path: + self.export_path = Path(export_path) + + if not self.export_path or not self.export_path.exists(): + raise IntegrationError("Chrome passwords export file not found") + + self.connected = True + return True + + def list_entries(self) -> List[PasswordEntry]: + """List all password entries from the Chrome export. + + Returns: + List of PasswordEntry objects + + Raises: + IntegrationError: If not connected or error reading the export file + """ + if not self.connected: + raise IntegrationError("Not connected to Chrome passwords") + + entries: List[PasswordEntry] = [] + + try: + with open(self.export_path, 'r', encoding='utf-8') as f: + # Chrome CSV format: name,url,username,password + reader = csv.reader(f) + + # Skip header if it exists + header = next(reader, None) + if not header or len(header) < 4: + # Try without skipping header + f.seek(0) + + for row in reader: + if len(row) < 4: # Ensure we have enough columns + continue + + name, url, username, password = row[:4] + + # Create a PasswordEntry + entry = PasswordEntry( + website=name or url or "Unknown", + username=username, + password=password, + url=url + ) + + entries.append(entry) + + except Exception as e: + raise IntegrationError(f"Error reading Chrome passwords export: {e}") + + return entries + + def import_entries(self, input_path: Optional[Path] = None) -> List[PasswordEntry]: + """Import entries from a Chrome passwords export. + + Args: + input_path: Path to the Chrome passwords export file + + Returns: + List of imported PasswordEntry objects + """ + if input_path: + self.export_path = Path(input_path) + + if not self.export_path or not self.export_path.exists(): + raise IntegrationError("Chrome passwords export file not found") + + return self.list_entries() + + def export_entries(self, output_path: Path) -> bool: + """Export entries to a Chrome-compatible CSV file. + + Args: + output_path: Path to save the exported data + + Returns: + bool: True if export was successful + """ + # This would require converting from our format to Chrome's format + # For now, we'll just raise a NotImplementedError + raise NotImplementedError("Export to Chrome format is not yet implemented") + + @classmethod + def export_help(cls) -> str: + """Get instructions for exporting from Chrome. + + Returns: + str: Instructions for exporting from Chrome + """ + return """To export passwords from Chrome: + 1. Open Chrome and go to: chrome://settings/passwords + 2. Click the three dots menu next to 'Saved Passwords' + 3. Select 'Export passwords...' + 4. Follow the prompts to save the passwords to a CSV file + """ + +# Register the integration with the name 'chrome' +register_integration("chrome")(ChromeIntegration) |
