| 101 | |
| 102 | |
| 103 | class KeeperPasswordGenerator(PasswordGenerator): |
| 104 | def __init__(self, length: int = DEFAULT_PASSWORD_LENGTH, |
| 105 | symbols: Optional[int] = None, |
| 106 | digits: Optional[int] = None, |
| 107 | caps: Optional[int] = None, |
| 108 | lower: Optional[int] = None, |
| 109 | special_characters: str = PW_SPECIAL_CHARACTERS): |
| 110 | |
| 111 | sum_categories = sum((abs(i) if isinstance(i, int) else 0) for i in (symbols, digits, caps, lower)) |
| 112 | extra_count = length - sum_categories if length > sum_categories else 0 |
| 113 | extra_chars = '' |
| 114 | if symbols is None or isinstance(symbols, int) and symbols > 0: |
| 115 | extra_chars += special_characters |
| 116 | if digits is None or isinstance(digits, int) and digits > 0: |
| 117 | extra_chars += string.digits |
| 118 | if caps is None or isinstance(caps, int) and caps > 0: |
| 119 | extra_chars += string.ascii_uppercase |
| 120 | if lower is None or isinstance(lower, int) and lower > 0: |
| 121 | extra_chars += string.ascii_lowercase |
| 122 | if extra_count > 0 and not extra_chars: |
| 123 | if isinstance(symbols, int) and symbols < 0: |
| 124 | extra_chars += special_characters |
| 125 | if isinstance(digits, int) and digits < 0: |
| 126 | extra_chars += string.digits |
| 127 | if isinstance(caps, int) and caps < 0: |
| 128 | extra_chars += string.ascii_uppercase |
| 129 | if isinstance(lower, int) and lower < 0: |
| 130 | extra_chars += string.ascii_lowercase |
| 131 | |
| 132 | if extra_count > 0 and not extra_chars: |
| 133 | raise Exception('Password character set is empty') |
| 134 | self.category_map = [ |
| 135 | (abs(symbols) if isinstance(symbols, int) else 0, special_characters), |
| 136 | (abs(digits) if isinstance(digits, int) else 0, string.digits), |
| 137 | (abs(caps) if isinstance(caps, int) else 0, string.ascii_uppercase), |
| 138 | (abs(lower) if isinstance(lower, int) else 0, string.ascii_lowercase), |
| 139 | (extra_count, extra_chars) |
| 140 | ] |
| 141 | |
| 142 | def generate(self) -> str: |
| 143 | password_list = [] |
| 144 | for count, chars in self.category_map: |
| 145 | password_list.extend(choice(chars) for i in range(count)) |
| 146 | shuffle(password_list) |
| 147 | return ''.join(password_list) |
| 148 | |
| 149 | @classmethod |
| 150 | def create_from_rules(cls, rule_string: str, length: Optional[int] = None, |
| 151 | special_characters: str = PW_SPECIAL_CHARACTERS): |
| 152 | """Create instance of class from rules string |
| 153 | |
| 154 | rule_string: comma separated integer character counts of [length,] uppercase, lowercase, numbers, symbols |
| 155 | length: length of password |
| 156 | special_characters: set of characters used to generate password symbols |
| 157 | """ |
| 158 | try: |
| 159 | rule_list = [int(s.strip()) for s in rule_string.split(',')] |
| 160 | if len(rule_list) == 5: |
no outgoing calls
no test coverage detected