Skip to content

Commit

Permalink
Merge pull request #185 from ganwell/t/callback_eb18
Browse files Browse the repository at this point in the history
feat: add pre_write_callback and hook/callback approve write
  • Loading branch information
winged authored Jun 22, 2023
2 parents 89b1606 + 08b6a6f commit 11757f1
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 25 deletions.
9 changes: 9 additions & 0 deletions manabi/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,15 @@ def pre_write_hook():
mock._pre_write_hook = None


@pytest.fixture()
def pre_write_callback():
try:
mock._pre_write_callback = mock.check_token
yield mock.check_token
finally:
mock._pre_write_callback = None


@pytest.fixture()
def server_dir() -> Path:
return mock.get_server_dir()
Expand Down
46 changes: 36 additions & 10 deletions manabi/filesystem.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
from pathlib import Path
from typing import Any, Dict
from typing import Any, Dict, Optional

from wsgidav.dav_error import HTTP_FORBIDDEN, DAVError
from wsgidav.fs_dav_provider import FileResource, FilesystemProvider, FolderResource

from .token import Token
from .type_alias import PreWriteType
from .util import requests_session


Expand Down Expand Up @@ -51,8 +52,17 @@ def set_last_modified(self, dest_path, time_stamp, dry_run):


class ManabiFileResource(FileResource):
def __init__(self, path, environ, file_path, pre_write_hook=None):
def __init__(
self,
path,
environ,
file_path,
*,
pre_write_hook: Optional[str] = None,
pre_write_callback: Optional[PreWriteType] = None,
):
self._pre_write_hook = pre_write_hook
self._pre_write_callback = pre_write_callback
self._token = environ["manabi.token"]
super().__init__(path, environ, file_path)

Expand All @@ -69,21 +79,36 @@ def move_recursive(self, dest_path):
raise DAVError(HTTP_FORBIDDEN)

def begin_write(self, *, content_type):
pre = self._pre_write_hook
token = self._token
if pre and token:
session = requests_session()
session.post(pre, data=token.encode())
# The webhhook returned and hopefully created a new version.
# Now we can save.
if token:
pre_hook = self._pre_write_hook
pre_callback = self._pre_write_callback

if pre_hook:
session = requests_session()
res = session.post(pre_hook, data=token.encode())
if res.status_code != 200:
raise DAVError(HTTP_FORBIDDEN)
if pre_callback:
if not pre_callback(token):
raise DAVError(HTTP_FORBIDDEN)
# The hook returned and hopefully created a new version.
# Now we can save.
return super().begin_write(content_type=content_type)


class ManabiProvider(FilesystemProvider):
def __init__(
self, root_folder, *, readonly=False, shadow=None, pre_write_hook=None
self,
root_folder,
*,
readonly=False,
shadow=None,
pre_write_hook: Optional[str] = None,
pre_write_callback: Optional[PreWriteType] = None,
):
self._pre_write_hook = pre_write_hook
self._pre_write_callback = pre_write_callback
super().__init__(root_folder, readonly=readonly, shadow=shadow)

def get_resource_inst(self, path: str, environ: Dict[str, Any]):
Expand All @@ -103,7 +128,8 @@ def get_resource_inst(self, path: str, environ: Dict[str, Any]):
path,
environ,
fp,
self._pre_write_hook,
pre_write_hook=self._pre_write_hook,
pre_write_callback=self._pre_write_callback,
)
else:
return None
49 changes: 38 additions & 11 deletions manabi/filesystem_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,56 @@
from . import mock


def test_get_and_put(config: Dict[str, Any], server):
@pytest.mark.parametrize("tamper, expect_status", [(True, 403), (False, 204)])
def test_get_and_put(tamper, expect_status, config: Dict[str, Any], server):
req = mock.make_req(config)
res = requests.get(req)
assert res.status_code == 200
if tamper:
i = 58
if req[i] == "f":
req = req[0:i] + "g" + req[i + 1 :]
else:
req = req[0:i] + "f" + req[i + 1 :]
res = requests.put(req, data=res.content)
assert res.status_code == 204
assert res.status_code == expect_status


@pytest.mark.parametrize("tamper, expect_status", [(True, 403), (False, 204)])
@pytest.mark.parametrize(
"hook_status, expect_status",
[
(None, 204),
(403, 403),
(200, 204),
],
)
def test_get_and_put_hooked(
tamper, expect_status, pre_write_hook, config: Dict[str, Any], server
hook_status, expect_status, pre_write_hook, config: Dict[str, Any], server
):
assert config["pre_write_hook"] == "http://127.0.0.1/pre_write_hook"
with mock.with_pre_write_hook(config):
with mock.with_pre_write_hook(config, hook_status):
req = mock.make_req(config)
res = requests.get(req)
assert res.status_code == 200
res = requests.put(req, data=res.content)
assert res.status_code == expect_status


if tamper:
i = 58
if req[i] == "f":
req = req[0:i] + "g" + req[i + 1 :]
else:
req = req[0:i] + "f" + req[i + 1 :]
@pytest.mark.parametrize("callback_return, expect_status", [(False, 403), (True, 204)])
def test_get_and_put_called(
callback_return,
expect_status,
pre_write_callback,
config: Dict[str, Any],
server,
):
assert config["pre_write_callback"] == pre_write_callback
req = mock.make_req(config)
res = requests.get(req)
assert res.status_code == 200
try:
mock._check_token_return = callback_return
res = requests.put(req, data=res.content)
assert res.status_code == expect_status
finally:
mock._check_token_return = True
27 changes: 24 additions & 3 deletions manabi/mock.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from .lock import ManabiDbLockStorage as ManabiDbLockStorageOrig
from .log import HeaderLogger, ResponseLogger
from .token import Config, Key, Token, now
from .type_alias import PreWriteType
from .util import get_rfc1123_time

_servers: Dict[Tuple[str, int], wsgi.Server] = dict()
Expand All @@ -48,16 +49,28 @@ def get_server_dir():
return _server_dir


_check_token_return = True


def check_token(token: Token) -> bool:
assert token.check()
return _check_token_return


_pre_write_hook: Optional[str] = None


@contextmanager
def with_pre_write_hook(config: Dict[str, Any]):
cfg = Config.from_dictionary(config)
def with_pre_write_hook(config: Dict[str, Any], status_code=None):
if not _pre_write_hook:
return

cfg = Config.from_dictionary(config)

def check_token(request, context):
if status_code:
context.status_code = status_code
return
token = Token.from_ciphertext(cfg.key, request.text)
if token.check():
context.status_code = 200
Expand All @@ -69,6 +82,9 @@ def check_token(request, context):
yield


_pre_write_callback: Optional[PreWriteType] = None


def get_config(server_dir: Path, lock_storage: Union[Path, str]):
refresh = 600
base_url = os.environ.get("MANABI_BASE_URL") or "localhost:8081"
Expand All @@ -79,12 +95,17 @@ def get_config(server_dir: Path, lock_storage: Union[Path, str]):
lock_obj = mlock.ManabiDbLockStorage(refresh, lock_storage)
return {
"pre_write_hook": _pre_write_hook,
"pre_write_callback": _pre_write_callback,
"host": "0.0.0.0",
"port": 8081,
"mount_path": "/dav",
"lock_storage": lock_obj,
"provider_mapping": {
"/": ManabiProvider(server_dir, pre_write_hook=_pre_write_hook),
"/": ManabiProvider(
server_dir,
pre_write_hook=_pre_write_hook,
pre_write_callback=_pre_write_callback,
),
},
"middleware_stack": [
HeaderLogger,
Expand Down
6 changes: 5 additions & 1 deletion manabi/type_alias.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
from typing import Any, Dict, List, Optional, Tuple, Union
from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Tuple, Union

if TYPE_CHECKING:
from .token import Token

try:
from typing import TypeAlias
Expand All @@ -18,3 +21,4 @@
bool,
]
OptionalProp = Optional[PropType]
PreWriteType = Callable[["Token"], bool]

0 comments on commit 11757f1

Please sign in to comment.