gtk2passwordapp 0.0.1

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.
data/README.txt ADDED
@@ -0,0 +1,8 @@
1
+ Just run:
2
+ gtk2passwordapp
3
+
4
+ For some documentation on usage, see
5
+
6
+ http://ruby-gnome-apps.blogspot.com/search/label/Passwords
7
+
8
+ -Carlos
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env ruby
2
+ # $Date: 2009/02/19 15:50:30 $
3
+ ##########################################################
4
+ require 'lib/global_options_variables'
5
+ GlobalOptionsVariables.set('0.0.1',
6
+ <<EOT
7
+ Usage: #{$0.sub(/^.*\//,'')} [options]
8
+
9
+ Options:
10
+ -h, --help print this help text and exit
11
+ -v, --version print program version and exit
12
+ -t, --test test
13
+ -T, --trace trace
14
+ EOT
15
+ )
16
+ require 'lib/setup_user_space'
17
+ UserSpace.setup
18
+ UserSpace.mkdir('/gifs')
19
+ UserSpace.copy('/gifs/logo.gif')
20
+ require USER_CONF_DIR+CONF_FILE
21
+ ##########################################################
22
+ require 'lib/gtk2passwordapp'
23
+
24
+ lock = USER_CONF_DIR+'/lock'
25
+ if File.exist?(lock) then
26
+ $stderr.puts "process already running?"
27
+ # user should then notice it's already running, but
28
+ # let's remove the lock in case it's not and user
29
+ # tries again....
30
+ File.unlink(lock)
31
+ else
32
+ begin
33
+ File.open(lock,'w'){|fh| fh.puts $$ }
34
+ gpa = Gtk2PasswordApp.new
35
+ gpa.status_icon
36
+ rescue Exception
37
+ puts_bang!
38
+ ensure
39
+ File.unlink(lock) if File.exist?(lock)
40
+ end
41
+ end
data/gifs/logo.gif ADDED
Binary file
@@ -0,0 +1,49 @@
1
+ #$Date: 2009/02/19 17:56:37 $
2
+ # Note: you'll see in ~/.gtk2passwordapp a file called passphrase.txt.
3
+ # Do not edit or delete passphrase, or you'll loose your passwords data.
4
+ module Configuration
5
+ ENTRY_WIDTH = 275
6
+ LABEL_WIDTH = 75
7
+ SPIN_BUTTON_LENGTH = 50
8
+ PAD = 2 # cell padding
9
+
10
+ FONT_NAME = 'Arial 10'
11
+
12
+ MAX_PASSWORD_LENGTH = 20
13
+ DEFAULT_PASSWORD_LENGTH = 7
14
+ MIN_PASSWORD_LENGTH = 3
15
+
16
+ VERIFIED_EXPIRED = 60*15 # 15 minutes
17
+ PASSWORD_EXPIRED = 60*60*24*30*3 # 3 months
18
+
19
+ URL_PATTERN = Regexp.new('^https?:\/\/[^\s\']+$')
20
+
21
+ # Here you can set your browser preferences for remote control.
22
+ # As is, the first browser found will be used, like
23
+ # BROWSER = '/usr/bin/epiphany -n'
24
+ [
25
+ # browser url remote option code
26
+ [ 'browser', '--url' ],
27
+ [ 'fennec', '' ],
28
+ [ 'epiphany', '-n' ],
29
+ [ 'firefox', '-new-tab' ],
30
+ [ 'opera', '' ]
31
+ ].each {|try|
32
+ browser = `which #{try[0]} 2> /dev/null`.strip
33
+ next if browser == ''
34
+ BROWSER = "#{browser} #{try[1]}"
35
+ break
36
+ }
37
+ $stderr.puts "Browser remote command: #{BROWSER}" if $trace
38
+
39
+ LOGO_IMAGE = USER_CONF_DIR+'/gifs/logo.gif'
40
+ end
41
+
42
+ def puts_bang!(h=nil, t=nil)
43
+ if $trace then
44
+ $stderr.puts h if h
45
+ $stderr.puts $!
46
+ $stderr.puts $!.backtrace
47
+ $stderr.puts t if t
48
+ end
49
+ end
@@ -0,0 +1,40 @@
1
+ # $Date: 2009/02/14 16:30:05 $
2
+ GEM_LIB_DIR = File.expand_path( File.dirname(__FILE__) )
3
+ GEM = GEM_LIB_DIR.split(/[\/\\]/)[-2]
4
+ USER_CONF_DIR = ENV['HOME'] + "/.#{GEM}"
5
+ GEM_ROOT_DIR = GEM_LIB_DIR.sub(/\/[^\/\\]+$/,'')
6
+ CONF_FILE = '/configuration.rb'
7
+ module GlobalOptionsVariables
8
+ def self.set( version, help=nil, additional_options={} )
9
+ raise "globals have been set elsewhere :-??" if $version || $quiet || $test || $trace
10
+ $version = version
11
+
12
+ options_map = {
13
+ '--help' => 'h',
14
+ '--quiet' => 'q',
15
+ '--test' => 't',
16
+ '--trace' => 'T',
17
+ }
18
+
19
+ options = ''
20
+ opt = nil
21
+ while ARGV[0]=~/^\-/ do
22
+ opt = ARGV.shift
23
+ options += (additional_options[opt])? additional_options[opt]: (options_map[opt])? options_map[opt]: opt
24
+ end
25
+
26
+ if help && options=~/h/ then
27
+ puts help if help
28
+ exit
29
+ elsif options=~/v/ then
30
+ puts $version
31
+ exit
32
+ end
33
+
34
+ $quiet = (options=~/q/)? true: false
35
+ $test = (options=~/t/)? true: false
36
+ $trace = (options=~/t/i)? true: false
37
+
38
+ return options
39
+ end
40
+ end
@@ -0,0 +1,597 @@
1
+ # $Date: 2009/02/19 20:50:22 $
2
+ require 'lib/passwords_data'
3
+ require 'gtk2'
4
+ require 'find'
5
+
6
+ class Gtk2PasswordApp
7
+ include Configuration
8
+
9
+ ABOUT = {
10
+ 'authors' => ['carlosjhr64@gmail.com'],
11
+ 'comments' => "Ruby-Gtk2 Password Manager.",
12
+ 'version' => $version,
13
+ 'website' => 'http://ruby-gnome-apps.blogspot.com/search/label/Passwords',
14
+ 'website-label' => 'Ruby Gnome Password Manager',
15
+ 'license' => 'GPL',
16
+ 'copyright' => '$Date: 2009/02/19 20:50:22 $'.gsub(/\s*\$\s*/,''),
17
+ 'logo' => Gdk::Pixbuf.new(LOGO_IMAGE),
18
+ }
19
+
20
+ BUTTONS = [[ :username, :current, :url, ],[ :note, :edit, :quit, ],]
21
+
22
+ EDITOR_LABELS = [
23
+ :account,
24
+ :url,
25
+ :note,
26
+ :username,
27
+ :password,
28
+ ]
29
+
30
+
31
+ EDITOR_BUTTONS = [
32
+ [ :random, :alphanum, :num, :alpha, :caps, ],
33
+ [ :visibility, :current, :previous, :cancel, :save, :update, ],
34
+ [ :delete, :cpwd, :cpph ],
35
+ ]
36
+
37
+ TEXT = {
38
+ # Labels
39
+ :account => 'Account',
40
+ :note => 'Note',
41
+ :password => 'New',
42
+ # Buttons
43
+ :username => 'Username',
44
+ :current => 'Current',
45
+ :url => 'Url',
46
+ :note => 'Note',
47
+ :edit => 'Edit',
48
+ :update => 'Update',
49
+ :visibility => 'Visible',
50
+ :alphanum => 'Alpha-Numeric',
51
+ :num => 'Numeric',
52
+ :alpha => 'Letters',
53
+ :caps => 'All-Caps',
54
+ :random => 'Random',
55
+ :previous => 'Previous',
56
+ :quit => 'Quit',
57
+ :cancel => 'Cancel',
58
+ :save => 'Save',
59
+ :cpwd => 'Data File Password',
60
+ :cpph => 'Data File Passphrase',
61
+ :delete => 'Delete Account',
62
+ }
63
+
64
+ FONT = Pango::FontDescription.new(FONT_NAME)
65
+ RED = Gdk::Color.parse("#A00000")
66
+ BLACK = Gdk::Color.parse("#000000")
67
+
68
+ def quit_windows
69
+ if @editing
70
+ @editing.hide
71
+ @editing.destroy
72
+ @editing = nil
73
+ end
74
+ if @window
75
+ @window.hide
76
+ @window.destroy
77
+ @window = nil
78
+ end
79
+ end
80
+
81
+ def quick_message(message, window, title='Note', font=FONT)
82
+ # Create the dialog
83
+ dialog = Gtk::Dialog.new(
84
+ title,
85
+ window, Gtk::Dialog::DESTROY_WITH_PARENT,
86
+ [ Gtk::Stock::OK, Gtk::Dialog::RESPONSE_NONE ])
87
+
88
+ # Ensure that the dialog box is destroyed when the user responds.
89
+ dialog.signal_connect('response') { dialog.destroy }
90
+
91
+ # Add the message in a label, and show everything we've added to the dialog.
92
+ label = Gtk::Label.new(message)
93
+ label.wrap = true
94
+ label.modify_font(font) if font
95
+ dialog.vbox.add(label)
96
+ dialog.show_all
97
+ end
98
+
99
+ def get_salt(title='Short Password')
100
+ dialog = Gtk::Dialog.new(
101
+ title,
102
+ nil, nil,
103
+ [ Gtk::Stock::QUIT, 0 ],
104
+ [ Gtk::Stock::OK, 1 ])
105
+
106
+ label = Gtk::Label.new(title)
107
+ label.justify = Gtk::JUSTIFY_LEFT
108
+ label.wrap = true
109
+ label.modify_font(FONT)
110
+ dialog.vbox.add(label)
111
+ entry = Gtk::Entry.new
112
+ entry.visibility = false
113
+ entry.modify_font(FONT)
114
+ dialog.vbox.add(entry)
115
+ dialog.show_all
116
+
117
+ entry.signal_connect('activate'){
118
+ dialog.response(1)
119
+ }
120
+
121
+ ret = nil
122
+ dialog.run {|response|
123
+ ret = entry.text.strip if response == 1
124
+ }
125
+ dialog.destroy
126
+
127
+ return ret
128
+ end
129
+
130
+ def _create_passphrase(pfile)
131
+ passphrase = ''
132
+
133
+ 56.times do
134
+ passphrase += (rand(94)+33).chr
135
+ end
136
+ File.open(pfile,'w'){|fh| fh.write passphrase }
137
+ File.chmod(0600, pfile)
138
+
139
+ return passphrase
140
+ end
141
+
142
+ def get_passphrase(mv=false)
143
+ passphrase = ''
144
+
145
+ pfile = USER_CONF_DIR+'/passphrase.txt'
146
+ if mv then
147
+ File.rename(pfile, pfile+'.bak') if File.exist?(pfile)
148
+ passphrase = _create_passphrase(pfile)
149
+ else
150
+ if File.exist?(pfile) then
151
+ File.open(pfile,'r'){|fh| passphrase = fh.read }
152
+ else
153
+ passphrase = _create_passphrase(pfile)
154
+ end
155
+ end
156
+
157
+ return passphrase
158
+ end
159
+
160
+ def has_datafile?
161
+ Find.find(USER_CONF_DIR){|fn|
162
+ Find.prune if !(fn==USER_CONF_DIR) && File.directory?(fn)
163
+ if fn =~/[0123456789abcdef]{32}\.dat$/ then
164
+ return true
165
+ end
166
+ }
167
+ return false
168
+ end
169
+
170
+ def initialize
171
+ @updated = false # only saves data if data updated
172
+ @editing = nil # when editor window is up, this is set.
173
+ @verified = Time.now.to_i
174
+
175
+ @pwd = get_salt || exit
176
+ @pph = get_passphrase
177
+ @passwords = PasswordsData.new(@pwd+@pph)
178
+ # Password file exist?
179
+ if @passwords.exist? # then
180
+ # Yes, load passwords file.
181
+ @passwords.load
182
+ else
183
+ # No, check if there is a file....
184
+ if has_datafile? # then
185
+ # Yes, it's got a datafile. Ask for password again.
186
+ while !@passwords.exist? do
187
+ @pwd = get_salt('Try again!') || exit
188
+ @passwords = PasswordsData.new(@pwd+@pph)
189
+ end
190
+ @passwords.load
191
+ else
192
+ # Else, must be a new intall.
193
+ pwd = @pwd
194
+ @pwd = get_salt('Verify New Password') || exit
195
+ while !(pwd == @pwd) do
196
+ pwd = get_salt('Try again!') || exit
197
+ @pwd = get_salt('Verify New Password') || exit
198
+ end
199
+ end
200
+ end
201
+ # Off to the races...
202
+ end
203
+
204
+ def verify_user
205
+ now = Time.now.to_i
206
+ if now - @verified > VERIFIED_EXPIRED then
207
+ pwd0 = get_salt('Current Password')
208
+ return false if !pwd0
209
+ tries = 1
210
+ while !(pwd0==@pwd) do
211
+ tries += 1
212
+ pwd0 = get_salt('CURRENT PASSWORD???')
213
+ return false if !pwd0 || tries > 2
214
+ end
215
+ end
216
+ @verified = now
217
+ return true
218
+ end
219
+
220
+ def edit(combo_box, index)
221
+ begin
222
+ window = @editing
223
+ window.signal_connect('delete_event') { @editing = nil }
224
+ old_list = @passwords.accounts # dup not necessary
225
+
226
+ vbox = Gtk::VBox.new
227
+ window.add(vbox)
228
+
229
+ pwdlength = Gtk::SpinButton.new(MIN_PASSWORD_LENGTH, MAX_PASSWORD_LENGTH, 1)
230
+ pwdlength.value = DEFAULT_PASSWORD_LENGTH
231
+ pwdlength.width_request = SPIN_BUTTON_LENGTH
232
+
233
+ widget = {}
234
+ EDITOR_LABELS.each {|s|
235
+ hbox = Gtk::HBox.new
236
+ label = Gtk::Label.new(TEXT[s]+':')
237
+ label.modify_font(FONT)
238
+ label.width_request = LABEL_WIDTH
239
+ label.justify = Gtk::JUSTIFY_RIGHT
240
+ label.wrap = true
241
+ widget[s] = (s==:account)? Gtk::ComboBoxEntry.new : Gtk::Entry.new
242
+ widget[s].width_request = ENTRY_WIDTH - ((s == :password)? SPIN_BUTTON_LENGTH+2*PAD: 0)
243
+ widget[s].modify_font(FONT)
244
+ hbox.pack_start(label, false, false, PAD)
245
+ hbox.pack_end(pwdlength, false, false, PAD) if s == :password
246
+ hbox.pack_end(widget[s], false, false, PAD)
247
+ vbox.pack_start(hbox, false, false, PAD)
248
+ }
249
+
250
+ EDITOR_BUTTONS.each{|row|
251
+ hbox = Gtk::HBox.new
252
+ row.each {|s|
253
+ widget[s] = Gtk::Button.new(TEXT[s])
254
+ widget[s].child.modify_font(FONT)
255
+ (s==:cancel || s==:save || s==:update)?
256
+ hbox.pack_end(widget[s], false, false, PAD) :
257
+ hbox.pack_start(widget[s], false, false, PAD)
258
+ }
259
+ vbox.pack_start(hbox, false, false, PAD)
260
+ }
261
+
262
+ # Account
263
+ @passwords.accounts.each { |account|
264
+ widget[:account].append_text( account )
265
+ }
266
+ widget[:account].active = index if index
267
+ account_changed = proc {
268
+ account = (widget[:account].active_text)? widget[:account].active_text.strip: ''
269
+ if account.length > 0 then
270
+ widget[:password].text = ''
271
+ if @passwords.include?(account) then
272
+ widget[:url].text = @passwords.url_of(account)
273
+ widget[:note].text = @passwords.note_of(account)
274
+ widget[:username].text = @passwords.username_of(account)
275
+ else
276
+ widget[:url].text = ''
277
+ widget[:note].text = ''
278
+ widget[:username].text = ''
279
+ end
280
+ end
281
+ }
282
+ account_changed.call
283
+ widget[:account].signal_connect('changed'){
284
+ account_changed.call
285
+ }
286
+
287
+ # New Password
288
+ widget[:password].visibility = false
289
+
290
+ # Update
291
+ widget[:update].signal_connect('clicked'){
292
+ url = widget[:url].text.strip
293
+ if url.length == 0 || url =~ URL_PATTERN then
294
+ account = (widget[:account].active_text)? widget[:account].active_text.strip: ''
295
+ if account.length > 0 then
296
+ @updated = true if !@updated
297
+ if !@passwords.include?(account) then
298
+ @passwords.add(account)
299
+ i = @passwords.accounts.index(account)
300
+ widget[:account].insert_text(i,account)
301
+ end
302
+ @passwords.url_of(account, url)
303
+ @passwords.note_of(account, widget[:note].text.strip)
304
+ @passwords.username_of(account, widget[:username].text.strip)
305
+ password = widget[:password].text.strip
306
+ if password.length > 0 then
307
+ @passwords.password_of(account, password) if !@passwords.verify?(account, password)
308
+ widget[:password].text = ''
309
+ end
310
+ end
311
+ else
312
+ quick_message('Need url like http://www.site.com/page.html', window)
313
+ end
314
+ }
315
+
316
+ # Random
317
+ widget[:random].signal_connect('clicked'){
318
+ suggestion = ''
319
+ pwdlength.value.to_i.times do
320
+ suggestion += (rand(94)+33).chr
321
+ end
322
+ widget[:password].text = suggestion
323
+ }
324
+ # Alpha-Numeric
325
+ widget[:alphanum].signal_connect('clicked'){
326
+ suggestion = ''
327
+ while suggestion.length < pwdlength.value.to_i do
328
+ chr = (rand(75)+48).chr
329
+ suggestion += chr if chr =~/\w/
330
+ end
331
+ widget[:password].text = suggestion
332
+ }
333
+ # Numeric
334
+ widget[:num].signal_connect('clicked'){
335
+ suggestion = ''
336
+ pwdlength.value.to_i.times do
337
+ chr = (rand(10)+48).chr
338
+ suggestion += chr
339
+ end
340
+ widget[:password].text = suggestion
341
+ }
342
+ # Letters
343
+ widget[:alpha].signal_connect('clicked'){
344
+ suggestion = ''
345
+ while suggestion.length < pwdlength.value.to_i do
346
+ chr = (rand(58)+65).chr
347
+ suggestion += chr if chr =~/[A-Z]/i
348
+ end
349
+ widget[:password].text = suggestion
350
+ }
351
+ # Caps
352
+ widget[:caps].signal_connect('clicked'){
353
+ suggestion = ''
354
+ pwdlength.value.to_i.times do
355
+ chr = (rand(26)+65).chr
356
+ suggestion += chr
357
+ end
358
+ widget[:password].text = suggestion
359
+ }
360
+
361
+ # Visibility
362
+ widget[:visibility].signal_connect('clicked'){
363
+ widget[:password].visibility = !widget[:password].visibility?
364
+ }
365
+
366
+ # Current
367
+ widget[:current].signal_connect('clicked'){
368
+ primary = Gtk::Clipboard.get(Gdk::Selection::PRIMARY)
369
+ clipboard = Gtk::Clipboard.get(Gdk::Selection::CLIPBOARD)
370
+ account = (widget[:account].active_text)? widget[:account].active_text.strip: ''
371
+ primary.text = clipboard.text = @passwords.password_of(account)
372
+ }
373
+
374
+ # Previous
375
+ widget[:previous].signal_connect('clicked'){
376
+ primary = Gtk::Clipboard.get(Gdk::Selection::PRIMARY)
377
+ clipboard = Gtk::Clipboard.get(Gdk::Selection::CLIPBOARD)
378
+ account = (widget[:account].active_text)? widget[:account].active_text.strip: ''
379
+ primary.text = clipboard.text = @passwords.previous_password_of(account)
380
+ }
381
+
382
+ # Change Password
383
+ widget[:cpwd].signal_connect('clicked'){
384
+ if verify_user then
385
+ pwd1 = get_salt('New Password') || return
386
+ pwd2 = get_salt('Verify') || return
387
+ while !(pwd1==pwd2) do
388
+ pwd1 = get_salt('Try again!') || return
389
+ pwd2 = get_salt('Verify') || return
390
+ end
391
+ @pwd = pwd1
392
+ @passwords.save(@pwd+@pph)
393
+ else
394
+ quit_windows
395
+ end
396
+ }
397
+
398
+ # Change Passphrase
399
+ widget[:cpph].signal_connect('clicked'){
400
+ if verify_user then
401
+ @pph = get_passphrase(true) # mv old passphrase? true
402
+ @passwords.save(@pwd+@pph)
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
+ else
431
+ quit_windows
432
+ end
433
+ end
434
+ window.destroy
435
+ @editing = nil
436
+ }
437
+
438
+ # Delete
439
+ widget[:delete].signal_connect('clicked'){
440
+ account = (widget[:account].active_text)? widget[:account].active_text.strip: ''
441
+ i = @passwords.accounts.index(account)
442
+ @passwords.delete(account)
443
+ widget[:account].remove_text(i)
444
+ @updated = true
445
+ }
446
+
447
+ # Cancel
448
+ widget[:cancel].signal_connect('clicked'){
449
+ window.hide
450
+ if @updated then
451
+ @passwords.load # revert
452
+ @updated = false
453
+ end
454
+ window.destroy
455
+ @editing = nil
456
+ }
457
+
458
+ window.show_all
459
+ rescue Exception
460
+ puts_bang!
461
+ end
462
+ end
463
+
464
+ def run
465
+ begin
466
+ @window = window = Gtk::Window.new
467
+ window.signal_connect('delete_event') { quit_windows }
468
+
469
+ vbox = Gtk::VBox.new
470
+ window.add(vbox)
471
+
472
+ combo_box =Gtk::ComboBox.new
473
+ combo_box.modify_font(FONT)
474
+ vbox.pack_start(combo_box, false, false, PAD)
475
+
476
+ button = {}
477
+
478
+ BUTTONS.each{ |row|
479
+ hbox = Gtk::HBox.new
480
+ row.each{|b|
481
+ button[b] = Gtk::Button.new(TEXT[b])
482
+ button[b].modify_font(FONT)
483
+ button[b].width_request = LABEL_WIDTH
484
+ hbox.pack_start(button[b], false, false, PAD)
485
+ }
486
+ vbox.pack_start(hbox, false, false, PAD)
487
+ }
488
+
489
+ @passwords.accounts.each { |account|
490
+ combo_box.append_text( account )
491
+ }
492
+ combo_box.active = 0
493
+ button[:edit].child.modify_fg(Gtk::STATE_NORMAL, (@passwords.expired?(combo_box.active_text.strip))? RED: BLACK) if combo_box.active_text
494
+ combo_box.signal_connect('changed'){
495
+ button[:edit].child.modify_fg(Gtk::STATE_NORMAL, (@passwords.expired?(combo_box.active_text.strip))? RED: BLACK)
496
+ }
497
+ button[:username].signal_connect('clicked'){
498
+ primary = Gtk::Clipboard.get(Gdk::Selection::PRIMARY)
499
+ clipboard = Gtk::Clipboard.get(Gdk::Selection::CLIPBOARD)
500
+ account = (combo_box.active_text)? combo_box.active_text.strip: ''
501
+ primary.text = clipboard.text = @passwords.username_of(account)
502
+ }
503
+ button[:current].signal_connect('clicked'){
504
+ primary = Gtk::Clipboard.get(Gdk::Selection::PRIMARY)
505
+ clipboard = Gtk::Clipboard.get(Gdk::Selection::CLIPBOARD)
506
+ account = (combo_box.active_text)? combo_box.active_text.strip: ''
507
+ primary.text = clipboard.text = @passwords.password_of(account)
508
+ }
509
+ button[:url].signal_connect('clicked'){
510
+ account = (combo_box.active_text)? combo_box.active_text.strip: ''
511
+ url = @passwords.url_of(account)
512
+ if url.length > 0 && url =~ URL_PATTERN then
513
+ system("#{BROWSER} '#{url}' > /dev/null 2>&1 &")
514
+ end
515
+ }
516
+ button[:note].signal_connect('clicked'){
517
+ account = (combo_box.active_text)? combo_box.active_text.strip: ''
518
+ note = @passwords.note_of(account).strip
519
+ note = '*** empty note ***' if note.length == 0
520
+ quick_message(note,window)
521
+ }
522
+ button[:edit].signal_connect('clicked'){
523
+ if !@editing then
524
+ account = (combo_box.active_text)? combo_box.active_text.strip: ''
525
+ i = @passwords.accounts.index(account)
526
+ @editing = Gtk::Window.new
527
+ edit(combo_box,i)
528
+ end
529
+ }
530
+ button[:quit].signal_connect('clicked'){ quit_windows }
531
+
532
+ if !@passwords.exist? then
533
+ @editing = Gtk::Window.new
534
+ edit(combo_box,0)
535
+ end
536
+
537
+ window.show_all
538
+ rescue Exception
539
+ puts_bang!
540
+ end
541
+ end
542
+
543
+ def status_icon
544
+ icon = Gtk::StatusIcon.new
545
+ icon.set_icon_name(Gtk::Stock::DIALOG_AUTHENTICATION)
546
+ icon.tooltip = 'Password Manager'
547
+ icon.signal_connect('activate') {
548
+ if @window then
549
+ quit_windows
550
+ else
551
+ menu = Gtk::Menu.new
552
+ @passwords.accounts.each {|account|
553
+ menuitem = Gtk::MenuItem.new(account)
554
+ menuitem.child.modify_fg(Gtk::STATE_NORMAL, RED) if @passwords.expired?(account)
555
+ menu.append(menuitem)
556
+ menuitem.signal_connect('activate'){|b|
557
+ primary = Gtk::Clipboard.get(Gdk::Selection::PRIMARY)
558
+ clipboard = Gtk::Clipboard.get(Gdk::Selection::CLIPBOARD)
559
+ primary.text = clipboard.text = @passwords.password_of(b.child.text.strip)
560
+ }
561
+ }
562
+
563
+ menu.append( Gtk::SeparatorMenuItem.new )
564
+
565
+ menuitem = Gtk::MenuItem.new('Quit')
566
+ menuitem.signal_connect('activate'){
567
+ quit_windows
568
+ icon.set_visible(false)
569
+ icon = nil
570
+ Gtk.main_quit
571
+ }
572
+ menu.append(menuitem)
573
+
574
+ Gtk::AboutDialog.set_url_hook{|about,link| system( "#{BROWSER} '#{link}' > /dev/null 2>&1 &" ) }
575
+ menuitem = Gtk::MenuItem.new('About')
576
+ menuitem.signal_connect('activate'){
577
+ Gtk::AboutDialog.show(nil, ABOUT)
578
+ }
579
+ menu.append(menuitem)
580
+
581
+ menuitem = Gtk::MenuItem.new('Run')
582
+ menuitem.signal_connect('activate'){
583
+ if !@window
584
+ verify_user
585
+ run
586
+ end
587
+ }
588
+ menu.append(menuitem)
589
+
590
+ menu.show_all
591
+ menu.popup(nil, nil, 0, 0)
592
+ end
593
+ }
594
+ run if !@passwords.exist?
595
+ Gtk.main
596
+ end
597
+ end
data/lib/iocrypt.rb ADDED
@@ -0,0 +1,30 @@
1
+ # $Date: 2009/02/18 00:41:54 $
2
+ require 'yaml'
3
+ require 'rubygems'
4
+ require 'crypt/blowfish'
5
+
6
+ class IOCrypt
7
+ def initialize(passphrase)
8
+ @blowfish = Crypt::Blowfish.new(passphrase[0..55])
9
+ end
10
+
11
+ def load(dumpfile)
12
+ data = nil
13
+
14
+ File.open(dumpfile,'r'){|fh|
15
+ data = YAML.load( @blowfish.decrypt_string( fh.read ) )
16
+ }
17
+
18
+ return data
19
+ end
20
+
21
+ def dump(dumpfile, data)
22
+ count = nil
23
+
24
+ File.open(dumpfile,'w') do |fh|
25
+ count = fh.write( @blowfish.encrypt_string( YAML.dump( data ) ) )
26
+ end
27
+
28
+ return count
29
+ end
30
+ end
@@ -0,0 +1,110 @@
1
+ # $Date: 2009/02/18 16:07:01 $
2
+ require 'lib/iocrypt'
3
+ require 'digest/md5'
4
+
5
+ class PasswordsData
6
+ include Configuration
7
+ attr_accessor :account
8
+ attr_reader :data
9
+
10
+ PASSWORD = 0
11
+ PREVIOUS = 1
12
+ NOTE = 2
13
+ USERNAME = 3
14
+ URL = 4
15
+ LAST_UPDATE = 5
16
+
17
+ def _reset(passphrase)
18
+ raise "Need a good passphrase" if !passphrase || passphrase.length < 7
19
+ @passphrase = passphrase[0..55]
20
+ @dumpfile = USER_CONF_DIR + '/' + Digest::MD5.hexdigest(@passphrase) + '.dat'
21
+ end
22
+
23
+ def initialize(passphrase)
24
+ _reset(passphrase)
25
+ @data = {}
26
+ end
27
+
28
+ def exist?
29
+ File.exist?(@dumpfile)
30
+ end
31
+
32
+ def load(passphrase = nil)
33
+ _reset(passphrase) if passphrase
34
+ raise "Wrong passphrase" if !exist?
35
+ iocrypt = IOCrypt.new(@passphrase)
36
+ @data = iocrypt.load(@dumpfile)
37
+ end
38
+
39
+ def save(passphrase = nil)
40
+ # just in case, keep a backup
41
+ File.rename(@dumpfile, @dumpfile+'.bak') if File.exist?(@dumpfile)
42
+ _reset(passphrase) if passphrase
43
+ iocrypt = IOCrypt.new(@passphrase)
44
+ iocrypt.dump(@dumpfile, @data)
45
+ File.chmod(0600, @dumpfile)
46
+ end
47
+
48
+ def add(account)
49
+ raise "Pre-existing" if @data[account]
50
+ raise "Can't have nil account" if !account
51
+ @data[account] = ['','','','','']
52
+ end
53
+
54
+ def accounts
55
+ @data.keys.sort
56
+ end
57
+
58
+ def include?(account)
59
+ return (@data[account])? true: false
60
+ end
61
+
62
+ def verify?(account,password)
63
+ return @data[account][PASSWORD] == password
64
+ end
65
+
66
+ def delete(account)
67
+ raise "#{account} not found" if !@data[account]
68
+ @data.delete(account)
69
+ end
70
+
71
+ def url_of(account, url=nil)
72
+ raise "#{account} not found" if !@data[account]
73
+ @data[account][URL] = url if url
74
+ return @data[account][URL] || ''
75
+ end
76
+
77
+ def expired?(account)
78
+ raise "#{account} not found" if !@data[account]
79
+ return true if !@data[account][LAST_UPDATE] || ((Time.now.to_i - @data[account][LAST_UPDATE]) > PASSWORD_EXPIRED)
80
+ return false
81
+ end
82
+
83
+ def password_of(account, p=nil)
84
+ raise "#{account} not found" if !@data[account]
85
+ if p then
86
+ @data[account][PREVIOUS] = @data[account][PASSWORD]
87
+ @data[account][PASSWORD] = p
88
+ @data[account][LAST_UPDATE] = Time.now.to_i
89
+ end
90
+ return @data[account][PASSWORD] || ''
91
+ end
92
+
93
+ # previous password
94
+ def previous_password_of(account)
95
+ raise "#{account} not found" if !@data[account]
96
+ return @data[account][PREVIOUS] || ''
97
+ end
98
+
99
+ def note_of(account, n=nil)
100
+ raise "#{account} not found" if !@data[account]
101
+ @data[account][NOTE] = n if n
102
+ return @data[account][NOTE] || ''
103
+ end
104
+
105
+ def username_of(account, usr=nil)
106
+ raise "#{account} not found" if !@data[account]
107
+ @data[account][USERNAME] = usr if usr
108
+ return @data[account][USERNAME] || ''
109
+ end
110
+ end
@@ -0,0 +1,37 @@
1
+ # $Date: 2009/02/19 15:49:32 $
2
+ module UserSpace
3
+ # provided by caller (global_options_variables):
4
+ # USER_CONF_DIR
5
+ # GEM_LIB_DIR
6
+ # CONF_FILE
7
+
8
+ def self.mkdir(subdir='')
9
+ dir = USER_CONF_DIR+subdir
10
+ Dir.mkdir(dir) if !File.exist?(dir)
11
+ end
12
+
13
+ def self.cp(rfile,wfile)
14
+ if !File.exist?(wfile) then
15
+ File.open(rfile, 'r'){|fhr|
16
+ File.open(wfile, 'w'){|fhw|
17
+ fhr.each{|ln| fhw.puts ln}
18
+ }
19
+ }
20
+ File.chmod(0600, wfile) # rw to user only
21
+ end
22
+ end
23
+
24
+ def self.copy(rfile, wfile=nil)
25
+ wfile = rfile if !wfile
26
+ self.cp(GEM_ROOT_DIR+rfile, USER_CONF_DIR+wfile)
27
+ end
28
+
29
+ def self.cp_conf
30
+ self.cp(GEM_LIB_DIR+CONF_FILE, USER_CONF_DIR+CONF_FILE)
31
+ end
32
+
33
+ def self.setup
34
+ self.mkdir
35
+ self.cp_conf
36
+ end
37
+ end
metadata ADDED
@@ -0,0 +1,69 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gtk2passwordapp
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - carlosjhr64@gmail.com
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-02-15 00:00:00 -08:00
13
+ default_executable: gtk2youtubeapp
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: crypt
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ description: A Ruby-Gtk2 application to keep passwords. Uses gem crypt/blofish to encrypt data. Requires crypt and gtk2.
26
+ email: carlosjhr64@gmail.com
27
+ executables:
28
+ - gtk2passwordapp
29
+ extensions: []
30
+
31
+ extra_rdoc_files: []
32
+
33
+ files:
34
+ - lib/configuration.rb
35
+ - lib/global_options_variables.rb
36
+ - lib/gtk2passwordapp.rb
37
+ - lib/iocrypt.rb
38
+ - lib/passwords_data.rb
39
+ - lib/setup_user_space.rb
40
+ - gifs/logo.gif
41
+ - README.txt
42
+ has_rdoc: false
43
+ homepage: http://ruby-gnome-apps.blogspot.com/search/label/Password
44
+ post_install_message:
45
+ rdoc_options: []
46
+
47
+ require_paths:
48
+ - .
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: "0"
54
+ version:
55
+ required_rubygems_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: "0"
60
+ version:
61
+ requirements:
62
+ - ruby-gtk2
63
+ rubyforge_project: gtk2passwordapp
64
+ rubygems_version: 1.3.1
65
+ signing_key:
66
+ specification_version: 2
67
+ summary: Ruby-Gtk2 application to maitain passwords.
68
+ test_files: []
69
+