image_vise 0.2.6 → 0.3.0
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/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
|