condo 1.0.4 → 1.0.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. checksums.yaml +7 -0
  2. data/README.textile +133 -133
  3. data/app/assets/javascripts/condo.js +9 -6
  4. data/app/assets/javascripts/condo/amazon.js +403 -406
  5. data/app/assets/javascripts/condo/condo.js +184 -0
  6. data/app/assets/javascripts/condo/config.js +69 -80
  7. data/app/assets/javascripts/condo/google.js +338 -255
  8. data/app/assets/javascripts/condo/md5/hash.worker.emulator.js +23 -23
  9. data/app/assets/javascripts/condo/md5/hash.worker.js +11 -11
  10. data/app/assets/javascripts/condo/md5/hasher.js +119 -100
  11. data/app/assets/javascripts/condo/md5/spark-md5.js +276 -161
  12. data/app/assets/javascripts/condo/rackspace.js +326 -329
  13. data/app/assets/javascripts/condo/{abstract-md5.js.erb → services/abstract-md5.js.erb} +86 -93
  14. data/app/assets/javascripts/condo/{base64.js → services/base64.js} +2 -10
  15. data/app/assets/javascripts/condo/services/broadcaster.js +26 -0
  16. data/app/assets/javascripts/condo/services/uploader.js +302 -0
  17. data/app/assets/javascripts/core/core.js +4 -0
  18. data/app/assets/javascripts/core/services/1-safe-apply.js +17 -0
  19. data/app/assets/javascripts/core/services/2-messaging.js +171 -0
  20. data/lib/condo.rb +269 -269
  21. data/lib/condo/configuration.rb +137 -139
  22. data/lib/condo/errors.rb +8 -8
  23. data/lib/condo/strata/amazon_s3.rb +301 -301
  24. data/lib/condo/strata/google_cloud_storage.rb +315 -314
  25. data/lib/condo/strata/rackspace_cloud_files.rb +245 -223
  26. data/lib/condo/version.rb +1 -1
  27. metadata +21 -44
  28. data/app/assets/javascripts/condo/broadcaster.js +0 -60
  29. data/app/assets/javascripts/condo/controller.js +0 -194
  30. data/app/assets/javascripts/condo/uploader.js +0 -310
  31. data/test/dummy/db/test.sqlite3 +0 -0
  32. data/test/dummy/log/test.log +0 -25
@@ -1,314 +1,315 @@
1
- module Condo; end
2
- module Condo::Strata; end
3
-
4
-
5
- class Fog::Storage::Google::Real
6
- def condo_request(*args)
7
- request(*args)
8
- end
9
- end
10
-
11
-
12
- class Condo::Strata::GoogleCloudStorage
13
-
14
- def initialize(options)
15
- @options = {
16
- :name => :GoogleCloudStorage,
17
- :location => :na, # US or Europe, set at bucket creation time
18
- :fog => {
19
- :provider => 'Google',
20
- :google_storage_access_key_id => options[:fog_access_id] || options[:access_id],
21
- :google_storage_secret_access_key => options[:fog_secret_key] || options[:secret_key]
22
- },
23
- :api => 1
24
- }.merge!(options)
25
-
26
-
27
- raise ArgumentError, 'Google Access ID missing' if @options[:access_id].nil?
28
- raise ArgumentError, 'Google Secret Key missing' if @options[:secret_key].nil?
29
-
30
- if @options[:api] == 2
31
- @options[:secret_key] = OpenSSL::PKey::RSA.new(@options[:secret_key])
32
- end
33
-
34
- @options[:location] = @options[:location].to_sym
35
- end
36
-
37
-
38
- #
39
- # Enable CORS on a bucket for a domain
40
- #
41
- def enable_cors(bucket, origin = '*')
42
- data =
43
- <<-DATA
44
- <?xml version="1.0" encoding="UTF-8"?>
45
- <CorsConfig>
46
- <Cors>
47
- <Origins>
48
- <Origin>#{origin}</Origin>
49
- </Origins>
50
- <Methods>
51
- <Method>GET</Method>
52
- <Method>HEAD</Method>
53
- <Method>POST</Method>
54
- <Method>PUT</Method>
55
- </Methods>
56
- <ResponseHeaders>
57
- <ResponseHeader>origin</ResponseHeader>
58
- <ResponseHeader>content-md5</ResponseHeader>
59
- <ResponseHeader>authorization</ResponseHeader>
60
- <ResponseHeader>x-goog-date</ResponseHeader>
61
- <ResponseHeader>x-goog-acl</ResponseHeader>
62
- <ResponseHeader>content-type</ResponseHeader>
63
- <ResponseHeader>accept</ResponseHeader>
64
- <ResponseHeader>x-goog-api-version</ResponseHeader>
65
- <ResponseHeader>x-goog-resumable</ResponseHeader>
66
- </ResponseHeaders>
67
- <MaxAgeSec>1800</MaxAgeSec>
68
- </Cors>
69
- </CorsConfig>
70
- DATA
71
-
72
- fog_connection.condo_request(
73
- :expects => 200,
74
- :body => data,
75
- :method => 'PUT',
76
- :headers => {},
77
- :host => "#{bucket}.storage.googleapis.com",
78
- :idempotent => true,
79
- :path => '?cors' # There is an issue with Fog where this isn't included as a canonical_resource
80
- )
81
- end
82
-
83
-
84
- def name
85
- @options[:name]
86
- end
87
-
88
-
89
- def location
90
- @options[:location]
91
- end
92
-
93
-
94
- #
95
- # Create a signed URL for accessing a private file
96
- #
97
- def get_object(options)
98
- options = {}.merge!(options) # Need to deep copy here
99
- options[:object_options] = {
100
- :expires => 5.minutes.from_now,
101
- :verb => :get,
102
- :headers => {},
103
- :parameters => {},
104
- :protocol => :https
105
- }.merge!(options[:object_options] || {})
106
- options.merge!(@options)
107
-
108
- #
109
- # provide the signed request
110
- #
111
- sign_request(options)[:url]
112
- end
113
-
114
-
115
- #
116
- # Creates a new upload request (either single shot or multi-part)
117
- # => Passed: bucket_name, object_key, object_options, file_size
118
- #
119
- def new_upload(options)
120
- options = {}.merge!(options) # Need to deep copy here
121
- options[:object_options] = {
122
- :permissions => :private,
123
- :expires => 5.minutes.from_now,
124
- :verb => :put, # This will be a post for resumable uploads
125
- :headers => {},
126
- :parameters => {},
127
- :protocol => :https
128
- }.merge!(options[:object_options] || {})
129
- options.merge!(@options)
130
-
131
-
132
- #
133
- # Set the access control headers
134
- #
135
- options[:object_options][:headers]['x-goog-api-version'] = @options[:api]
136
-
137
- if options[:object_options][:headers]['x-goog-acl'].nil?
138
- options[:object_options][:headers]['x-goog-acl'] = case options[:object_options][:permissions]
139
- when :public
140
- :'public-read'
141
- else
142
- :private
143
- end
144
- end
145
-
146
- options[:object_options][:headers]['Content-Md5'] = options[:file_id] if options[:file_id].present? && options[:object_options][:headers]['Content-Md5'].nil?
147
- options[:object_options][:headers]['Content-Type'] = 'binary/octet-stream' if options[:object_options][:headers]['Content-Type'].nil?
148
-
149
-
150
- #
151
- # Decide what type of request is being sent
152
- # => Currently google only supports direct uploads (no CORS resumables yet!)
153
- #
154
- return {
155
- :signature => sign_request(options),
156
- :type => :direct_upload
157
- }
158
-
159
- #
160
- # This is what we'll return when resumables work with CORS
161
- #
162
- if options[:file_size] > 2.megabytes
163
- # Resumables may not support the md5 header at this time - have to compare ETag and fail on the client side
164
- options[:object_options][:verb] = :post
165
- options[:object_options][:headers]['x-goog-resumable'] = 'start'
166
- return {
167
- :signature => sign_request(options),
168
- :type => :chunked_upload # triggers resumable
169
- }
170
- end
171
- end
172
-
173
-
174
- #
175
- # Creates a request for the byte we were up to
176
- # => doesn't work with CORS yet
177
- #
178
- def get_parts(options)
179
- options[:object_options] = {
180
- :expires => 5.minutes.from_now,
181
- :verb => :put,
182
- :headers => {},
183
- :parameters => {},
184
- :protocol => :https
185
- }.merge!(options[:object_options])
186
- options.merge!(@options)
187
-
188
- #
189
- # Set the upload and request the range of bytes we are after
190
- #
191
- options[:object_options][:parameters]['upload_id'] = options[:resumable_id]
192
- ## This can be set on the client side as it is not part of the signed request
193
- #options[:object_options][:headers]['Content-Range'] = "bytes */#{options[:file_size]}"
194
-
195
- #
196
- # provide the signed request
197
- #
198
- {
199
- :type => :parts,
200
- :signature => sign_request(options)
201
- }
202
- end
203
-
204
-
205
- #
206
- # Returns the requests for uploading parts and completing a resumable upload
207
- #
208
- def set_part(options)
209
- resp = get_parts(options)
210
- resp[:type] = :part_upload
211
- return resp
212
- end
213
-
214
-
215
- def fog_connection
216
- @fog = @fog || Fog::Storage.new(@options[:fog])
217
- return @fog
218
- end
219
-
220
-
221
- def destroy(upload)
222
- connection = fog_connection
223
- directory = connection.directories.get(upload.bucket_name) # it is assumed this exists - if not then the upload wouldn't have taken place
224
- file = directory.files.get(upload.object_key) # NOTE:: I only assume this works with resumables... should look into it
225
-
226
- return true if file.nil?
227
- return file.destroy
228
- end
229
-
230
-
231
-
232
- protected
233
-
234
-
235
-
236
- def sign_request(options)
237
-
238
- #
239
- # Build base URL
240
- #
241
- verb = options[:object_options][:verb].to_s.upcase.to_sym
242
- options[:object_options][:expires] = options[:object_options][:expires].utc.to_i
243
-
244
- url = "/#{options[:object_key]}"
245
-
246
-
247
- #
248
- # Add signed request params
249
- #
250
- other_params = ''
251
- signed_params = '?'
252
- (options[:object_options][:parameters] || {}).each do |key, value|
253
- if ['acl', 'cors', 'location', 'logging', 'requestPayment', 'torrent', 'versions', 'versioning'].include?(key)
254
- signed_params << "#{key}&"
255
- else
256
- other_params << (value.empty? ? "#{key}&" : "#{key}=#{value}&")
257
- end
258
- end
259
- signed_params.chop!
260
-
261
- url << signed_params
262
-
263
-
264
- #
265
- # Build a request signature
266
- #
267
- signature = "#{verb}\n#{options[:file_id]}\n#{options[:object_options][:headers]['Content-Type']}\n#{options[:object_options][:expires]}\n"
268
- if verb != :GET
269
- options[:object_options][:headers]['x-goog-date'] ||= Time.now.utc.httpdate
270
-
271
- google_headers, canonical_google_headers = {}, '' # Copied from https://github.com/fog/fog/blob/master/lib/fog/google/storage.rb
272
- for key, value in options[:object_options][:headers]
273
- if key[0..6] == 'x-goog-'
274
- google_headers[key] = value
275
- end
276
- end
277
-
278
- google_headers = google_headers.sort {|x, y| x[0] <=> y[0]}
279
- for key, value in google_headers
280
- signature << "#{key}:#{value}\n"
281
- end
282
- end
283
-
284
- signature << "/#{options[:bucket_name]}#{url}"
285
-
286
-
287
- #
288
- # Encode the request signature
289
- #
290
- if @options[:api] == 1
291
- signature = Base64.encode64(OpenSSL::HMAC.digest(OpenSSL::Digest::Digest.new('sha1'), @options[:secret_key], signature)).gsub("\n","")
292
- options[:object_options][:headers]['Authorization'] = "GOOG1 #{@options[:access_id]}:#{signature}"
293
- else
294
- signature = Base64.encode64(@options[:secret_key].sign(OpenSSL::Digest::SHA256.new, signature)).gsub("\n","")
295
- end
296
-
297
-
298
- url += signed_params.present? ? '&' : '?'
299
- url = "#{options[:object_options][:protocol]}://#{options[:bucket_name]}.storage.googleapis.com#{url}#{other_params}GoogleAccessId=#{@options[:access_id]}&Expires=#{options[:object_options][:expires]}&Signature=#{CGI::escape(signature)}"
300
-
301
-
302
- #
303
- # Finish building the request
304
- #
305
- return {
306
- :verb => options[:object_options][:verb].to_s.upcase,
307
- :url => url,
308
- :headers => options[:object_options][:headers]
309
- }
310
- end
311
-
312
-
313
- end
314
-
1
+ module Condo; end
2
+ module Condo::Strata; end
3
+
4
+
5
+ class Fog::Storage::Google::Real
6
+ def condo_request(*args)
7
+ request(*args)
8
+ end
9
+ end
10
+
11
+
12
+ class Condo::Strata::GoogleCloudStorage
13
+
14
+ def initialize(options)
15
+ @options = {
16
+ :name => :GoogleCloudStorage,
17
+ :location => :na, # US or Europe, set at bucket creation time
18
+ :fog => {
19
+ :provider => 'Google',
20
+ :google_storage_access_key_id => options[:fog_access_id] || options[:access_id],
21
+ :google_storage_secret_access_key => options[:fog_secret_key] || options[:secret_key]
22
+ },
23
+ :api => 1
24
+ }.merge!(options)
25
+
26
+
27
+ raise ArgumentError, 'Google Access ID missing' if @options[:access_id].nil?
28
+ raise ArgumentError, 'Google Secret Key missing' if @options[:secret_key].nil?
29
+
30
+ if @options[:api] == 2
31
+ @options[:secret_key] = OpenSSL::PKey::RSA.new(@options[:secret_key])
32
+ end
33
+
34
+ @options[:location] = @options[:location].to_sym
35
+ end
36
+
37
+
38
+ #
39
+ # Enable CORS on a bucket for a domain
40
+ #
41
+ def enable_cors(bucket, origin = '*')
42
+ data =
43
+ <<-DATA
44
+ <?xml version="1.0" encoding="UTF-8"?>
45
+ <CorsConfig>
46
+ <Cors>
47
+ <Origins>
48
+ <Origin>#{origin}</Origin>
49
+ </Origins>
50
+ <Methods>
51
+ <Method>GET</Method>
52
+ <Method>HEAD</Method>
53
+ <Method>POST</Method>
54
+ <Method>PUT</Method>
55
+ </Methods>
56
+ <ResponseHeaders>
57
+ <ResponseHeader>origin</ResponseHeader>
58
+ <ResponseHeader>content-md5</ResponseHeader>
59
+ <ResponseHeader>authorization</ResponseHeader>
60
+ <ResponseHeader>x-goog-date</ResponseHeader>
61
+ <ResponseHeader>x-goog-acl</ResponseHeader>
62
+ <ResponseHeader>content-type</ResponseHeader>
63
+ <ResponseHeader>accept</ResponseHeader>
64
+ <ResponseHeader>x-goog-api-version</ResponseHeader>
65
+ <ResponseHeader>x-goog-resumable</ResponseHeader>
66
+ <ResponseHeader>content-range</ResponseHeader>
67
+ <ResponseHeader>x-requested-with</ResponseHeader>
68
+ </ResponseHeaders>
69
+ <MaxAgeSec>1800</MaxAgeSec>
70
+ </Cors>
71
+ </CorsConfig>
72
+ DATA
73
+
74
+ fog_connection.condo_request(
75
+ :expects => 200,
76
+ :body => data,
77
+ :method => 'PUT',
78
+ :headers => {},
79
+ :host => "#{bucket}.storage.googleapis.com",
80
+ :idempotent => true,
81
+ :path => '?cors' # There is an issue with Fog where this isn't included as a canonical_resource
82
+ )
83
+ end
84
+
85
+
86
+ def name
87
+ @options[:name]
88
+ end
89
+
90
+
91
+ def location
92
+ @options[:location]
93
+ end
94
+
95
+
96
+ #
97
+ # Create a signed URL for accessing a private file
98
+ #
99
+ def get_object(options)
100
+ options = {}.merge!(options) # Need to deep copy here
101
+ options[:object_options] = {
102
+ :expires => 5.minutes.from_now,
103
+ :verb => :get,
104
+ :headers => {},
105
+ :parameters => {},
106
+ :protocol => :https
107
+ }.merge!(options[:object_options] || {})
108
+ options.merge!(@options)
109
+
110
+ #
111
+ # provide the signed request
112
+ #
113
+ sign_request(options)[:url]
114
+ end
115
+
116
+
117
+ #
118
+ # Creates a new upload request (either single shot or multi-part)
119
+ # => Passed: bucket_name, object_key, object_options, file_size
120
+ #
121
+ def new_upload(options)
122
+ options = {}.merge!(options) # Need to deep copy here
123
+ options[:object_options] = {
124
+ :permissions => :private,
125
+ :expires => 5.minutes.from_now,
126
+ :verb => :put, # put for direct uploads
127
+ :headers => {},
128
+ :parameters => {},
129
+ :protocol => :https
130
+ }.merge!(options[:object_options] || {})
131
+ options.merge!(@options)
132
+
133
+
134
+ options[:object_options][:headers]['x-goog-api-version'] = @options[:api]
135
+
136
+ if options[:object_options][:headers]['x-goog-acl'].nil?
137
+ options[:object_options][:headers]['x-goog-acl'] = case options[:object_options][:permissions]
138
+ when :public
139
+ :'public-read'
140
+ else
141
+ :private
142
+ end
143
+ end
144
+
145
+ options[:object_options][:headers]['Content-Type'] = 'binary/octet-stream' if options[:object_options][:headers]['Content-Type'].nil?
146
+
147
+
148
+ #
149
+ # Decide what type of request is being sent
150
+ #
151
+ if options[:file_size] > 1.megabytes
152
+ # Resumables may not support the md5 header at this time - have to compare ETag and fail on the client side
153
+ options[:object_options][:verb] = :post
154
+ options[:object_options][:headers]['x-goog-resumable'] = 'start'
155
+ return {
156
+ :signature => sign_request(options),
157
+ :type => :chunked_upload # triggers resumable
158
+ }
159
+ else
160
+ options[:object_options][:headers]['Content-Md5'] = options[:file_id] if options[:file_id].present? && options[:object_options][:headers]['Content-Md5'].nil?
161
+ return {
162
+ :signature => sign_request(options),
163
+ :type => :direct_upload
164
+ }
165
+ end
166
+ end
167
+
168
+
169
+ #
170
+ # Creates a request for the byte we were up to
171
+ #
172
+ def get_parts(options, setting_parts = false)
173
+ options[:object_options] = {
174
+ :expires => 5.minutes.from_now,
175
+ :verb => :put, # put for direct uploads
176
+ :headers => {},
177
+ :parameters => {},
178
+ :protocol => :https
179
+ }.merge!(options[:object_options] || {})
180
+ options.merge!(@options)
181
+
182
+ #
183
+ # Set the upload and request the range of bytes we are after
184
+ #
185
+ if setting_parts
186
+ options[:object_options][:headers]['Content-Md5'] = options[:file_id] if options[:file_id].present? && options[:object_options][:headers]['Content-Md5'].nil?
187
+ options[:object_options][:headers]['Content-Range'] = "bytes #{options[:part]}-#{options[:file_size] - 1}/#{options[:file_size]}"
188
+ else
189
+ options[:object_options][:headers]['Content-Range'] = "bytes */#{options[:file_size]}"
190
+ end
191
+ options[:object_options][:headers]['x-goog-api-version'] = @options[:api]
192
+ options[:object_options][:parameters]['upload_id'] = options[:resumable_id]
193
+
194
+ #
195
+ # provide the signed request
196
+ #
197
+ {
198
+ :expected => 308,
199
+ :type => :status,
200
+ :signature => sign_request(options)
201
+ }
202
+ end
203
+
204
+
205
+ #
206
+ # Returns the requests for uploading parts and completing a resumable upload
207
+ #
208
+ def set_part(options)
209
+ resp = get_parts(options, true)
210
+ resp[:type] = :resume_upload
211
+ resp[:type] = :resume_upload
212
+ return resp
213
+ end
214
+
215
+
216
+ def fog_connection
217
+ @fog = @fog || Fog::Storage.new(@options[:fog])
218
+ return @fog
219
+ end
220
+
221
+
222
+ def destroy(upload)
223
+ connection = fog_connection
224
+ directory = connection.directories.get(upload.bucket_name) # it is assumed this exists - if not then the upload wouldn't have taken place
225
+ file = directory.files.get(upload.object_key) # NOTE:: I only assume this works with resumables... should look into it
226
+
227
+ return true if file.nil?
228
+ return file.destroy
229
+ end
230
+
231
+
232
+
233
+ protected
234
+
235
+
236
+
237
+ def sign_request(options)
238
+
239
+ #
240
+ # Build base URL
241
+ #
242
+ verb = options[:object_options][:verb].to_s.upcase.to_sym
243
+ options[:object_options][:expires] = options[:object_options][:expires].utc.to_i
244
+
245
+ url = "/#{options[:object_key]}"
246
+
247
+
248
+ #
249
+ # Add signed request params
250
+ #
251
+ other_params = ''
252
+ signed_params = '?'
253
+ (options[:object_options][:parameters] || {}).each do |key, value|
254
+ if ['acl', 'cors', 'location', 'logging', 'requestPayment', 'torrent', 'versions', 'versioning'].include?(key)
255
+ signed_params << "#{key}&"
256
+ else
257
+ other_params << (value.empty? ? "#{key}&" : "#{key}=#{value}&")
258
+ end
259
+ end
260
+ signed_params.chop!
261
+
262
+ url << signed_params
263
+
264
+
265
+ #
266
+ # Build a request signature
267
+ #
268
+ signature = "#{verb}\n#{options[:object_options][:headers]['Content-Md5']}\n#{options[:object_options][:headers]['Content-Type']}\n#{options[:object_options][:expires]}\n"
269
+ if verb != :GET
270
+ options[:object_options][:headers]['x-goog-date'] ||= Time.now.utc.httpdate
271
+
272
+ google_headers, canonical_google_headers = {}, '' # Copied from https://github.com/fog/fog/blob/master/lib/fog/google/storage.rb
273
+ for key, value in options[:object_options][:headers]
274
+ if key[0..6] == 'x-goog-'
275
+ google_headers[key] = value
276
+ end
277
+ end
278
+
279
+ google_headers = google_headers.sort {|x, y| x[0] <=> y[0]}
280
+ for key, value in google_headers
281
+ signature << "#{key}:#{value}\n"
282
+ end
283
+ end
284
+
285
+ signature << "/#{options[:bucket_name]}#{url}"
286
+
287
+
288
+ #
289
+ # Encode the request signature
290
+ #
291
+ if @options[:api] == 1
292
+ signature = Base64.encode64(OpenSSL::HMAC.digest(OpenSSL::Digest::Digest.new('sha1'), @options[:secret_key], signature)).gsub("\n","")
293
+ options[:object_options][:headers]['Authorization'] = "GOOG1 #{@options[:access_id]}:#{signature}"
294
+ else
295
+ signature = Base64.encode64(@options[:secret_key].sign(OpenSSL::Digest::SHA256.new, signature)).gsub("\n","")
296
+ end
297
+
298
+
299
+ url += signed_params.present? ? '&' : '?'
300
+ url = "#{options[:object_options][:protocol]}://#{options[:bucket_name]}.storage.googleapis.com#{url}#{other_params}GoogleAccessId=#{@options[:access_id]}&Expires=#{options[:object_options][:expires]}&Signature=#{CGI::escape(signature)}"
301
+
302
+
303
+ #
304
+ # Finish building the request
305
+ #
306
+ return {
307
+ :verb => options[:object_options][:verb].to_s.upcase,
308
+ :url => url,
309
+ :headers => options[:object_options][:headers]
310
+ }
311
+ end
312
+
313
+
314
+ end
315
+