pwrb 0.0.1 → 0.0.2

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/bin/pwrb CHANGED
@@ -4,11 +4,12 @@ $:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
4
4
 
5
5
  require 'pwrb'
6
6
  require 'optparse'
7
+ require 'highline/import'
7
8
 
8
9
  begin
9
10
  options = {}
10
11
 
11
- optparse = OptionParser.new do |opts|
12
+ opts = OptionParser.new do |opts|
12
13
  opts.banner = "Usage: pw add|update|edit|list|copy|remove [user@domain] [options]"
13
14
  opts.separator ""
14
15
  opts.separator "Specific options:"
@@ -18,30 +19,40 @@ begin
18
19
  exit
19
20
  end
20
21
 
21
- opts.on('-p', '--print', 'Print password in text') do
22
+ # Default password file is "~/.pw"
23
+ options[:file] = File.join(Dir.home, '.pw')
24
+ opts.on('-f', '--file FILE', 'Use FILE as as password') do |f|
25
+ options[:file] = File.expand_path(f)
26
+ end
27
+
28
+ options[:print] = false
29
+ opts.on('-p', '--print', 'Print password in plain text') do
22
30
  options[:print] = true
23
31
  end
24
32
  end
25
33
 
26
- optparse.parse!
34
+ # Read command line into `option` hash
35
+ opts.parse!
27
36
 
28
- aliases = {'cp' => 'copy',
29
- 'ls' => 'list',
30
- 'up' => 'update',
37
+ # Handle commands
38
+ aliases = {'cp' => 'copy',
39
+ 'ls' => 'list',
40
+ 'up' => 'update',
31
41
  'create' => 'add',
32
- 'rm' => 'remove',
42
+ 'rm' => 'remove',
33
43
  'delete' => 'remove',
34
- 'del' => 'remove'}
44
+ 'del' => 'remove'}
35
45
 
36
46
  cmd = ARGV.shift
37
47
  cmd = aliases[cmd] if aliases[cmd]
38
48
 
39
49
  unless ['list', 'copy', 'update', 'remove', 'add', 'edit'].include?(cmd)
40
50
  puts "Command not found: #{cmd}" if cmd
41
- puts optparse
51
+ puts opts
42
52
  exit 1
43
53
  end
44
54
 
55
+ # Search pattern (optional), e.g. foo@github
45
56
  pattern = ARGV.shift
46
57
  abort("Unknown arguments: #{ARGV}") unless ARGV.empty?
47
58
 
@@ -50,9 +61,14 @@ begin
50
61
 
51
62
  abort("Unknow pattern: #{pattern}") if args.length > 2
52
63
 
53
- options[:site] = args.last
54
- options[:user] = args.first if args.length == 2
64
+ site = args.last
65
+ user = args.first if args.length == 2
55
66
  end
56
67
 
57
- Pwrb::PasswordDB.new(options).send cmd
58
- end
68
+ # Open password database, master password needed
69
+ master_password = ask("Enter master passphrase: ") { |q| q.echo = false }
70
+ db = Pwrb::DB.new(options[:file], master_password)
71
+ cli = Pwrb::CLI.new(db, options[:print])
72
+
73
+ cli.send cmd, user, site
74
+ end
@@ -1,4 +1,6 @@
1
1
  require "pwrb/version"
2
+ require 'pwrb/db'
3
+ require 'pwrb/cli'
2
4
 
3
5
  require 'clipboard'
4
6
  require 'gpgme'
@@ -9,56 +11,34 @@ require 'securerandom'
9
11
 
10
12
  module Pwrb
11
13
  class PasswordDB
12
- def initialize(options=nil)
13
- @filename = File.expand_path('~/.pw')
14
- @filename = File.readlink(@filename) if File.symlink?(@filename)
15
- @options = options
14
+ def initialize(filename, password)
15
+ @filename = filename
16
+ @passwords = []
16
17
  @crypto = GPGME::Crypto.new
17
18
 
18
- read_safe
19
- @selected = @data.select{ |item| match(item, @options) }
20
- end
21
-
22
- def read_safe(password=nil)
23
- if File.file?(@filename)
24
- @password = password || ask_for_password("Enter master passphrase")
25
- plain_data = @crypto.decrypt(File.open(@filename), :password => @password).to_s
26
- @data = JSON.parse(plain_data, :symbolize_names => true)
19
+ # Read encrypted file
20
+ if File.exist?(filename)
21
+ text = @crypto.decrypt(filename, :password => password).to_s
22
+ @passwords = JSON.parse(text, :symbolize_names => true)
27
23
  else
28
- create_safe(password)
24
+ FileUtils.mkdir_p(File.dirname(filename))
25
+ save
26
+ File.chmod(0600, filename)
29
27
  end
30
28
  end
31
29
 
32
- def write_safe
33
- plain_data = JSON.generate(@data)
34
- plain_data.force_encoding('ASCII-8BIT')
35
- @crypto.encrypt(plain_data, :output => File.open(@filename, 'w+'))
36
- end
37
-
38
- def create_safe(password=nil)
39
- puts "No password file detected, creating one at #{@filename}"
40
- @password = ask_for_password("Enter master passphrase", confirm = true) unless @password == password
41
-
42
- FileUtils.mkdir_p(File.dirname(@filename))
43
- @data = []
44
- write_safe
45
- File.chmod(0600, @filename)
30
+ def save
31
+ text = JSON.generate(@passwords)
32
+ text.force_encoding('ASCII-8BIT')
33
+ @crypto.encrypt(text, :output => File.open(@filename, 'w+'))
46
34
  end
47
35
 
48
36
  def to_s
49
- "<*******>"
50
- end
51
-
52
- def user_match(item, user)
53
- /#{user}/i =~ item[:user]
54
- end
55
-
56
- def site_match(item, site)
57
- /#{site}/i =~ item[:site] || /#{site}/i =~ item[:url]
37
+ "<*>"
58
38
  end
59
39
 
60
- def match(item, options)
61
- user_match(item, options[:user]) && site_match(item, options[:site])
40
+ def query(user, site)
41
+ @passwords.select {|p| /#{user}/i =~ p[:user] && (/#{site}/i =~ p[:site] || /#{site}/i =~ p[:url]) }
62
42
  end
63
43
 
64
44
  def ask_for_password(prompt, confirm=false)
@@ -0,0 +1,118 @@
1
+ require 'highline/import'
2
+ require 'clipboard'
3
+
4
+ module Pwrb
5
+ class CLI
6
+ def initialize(db, echo = false)
7
+ @db = db
8
+ @echo = echo
9
+ end
10
+
11
+ def list(user, site)
12
+ result = @db.query(user, site)
13
+ return if result.empty?
14
+
15
+ table = Tabularize.new
16
+
17
+ table << %w[# ID User Password Site Date]
18
+ table.separator!
19
+
20
+ result.each_with_index do |p, index|
21
+ user = p[:user]
22
+ user += "<%s>" % p[:email] unless p[:email].empty?
23
+ site = "%s[%s]" % [p[:site], p[:url]]
24
+ password = (@echo ? p[:password] : "***")
25
+
26
+ table << [index.to_s, p[:id].split('-').first, user, password, site, p[:date]]
27
+ end
28
+
29
+ puts table
30
+ end
31
+
32
+ def select(user, site)
33
+ result = @db.query(user, site)
34
+
35
+ case result.count
36
+ when 0
37
+ nil
38
+ when 1
39
+ result[0]
40
+ else
41
+ list(user, site)
42
+ index = ask('Which one?', Integer) { |q| q.in = 0...result.count }
43
+ result[index]
44
+ end
45
+ end
46
+
47
+ def copy(user, site)
48
+ item = select(user, site)
49
+ return unless item
50
+
51
+ seconds = 10
52
+ original_clipboard = Clipboard.paste
53
+ sleep(0.1)
54
+ Clipboard.copy(item[:password])
55
+ puts "Password for #{item[:site]} is in clipboard for #{seconds} seconds"
56
+
57
+ begin
58
+ sleep(seconds)
59
+ rescue Interrupt
60
+ Clipboard.copy(original_clipboard)
61
+ raise
62
+ end
63
+
64
+ Clipboard.copy(original_clipboard)
65
+ end
66
+
67
+ def update(user, site)
68
+ item = select(user, site)
69
+ return unless item
70
+
71
+ @db.update(item.merge(:password => new_password("Enter new password")))
72
+ end
73
+
74
+ def add(user, site)
75
+ item = new_item(:user => user, :site => site)
76
+ item[:password] = new_password("Enter password")
77
+
78
+ @db.insert(item)
79
+ end
80
+
81
+ def edit(user, site)
82
+ item = select(user, site)
83
+ return unless item
84
+
85
+ @db.update(new_item(item.dup))
86
+ end
87
+
88
+ def remove(user, site)
89
+ item = select(user, site)
90
+ return unless item
91
+
92
+ @db.remove(item)
93
+ end
94
+
95
+ def new_item(init = {})
96
+ item = init
97
+
98
+ item[:user] = ask("User name: ") { |q| q.default = init[:user]; q.validate = /\A\S+\Z/ }
99
+ item[:email] = ask("Email: ") { |q| q.default = init[:email] }
100
+ item[:site] = ask("Site: ") { |q| q.default = init[:site]; q.validate = /\A.+\Z/ }
101
+ item[:url] = ask("URL: ") { |q| q.default = init[:url] }
102
+
103
+ item
104
+ end
105
+
106
+ def new_password(prompt)
107
+ password = ask(prompt + ": ") { |q| q.echo = false }
108
+ confirm = ask(prompt + " again: ") { |q| q.echo = false }
109
+
110
+ if password == confirm
111
+ password
112
+ else
113
+ puts "Password mismatch, try again!"
114
+ new_password(prompt)
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,60 @@
1
+ require 'gpgme'
2
+ require 'json'
3
+ require 'securerandom'
4
+
5
+ module Pwrb
6
+ class DB
7
+ def initialize(filename, password)
8
+ @filename = filename
9
+ @passwords = []
10
+ @crypto = GPGME::Crypto.new
11
+
12
+ # Read encrypted file
13
+ if File.exist?(filename)
14
+ text = @crypto.decrypt(File.open(filename), :password => password).to_s
15
+ @passwords = JSON.parse(text, :symbolize_names => true)
16
+ else
17
+ FileUtils.mkdir_p(File.dirname(filename))
18
+ save
19
+ File.chmod(0600, filename)
20
+ end
21
+ end
22
+
23
+ def save
24
+ text = JSON.generate(@passwords)
25
+ text.force_encoding('ASCII-8BIT')
26
+ @crypto.encrypt(text, :output => File.open(@filename, 'w+'))
27
+ end
28
+
29
+ def to_s
30
+ "<*>"
31
+ end
32
+
33
+ def query(user, site)
34
+ @passwords.select {|p| /#{user}/i =~ p[:user] && (/#{site}/i =~ p[:site] || /#{site}/i =~ p[:url]) }
35
+ end
36
+
37
+ def insert(item)
38
+ @passwords << item.merge(:id => SecureRandom.uuid, :date => timestamp)
39
+ save
40
+ end
41
+
42
+ def update(new_item)
43
+ new_item.merge!(:date => timestamp)
44
+ item = @passwords.find { |p| p[:id] == new_item[:id] }
45
+ if item
46
+ item.merge!(new_item) { |_, oldval, newval| (newval && !newval.empty?) ? newval : oldval }
47
+ save
48
+ end
49
+ end
50
+
51
+ def remove(item)
52
+ @passwords.delete(item)
53
+ save
54
+ end
55
+
56
+ def timestamp
57
+ Date.today.strftime("%Y%m%d")
58
+ end
59
+ end
60
+ end
@@ -1,3 +1,3 @@
1
1
  module Pwrb
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
@@ -0,0 +1,16 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <module type="RUBY_MODULE" version="4">
3
+ <component name="NewModuleRootManager" inherit-compiler-output="true">
4
+ <exclude-output />
5
+ <content url="file://$MODULE_DIR$" />
6
+ <orderEntry type="jdk" jdkName="ruby-1.9.3-p327" jdkType="RUBY_SDK" />
7
+ <orderEntry type="sourceFolder" forTests="false" />
8
+ <orderEntry type="library" scope="PROVIDED" name="bundler (v1.2.3, ruby-1.9.3-p327) [gem]" level="application" />
9
+ <orderEntry type="library" scope="PROVIDED" name="clipboard (v1.0.1, ruby-1.9.3-p327) [gem]" level="application" />
10
+ <orderEntry type="library" scope="PROVIDED" name="gpgme (v2.0.1, ruby-1.9.3-p327) [gem]" level="application" />
11
+ <orderEntry type="library" scope="PROVIDED" name="highline (v1.6.15, ruby-1.9.3-p327) [gem]" level="application" />
12
+ <orderEntry type="library" scope="PROVIDED" name="tabularize (v0.2.9, ruby-1.9.3-p327) [gem]" level="application" />
13
+ <orderEntry type="library" scope="PROVIDED" name="unicode-display_width (v0.1.1, ruby-1.9.3-p327) [gem]" level="application" />
14
+ </component>
15
+ </module>
16
+
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pwrb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-12-20 00:00:00.000000000 Z
12
+ date: 2012-12-22 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: gpgme
@@ -100,14 +100,30 @@ extensions: []
100
100
  extra_rdoc_files: []
101
101
  files:
102
102
  - .gitignore
103
+ - .idea/.name
104
+ - .idea/.rakeTasks
105
+ - .idea/compiler.xml
106
+ - .idea/copyright/profiles_settings.xml
107
+ - .idea/dictionaries/hanjianwei.xml
108
+ - .idea/encodings.xml
109
+ - .idea/inspectionProfiles/Project_Default.xml
110
+ - .idea/inspectionProfiles/profiles_settings.xml
111
+ - .idea/misc.xml
112
+ - .idea/modules.xml
113
+ - .idea/scopes/scope_settings.xml
114
+ - .idea/vcs.xml
115
+ - .idea/workspace.xml
103
116
  - Gemfile
104
117
  - LICENSE.txt
105
118
  - README.md
106
119
  - Rakefile
107
120
  - bin/pwrb
108
121
  - lib/pwrb.rb
122
+ - lib/pwrb/cli.rb
123
+ - lib/pwrb/db.rb
109
124
  - lib/pwrb/version.rb
110
125
  - pwrb.gemspec
126
+ - pwrb.iml
111
127
  homepage: https://github.com/hanjianwei/pwrb
112
128
  licenses: []
113
129
  post_install_message:
@@ -128,7 +144,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
128
144
  version: '0'
129
145
  requirements: []
130
146
  rubyforge_project:
131
- rubygems_version: 1.8.24
147
+ rubygems_version: 1.8.23
132
148
  signing_key:
133
149
  specification_version: 3
134
150
  summary: pwrb is a command line password management software based on GPG. Please