gtk2passwordapp 0.0.8 → 1.0.0

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,623 +0,0 @@
1
- # $Date: 2009/02/28 00:30:16 $
2
- require 'lib/passwords_data'
3
- require 'find'
4
-
5
- class Gtk2PasswordApp
6
- include Configuration
7
-
8
- ABOUT = {
9
- 'authors' => ['carlosjhr64@gmail.com'],
10
- 'comments' => "Ruby-Gtk2 Password Manager.",
11
- 'version' => $version,
12
- 'website' => 'http://ruby-gnome-apps.blogspot.com/search/label/Passwords',
13
- 'website-label' => 'Ruby Gnome Password Manager',
14
- 'license' => 'GPL',
15
- 'copyright' => '$Date: 2009/02/28 00:30:16 $'.gsub(/\s*\$\s*/,''),
16
- 'logo' => Gdk::Pixbuf.new(GEM_ROOT_DIR+'/gifs/logo.gif'),
17
- }
18
-
19
- BUTTONS = [[ :username, :current, :url, ],[ :note, :edit, :quit, ],]
20
-
21
- EDITOR_LABELS = [
22
- :account,
23
- :url,
24
- :note,
25
- :username,
26
- :password,
27
- ]
28
-
29
-
30
- EDITOR_BUTTONS = [
31
- [ :random, :alphanum, :num, :alpha, :caps, ],
32
- [ :visibility, :current, :previous, :cancel, :save, :update, ],
33
- [ :delete, :cpwd, :cpph ],
34
- ]
35
-
36
- TEXT = {
37
- # Labels
38
- :account => 'Account',
39
- :note => 'Note',
40
- :password => 'New',
41
- # Buttons
42
- :username => 'Username',
43
- :current => 'Current',
44
- :url => 'Url',
45
- :note => 'Note',
46
- :edit => 'Edit',
47
- :update => 'Update',
48
- :visibility => 'Visible',
49
- :alphanum => 'Alpha-Numeric',
50
- :num => 'Numeric',
51
- :alpha => 'Letters',
52
- :caps => 'All-Caps',
53
- :random => 'Random',
54
- :previous => 'Previous',
55
- :quit => 'Quit',
56
- :cancel => 'Cancel',
57
- :save => 'Save',
58
- :cpwd => 'Data File Password',
59
- :cpph => 'Data File Passphrase',
60
- :delete => 'Delete Account',
61
- }
62
-
63
- def quit_windows
64
- if @editing
65
- @editing.hide
66
- @editing.destroy
67
- @editing = nil
68
- end
69
- if @window
70
- @window.hide
71
- @window.destroy
72
- @window = nil
73
- end
74
- end
75
-
76
- def quick_message(message, window, title='Note', font=FONT)
77
- # Create the dialog
78
- dialog = Gtk::Dialog.new(
79
- title,
80
- window, Gtk::Dialog::DESTROY_WITH_PARENT,
81
- [ Gtk::Stock::OK, Gtk::Dialog::RESPONSE_NONE ])
82
-
83
- # Ensure that the dialog box is destroyed when the user responds.
84
- dialog.signal_connect('response') { dialog.destroy }
85
-
86
- # Add the message in a label, and show everything we've added to the dialog.
87
- label = Gtk::Label.new(message)
88
- label.wrap = true
89
- label.modify_font(font) if font
90
- dialog.vbox.add(label)
91
- dialog.show_all
92
- end
93
-
94
- def get_salt(title='Short Password')
95
- dialog = Gtk::Dialog.new(
96
- title,
97
- nil, nil,
98
- [ Gtk::Stock::QUIT, 0 ],
99
- [ Gtk::Stock::OK, 1 ])
100
-
101
- label = Gtk::Label.new(title)
102
- label.justify = Gtk::JUSTIFY_LEFT
103
- label.wrap = true
104
- label.modify_font(FONT)
105
- dialog.vbox.add(label)
106
- entry = Gtk::Entry.new
107
- entry.visibility = false
108
- entry.modify_font(FONT)
109
- dialog.vbox.add(entry)
110
- dialog.show_all
111
-
112
- entry.signal_connect('activate'){
113
- dialog.response(1)
114
- }
115
-
116
- ret = nil
117
- dialog.run {|response|
118
- ret = entry.text.strip if response == 1
119
- }
120
- dialog.destroy
121
-
122
- return ret
123
- end
124
-
125
- def _create_passphrase(pfile)
126
- passphrase = ''
127
-
128
- 56.times do
129
- passphrase += (rand(94)+33).chr
130
- end
131
- File.open(pfile,'w'){|fh| fh.write passphrase }
132
- File.chmod(0600, pfile)
133
-
134
- return passphrase
135
- end
136
-
137
- def get_passphrase(mv=false)
138
- passphrase = ''
139
-
140
- pfile = USER_CONF_DIR+'/passphrase.txt'
141
- if mv then
142
- File.rename(pfile, pfile+'.bak') if File.exist?(pfile)
143
- passphrase = _create_passphrase(pfile)
144
- else
145
- if File.exist?(pfile) then
146
- File.open(pfile,'r'){|fh| passphrase = fh.read }
147
- else
148
- passphrase = _create_passphrase(pfile)
149
- end
150
- end
151
-
152
- return passphrase
153
- end
154
-
155
- def has_datafile?
156
- Find.find(USER_CONF_DIR){|fn|
157
- Find.prune if !(fn==USER_CONF_DIR) && File.directory?(fn)
158
- if fn =~/[0123456789abcdef]{32}\.dat$/ then
159
- return true
160
- end
161
- }
162
- return false
163
- end
164
-
165
- def initialize
166
- @updated = false # only saves data if data updated
167
- @editing = nil # when editor window is up, this is set.
168
- @verified = Time.now.to_i
169
-
170
- @pwd = get_salt || exit
171
- @pph = get_passphrase
172
- @passwords = PasswordsData.new(@pwd+@pph)
173
- # Password file exist?
174
- if @passwords.online? || @passwords.exist? # then
175
- # Yes, load passwords file.
176
- @passwords.load
177
- else
178
- # No, check if there is a file....
179
- if has_datafile? # then
180
- # Yes, it's got a datafile. Ask for password again.
181
- while !@passwords.exist? do
182
- @pwd = get_salt('Try again!') || exit
183
- @passwords = PasswordsData.new(@pwd+@pph)
184
- end
185
- @passwords.load
186
- else
187
- # Else, must be a new intall.
188
- pwd = @pwd
189
- @pwd = get_salt('Verify New Password') || exit
190
- while !(pwd == @pwd) do
191
- pwd = get_salt('Try again!') || exit
192
- @pwd = get_salt('Verify New Password') || exit
193
- end
194
- end
195
- end
196
- # Off to the races...
197
- end
198
-
199
- def verify_user
200
- now = Time.now.to_i
201
- if now - @verified > VERIFIED_EXPIRED then
202
- pwd0 = get_salt('Current Password')
203
- return false if !pwd0
204
- tries = 1
205
- while !(pwd0==@pwd) do
206
- tries += 1
207
- pwd0 = get_salt('CURRENT PASSWORD???')
208
- return false if !pwd0 || tries > 2
209
- end
210
- end
211
- @verified = now
212
- return true
213
- end
214
-
215
- def edit(combo_box, index)
216
- begin
217
- window = @editing
218
- window.signal_connect('delete_event') { @editing = nil }
219
- old_list = @passwords.accounts # dup not necessary
220
-
221
- vbox = Gtk::VBox.new
222
- window.add(vbox)
223
-
224
- pwdlength = Gtk::SpinButton.new(MIN_PASSWORD_LENGTH, MAX_PASSWORD_LENGTH, 1)
225
- pwdlength.value = DEFAULT_PASSWORD_LENGTH
226
- pwdlength.width_request = SPIN_BUTTON_LENGTH
227
-
228
- widget = {}
229
- EDITOR_LABELS.each {|s|
230
- hbox = Gtk::HBox.new
231
- label = Gtk::Label.new(TEXT[s]+':')
232
- label.modify_font(FONT)
233
- label.width_request = LABEL_WIDTH
234
- label.justify = Gtk::JUSTIFY_RIGHT
235
- label.wrap = true
236
- widget[s] = (s==:account)? Gtk::ComboBoxEntry.new : Gtk::Entry.new
237
- widget[s].width_request = ENTRY_WIDTH - ((s == :password)? SPIN_BUTTON_LENGTH+2*PAD: 0)
238
- widget[s].modify_font(FONT)
239
- hbox.pack_start(label, false, false, PAD)
240
- hbox.pack_end(pwdlength, false, false, PAD) if s == :password
241
- hbox.pack_end(widget[s], false, false, PAD)
242
- vbox.pack_start(hbox, false, false, PAD)
243
- }
244
-
245
- EDITOR_BUTTONS.each{|row|
246
- hbox = Gtk::HBox.new
247
- row.each {|s|
248
- widget[s] = Gtk::Button.new(TEXT[s])
249
- widget[s].child.modify_font(FONT)
250
- (s==:cancel || s==:save || s==:update)?
251
- hbox.pack_end(widget[s], false, false, PAD) :
252
- hbox.pack_start(widget[s], false, false, PAD)
253
- }
254
- vbox.pack_start(hbox, false, false, PAD)
255
- }
256
-
257
- # Account
258
- @passwords.accounts.each { |account|
259
- widget[:account].append_text( account )
260
- }
261
- widget[:account].active = index if index
262
- account_changed = proc {
263
- account = (widget[:account].active_text)? widget[:account].active_text.strip: ''
264
- if account.length > 0 then
265
- widget[:password].text = ''
266
- if @passwords.include?(account) then
267
- widget[:url].text = @passwords.url_of(account)
268
- widget[:note].text = @passwords.note_of(account)
269
- widget[:username].text = @passwords.username_of(account)
270
- else
271
- widget[:url].text = ''
272
- widget[:note].text = ''
273
- widget[:username].text = ''
274
- end
275
- end
276
- }
277
- account_changed.call
278
- widget[:account].signal_connect('changed'){
279
- account_changed.call
280
- }
281
-
282
- # New Password
283
- widget[:password].visibility = false
284
-
285
- # Update
286
- widget[:update].signal_connect('clicked'){
287
- url = widget[:url].text.strip
288
- if url.length == 0 || url =~ URL_PATTERN then
289
- account = (widget[:account].active_text)? widget[:account].active_text.strip: ''
290
- if account.length > 0 then
291
- @updated = true if !@updated
292
- if !@passwords.include?(account) then
293
- @passwords.add(account)
294
- i = @passwords.accounts.index(account)
295
- widget[:account].insert_text(i,account)
296
- end
297
- @passwords.url_of(account, url)
298
- @passwords.note_of(account, widget[:note].text.strip)
299
- @passwords.username_of(account, widget[:username].text.strip)
300
- password = widget[:password].text.strip
301
- if password.length > 0 then
302
- @passwords.password_of(account, password) if !@passwords.verify?(account, password)
303
- widget[:password].text = ''
304
- end
305
- end
306
- else
307
- quick_message('Need url like http://www.site.com/page.html', window)
308
- end
309
- }
310
-
311
- # Random
312
- widget[:random].signal_connect('clicked'){
313
- suggestion = ''
314
- pwdlength.value.to_i.times do
315
- suggestion += (rand(94)+33).chr
316
- end
317
- widget[:password].text = suggestion
318
- }
319
- # Alpha-Numeric
320
- widget[:alphanum].signal_connect('clicked'){
321
- suggestion = ''
322
- while suggestion.length < pwdlength.value.to_i do
323
- chr = (rand(75)+48).chr
324
- suggestion += chr if chr =~/\w/
325
- end
326
- widget[:password].text = suggestion
327
- }
328
- # Numeric
329
- widget[:num].signal_connect('clicked'){
330
- suggestion = ''
331
- pwdlength.value.to_i.times do
332
- chr = (rand(10)+48).chr
333
- suggestion += chr
334
- end
335
- widget[:password].text = suggestion
336
- }
337
- # Letters
338
- widget[:alpha].signal_connect('clicked'){
339
- suggestion = ''
340
- while suggestion.length < pwdlength.value.to_i do
341
- chr = (rand(58)+65).chr
342
- suggestion += chr if chr =~/[A-Z]/i
343
- end
344
- widget[:password].text = suggestion
345
- }
346
- # Caps
347
- widget[:caps].signal_connect('clicked'){
348
- suggestion = ''
349
- pwdlength.value.to_i.times do
350
- chr = (rand(26)+65).chr
351
- suggestion += chr
352
- end
353
- widget[:password].text = suggestion
354
- }
355
-
356
- # Visibility
357
- widget[:visibility].signal_connect('clicked'){
358
- widget[:password].visibility = !widget[:password].visibility?
359
- }
360
-
361
- # Current
362
- widget[:current].signal_connect('clicked'){
363
- primary = Gtk::Clipboard.get(Gdk::Selection::PRIMARY)
364
- clipboard = Gtk::Clipboard.get(Gdk::Selection::CLIPBOARD)
365
- account = (widget[:account].active_text)? widget[:account].active_text.strip: ''
366
- primary.text = clipboard.text = @passwords.password_of(account)
367
- }
368
-
369
- # Previous
370
- widget[:previous].signal_connect('clicked'){
371
- primary = Gtk::Clipboard.get(Gdk::Selection::PRIMARY)
372
- clipboard = Gtk::Clipboard.get(Gdk::Selection::CLIPBOARD)
373
- account = (widget[:account].active_text)? widget[:account].active_text.strip: ''
374
- primary.text = clipboard.text = @passwords.previous_password_of(account)
375
- }
376
-
377
- # Change Password
378
- widget[:cpwd].signal_connect('clicked'){
379
- if verify_user then
380
- if pwd1 = get_salt('New Password') then
381
- if pwd2 = get_salt('Verify') then
382
- while !(pwd1==pwd2) do
383
- pwd1 = get_salt('Try again!')
384
- return if !pwd1
385
- pwd2 = get_salt('Verify')
386
- return if !pwd2
387
- end
388
- @pwd = pwd1
389
- @passwords.save(@pwd+@pph)
390
- end
391
- end
392
- else
393
- quit_windows
394
- end
395
- }
396
-
397
- # Change Passphrase
398
- widget[:cpph].signal_connect('clicked'){
399
- if verify_user then
400
- @pph = get_passphrase(true) # mv old passphrase? true
401
- @passwords.save(@pwd+@pph)
402
- quick_message('Passphrase Changed.', window)
403
- else
404
- quit_windows
405
- end
406
- }
407
-
408
- # Save
409
- widget[:save].signal_connect('clicked'){
410
- window.hide
411
- if @updated then
412
- if verify_user then
413
- @passwords.save
414
- @updated = false
415
- new_list = @passwords.accounts # dup not needed
416
- new_list.each {|account|
417
- if !old_list.include?(account) then
418
- i = new_list.index(account)
419
- old_list.insert(i,account)
420
- combo_box.insert_text(i,account)
421
- end
422
- }
423
- old_list.each {|account|
424
- if !new_list.include?(account) then
425
- i = old_list.index(account)
426
- old_list.delete_at(i)
427
- combo_box.remove_text(i)
428
- end
429
- }
430
- if i = new_list.index( widget[:account].active_text ) then
431
- combo_box.active = i
432
- end
433
- else
434
- quit_windows
435
- end
436
- end
437
- window.destroy
438
- @editing = nil
439
- }
440
-
441
- # Delete
442
- widget[:delete].signal_connect('clicked'){
443
- account = (widget[:account].active_text)? widget[:account].active_text.strip: nil
444
- if account then
445
- i = @passwords.accounts.index(account)
446
- if i then
447
- @passwords.delete(account)
448
- widget[:account].remove_text(i)
449
- widget[:account].active = (i > 0)? i - 1: 0
450
- @updated = true
451
- quick_message("#{account} deleted.", window)
452
- end
453
- end
454
- }
455
-
456
- # Cancel
457
- widget[:cancel].signal_connect('clicked'){
458
- window.hide
459
- if @updated then
460
- @passwords.load # revert
461
- @updated = false
462
- end
463
- window.destroy
464
- @editing = nil
465
- }
466
-
467
- window.show_all
468
- rescue Exception
469
- puts_bang!
470
- end
471
- end
472
-
473
- def run
474
- begin
475
- @window = window = Gtk::Window.new
476
- window.signal_connect('delete_event') { quit_windows }
477
-
478
- vbox = Gtk::VBox.new
479
- window.add(vbox)
480
-
481
- combo_box =Gtk::ComboBox.new
482
- combo_box.modify_font(FONT)
483
- vbox.pack_start(combo_box, false, false, PAD)
484
-
485
- button = {}
486
-
487
- BUTTONS.each{ |row|
488
- hbox = Gtk::HBox.new
489
- row.each{|b|
490
- next if b == :edit && @passwords.online?
491
- button[b] = Gtk::Button.new(TEXT[b])
492
- button[b].modify_font(FONT)
493
- button[b].width_request = LABEL_WIDTH
494
- hbox.pack_start(button[b], false, false, PAD)
495
- }
496
- vbox.pack_start(hbox, false, false, PAD)
497
- }
498
-
499
- @passwords.accounts.each { |account|
500
- combo_box.append_text( account )
501
- }
502
- combo_box.active = 0
503
-
504
- if !@passwords.online? then
505
- button[:edit].child.modify_fg(Gtk::STATE_NORMAL, (@passwords.expired?(combo_box.active_text.strip))? RED: BLACK) if combo_box.active_text
506
- combo_box.signal_connect('changed'){
507
- txt = combo_box.active_text
508
- button[:edit].child.modify_fg(Gtk::STATE_NORMAL, (@passwords.expired?(txt.strip))? RED: BLACK) if txt
509
- }
510
- end
511
-
512
- button[:username].signal_connect('clicked'){
513
- primary = Gtk::Clipboard.get(Gdk::Selection::PRIMARY)
514
- clipboard = Gtk::Clipboard.get(Gdk::Selection::CLIPBOARD)
515
- account = (combo_box.active_text)? combo_box.active_text.strip: ''
516
- primary.text = clipboard.text = @passwords.username_of(account)
517
- }
518
- button[:current].signal_connect('clicked'){
519
- primary = Gtk::Clipboard.get(Gdk::Selection::PRIMARY)
520
- clipboard = Gtk::Clipboard.get(Gdk::Selection::CLIPBOARD)
521
- account = (combo_box.active_text)? combo_box.active_text.strip: ''
522
- primary.text = clipboard.text = @passwords.password_of(account)
523
- }
524
- button[:url].signal_connect('clicked'){
525
- account = (combo_box.active_text)? combo_box.active_text.strip: ''
526
- url = @passwords.url_of(account)
527
- if url.length > 0 && url =~ URL_PATTERN then
528
- system("#{BROWSER} '#{url}' > /dev/null 2>&1 &")
529
- end
530
- }
531
- button[:note].signal_connect('clicked'){
532
- account = (combo_box.active_text)? combo_box.active_text.strip: ''
533
- note = @passwords.note_of(account).strip
534
- note = '*** empty note ***' if note.length == 0
535
- quick_message(note,window)
536
- }
537
-
538
- if !@passwords.online? then
539
- button[:edit].signal_connect('clicked'){
540
- if !@editing then
541
- account = (combo_box.active_text)? combo_box.active_text.strip: ''
542
- i = @passwords.accounts.index(account)
543
- @editing = Gtk::Window.new
544
- edit(combo_box,i)
545
- end
546
- }
547
- end
548
-
549
- button[:quit].signal_connect('clicked'){ quit_windows }
550
-
551
- if !@passwords.online? && !@passwords.exist? then
552
- @editing = Gtk::Window.new
553
- edit(combo_box,0)
554
- end
555
-
556
- window.show_all
557
- rescue Exception
558
- puts_bang!
559
- end
560
- end
561
-
562
- def main_quit(icon)
563
- quit_windows
564
- icon.set_visible(false)
565
- icon = nil
566
- Gtk.main_quit
567
- end
568
-
569
- def status_icon
570
- icon = Gtk::StatusIcon.new
571
- icon.set_icon_name(Gtk::Stock::DIALOG_AUTHENTICATION)
572
- icon.tooltip = 'Password Manager'
573
- unlocked = true
574
- icon.signal_connect('activate') {
575
- if @window then
576
- quit_windows
577
- elsif unlocked then
578
- unlocked = false
579
- if verify_user then
580
- menu = Gtk::Menu.new
581
- @passwords.accounts.each {|account|
582
- menuitem = Gtk::MenuItem.new(account)
583
- menuitem.child.modify_fg(Gtk::STATE_NORMAL, RED) if @passwords.expired?(account)
584
- menu.append(menuitem)
585
- menuitem.signal_connect('activate'){|b|
586
- primary = Gtk::Clipboard.get(Gdk::Selection::PRIMARY)
587
- clipboard = Gtk::Clipboard.get(Gdk::Selection::CLIPBOARD)
588
- primary.text = clipboard.text = @passwords.password_of(b.child.text.strip)
589
- }
590
- }
591
- menu.append( Gtk::SeparatorMenuItem.new )
592
-
593
- menuitem = Gtk::MenuItem.new('Quit')
594
- menuitem.signal_connect('activate'){ main_quit(icon) }
595
- menu.append(menuitem)
596
-
597
- Gtk::AboutDialog.set_url_hook{|about,link| system( "#{BROWSER} '#{link}' > /dev/null 2>&1 &" ) }
598
- menuitem = Gtk::MenuItem.new('About')
599
- menuitem.signal_connect('activate'){
600
- Gtk::AboutDialog.show(nil, ABOUT)
601
- }
602
- menu.append(menuitem)
603
-
604
- menuitem = Gtk::MenuItem.new('Run')
605
- menuitem.signal_connect('activate'){
606
- if !@window
607
- run if verify_user
608
- end
609
- }
610
- menu.append(menuitem)
611
-
612
- menu.show_all
613
- menu.popup(nil, nil, 0, 0)
614
- unlocked = true
615
- else
616
- main_quit(icon)
617
- end
618
- end
619
- }
620
- run if !@passwords.online? && !@passwords.exist?
621
- Gtk.main
622
- end
623
- end