implement level permissions in tileserver/cachepackage

This commit is contained in:
Laura Klünder 2024-12-19 00:51:48 +01:00
parent 9d77707cc5
commit 6167c92e98
4 changed files with 37 additions and 9 deletions

View file

@ -358,7 +358,9 @@ class LevelRenderData:
map_history.save_level(render_level.pk, 'composite') map_history.save_level(render_level.pk, 'composite')
package.add_level(render_level.pk, theme, map_history, access_restriction_affected) package.add_level(level_id=render_level.pk, theme_id=theme, history=map_history,
restrictions=access_restriction_affected,
level_restriction=render_level.access_restriction_id)
render_data.save(update_cache_key, render_level.pk, theme) render_data.save(update_cache_key, render_level.pk, theme)

View file

@ -4,7 +4,7 @@ from collections import namedtuple
from io import BytesIO from io import BytesIO
from pathlib import Path from pathlib import Path
from tarfile import TarFile, TarInfo from tarfile import TarFile, TarInfo
from typing import BinaryIO, Optional, Self from typing import BinaryIO, Optional, Self, NamedTuple
from pyzstd import CParameter, ZstdError, ZstdFile from pyzstd import CParameter, ZstdError, ZstdFile
@ -16,7 +16,12 @@ except ImportError:
from threading import local as LocalContext from threading import local as LocalContext
ZSTD_MAGIC_NUMBER = b"\x28\xb5\x2f\xfd" ZSTD_MAGIC_NUMBER = b"\x28\xb5\x2f\xfd"
CachePackageLevel = namedtuple('CachePackageLevel', ('history', 'restrictions'))
class CachePackageLevel(NamedTuple):
history: MapHistory
restrictions: AccessRestrictionAffected
global_restrictions: frozenset[int]
class CachePackage: class CachePackage:
@ -25,8 +30,13 @@ class CachePackage:
self.levels = {} if levels is None else levels self.levels = {} if levels is None else levels
self.theme_ids = [] self.theme_ids = []
def add_level(self, level_id: int, theme_id, history: MapHistory, restrictions: AccessRestrictionAffected): def add_level(self, level_id: int, theme_id, history: MapHistory, restrictions: AccessRestrictionAffected,
self.levels[(level_id, theme_id)] = CachePackageLevel(history, restrictions) level_restriction: int | None):
self.levels[(level_id, theme_id)] = CachePackageLevel(
history=history,
restrictions=restrictions,
global_restrictions=frozenset() if level_restriction is None else frozenset((level_restriction, ))
)
if theme_id not in self.theme_ids: if theme_id not in self.theme_ids:
self.theme_ids.append(theme_id) self.theme_ids.append(theme_id)
@ -62,6 +72,10 @@ class CachePackage:
key = '%d' % level_id key = '%d' % level_id
else: else:
key = '%d_%d' % (level_id, theme_id) key = '%d_%d' % (level_id, theme_id)
self._add_bytesio(f, 'global_restrictions_%s' % key,
BytesIO(struct.pack('<B'+('I'*len(level_data.global_restrictions)),
len(level_data.global_restrictions),
*level_data.global_restrictions)))
self._add_geometryindexed(f, 'history_%s' % key, level_data.history) self._add_geometryindexed(f, 'history_%s' % key, level_data.history)
self._add_geometryindexed(f, 'restrictions_%s' % key, level_data.restrictions) self._add_geometryindexed(f, 'restrictions_%s' % key, level_data.restrictions)
finally: finally:
@ -116,9 +130,13 @@ class CachePackage:
else: else:
level_id = int(key) level_id = int(key)
theme_id = None theme_id = None
global_restrictions_data = f.extractfile(files['global_restrictions_%s' % key]).read()
levels[(level_id, theme_id)] = CachePackageLevel( levels[(level_id, theme_id)] = CachePackageLevel(
history=MapHistory.read(f.extractfile(files['history_%s' % key])), history=MapHistory.read(f.extractfile(files['history_%s' % key])),
restrictions=AccessRestrictionAffected.read(f.extractfile(files['restrictions_%s' % key])) restrictions=AccessRestrictionAffected.read(f.extractfile(files['restrictions_%s' % key])),
global_restrictions=frozenset(
struct.unpack('<'+('I'*global_restrictions_data[0]), global_restrictions_data[1:])
)
) )
return cls(bounds, levels) return cls(bounds, levels)

View file

@ -370,10 +370,13 @@ def tile(request, level, zoom, x, y, theme, access_permissions: Optional[set] =
access_permissions = set() access_permissions = set()
else: else:
access_permissions = parse_tile_access_cookie(cookie, settings.SECRET_TILE_KEY) access_permissions = parse_tile_access_cookie(cookie, settings.SECRET_TILE_KEY)
access_permissions &= set(level_data.restrictions[minx:maxx, miny:maxy]) access_permissions &= set(level_data.restrictions[minx:maxx, miny:maxy]) | level_data.global_restrictions
else: else:
access_permissions = access_permissions - {0} access_permissions = access_permissions - {0}
if not all((r in access_permissions) for r in level_data.global_restrictions):
raise Http404
# build cache keys # build cache keys
last_update = level_data.history.last_update(minx, miny, maxx, maxy) last_update = level_data.history.last_update(minx, miny, maxx, maxy)
base_cache_key = build_base_cache_key(last_update) base_cache_key = build_base_cache_key(last_update)

View file

@ -285,10 +285,15 @@ class TileServer:
cookie = self.cookie_regex.search(cookie) cookie = self.cookie_regex.search(cookie)
if cookie: if cookie:
cookie = cookie.group(2) cookie = cookie.group(2)
access_permissions = (parse_tile_access_cookie(cookie, self.tile_secret) & access_permissions = (
set(level_data.restrictions[minx:maxx, miny:maxy])) parse_tile_access_cookie(cookie, self.tile_secret) &
(set(level_data.restrictions[minx:maxx, miny:maxy]) | level_data.global_restrictions)
)
access_cache_key = build_access_cache_key(access_permissions) access_cache_key = build_access_cache_key(access_permissions)
if not all((r in access_permissions) for r in level_data.global_restrictions):
return self.not_found(start_response, b'invalid level or theme.')
# check browser cache # check browser cache
if_none_match = env.get('HTTP_IF_NONE_MATCH') if_none_match = env.get('HTTP_IF_NONE_MATCH')
tile_etag = build_tile_etag(level, zoom, x, y, theme_id, base_cache_key, access_cache_key, self.tile_secret) tile_etag = build_tile_etag(level, zoom, x, y, theme_id, base_cache_key, access_cache_key, self.tile_secret)