files.com 1.1.804 → 1.1.805
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 +4 -4
- data/_VERSION +1 -1
- data/lib/files.com/api_client.rb +27 -9
- data/lib/files.com.rb +6 -3
- data/spec/lib/region_routing_spec.rb +254 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ec7bf62c47e0eaca608b506497fd71e4466fcf5759e180b5c3f63b08ad9b4d07
|
|
4
|
+
data.tar.gz: c0f50f7d3a72d3ae50a57cdccf114da044711f16ab1c2abffe6217516c56030e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4ccda75a1ac5b95145654244f5619ab001a187c63a5a07f1ce54a3becb1226b1963499ccf295d7508996fd17cef4d1131cf7acd220f974154957604f4279a3de
|
|
7
|
+
data.tar.gz: 6d7681f622b12d307abcc66bca066b2038e3f95c93cbe4823af517b9d6f1f908d245d8df1ce4e70101416e9516db27a526864917ab35ad0b1ae8d2ac6ebae943
|
data/_VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
1.1.
|
|
1
|
+
1.1.805
|
data/lib/files.com/api_client.rb
CHANGED
|
@@ -341,8 +341,8 @@ module Files
|
|
|
341
341
|
end
|
|
342
342
|
|
|
343
343
|
# The origin a lockout region mismatch moves this request to, or nil to raise the error as it is. The API answers
|
|
344
|
-
# with that error, naming the site's own origin in data.host, when the site is pinned to another
|
|
345
|
-
# custom domain. Only the configured HTTPS origin, reached over this client's verified connection, may send the
|
|
344
|
+
# with that error, naming the site's own origin in data.host or X-Files-Host, when the site is pinned to another
|
|
345
|
+
# region or to a custom domain. Only the configured HTTPS origin, reached over this client's verified connection, may send the
|
|
346
346
|
# credentials elsewhere, apart from an http:// origin to the destination +http_pairs+ pairs it with.
|
|
347
347
|
private def region_continuation_base_url(error_response, base_url, body, http_pairs)
|
|
348
348
|
return if Files.region_policy == :disabled
|
|
@@ -351,7 +351,11 @@ module Files
|
|
|
351
351
|
return unless error_response.http_status == 403 && error_object[:type] == "not-authenticated/lockout-region-mismatch"
|
|
352
352
|
return unless replayable?(body)
|
|
353
353
|
|
|
354
|
-
|
|
354
|
+
data = error_object[:data]
|
|
355
|
+
return unless data.nil? || data.is_a?(Hash)
|
|
356
|
+
|
|
357
|
+
host = data && data[:host]
|
|
358
|
+
host = Faraday::Utils::Headers.new(error_response.http_headers || {})["X-Files-Host"] if host.nil? || host == ""
|
|
355
359
|
return http_pair_origin(base_url, host, http_pairs) unless verified_https_connection?(base_url)
|
|
356
360
|
|
|
357
361
|
configured = Addressable::URI.parse(base_url)
|
|
@@ -507,10 +511,11 @@ module Files
|
|
|
507
511
|
|
|
508
512
|
case e
|
|
509
513
|
when Faraday::ClientError, Faraday::ServerError
|
|
510
|
-
error_response = api_error_response(e.response)
|
|
511
|
-
if relocate && error_response && (regional_base_url = relocate.call(error_response))
|
|
514
|
+
error_response = api_error_response(e.response, allow_legacy_region_mismatch: !is_transfer)
|
|
515
|
+
if relocate && num_retries < Files.max_network_retries && error_response && (regional_base_url = relocate.call(error_response))
|
|
512
516
|
relocate = nil
|
|
513
517
|
base_url = regional_base_url
|
|
518
|
+
num_retries += 1
|
|
514
519
|
retry
|
|
515
520
|
end
|
|
516
521
|
|
|
@@ -547,16 +552,29 @@ module Files
|
|
|
547
552
|
end
|
|
548
553
|
|
|
549
554
|
# A failed response carrying a Files.com error object: a JSON object with an
|
|
550
|
-
# error message or type
|
|
551
|
-
# SDK
|
|
552
|
-
private def api_error_response(http_resp)
|
|
555
|
+
# error message or type, or an ordinary API request's recognized legacy
|
|
556
|
+
# plain-text region mismatch. Other bodies give the SDK no usable answer.
|
|
557
|
+
private def api_error_response(http_resp, allow_legacy_region_mismatch: false)
|
|
553
558
|
return unless http_resp
|
|
554
559
|
|
|
555
560
|
resp = Response.from_faraday_hash(http_resp)
|
|
556
561
|
error_object = resp.data
|
|
557
562
|
resp if error_object.is_a?(Hash) && (error_object[:error] || error_object[:errors] || error_object[:type])
|
|
558
563
|
rescue JSON::ParserError
|
|
559
|
-
|
|
564
|
+
return unless allow_legacy_region_mismatch && http_resp[:status] == 403
|
|
565
|
+
|
|
566
|
+
body = http_resp[:body]
|
|
567
|
+
host = Faraday::Utils::Headers.new(http_resp[:headers] || {})["X-Files-Host"]
|
|
568
|
+
return unless body.is_a?(String) && body.start_with?("You have connected to a URL") && host.is_a?(String) && !host.empty?
|
|
569
|
+
|
|
570
|
+
# The legacy API answer predates JSON errors. Give callers the same typed error and routing data while keeping
|
|
571
|
+
# the original text and headers on the response; a storage response never acquires API routing authority.
|
|
572
|
+
resp = Response.new
|
|
573
|
+
resp.http_status = http_resp[:status]
|
|
574
|
+
resp.http_headers = http_resp[:headers]
|
|
575
|
+
resp.http_body = body
|
|
576
|
+
resp.data = { type: "not-authenticated/lockout-region-mismatch", error: body, data: { host: host } }
|
|
577
|
+
resp
|
|
560
578
|
end
|
|
561
579
|
|
|
562
580
|
private def handle_error_response(resp, context, is_transfer: false)
|
data/lib/files.com.rb
CHANGED
|
@@ -285,7 +285,10 @@ module Files
|
|
|
285
285
|
end
|
|
286
286
|
|
|
287
287
|
# What a request does when the API answers that the site must be reached through another origin, such as its
|
|
288
|
-
# region or its custom domain
|
|
288
|
+
# region or its custom domain: the typed HTTP 403 Files::LockoutRegionMismatchError, with that origin in data[:host]
|
|
289
|
+
# or X-Files-Host when the data host is absent or empty, or the legacy plain-text HTTP 403 beginning "You have connected to a URL"
|
|
290
|
+
# with X-Files-Host. A recognized legacy answer raises the same typed error when the request cannot continue, keeping
|
|
291
|
+
# its original text and headers in http_body and http_headers. An unrelated 403 never moves the request.
|
|
289
292
|
#
|
|
290
293
|
# :automatic (the default) sends the same request once more to that origin, within the same retry count, and keeps
|
|
291
294
|
# using it for the records and pages the request returns. Other requests still start at Files.base_url. It does so
|
|
@@ -305,8 +308,8 @@ module Files
|
|
|
305
308
|
# Each pair is [source, destination], such as [["http://localhost:3000", "http://site.localhost:3000"]].
|
|
306
309
|
#
|
|
307
310
|
# With region_policy :automatic, a request to the source (Files.base_url, or the request's own base_url) that gets
|
|
308
|
-
# the
|
|
309
|
-
# when it is not 80), is sent once more to the destination, within the same retry count, and the records and pages
|
|
311
|
+
# the mismatch naming the destination in data[:host] or X-Files-Host, as that full http:// origin or as its bare host
|
|
312
|
+
# (with its port when it is not 80), is sent once more to the destination, within the same retry count, and the records and pages
|
|
310
313
|
# it returns keep using it. region_handler, when set, is still asked first and can decline. Nothing else continues
|
|
311
314
|
# over plain HTTP, a request to an https:// origin never does, and :disabled still raises the error.
|
|
312
315
|
#
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
require_relative "../support/stubbed_api"
|
|
3
|
+
|
|
4
|
+
RSpec.describe "API region routing" do
|
|
5
|
+
include StubbedApi
|
|
6
|
+
|
|
7
|
+
let(:source) { "http://issuer.example.com" }
|
|
8
|
+
let(:target) { "http://target.example.com" }
|
|
9
|
+
let(:path) { "/api/rest/v1/bundles/842" }
|
|
10
|
+
let(:mismatch_type) { "not-authenticated/lockout-region-mismatch" }
|
|
11
|
+
let(:legacy_body) { "You have connected to a URL that does not match this site's region." }
|
|
12
|
+
|
|
13
|
+
around do |example|
|
|
14
|
+
original = [ Files.base_url, Files.region_policy, Files.region_handler, Files.region_http_pairs,
|
|
15
|
+
Files.max_network_retries, Files.initial_network_retry_delay ]
|
|
16
|
+
Files.base_url = source
|
|
17
|
+
Files.region_policy = :automatic
|
|
18
|
+
Files.region_handler = nil
|
|
19
|
+
Files.region_http_pairs = [ [ source, target ] ]
|
|
20
|
+
Files.max_network_retries = 3
|
|
21
|
+
Files.initial_network_retry_delay = 0
|
|
22
|
+
example.run
|
|
23
|
+
ensure
|
|
24
|
+
Files.base_url, Files.region_policy, Files.region_handler, Files.region_http_pairs,
|
|
25
|
+
Files.max_network_retries, Files.initial_network_retry_delay = original
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
shared_examples "a header-named mismatch" do
|
|
29
|
+
it "replays the API request unchanged at the named origin and retains only its operation's routing context" do
|
|
30
|
+
requests = []
|
|
31
|
+
client = stubbed_api_client do |stub|
|
|
32
|
+
[ source, target ].each do |origin|
|
|
33
|
+
stub.post("#{origin}#{path}") do |env|
|
|
34
|
+
requests << [ env.url.to_s, env.method, env.body, env.request_headers.to_h ]
|
|
35
|
+
origin == source ? mismatch : json_response(200, { id: 842, code: "regional" })
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
params = { code: "regional", permissions: "read" }
|
|
40
|
+
options = { client: client, session_id: "request-session", workspace_id: 0, "X-Request-Context" => "caller" }
|
|
41
|
+
|
|
42
|
+
response, returned_options = Files::Api.send_request("/bundles/842", :post, params, options)
|
|
43
|
+
|
|
44
|
+
expect(response.data).to include(id: 842, code: "regional")
|
|
45
|
+
expect(requests.map(&:first)).to eq([ "#{source}#{path}", "#{target}#{path}" ])
|
|
46
|
+
expect(requests.last.drop(1)).to eq(requests.first.drop(1))
|
|
47
|
+
expect(requests.last[2]).to eq("code=regional&permissions=read")
|
|
48
|
+
expect(requests.last[3]).to include("X-FilesAPI-Auth" => "request-session", "X-Files-Workspace-Id" => "0", "X-Request-Context" => "caller")
|
|
49
|
+
expect(requests.last[3]).not_to have_key("X-FilesAPI-Key")
|
|
50
|
+
expect(returned_options).to include(base_url: target, session_id: "request-session", workspace_id: 0, region_http_pairs: [ [ source, target ] ])
|
|
51
|
+
expect(options).not_to have_key(:base_url)
|
|
52
|
+
expect(params).to eq(code: "regional", permissions: "read")
|
|
53
|
+
expect(Files.base_url).to eq(source)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
context "for a legacy plain-text 403 without Content-Type" do
|
|
58
|
+
let(:mismatch) { [ 403, { "X-Files-Host" => "target.example.com" }, legacy_body ] }
|
|
59
|
+
include_examples "a header-named mismatch"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
context "for a legacy plain-text 403 with Content-Type" do
|
|
63
|
+
let(:mismatch) { [ 403, { "Content-Type" => "text/plain", "x-files-host" => target }, legacy_body ] }
|
|
64
|
+
include_examples "a header-named mismatch"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
context "for a typed 403 with no data" do
|
|
68
|
+
let(:mismatch) { json_response(403, { type: mismatch_type, error: "Region mismatch" }, "X-Files-Host" => "target.example.com") }
|
|
69
|
+
include_examples "a header-named mismatch"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
context "for a typed 403 whose data names no host" do
|
|
73
|
+
let(:mismatch) { json_response(403, { type: mismatch_type, error: "Region mismatch", data: { host: nil } }, "x-files-host" => target) }
|
|
74
|
+
include_examples "a header-named mismatch"
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
context "for a typed 403 whose data host is empty" do
|
|
78
|
+
let(:mismatch) { json_response(403, { type: mismatch_type, error: "Region mismatch", data: { host: "" } }, "X-Files-Host" => "target.example.com") }
|
|
79
|
+
include_examples "a header-named mismatch"
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
it "uses the typed data host before a different host header and keeps the origin on returned models" do
|
|
83
|
+
requests = []
|
|
84
|
+
client = stubbed_api_client do |stub|
|
|
85
|
+
stub.get("#{source}#{path}") do |env|
|
|
86
|
+
requests << env.url.to_s
|
|
87
|
+
json_response(403, { type: mismatch_type, data: { host: "target.example.com" } }, "X-Files-Host" => "unapproved.example.com")
|
|
88
|
+
end
|
|
89
|
+
stub.get("#{target}#{path}") do |env|
|
|
90
|
+
requests << env.url.to_s
|
|
91
|
+
expect(env.request_headers["X-FilesAPI-Key"]).to eq("request-key")
|
|
92
|
+
json_response(200, { id: 842 })
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
bundle = Files::Bundle.find(842, {}, api_key: "request-key", client: client)
|
|
97
|
+
|
|
98
|
+
expect(bundle.options).to include(base_url: target, api_key: "request-key", client: client)
|
|
99
|
+
expect(requests).to eq([ "#{source}#{path}?id=842", "#{target}#{path}?id=842" ])
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
{
|
|
103
|
+
"a path" => "target.example.com/private",
|
|
104
|
+
"credentials" => "user@target.example.com",
|
|
105
|
+
"a query" => "target.example.com?token=private",
|
|
106
|
+
"a fragment" => "target.example.com#private",
|
|
107
|
+
"whitespace" => " target.example.com",
|
|
108
|
+
"an unapproved origin" => "unapproved.example.com",
|
|
109
|
+
"the original origin" => "issuer.example.com"
|
|
110
|
+
}.each do |description, host|
|
|
111
|
+
it "refuses a legacy target naming #{description}" do
|
|
112
|
+
client = stubbed_api_client do |stub|
|
|
113
|
+
stub.get("#{source}#{path}") { [ 403, { "X-Files-Host" => host }, legacy_body ] }
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
expect { Files::Bundle.find(842, {}, api_key: "key", client: client) }.to raise_error(Files::LockoutRegionMismatchError) { |error|
|
|
117
|
+
expect(error.http_status).to eq(403)
|
|
118
|
+
expect(error.http_body).to eq(legacy_body)
|
|
119
|
+
expect(error.http_headers["x-files-host"]).to eq(host)
|
|
120
|
+
expect(error.data).to eq(host: host)
|
|
121
|
+
}
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
[ "target.example.com/private", 42 ].each do |host|
|
|
126
|
+
it "does not mask an invalid typed data host #{host.inspect} with a valid host header" do
|
|
127
|
+
client = stubbed_api_client do |stub|
|
|
128
|
+
stub.get("#{source}#{path}") { json_response(403, { type: mismatch_type, data: { host: host } }, "X-Files-Host" => "target.example.com") }
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
expect { Files::Bundle.find(842, {}, api_key: "key", client: client) }.to raise_error(Files::LockoutRegionMismatchError)
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
it "does not treat non-object typed data as a host-header fallback" do
|
|
136
|
+
client = stubbed_api_client do |stub|
|
|
137
|
+
stub.get("#{source}#{path}") { json_response(403, { type: mismatch_type, data: [] }, "X-Files-Host" => "target.example.com") }
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
expect { Files::Bundle.find(842, {}, api_key: "key", client: client) }.to raise_error(Files::LockoutRegionMismatchError)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
it "requires the configured source's authority even when a region handler approves" do
|
|
144
|
+
Files.region_http_pairs = []
|
|
145
|
+
Files.region_handler = ->(_event) { raise "an untrusted answer must not ask for approval" }
|
|
146
|
+
client = stubbed_api_client do |stub|
|
|
147
|
+
stub.get("#{source}#{path}") { [ 403, { "X-Files-Host" => "target.example.com" }, legacy_body ] }
|
|
148
|
+
stub.get("https://issuer.example.com#{path}") { [ 403, { "X-Files-Host" => "target.example.com" }, legacy_body ] }
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
[ source, "https://issuer.example.com" ].each do |origin|
|
|
152
|
+
expect { Files::Bundle.find(842, {}, api_key: "key", base_url: origin, client: client) }.to raise_error(Files::LockoutRegionMismatchError)
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
[ :disabled, :declined ].each do |policy|
|
|
157
|
+
it "keeps a legacy mismatch available to callers when routing is #{policy}" do
|
|
158
|
+
Files.region_policy = :disabled if policy == :disabled
|
|
159
|
+
Files.region_handler = ->(_event) { false } if policy == :declined
|
|
160
|
+
client = stubbed_api_client do |stub|
|
|
161
|
+
stub.get("#{source}#{path}") { [ 403, { "X-Files-Host" => "target.example.com" }, legacy_body ] }
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
expect { Files::Bundle.find(842, {}, api_key: "key", client: client) }.to raise_error(Files::LockoutRegionMismatchError) { |error|
|
|
165
|
+
expect(error.http_body).to eq(legacy_body)
|
|
166
|
+
expect(error.response.http_body).to eq(legacy_body)
|
|
167
|
+
}
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
it "does not route unrelated 403 errors or a legacy-looking answer with another status or no host" do
|
|
172
|
+
answers = [
|
|
173
|
+
json_response(403, { type: "not-authenticated", error: "Forbidden" }, "X-Files-Host" => "target.example.com"),
|
|
174
|
+
[ 403, { "X-Files-Host" => "target.example.com" }, "Forbidden" ],
|
|
175
|
+
[ 403, {}, legacy_body ],
|
|
176
|
+
[ 400, { "X-Files-Host" => "target.example.com" }, legacy_body ]
|
|
177
|
+
]
|
|
178
|
+
answers.each do |answer|
|
|
179
|
+
client = stubbed_api_client do |stub|
|
|
180
|
+
stub.get("#{source}#{path}") { answer }
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
expect { Files::Bundle.find(842, {}, api_key: "key", client: client) }.to raise_error(Files::Error) { |error|
|
|
184
|
+
expect(error).not_to be_a(Files::LockoutRegionMismatchError)
|
|
185
|
+
}
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
it "shares the retry budget with ordinary failures before and after a region continuation" do
|
|
190
|
+
[ 0, 1, 2 ].each do |retries|
|
|
191
|
+
Files.max_network_retries = retries
|
|
192
|
+
requests = []
|
|
193
|
+
client = stubbed_api_client do |stub|
|
|
194
|
+
stub.get("#{source}#{path}") do |env|
|
|
195
|
+
requests << env.url.host
|
|
196
|
+
requests.size == 1 ? json_response(429, { error: "Busy" }) : [ 403, { "X-Files-Host" => "target.example.com" }, legacy_body ]
|
|
197
|
+
end
|
|
198
|
+
stub.get("#{target}#{path}") do |env|
|
|
199
|
+
requests << env.url.host
|
|
200
|
+
json_response(429, { error: "Busy" })
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
expect { Files::Bundle.find(842, {}, api_key: "key", client: client) }.to raise_error(Files::APIError)
|
|
205
|
+
expect(requests).to eq([ "issuer.example.com", "issuer.example.com", "target.example.com" ].first(retries + 1))
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
it "does not continue a mismatch when retries are disabled" do
|
|
210
|
+
Files.max_network_retries = 0
|
|
211
|
+
client = stubbed_api_client do |stub|
|
|
212
|
+
stub.get("#{source}#{path}") { [ 403, { "X-Files-Host" => "target.example.com" }, legacy_body ] }
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
expect { Files::Bundle.find(842, {}, api_key: "key", client: client) }.to raise_error(Files::LockoutRegionMismatchError)
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
it "does not continue a second mismatch even when ordinary retries remain" do
|
|
219
|
+
requests = []
|
|
220
|
+
Files.region_http_pairs = [ [ source, target ], [ target, source ] ]
|
|
221
|
+
client = stubbed_api_client do |stub|
|
|
222
|
+
[ source, target ].each do |origin|
|
|
223
|
+
stub.get("#{origin}#{path}") do |env|
|
|
224
|
+
requests << env.url.host
|
|
225
|
+
[ 403, { "X-Files-Host" => origin == source ? target : source }, legacy_body ]
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
expect { Files::Bundle.find(842, {}, api_key: "key", client: client) }.to raise_error(Files::LockoutRegionMismatchError)
|
|
231
|
+
expect(requests).to eq([ "issuer.example.com", "target.example.com" ])
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
it "does not replay a stream body or reroute a signed storage URL" do
|
|
235
|
+
signed_url = "#{source}/part?X-Amz-Signature=unchanged%2Fvalue"
|
|
236
|
+
requests = []
|
|
237
|
+
client = stubbed_api_client do |stub|
|
|
238
|
+
stub.put("#{source}#{path}") do |env|
|
|
239
|
+
requests << env.url.to_s
|
|
240
|
+
[ 403, { "X-Files-Host" => "target.example.com" }, legacy_body ]
|
|
241
|
+
end
|
|
242
|
+
stub.put(signed_url) do |env|
|
|
243
|
+
requests << env.url.to_s
|
|
244
|
+
expect(env.request_headers).not_to have_key("X-FilesAPI-Key")
|
|
245
|
+
expect(env.request_headers).not_to have_key("X-FilesAPI-Auth")
|
|
246
|
+
[ 403, { "X-Files-Host" => "target.example.com" }, legacy_body ]
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
expect { client.execute_request(:put, "/bundles/842", api_key: "key", params: { content: StringIO.new("bytes") }) }.to raise_error(Files::LockoutRegionMismatchError)
|
|
251
|
+
expect { client.remote_request(:put, signed_url, {}, "part") }.to raise_error(Files::APIConnectionError)
|
|
252
|
+
expect(requests).to eq([ "#{source}#{path}", signed_url ])
|
|
253
|
+
end
|
|
254
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: files.com
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.1.
|
|
4
|
+
version: 1.1.805
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- files.com
|
|
@@ -486,6 +486,7 @@ files:
|
|
|
486
486
|
- spec/decimal_contract_spec.rb
|
|
487
487
|
- spec/language_spec.rb
|
|
488
488
|
- spec/lib/api_client_spec.rb
|
|
489
|
+
- spec/lib/region_routing_spec.rb
|
|
489
490
|
- spec/list_spec.rb
|
|
490
491
|
- spec/models/file_download_default_path_spec.rb
|
|
491
492
|
- spec/models/file_spec.rb
|