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')
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)

View file

@ -4,7 +4,7 @@ from collections import namedtuple
from io import BytesIO
from pathlib import Path
from tarfile import TarFile, TarInfo
from typing import BinaryIO, Optional, Self
from typing import BinaryIO, Optional, Self, NamedTuple
from pyzstd import CParameter, ZstdError, ZstdFile
@ -16,7 +16,12 @@ except ImportError:
from threading import local as LocalContext
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:
@ -25,8 +30,13 @@ class CachePackage:
self.levels = {} if levels is None else levels
self.theme_ids = []
def add_level(self, level_id: int, theme_id, history: MapHistory, restrictions: AccessRestrictionAffected):
self.levels[(level_id, theme_id)] = CachePackageLevel(history, restrictions)
def add_level(self, level_id: int, theme_id, history: MapHistory, restrictions: AccessRestrictionAffected,
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:
self.theme_ids.append(theme_id)
@ -62,6 +72,10 @@ class CachePackage:
key = '%d' % level_id
else:
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, 'restrictions_%s' % key, level_data.restrictions)
finally:
@ -116,9 +130,13 @@ class CachePackage:
else:
level_id = int(key)
theme_id = None
global_restrictions_data = f.extractfile(files['global_restrictions_%s' % key]).read()
levels[(level_id, theme_id)] = CachePackageLevel(
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)

View file

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

View file

@ -285,10 +285,15 @@ class TileServer:
cookie = self.cookie_regex.search(cookie)
if cookie:
cookie = cookie.group(2)
access_permissions = (parse_tile_access_cookie(cookie, self.tile_secret) &
set(level_data.restrictions[minx:maxx, miny:maxy]))
access_permissions = (
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)
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
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)