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/.idea/.name +1 -0
- data/.idea/.rakeTasks +7 -0
- data/.idea/compiler.xml +23 -0
- data/.idea/copyright/profiles_settings.xml +5 -0
- data/.idea/dictionaries/hanjianwei.xml +3 -0
- data/.idea/encodings.xml +5 -0
- data/.idea/inspectionProfiles/Project_Default.xml +11 -0
- data/.idea/inspectionProfiles/profiles_settings.xml +7 -0
- data/.idea/misc.xml +7 -0
- data/.idea/modules.xml +9 -0
- data/.idea/scopes/scope_settings.xml +5 -0
- data/.idea/vcs.xml +7 -0
- data/.idea/workspace.xml +788 -0
- data/bin/pwrb +29 -13
- data/lib/pwrb.rb +19 -39
- data/lib/pwrb/cli.rb +118 -0
- data/lib/pwrb/db.rb +60 -0
- data/lib/pwrb/version.rb +1 -1
- data/pwrb.iml +16 -0
- metadata +19 -3
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
34
|
+
# Read command line into `option` hash
|
|
35
|
+
opts.parse!
|
|
27
36
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
'
|
|
37
|
+
# Handle commands
|
|
38
|
+
aliases = {'cp' => 'copy',
|
|
39
|
+
'ls' => 'list',
|
|
40
|
+
'up' => 'update',
|
|
31
41
|
'create' => 'add',
|
|
32
|
-
'rm'
|
|
42
|
+
'rm' => 'remove',
|
|
33
43
|
'delete' => 'remove',
|
|
34
|
-
'del'
|
|
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
|
|
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
|
-
|
|
54
|
-
|
|
64
|
+
site = args.last
|
|
65
|
+
user = args.first if args.length == 2
|
|
55
66
|
end
|
|
56
67
|
|
|
57
|
-
|
|
58
|
-
|
|
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
|
data/lib/pwrb.rb
CHANGED
|
@@ -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(
|
|
13
|
-
@filename =
|
|
14
|
-
@
|
|
15
|
-
@options = options
|
|
14
|
+
def initialize(filename, password)
|
|
15
|
+
@filename = filename
|
|
16
|
+
@passwords = []
|
|
16
17
|
@crypto = GPGME::Crypto.new
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
24
|
+
FileUtils.mkdir_p(File.dirname(filename))
|
|
25
|
+
save
|
|
26
|
+
File.chmod(0600, filename)
|
|
29
27
|
end
|
|
30
28
|
end
|
|
31
29
|
|
|
32
|
-
def
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
@crypto.encrypt(
|
|
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
|
|
61
|
-
|
|
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)
|
data/lib/pwrb/cli.rb
ADDED
|
@@ -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
|
data/lib/pwrb/db.rb
ADDED
|
@@ -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
|
data/lib/pwrb/version.rb
CHANGED
data/pwrb.iml
ADDED
|
@@ -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.
|
|
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-
|
|
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.
|
|
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
|