Coverage for pass_import/__init__.py: 96%

78 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"""Passwords importer swiss army knife.""" 

6 

7from collections import OrderedDict, defaultdict 

8from typing import Callable, List, Dict, Union, Generator 

9 

10import pass_import.decrypters # noqa 

11import pass_import.formats # noqa 

12import pass_import.managers # noqa 

13from pass_import.__about__ import (__author__, __copyright__, __email__, 

14 __license__, __summary__, __title__, 

15 __uri__, __version__) 

16from pass_import.core import Cap, get_detecters, get_managers 

17 

18__all__ = [ 

19 '__title__', '__summary__', '__uri__', '__version__', '__author__', 

20 '__email__', '__license__', '__copyright__' 

21] 

22 

23 

24class ManagerError(Exception): 

25 """Errors related to managers' management. Most likely a bug if raised.""" 

26 

27 

28class Managers(set): 

29 """Provide an interface to manage the managers' classes easily.""" 

30 

31 def __init__(self): 

32 super().__init__(get_managers()) 

33 

34 def classes(self, cap=Cap.IMPORT, frmt=None) -> Generator: 

35 """Generate the classes of pm with capabilities and format.""" 

36 ignore = {'csv'} 

37 for pm in self: 

38 if cap in pm.cap: 

39 if frmt: 

40 if pm.name in ignore: 

41 continue 

42 if pm.format == frmt: 

43 yield pm 

44 else: 

45 yield pm 

46 

47 def get(self, name, frmt='', version='', cap=Cap.IMPORT 

48 ) -> Union[Callable, None]: 

49 """Return a manager class from its classname or its format.""" 

50 default = None 

51 for pm in self.classes(cap): 

52 # If name is a classname, return the class 

53 if pm.__name__ == name: 

54 return pm 

55 

56 # If name is a password manager name, check its metadata 

57 if pm.name == name: 

58 if not default: 

59 default = pm 

60 if pm.format == frmt and pm.version == version: 

61 return pm 

62 

63 if default: 

64 return default 

65 raise ManagerError(f'Unknown password manager: {name}') 

66 

67 def clsnames(self, cap=Cap.IMPORT) -> List[str]: 

68 """Return the sorted list of password managers classes name.""" 

69 names = set() 

70 for pm in self.classes(cap): 

71 names.add(pm.__name__) 

72 return sorted(names) 

73 

74 def names(self, cap=Cap.IMPORT) -> List[str]: 

75 """Return the sorted list of password managers name.""" 

76 names = set() 

77 for pm in self.classes(cap): 

78 names.add(pm.name) 

79 return sorted(names) 

80 

81 def matrix(self, cap=Cap.IMPORT) -> Dict[str, List[Callable]]: 

82 """Return a dict of ordered managers classes and formats. 

83 

84 :return dict matrix: 

85 { name: [pm_1, pm_2, ..., pm_n] } such as pm1 is the dedault pm and 

86 the other pm are ordered by they format. 

87 """ 

88 umatrix = defaultdict(list) # unordered matrix 

89 for pm in self.classes(cap): 

90 umatrix[pm.name].append(pm) 

91 

92 matrix = defaultdict(list) 

93 for name in umatrix: 

94 formats = [] 

95 default = None 

96 for pm in umatrix[name]: 

97 if pm.default: 

98 default = pm 

99 else: 

100 formats.append(pm) 

101 

102 formats.sort(key=lambda x: x.format) 

103 formats.insert(0, default) 

104 matrix[name] = formats 

105 return matrix 

106 

107 

108class Detecters(OrderedDict): 

109 """An ordered dictionary of the password managers format supported. 

110 

111 This format dictionary is ordered to take care of the following 

112 requirements: 

113 

114 - Most common format first 

115 - Parent format first. Eg: ``XML`` before ``HTML``, 

116 ``JSON`` before ``YAML``... 

117 

118 """ 

119 orders = { 

120 Cap.FORMAT: [ 

121 'csv', 'xml', 'json', 'kdbx', 'yaml', '1pif', 'html', 'keychain' 

122 ], 

123 Cap.DECRYPT: [] 

124 } 

125 

126 def __init__(self, cap=Cap.FORMAT): 

127 self.cap = cap 

128 if self.cap not in Cap.FORMAT | Cap.DECRYPT: 

129 raise ManagerError('Capability not supported') 

130 

131 cls = get_detecters() 

132 detecters = OrderedDict() 

133 for frmt in self.orders[cap]: 

134 for pm in cls: 

135 if pm.format == frmt and cap in pm.cap: 

136 detecters[pm.format] = pm 

137 

138 for pm in cls: 

139 if pm.format not in self.orders[cap] and cap in pm.cap: 

140 detecters[pm.format] = pm 

141 super().__init__(detecters)