gtk2passwordapp 4.3.0 → 6.0.210202

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,161 +1,213 @@
1
- module Gtk2passwordapp
1
+ class Gtk2PasswordApp
2
2
  using Rafini::String
3
+ extend Rafini::Empty
3
4
 
4
- help = <<-HELP
5
- Usage:
6
- gtk3app gtk2passwordapp [--help] [--version]
7
- gtk2passwordapp [--no-gui [--dump [--verbose]]] [account]
8
- HELP
9
-
10
- APPDIR = File.dirname File.dirname __dir__
11
-
12
- s0 = Rafini::Empty::STRING
13
- h0 = Rafini::Empty::HASH
14
- a0 = Rafini::Empty::ARRAY
5
+ H2Q = BaseConvert::FromTo.new(base: 16, digits: '0123456789ABCDEF', to_base:91, to_digits: :qgraph)
6
+ RND = SuperRandom.new
7
+ TOTPx = /^[A-Z2-7]{16,}$/
15
8
 
16
9
  CONFIG = {
17
- Help: help,
18
10
 
19
- # Password Data File
20
- PwdFile: "#{XDG['CACHE']}/gtk3app/gtk2passwordapp/passwords.dat",
21
- # Shared Secret File
22
- # Consider using a file found in a removable flashdrive.
23
- SharedSecretFile: "#{XDG['CACHE']}/gtk3app/gtk2passwordapp/key.ssss",
24
- BackupFile: "#{ENV['HOME']}/Dropbox/gtk2passwordapp.bak",
11
+ # Hashing
25
12
 
26
- # Mark Recent Selections
27
- Recent: 7,
13
+ Salt: s0,
14
+ LongPwd: 14,
28
15
 
29
- # Mark Old Passwords
30
- TooOld: 60*60*24*365, # Year
16
+ # Miscellaneous Strings
31
17
 
32
- # Timeout for qr-code read.
33
- QrcTimeOut: 9,
34
-
35
- # Password Generators
36
- Random: 'Random',
37
- AlphaNumeric: 'Alpha-Numeric',
38
- Custom: 'Caps',
39
- CustomDigits: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
40
-
41
- # Button Labels
42
- Go: 'Go',
43
- Edit: 'Edit',
44
- Add: 'Add',
45
- Goto: 'Goto',
46
- Current: 'Current',
47
- Previous: 'Previous',
48
- Show: 'Show',
49
- Cancel: 'Cancel',
50
- Delete: 'Delete',
51
- Save: 'Save',
52
-
53
- # Labels
54
- ReTry: 'Try Again!',
18
+ Logo: "#{UserSpace::XDG['data']}/gtk3app/gtk2passwordapp/logo.png",
55
19
  HiddenPwd: ' * * * ',
56
20
 
21
+ # Overriding Gtk3App's window, main:
22
+
23
+ main: { set_title: 'Password Manager' },
24
+
25
+ # Overriding Gtk3App's toolbar
26
+ toolbar: {
27
+ set_expanded: true,
28
+ into: [:pack_start, expand:true, fill:true, padding:4],
29
+ },
30
+
31
+ # Overriding Gtk3App's app_menu:
32
+
33
+ app_menu: {
34
+ add_menu_item: [:minime!,:about!,:quit!],
35
+ },
36
+
57
37
  # Colors
58
- Blue: '#00F',
59
- Red: '#F00',
60
- Black: '#000',
61
38
 
62
- # Clipboard
63
- SwitchClipboard: false,
39
+ Red: '#900',
40
+ Green: '#090',
41
+ Blue: '#009',
42
+ TooOld: 60*60*24*365, # Year
43
+
44
+ # Buttons
45
+
46
+ tool_button: {
47
+ set_width_request: 1,
48
+ into: [:pack_start, expand:true, fill:true, padding:1],
49
+ },
50
+
51
+ # Spin Buttons
52
+
53
+ PWDLEN: [3,40,1],
54
+ pwdlen: {set_value: 13},
55
+ pwdlen!: [:PWDLEN,:pwdlen],
56
+
57
+ # Tools Labels
58
+
59
+ ADD: [label: 'Add'],
60
+ EDIT: [label: 'Edit'],
61
+ GO: [label: 'Go'],
62
+ CANCEL: [label: 'Cancel'],
63
+ DELETE: [label: 'Delete'],
64
+ SAVE: [label: 'Save'],
65
+ CURRENT: [label: 'Current'],
66
+ PREVIOUS: [label: 'Previous'],
67
+ RAND: [label: 'Random'],
68
+
69
+ # Initialize
70
+ # Stage and Toolbar
71
+
64
72
  ClipboardTimeout: 15,
73
+ PwdFile: "#{UserSpace::XDG['cache']}/gtk3app/gtk2passwordapp/dump.yzb",
74
+
75
+ # Logo's Main Menu
76
+
77
+ MAIN_MENU: a0,
78
+ main_menu: h0,
79
+ main_menu!: [:MAIN_MENU,:main_menu],
80
+
81
+ main_menu_item: h0,
82
+
83
+ # Toolbar's Toolbox
84
+
85
+ TOOLBOX: [:horizontal],
86
+ toolbox: h0,
87
+ toolbox!: [:TOOLBOX,:toolbox],
88
+
89
+ # Stage's Pages
90
+
91
+ PAGES: [:vertical],
92
+ pages: h0,
93
+ pages!: [:PAGES,:pages],
94
+
95
+ # Page
96
+
97
+ PAGE: [:vertical],
98
+ page: h0,
99
+ page!: [:PAGE,:page],
100
+
101
+ # Page Label
102
+
103
+ page_label: h0,
104
+
105
+ # Field Row
106
+
107
+ FIELD_ROW: [:horizontal],
108
+ field_row: h0,
109
+ field_row!: [:FIELD_ROW,:field_row],
110
+
111
+ # Field Label
112
+
113
+ FIELD_LABEL: a0,
114
+ field_label: {
115
+ set_selectable: false,
116
+ set_width_request: 80,
117
+ set_alignment: [1.0,0.5],
118
+ set_padding: [4,4],
119
+ },
120
+ field_label!: [:FIELD_LABEL, :field_label],
121
+
122
+ # Field View
123
+
124
+ FIELD_VIEW: a0,
125
+ field_view: {
126
+ set_selectable: true,
127
+ set_width_request: 250,
128
+ set_alignment: [0.0,0.5],
129
+ set_padding: [4,4],
130
+ },
131
+ field_view!: [:FIELD_VIEW, :field_view],
132
+
133
+ # Field Entry
134
+
135
+ FIELD_ENTRY: a0,
136
+ field_entry: {
137
+ set_width_request: 250,
138
+ into: [:pack_start, expand:true, fill:true, padding:4],
139
+ },
140
+ field_entry!: [:FIELD_ENTRY,:field_entry],
141
+
142
+ # Password Entry
143
+
144
+ PASSWORD_ENTRY: a0,
145
+ password_entry: {
146
+ set_visibility: false,
147
+ set_width_request: 250,
148
+ into: [:pack_start, expand:true, fill:true, padding:4],
149
+ },
150
+ password_entry!: [:PASSWORD_ENTRY,:password_entry],
151
+
152
+ # Error Label
153
+
154
+ ERROR_LABEL: a0,
155
+ error_label: h0,
156
+ error_label!: [:ERROR_LABEL,:error_label],
157
+
158
+ # Password Page
159
+
160
+ MinPwdLen: 7,
161
+ Confirm: "Confirm password!",
162
+
163
+ # Page Labels
164
+
165
+ PASSWORD_PAGE_LABEL: ['Enter Master Password'],
166
+ ADD_PAGE_LABEL: ['Add Account'],
167
+ EDIT_PAGE_LABEL: ['Edit Account'],
168
+ MAIN_PAGE_LABEL: ['View Account'],
169
+
170
+ # Fields
171
+
172
+ NAME: ['Name:'],
173
+ URL: ['URL:'],
174
+ NOTE: ['Note:'],
175
+ USERNAME: ['Username:'],
176
+ PASSWORD: ['Password:'],
177
+
178
+ # About Dialog
179
+
180
+ about_dialog: {
181
+ set_program_name: 'Password Manager',
182
+ set_version: VERSION.semantic(0..1),
183
+ set_copyright: '(c) 2021 CarlosJHR64',
184
+ set_comments: 'A Gtk3App Password Manager',
185
+ set_website: 'https://github.com/carlosjhr64/gtk2passwordapp',
186
+ set_website_label: 'See it at GitHub!',
187
+ },
188
+
189
+ # Delete Dialog
190
+
191
+ DELETE_URSURE: a0,
192
+ delete_ursure: {add_label: 'Delete?'},
193
+ delete_ursure!: [:DELETE_URSURE,:delete_ursure],
194
+
195
+ # Reset Dialog
196
+
197
+ RESET_URSURE: a0,
198
+ reset_ursure: {add_label: 'Reset Master Password?'},
199
+ reset_ursure!: [:RESET_URSURE,:reset_ursure],
200
+
201
+ # Errors
202
+
203
+ BadUrl: 'URL must be like http://site.',
204
+ BadUsername: 'Username must be all graph.',
205
+ BadPassword: 'Password must be all graph.',
206
+ BadName: 'Account name must be a non-empty String.',
207
+ CipherError: 'Decryption error.',
208
+ AccountHit: 'Account exists.',
209
+ AccountMiss: 'Account does NOT exist.',
210
+ TooShort: "Password too short!",
65
211
 
66
- # Fields' Labels
67
- Name: 'Account:',
68
- FIELDS: [
69
- [:url, 'Url:' ],
70
- [:note, 'Note:' ],
71
- [:username, 'Username:'],
72
- [:password, 'Password:'],
73
- ],
74
- FIELD_ALIGNMENT: [0.0, 0.5],
75
-
76
- # Such::Thing::PARAMETERS
77
- thing: {
78
-
79
- box: h0,
80
- label: h0,
81
- check_button: h0,
82
- entry: h0,
83
-
84
- button: {
85
- set_width_request: 75,
86
- into: [:pack_start, expand:true, fill:true, padding:0],
87
- },
88
-
89
- vbox!: [[:vertical], :box, s0],
90
- hbox!: [[:horizontal], :box, s0],
91
-
92
- prompt: {
93
- set_width_request: 75,
94
- set_alignment: [1.0, 0.5],
95
- set_padding: [4,4],
96
- },
97
- prompt!: [a0, :prompt],
98
-
99
- prompted: {
100
- set_width_request: 325,
101
- },
102
- prompted!: [a0, :prompted],
103
-
104
- a!: [a0, :button],
105
- b!: [a0, :button],
106
- c!: [a0, :button],
107
-
108
- window: {
109
- set_title: 'Password Manager',
110
- set_window_position: :center,
111
- },
112
-
113
- password_label!: [['Password:'], :label],
114
- password_entry!: [a0, :entry, {set_visibility: false}],
115
-
116
- edit_label!: [['Edit Account'], :label],
117
- add_label!: [['Add Account'], :label],
118
- view_label!: [['View Account'], :label],
119
-
120
- pwd_size_check!: [:check_button],
121
- pwd_size_spin!: [
122
- [4,64,1],
123
- {
124
- set_increments: [1,10],
125
- set_digits: 0,
126
- set_value: 14,
127
- },
128
- ],
129
-
130
- reset!: [['Reset Master Password'], 'activate'],
131
- backup!: [['Backup Passwords'], 'activate'],
132
-
133
- about_dialog: {
134
- set_program_name: 'Password Manager',
135
- set_version: VERSION.semantic(0..1),
136
- set_copyright: '(c) 2014 CarlosJHR64',
137
- set_comments: 'A Gtk3App Password Manager',
138
- set_website: 'https://github.com/carlosjhr64/gtk2passwordapp',
139
- set_website_label: 'See it at GitHub!',
140
- },
141
- HelpFile: 'https://github.com/carlosjhr64/gtk2passwordapp',
142
- Logo: "#{XDG['DATA']}/gtk3app/gtk2passwordapp/logo.png",
143
-
144
- backup_dialog: {
145
- set_title: 'Backup Passwords',
146
- set_window_position: :center_on_parent,
147
- },
148
-
149
- error_dialog: {
150
- set_text: 'Backup Error',
151
- set_window_position: :center_on_parent,
152
- },
153
-
154
- delete_dialog: {
155
- set_window_position: :center_on_parent,
156
- },
157
- delete_label!: [['Delete?'], :label],
158
-
159
- }
160
212
  }
161
213
  end
@@ -0,0 +1,314 @@
1
+ class Gtk2PasswordApp
2
+ def initialize(stage, toolbar, options)
3
+ @primary = Gtk::Clipboard.get(Gdk::Selection::PRIMARY)
4
+ @clipboard = Gtk::Clipboard.get(Gdk::Selection::CLIPBOARD)
5
+ @accounts = Accounts.new(CONFIG[:PwdFile])
6
+ @toolbox = Such::Box.new toolbar, :toolbox!
7
+ @pages = Such::Box.new stage, :pages!
8
+ @recent = []
9
+ @reset = false
10
+ @red,@green,@blue = [:Red,:Green,:Blue].map{Gdk::RGBA.parse(CONFIG[_1])}
11
+ @tools = @password_page = @add_page = @edit_page = @main_page = @menu = nil
12
+ build_password_page
13
+ build_logo_menu
14
+ end
15
+
16
+ def build_logo_menu
17
+ Gtk3App.logo_press_event do |button|
18
+ next unless @main_page&.visible?
19
+ case button
20
+ when 1
21
+ popup_accounts_menu unless @accounts.data.empty?
22
+ when 2
23
+ reset_password
24
+ when 3
25
+ # Gets captured by Gtk3App's main menu
26
+ end
27
+ end
28
+ end
29
+
30
+ def reset_password
31
+ ursure = Gtk3App::YesNoDialog.new :reset_ursure!
32
+ Gtk3App.transient ursure
33
+ if ursure.ok?
34
+ @reset = true
35
+ hide_main_page
36
+ @password_page.show
37
+ end
38
+ end
39
+
40
+ def popup_accounts_menu
41
+ @menu = Such::Menu.new :main_menu!
42
+ @recent.each do |name| add_menu_item(name, @green) end
43
+ @accounts.data.keys.sort{|a,b|a.upcase<=>b.upcase}.each do |name| add_menu_item(name) end
44
+ @menu.show_all
45
+ @menu.popup_at_pointer
46
+ end
47
+
48
+ def add_menu_item(name, color=nil)
49
+ menu_item = Such::MenuItem.new [label:name], :main_menu_item, 'activate' do selected_account(name) end
50
+ account = @accounts.get name
51
+ color = (Time.now.to_i - account.updated > CONFIG[:TooOld])? @red : @blue unless color
52
+ menu_item.override_color :normal, color
53
+ @menu.append menu_item
54
+ end
55
+
56
+ def selected_account(name)
57
+ @recent.unshift name; @recent.uniq!; @recent.pop if @recent.length>3
58
+ account = @accounts.get name
59
+ setup_main_page account
60
+ setup_edit_page account
61
+ copy2clipboard(account.password, account.username)
62
+ end
63
+
64
+ def copy2clipboard(pwd, user)
65
+ @primary.text, @clipboard.text = pwd, user
66
+ GLib::Timeout.add_seconds(CONFIG[:ClipboardTimeout]) do
67
+ @primary.text = '' if @primary.wait_for_text == pwd
68
+ @clipboard.text = '' if @clipboard.wait_for_text == user
69
+ false
70
+ end
71
+ end
72
+
73
+ def view_row(page, label)
74
+ row = Such::Box.new page, :field_row!
75
+ Such::Label.new(row, label, :field_label)
76
+ Such::Label.new(row, :field_view!)
77
+ end
78
+
79
+ def field_row(page, label, entry=:field_entry!, &block)
80
+ row = Such::Box.new page, :field_row!
81
+ Such::Label.new(row, label, :field_label)
82
+ Such::Entry.new(row, entry, ((block)? 'activate' : ''), &block)
83
+ end
84
+
85
+ def show_main_page
86
+ @main_page.show_all
87
+ @toolbox.show_all
88
+ end
89
+
90
+ def hide_main_page
91
+ @main_page.hide
92
+ @toolbox.hide
93
+ end
94
+
95
+ def bootstrap_setups
96
+ names = @accounts.data.keys
97
+ account = (names.empty?)? nil : @accounts.get(names[rand(names.length)])
98
+ setup_edit_page(account) if account
99
+ setup_main_page(account)
100
+ end
101
+
102
+ def rehash(pwd)
103
+ pwd += CONFIG[:Salt] if pwd.length < CONFIG[:LongPwd]
104
+ pwd = Digest::SHA256.hexdigest(pwd).upcase
105
+ H2Q.convert pwd
106
+ end
107
+
108
+ def build_password_page
109
+ @password_page = Such::Box.new @pages, :page!
110
+ Such::Label.new @password_page, :PASSWORD_PAGE_LABEL, :page_label
111
+ error_label,previous = nil,'' # updates below
112
+ password_entry = field_row(@password_page, :PASSWORD, :password_entry!) do
113
+ pwd = password_entry.text.strip
114
+ password_entry.text = ''
115
+ raise CONFIG[:TooShort] if pwd.length < CONFIG[:MinPwdLen]
116
+ if not @reset and @accounts.exist?
117
+ @accounts.load rehash pwd
118
+ else
119
+ raise CONFIG[:Confirm] unless pwd==previous
120
+ @accounts.save rehash pwd
121
+ @reset = false
122
+ end
123
+ @password_page.hide
124
+ build_add_page unless @add_page
125
+ build_edit_page unless @edit_page
126
+ build_main_page unless @main_page
127
+ build_tools unless @tools
128
+ bootstrap_setups
129
+ show_main_page
130
+ rescue
131
+ error_label.text = $!.message
132
+ previous = pwd
133
+ end
134
+ error_label = Such::Label.new @password_page, :error_label!
135
+ end
136
+
137
+ def build_add_page
138
+ @add_page = Such::Box.new @pages, :page!
139
+ Such::Label.new @add_page, :ADD_PAGE_LABEL, :page_label
140
+ error_label = nil # updates below
141
+ add_account_entry = field_row(@add_page, :NAME) do
142
+ name = add_account_entry.text.strip
143
+ @accounts.add name
144
+ @accounts.save
145
+ account = @accounts.get name
146
+ setup_edit_page(account)
147
+ setup_main_page(account)
148
+ @add_page.hide
149
+ add_account_entry.text = ''
150
+ @edit_page.show_all
151
+ rescue
152
+ error_label.text = $!.message
153
+ end
154
+ Such::Button.new @add_page, :CANCEL, :tool_button do
155
+ @add_page.hide
156
+ add_account_entry.text = ''
157
+ show_main_page
158
+ end
159
+ error_label = Such::Label.new @add_page, :error_label!
160
+ end
161
+
162
+ def visibility_toggleling(entry)
163
+ entry.signal_connect('enter-notify-event') do
164
+ entry.set_visibility true unless entry.has_focus?
165
+ end
166
+ entry.signal_connect('leave-notify-event') do
167
+ entry.set_visibility false unless entry.has_focus?
168
+ end
169
+ entry.signal_connect('focus-in-event') do
170
+ entry.set_visibility true
171
+ end
172
+ entry.signal_connect('focus-out-event') do
173
+ entry.set_visibility false
174
+ end
175
+ end
176
+
177
+ def show_toggleling(label)
178
+ label.signal_connect('button-press-event') do |_,e|
179
+ if e.button==1 and not (pwd=label.text).empty?
180
+ case pwd
181
+ when CONFIG[:HiddenPwd]
182
+ label.text = @accounts.get(@name.text).password
183
+ when TOTPx
184
+ label.text = TOTP.passwords(label.text)[1].to_s
185
+ else
186
+ label.text = CONFIG[:HiddenPwd]
187
+ end
188
+ end
189
+ end
190
+ end
191
+
192
+ def build_edit_page
193
+ @edit_page = Such::Box.new @pages, :page!
194
+ Such::Label.new @edit_page, :EDIT_PAGE_LABEL, :page_label
195
+
196
+ @edit_name = view_row @edit_page, :NAME
197
+ @edit_url = field_row @edit_page, :URL
198
+ @edit_note = field_row @edit_page, :NOTE
199
+ @edit_username = field_row @edit_page, :USERNAME
200
+ @edit_password = field_row @edit_page, :PASSWORD, :password_entry!
201
+
202
+ visibility_toggleling @edit_password
203
+
204
+ generator = Such::Box.new @edit_page, :toolbox!
205
+ pwdlen=rndpwd=nil
206
+ Such::Button.new generator, :RAND, :tool_button do
207
+ rndpwd = H2Q.convert RND.hexadecimal
208
+ @edit_password.text = rndpwd[0...pwdlen.value]
209
+ end
210
+ pwdlen = Such::SpinButton.new generator, :pwdlen!, 'value-changed' do
211
+ @edit_password.text = rndpwd[0...pwdlen.value] if rndpwd
212
+ end
213
+
214
+ toolbox = Such::Box.new @edit_page, :toolbox!
215
+ error_label = Such::Label.new @edit_page, :error_label!
216
+
217
+ Such::Button.new toolbox, :SAVE, :tool_button do
218
+ account = @accounts.get @edit_name.text.strip
219
+ account.url = @edit_url.text.strip
220
+ account.note = @edit_note.text.strip
221
+ account.username = @edit_username.text.strip
222
+ account.password = @edit_password.text.strip
223
+ @accounts.save
224
+ rndpwd = nil
225
+ @edit_page.hide
226
+ setup_main_page(account)
227
+ show_main_page
228
+ rescue
229
+ error_label.text = $!.message
230
+ end
231
+ Such::Button.new toolbox, :CANCEL, :tool_button do
232
+ account = @accounts.get @edit_name.text
233
+ rndpwd = nil
234
+ @edit_page.hide
235
+ setup_edit_page(account) # restore values
236
+ show_main_page
237
+ end
238
+ Such::Button.new toolbox, :DELETE, :tool_button do
239
+ ursure = Gtk3App::YesNoDialog.new :delete_ursure!
240
+ Gtk3App.transient ursure
241
+ if ursure.ok?
242
+ @accounts.delete @edit_name.text
243
+ @accounts.save
244
+ rndpwd = nil
245
+ @edit_page.hide
246
+ bootstrap_setups
247
+ show_main_page
248
+ end
249
+ end
250
+ end
251
+
252
+ def setup_edit_page(account)
253
+ @edit_name.text = account.name
254
+ @edit_url.text = account.url
255
+ @edit_note.text = account.note
256
+ @edit_username.text = account.username
257
+ @edit_password.text = account.password
258
+ end
259
+
260
+ def build_main_page
261
+ @main_page = Such::Box.new @pages, :page!
262
+ Such::Label.new @main_page, :MAIN_PAGE_LABEL, :page_label
263
+
264
+ @name = view_row @main_page, :NAME
265
+ @url = view_row @main_page, :URL
266
+ @note = view_row @main_page, :NOTE
267
+ @username = view_row @main_page, :USERNAME
268
+ @password = view_row @main_page, :PASSWORD
269
+
270
+ show_toggleling @password
271
+ end
272
+
273
+ def setup_main_page(account)
274
+ @name.text = account&.name || ''
275
+ @url.text = account&.url || ''
276
+ @note.text = account&.note || ''
277
+ @username.text = account&.username || ''
278
+ @password.text =(account&.password&.>'')? CONFIG[:HiddenPwd] : ''
279
+ end
280
+
281
+ def build_tools
282
+ @tools = true
283
+ Such::Button.new @toolbox, :ADD, :tool_button do
284
+ hide_main_page
285
+ @add_page.show_all
286
+ end
287
+ Such::Button.new @toolbox, :EDIT, :tool_button do
288
+ unless (name=@name.text).empty?
289
+ hide_main_page
290
+ @edit_page.show_all
291
+ end
292
+ end
293
+ Such::Button.new @toolbox, :GO, :tool_button do
294
+ unless (name=@name.text).empty?
295
+ url = @accounts.get(name).url
296
+ system(Gtk3App::CONFIG[:Open], url) unless url.empty?
297
+ end
298
+ end
299
+ Such::Button.new @toolbox, :CURRENT, :tool_button do
300
+ unless (name=@name.text).empty?
301
+ account = @accounts.get name
302
+ copy2clipboard(account.password, account.username)
303
+ end
304
+ end
305
+ Such::Button.new @toolbox, :PREVIOUS, :tool_button do
306
+ unless (name=@name.text).empty?
307
+ account = @accounts.get name
308
+ copy2clipboard(account.previous, account.password)
309
+ end
310
+ end
311
+ end
312
+
313
+ def self.run = Gtk3App.run(klass:Gtk2PasswordApp)
314
+ end