gtk2passwordapp 0.0.8 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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