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

1# -*- encoding: utf-8 -*- 

2# pass import - Passwords importer swiss army knife 

3# Copyright (C) 2017-2024 Alexandre PUJOL <alexandre@pujol.io>. 

4# 

5 

6import io 

7import os 

8from abc import ABC 

9from enum import IntFlag, auto 

10from typing import Set, Callable 

11 

12from pass_import.errors import PMError 

13 

14_MANAGERS = set() 

15_DETECTERS = set() 

16 

17 

18def register_managers(*managers): 

19 """Register new password manager(s).""" 

20 for cls in managers: 

21 _MANAGERS.add(cls) 

22 

23 

24def register_detecters(*detecters): 

25 """Register new detecter(s).""" 

26 for cls in detecters: 

27 _DETECTERS.add(cls) 

28 

29 

30def get_managers() -> Set[Callable]: 

31 """Return the registered password manager set.""" 

32 return _MANAGERS 

33 

34 

35def get_detecters() -> Set[Callable]: 

36 """Return the registered detecter set.""" 

37 return _DETECTERS 

38 

39 

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() 

47 

48 

49class Asset(ABC): 

50 """Password managers/detectors abstract assets. 

51 

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`` 

63 

64 """ 

65 cap = Cap.UNKNOWN 

66 name = '' 

67 format = '' 

68 version = '' 

69 default = True 

70 only = False 

71 encoding = 'utf-8' 

72 mode = 'r' 

73 

74 def __init__(self, prefix=None): 

75 """Asset manager initialisation. 

76 

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. 

80 

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__() 

89 

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) 

94 

95 def close(self): 

96 """Close the file.""" 

97 if isinstance(self.file, io.IOBase): 

98 self.file.close() 

99 

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) 

105 

106 @classmethod 

107 def isvalid(cls) -> bool: 

108 """Ensure the user has the credential to use the file/repository.""" 

109 return True 

110 

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") 

119 

120 self.open() 

121 return self 

122 

123 def __exit__(self, *exc): 

124 """Leave the context manager.""" 

125 self.close()