zanzibar 0.1.9 → 0.1.10

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- MGVkMGM2YjU2MjA3YTAyZjQ2NDBkZTUxNjkzZTdhZDk1NGFmMjdmZg==
4
+ Y2NmMTgxMGFhODNjYWQ4MWNmNjY3MWFjZWZiMGYyZjIzMzgwYzc1Yw==
5
5
  data.tar.gz: !binary |-
6
- OGVkZTA1ZjljMjVkYTA1MzMwMTRiODIzYmM1NzBiODQzMmRiMWU1OQ==
6
+ ZDZhYThhNTk3MTk4MTc1MzJjYjlhMDQzMzI1YmI1Yjc3MTZlNDJkZg==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- MmJhMzQ3NDczMTU0YWQyNTIwODJmNjAzNzg4MDBjY2M3NDM3ODY2N2MwOGY4
10
- MWE1NTMwNjBlYmIyYTdmMGM3NDBkNjg5MzE0N2I5YTJmMjAzOTFjNmRlZWI5
11
- MTE2YjJjY2IzOTViMzhkNDEwOTllMDE1MzFkN2NhODBkMzc3MmU=
9
+ Y2Y3NDM5M2E3MDM3NTZkOGE4OWQ0YTZkZThhNmMxNjcxNzU3MjcwNTEzYzAw
10
+ MWNlNGY3MTdmODdjZGIxY2VjMzliM2JlYzJlYTZjMzQ1YTBmYTkxYzZjYmNi
11
+ ZDIzNzIzZGEwNWQwMDczMjVjY2JkOTE1Nzk5N2RmM2MzODIxNDE=
12
12
  data.tar.gz: !binary |-
13
- YjMwMjI1YzhkOThjN2FhMGFjZjYyZjY4MmJmODY2MmU1NzY5MjU2NGJmMjBi
14
- ZTAwYmQ3NWY5OGU4NzQxMjZiZjkyOWVkMGI0MmFkNDg4NGEwOWJlNGJhODk2
15
- Zjc2NjUyYmE0MDk1YjM2YzZiZDFjOTA4MzQ0OTJmZWJlMGMzN2U=
13
+ ZGFkNDI2NDNlMjBiZDI4ZjYzNDdlMzZkMmE4ZGI1NDc4MTk0ZjRmODMyZWU5
14
+ NDdhOTg4NjA3YTNiMDg1MDhlYjRhMWVkODI3NzM1NTc4MmQ0MGMyNDM4OGJk
15
+ YjA3YzVmODI5YzkxZDU0NzMyYzM5MDQ2NTcxNTg1YmVlY2FlZTk=
data/lib/zanzibar.rb CHANGED
@@ -1,210 +1,188 @@
1
- require "zanzibar/version"
2
- require 'savon'
3
- require 'io/console'
4
- require 'fileutils'
5
-
6
- module Zanzibar
7
-
8
- ##
9
- # Class for interacting with Secret Server
10
- class Zanzibar
11
-
12
- ##
13
- # @param args{:domain, :wsdl, :pwd, :username, :globals{}}
14
-
15
- def initialize(args = {})
16
-
17
- if args[:username]
18
- @@username = args[:username]
19
- else
20
- @@username = ENV['USER']
21
- end
22
-
23
- if args[:wsdl]
24
- @@wsdl = args[:wsdl]
25
- else
26
- @@wsdl = get_wsdl_location
27
- end
28
- if args[:pwd]
29
- @@password = args[:pwd]
30
- else
31
- @@password = prompt_for_password
32
- end
33
- if args[:domain]
34
- @@domain = args[:domain]
35
- else
36
- @@domain = prompt_for_domain
37
- end
38
- args[:globals] = {} unless args[:globals]
39
- init_client(args[:globals])
40
- end
41
-
42
- ## Initializes the Savon client class variable with the wdsl document location and optional global variables
43
- # @param globals{}, optional
44
-
45
- def init_client(globals = {})
46
- globals = {} if globals == nil
47
- @@client = Savon.client(globals) do
48
- wsdl @@wsdl
49
- end
50
- end
51
-
52
- ## Gets the user's password if none is provided in the constructor.
53
- # @return [String] the password for the current user
54
-
55
- def prompt_for_password
56
- puts "Please enter password for #{@@username}:"
57
- return STDIN.noecho(&:gets).chomp
58
- end
59
-
60
- ## Gets the wsdl document location if none is provided in the constructor
61
- # @return [String] the location of the WDSL document
62
-
63
- def get_wsdl_location
64
- puts "Enter the URL of the Secret Server WSDL:"
65
- return STDIN.gets.chomp
66
- end
67
-
68
- ## Gets the domain of the Secret Server installation if none is provided in the constructor
69
- # @return [String] the domain of the secret server installation
70
-
71
- def prompt_for_domain
72
- puts "Enter the domain of your Secret Server:"
73
- return STDIN.gets.chomp
74
- end
75
-
76
-
77
- ## Get an authentication token for interacting with Secret Server. These are only good for about 10 minutes so just get a new one each time.
78
- # Will raise an error if there is an issue with the authentication.
79
- # @return the authentication token for the current user.
80
-
81
- def get_token
82
- begin
83
- response = @@client.call(:authenticate, message: { username: @@username, password: @@password, organization: "", domain: @@domain }).hash
84
- if response[:envelope][:body][:authenticate_response][:authenticate_result][:errors]
85
- raise "Error generating the authentication token for user #{@@username}: #{response[:envelope][:body][:authenticate_response][:authenticate_result][:errors][:string]}"
86
- end
87
- response[:envelope][:body][:authenticate_response][:authenticate_result][:token]
88
- rescue Savon::Error => err
89
- raise "There was an error generating the authentiaton token for user #{@@username}: #{err}"
90
- end
91
- end
92
-
93
- ## Get a secret returned as a hash
94
- # Will raise an error if there was an issue getting the secret
95
- # @param [Integer] the secret id
96
- # @return [Hash] the secret hash retrieved from the wsdl
97
-
98
- def get_secret(scrt_id, token = nil)
99
- begin
100
- secret = @@client.call(:get_secret, message: { token: token || get_token, secretId: scrt_id}).hash
101
- if secret[:envelope][:body][:get_secret_response][:get_secret_result][:errors]
102
- raise "There was an error getting secret #{scrt_id}: #{secret[:envelope][:body][:get_secret_response][:get_secret_result][:errors][:string]}"
103
- end
104
- return secret
105
- rescue Savon::Error => err
106
- raise "There was an error getting the secret with id #{scrt_id}: #{err}"
107
- end
108
- end
109
-
110
- ## Retrieve a simple password from a secret
111
- # Will raise an error if there are any issues
112
- # @param [Integer] the secret id
113
- # @return [String] the password for the given secret
114
-
115
- def get_password(scrt_id)
116
- begin
117
- secret = get_secret(scrt_id)
118
- secret_items = secret[:envelope][:body][:get_secret_response][:get_secret_result][:secret][:items][:secret_item]
119
- return get_secret_item_by_field_name(secret_items,"Password")[:value]
120
- rescue Savon::Error => err
121
- raise "There was an error getting the password for secret #{scrt_id}: #{err}"
122
- end
123
- end
124
-
125
- def get_secret_item_by_field_name(secret_items, field_name)
126
- secret_items.each do |item|
127
- return item if item[:field_name] == field_name
128
- end
129
- end
130
-
131
- ## Get the secret item id that relates to a key file or attachment.
132
- # Will raise on error
133
- # @param [Integer] the secret id
134
- # @param [String] the type of secret item to get, one of privatekey, publickey, attachment
135
- # @return [Integer] the secret item id
136
-
137
- def get_scrt_item_id(scrt_id, type, token)
138
- secret = get_secret(scrt_id, token)
139
- secret_items = secret[:envelope][:body][:get_secret_response][:get_secret_result][:secret][:items][:secret_item]
140
- begin
141
- return get_secret_item_by_field_name(secret_items, type)[:id]
142
- rescue
143
- raise "Unknown type, #{type}."
144
- end
145
- end
146
-
147
- ## Downloads the private key for a secret and places it where Zanzibar is running, or :path if specified
148
- # Raise on error
149
- # @param [Hash] args, :scrt_id, :scrt_item_id - optional, :path - optional
150
-
151
- def download_private_key(args = {})
152
- token = get_token
153
- FileUtils.mkdir_p(args[:path]) if args[:path]
154
- path = args[:path] ? args[:path] : '.' ## The File.join below doesn't handle nils well, so let's take that possibility away.
155
- begin
156
- response = @@client.call(:download_file_attachment_by_item_id, message: { token: token, secretId: args[:scrt_id], secretItemId: args[:scrt_item_id] || get_scrt_item_id(args[:scrt_id], 'Private Key', token)}).hash
157
- if response[:envelope][:body][:download_file_attachment_by_item_id_response][:download_file_attachment_by_item_id_result][:errors]
158
- raise "There was an error getting the private key for secret #{args[:scrt_id]}: #{response[:envelope][:body][:download_file_attachment_by_item_id_response][:download_file_attachment_by_item_id_result][:string]}"
159
- end
160
- File.open(File.join(path, response[:envelope][:body][:download_file_attachment_by_item_id_response][:download_file_attachment_by_item_id_result][:file_name]), 'wb') do |file|
161
- file.puts Base64.decode64(response[:envelope][:body][:download_file_attachment_by_item_id_response][:download_file_attachment_by_item_id_result][:file_attachment])
162
- end
163
- rescue Savon::Error => err
164
- raise "There was an error getting the private key for secret #{args[:scrt_id]}: #{err}"
165
- end
166
- end
167
-
168
- ## Downloads the public key for a secret and places it where Zanzibar is running, or :path if specified
169
- # Raise on error
170
- # @param [Hash] args, :scrt_id, :scrt_item_id - optional, :path - optional
171
-
172
- def download_public_key(args = {})
173
- token = get_token
174
- FileUtils.mkdir_p(args[:path]) if args[:path]
175
- path = args[:path] ? args[:path] : '.' ## The File.join below doesn't handle nils well, so let's take that possibility away.
176
- begin
177
- response = @@client.call(:download_file_attachment_by_item_id, message: { token: token, secretId: args[:scrt_id], secretItemId: args[:scrt_item_id] || get_scrt_item_id(args[:scrt_id], 'Public Key', token)}).hash
178
- if response[:envelope][:body][:download_file_attachment_by_item_id_response][:download_file_attachment_by_item_id_result][:errors]
179
- raise "There was an error getting the public key for secret #{args[:scrt_id]}: #{response[:envelope][:body][:download_file_attachment_by_item_id_response][:download_file_attachment_by_item_id_result][:string]}"
180
- end
181
- File.open(File.join(path, response[:envelope][:body][:download_file_attachment_by_item_id_response][:download_file_attachment_by_item_id_result][:file_name]), 'wb') do |file|
182
- file.puts Base64.decode64(response[:envelope][:body][:download_file_attachment_by_item_id_response][:download_file_attachment_by_item_id_result][:file_attachment])
183
- end
184
- rescue Savon::Error => err
185
- raise "There was an error getting the public key for secret #{args[:scrt_id]}: #{err}"
186
- end
187
- end
188
-
189
- ## Downloads an attachment for a secret and places it where Zanzibar is running, or :path if specified
190
- # Raise on error
191
- # @param [Hash] args, :scrt_id, :scrt_item_id - optional, :path - optional
192
-
193
- def download_attachment(args = {})
194
- token = get_token
195
- FileUtils.mkdir_p(args[:path]) if args[:path]
196
- path = args[:path] ? args[:path] : '.' ## The File.join below doesn't handle nils well, so let's take that possibility away.
197
- begin
198
- response = @@client.call(:download_file_attachment_by_item_id, message: { token: token, secretId: args[:scrt_id], secretItemId: args[:scrt_item_id] || get_scrt_item_id(args[:scrt_id], 'Attachment', token)}).hash
199
- if response[:envelope][:body][:download_file_attachment_by_item_id_response][:download_file_attachment_by_item_id_result][:errors]
200
- raise "There was an error getting the attachment for secret #{args[:scrt_id]}: #{response[:envelope][:body][:download_file_attachment_by_item_id_response][:download_file_attachment_by_item_id_result][:string]}"
201
- end
202
- File.open(File.join(path, response[:envelope][:body][:download_file_attachment_by_item_id_response][:download_file_attachment_by_item_id_result][:file_name]), 'wb') do |file|
203
- file.puts Base64.decode64(response[:envelope][:body][:download_file_attachment_by_item_id_response][:download_file_attachment_by_item_id_result][:file_attachment])
204
- end
205
- rescue Savon::Error => err
206
- raise "There was an error getting the attachment from secret #{args[:scrt_id]}: #{err}"
207
- end
208
- end
209
- end
210
- end
1
+ require "zanzibar/version"
2
+ require 'savon'
3
+ require 'io/console'
4
+ require 'fileutils'
5
+
6
+ module Zanzibar
7
+
8
+ ##
9
+ # Class for interacting with Secret Server
10
+ class Zanzibar
11
+
12
+ ##
13
+ # @param args{:domain, :wsdl, :pwd, :username, :globals{}}
14
+
15
+ def initialize(args = {})
16
+
17
+ if args[:username]
18
+ @@username = args[:username]
19
+ else
20
+ @@username = ENV['USER']
21
+ end
22
+
23
+ if args[:wsdl]
24
+ @@wsdl = args[:wsdl]
25
+ else
26
+ @@wsdl = get_wsdl_location
27
+ end
28
+ if args[:pwd]
29
+ @@password = args[:pwd]
30
+ else
31
+ @@password = prompt_for_password
32
+ end
33
+ if args[:domain]
34
+ @@domain = args[:domain]
35
+ else
36
+ @@domain = prompt_for_domain
37
+ end
38
+ args[:globals] = {} unless args[:globals]
39
+ init_client(args[:globals])
40
+ end
41
+
42
+ ## Initializes the Savon client class variable with the wdsl document location and optional global variables
43
+ # @param globals{}, optional
44
+
45
+ def init_client(globals = {})
46
+ globals = {} if globals == nil
47
+ @@client = Savon.client(globals) do
48
+ wsdl @@wsdl
49
+ end
50
+ end
51
+
52
+ ## Gets the user's password if none is provided in the constructor.
53
+ # @return [String] the password for the current user
54
+
55
+ def prompt_for_password
56
+ puts "Please enter password for #{@@username}:"
57
+ return STDIN.noecho(&:gets).chomp
58
+ end
59
+
60
+ ## Gets the wsdl document location if none is provided in the constructor
61
+ # @return [String] the location of the WDSL document
62
+
63
+ def prompt_for_wsdl_location
64
+ puts "Enter the URL of the Secret Server WSDL:"
65
+ return STDIN.gets.chomp
66
+ end
67
+
68
+ ## Gets the domain of the Secret Server installation if none is provided in the constructor
69
+ # @return [String] the domain of the secret server installation
70
+
71
+ def prompt_for_domain
72
+ puts "Enter the domain of your Secret Server:"
73
+ return STDIN.gets.chomp
74
+ end
75
+
76
+
77
+ ## Get an authentication token for interacting with Secret Server. These are only good for about 10 minutes so just get a new one each time.
78
+ # Will raise an error if there is an issue with the authentication.
79
+ # @return the authentication token for the current user.
80
+
81
+ def get_token
82
+ begin
83
+ response = @@client.call(:authenticate, message: { username: @@username, password: @@password, organization: "", domain: @@domain })
84
+ .hash[:envelope][:body][:authenticate_response][:authenticate_result]
85
+ raise "Error generating the authentication token for user #{@@username}: #{response[:errors][:string]}" if response[:errors]
86
+ response[:token]
87
+ rescue Savon::Error => err
88
+ raise "There was an error generating the authentiaton token for user #{@@username}: #{err}"
89
+ end
90
+ end
91
+
92
+ ## Get a secret returned as a hash
93
+ # Will raise an error if there was an issue getting the secret
94
+ # @param [Integer] the secret id
95
+ # @return [Hash] the secret hash retrieved from the wsdl
96
+
97
+ def get_secret(scrt_id, token = nil)
98
+ begin
99
+ secret = @@client.call(:get_secret, message: { token: token || get_token, secretId: scrt_id}).hash[:envelope][:body][:get_secret_response][:get_secret_result]
100
+ raise "There was an error getting secret #{scrt_id}: #{secret[:errors][:string]}" if secret[:errors]
101
+ return secret
102
+ rescue Savon::Error => err
103
+ raise "There was an error getting the secret with id #{scrt_id}: #{err}"
104
+ end
105
+ end
106
+
107
+ ## Retrieve a simple password from a secret
108
+ # Will raise an error if there are any issues
109
+ # @param [Integer] the secret id
110
+ # @return [String] the password for the given secret
111
+
112
+ def get_password(scrt_id)
113
+ begin
114
+ secret = get_secret(scrt_id)
115
+ secret_items = secret[:secret][:items][:secret_item]
116
+ return get_secret_item_by_field_name(secret_items,"Password")[:value]
117
+ rescue Savon::Error => err
118
+ raise "There was an error getting the password for secret #{scrt_id}: #{err}"
119
+ end
120
+ end
121
+
122
+ def write_secret_to_file(path, secret_response)
123
+ File.open(File.join(path, secret_response[:file_name]), 'wb') do |file|
124
+ file.puts Base64.decode64(secret_response[:file_attachment])
125
+ end
126
+ end
127
+
128
+ def get_secret_item_by_field_name(secret_items, field_name)
129
+ secret_items.each do |item|
130
+ return item if item[:field_name] == field_name
131
+ end
132
+ end
133
+
134
+ ## Get the secret item id that relates to a key file or attachment.
135
+ # Will raise on error
136
+ # @param [Integer] the secret id
137
+ # @param [String] the type of secret item to get, one of privatekey, publickey, attachment
138
+ # @return [Integer] the secret item id
139
+
140
+ def get_scrt_item_id(scrt_id, type, token)
141
+ secret = get_secret(scrt_id, token)
142
+ secret_items = secret[:secret][:items][:secret_item]
143
+ begin
144
+ return get_secret_item_by_field_name(secret_items, type)[:id]
145
+ rescue
146
+ raise "Unknown type, #{type}."
147
+ end
148
+ end
149
+
150
+ ## Downloads a file for a secret and places it where Zanzibar is running, or :path if specified
151
+ # Raise on error
152
+ # @param [Hash] args, :scrt_id, :type (one of "Private Key", "Public Key", "Attachment"), :scrt_item_id - optional, :path - optional
153
+
154
+
155
+ def download_secret_file(args = {})
156
+ token = get_token
157
+ FileUtils.mkdir_p(args[:path]) if args[:path]
158
+ path = args[:path] ? args[:path] : '.' ## The File.join below doesn't handle nils well, so let's take that possibility away.
159
+ begin
160
+ response = @@client.call(:download_file_attachment_by_item_id, message:
161
+ { token: token, secretId: args[:scrt_id], secretItemId: args[:scrt_item_id] || get_scrt_item_id(args[:scrt_id], args[:type], token)})
162
+ .hash[:envelope][:body][:download_file_attachment_by_item_id_response][:download_file_attachment_by_item_id_result]
163
+ raise "There was an error getting the #{args[:type]} for secret #{args[:scrt_id]}: #{response[:errors][:string]}" if response[:errors]
164
+ write_secret_to_file(path, response)
165
+ rescue Savon::Error => err
166
+ raise "There was an error getting the #{args[:type]} for secret #{args[:scrt_id]}: #{err}"
167
+ end
168
+ end
169
+
170
+
171
+ ## Methods to maintain backwards compatibility
172
+ def download_private_key(args = {})
173
+ args[:type] = 'Private Key'
174
+ download_secret_file(args)
175
+ end
176
+
177
+ def download_public_key(args = {})
178
+ args[:type] = 'Public Key'
179
+ download_secret_file(args)
180
+ end
181
+
182
+ def download_attachment(args = {})
183
+ args[:type] = 'Attachment'
184
+ download_secret_file(args)
185
+ end
186
+
187
+ end
188
+ end
@@ -1,3 +1,3 @@
1
1
  module Zanzibar
2
- VERSION = "0.1.9"
2
+ VERSION = "0.1.10"
3
3
  end
@@ -30,7 +30,7 @@ describe "Zanzibar Test" do
30
30
  to_return(:body => auth_xml, :status => 200).then.
31
31
  to_return(:body => secret_xml, :status => 200)
32
32
 
33
- expect(client.get_secret(1234)[:envelope][:body][:get_secret_response][:get_secret_result][:secret][:name]).to eq("Zanzi Test Secret")
33
+ expect(client.get_secret(1234)[:secret][:name]).to eq("Zanzi Test Secret")
34
34
  end
35
35
 
36
36
  it 'should get a password' do
@@ -47,12 +47,24 @@ describe "Zanzibar Test" do
47
47
  to_return(:body => secret_with_key_xml, :status => 200).then.
48
48
  to_return(:body => private_key_xml, :status => 200)
49
49
 
50
- client.download_private_key(:scrt_id => 2345)
50
+ client.download_secret_file(:scrt_id => 2345, :type => 'Private Key')
51
51
  expect(File.exist? 'zanzi_key')
52
52
  expect(File.read('zanzi_key')).to eq("-----BEGIN RSA PRIVATE KEY -----\nzanzibarTestPassword\n-----END RSA PRIVATE KEY-----\n")
53
53
  File.delete('zanzi_key')
54
54
  end
55
55
 
56
+ it 'should download a private key legacy' do
57
+ stub_request(:any, "https://www.zanzitest.net/webservices/sswebservice.asmx").
58
+ to_return(:body => auth_xml, :status => 200).then.
59
+ to_return(:body => secret_with_key_xml, :status => 200).then.
60
+ to_return(:body => private_key_xml, :status => 200)
61
+
62
+ client.download_private_key(:scrt_id => 2345)
63
+ expect(File.exist? 'zanzi_key')
64
+ expect(File.read('zanzi_key')).to eq("-----BEGIN RSA PRIVATE KEY -----\nzanzibarTestPassword\n-----END RSA PRIVATE KEY-----\n")
65
+ File.delete('zanzi_key')
66
+ end
67
+
56
68
 
57
69
  it 'should download a public key' do
58
70
  stub_request(:any, "https://www.zanzitest.net/webservices/sswebservice.asmx").
@@ -60,18 +72,42 @@ describe "Zanzibar Test" do
60
72
  to_return(:body => secret_with_key_xml, :status => 200).then.
61
73
  to_return(:body => public_key_xml, :status => 200)
62
74
 
63
- client.download_public_key(:scrt_id => 2345)
75
+ client.download_secret_file(:scrt_id => 2345, :type => 'Public Key')
64
76
  expect(File.exist? 'zanzi_key.pub')
65
77
  expect(File.read('zanzi_key.pub')).to eq("1234PublicKey5678==\n")
66
78
  File.delete('zanzi_key.pub')
67
79
  end
68
80
 
81
+ it 'should download a public key legacy' do
82
+ stub_request(:any, "https://www.zanzitest.net/webservices/sswebservice.asmx").
83
+ to_return(:body => auth_xml, :status => 200).then.
84
+ to_return(:body => secret_with_key_xml, :status => 200).then.
85
+ to_return(:body => public_key_xml, :status => 200)
86
+
87
+ client.download_public_key(:scrt_id => 2345)
88
+ expect(File.exist? 'zanzi_key.pub')
89
+ expect(File.read('zanzi_key.pub')).to eq("1234PublicKey5678==\n")
90
+ File.delete('zanzi_key.pub')
91
+ end
92
+
69
93
  it 'should download an attachment' do
70
94
  stub_request(:any, "https://www.zanzitest.net/webservices/sswebservice.asmx").
71
95
  to_return(:body => auth_xml, :status => 200).then.
72
96
  to_return(:body => secret_with_attachment_xml, :status => 200).then.
73
97
  to_return(:body => attachment_xml, :status => 200)
74
98
 
99
+ client.download_secret_file(:scrt_id => 3456, :type => 'Attachment')
100
+ expect(File.exist? 'attachment.txt')
101
+ expect(File.read('attachment.txt')).to eq("I am a secret attachment\n")
102
+ File.delete('attachment.txt')
103
+ end
104
+
105
+ it 'should download an attachment legacy' do
106
+ stub_request(:any, "https://www.zanzitest.net/webservices/sswebservice.asmx").
107
+ to_return(:body => auth_xml, :status => 200).then.
108
+ to_return(:body => secret_with_attachment_xml, :status => 200).then.
109
+ to_return(:body => attachment_xml, :status => 200)
110
+
75
111
  client.download_attachment(:scrt_id => 3456)
76
112
  expect(File.exist? 'attachment.txt')
77
113
  expect(File.read('attachment.txt')).to eq("I am a secret attachment\n")
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: zanzibar
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.9
4
+ version: 0.1.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jason Davis-Cooke