image_vise 0.2.6 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +3 -8
- data/lib/image_vise/image_request.rb +9 -16
- data/lib/image_vise/pipeline.rb +1 -5
- data/lib/image_vise/render_engine.rb +8 -4
- data/lib/image_vise/version.rb +1 -1
- data/lib/image_vise.rb +0 -19
- data/spec/image_vise/image_request_spec.rb +16 -14
- data/spec/image_vise/pipeline_spec.rb +2 -2
- data/spec/image_vise/render_engine_spec.rb +5 -3
- data/spec/image_vise_spec.rb +0 -11
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '0790cd6968b8c71bdb827b446d0505169e6d6254'
|
4
|
+
data.tar.gz: 7b98094a9cffaf0f518ef1e1b2e851fbce962fa2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1806c4f6da54b58dc7b4b38f286c6afd411a3a770c51b7cc6e258407e6fd1339063b43a1c9372b09d32345a5a4e13dc8f2e77a382b887eac798b4a7d113700a2
|
7
|
+
data.tar.gz: cb9a32c8dabe2261ff291d1d0ba778d79056f0fc8c41cba459f635a52fb76cfb99db13d24e08ea979525a3a8c6f2b73e3e8c275b66bdf096ccdc0fd84c3d75ac
|
data/README.md
CHANGED
@@ -7,11 +7,11 @@ framework. The main uses are:
|
|
7
7
|
* Applying image filters
|
8
8
|
|
9
9
|
It is implemented as a Rack application that responds to any URL and accepts the following two _last_ path
|
10
|
-
compnents, internally named `
|
10
|
+
compnents, internally named `request` and `signature`:
|
11
11
|
|
12
|
-
* `
|
12
|
+
* `request` - Base64 encoded JSON object with `src_url` and `pipeline` properties
|
13
13
|
(the source URL of the image and processing steps to apply)
|
14
|
-
* `
|
14
|
+
* `signature` - the HMAC signature, computed over the JSON in `q` before it gets Base64-encoded
|
15
15
|
|
16
16
|
A request to `ImageVise` might look like this:
|
17
17
|
|
@@ -90,11 +90,6 @@ def thumb_url(source_image_url)
|
|
90
90
|
'/images' + path
|
91
91
|
end
|
92
92
|
```
|
93
|
-
## Path decoding and SCRIPT_NAME
|
94
|
-
|
95
|
-
`ImageVise::RenderEngine` _must_ be mounted under a `SCRIPT_NAME` (using either `mount` in Rails
|
96
|
-
or using `map` in Rack). That is so since we may have more than 1 path component that we have to
|
97
|
-
decode (when the Base64 payload contains slashes).
|
98
93
|
|
99
94
|
## Processing files on the local filesystem instead of remote ones
|
100
95
|
|
@@ -8,10 +8,7 @@ class ImageVise::ImageRequest < Ks.strict(:src_url, :pipeline)
|
|
8
8
|
|
9
9
|
# Initializes a new ParamsChecker from given HTTP server framework
|
10
10
|
# params. The params can be symbol- or string-keyed, does not matter.
|
11
|
-
def self.from_params(
|
12
|
-
base64_encoded_params = qs_params.fetch(:q) rescue qs_params.fetch('q')
|
13
|
-
given_signature = qs_params.fetch(:sig) rescue qs_params.fetch('sig')
|
14
|
-
|
11
|
+
def self.from_params(base64_encoded_params:, given_signature:, secrets:)
|
15
12
|
# Unmask slashes and equals signs (if they are present)
|
16
13
|
base64_encoded_params = base64_encoded_params.tr('-', '/').tr('_', '+')
|
17
14
|
|
@@ -20,8 +17,8 @@ class ImageVise::ImageRequest < Ks.strict(:src_url, :pipeline)
|
|
20
17
|
raise SignatureError, "Invalid or missing signature"
|
21
18
|
end
|
22
19
|
|
23
|
-
# Decode the JSON
|
24
|
-
#
|
20
|
+
# Decode the JSON - only AFTER the signature has been validated,
|
21
|
+
# so we can use symbol keys
|
25
22
|
decoded_json = Base64.decode64(base64_encoded_params)
|
26
23
|
params = JSON.parse(decoded_json, symbolize_names: true)
|
27
24
|
|
@@ -29,21 +26,17 @@ class ImageVise::ImageRequest < Ks.strict(:src_url, :pipeline)
|
|
29
26
|
source_url_str = params.fetch(:src_url).to_s
|
30
27
|
raise URLError, "the :src_url parameter must be non-empty" if source_url_str.empty?
|
31
28
|
pipeline_definition = params.fetch(:pipeline)
|
32
|
-
new(src_url: URI(source_url_str), pipeline: ImageVise::Pipeline.
|
29
|
+
new(src_url: URI(source_url_str), pipeline: ImageVise::Pipeline.from_array_of_operator_params(pipeline_definition))
|
33
30
|
rescue KeyError => e
|
34
31
|
raise InvalidRequest.new(e.message)
|
35
32
|
end
|
36
33
|
|
37
|
-
def to_path_params(
|
38
|
-
qs = to_query_string_params(signed_with_secret)
|
39
|
-
q_masked = qs.fetch(:q).tr('/', '-').tr('+', '_')
|
40
|
-
'/%s/%s' % [q_masked, qs[:sig]]
|
41
|
-
end
|
42
|
-
|
43
|
-
def to_query_string_params(signed_with_secret)
|
34
|
+
def to_path_params(signing_secret)
|
44
35
|
payload = JSON.dump(to_h)
|
45
|
-
|
46
|
-
|
36
|
+
req_base64_enc = Base64.strict_encode64(payload).gsub(/\=+$/, '')
|
37
|
+
req_masked = req_base64_enc.tr('/', '-').tr('+', '_')
|
38
|
+
sig = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA256.new, signing_secret, req_base64_enc)
|
39
|
+
'/%s/%s' % [req_masked, sig]
|
47
40
|
end
|
48
41
|
|
49
42
|
def to_h
|
data/lib/image_vise/pipeline.rb
CHANGED
@@ -3,7 +3,7 @@ class ImageVise::Pipeline
|
|
3
3
|
operator = ImageVise.operator_from(name) or raise "Unknown operator #{name}"
|
4
4
|
end
|
5
5
|
|
6
|
-
def self.
|
6
|
+
def self.from_array_of_operator_params(array_of_operator_names_to_operator_params)
|
7
7
|
operators = array_of_operator_names_to_operator_params.map do |(operator_name, operator_params)|
|
8
8
|
operator_class = operator_by_name(operator_name)
|
9
9
|
if operator_params && operator_params.any? && operator_class.method(:new).arity.nonzero?
|
@@ -57,8 +57,4 @@ class ImageVise::Pipeline
|
|
57
57
|
operator.apply!(magick_image, image_metadata)
|
58
58
|
end
|
59
59
|
end
|
60
|
-
|
61
|
-
def each(&b)
|
62
|
-
@ops.each(&b)
|
63
|
-
end
|
64
60
|
end
|
@@ -72,9 +72,13 @@
|
|
72
72
|
|
73
73
|
req = parse_env_into_request(env)
|
74
74
|
bail(405, 'Only GET supported') unless req.get?
|
75
|
-
|
75
|
+
encoded_request, signature = extract_params_from_request(req)
|
76
76
|
|
77
|
-
image_request = ImageVise::ImageRequest.from_params(
|
77
|
+
image_request = ImageVise::ImageRequest.from_params(
|
78
|
+
base64_encoded_params: encoded_request,
|
79
|
+
given_signature: signature,
|
80
|
+
secrets: ImageVise.secret_keys
|
81
|
+
)
|
78
82
|
render_destination_file, render_file_type, etag = process_image_request(image_request)
|
79
83
|
image_rack_response(render_destination_file, render_file_type, etag)
|
80
84
|
rescue *permanent_failures => e
|
@@ -104,7 +108,7 @@
|
|
104
108
|
# Extracts the image params from the Rack::Request
|
105
109
|
#
|
106
110
|
# @param rack_request[#path_info] an object that has a path info
|
107
|
-
# @return [
|
111
|
+
# @return [String, String] the Base64-encoded image request and the signature
|
108
112
|
def extract_params_from_request(rack_request)
|
109
113
|
# Prevent cache bypass DOS attacks by only permitting :sig and :q
|
110
114
|
bail(400, 'Query strings are not supported') if rack_request.params.any?
|
@@ -121,7 +125,7 @@
|
|
121
125
|
nothing_recovered = [q_from_path, sig_from_path].all?{|v| v.nil? || v.empty? }
|
122
126
|
bail(400, 'Need 2 usable path components') if nothing_recovered
|
123
127
|
|
124
|
-
|
128
|
+
[q_from_path, sig_from_path]
|
125
129
|
end
|
126
130
|
|
127
131
|
# Processes the ImageRequest object created from the request parameters,
|
data/lib/image_vise/version.rb
CHANGED
data/lib/image_vise.rb
CHANGED
@@ -83,25 +83,6 @@ class ImageVise
|
|
83
83
|
keys or raise "No keys set, add a key using `ImageVise.add_secret_key!(key)'"
|
84
84
|
end
|
85
85
|
|
86
|
-
# Generate a set of querystring params for a resized image. Yields a Pipeline object that
|
87
|
-
# will receive method calls for adding image operations to a stack.
|
88
|
-
#
|
89
|
-
# ImageVise.image_params(src_url: image_url_on_s3, secret: '...') do |p|
|
90
|
-
# p.center_fit width: 128, height: 128
|
91
|
-
# p.elliptic_stencil
|
92
|
-
# end #=> {q: '...', sig: '...'}
|
93
|
-
#
|
94
|
-
# The query string elements can be then passed on to RenderEngine for validation and execution.
|
95
|
-
#
|
96
|
-
# @yield {ImageVise::Pipeline}
|
97
|
-
# @return [Hash]
|
98
|
-
def image_params(src_url:, secret:)
|
99
|
-
p = Pipeline.new
|
100
|
-
yield(p)
|
101
|
-
raise ArgumentError, "Image pipeline has no steps defined" if p.empty?
|
102
|
-
ImageRequest.new(src_url: URI(src_url), pipeline: p).to_query_string_params(secret)
|
103
|
-
end
|
104
|
-
|
105
86
|
# Generate a path for a resized image. Yields a Pipeline object that
|
106
87
|
# will receive method calls for adding image operations to a stack.
|
107
88
|
#
|
@@ -6,23 +6,26 @@ describe ImageVise::ImageRequest do
|
|
6
6
|
img_params_json = JSON.dump(img_params)
|
7
7
|
|
8
8
|
q = Base64.encode64(img_params_json)
|
9
|
-
|
10
|
-
params = {q: q, sig: signature}
|
9
|
+
sig = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA256.new, 'this is a secret', q)
|
11
10
|
|
12
|
-
image_request = described_class.from_params(
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
11
|
+
image_request = described_class.from_params(
|
12
|
+
base64_encoded_params: q,
|
13
|
+
given_signature: sig,
|
14
|
+
secrets: ['this is a secret']
|
15
|
+
)
|
16
|
+
expect(image_request).to be_kind_of(described_class)
|
17
17
|
end
|
18
18
|
|
19
|
-
it 'converts a file:// URL into a URI
|
19
|
+
it 'converts a file:// URL into a URI object' do
|
20
20
|
img_params = {src_url: 'file:///etc/passwd', pipeline: [[:auto_orient, {}]]}
|
21
21
|
img_params_json = JSON.dump(img_params)
|
22
22
|
q = Base64.encode64(img_params_json)
|
23
|
-
|
24
|
-
|
25
|
-
|
23
|
+
sig = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA256.new, 'this is a secret', q)
|
24
|
+
image_request = described_class.from_params(
|
25
|
+
base64_encoded_params: q,
|
26
|
+
given_signature: sig,
|
27
|
+
secrets: ['this is a secret']
|
28
|
+
)
|
26
29
|
expect(image_request.src_url).to be_kind_of(URI)
|
27
30
|
end
|
28
31
|
|
@@ -40,7 +43,7 @@ describe ImageVise::ImageRequest do
|
|
40
43
|
(1..12).each do |num_chars_in_url|
|
41
44
|
uri = URI('http://ex.com/%s' % ('i' * num_chars_in_url))
|
42
45
|
image_request = described_class.new(src_url: uri, pipeline: parametrized)
|
43
|
-
q = image_request.
|
46
|
+
q = image_request.to_path_params('password')
|
44
47
|
expect(q).not_to include('=')
|
45
48
|
end
|
46
49
|
end
|
@@ -52,10 +55,9 @@ describe ImageVise::ImageRequest do
|
|
52
55
|
img_params_json = JSON.dump(img_params)
|
53
56
|
enc = Base64.encode64(img_params_json)
|
54
57
|
signature = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA256.new, 'a', enc)
|
55
|
-
params = {q: enc, sig: signature}
|
56
58
|
|
57
59
|
expect {
|
58
|
-
described_class.from_params(
|
60
|
+
described_class.from_params(base64_encoded_params: enc, given_signature: signature, secrets: ['b'])
|
59
61
|
}.to raise_error(/Invalid or missing signature/)
|
60
62
|
end
|
61
63
|
end
|
@@ -12,7 +12,7 @@ describe ImageVise::Pipeline do
|
|
12
12
|
["auto_orient", {}],
|
13
13
|
["fit_crop", {:width=>10, :height=>32, :gravity=>"c"}]
|
14
14
|
]
|
15
|
-
pipeline = described_class.
|
15
|
+
pipeline = described_class.from_array_of_operator_params(params)
|
16
16
|
expect(pipeline).not_to be_empty
|
17
17
|
end
|
18
18
|
|
@@ -29,7 +29,7 @@ describe ImageVise::Pipeline do
|
|
29
29
|
["fit_crop", {:width=>10, :height=>32, :gravity=>"c"}]
|
30
30
|
])
|
31
31
|
|
32
|
-
pipeline = described_class.
|
32
|
+
pipeline = described_class.from_array_of_operator_params(operator_list)
|
33
33
|
expect(pipeline).not_to be_empty
|
34
34
|
end
|
35
35
|
|
@@ -196,8 +196,11 @@ describe ImageVise::RenderEngine do
|
|
196
196
|
'cmljb246L0NQR1BfRmlyZWJhbGw-Yz1kOWM4ZTMzO'+
|
197
197
|
'TZmNjMwYzM1MjM0MTYwMmM2YzJhYmQyZjAzNTcxMTF'+
|
198
198
|
'jIn0'
|
199
|
-
|
200
|
-
|
199
|
+
req = ImageVise::ImageRequest.from_params(
|
200
|
+
base64_encoded_params: q,
|
201
|
+
given_signature: sig,
|
202
|
+
secrets: ['this is fab']
|
203
|
+
)
|
201
204
|
|
202
205
|
# We do a check based on the raised exception - the request will fail
|
203
206
|
# at the fetcher lookup stage. That stage however takes place _after_ the
|
@@ -216,7 +219,6 @@ describe ImageVise::RenderEngine do
|
|
216
219
|
|
217
220
|
p = ImageVise::Pipeline.new.geom(geometry_string: '512x335').fit_crop(width: 10, height: 10, gravity: 'c')
|
218
221
|
image_request = ImageVise::ImageRequest.new(src_url: uri.to_s, pipeline: p)
|
219
|
-
params = image_request.to_query_string_params('l33tness')
|
220
222
|
|
221
223
|
expect(app).to receive(:parse_env_into_request).and_call_original
|
222
224
|
expect(app).to receive(:process_image_request).and_call_original
|
data/spec/image_vise_spec.rb
CHANGED
@@ -80,17 +80,6 @@ describe ImageVise do
|
|
80
80
|
end
|
81
81
|
end
|
82
82
|
|
83
|
-
describe '.image_params' do
|
84
|
-
it 'generates a Hash with paremeters for processing the resized image' do
|
85
|
-
params = ImageVise.image_params(src_url: 'http://host.com/image.jpg', secret: 'l33t') do |pipe|
|
86
|
-
pipe.fit_crop width: 128, height: 256, gravity: 'c'
|
87
|
-
end
|
88
|
-
expect(params).to be_kind_of(Hash)
|
89
|
-
expect(params[:q]).not_to be_empty
|
90
|
-
expect(params[:sig]).not_to be_empty
|
91
|
-
end
|
92
|
-
end
|
93
|
-
|
94
83
|
describe 'methods dealing with fetchers' do
|
95
84
|
it 'returns the fetchers for the default schemes' do
|
96
85
|
http = ImageVise.fetcher_for('http')
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: image_vise
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Julik Tarkhanov
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2018-02-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: patron
|