diff --git a/.vscode/settings.json b/.vscode/settings.json index ca3a632..dac1d3b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -14,7 +14,7 @@ }, // Extensions - Code Spell Checker "cSpell.ignoreWords": ["getframe", "pydantic"], - "cSpell.words": ["mdrs", "mdrsclient", "neurodata", "Neuroinformatics", "RIKEN"], + "cSpell.words": ["mdrs", "mdrsclient", "neurodata", "chacl", "Neuroinformatics", "RIKEN"], // Extensions - isort "isort.args": ["--profile=black"], // Extensions - Prettier diff --git a/mdrsclient/__main__.py b/mdrsclient/__main__.py index 1aeda85..7a6a4b5 100644 --- a/mdrsclient/__main__.py +++ b/mdrsclient/__main__.py @@ -1,6 +1,7 @@ import argparse from mdrsclient.commands import ( + ChaclCommand, ConfigCommand, DownloadCommand, FileMetadataCommand, @@ -35,6 +36,7 @@ def main() -> None: DownloadCommand.register(parsers) MvCommand.register(parsers) RmCommand.register(parsers) + ChaclCommand.register(parsers) MetadataCommand.register(parsers) FileMetadataCommand.register(parsers) diff --git a/mdrsclient/api/folder.py b/mdrsclient/api/folder.py index 891a0e6..f5b042d 100644 --- a/mdrsclient/api/folder.py +++ b/mdrsclient/api/folder.py @@ -81,6 +81,19 @@ class FolderApi(BaseApi): raise UnauthorizedException("Password is incorrect.") return True + def acl(self, id: str, access_level: int, recursive: bool, password: str | None) -> bool: + # print(self.__class__.__name__ + "::" + sys._getframe().f_code.co_name) + url = self.ENTRYPOINT + id + "/acl/" + data: dict[str, int | str] = {"access_level": access_level} + if password is not None: + data.update({"password": password}) + if recursive is True: + data.update({"lower": 1}) + token_check(self.connection) + response = self.connection.post(url, data=data) + self._raise_response_error(response) + return True + def move(self, folder: FolderSimple, folder_id: str) -> bool: # print(self.__class__.__name__ + "::" + sys._getframe().f_code.co_name) url = self.ENTRYPOINT + folder.id + "/move/" diff --git a/mdrsclient/commands/__init__.py b/mdrsclient/commands/__init__.py index 4d907bf..8fa1445 100644 --- a/mdrsclient/commands/__init__.py +++ b/mdrsclient/commands/__init__.py @@ -1,3 +1,4 @@ +from mdrsclient.commands.chacl import ChaclCommand from mdrsclient.commands.config import ConfigCommand from mdrsclient.commands.download import DownloadCommand from mdrsclient.commands.file_metadata import FileMetadataCommand @@ -14,6 +15,7 @@ from mdrsclient.commands.whoami import WhoamiCommand __all__ = [ "ConfigCommand", + "ChaclCommand", "DownloadCommand", "FileMetadataCommand", "LabsCommand", diff --git a/mdrsclient/commands/chacl.py b/mdrsclient/commands/chacl.py new file mode 100644 index 0000000..6d9c99f --- /dev/null +++ b/mdrsclient/commands/chacl.py @@ -0,0 +1,32 @@ +from argparse import Namespace, _SubParsersAction + +from mdrsclient.api import FolderApi +from mdrsclient.commands.base import BaseCommand +from mdrsclient.exceptions import IllegalArgumentException +from mdrsclient.models import FolderAccessLevel + + +class ChaclCommand(BaseCommand): + @classmethod + def register(cls, parsers: _SubParsersAction) -> None: + command = cls() + chacl_parser = parsers.add_parser("chacl", help="change the folder access level") + chacl_parser.add_argument("access_level", help="access level (private, cbs_open, pw_open)") + chacl_parser.add_argument("-r", "--recursive", help="change access levels recursively", action="store_true") + chacl_parser.add_argument("-p", "--password", help="password to set when access level is `pw_open`") + chacl_parser.add_argument("remote_path", help="remote folder path (remote:/lab/path/)") + chacl_parser.set_defaults(func=command.chacl) + + def chacl(self, args: Namespace) -> None: + (remote, laboratory_name, r_path) = self._parse_remote_host_with_path(args.remote_path) + r_path = r_path.rstrip("/") + access_level = FolderAccessLevel.key2id(args.access_level) + if access_level is None: + raise IllegalArgumentException( + "Invalid `access_level` parameter. must be `private`, `cbs_open` or `pw_open`." + ) + connection = self._create_connection(remote) + laboratory = self._find_laboratory(connection, laboratory_name) + folder = self._find_folder(connection, laboratory, r_path) + folder_api = FolderApi(connection) + folder_api.acl(folder.id, access_level, args.recursive, args.password) diff --git a/mdrsclient/commands/mkdir.py b/mdrsclient/commands/mkdir.py index 18467f7..cfb0d4f 100644 --- a/mdrsclient/commands/mkdir.py +++ b/mdrsclient/commands/mkdir.py @@ -10,7 +10,6 @@ class MkdirCommand(BaseCommand): @classmethod def register(cls, parsers: _SubParsersAction) -> None: command = cls() - # mkdir mkdir_parser = parsers.add_parser("mkdir", help="create a new folder") mkdir_parser.add_argument("remote_path", help="remote folder path (remote:/lab/path/)") mkdir_parser.set_defaults(func=command.mkdir) diff --git a/mdrsclient/models/__init__.py b/mdrsclient/models/__init__.py index dcc9c74..5984baa 100644 --- a/mdrsclient/models/__init__.py +++ b/mdrsclient/models/__init__.py @@ -1,6 +1,6 @@ from mdrsclient.models.error import DRFStandardizedErrors from mdrsclient.models.file import File -from mdrsclient.models.folder import Folder, FolderSimple +from mdrsclient.models.folder import Folder, FolderAccessLevel, FolderSimple from mdrsclient.models.laboratory import Laboratories, Laboratory from mdrsclient.models.user import Token, User @@ -8,6 +8,7 @@ __all__ = [ "DRFStandardizedErrors", "File", "Folder", + "FolderAccessLevel", "FolderSimple", "Laboratories", "Laboratory", diff --git a/mdrsclient/models/folder.py b/mdrsclient/models/folder.py index 1c5fe3d..cabd894 100644 --- a/mdrsclient/models/folder.py +++ b/mdrsclient/models/folder.py @@ -1,17 +1,35 @@ -from typing import Final +from typing import Final, NamedTuple from pydantic.dataclasses import dataclass from mdrsclient.models.file import File from mdrsclient.models.utils import iso8601_to_user_friendly -ACCESS_LEVEL_NAMES: Final[dict[int, str]] = { - -1: "Storage", - 0: "Private", - 1: "CBS Open", - 2: "PW Open", - 3: "Public", -} + +class FolderAccessLevelItem(NamedTuple): + id: int + key: str + label: str + + +class FolderAccessLevel: + ACCESS_LEVELS: Final[list[FolderAccessLevelItem]] = [ + FolderAccessLevelItem(-1, "storage", "Storage"), + FolderAccessLevelItem(0, "private", "Private"), + FolderAccessLevelItem(1, "cbs_open", "CBS Open"), + FolderAccessLevelItem(2, "pw_open", "PW Open"), + FolderAccessLevelItem(3, "public", "Public"), + ] + + @staticmethod + def key2id(key: str) -> int | None: + acl = next((x for x in FolderAccessLevel.ACCESS_LEVELS if x.key == key), None) + return acl.id if acl is not None else None + + @staticmethod + def id2label(id: int) -> str | None: + acl = next((x for x in FolderAccessLevel.ACCESS_LEVELS if x.id == id), None) + return acl.label if acl is not None else None @dataclass(frozen=True) @@ -29,7 +47,8 @@ class FolderSimple: @property def access_level_name(self) -> str: - return ACCESS_LEVEL_NAMES[self.access_level] + label = FolderAccessLevel.id2label(self.access_level) + return label if label is not None else "" @property def lock_name(self) -> str: