Skip to content

Commit

Permalink
Merge pull request #495 from loris-imageserver/auth_rules
Browse files Browse the repository at this point in the history
ImageInfo/auth_rules updates
  • Loading branch information
bcail authored Jan 6, 2020
2 parents 67eb7ca + 1007834 commit 268b813
Show file tree
Hide file tree
Showing 8 changed files with 91 additions and 118 deletions.
41 changes: 16 additions & 25 deletions loris/img_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,50 +57,41 @@ def default(self, obj):

class ImageInfo(JP2Extractor):
'''Info about the image.
See: <http://iiif.io/api/image/>
See: <http://iiif.io/api/image/>, <https://iiif.io/api/image/2.1/#complete-response>
Slots:
width (int)
height (int)
scaleFactors [(int)]
sizes [(str)]: the optimal sizes of the image to request
tiles: [{}]
service (dict): services associated with the image
profile (Profile): Features supported by the server/available for
this image
service [{}]: optional - services associated with the image
attribution [{}]: optional - text that must be shown when content obtained from the Image API service is
displayed or used
license []: optional - link to an external resource that describes the license or rights statement under which
content obtained from the Image API service may be used
logo {}: optional - small image that represents an individual or organization associated with the content
src_img_fp (str): the absolute path on the file system [non IIIF]
src_format (str): the format of the source image file [non IIIF]
color_profile_bytes []: the embedded color profile, if any [non IIIF]
auth_rules (dict): extra information about authorization [non IIIF]
'''
__slots__ = ('scaleFactors', 'width', 'tiles', 'height',
'profile', 'sizes', 'service',
'attribution', 'logo', 'license', 'auth_rules',
'src_format', 'src_img_fp', 'color_profile_bytes')
__slots__ = ('width', 'height', 'scaleFactors', 'sizes', 'tiles',
'profile', 'service', 'attribution', 'license', 'logo',
'src_img_fp', 'src_format', 'color_profile_bytes', 'auth_rules')

def __init__(self, app=None, src_img_fp="", src_format="", extra={}):
def __init__(self, app=None, service=None, attribution=None, license=None, logo=None, src_img_fp="", src_format="", auth_rules=None):
self.src_img_fp = src_img_fp
self.src_format = src_format
self.attribution = None
self.logo = None
self.license = None
self.service = {}
self.auth_rules = extra

# The extraInfo parameter can be used to override specific attributes.
# If there are extra attributes, drop an error.
bad_attrs = []
for (k, v) in extra.get('extraInfo', {}).items():
try:
setattr(self, k, v)
except AttributeError:
bad_attrs.append(k)
if bad_attrs:
raise ImageInfoException(
"Invalid parameters in extraInfo: %s." % ', '.join(bad_attrs)
)
self.attribution = attribution
self.logo = logo
self.license = license
self.service = service or {}
self.auth_rules = auth_rules or {}

# If constructed from JSON, the pixel info will already be processed
if app:
Expand Down
82 changes: 42 additions & 40 deletions loris/resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,44 @@
`resolver` -- Resolve Identifiers to Image Paths
================================================
"""
from contextlib import closing
import glob
import json
from logging import getLogger
from os.path import join, exists, dirname, split
from os import remove
from shutil import copy
import tempfile
from contextlib import closing
import glob
import json
import os
from urllib.parse import unquote
import warnings

import requests

from loris import constants
from loris.identifiers import CacheNamer, IdentRegexChecker
from loris.loris_exception import ResolverException
from loris.loris_exception import ResolverException, ConfigError
from loris.utils import mkdir_p, safe_rename
from loris.img_info import ImageInfo


logger = getLogger(__name__)


class _AbstractResolver(object):
class _AbstractResolver:

def __init__(self, config):
self.config = config
if config:
# check for previous settings
if "use_extra_info" in self.config and "use_auth_rules" in self.config:
raise ConfigError("You cannot set both use_extra_info and use_auth_rules. Please remove use_extra_info from your config.")

if "use_extra_info" in self.config:
warnings.warn("The use_extra_info field has been renamed to use_auth_rules and will be removed in a future version. Please update your config.", DeprecationWarning)
self.config["use_auth_rules"] = self.config["use_extra_info"]

self.auth_rules_ext = self.config.get('auth_rules_ext', 'rules.json')
self.use_extra_info = self.config.get('use_extra_info', True)
self.use_auth_rules = self.config.get('use_auth_rules', False)

def is_resolvable(self, ident):
"""
Expand Down Expand Up @@ -65,12 +73,10 @@ def resolve(self, app, ident, base_uri):
cn = self.__class__.__name__
raise NotImplementedError('resolve() not implemented for %s' % (cn,))

def get_extra_info(self, ident, source_fp):
def get_auth_rules(self, ident, source_fp):
"""
Given the identifier and any resolved source file, find the associated
extra information to include in info.json, plus any additional authorizer
specific information. It might end up there after being copied by a
caching implementation, or live there permanently.
Given the identifier and any resolved source file (ie. on the filesystem), grab the associated
authentication rules from the filesystem (if they exist).
Args:
ident (str):
Expand All @@ -80,12 +86,12 @@ def get_extra_info(self, ident, source_fp):
Returns:
dict: The dict of information to embed in info.json
"""
if not self.use_auth_rules:
return {}
xjsfp = source_fp.rsplit('.', 1)[0] + "." + self.auth_rules_ext
if exists(xjsfp):
fh = open(xjsfp)
xjs = json.load(fh)
fh.close()
return xjs
with open(xjsfp) as fh:
return json.load(fh)
else:
return {}

Expand Down Expand Up @@ -130,14 +136,13 @@ def is_resolvable(self, ident):
return not self.source_file_path(ident) is None

def resolve(self, app, ident, base_uri):

if not self.is_resolvable(ident):
self.raise_404_for_ident(ident)

source_fp = self.source_file_path(ident)
format_ = self.format_from_ident(ident)
extra = self.get_extra_info(ident, source_fp)
return ImageInfo(app, source_fp, format_, extra)
auth_rules = self.get_auth_rules(ident, source_fp)
return ImageInfo(app=app, src_img_fp=source_fp, src_format=format_, auth_rules=auth_rules)


class ExtensionNormalizingFSResolver(SimpleFSResolver):
Expand Down Expand Up @@ -271,7 +276,7 @@ def _web_request_url(self, ident):
return (url, self.request_options())

def cache_dir_path(self, ident):
return os.path.join(
return join(
self.cache_root,
CacheNamer.cache_directory_name(ident=ident),
ident,
Expand Down Expand Up @@ -347,19 +352,19 @@ def copy_to_cache(self, ident):
# These files are < 2k in size, so fetch in one go.
# Assumes that the rules will be next to the image
# cache_dir is image specific, so this is easy

bits = split(source_url)
fn = bits[1].rsplit('.', 1)[0] + "." + self.auth_rules_ext
rules_url = bits[0] + '/' + fn
try:
resp = requests.get(rules_url)
if resp.status_code == 200:
local_rules_fp = join(cache_dir, "loris_cache." + self.auth_rules_ext)
if not exists(local_rules_fp):
with open(local_rules_fp, 'w') as fh:
fh.write(resp.text)
except requests.exceptions.RequestException:
pass
if self.use_auth_rules:
bits = split(source_url)
fn = bits[1].rsplit('.', 1)[0] + "." + self.auth_rules_ext
rules_url = bits[0] + '/' + fn
try:
resp = requests.get(rules_url)
if resp.status_code == 200:
local_rules_fp = join(cache_dir, "loris_cache." + self.auth_rules_ext)
if not exists(local_rules_fp):
with open(local_rules_fp, 'w') as fh:
fh.write(resp.text)
except requests.exceptions.RequestException:
pass

return local_fp

Expand All @@ -368,11 +373,8 @@ def resolve(self, app, ident, base_uri):
if not cached_file_path:
cached_file_path = self.copy_to_cache(ident)
format_ = self.get_format(cached_file_path, None)
if self.use_extra_info:
extra = self.get_extra_info(ident, cached_file_path)
else:
extra = {}
return ImageInfo(app, cached_file_path, format_, extra)
auth_rules = self.get_auth_rules(ident, cached_file_path)
return ImageInfo(app=app, src_img_fp=cached_file_path, src_format=format_, auth_rules=auth_rules)


class TemplateHTTPResolver(SimpleHTTPResolver):
Expand Down Expand Up @@ -553,5 +555,5 @@ def resolve(self, app, ident, base_uri):

cache_fp = self.cache_file_path(ident)
format_ = self.format_from_ident(ident)
extra = self.get_extra_info(ident, cache_fp)
return ImageInfo(app, cache_fp, format_, extra)
auth_rules = self.get_auth_rules(ident, cache_fp)
return ImageInfo(app=app, src_img_fp=cache_fp, src_format=format_, auth_rules=auth_rules)
12 changes: 6 additions & 6 deletions tests/authorizer_t.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def setUp(self):
fp = "img/test.png"
fmt = "png"
self.authorizer = NullAuthorizer({})
self.info = ImageInfo(None, fp, fmt)
self.info = ImageInfo(app=None, src_img_fp=fp, src_format=fmt)
self.request = MockRequest()

def test_is_protected(self):
Expand All @@ -69,7 +69,7 @@ def setUp(self):
fp = "img/test.png"
fmt = "png"
self.authorizer = NooneAuthorizer({})
self.info = ImageInfo(None, fp, fmt)
self.info = ImageInfo(app=None, src_img_fp=fp, src_format=fmt)
self.request = MockRequest()

def test_is_protected(self):
Expand All @@ -90,9 +90,9 @@ def setUp(self):
fp = "img/test.png"
fmt = "png"
self.authorizer = SingleDegradingAuthorizer({})
self.badInfo = ImageInfo(None, fp, fmt)
self.badInfo = ImageInfo(app=None, src_img_fp=fp, src_format=fmt)
self.okayIdent = "67352ccc-d1b0-11e1-89ae-279075081939.jp2"
self.okayInfo = ImageInfo(None, "img/%s" % self.okayIdent, "jp2")
self.okayInfo = ImageInfo(app=None, src_img_fp="img/%s" % self.okayIdent, src_format="jp2")
self.request = MockRequest()

def test_is_protected(self):
Expand Down Expand Up @@ -120,9 +120,9 @@ def setUp(self):
{"cookie_secret": b"4rakTQJDyhaYgoew802q78pNnsXR7ClvbYtAF1YC87o=",
"token_secret": b"hyQijpEEe9z1OB9NOkHvmSA4lC1B4lu1n80bKNx0Uz0=",
"salt": b"4rakTQJD4lC1B4lu"})
self.badInfo = ImageInfo(None, fp, fmt)
self.badInfo = ImageInfo(app=None, src_img_fp=fp, src_format=fmt)
self.okayIdent = "67352ccc-d1b0-11e1-89ae-279075081939.jp2"
self.okayInfo = ImageInfo(None, "img/%s" % self.okayIdent, "jp2")
self.okayInfo = ImageInfo(app=None, src_img_fp="img/%s" % self.okayIdent, src_format="jp2")

self.origin = "localhost"
# role to get access is "test"
Expand Down
Loading

0 comments on commit 268b813

Please sign in to comment.