dotenv-gpg 0.3
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.
- checksums.yaml +7 -0
- data/.gitignore +1 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +33 -0
- data/README.md +132 -0
- data/Rakefile +7 -0
- data/bin/dotgpg +5 -0
- data/dotenv-gpg.gemspec +22 -0
- data/lib/dotgpg.rb +96 -0
- data/lib/dotgpg/cli.rb +194 -0
- data/lib/dotgpg/dir.rb +220 -0
- data/lib/dotgpg/key.rb +105 -0
- data/lib/dotgpg/template/README.md +20 -0
- data/spec/cli_spec.rb +255 -0
- data/spec/dir_spec.rb +214 -0
- data/spec/fixture/add1.key +30 -0
- data/spec/fixture/add2.key +30 -0
- data/spec/fixture/add3.key +30 -0
- data/spec/fixture/basic/.gpg/removed1@example.com +30 -0
- data/spec/fixture/basic/.gpg/removed2@example.com +30 -0
- data/spec/fixture/basic/.gpg/removed3@example.com +0 -0
- data/spec/fixture/basic/.gpg/test2@example.com +31 -0
- data/spec/fixture/basic/.gpg/test3@example.com +31 -0
- data/spec/fixture/basic/.gpg/test@example.com +31 -0
- data/spec/fixture/basic/README.md +28 -0
- data/spec/fixture/basic/a +47 -0
- data/spec/fixture/basic/b/c +47 -0
- data/spec/fixture/gnupghome/pubring.gpg +0 -0
- data/spec/fixture/gnupghome/pubring.gpg~ +0 -0
- data/spec/fixture/gnupghome/random_seed +0 -0
- data/spec/fixture/gnupghome/secring.gpg +0 -0
- data/spec/fixture/gnupghome/trustdb.gpg +0 -0
- data/spec/fixture/secret1.key +31 -0
- data/spec/helper.rb +19 -0
- data/spec/helper/assertions.rb +54 -0
- metadata +136 -0
data/lib/dotgpg/dir.rb
ADDED
@@ -0,0 +1,220 @@
|
|
1
|
+
class Dotgpg
|
2
|
+
class Dir
|
3
|
+
|
4
|
+
attr_reader :path
|
5
|
+
|
6
|
+
# Find the Dotgpg::Dir that contains the given path.
|
7
|
+
#
|
8
|
+
# If multiple are given only returns the directory if it contains all
|
9
|
+
# paths.
|
10
|
+
#
|
11
|
+
# If no path is given, find the Dotgpg::Dir that contains the current
|
12
|
+
# working directory.
|
13
|
+
#
|
14
|
+
# @param [*Array<String>] paths
|
15
|
+
# @return {nil|[Dotgpg::Dir]}
|
16
|
+
def self.closest(path=".", *others)
|
17
|
+
path = Pathname.new(File.absolute_path(path)).cleanpath
|
18
|
+
|
19
|
+
result = path.ascend do |parent|
|
20
|
+
maybe = Dotgpg::Dir.new(parent)
|
21
|
+
break maybe if maybe.dotgpg?
|
22
|
+
end
|
23
|
+
|
24
|
+
if others.any? && closest(*others) != result
|
25
|
+
nil
|
26
|
+
else
|
27
|
+
result
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Open a Dotgpg::Dir
|
32
|
+
#
|
33
|
+
# @param [String] path The location of the directory
|
34
|
+
def initialize(path)
|
35
|
+
@path = Pathname.new(File.absolute_path(path)).cleanpath
|
36
|
+
end
|
37
|
+
|
38
|
+
# Get the keys currently associated with this directory.
|
39
|
+
#
|
40
|
+
# @return [Array<GPGME::Key>]
|
41
|
+
def known_keys
|
42
|
+
dotgpg.each_child.map do |key_file|
|
43
|
+
Dotgpg::Key.read key_file.open
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Decrypt the contents of path and write to output.
|
48
|
+
#
|
49
|
+
# The path should be absolute, and may point to outside
|
50
|
+
# this directory, though that is not recommended.
|
51
|
+
#
|
52
|
+
# @param [Pathname] path The file to decrypt
|
53
|
+
# @param [IO] output The IO to write to
|
54
|
+
# @return [Boolean] false if decryption failed for an understandable reason
|
55
|
+
def decrypt(path, output)
|
56
|
+
File.open(path) do |f|
|
57
|
+
signature = false
|
58
|
+
temp = GPGME::Crypto.new.decrypt f, passphrase_callback: Dotgpg.method(:passfunc) do |s|
|
59
|
+
signature = s
|
60
|
+
end
|
61
|
+
|
62
|
+
unless ENV["DOTGPG_ALLOW_INJECTION_ATTACK"]
|
63
|
+
raise InvalidSignature, "file was not signed" unless signature
|
64
|
+
raise InvalidSignature, "signature was incorrect" unless signature.valid?
|
65
|
+
raise InvalidSignature, "signed by a stranger" unless known_keys.include?(signature.key)
|
66
|
+
end
|
67
|
+
|
68
|
+
output.write temp.read
|
69
|
+
end
|
70
|
+
true
|
71
|
+
rescue GPGME::Error::NoData, GPGME::Error::DecryptFailed, SystemCallError => e
|
72
|
+
Dotgpg.warn path, e
|
73
|
+
false
|
74
|
+
end
|
75
|
+
|
76
|
+
# Encrypt the input and write it to the given path.
|
77
|
+
#
|
78
|
+
# The path should be absolute, and may point to outside
|
79
|
+
# this directory, though that is not recommended.
|
80
|
+
#
|
81
|
+
# @param [Pathname] path The desired destination
|
82
|
+
# @param [IO] input The IO containing the plaintext
|
83
|
+
# @return [Boolean] false if encryption failed for an understandable reason
|
84
|
+
def encrypt(path, input)
|
85
|
+
File.open(path, "w") do |f|
|
86
|
+
GPGME::Crypto.new.encrypt input, output: f,
|
87
|
+
recipients: known_keys,
|
88
|
+
armor: true,
|
89
|
+
always_trust: true,
|
90
|
+
sign: true,
|
91
|
+
passphrase_callback: Dotgpg.method(:passfunc),
|
92
|
+
signers: known_keys.detect{ |key| GPGME::Key.find(:secret).include?(key) }
|
93
|
+
end
|
94
|
+
true
|
95
|
+
rescue SystemCallError => e
|
96
|
+
Dotgpg.warn path, e
|
97
|
+
false
|
98
|
+
end
|
99
|
+
|
100
|
+
# Re-encrypts a set of files with the currently known keys.
|
101
|
+
#
|
102
|
+
# If a block is provided, it can be used to edit the files in
|
103
|
+
# their temporary un-encrypted state.
|
104
|
+
#
|
105
|
+
# @param [Array<Pathname>] files the files to re-encrypt
|
106
|
+
# @yieldparam [Hash<Pathname, Tempfile>] the unencrypted files for each param
|
107
|
+
def reencrypt(files, &block)
|
108
|
+
tempfiles = {}
|
109
|
+
|
110
|
+
files.uniq.each do |f|
|
111
|
+
temp = Tempfile.new([File.basename(f), ".sh"])
|
112
|
+
tempfiles[f] = temp
|
113
|
+
if File.exist? f
|
114
|
+
decrypted = decrypt f, temp
|
115
|
+
tempfiles.delete f unless decrypted
|
116
|
+
end
|
117
|
+
temp.flush
|
118
|
+
temp.close(false)
|
119
|
+
end
|
120
|
+
|
121
|
+
yield tempfiles if block_given?
|
122
|
+
|
123
|
+
tempfiles.each_pair do |f, temp|
|
124
|
+
temp.open
|
125
|
+
temp.seek(0)
|
126
|
+
encrypt f, temp
|
127
|
+
end
|
128
|
+
|
129
|
+
nil
|
130
|
+
ensure
|
131
|
+
tempfiles.values.each do |temp|
|
132
|
+
temp.close(true)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
# List every GPG-encrypted file in a directory recursively.
|
137
|
+
#
|
138
|
+
# Assumes the files are armored (non-armored files are hard to detect and
|
139
|
+
# dotgpg itself always armors)
|
140
|
+
#
|
141
|
+
# This is used to decide which files to re-encrypt when adding a user.
|
142
|
+
#
|
143
|
+
# @param [Pathname] dir
|
144
|
+
# @return [Array<Pathname>]
|
145
|
+
def all_encrypted_files(dir=path)
|
146
|
+
results = []
|
147
|
+
dir.each_child do |child|
|
148
|
+
if child.directory?
|
149
|
+
if !child.symlink? && child != dotgpg
|
150
|
+
results += all_encrypted_files(child)
|
151
|
+
end
|
152
|
+
elsif child.readable?
|
153
|
+
if child.read(1024) =~ /-----BEGIN PGP MESSAGE-----/
|
154
|
+
results << child
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
results
|
160
|
+
end
|
161
|
+
|
162
|
+
# Does this directory includea key for the given user yet?
|
163
|
+
#
|
164
|
+
# @param [GPGME::Key]
|
165
|
+
# @return [Boolean]
|
166
|
+
def has_key?(key)
|
167
|
+
File.exist? key_path(key)
|
168
|
+
end
|
169
|
+
|
170
|
+
# Add a given key to the directory
|
171
|
+
#
|
172
|
+
# Re-encrypts all files to add the new key as a recipient.
|
173
|
+
#
|
174
|
+
# @param [GPGME::Key]
|
175
|
+
def add_key(key)
|
176
|
+
reencrypt all_encrypted_files do
|
177
|
+
File.write key_path(key), key.export(armor: true).to_s
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
# Remove a given key from a directory
|
182
|
+
#
|
183
|
+
# Re-encrypts all files so that the removed key no-longer has access.
|
184
|
+
#
|
185
|
+
# @param [GPGME::Key]
|
186
|
+
def remove_key(key)
|
187
|
+
reencrypt all_encrypted_files do
|
188
|
+
key_path(key).unlink
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
# The path at which a key should be stored
|
193
|
+
#
|
194
|
+
# (i.e. .gpg/me@cirw.in)
|
195
|
+
#
|
196
|
+
# @param [GPGME::Key]
|
197
|
+
# @return [Pathname]
|
198
|
+
def key_path(key)
|
199
|
+
dotgpg + key.email
|
200
|
+
end
|
201
|
+
|
202
|
+
# The .gpg directory
|
203
|
+
#
|
204
|
+
# @return [Pathname]
|
205
|
+
def dotgpg
|
206
|
+
path + ".gpg"
|
207
|
+
end
|
208
|
+
|
209
|
+
# Does the .gpg directory exist?
|
210
|
+
#
|
211
|
+
# @return [Boolean]
|
212
|
+
def dotgpg?
|
213
|
+
dotgpg.directory?
|
214
|
+
end
|
215
|
+
|
216
|
+
def ==(other)
|
217
|
+
Dotgpg::Dir === other && other.path == self.path
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
data/lib/dotgpg/key.rb
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
class Dotgpg
|
2
|
+
class Key
|
3
|
+
|
4
|
+
def self.read(file)
|
5
|
+
GPGME::Key.import(file).imports.map do |import|
|
6
|
+
GPGME::Key.find(:public, import.fingerprint)
|
7
|
+
end.flatten.first
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.secret_key(email=nil, force_new=nil)
|
11
|
+
new.secret_key(email, force_new)
|
12
|
+
end
|
13
|
+
|
14
|
+
def secret_key(email=nil, force_new=nil)
|
15
|
+
existing = existing_key(email)
|
16
|
+
if existing && !force_new
|
17
|
+
existing
|
18
|
+
else
|
19
|
+
create_new_key email
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def existing_key(email=nil)
|
26
|
+
existing_private_keys.detect do |k|
|
27
|
+
email.nil? || k.email == email
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def create_new_key(email=nil)
|
32
|
+
name = guess_name
|
33
|
+
email ||= guess_email
|
34
|
+
|
35
|
+
if email
|
36
|
+
puts "Creating a new GPG key: #{name} <#{email}>"
|
37
|
+
passphrase = get_passphrase
|
38
|
+
else
|
39
|
+
puts "Creating a new GPG key for #{name}"
|
40
|
+
email = get_email
|
41
|
+
passphrase = get_passphrase
|
42
|
+
end
|
43
|
+
|
44
|
+
puts "Generating large prime numbers, please wait..."
|
45
|
+
ctx = GPGME::Ctx.new
|
46
|
+
ctx.genkey(<<EOF, nil, nil)
|
47
|
+
<GnupgKeyParms format="internal">
|
48
|
+
Key-Type: RSA
|
49
|
+
Key-Length: 2048
|
50
|
+
Subkey-Type: RSA
|
51
|
+
Subkey-Length: 2048
|
52
|
+
Name-Real: #{name}
|
53
|
+
Name-Comment: dotgpg
|
54
|
+
Name-Email: #{email}
|
55
|
+
Expire-Date: 0
|
56
|
+
Passphrase: #{passphrase}
|
57
|
+
</GnupgKeyParms>
|
58
|
+
EOF
|
59
|
+
|
60
|
+
# return the most recently created key (race!)
|
61
|
+
GPGME::Key.find(:secret).sort_by{ |key|
|
62
|
+
key.primary_subkey.timestamp
|
63
|
+
}.last
|
64
|
+
end
|
65
|
+
|
66
|
+
def guess_name
|
67
|
+
name = `git config user.name 2>/dev/null`.strip
|
68
|
+
name = `whoami`.strip if name == ""
|
69
|
+
name
|
70
|
+
end
|
71
|
+
|
72
|
+
def guess_email
|
73
|
+
email = `git config user.email 2>/dev/null`.strip
|
74
|
+
email if email != ""
|
75
|
+
end
|
76
|
+
|
77
|
+
def get_email
|
78
|
+
email = ""
|
79
|
+
until email =~ /@/
|
80
|
+
email = Dotgpg.read_input "Email address: "
|
81
|
+
end
|
82
|
+
email
|
83
|
+
end
|
84
|
+
|
85
|
+
def get_passphrase
|
86
|
+
passphrase = confirmation = nil
|
87
|
+
until passphrase && passphrase == confirmation
|
88
|
+
times = 0
|
89
|
+
until passphrase && passphrase.length >= 10
|
90
|
+
times += 1
|
91
|
+
$stderr.puts "Passphrases should be secure! (>=10 chars)" if times >= 2
|
92
|
+
passphrase = Dotgpg.read_passphrase("Passphrase: ")
|
93
|
+
end
|
94
|
+
until confirmation && confirmation.length >= 10
|
95
|
+
confirmation = Dotgpg.read_passphrase("Passphrase confirmation: ")
|
96
|
+
end
|
97
|
+
end
|
98
|
+
passphrase
|
99
|
+
end
|
100
|
+
|
101
|
+
def existing_private_keys
|
102
|
+
GPGME::Key.find(:secret)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
This directory contains GPG-encrypted files managed by `dotgpg`.
|
2
|
+
|
3
|
+
Getting started
|
4
|
+
---------------
|
5
|
+
|
6
|
+
To read files in this directory, send the output of running `dotgpg key` to someone
|
7
|
+
who has access already. They will be able to run `dotgpg add` on your behalf.
|
8
|
+
|
9
|
+
Usage
|
10
|
+
-----
|
11
|
+
|
12
|
+
You can edit any file with `dotgpg edit FILE`, and read any file with `dotgpg cat FILE`.
|
13
|
+
|
14
|
+
The edit command looks at the value of `$EDITOR`, the internet will have a tutorial on
|
15
|
+
how to set this up with your favourite editor.
|
16
|
+
|
17
|
+
Details
|
18
|
+
-------
|
19
|
+
|
20
|
+
For more information, please see `dotgpg --help`, or the [README](https://github.com/ConradIrwin/dotgpg).
|
data/spec/cli_spec.rb
ADDED
@@ -0,0 +1,255 @@
|
|
1
|
+
require "./spec/helper"
|
2
|
+
|
3
|
+
describe Dotgpg::Cli do
|
4
|
+
before do
|
5
|
+
@dotgpg = Dotgpg::Cli.new
|
6
|
+
end
|
7
|
+
|
8
|
+
describe "init" do
|
9
|
+
it "should default to the current directory" do
|
10
|
+
$fixture.join("create-0").mkdir
|
11
|
+
Dir.chdir $fixture.join("create-0") do
|
12
|
+
@dotgpg.invoke(:init, [])
|
13
|
+
assert $fixture.join("create-0", ".gpg", "test@example.com").exist?
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should create a .gpg directory" do
|
18
|
+
refute $fixture.join("create-1", ".gpg").exist?
|
19
|
+
@dotgpg.invoke(:init, [($fixture + "create-1").to_s])
|
20
|
+
assert $fixture.join("create-1", ".gpg").exist?
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should add the user's secret key to the .gpg directory" do
|
24
|
+
@dotgpg.invoke(:init, [($fixture + "create-2").to_s])
|
25
|
+
assert_equal $fixture.join("create-2", ".gpg", "test@example.com").read, GPGME::Key.find(:secret).first.export(armor: true).to_s
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should add a README to the directory" do
|
29
|
+
@dotgpg.invoke(:init, [($fixture + "create-3").to_s])
|
30
|
+
assert_equal $fixture.join("create-3", ".gpg", "test@example.com").read, GPGME::Key.find(:secret).first.export(armor: true).to_s
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should fail if the .gpg directory already exists" do
|
34
|
+
FileUtils.mkdir_p $fixture + "create-4" + ".gpg"
|
35
|
+
assert_fails(/\.gpg already exists/) do
|
36
|
+
@dotgpg.invoke(:init, [($fixture + "create-4").to_s])
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
it "can succeed if the directory itself already exists" do
|
41
|
+
FileUtils.mkdir_p $fixture + "create-5"
|
42
|
+
@dotgpg.invoke(:init, [($fixture + "create-5").to_s])
|
43
|
+
assert $fixture.join("create-5", ".gpg").exist?
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe "key" do
|
48
|
+
it "should output the secret key" do
|
49
|
+
assert_outputs GPGME::Key.find(:secret).first.export(armor: true).to_s do
|
50
|
+
@dotgpg.invoke(:key)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe "add" do
|
56
|
+
before do
|
57
|
+
@path = $fixture + rand.to_s.gsub(".", "")
|
58
|
+
@path.mkdir
|
59
|
+
Dir.chdir @path do
|
60
|
+
Dotgpg::Cli.new.invoke(:init, [])
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should add the specified key" do
|
65
|
+
key_path = ($fixture + "add1.key").to_s
|
66
|
+
Dir.chdir @path do
|
67
|
+
@dotgpg.invoke(:add, [key_path])
|
68
|
+
end
|
69
|
+
assert Dotgpg::Dir.new(@path).has_key? Dotgpg::Key.read(File.read(key_path))
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should abort if the current working directory is not dotgpg" do
|
73
|
+
key_path = ($fixture + "add1.key").to_s
|
74
|
+
assert_fails(/not in a dotgpg directory/) do
|
75
|
+
@dotgpg.invoke(:add, [key_path])
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should abort if the key cannot be read" do
|
80
|
+
key_path = ($fixture + "no-add1.key").to_s
|
81
|
+
Dir.chdir @path do
|
82
|
+
assert_fails(/no-add1.key: not a valid GPG key/) do
|
83
|
+
@dotgpg.invoke(:add, [key_path])
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
it "should abort if the key already exists" do
|
89
|
+
key_path = ($fixture + "add1.key").to_s
|
90
|
+
Dir.chdir @path do
|
91
|
+
Dotgpg::Cli.new.invoke(:add, [key_path])
|
92
|
+
|
93
|
+
assert_fails(/add1@example.com: already exists/) do
|
94
|
+
@dotgpg.invoke(:add, [key_path])
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
it "should do nothing if the key exists and --force is specified" do
|
100
|
+
key_path = ($fixture + "add1.key").to_s
|
101
|
+
Dir.chdir @path do
|
102
|
+
Dotgpg::Cli.new.invoke(:add, [key_path])
|
103
|
+
|
104
|
+
@dotgpg.invoke(:add, [key_path], force: true)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
describe "rm" do
|
110
|
+
before do
|
111
|
+
@path = $fixture + rand.to_s.gsub(".", "")
|
112
|
+
@path.mkdir
|
113
|
+
Dir.chdir @path do
|
114
|
+
Dotgpg::Cli.new.invoke(:init, [])
|
115
|
+
Dotgpg::Cli.new.invoke(:add, [($fixture + "add1.key").to_s])
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
it "should remove the specified key" do
|
120
|
+
Dir.chdir @path do
|
121
|
+
@dotgpg.invoke :rm, [".gpg/add1@example.com"]
|
122
|
+
end
|
123
|
+
refute Dotgpg::Dir.new(@path).has_key? Dotgpg::Key.read(File.read($fixture + "add1.key"))
|
124
|
+
end
|
125
|
+
|
126
|
+
it "should find the key by email" do
|
127
|
+
Dir.chdir @path do
|
128
|
+
@dotgpg.invoke :rm, ["add1@example.com"]
|
129
|
+
end
|
130
|
+
refute Dotgpg::Dir.new(@path).has_key? Dotgpg::Key.read(File.read($fixture + "add1.key"))
|
131
|
+
end
|
132
|
+
|
133
|
+
it "should abort if the key doesn't exist" do
|
134
|
+
Dir.chdir @path do
|
135
|
+
assert_fails(/add2@example.com: not a valid GPG key/) do
|
136
|
+
@dotgpg.invoke :rm, ["add2@example.com"]
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
it "should abort if the key is the user's secret key" do
|
142
|
+
Dir.chdir @path do
|
143
|
+
assert_fails(/test@example.com: refusing to remove your own secret key/) do
|
144
|
+
@dotgpg.invoke :rm, ["test@example.com"]
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
it "should do nothing if they key doesn't exist and --force is specified" do
|
150
|
+
Dir.chdir @path do
|
151
|
+
@dotgpg.invoke :rm, ["add2@example.com"], force: true
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
it "should remove a secret key if --force is given" do
|
156
|
+
key = Dotgpg::Key.read(File.read(@path + ".gpg" + "test@example.com"))
|
157
|
+
Dir.chdir @path do
|
158
|
+
@dotgpg.invoke :rm, ["test@example.com"], force: true
|
159
|
+
end
|
160
|
+
|
161
|
+
refute Dotgpg::Dir.new(@path).has_key? key
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
describe "cat" do
|
166
|
+
before do
|
167
|
+
Dotgpg.passphrase = 'test'
|
168
|
+
|
169
|
+
@path = $fixture + rand.to_s.gsub(".", "")
|
170
|
+
Dotgpg::Cli.new.invoke(:init, [@path.to_s])
|
171
|
+
Dotgpg::Dir.new(@path).encrypt @path + "a", "Test\n"
|
172
|
+
end
|
173
|
+
|
174
|
+
it "should cat an existing encrypted file" do
|
175
|
+
assert_outputs "Test\n" do
|
176
|
+
@dotgpg.invoke :cat, [(@path + "a").to_s]
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
it "should warn if a file doesn't exist" do
|
181
|
+
assert_warns "#{@path + "b"}: No such file or directory" do
|
182
|
+
@dotgpg.invoke :cat, [(@path + "b").to_s]
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
it "should cat the existing files if a mixture is specified" do
|
187
|
+
assert_outputs "Test\n" do
|
188
|
+
assert_warns "#{@path + "b"}: No such file or directory" do
|
189
|
+
@dotgpg.invoke :cat, [(@path + "b").to_s, (@path + "a").to_s]
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
it "should fail if the file is not in a .gpg directory" do
|
195
|
+
assert_fails "not in a dotgpg directory" do
|
196
|
+
@dotgpg.invoke :cat, ["/tmp/b"]
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
it "should fail if the passphrase is wrong" do
|
201
|
+
Dotgpg.passphrase = 'wrong'
|
202
|
+
assert_fails "Bad passphrase" do
|
203
|
+
@dotgpg.invoke :cat, [(@path + "a").to_s]
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
describe "edit" do
|
209
|
+
before do
|
210
|
+
Dotgpg.passphrase = 'test'
|
211
|
+
|
212
|
+
@path = $fixture + rand.to_s.gsub(".", "")
|
213
|
+
Dotgpg::Cli.new.invoke(:init, [@path.to_s])
|
214
|
+
Dotgpg::Dir.new(@path).encrypt @path + "a", "Bad test\n"
|
215
|
+
|
216
|
+
end
|
217
|
+
|
218
|
+
it "should let you edit an existing file" do
|
219
|
+
ENV['EDITOR'] = "sed -i '' s/Bad/Good/"
|
220
|
+
path = (@path + "a").to_s
|
221
|
+
@dotgpg.invoke(:edit, [path])
|
222
|
+
assert_outputs "Good test\n" do
|
223
|
+
Dotgpg::Dir.new(@path).decrypt path, $stdout
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
it "should open a non-existing file as blank" do
|
228
|
+
ENV['EDITOR'] = "ruby -e 'File.write(ARGV[0], %(Good test\n)) if File.read(ARGV[0]) == %()'"
|
229
|
+
path = (@path + "b").to_s
|
230
|
+
@dotgpg.invoke(:edit, [path])
|
231
|
+
assert_outputs "Good test\n" do
|
232
|
+
Dotgpg::Dir.new(@path).decrypt path, $stdout
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
it "should warn if a file cannot be decrypted" do
|
237
|
+
File.write(@path + "d", "not encrypted...")
|
238
|
+
path = (@path + "d").to_s
|
239
|
+
assert_warns "#{@path + "d"}: No data" do
|
240
|
+
@dotgpg.invoke(:edit, [path])
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
it "should fail if invoking the editor doesn't work" do
|
245
|
+
ENV['EDITOR'] = 'not-an-editor'
|
246
|
+
assert_fails "Problem with editor. Not saving changes" do
|
247
|
+
@dotgpg.invoke :edit, [(@path + "a").to_s]
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
it "should edit the existing files if a mixture is specified" do
|
252
|
+
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|