uploadcare-ruby 1.0.6 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -14,6 +14,7 @@ module Uploadcare
14
14
  static_url_base: 'https://ucarecdn.com',
15
15
  api_version: '0.3',
16
16
  cache_files: true,
17
+ auth_scheme: :secure
17
18
  }
18
19
 
19
20
  USER_AGENT = "uploadcare-ruby/#{Gem.ruby_version}/#{Uploadcare::VERSION}"
@@ -4,6 +4,7 @@ Dir[File.dirname(__FILE__) + '/utils/*.rb'].each {|file| require file }
4
4
  Dir[File.dirname(__FILE__) + '/errors/*.rb'].each {|file| require file }
5
5
  Dir[File.dirname(__FILE__) + '/rest/middlewares/*.rb'].each {|file| require file }
6
6
  Dir[File.dirname(__FILE__) + '/rest/connections/*.rb'].each {|file| require file }
7
+ Dir[File.dirname(__FILE__) + '/rest/auth/*.rb'].sort.each {|file| require file }
7
8
  Dir[File.dirname(__FILE__) + '/api/*.rb'].each {|file| require file }
8
9
  Dir[File.dirname(__FILE__) + '/resources/*.rb'].each {|file| require file }
9
10
 
@@ -11,7 +12,7 @@ Dir[File.dirname(__FILE__) + '/resources/*.rb'].each {|file| require file }
11
12
  module Uploadcare
12
13
  class Api
13
14
  attr_reader :options
14
-
15
+
15
16
  include Uploadcare::RawApi
16
17
  include Uploadcare::UploadingApi
17
18
  include Uploadcare::FileApi
@@ -5,7 +5,7 @@ module Uploadcare
5
5
  class File < OpenStruct
6
6
  def initialize api, uuid_or_cdn_url, data=nil
7
7
  result = Uploadcare::Parser.parse_file_string uuid_or_cdn_url
8
-
8
+
9
9
  file = {uuid: result.uuid, operations: result.operations}
10
10
 
11
11
  @api = api
@@ -65,7 +65,7 @@ module Uploadcare
65
65
  def store
66
66
  data = @api.put "/files/#{uuid}/storage/"
67
67
  set_data data
68
- self
68
+ self
69
69
  end
70
70
 
71
71
  # nil is returning if there is no way to say for sure
@@ -95,6 +95,8 @@ module Uploadcare
95
95
  # copy file to target location
96
96
  # note what file copied with operations
97
97
  def copy with_operations=true, target=nil
98
+ warn "[DEPRECATION] `copy` is deprecated and will be removed in "\
99
+ "version 3.0. Please use `internal_copy` or `external_copy` instead."
98
100
  data = Hash.new
99
101
  data[:target] = target if target
100
102
  data[:source] = self.cdn_url_with_operations if with_operations
@@ -103,6 +105,28 @@ module Uploadcare
103
105
  @api.post "/files/", data
104
106
  end
105
107
 
108
+ # Create a copy of the file in a default storage
109
+ def internal_copy(options={})
110
+ data = {
111
+ source: cdn_url(!options.fetch(:strip_operations){ false }),
112
+ store: options.fetch(:store){ nil }
113
+ }.reject{|_,v| v.nil?}
114
+
115
+ @api.post "/files/", data
116
+ end
117
+
118
+ # Copy file to a custom storage
119
+ def external_copy(target, options={})
120
+ data = {
121
+ source: cdn_url(!options.fetch(:strip_operations){ false }),
122
+ target: target,
123
+ pattern: options.fetch(:pattern){ nil },
124
+ make_public: options.fetch(:make_public){ nil },
125
+ }.reject{|_,v| v.nil?}
126
+
127
+ @api.post "/files/", data
128
+ end
129
+
106
130
 
107
131
  # Datetime methods
108
132
  # practicly try and parse the string to date objects
@@ -137,4 +161,4 @@ module Uploadcare
137
161
  end
138
162
  end
139
163
  end
140
- end
164
+ end
@@ -0,0 +1,31 @@
1
+ module Uploadcare
2
+ module Connections
3
+ module Auth
4
+
5
+ def self.strategy(options)
6
+ auth_scheme = options.fetch(:auth_scheme)
7
+
8
+ unless [:simple, :secure].include?(auth_scheme)
9
+ raise ArgumentError, "Unknown auth_scheme: '#{auth_scheme}'"
10
+ end
11
+
12
+ klass = const_get(auth_scheme.capitalize)
13
+ klass.new(options)
14
+ end
15
+
16
+ class Base
17
+ attr_reader :public_key, :private_key
18
+
19
+ def initialize(options)
20
+ @public_key = options.fetch(:public_key)
21
+ @private_key = options.fetch(:private_key)
22
+ end
23
+
24
+ def apply(env)
25
+ raise NotImplementedError
26
+ end
27
+ end
28
+
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,43 @@
1
+ require 'time'
2
+
3
+ module Uploadcare
4
+ module Connections
5
+ module Auth
6
+ class Secure < Base
7
+
8
+ def apply(env)
9
+ date = Time.now.utc
10
+ headers(env, date).each{|k, v| env.request_headers[k] = v}
11
+ env
12
+ end
13
+
14
+ private
15
+
16
+ def headers(env, date)
17
+ {
18
+ "Date" => date.rfc2822,
19
+ "Authorization" => "Uploadcare #{public_key}:#{signature(env, date)}"
20
+ }
21
+ end
22
+
23
+ def signature(env, date)
24
+ sign_string = sign_string(env, date)
25
+ digest = OpenSSL::Digest.new('sha1')
26
+
27
+ OpenSSL::HMAC.hexdigest(digest, private_key, sign_string)
28
+ end
29
+
30
+ def sign_string(env, date)
31
+ verb = env.method.upcase.to_s
32
+ uri = env.url.request_uri
33
+ date_header = date.rfc2822
34
+ content_type = env.request_headers['Content-Type']
35
+ content_md5 = OpenSSL::Digest.new('md5').hexdigest(env.body || "")
36
+
37
+ [verb, content_md5, content_type, date_header, uri].join("\n")
38
+ end
39
+
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,16 @@
1
+ module Uploadcare
2
+ module Connections
3
+ module Auth
4
+ class Simple < Base
5
+
6
+ def apply(env)
7
+ auth_string = "Uploadcare.Simple #{public_key}:#{private_key}"
8
+ env.request_headers['Authorization'] = auth_string
9
+
10
+ env
11
+ end
12
+
13
+ end
14
+ end
15
+ end
16
+ end
@@ -1,21 +1,33 @@
1
1
  require 'faraday'
2
2
  require "faraday_middleware"
3
+
3
4
  module Uploadcare
4
5
  module Connections
5
6
  class ApiConnection < Faraday::Connection
7
+
6
8
  def initialize options
7
9
  super options[:api_url_base] do |frd|
8
- frd.request :url_encoded
9
- frd.use ::FaradayMiddleware::FollowRedirects, limit: 3
10
- frd.adapter :net_http # actually, default adapter, just to be clear
11
- frd.headers['Authorization'] = "Uploadcare.Simple #{options[:public_key]}:#{options[:private_key]}"
10
+ auth_strategy = Auth.strategy(options)
11
+
12
12
  frd.headers['Accept'] = "application/vnd.uploadcare-v#{options[:api_version]}+json"
13
13
  frd.headers['User-Agent'] = Uploadcare::user_agent(options)
14
14
 
15
- frd.response :raise_error
16
- frd.response :parse_json
15
+ # order of middleware matters!
16
+
17
+ # url_encoded changes request body and thus should be before
18
+ # uploadcare_auth which uses it to sign requests when secure auth
19
+ # strategy is being used
20
+ frd.request :url_encoded
21
+ frd.request :uploadcare_auth, auth_strategy
22
+
23
+ frd.response :uploadcare_raise_error
24
+ frd.response :follow_redirects, limit: 3, callback: lambda{|old, env| auth_strategy.apply(env) }
25
+ frd.response :uploadcare_parse_json
26
+
27
+ frd.adapter :net_http # actually, default adapter, just to be clear
17
28
  end
18
29
  end
30
+
19
31
  end
20
32
  end
21
- end
33
+ end
@@ -12,8 +12,8 @@ module Uploadcare
12
12
  frd.adapter :net_http
13
13
  frd.headers['User-Agent'] = Uploadcare::user_agent(options)
14
14
 
15
- frd.response :raise_error
16
- frd.response :parse_json
15
+ frd.response :uploadcare_raise_error
16
+ frd.response :uploadcare_parse_json
17
17
  end
18
18
  end
19
19
  end
@@ -0,0 +1,24 @@
1
+ require 'faraday'
2
+
3
+ module Uploadcare
4
+ module Connections
5
+ module Request
6
+ class Auth < Faraday::Middleware
7
+ attr_reader :auth_strategy
8
+
9
+ def initialize(app=nil, auth_strategy)
10
+ @auth_strategy = auth_strategy
11
+ super(app)
12
+ end
13
+
14
+ def call(env)
15
+ auth_strategy.apply(env)
16
+ @app.call(env)
17
+ end
18
+
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ Faraday::Request.register_middleware uploadcare_auth: Uploadcare::Connections::Request::Auth
@@ -30,4 +30,4 @@ module Uploadcare
30
30
  end
31
31
  end
32
32
 
33
- Faraday::Response.register_middleware :parse_json => Uploadcare::Connections::Response::ParseJson
33
+ Faraday::Response.register_middleware :uploadcare_parse_json => Uploadcare::Connections::Response::ParseJson
@@ -7,7 +7,7 @@ module Uploadcare
7
7
  def on_complete(response)
8
8
  @error_codes = Uploadcare::Error.errors.keys
9
9
  @status = response[:status]
10
-
10
+
11
11
  if @error_codes.include?(@status)
12
12
  error = Uploadcare::Error.errors[@status].new
13
13
  fail(error)
@@ -18,4 +18,4 @@ module Uploadcare
18
18
  end
19
19
  end
20
20
 
21
- Faraday::Response.register_middleware :raise_error => Uploadcare::Connections::Response::RaiseError
21
+ Faraday::Response.register_middleware :uploadcare_raise_error => Uploadcare::Connections::Response::RaiseError
@@ -1,3 +1,3 @@
1
1
  module Uploadcare
2
- VERSION = "1.0.6"
2
+ VERSION = "1.1.0"
3
3
  end
@@ -1,6 +1,7 @@
1
1
  require 'spec_helper'
2
2
  require 'uri'
3
3
  require 'socket'
4
+ require 'securerandom'
4
5
 
5
6
  describe Uploadcare::Api::File do
6
7
  before :each do
@@ -111,12 +112,111 @@ describe Uploadcare::Api::File do
111
112
  end
112
113
 
113
114
 
114
- def retry_if(error, retries=3, &block)
115
- block.call
116
- rescue error
117
- raise if retries <= 0
118
- sleep 0.2
119
- retry_if(error, retries-1, &block)
115
+ describe '#internal_copy' do
116
+ describe 'integration' do
117
+ it 'creates an internal copy of the file' do
118
+ response = retry_if(Uploadcare::Error::RequestError::BadRequest){@file.internal_copy}
119
+
120
+ expect( response['type'] ).to eq 'file'
121
+ expect( response['result']['uuid'] ).not_to eq @file.uuid
122
+ end
123
+ end
124
+
125
+ describe 'params' do
126
+ let(:url_without_ops){ @api.file(SecureRandom.uuid).cdn_url }
127
+ let(:url_with_ops){ url_without_ops + "-/crop/5x5/center/" }
128
+ let(:file){ @api.file(url_with_ops) }
129
+
130
+ context 'if no params given' do
131
+ it 'requests server to create an unstored copy with operataions applied' do
132
+ expect(@api).to receive(:post)
133
+ .with('/files/', source: url_with_ops)
134
+
135
+ file.internal_copy
136
+ end
137
+ end
138
+
139
+ context 'if strip_operations: true given' do
140
+ it 'passes url without operations as a source for a copy' do
141
+ expect(@api).to receive(:post)
142
+ .with('/files/', source: url_without_ops)
143
+
144
+ file.internal_copy(strip_operations: true)
145
+ end
146
+ end
147
+
148
+ context 'if store: true given' do
149
+ it 'requests server to create a stored copy' do
150
+ expect(@api).to receive(:post)
151
+ .with('/files/', source: url_with_ops, store: true)
152
+
153
+ file.internal_copy(store: true)
154
+ end
155
+ end
156
+ end
120
157
  end
121
158
 
122
- end
159
+
160
+ describe '#external_copy' do
161
+ let(:target){ 'with-prefix' }
162
+
163
+ describe 'integration', :payed_feature do
164
+ it 'creates an external copy of the file' do
165
+ response = retry_if(Uploadcare::Error::RequestError::BadRequest) do
166
+ @file.external_copy(target)
167
+ end
168
+
169
+ expect( response['type'] ).to eq 'url'
170
+ expect( response['result'] ).to match(URI.regexp)
171
+ end
172
+ end
173
+
174
+ describe 'params' do
175
+ let(:url_without_ops){ @api.file(SecureRandom.uuid).cdn_url }
176
+ let(:url_with_ops){ url_without_ops + "-/resize/50x50/" }
177
+ let(:file){ @api.file(url_with_ops) }
178
+
179
+ context 'if only target is given' do
180
+ it 'requests server to create a private copy with default name and with operataions applied' do
181
+ expect(@api).to receive(:post)
182
+ .with('/files/', source: url_with_ops, target: target)
183
+
184
+ file.external_copy(target)
185
+ end
186
+ end
187
+
188
+ context 'if target is not given' do
189
+ it 'raises ArgumentError' do
190
+ expect{ file.external_copy }.to raise_error(ArgumentError)
191
+ end
192
+ end
193
+
194
+ context 'if strip_operations: true given' do
195
+ it 'passes url without operations as a source for a copy' do
196
+ expect(@api).to receive(:post)
197
+ .with('/files/', source: url_without_ops, target: target)
198
+
199
+ file.external_copy(target, strip_operations: true)
200
+ end
201
+ end
202
+
203
+ context 'if :make_public given' do
204
+ it 'requests server to create a copy with correspondent permissions' do
205
+ expect(@api).to receive(:post)
206
+ .with('/files/', source: url_with_ops, target: target, make_public: false)
207
+
208
+ file.external_copy(target, make_public: false)
209
+ end
210
+ end
211
+
212
+ context 'if :pattern given' do
213
+ it 'requests server to apply given pattern to name of a copy' do
214
+ expect(@api).to receive(:post)
215
+ .with('/files/', source: url_with_ops, target: target, pattern: 'test')
216
+
217
+ file.external_copy(target, pattern: 'test')
218
+ end
219
+ end
220
+ end
221
+ end
222
+ end
@@ -3,17 +3,66 @@ require 'uri'
3
3
  require 'socket'
4
4
 
5
5
  describe Uploadcare::Connections::ApiConnection do
6
- before(:all) do
7
- @settings = Uploadcare.default_settings
6
+ let(:settings){ Uploadcare.default_settings }
7
+
8
+ it 'is initializable with default settings' do
9
+ expect {described_class.new(settings)}.to_not raise_error
8
10
  end
9
11
 
10
- it 'should initialize api connection' do
11
- expect {Uploadcare::Connections::ApiConnection.new(@settings)}.to_not raise_error
12
+
13
+ describe 'default request headers' do
14
+ subject{ described_class.new(settings).headers }
15
+
16
+ it 'includes correct Accept header' do
17
+ expected = "application/vnd.uploadcare-v#{settings[:api_version]}+json"
18
+ expect(subject['Accept']).to eq expected
19
+ end
20
+
21
+ it 'includes correct User-Agent header' do
22
+ expected = Uploadcare::user_agent(settings)
23
+ expect(subject['User-Agent']).to eq expected
24
+ end
25
+ end
26
+
27
+
28
+ describe 'middleware' do
29
+ subject{ described_class.new(settings).builder.handlers }
30
+
31
+ it 'uses Request::Auth middleware' do
32
+ expect(subject).to include(Uploadcare::Connections::Request::Auth)
33
+ end
34
+
35
+ it 'uses Response::ParseJson middleware' do
36
+ expect(subject).to include(Uploadcare::Connections::Response::ParseJson)
37
+ end
38
+
39
+ it 'uses Response::RaiseError middleware' do
40
+ expect(subject).to include(Uploadcare::Connections::Response::RaiseError)
41
+ end
12
42
  end
13
43
 
14
- it 'should use ParseJson and RaiseError middlewares' do
15
- connection = Uploadcare::Connections::ApiConnection.new(@settings)
16
- connection.builder.handlers.include?(Uploadcare::Connections::Response::ParseJson).should == true
17
- connection.builder.handlers.include?(Uploadcare::Connections::Response::RaiseError).should == true
44
+
45
+ describe 'auth scheme' do
46
+ it 'uses simple auth when auth_scheme: :simple setting is provided' do
47
+ expect(Uploadcare::Connections::Auth::Simple).to receive(:new)
48
+ described_class.new(settings.merge(auth_scheme: :simple))
49
+ end
50
+
51
+ it 'uses secure auth when auth_scheme: :secure setting is provided' do
52
+ expect(Uploadcare::Connections::Auth::Secure).to receive(:new)
53
+ described_class.new(settings.merge(auth_scheme: :secure))
54
+ end
55
+
56
+ it 'raises KeyError when :auth_scheme options is not provided' do
57
+ expect{
58
+ described_class.new(settings.reject{|k,_| k == :auth_scheme})
59
+ }.to raise_error(KeyError)
60
+ end
61
+
62
+ it 'raises ArgumentError when provided :auth_scheme is unknown' do
63
+ expect{
64
+ described_class.new(settings.merge(auth_scheme: :unknown))
65
+ }.to raise_error(ArgumentError)
66
+ end
18
67
  end
19
- end
68
+ end