pwrb 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/.gitignore +17 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +22 -0
- data/README.md +31 -0
- data/Rakefile +1 -0
- data/bin/pwrb +58 -0
- data/lib/pwrb.rb +202 -0
- data/lib/pwrb/version.rb +3 -0
- data/pwrb.gemspec +26 -0
- metadata +136 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Jianwei Han
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# Pwrb
|
2
|
+
|
3
|
+
Pwrb is a password management gem based on GPG.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'pwrb'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install pwrb
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
``` sh
|
22
|
+
pwrb -h
|
23
|
+
```
|
24
|
+
|
25
|
+
## Contributing
|
26
|
+
|
27
|
+
1. Fork it
|
28
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
29
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
30
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
31
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/pwrb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
|
4
|
+
|
5
|
+
require 'pwrb'
|
6
|
+
require 'optparse'
|
7
|
+
|
8
|
+
begin
|
9
|
+
options = {}
|
10
|
+
|
11
|
+
optparse = OptionParser.new do |opts|
|
12
|
+
opts.banner = "Usage: pw add|update|edit|list|copy|remove [user@domain] [options]"
|
13
|
+
opts.separator ""
|
14
|
+
opts.separator "Specific options:"
|
15
|
+
|
16
|
+
opts.on('-h', '--help', 'Display help message') do
|
17
|
+
puts opts
|
18
|
+
exit
|
19
|
+
end
|
20
|
+
|
21
|
+
opts.on('-p', '--print', 'Print password in text') do
|
22
|
+
options[:print] = true
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
optparse.parse!
|
27
|
+
|
28
|
+
aliases = {'cp' => 'copy',
|
29
|
+
'ls' => 'list',
|
30
|
+
'up' => 'update',
|
31
|
+
'create' => 'add',
|
32
|
+
'rm' => 'remove',
|
33
|
+
'delete' => 'remove',
|
34
|
+
'del' => 'remove'}
|
35
|
+
|
36
|
+
cmd = ARGV.shift
|
37
|
+
cmd = aliases[cmd] if aliases[cmd]
|
38
|
+
|
39
|
+
unless ['list', 'copy', 'update', 'remove', 'add', 'edit'].include?(cmd)
|
40
|
+
puts "Command not found: #{cmd}" if cmd
|
41
|
+
puts optparse
|
42
|
+
exit 1
|
43
|
+
end
|
44
|
+
|
45
|
+
pattern = ARGV.shift
|
46
|
+
abort("Unknown arguments: #{ARGV}") unless ARGV.empty?
|
47
|
+
|
48
|
+
if pattern
|
49
|
+
args = pattern.split('@', -1)
|
50
|
+
|
51
|
+
abort("Unknow pattern: #{pattern}") if args.length > 2
|
52
|
+
|
53
|
+
options[:site] = args.last
|
54
|
+
options[:user] = args.first if args.length == 2
|
55
|
+
end
|
56
|
+
|
57
|
+
Pwrb::PasswordDB.new(options).send cmd
|
58
|
+
end
|
data/lib/pwrb.rb
ADDED
@@ -0,0 +1,202 @@
|
|
1
|
+
require "pwrb/version"
|
2
|
+
|
3
|
+
require 'clipboard'
|
4
|
+
require 'gpgme'
|
5
|
+
require 'json'
|
6
|
+
require 'highline/import'
|
7
|
+
require 'tabularize'
|
8
|
+
require 'securerandom'
|
9
|
+
|
10
|
+
module Pwrb
|
11
|
+
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
|
16
|
+
@crypto = GPGME::Crypto.new
|
17
|
+
|
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)
|
27
|
+
else
|
28
|
+
create_safe(password)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
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)
|
46
|
+
end
|
47
|
+
|
48
|
+
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]
|
58
|
+
end
|
59
|
+
|
60
|
+
def match(item, options)
|
61
|
+
user_match(item, options[:user]) && site_match(item, options[:site])
|
62
|
+
end
|
63
|
+
|
64
|
+
def ask_for_password(prompt, confirm=false)
|
65
|
+
password = ask(prompt + ": ") { |q| q.echo = false; q.validate = /\A.+\Z/ }
|
66
|
+
if confirm
|
67
|
+
password_confirm = ask_for_password(prompt + " again")
|
68
|
+
|
69
|
+
if password == password_confirm
|
70
|
+
password
|
71
|
+
else
|
72
|
+
puts "Password mismatch, try again!"
|
73
|
+
ask_for_password(prompt, true)
|
74
|
+
end
|
75
|
+
else
|
76
|
+
password
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def ask_for_selection
|
81
|
+
case
|
82
|
+
when @selected.empty?
|
83
|
+
puts "Record not exists!"
|
84
|
+
nil
|
85
|
+
when @selected.length == 1
|
86
|
+
@selected.first
|
87
|
+
else
|
88
|
+
list
|
89
|
+
choose = ask('Which one? ', Integer) { |q| q.in = 0..@selected.length-1 }
|
90
|
+
@selected[choose]
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def ask_for_item(init={})
|
95
|
+
item = init
|
96
|
+
|
97
|
+
item[:user] = ask("User name? ") { |q| q.default = init[:user] || @options[:user]; q.validate = /\A\S+\Z/ }
|
98
|
+
item[:email] = ask("Email? ") { |q| q.default = init[:email] }
|
99
|
+
item[:site] = ask("Site? ") { |q| q.default = init[:site] || @options[:site]; q.validate = /\A.+\Z/ }
|
100
|
+
item[:url] = ask("URL? ") { |q| q.default = init[:url] || @options[:site] }
|
101
|
+
|
102
|
+
item
|
103
|
+
end
|
104
|
+
|
105
|
+
def ask_for_confirm
|
106
|
+
confirm = ask("Are you sure? ") { |q| q.validate = /\A(y|yes|n|no)\Z/i }
|
107
|
+
['y', 'yes'].include?(confirm)
|
108
|
+
end
|
109
|
+
|
110
|
+
def timestamp
|
111
|
+
Date.today.strftime("%Y%m%d")
|
112
|
+
end
|
113
|
+
|
114
|
+
def list
|
115
|
+
return if @selected.empty?
|
116
|
+
|
117
|
+
table = Tabularize.new
|
118
|
+
table << %w[# ID User Password Site Date]
|
119
|
+
table.separator!
|
120
|
+
|
121
|
+
@selected.each_with_index do |item, index|
|
122
|
+
user = item[:user]
|
123
|
+
user += "<%s>" % item[:email] unless item[:email].empty?
|
124
|
+
site = "%s[%s]" % [item[:site], item[:url]]
|
125
|
+
password = (@options[:print] ? item[:password] : "***")
|
126
|
+
|
127
|
+
table << [index.to_s, item[:id].split('-').first, user, password, site, item[:date]]
|
128
|
+
end
|
129
|
+
|
130
|
+
puts table
|
131
|
+
end
|
132
|
+
|
133
|
+
def copy
|
134
|
+
target = ask_for_selection
|
135
|
+
seconds = 10
|
136
|
+
|
137
|
+
if target
|
138
|
+
original_clipboard_content = Clipboard.paste
|
139
|
+
sleep 0.1
|
140
|
+
Clipboard.copy target[:password]
|
141
|
+
puts "Password for #{target[:site]} is in clipboard for #{seconds} seconds"
|
142
|
+
begin
|
143
|
+
sleep seconds
|
144
|
+
rescue Interrupt
|
145
|
+
Clipboard.copy original_clipboard_content
|
146
|
+
raise
|
147
|
+
end
|
148
|
+
Clipboard.copy original_clipboard_content
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def update
|
153
|
+
target = ask_for_selection
|
154
|
+
|
155
|
+
if target
|
156
|
+
target[:password] = ask_for_password("Enter new password", confirm = true)
|
157
|
+
target[:date] = timestamp
|
158
|
+
write_safe
|
159
|
+
puts "Update complete!"
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
|
164
|
+
def add
|
165
|
+
item = ask_for_item
|
166
|
+
|
167
|
+
item[:id] = SecureRandom.uuid
|
168
|
+
item[:password] = ask_for_password("Enter password", confirm = true)
|
169
|
+
item[:date] = timestamp
|
170
|
+
|
171
|
+
if ask_for_confirm
|
172
|
+
@data << item
|
173
|
+
write_safe
|
174
|
+
puts "Added #{item[:user]}@#{item[:site]}!"
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def edit
|
179
|
+
target = ask_for_selection
|
180
|
+
|
181
|
+
if target
|
182
|
+
ask_for_item(target)
|
183
|
+
if ask_for_confirm
|
184
|
+
target[:date] = timestamp
|
185
|
+
write_safe
|
186
|
+
puts "Update #{target[:user]}@#{target[:site]}!"
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def remove
|
192
|
+
target = ask_for_selection
|
193
|
+
|
194
|
+
if target && ask_for_confirm
|
195
|
+
@data.delete(target)
|
196
|
+
write_safe
|
197
|
+
puts "#{target[:user]}@#{target[:site]} Removed!"
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
end
|
202
|
+
end
|
data/lib/pwrb/version.rb
ADDED
data/pwrb.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'pwrb/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "pwrb"
|
8
|
+
gem.version = Pwrb::VERSION
|
9
|
+
gem.authors = ["Jianwei Han"]
|
10
|
+
gem.email = ["hanjianwei@gmail.com"]
|
11
|
+
gem.description = %q{pwrb is a cli password management software.}
|
12
|
+
gem.summary = %q{pwrb is a command line password management software based on GPG. Please run `pwrb -h` for more information. }
|
13
|
+
gem.homepage = "https://github.com/hanjianwei/pwrb"
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
|
20
|
+
gem.add_runtime_dependency('gpgme', "~> 2.0.1")
|
21
|
+
gem.add_runtime_dependency('tabularize', "~> 0.2.9")
|
22
|
+
gem.add_runtime_dependency('clipboard', "~> 1.0.1")
|
23
|
+
gem.add_runtime_dependency('highline', "~> 1.6.15")
|
24
|
+
|
25
|
+
gem.add_development_dependency "bundler", ">= 1.2.3"
|
26
|
+
end
|
metadata
ADDED
@@ -0,0 +1,136 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: pwrb
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Jianwei Han
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-12-20 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: gpgme
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 2.0.1
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 2.0.1
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: tabularize
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 0.2.9
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 0.2.9
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: clipboard
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 1.0.1
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 1.0.1
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: highline
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ~>
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: 1.6.15
|
70
|
+
type: :runtime
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ~>
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: 1.6.15
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: bundler
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: 1.2.3
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: 1.2.3
|
94
|
+
description: pwrb is a cli password management software.
|
95
|
+
email:
|
96
|
+
- hanjianwei@gmail.com
|
97
|
+
executables:
|
98
|
+
- pwrb
|
99
|
+
extensions: []
|
100
|
+
extra_rdoc_files: []
|
101
|
+
files:
|
102
|
+
- .gitignore
|
103
|
+
- Gemfile
|
104
|
+
- LICENSE.txt
|
105
|
+
- README.md
|
106
|
+
- Rakefile
|
107
|
+
- bin/pwrb
|
108
|
+
- lib/pwrb.rb
|
109
|
+
- lib/pwrb/version.rb
|
110
|
+
- pwrb.gemspec
|
111
|
+
homepage: https://github.com/hanjianwei/pwrb
|
112
|
+
licenses: []
|
113
|
+
post_install_message:
|
114
|
+
rdoc_options: []
|
115
|
+
require_paths:
|
116
|
+
- lib
|
117
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
118
|
+
none: false
|
119
|
+
requirements:
|
120
|
+
- - ! '>='
|
121
|
+
- !ruby/object:Gem::Version
|
122
|
+
version: '0'
|
123
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
124
|
+
none: false
|
125
|
+
requirements:
|
126
|
+
- - ! '>='
|
127
|
+
- !ruby/object:Gem::Version
|
128
|
+
version: '0'
|
129
|
+
requirements: []
|
130
|
+
rubyforge_project:
|
131
|
+
rubygems_version: 1.8.24
|
132
|
+
signing_key:
|
133
|
+
specification_version: 3
|
134
|
+
summary: pwrb is a command line password management software based on GPG. Please
|
135
|
+
run `pwrb -h` for more information.
|
136
|
+
test_files: []
|