To enable HTTP Basic authentication on image and information endpoints, set the following configuration keys:
endpoint.public.auth.basic.enabled = true
endpoint.public.auth.basic.username = myusername
endpoint.public.auth.basic.secret = mypassword
A custom delegate method, authorized?()
, can be used to implement authorization logic ranging from simple to complex. It will be invoked upon every image request. A skeleton with documented parameters and return values is present in the delegates.rb.sample file. By default, it just returns true
, authorizing all requests.
Another method, redirect()
, can be used to conditionally redirect to a different URL.
class CustomDelegate
def authorized?(options = {})
scale = context['operations'].find{ |op| op['class'] == 'Scale' }
if scale
max_scale = 0.5
return scale['width'] <= full_size['width'] * max_scale and
scale['height'] <= full_size['height'] * max_scale
end
false
end
end
class CustomDelegate
def authorized?(options = {})
identifier = context['identifier']
# Allow only identifiers that don't include "_restricted"
return !identifier.include?('_restricted')
# Allow only identifiers that start with "_public"
return identifier.start_with?('public_')
# Allow only identifiers matching a regex
return identifier.match(/^image[5-9][0-9]/)
end
end
(The MySQL JDBC driver will need to be installed first.)
context['identifier']
, for example, will be exactly as the application receives it. Prefer prepared statements over string concatenation in order to reduce vulnerability to injection attacks.
require 'rubygems'
require 'jdbc/mysql'
require 'java'
class CustomDelegate
def authorized?(options = {})
authorized = false
Java::com.mysql.jdbc.Driver
url = 'jdbc:mysql://HOST/DATABASE'
conn = java.sql.DriverManager.get_connection(url, 'USERNAME', 'PASSWORD')
stmt = conn.create_statement
begin
query = %q{SELECT is_public
FROM image
WHERE identifier = ?
LIMIT 1}
stmt = conn.prepare_statement(query)
stmt.setString(1, context['identifier']);
result_set = stmt.execute_query
while result_set.next do
authorized = result_set.getBoolean(1)
end
ensure
stmt.close
conn.close
end
authorized
end
end
class CustomDelegate
def authorized?(options = {})
context['output_format'] == 'image/jpeg'
end
end
This is not foolproof—if a client knows what User-Agent you are checking for, they can spoof it.
class CustomDelegate
def authorized?(options = {})
headers = context['request_headers']
agent = headers.find{ |h, v| h.downcase == 'user-agent' }
agent.start_with?('MyAllowedUserAgent/')
end
end
class CustomDelegate
def authorized?(options = {})
headers['X-MyToken'] == ... # write code to authorize the token
end
end
In this example, requests for images containing any part of the bottom right quadrant of the source image will be denied.
(Also see redaction.)
class CustomDelegate
def authorized?(options = {})
crop = context['operations'].find{ |op| op['class'] == 'Crop' }
if crop
max_x = full_size['width'] / 2.0
max_y = full_size['height'] / 2.0
return !(crop['x'] + crop['width'] > max_x and
crop['y'] + crop['height'] > max_y)
end
false
end
end
class CustomDelegate
def redirect(options = {})
{
'location' => 'http://example.org/some-other-url',
'status_code' => 302
}
end
end