Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

# Password Generator 

 

# License {{{1 

# Copyright (C) 2016 Kenneth S. Kundert 

# 

# This program is free software: you can redistribute it and/or modify it under 

# the terms of the GNU General Public License as published by the Free Software 

# Foundation, either version 3 of the License, or (at your option) any later 

# version. 

# 

# This program is distributed in the hope that it will be useful, but WITHOUT 

# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 

# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 

# details. 

# 

# You should have received a copy of the GNU General Public License along with 

# this program. If not, see http://www.gnu.org/licenses/. 

 

 

# Imports {{{1 

from .account import Account 

from .config import read_config, get_setting 

from .dialog import show_list_dialog 

from .gpg import GnuPG, PythonFile, GPG_EXTENSIONS 

from .obscure import Hidden 

from .preferences import ( 

CONFIG_DEFAULTS, NONCONFIG_SETTINGS, ACCOUNTS_FILE_INITIAL_CONTENTS, 

CONFIG_FILE_INITIAL_CONTENTS, USER_KEY_FILE_INITIAL_CONTENTS, 

HASH_FILE_INITIAL_CONTENTS, STEALTH_ACCOUNTS_FILE_INITIAL_CONTENTS, 

ACCOUNT_LIST_FILE_CONTENTS, 

) 

from .title import Title 

from .utilities import generate_random_string, validate_componenets 

from inform import ( 

debug, Error, notify, log, render, terminate, terminate_if_errors 

) 

from shlib import to_path 

from pathlib import Path 

 

# PasswordGenerator class{{{1 

class PasswordGenerator(object): 

 

# Constructor {{{2 

def __init__(self, init=False, gpg_ids=None): 

# initialize avendesora (these should already be done if called from  

# main, but it is safe to call them again) 

read_config() 

GnuPG.initialize() 

 

# check the integrity of avendesora 

validate_componenets() 

 

# create the avendesora data directory 

if init: 

self.initialize(gpg_ids, init) 

terminate() 

 

# read the accounts files 

self.accounts = set() 

for filename in get_setting('accounts_files', []): 

try: 

path = to_path(get_setting('settings_dir'), filename) 

account_file = PythonFile(path) 

contents = account_file.run() 

master_password = contents.get('master_password') 

 

# traverse through all accounts, determine which are new, bind 

# required information to new accounts, and update account list. 

for account in Account.all_accounts(): 

if account not in self.accounts: 

account.add_fileinfo(master_password, account_file) 

 

# save a copy of account so it is not garbage collected 

self.accounts.add(account) 

except Error as err: 

err.terminate() 

terminate_if_errors() 

 

# initialize() {{{2 

def initialize(self, gpg_ids, filename): 

# if filename is True, this is being called as part of the Avendesora 

# 'initialize' command, in which case all missing files should be created. 

# if filename is a string, this is being called as part of the 

# Avendesora 'new' command, in which case a single new account file 

# should be created. 

def split(s, l=72): 

# Break long string into a series of adjacent shorter strings 

if len(s) < l: 

return '"%s"' % s 

chunks = [' "%s"' % s[i:i+l] for i in range(0, len(s), l)] 

return '\n' + '\n'.join(chunks) + '\n' 

 

# Create dictionary of available substitutions for CONTENTS strings 

fields = {} 

for key in CONFIG_DEFAULTS: 

value = get_setting(key, expand=False) 

value = render(str(value) if isinstance(value, Path) else value) 

fields.update({key: value}) 

for key in NONCONFIG_SETTINGS: 

value = get_setting(key, expand=False) 

value = render(str(value) if isinstance(value, Path) else value) 

fields.update({key: value}) 

gpg_ids = gpg_ids if gpg_ids else get_setting('gpg_ids', []) 

fields.update({ 

'section': '{''{''{''1', 

'master_password': split(Hidden.conceal(generate_random_string(72))), 

'master_password2': split(Hidden.conceal(generate_random_string(72))), 

'user_key': split(Hidden.conceal(generate_random_string(72))), 

'gpg_ids': repr(' '.join(gpg_ids)), 

}) 

 

# create the initial versions of the files in the settings directory 

if filename is True: 

# Assure that the default initial set of files is present 

for path, contents in [ 

(get_setting('config_file'), CONFIG_FILE_INITIAL_CONTENTS), 

(get_setting('hashes_file'), HASH_FILE_INITIAL_CONTENTS), 

(get_setting('user_key_file'), USER_KEY_FILE_INITIAL_CONTENTS), 

(get_setting('default_accounts_file'), ACCOUNTS_FILE_INITIAL_CONTENTS), 

(get_setting('default_stealth_accounts_file'), STEALTH_ACCOUNTS_FILE_INITIAL_CONTENTS), 

]: 

if path: 

log('creating initial version.', culprit=path) 

f = PythonFile(path) 

f.create(contents.format(**fields), gpg_ids) 

# create will not overwrite an existing file, instead it 

# reads the file. 

else: 

# Create a new accounts file 

fields['accounts_files'] = get_setting('accounts_files', []) + [filename] 

path = to_path(get_setting('settings_dir'), filename) 

if path.exists(): 

raise Error('exists.', culprit=path) 

if path.suffix in GPG_EXTENSIONS and not gpg_ids: 

raise Error('Must specify GPG IDs.') 

log('creating accounts file.', culprit=path) 

f = PythonFile(path) 

f.create(ACCOUNTS_FILE_INITIAL_CONTENTS.format(**fields), gpg_ids) 

 

# Create a new accounts file 

path = to_path(get_setting('account_list_file')) 

if path.suffix in GPG_EXTENSIONS: 

raise Error('encryption is not supported.', culprit=path) 

try: 

log('writing.', culprit=path) 

path.write_text( 

ACCOUNT_LIST_FILE_CONTENTS.format(**fields).decode( 

get_setting('encoding') 

) 

) 

except OSError as err: 

raise Error(os_error(err)) 

 

# get_account() {{{2 

def get_account(self, name, request_seed=False): 

if not name: 

raise Error('no account specified.') 

for account in self.all_accounts(): 

if account.matches_exactly(name): 

account.initialize(request_seed) 

return account 

raise Error('not found.', culprit=name) 

 

# discover_account() {{{2 

def discover_account(self, title=None, verbose=False): 

log('Account Discovery ...') 

if get_setting('verbose'): 

verbose = True 

 

# get and parse the title 

data = Title(override=title).get_data() 

 

# sweep through accounts to see if any recognize this title data 

matches = {} 

for account in self.all_accounts(): 

name = account.get_name() 

if verbose: 

log('Trying:', name) 

for key, script in account.recognize(data, verbose): 

ident = '%s (%s)' % (name, key) if key else name 

matches[ident] = name, script 

if verbose: 

log(' %s matches' % ident) 

 

if not matches: 

msg = 'cannot find appropriate account.' 

notify(msg) 

raise Error(msg) 

if len(matches) > 1: 

choice = show_list_dialog(sorted(matches.keys())) 

if choice: 

return matches[choice] 

else: 

return matches.popitem()[1] 

 

 

# all_accounts() {{{2 

def all_accounts(self): 

for account in self.accounts: 

yield account 

 

# find_acounts() {{{2 

def find_accounts(self, target): 

for account in self.all_accounts(): 

if account.id_contains(target): 

yield account 

 

# search_acounts() {{{2 

def search_accounts(self, target): 

for account in self.all_accounts(): 

if account.account_contains(target): 

yield account