Coverage for pass_import/core.py: 97%
62 statements
« prev ^ index » next coverage.py v7.4.3, created at 2024-02-26 12:11 +0000
« prev ^ index » next coverage.py v7.4.3, created at 2024-02-26 12:11 +0000
1# -*- encoding: utf-8 -*-
2# pass import - Passwords importer swiss army knife
3# Copyright (C) 2017-2024 Alexandre PUJOL <alexandre@pujol.io>.
4#
6import io
7import os
8from abc import ABC
9from enum import IntFlag, auto
10from typing import Set, Callable
12from pass_import.errors import PMError
14_MANAGERS = set()
15_DETECTERS = set()
18def register_managers(*managers):
19 """Register new password manager(s)."""
20 for cls in managers:
21 _MANAGERS.add(cls)
24def register_detecters(*detecters):
25 """Register new detecter(s)."""
26 for cls in detecters:
27 _DETECTERS.add(cls)
30def get_managers() -> Set[Callable]:
31 """Return the registered password manager set."""
32 return _MANAGERS
35def get_detecters() -> Set[Callable]:
36 """Return the registered detecter set."""
37 return _DETECTERS
40class Cap(IntFlag):
41 """Set the class capabilities (IMPORT, EXPORT, FORMAT, DECRYPT)."""
42 UNKNOWN = auto()
43 IMPORT = auto()
44 EXPORT = auto()
45 FORMAT = auto()
46 DECRYPT = auto()
49class Asset(ABC):
50 """Password managers/detectors abstract assets.
52 :param Cap cap: Capabilities of the class.
53 :param str name: Name of the password manager.
54 :param str format: Format of the password manager supported.
55 :param str version: Version of the password manager supported.
56 :param bool default: ``True`` is the pm is the default pm with its name.
57 :param bool only: On header check, set if the file can have more header
58 than the headers set in the header list or dict given by
59 :func:`~header`. Default: ``False``. Required by some managers that
60 contain only a very generic header.
61 :param str encoding: File encoding, default: ``utf-8``
62 :param str mode: File reading mode, default: ``r``
64 """
65 cap = Cap.UNKNOWN
66 name = ''
67 format = ''
68 version = ''
69 default = True
70 only = False
71 encoding = 'utf-8'
72 mode = 'r'
74 def __init__(self, prefix=None):
75 """Asset manager initialisation.
77 :param prefix: (optional) Path, identifiant of the pm. It can also
78 be a file object. Therefore, the passwords are read from it.
79 :param dict settings: (optional) Additional settings for the pm.
81 """
82 self.prefix = None
83 self.file = None
84 if isinstance(prefix, io.IOBase):
85 self.file = prefix
86 else:
87 self.prefix = prefix
88 super().__init__()
90 def open(self):
91 """Open the file at ``prefix``."""
92 if self.file is None:
93 self.file = open(self.prefix, self.mode, encoding=self.encoding)
95 def close(self):
96 """Close the file."""
97 if isinstance(self.file, io.IOBase):
98 self.file.close()
100 def exist(self) -> bool:
101 """Ensure the file/repository exist."""
102 if isinstance(self.file, io.IOBase):
103 return True
104 return os.path.isfile(self.prefix)
106 @classmethod
107 def isvalid(cls) -> bool:
108 """Ensure the user has the credential to use the file/repository."""
109 return True
111 def __enter__(self):
112 """Enter the context manager."""
113 if not self.exist():
114 raise PMError(f"{self.prefix} is not a password repository.")
115 if not self.isvalid():
116 raise PMError(
117 "invalid credentials, password encryption/decryption aborted. "
118 "See https://github.com/roddhjav/pass-import#gpg-keyring")
120 self.open()
121 return self
123 def __exit__(self, *exc):
124 """Leave the context manager."""
125 self.close()