Skip to content

Commit 05a5ac2

Browse files
committed
PIM-6080
Add `strict_permission` flag to core file operations This commit introduces an optional `strict_permission` parameter to multiple file operation methods, which enables enhanced permission validation before executing actions like copy, move, upload, delete, and rename.
1 parent 56b5c35 commit 05a5ac2

3 files changed

Lines changed: 190 additions & 52 deletions

File tree

cterasdk/asynchronous/core/files/browser.py

Lines changed: 57 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ async def public_link(self, path, access='RO', expire_in=30):
142142
"""
143143
return await Link(io.public_link, self._core, path, access, expire_in).a_execute()
144144

145-
async def copy(self, *paths, destination=None, resolver=None, cursor=None, wait=False):
145+
async def copy(self, *paths, destination=None, resolver=None, cursor=None, wait=False, strict_permission=False):
146146
"""
147147
Copy one or more files or folders.
148148
@@ -156,7 +156,16 @@ async def copy(self, *paths, destination=None, resolver=None, cursor=None, wait=
156156
:raises cterasdk.exceptions.io.core.CopyError: Raised on failure copying resources.
157157
"""
158158
try:
159-
return await Copy(io.copy, self._core, wait, *paths, destination=destination, resolver=resolver, cursor=cursor).a_execute()
159+
return await Copy(
160+
io.copy,
161+
self._core,
162+
wait,
163+
*paths,
164+
destination=destination,
165+
resolver=resolver,
166+
cursor=cursor,
167+
strict_permission=strict_permission
168+
).a_execute()
160169
except ValueError:
161170
raise ValueError('Copy destination was not specified.')
162171

@@ -175,7 +184,7 @@ async def permalink(self, path):
175184
class CloudDrive(FileBrowser):
176185
"""Async CloudDrive API with upload and sharing functionality."""
177186

178-
async def upload(self, destination, handle, name=None, size=None):
187+
async def upload(self, destination, handle, name=None, size=None, strict_permission=False):
179188
"""
180189
Upload from file handle.
181190
@@ -187,9 +196,18 @@ async def upload(self, destination, handle, name=None, size=None):
187196
:rtype: str
188197
:raises cterasdk.exceptions.io.core.UploadError: Raised on upload failure.
189198
"""
190-
return await Upload(io.upload, self._core, io.listdir, destination, handle, name, size).a_execute()
199+
return await Upload(
200+
io.upload,
201+
self._core,
202+
io.listdir,
203+
destination,
204+
handle,
205+
name,
206+
size,
207+
strict_permission=strict_permission
208+
).a_execute()
191209

192-
async def upload_file(self, path, destination):
210+
async def upload_file(self, path, destination, strict_permission=False):
193211
"""
194212
Upload a file.
195213
@@ -201,9 +219,15 @@ async def upload_file(self, path, destination):
201219
"""
202220
_, name = commonfs.split_file_directory(path)
203221
with open(path, 'rb') as handle:
204-
return await self.upload(destination, handle, name, commonfs.properties(path)['size'])
222+
return await self.upload(
223+
destination,
224+
handle,
225+
name,
226+
commonfs.properties(path)['size'],
227+
strict_permission=strict_permission
228+
)
205229

206-
async def mkdir(self, path):
230+
async def mkdir(self, path, strict_permission=False):
207231
"""
208232
Create a directory.
209233
@@ -212,9 +236,9 @@ async def mkdir(self, path):
212236
:rtype: str
213237
:raises cterasdk.exceptions.io.core.CreateDirectoryError: Raised on error creating directory.
214238
"""
215-
return await CreateDirectory(io.mkdir, self._core, path).a_execute()
239+
return await CreateDirectory(io.mkdir, self._core, path, strict_permission=strict_permission).a_execute()
216240

217-
async def makedirs(self, path):
241+
async def makedirs(self, path, strict_permission=False):
218242
"""
219243
Recursively create a directory.
220244
@@ -223,9 +247,9 @@ async def makedirs(self, path):
223247
:rtype: str
224248
:raises cterasdk.exceptions.io.core.CreateDirectoryError: Raised on error creating directory.
225249
"""
226-
return await CreateDirectory(io.mkdir, self._core, path, True).a_execute()
250+
return await CreateDirectory(io.mkdir, self._core, path, True, strict_permission=strict_permission).a_execute()
227251

228-
async def rename(self, path, name, *, resolver=None, wait=False):
252+
async def rename(self, path, name, *, resolver=None, wait=False, strict_permission=False):
229253
"""
230254
Rename a file or folder.
231255
@@ -237,9 +261,17 @@ async def rename(self, path, name, *, resolver=None, wait=False):
237261
:rtype: cterasdk.common.object.Object or :class:`cterasdk.lib.tasks.AwaitablePortalTask`
238262
:raises cterasdk.exceptions.io.core.RenameError: Raised on error renaming object.
239263
"""
240-
return await Rename(io.move, self._core, wait, path, name, resolver).a_execute()
264+
return await Rename(
265+
io.move,
266+
self._core,
267+
wait,
268+
path,
269+
name,
270+
resolver,
271+
strict_permission=strict_permission
272+
).a_execute()
241273

242-
async def delete(self, *paths, wait=False):
274+
async def delete(self, *paths, wait=False, strict_permission=False):
243275
"""
244276
Delete one or more files or folders.
245277
@@ -249,7 +281,7 @@ async def delete(self, *paths, wait=False):
249281
:rtype: cterasdk.common.object.Object or :class:`cterasdk.lib.tasks.AwaitablePortalTask`
250282
:raises cterasdk.exceptions.io.core.DeleteError: Raised on error deleting resources.
251283
"""
252-
return await Delete(io.delete, self._core, wait, *paths).a_execute()
284+
return await Delete(io.delete, self._core, wait, *paths, strict_permission=strict_permission).a_execute()
253285

254286
async def undelete(self, *paths, wait=False):
255287
"""
@@ -263,7 +295,7 @@ async def undelete(self, *paths, wait=False):
263295
"""
264296
return await Recover(io.undelete, self._core, wait, *paths).a_execute()
265297

266-
async def move(self, *paths, destination=None, resolver=None, cursor=None, wait=False):
298+
async def move(self, *paths, destination=None, resolver=None, cursor=None, wait=False, strict_permission=False):
267299
"""
268300
Move one or more files or folders.
269301
@@ -277,7 +309,16 @@ async def move(self, *paths, destination=None, resolver=None, cursor=None, wait=
277309
:raises cterasdk.exceptions.io.core.MoveError: Raised on error moving resources.
278310
"""
279311
try:
280-
return await Move(io.move, self._core, wait, *paths, destination=destination, resolver=resolver, cursor=cursor).a_execute()
312+
return await Move(
313+
io.move,
314+
self._core,
315+
wait,
316+
*paths,
317+
destination=destination,
318+
resolver=resolver,
319+
cursor=cursor,
320+
strict_permission=strict_permission
321+
).a_execute()
281322
except ValueError:
282323
raise ValueError('Move destination was not specified.')
283324

cterasdk/cio/core/commands.py

Lines changed: 76 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,30 @@
2020
logger = logging.getLogger('cterasdk.core')
2121

2222

23+
def _is_permission_denied_message(message):
24+
msg = (message or '').lower()
25+
return (
26+
'permission' in msg
27+
or 'denied' in msg
28+
or 'read only' in msg
29+
or 'action is not allowed' in msg
30+
)
31+
32+
33+
def _raise_strict_permission_denied(result, path):
34+
if result is None:
35+
raise exceptions.io.core.PrivilegeError(path)
36+
if isinstance(result, str) and not result.strip():
37+
raise exceptions.io.core.PrivilegeError(path)
38+
39+
msg = getattr(result, 'msg', None)
40+
rc = getattr(result, 'rc', None)
41+
if msg and _is_permission_denied_message(msg):
42+
raise exceptions.io.core.PrivilegeError(path)
43+
if msg in (None, '') and rc in (0, '0', None):
44+
raise exceptions.io.core.PrivilegeError(path)
45+
46+
2347
def split_file_directory(listdir, receiver, destination):
2448
"""
2549
Split a path into its parent directory and final component.
@@ -120,12 +144,13 @@ def ensure_writeable(resource, directory):
120144

121145
class Upload(PortalCommand):
122146

123-
def __init__(self, function, receiver, listdir, destination, fd, name, size):
147+
def __init__(self, function, receiver, listdir, destination, fd, name, size, strict_permission=False):
124148
super().__init__(function, receiver)
125149
self.destination = automatic_resolution(destination, receiver.context)
126150
self._resolver = PathResolver(listdir, receiver, self.destination, name)
127151
self.size = size
128152
self.fd = fd
153+
self._strict_permission = strict_permission
129154
self._resource = None
130155

131156
def get_parameter(self):
@@ -156,6 +181,8 @@ async def _a_execute(self):
156181

157182
def _handle_response(self, r):
158183
path = self.destination.relative
184+
if self._strict_permission:
185+
_raise_strict_permission_denied(r, path)
159186
if r.rc:
160187
error = exceptions.io.core.UploadError(r.msg, path)
161188
logger.error('Upload failed: %s', path)
@@ -596,10 +623,11 @@ def _handle_exception(self, e):
596623
class CreateDirectory(PortalCommand):
597624
"""Create Directory"""
598625

599-
def __init__(self, function, receiver, path, parents=False):
626+
def __init__(self, function, receiver, path, parents=False, strict_permission=False):
600627
super().__init__(function, receiver)
601628
self.path = automatic_resolution(path, receiver.context)
602629
self.parents = parents
630+
self._strict_permission = strict_permission
603631

604632
def get_parameter(self):
605633
param = Object()
@@ -622,7 +650,12 @@ def _execute(self):
622650
if self.parents:
623651
for path in self._parents_generator():
624652
try:
625-
CreateDirectory(self._function, self._receiver, path).execute()
653+
CreateDirectory(
654+
self._function,
655+
self._receiver,
656+
path,
657+
strict_permission=self._strict_permission
658+
).execute()
626659
except exceptions.io.core.CreateDirectoryError as e:
627660
CreateDirectory._suppress_file_conflict_error(e)
628661
with self.trace_execution():
@@ -632,7 +665,12 @@ async def _a_execute(self):
632665
if self.parents:
633666
for path in self._parents_generator():
634667
try:
635-
await CreateDirectory(self._function, self._receiver, path).a_execute()
668+
await CreateDirectory(
669+
self._function,
670+
self._receiver,
671+
path,
672+
strict_permission=self._strict_permission
673+
).a_execute()
636674
except exceptions.io.core.CreateDirectoryError as e:
637675
CreateDirectory._suppress_file_conflict_error(e)
638676
with self.trace_execution():
@@ -645,6 +683,8 @@ def _suppress_file_conflict_error(e):
645683

646684
def _handle_response(self, r):
647685
path = self.path.relative
686+
if self._strict_permission:
687+
_raise_strict_permission_denied(r, path)
648688
if r is None or r == 'Ok':
649689
return path
650690

@@ -903,10 +943,11 @@ def _handle_response(self, r):
903943

904944
class TaskCommand(PortalCommand):
905945

906-
def __init__(self, function, receiver, block):
946+
def __init__(self, function, receiver, block, strict_permission=False):
907947
super().__init__(function, receiver)
908948
self.block = block
909949
self.background = True
950+
self._strict_permission = strict_permission
910951

911952
@abstractmethod
912953
def _progress_str(self):
@@ -938,6 +979,11 @@ async def _a_execute(self):
938979
return await function(self.get_parameter())
939980

940981
def _handle_response(self, r):
982+
if self._strict_permission:
983+
msg = getattr(r, 'msg', None)
984+
error_type = getattr(r, 'error_type', None)
985+
if _is_permission_denied_message(str(msg)) or _is_permission_denied_message(str(error_type)):
986+
raise exceptions.io.core.PrivilegeError('')
941987
if not self.block:
942988
return r
943989

@@ -958,8 +1004,8 @@ def _task_error(self, task): # pylint: disable=no-self-use
9581004

9591005
class MultiResourceCommand(TaskCommand):
9601006

961-
def __init__(self, function, receiver, block, *paths):
962-
super().__init__(function, receiver, block)
1007+
def __init__(self, function, receiver, block, *paths, strict_permission=False):
1008+
super().__init__(function, receiver, block, strict_permission=strict_permission)
9631009
self.paths = list(automatic_resolution(paths, receiver.context))
9641010

9651011
def get_parameter(self):
@@ -998,8 +1044,9 @@ def _task_error(self, task):
9981044

9991045
class ResolverCommand(TaskCommand):
10001046

1001-
def __init__(self, function, receiver, block, *paths, destination=None, resolver=None, cursor=None):
1002-
super().__init__(function, receiver, block)
1047+
def __init__(self, function, receiver, block, *paths, destination=None, resolver=None, cursor=None,
1048+
strict_permission=False):
1049+
super().__init__(function, receiver, block, strict_permission=strict_permission)
10031050
self.paths = list(automatic_resolution(paths, receiver.context))
10041051
self.destination = automatic_resolution(destination, receiver.context)
10051052
self.resolver = resolver
@@ -1088,11 +1135,13 @@ def _progress_str(self):
10881135

10891136
def _try_with_resolver(self, cursor):
10901137
return Copy(self._function, self._receiver, self.block, *self.paths,
1091-
destination=self.destination, resolver=self.resolver, cursor=cursor).execute()
1138+
destination=self.destination, resolver=self.resolver, cursor=cursor,
1139+
strict_permission=self._strict_permission).execute()
10921140

10931141
async def _a_try_with_resolver(self, cursor):
10941142
return await Copy(self._function, self._receiver, self.block, *self.paths,
1095-
destination=self.destination, resolver=self.resolver, cursor=cursor).a_execute()
1143+
destination=self.destination, resolver=self.resolver, cursor=cursor,
1144+
strict_permission=self._strict_permission).a_execute()
10961145

10971146
@property
10981147
def _error_object(self):
@@ -1106,11 +1155,13 @@ def _progress_str(self):
11061155

11071156
def _try_with_resolver(self, cursor):
11081157
return Move(self._function, self._receiver, self.block, *self.paths,
1109-
destination=self.destination, resolver=self.resolver, cursor=cursor).execute()
1158+
destination=self.destination, resolver=self.resolver, cursor=cursor,
1159+
strict_permission=self._strict_permission).execute()
11101160

11111161
async def _a_try_with_resolver(self, cursor):
11121162
return await Move(self._function, self._receiver, self.block, *self.paths,
1113-
destination=self.destination, resolver=self.resolver, cursor=cursor).a_execute()
1163+
destination=self.destination, resolver=self.resolver, cursor=cursor,
1164+
strict_permission=self._strict_permission).a_execute()
11141165

11151166
@property
11161167
def _error_object(self):
@@ -1122,21 +1173,26 @@ class Rename(Move):
11221173
def _progress_str(self):
11231174
return 'Renaming'
11241175

1125-
def __init__(self, function, receiver, block, path, new_name, resolver, cursor=None):
1126-
super().__init__(function, receiver, block,
1127-
*[(path, automatic_resolution(path, receiver.context).parent.join(new_name))],
1128-
resolver=resolver, cursor=cursor
1129-
)
1176+
def __init__(self, function, receiver, block, path, new_name, resolver, cursor=None, strict_permission=False):
1177+
super().__init__(
1178+
function,
1179+
receiver,
1180+
block,
1181+
*[(path, automatic_resolution(path, receiver.context).parent.join(new_name))],
1182+
resolver=resolver,
1183+
cursor=cursor,
1184+
strict_permission=strict_permission
1185+
)
11301186

11311187
def _try_with_resolver(self, cursor):
11321188
source, destination = self.paths[0]
11331189
return Rename(self._function, self._receiver, self.block, source, destination.name,
1134-
resolver=self.resolver, cursor=cursor).execute()
1190+
resolver=self.resolver, cursor=cursor, strict_permission=self._strict_permission).execute()
11351191

11361192
async def _a_try_with_resolver(self, cursor):
11371193
source, destination = self.paths[0]
11381194
return await Rename(self._function, self._receiver, self.block, source, destination.name,
1139-
resolver=self.resolver, cursor=cursor).a_execute()
1195+
resolver=self.resolver, cursor=cursor, strict_permission=self._strict_permission).a_execute()
11401196

11411197
@property
11421198
def _error_object(self):

0 commit comments

Comments
 (0)