Skip to content

Commit 7b6e428

Browse files
committed
ensure destination is writeable target, update to pass exceptions
1 parent d262e56 commit 7b6e428

5 files changed

Lines changed: 109 additions & 21 deletions

File tree

cterasdk/asynchronous/core/files/io.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,7 @@ async def move(core, *paths, destination=None, resolver=None, cursor=None):
9595

9696
async def ensure_directory(core, directory, suppress_error=False):
9797
present, resource = await metadata(core, directory, suppress_error=True)
98-
if (not present or not resource.isFolder) and not suppress_error:
99-
raise NotADirectory(directory.absolute)
98+
fs.ensure_directory(present, resource, directory, suppress_error)
10099
return resource.isFolder if present else False, resource
101100

102101

@@ -146,7 +145,9 @@ async def _validate_destination(core, name, destination):
146145
is_dir, resource = await ensure_directory(core, destination, suppress_error=True)
147146
if not is_dir:
148147
is_dir, resource = await ensure_directory(core, destination.parent)
148+
fs.ensure_writeable(resource, destination.parent)
149149
return resource.cloudFolderInfo.uid, destination.name, destination.parent
150+
fs.ensure_writeable(resource, destination)
150151
return resource.cloudFolderInfo.uid, name, destination
151152

152153

cterasdk/cio/core.py

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@
44
from ..objects.uri import quote, unquote
55
from ..common import Object, DateTimeUtils
66
from ..core.enum import ProtectionLevel, CollaboratorType, SearchType, PortalAccountType, FileAccessMode, \
7-
UploadError
7+
UploadError, ResourceAction, ResourceScope
88
from ..core.types import PortalAccount, UserAccount, GroupAccount
99
from ..exceptions.io import ResourceExistsError, PathValidationError, NameSyntaxError, \
10-
ReservedNameError, RestrictedRoot, InsufficientPermission, FileConflict, RemoteStorageError
10+
ReservedNameError, RestrictedRoot, PermissionDenied, FileConflict, RemoteStorageError, NotADirectory, \
11+
UnwriteableScope
1112
from ..exceptions.io import UploadException, OutOfQuota, RejectedByPolicy, NoStorageBucket, WindowsACLError
1213
from ..lib.iterator import DefaultResponse
1314
from . import common
@@ -316,12 +317,24 @@ def handle(path):
316317

317318

318319
def destination_prerequisite_conditions(destination, name):
319-
if not len(destination.reference.parts) > 0:
320-
raise RestrictedRoot()
321320
if any(c in name for c in ['\\', '/', ':', '?', '&', '<', '>', '"', '|']):
322321
raise NameSyntaxError(destination.join(name).reference.as_posix())
323322

324323

324+
def ensure_directory(present, resource, directory, suppress_error):
325+
if (not present or not resource.isFolder) and not suppress_error:
326+
raise NotADirectory(directory.reference.as_posix())
327+
328+
329+
def ensure_writeable(resource, directory):
330+
if resource.scope == ResourceScope.Root:
331+
raise RestrictedRoot()
332+
if resource.scope not in [ResourceScope.Personal, ResourceScope.Project, ResourceScope.InsideCloudFolder]:
333+
raise UnwriteableScope(directory.reference.as_posix(), resource.scope)
334+
if resource.permission != FileAccessMode.RW:
335+
raise PermissionDenied(directory.reference.as_posix(), ResourceAction.Write)
336+
337+
325338
@contextmanager
326339
def upload(core, name, destination, size, fd):
327340
fd, size = common.encode_stream(fd, size)
@@ -555,7 +568,7 @@ def obtain_current_accounts(param):
555568

556569
file_access_errors = {
557570
"Conflict": FileConflict,
558-
"PermissionDenied": InsufficientPermission,
571+
"PermissionDenied": PermissionDenied,
559572
"DestinationNotExists": PathValidationError,
560573
"FileWithTheSameNameExist": ResourceExistsError,
561574
"InvalidName": NameSyntaxError,
@@ -572,7 +585,7 @@ def await_or_future(core, ref, wait):
572585
"""
573586
if wait:
574587
task = core.tasks.wait(ref)
575-
accept_error(task.error_type, action=task.name.lower(), name=task.cursor.destResource.name, cursor=task.cursor)
588+
accept_error(task.error_type, **error_metadata(task))
576589
return task
577590
return core.tasks.awaitable_task(ref)
578591

@@ -586,11 +599,22 @@ async def a_await_or_future(ctera, ref, wait):
586599
"""
587600
if wait:
588601
task = await ctera.tasks.wait(ref)
589-
accept_error(task.error_type, action=task.name.lower(), name=task.cursor.destResource.name, cursor=task.cursor)
602+
accept_error(task.error_type, **error_metadata(task))
590603
return task
591604
return ctera.tasks.awaitable_task(ref)
592605

593606

607+
def error_metadata(task):
608+
metadata = dict(
609+
action=task.name
610+
)
611+
if task.name in [ResourceAction.Copy, ResourceAction.Move]:
612+
metadata.update(dict(cursor=task.cursor))
613+
if task.cursor.destResource:
614+
metadata.update(dict(name=task.cursor.destResource.name))
615+
return metadata
616+
617+
594618
def accept_error(error_type, **kwargs):
595619
"""
596620
Check if response contains an error.
@@ -611,6 +635,9 @@ def accept_error(error_type, **kwargs):
611635
except ReservedNameError as error:
612636
logger.error('Reserved name error: the name is reserved and cannot be used.')
613637
raise error
638+
except PermissionDenied as error:
639+
logger.error('Permission denied: Inappropriate permissions to access this resource.')
640+
raise error
614641
except FileConflict as error:
615642
logger.error('Conflict: a file with the same name already exists: %s', error.name)
616643
raise error

cterasdk/core/enum.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -676,3 +676,54 @@ class UploadError:
676676
RejectedByPolicy = "Rejected by Cloud Drive policy rule"
677677
NoStorageBucket = "No available storage location"
678678
WindowsACL = "Illegal access to NTACL folder"
679+
680+
681+
class ResourceAction:
682+
"""
683+
Resource Action
684+
685+
:ivar str Delete: Delete.
686+
:ivar str Copy: Copy.
687+
:ivar str Move: Move.
688+
:ivar str Undelete: Undelete.
689+
"""
690+
Delete = 'Delete'
691+
Undelete = 'Undelete'
692+
Copy = 'Copy'
693+
Move = 'Move'
694+
Write = 'Write'
695+
696+
697+
class ResourceScope:
698+
"""
699+
Resource Scope
700+
701+
:ivar str Root: Root.
702+
:ivar str ProjectsContainer: ProjectsContainer.
703+
:ivar str Project: Project.
704+
:ivar str SharedContainer: SharedContainer.
705+
:ivar str SharedDomain: SharedDomain.
706+
:ivar str Shared: Shared.
707+
:ivar str BackupsContainer: BackupsContainer.
708+
:ivar str Backup: Backup.
709+
:ivar str Personal: Personal.
710+
:ivar str UsersContainer: UsersContainer.
711+
:ivar str UsersFoldersContainer: UsersFoldersContainer.
712+
:ivar str CloudDrivesContainer: CloudDrivesContainer.
713+
:ivar str OfflineFolder: OfflineFolder.
714+
:ivar str InsideCloudFolder: InsideCloudFolder.
715+
"""
716+
Root = "Root"
717+
ProjectsContainer = "ProjectsContainer"
718+
Project = "Project"
719+
SharedContainer = "SharedContainer"
720+
SharedDomain = "SharedDomain"
721+
Shared = "Shared"
722+
BackupsContainer = "BackupsContainer"
723+
Backup = "Backup"
724+
Personal = "Personal"
725+
UsersContainer = "UsersContainer"
726+
UsersFoldersContainer = "UsersFoldersContainer"
727+
CloudDrivesContainer = "CloudDrivesContainer"
728+
OfflineFolder = "OfflineFolder"
729+
InsideCloudFolder = "InsideCloudFolder"

cterasdk/core/files/io.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import logging
22
from ...cio.common import encode_request_parameter
33
from ...cio import core as fs
4-
from ...exceptions.io import ResourceNotFoundError, ResourceExistsError, NotADirectory
4+
from ...exceptions.io import ResourceNotFoundError, ResourceExistsError
55
from ...core import query
66
from ..enum import CollaboratorType
77

@@ -97,8 +97,7 @@ def move(core, *paths, destination=None, resolver=None, cursor=None):
9797

9898
def ensure_directory(core, directory, suppress_error=False):
9999
present, resource = metadata(core, directory, suppress_error=True)
100-
if (not present or not resource.isFolder) and not suppress_error:
101-
raise NotADirectory(directory.absolute)
100+
fs.ensure_directory(present, resource, directory, suppress_error)
102101
return resource.isFolder if present else False, resource
103102

104103

@@ -148,7 +147,9 @@ def _validate_destination(core, name, destination):
148147
is_dir, resource = ensure_directory(core, destination, suppress_error=True)
149148
if not is_dir:
150149
is_dir, resource = ensure_directory(core, destination.parent)
150+
fs.ensure_writeable(resource, destination.parent)
151151
return resource.cloudFolderInfo.uid, destination.name, destination.parent
152+
fs.ensure_writeable(resource, destination)
152153
return resource.cloudFolderInfo.uid, name, destination
153154

154155

cterasdk/exceptions/io.py

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,14 @@ def __init__(self, path):
2626

2727
class ResourceExistsError(RemoteStorageError):
2828

29-
def __init__(self):
30-
super().__init__('Resource already exists: a file or folder with this name already exists.')
29+
def __init__(self, path):
30+
super().__init__('Resource already exists: a file or folder with this name already exists.', path)
3131

3232

3333
class PathValidationError(RemoteStorageError):
3434

35-
def __init__(self):
36-
super().__init__('Path validation failed: the specified destination path does not exist.')
35+
def __init__(self, path=None, **kwargs):
36+
super().__init__('Path validation failed: the specified destination path does not exist.', path)
3737

3838

3939
class NameSyntaxError(RemoteStorageError):
@@ -44,8 +44,8 @@ def __init__(self, path):
4444

4545
class ReservedNameError(RemoteStorageError):
4646

47-
def __init__(self):
48-
super().__init__('Reserved name error: the name is reserved and cannot be used.')
47+
def __init__(self, path):
48+
super().__init__('Reserved name error: the name is reserved and cannot be used.', path)
4949

5050

5151
class RestrictedPathError(RemoteStorageError):
@@ -60,10 +60,18 @@ def __init__(self):
6060
super().__init__('Storing files to the root directory is forbidden.', '/')
6161

6262

63-
class InsufficientPermission(RemoteStorageError):
63+
class PermissionDenied(RemoteStorageError):
6464

65-
def __init__(self):
66-
super().__init__('Permission denied: You must have appropriate permissions to access this resource.')
65+
def __init__(self, action, path=None):
66+
super().__init__('Permission denied: Inappropriate permissions to access this resource.', path)
67+
self.action = action
68+
69+
70+
class UnwriteableScope(RemoteStorageError):
71+
72+
def __init__(self, path, scope):
73+
super().__init__("Write access denied. This target is protected and cannot be modified.", path)
74+
self.scope = scope
6775

6876

6977
class FileConflict(RemoteStorageError):

0 commit comments

Comments
 (0)