dtlpy 1.118.12__py3-none-any.whl → 1.118.13__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dtlpy/__version__.py +1 -1
- dtlpy/dlp/command_executor.py +55 -5
- dtlpy/miscellaneous/__init__.py +1 -0
- dtlpy/miscellaneous/path_utils.py +264 -0
- dtlpy/repositories/annotations.py +1 -4
- dtlpy/repositories/packages.py +9 -0
- dtlpy/repositories/projects.py +1 -3
- dtlpy/services/api_client.py +20 -4
- dtlpy/services/check_sdk.py +1 -4
- dtlpy/services/logins.py +21 -17
- dtlpy/utilities/videos/videos.py +4 -0
- {dtlpy-1.118.12.dist-info → dtlpy-1.118.13.dist-info}/METADATA +1 -1
- {dtlpy-1.118.12.dist-info → dtlpy-1.118.13.dist-info}/RECORD +20 -19
- {dtlpy-1.118.12.data → dtlpy-1.118.13.data}/scripts/dlp +0 -0
- {dtlpy-1.118.12.data → dtlpy-1.118.13.data}/scripts/dlp.bat +0 -0
- {dtlpy-1.118.12.data → dtlpy-1.118.13.data}/scripts/dlp.py +0 -0
- {dtlpy-1.118.12.dist-info → dtlpy-1.118.13.dist-info}/WHEEL +0 -0
- {dtlpy-1.118.12.dist-info → dtlpy-1.118.13.dist-info}/entry_points.txt +0 -0
- {dtlpy-1.118.12.dist-info → dtlpy-1.118.13.dist-info}/licenses/LICENSE +0 -0
- {dtlpy-1.118.12.dist-info → dtlpy-1.118.13.dist-info}/top_level.txt +0 -0
dtlpy/__version__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
version = '1.118.
|
|
1
|
+
version = '1.118.13'
|
dtlpy/dlp/command_executor.py
CHANGED
|
@@ -6,7 +6,7 @@ import os
|
|
|
6
6
|
import sys
|
|
7
7
|
import jwt
|
|
8
8
|
|
|
9
|
-
from .. import exceptions, entities, repositories, utilities, assets
|
|
9
|
+
from .. import exceptions, entities, repositories, utilities, assets, miscellaneous
|
|
10
10
|
|
|
11
11
|
logger = logging.getLogger(name='dtlpy')
|
|
12
12
|
|
|
@@ -76,8 +76,16 @@ class CommandExecutor:
|
|
|
76
76
|
url = 'dtlpy'
|
|
77
77
|
if args.url is None:
|
|
78
78
|
try:
|
|
79
|
-
|
|
80
|
-
|
|
79
|
+
# oxsec-disable jwt-signature-disabled - Client-side SDK: signature verification disabled intentionally to check admin role; server validates on API calls
|
|
80
|
+
payload = jwt.decode(
|
|
81
|
+
self.dl.client_api.token,
|
|
82
|
+
options={
|
|
83
|
+
"verify_signature": False,
|
|
84
|
+
"verify_exp": False,
|
|
85
|
+
"verify_aud": False,
|
|
86
|
+
"verify_iss": False,
|
|
87
|
+
}
|
|
88
|
+
)
|
|
81
89
|
if 'admin' in payload['https://dataloop.ai/authorization']['roles']:
|
|
82
90
|
url = "https://storage.googleapis.com/dtlpy/dev/dtlpy-latest-py3-none-any.whl"
|
|
83
91
|
except Exception:
|
|
@@ -235,6 +243,13 @@ class CommandExecutor:
|
|
|
235
243
|
project = self.dl.projects.get(project_name=args.project_name)
|
|
236
244
|
dataset = project.datasets.get(dataset_name=args.dataset_name)
|
|
237
245
|
|
|
246
|
+
# Validate local_path and local_annotations_path to prevent path traversal
|
|
247
|
+
miscellaneous.PathUtils.validate_paths(
|
|
248
|
+
[args.local_path, args.local_annotations_path],
|
|
249
|
+
base_path=os.getcwd(),
|
|
250
|
+
must_exist=True
|
|
251
|
+
)
|
|
252
|
+
|
|
238
253
|
dataset.items.upload(local_path=args.local_path,
|
|
239
254
|
remote_path=args.remote_path,
|
|
240
255
|
file_types=args.file_types,
|
|
@@ -277,6 +292,13 @@ class CommandExecutor:
|
|
|
277
292
|
remote_path.pop(remote_path.index(item))
|
|
278
293
|
filters.add(field="dir", values=remote_path, operator=entities.FiltersOperations.IN, method='or')
|
|
279
294
|
|
|
295
|
+
# Validate local_path to prevent path traversal
|
|
296
|
+
miscellaneous.PathUtils.validate_directory_path(
|
|
297
|
+
args.local_path,
|
|
298
|
+
base_path=os.getcwd(),
|
|
299
|
+
must_exist=False
|
|
300
|
+
)
|
|
301
|
+
|
|
280
302
|
if not args.without_binaries:
|
|
281
303
|
dataset.items.download(filters=filters,
|
|
282
304
|
local_path=args.local_path,
|
|
@@ -325,6 +347,9 @@ class CommandExecutor:
|
|
|
325
347
|
args.split_seconds = int(args.split_seconds)
|
|
326
348
|
if isinstance(args.split_times, str):
|
|
327
349
|
args.split_times = [int(sec) for sec in args.split_times.split(",")]
|
|
350
|
+
# Validate filepath to prevent path traversal
|
|
351
|
+
miscellaneous.PathUtils.validate_file_path(args.filename)
|
|
352
|
+
|
|
328
353
|
self.dl.utilities.videos.Videos.split_and_upload(
|
|
329
354
|
project_name=args.project_name,
|
|
330
355
|
dataset_name=args.dataset_name,
|
|
@@ -407,6 +432,8 @@ class CommandExecutor:
|
|
|
407
432
|
def deploy(self, args):
|
|
408
433
|
project = self.dl.projects.get(project_name=args.project_name)
|
|
409
434
|
json_filepath = args.json_file
|
|
435
|
+
# Validate file path to prevent path traversal
|
|
436
|
+
miscellaneous.PathUtils.validate_file_path(json_filepath)
|
|
410
437
|
deployed_services, package = self.dl.packages.deploy_from_file(project=project, json_filepath=json_filepath)
|
|
411
438
|
logger.info("Successfully deployed {} from file: {}\nServices: {}".format(len(deployed_services),
|
|
412
439
|
json_filepath,
|
|
@@ -464,6 +491,13 @@ class CommandExecutor:
|
|
|
464
491
|
elif args.packages == "push":
|
|
465
492
|
packages = self.utils.get_packages_repo(args=args)
|
|
466
493
|
|
|
494
|
+
# Validate src_path to prevent path traversal
|
|
495
|
+
miscellaneous.PathUtils.validate_directory_path(
|
|
496
|
+
args.src_path,
|
|
497
|
+
base_path=os.getcwd(),
|
|
498
|
+
must_exist=True
|
|
499
|
+
)
|
|
500
|
+
|
|
467
501
|
package = packages.push(src_path=args.src_path,
|
|
468
502
|
package_name=args.package_name,
|
|
469
503
|
checkout=args.checkout)
|
|
@@ -568,7 +602,13 @@ class CommandExecutor:
|
|
|
568
602
|
answers = inquirer.prompt(questions)
|
|
569
603
|
#####
|
|
570
604
|
# create a dir for that panel
|
|
571
|
-
|
|
605
|
+
# Validate panel name to prevent path traversal
|
|
606
|
+
panel_name = answers.get('name')
|
|
607
|
+
# Validate panel name to prevent path traversal
|
|
608
|
+
miscellaneous.PathUtils.validate_directory_name(panel_name)
|
|
609
|
+
# Create directory in current working directory
|
|
610
|
+
panel_dir = os.path.join(os.getcwd(), panel_name)
|
|
611
|
+
os.makedirs(panel_dir, exist_ok=True)
|
|
572
612
|
# dump to dataloop.json
|
|
573
613
|
app_filename = assets.paths.APP_JSON_FILENAME
|
|
574
614
|
if not os.path.isfile(app_filename):
|
|
@@ -630,12 +670,22 @@ class CommandExecutor:
|
|
|
630
670
|
directory = args.dir
|
|
631
671
|
if directory == '..':
|
|
632
672
|
directory = os.path.split(os.getcwd())[0]
|
|
673
|
+
# Validate path to prevent path traversal
|
|
674
|
+
miscellaneous.PathUtils.validate_directory_path(
|
|
675
|
+
directory,
|
|
676
|
+
base_path=os.getcwd(),
|
|
677
|
+
must_exist=True
|
|
678
|
+
)
|
|
633
679
|
os.chdir(directory)
|
|
634
680
|
print(os.getcwd())
|
|
635
681
|
|
|
636
682
|
@staticmethod
|
|
637
683
|
def mkdir(args):
|
|
638
|
-
|
|
684
|
+
# Validate directory name to prevent path traversal
|
|
685
|
+
miscellaneous.PathUtils.validate_directory_name(args.name)
|
|
686
|
+
# Create directory in current working directory
|
|
687
|
+
dir_path = os.path.join(os.getcwd(), args.name)
|
|
688
|
+
os.mkdir(dir_path)
|
|
639
689
|
|
|
640
690
|
# noinspection PyUnusedLocal
|
|
641
691
|
@staticmethod
|
dtlpy/miscellaneous/__init__.py
CHANGED
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import tempfile
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from .. import exceptions
|
|
5
|
+
from ..services import service_defaults
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class PathUtils:
|
|
9
|
+
"""
|
|
10
|
+
Utility class for path validation and sanitization to prevent path traversal attacks.
|
|
11
|
+
"""
|
|
12
|
+
allowed_roots = [tempfile.gettempdir(), service_defaults.DATALOOP_PATH]
|
|
13
|
+
|
|
14
|
+
@staticmethod
|
|
15
|
+
def _contains_traversal(path: str) -> bool:
|
|
16
|
+
"""
|
|
17
|
+
Check if path contains path traversal sequences.
|
|
18
|
+
|
|
19
|
+
:param str path: Path to check
|
|
20
|
+
:return: True if path contains traversal sequences
|
|
21
|
+
:rtype: bool
|
|
22
|
+
"""
|
|
23
|
+
if not path:
|
|
24
|
+
return False
|
|
25
|
+
|
|
26
|
+
# Normalize the path to handle different separators
|
|
27
|
+
normalized = os.path.normpath(path)
|
|
28
|
+
|
|
29
|
+
# Check for parent directory references
|
|
30
|
+
parts = Path(normalized).parts
|
|
31
|
+
if '..' in parts:
|
|
32
|
+
return True
|
|
33
|
+
|
|
34
|
+
# Check for encoded traversal sequences (evasion attempts)
|
|
35
|
+
if '%2e%2e' in path.lower() or '..%2f' in path.lower() or '..%5c' in path.lower():
|
|
36
|
+
return True
|
|
37
|
+
|
|
38
|
+
return False
|
|
39
|
+
|
|
40
|
+
@staticmethod
|
|
41
|
+
def _is_within_base(resolved_path: str, base_path: str) -> bool:
|
|
42
|
+
"""
|
|
43
|
+
Check if resolved_path is within base_path.
|
|
44
|
+
|
|
45
|
+
:param str resolved_path: Absolute resolved path
|
|
46
|
+
:param str base_path: Base directory path
|
|
47
|
+
:return: True if resolved_path is within base_path
|
|
48
|
+
:rtype: bool
|
|
49
|
+
"""
|
|
50
|
+
try:
|
|
51
|
+
resolved = os.path.abspath(os.path.normpath(resolved_path))
|
|
52
|
+
base = os.path.abspath(os.path.normpath(base_path))
|
|
53
|
+
|
|
54
|
+
# Get common path
|
|
55
|
+
common = os.path.commonpath([resolved, base])
|
|
56
|
+
return common == base
|
|
57
|
+
except (ValueError, OSError):
|
|
58
|
+
# On Windows, if paths are on different drives, commonpath raises ValueError
|
|
59
|
+
return False
|
|
60
|
+
|
|
61
|
+
@staticmethod
|
|
62
|
+
def _is_allowed_path(resolved_path: str, base_path: str) -> bool:
|
|
63
|
+
"""
|
|
64
|
+
Check if resolved_path is within base_path or any allowed_root.
|
|
65
|
+
|
|
66
|
+
:param str resolved_path: Absolute resolved path
|
|
67
|
+
:param str base_path: Base directory path
|
|
68
|
+
:return: True if resolved_path is within base_path or any allowed_root
|
|
69
|
+
:rtype: bool
|
|
70
|
+
"""
|
|
71
|
+
for allowed_root in [base_path] + PathUtils.allowed_roots:
|
|
72
|
+
if PathUtils._is_within_base(resolved_path, allowed_root):
|
|
73
|
+
return True
|
|
74
|
+
return False
|
|
75
|
+
|
|
76
|
+
@staticmethod
|
|
77
|
+
def validate_directory_name(name: str) -> str:
|
|
78
|
+
"""
|
|
79
|
+
Validate a directory name to ensure it doesn't contain path traversal sequences.
|
|
80
|
+
|
|
81
|
+
:param str name: Directory name to validate
|
|
82
|
+
:return: Validated directory name
|
|
83
|
+
:rtype: str
|
|
84
|
+
:raises PlatformException: If name contains invalid characters or traversal sequences
|
|
85
|
+
"""
|
|
86
|
+
if not name:
|
|
87
|
+
raise exceptions.PlatformException(
|
|
88
|
+
error='400',
|
|
89
|
+
message='Directory name cannot be empty'
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
# Check for path separators
|
|
93
|
+
if os.sep in name or (os.altsep and os.altsep in name):
|
|
94
|
+
raise exceptions.PlatformException(
|
|
95
|
+
error='400',
|
|
96
|
+
message='Directory name cannot contain path separators'
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
# Check for traversal sequences
|
|
100
|
+
if PathUtils._contains_traversal(name):
|
|
101
|
+
raise exceptions.PlatformException(
|
|
102
|
+
error='400',
|
|
103
|
+
message='Directory name cannot contain path traversal sequences'
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
return name
|
|
107
|
+
|
|
108
|
+
@staticmethod
|
|
109
|
+
def _validate_single_path(path, base_path: str, must_exist: bool):
|
|
110
|
+
"""
|
|
111
|
+
Internal method to validate a single path string.
|
|
112
|
+
|
|
113
|
+
:param path: Path to validate (str or Path object)
|
|
114
|
+
:param str base_path: Base directory to restrict path to
|
|
115
|
+
:param bool must_exist: If True, path must exist
|
|
116
|
+
:raises PlatformException: If path is invalid or contains traversal sequences
|
|
117
|
+
"""
|
|
118
|
+
# Convert Path objects to strings
|
|
119
|
+
if isinstance(path, Path):
|
|
120
|
+
path = str(path)
|
|
121
|
+
if isinstance(base_path, Path):
|
|
122
|
+
base_path = str(base_path)
|
|
123
|
+
|
|
124
|
+
# Skip validation if not a string
|
|
125
|
+
if not isinstance(path, str):
|
|
126
|
+
return
|
|
127
|
+
|
|
128
|
+
# Skip validation for URLs and external paths
|
|
129
|
+
if path.startswith(('http://', 'https://', 'external://')):
|
|
130
|
+
return
|
|
131
|
+
|
|
132
|
+
# Empty string check
|
|
133
|
+
if not path:
|
|
134
|
+
raise exceptions.PlatformException(
|
|
135
|
+
error='400',
|
|
136
|
+
message='Path cannot be empty'
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
# Check for traversal sequences in the original path
|
|
140
|
+
if PathUtils._contains_traversal(path):
|
|
141
|
+
raise exceptions.PlatformException(
|
|
142
|
+
error='400',
|
|
143
|
+
message='Path contains invalid traversal sequences'
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
# Resolve path (absolute paths allowed if within base_path)
|
|
147
|
+
if os.path.isabs(path):
|
|
148
|
+
resolved = os.path.abspath(os.path.normpath(path))
|
|
149
|
+
else:
|
|
150
|
+
resolved = os.path.abspath(os.path.normpath(os.path.join(base_path, path)))
|
|
151
|
+
|
|
152
|
+
# Reject if path is outside base_path or allowed_roots
|
|
153
|
+
if not PathUtils._is_allowed_path(resolved, base_path):
|
|
154
|
+
raise exceptions.PlatformException(
|
|
155
|
+
error='400',
|
|
156
|
+
message='Path resolves outside allowed directory'
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
# Check if path must exist
|
|
160
|
+
if must_exist and not os.path.exists(resolved):
|
|
161
|
+
raise exceptions.PlatformException(
|
|
162
|
+
error='404',
|
|
163
|
+
message='Path does not exist: {}'.format(path)
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
@staticmethod
|
|
167
|
+
def validate_paths(paths, base_path = None, must_exist: bool = False):
|
|
168
|
+
"""
|
|
169
|
+
Validate file or directory paths against path traversal attacks.
|
|
170
|
+
Accepts a list of paths and validates each one.
|
|
171
|
+
Skips validation if path is None or not a string.
|
|
172
|
+
Skips validation for URLs (http://, https://) and external paths (external://).
|
|
173
|
+
|
|
174
|
+
:param paths: Path(s) to validate - can be str, Path, list of str/Path, or None
|
|
175
|
+
:param base_path: Optional base directory to restrict path to (str or Path). If None, uses current working directory
|
|
176
|
+
:param bool must_exist: If True, path must exist
|
|
177
|
+
:raises PlatformException: If any path is invalid or contains traversal sequences
|
|
178
|
+
"""
|
|
179
|
+
# Handle None - skip validation
|
|
180
|
+
if paths is None:
|
|
181
|
+
return
|
|
182
|
+
|
|
183
|
+
# Convert base_path Path object to string
|
|
184
|
+
if isinstance(base_path, Path):
|
|
185
|
+
base_path = str(base_path)
|
|
186
|
+
|
|
187
|
+
# Resolve base_path
|
|
188
|
+
if base_path is None:
|
|
189
|
+
base_path = os.getcwd()
|
|
190
|
+
|
|
191
|
+
# Handle list of paths
|
|
192
|
+
if isinstance(paths, list):
|
|
193
|
+
for path in paths:
|
|
194
|
+
PathUtils._validate_single_path(path, base_path, must_exist)
|
|
195
|
+
else:
|
|
196
|
+
# Single path
|
|
197
|
+
PathUtils._validate_single_path(paths, base_path, must_exist)
|
|
198
|
+
|
|
199
|
+
@staticmethod
|
|
200
|
+
def validate_file_path(file_path, base_path = None, must_exist: bool = True):
|
|
201
|
+
"""
|
|
202
|
+
Validate a file path against path traversal attacks.
|
|
203
|
+
|
|
204
|
+
:param file_path: File path to validate (str or Path object)
|
|
205
|
+
:param base_path: Optional base directory to restrict path to (str or Path). If None, uses current working directory
|
|
206
|
+
:param bool must_exist: If True, file must exist (default: True)
|
|
207
|
+
:raises PlatformException: If path is invalid, contains traversal sequences, or is not a file
|
|
208
|
+
"""
|
|
209
|
+
# Convert Path objects to strings
|
|
210
|
+
if isinstance(file_path, Path):
|
|
211
|
+
file_path = str(file_path)
|
|
212
|
+
if isinstance(base_path, Path):
|
|
213
|
+
base_path = str(base_path)
|
|
214
|
+
|
|
215
|
+
PathUtils.validate_paths(file_path, base_path=base_path, must_exist=must_exist)
|
|
216
|
+
|
|
217
|
+
if must_exist and isinstance(file_path, str) and not file_path.startswith(('http://', 'https://', 'external://')):
|
|
218
|
+
# Resolve path to check if it's a file
|
|
219
|
+
if base_path is None:
|
|
220
|
+
base_path = os.getcwd()
|
|
221
|
+
if os.path.isabs(file_path):
|
|
222
|
+
resolved = os.path.abspath(os.path.normpath(file_path))
|
|
223
|
+
else:
|
|
224
|
+
resolved = os.path.abspath(os.path.normpath(os.path.join(base_path, file_path)))
|
|
225
|
+
|
|
226
|
+
if not os.path.isfile(resolved):
|
|
227
|
+
raise exceptions.PlatformException(
|
|
228
|
+
error='400',
|
|
229
|
+
message='Path is not a file: {}'.format(file_path)
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
@staticmethod
|
|
233
|
+
def validate_directory_path(dir_path, base_path = None, must_exist: bool = True):
|
|
234
|
+
"""
|
|
235
|
+
Validate a directory path against path traversal attacks.
|
|
236
|
+
|
|
237
|
+
:param dir_path: Directory path to validate (str or Path object)
|
|
238
|
+
:param base_path: Optional base directory to restrict path to (str or Path). If None, uses current working directory
|
|
239
|
+
:param bool must_exist: If True, directory must exist (default: True)
|
|
240
|
+
:raises PlatformException: If path is invalid, contains traversal sequences, or is not a directory
|
|
241
|
+
"""
|
|
242
|
+
# Convert Path objects to strings
|
|
243
|
+
if isinstance(dir_path, Path):
|
|
244
|
+
dir_path = str(dir_path)
|
|
245
|
+
if isinstance(base_path, Path):
|
|
246
|
+
base_path = str(base_path)
|
|
247
|
+
|
|
248
|
+
PathUtils.validate_paths(dir_path, base_path=base_path, must_exist=must_exist)
|
|
249
|
+
|
|
250
|
+
if must_exist and isinstance(dir_path, str) and not dir_path.startswith(('http://', 'https://', 'external://')):
|
|
251
|
+
# Resolve path to check if it's a directory
|
|
252
|
+
if base_path is None:
|
|
253
|
+
base_path = os.getcwd()
|
|
254
|
+
if os.path.isabs(dir_path):
|
|
255
|
+
resolved = os.path.abspath(os.path.normpath(dir_path))
|
|
256
|
+
else:
|
|
257
|
+
resolved = os.path.abspath(os.path.normpath(os.path.join(base_path, dir_path)))
|
|
258
|
+
|
|
259
|
+
if not os.path.isdir(resolved):
|
|
260
|
+
raise exceptions.PlatformException(
|
|
261
|
+
error='400',
|
|
262
|
+
message='Path is not a directory: {}'.format(dir_path)
|
|
263
|
+
)
|
|
264
|
+
|
|
@@ -2,7 +2,6 @@ from copy import deepcopy
|
|
|
2
2
|
import traceback
|
|
3
3
|
import logging
|
|
4
4
|
import json
|
|
5
|
-
import jwt
|
|
6
5
|
import os
|
|
7
6
|
from PIL import Image
|
|
8
7
|
from io import BytesIO
|
|
@@ -336,9 +335,7 @@ class Annotations:
|
|
|
336
335
|
|
|
337
336
|
def _delete_single_annotation(self, w_annotation_id):
|
|
338
337
|
try:
|
|
339
|
-
|
|
340
|
-
verify=False, options={'verify_signature': False})['email']
|
|
341
|
-
payload = {'username': creator}
|
|
338
|
+
payload = {'username': self._client_api.info()['user_email']}
|
|
342
339
|
success, response = self._client_api.gen_request(req_type='delete',
|
|
343
340
|
path='/annotations/{}'.format(w_annotation_id),
|
|
344
341
|
json_req=payload)
|
dtlpy/repositories/packages.py
CHANGED
|
@@ -513,6 +513,13 @@ class Packages:
|
|
|
513
513
|
if codebase is None:
|
|
514
514
|
src_path = os.getcwd()
|
|
515
515
|
logger.warning('No src_path is given, getting package information from cwd: {}'.format(src_path))
|
|
516
|
+
else:
|
|
517
|
+
# Validate src_path to prevent path traversal
|
|
518
|
+
miscellaneous.PathUtils.validate_directory_path(
|
|
519
|
+
src_path,
|
|
520
|
+
base_path=os.getcwd(),
|
|
521
|
+
must_exist=True
|
|
522
|
+
)
|
|
516
523
|
|
|
517
524
|
# get package json
|
|
518
525
|
package_from_json = dict()
|
|
@@ -941,6 +948,8 @@ class Packages:
|
|
|
941
948
|
|
|
942
949
|
project.packages.deploy_from_file(project='project_entity', json_filepath='json_filepath')
|
|
943
950
|
"""
|
|
951
|
+
# Validate file path to prevent path traversal
|
|
952
|
+
miscellaneous.PathUtils.validate_file_path(json_filepath)
|
|
944
953
|
with open(json_filepath, 'r') as f:
|
|
945
954
|
data = json.load(f)
|
|
946
955
|
|
dtlpy/repositories/projects.py
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
from urllib.parse import quote
|
|
3
|
-
import jwt
|
|
4
3
|
|
|
5
4
|
from .. import entities, miscellaneous, exceptions, _api_reference
|
|
6
5
|
from ..services.api_client import ApiClient
|
|
@@ -159,8 +158,7 @@ class Projects:
|
|
|
159
158
|
assert isinstance(title, str)
|
|
160
159
|
assert isinstance(content, str)
|
|
161
160
|
if self._client_api.token is not None:
|
|
162
|
-
sender =
|
|
163
|
-
verify=False, options={'verify_signature': False})['email']
|
|
161
|
+
sender = self._client_api.info()['user_email']
|
|
164
162
|
else:
|
|
165
163
|
raise exceptions.PlatformException('600', 'Token expired please log in')
|
|
166
164
|
|
dtlpy/services/api_client.py
CHANGED
|
@@ -856,8 +856,16 @@ class ApiClient:
|
|
|
856
856
|
"""
|
|
857
857
|
user_email = 'null'
|
|
858
858
|
if self.token is not None:
|
|
859
|
-
|
|
860
|
-
|
|
859
|
+
# oxsec-disable jwt-signature-disabled - Client-side SDK: signature verification disabled intentionally to extract user email; server validates on API calls
|
|
860
|
+
payload = jwt.decode(
|
|
861
|
+
self.token,
|
|
862
|
+
options={
|
|
863
|
+
"verify_signature": False,
|
|
864
|
+
"verify_exp": False,
|
|
865
|
+
"verify_aud": False,
|
|
866
|
+
"verify_iss": False,
|
|
867
|
+
}
|
|
868
|
+
)
|
|
861
869
|
user_email = payload['email']
|
|
862
870
|
information = {'environment': self.environment,
|
|
863
871
|
'user_email': user_email}
|
|
@@ -1353,8 +1361,16 @@ class ApiClient:
|
|
|
1353
1361
|
if self.token is None or self.token == '':
|
|
1354
1362
|
expired = True
|
|
1355
1363
|
else:
|
|
1356
|
-
|
|
1357
|
-
|
|
1364
|
+
# oxsec-disable jwt-signature-disabled - Client-side SDK: signature verification disabled intentionally to check token expiration; server validates on API calls
|
|
1365
|
+
payload = jwt.decode(
|
|
1366
|
+
self.token,
|
|
1367
|
+
options={
|
|
1368
|
+
"verify_signature": False,
|
|
1369
|
+
"verify_exp": False,
|
|
1370
|
+
"verify_aud": False,
|
|
1371
|
+
"verify_iss": False,
|
|
1372
|
+
}
|
|
1373
|
+
)
|
|
1358
1374
|
d = datetime.datetime.now(datetime.timezone.utc)
|
|
1359
1375
|
epoch = datetime.datetime(1970, 1, 1, tzinfo=datetime.timezone.utc)
|
|
1360
1376
|
now = (d - epoch).total_seconds()
|
dtlpy/services/check_sdk.py
CHANGED
|
@@ -2,7 +2,6 @@ import time
|
|
|
2
2
|
import logging
|
|
3
3
|
import threading
|
|
4
4
|
import traceback
|
|
5
|
-
import jwt
|
|
6
5
|
|
|
7
6
|
logger = logging.getLogger(name='dtlpy')
|
|
8
7
|
|
|
@@ -21,9 +20,7 @@ def check_in_thread(version, client_api):
|
|
|
21
20
|
|
|
22
21
|
# try read token for email
|
|
23
22
|
try:
|
|
24
|
-
|
|
25
|
-
verify=False, options={'verify_signature': False})
|
|
26
|
-
user_email = payload['email']
|
|
23
|
+
user_email = client_api.info()['user_email']
|
|
27
24
|
except Exception:
|
|
28
25
|
user_email = 'na'
|
|
29
26
|
logger.debug('SDK info: user: {}, version: {}'.format(user_email, version))
|
dtlpy/services/logins.py
CHANGED
|
@@ -32,15 +32,12 @@ def login_secret(api_client, email, password, client_id, client_secret=None, for
|
|
|
32
32
|
# TODO add deprecation warning to client_id
|
|
33
33
|
# check if already logged in with SAME email
|
|
34
34
|
if api_client.token is not None or api_client.token == '':
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
not force:
|
|
42
|
-
return True
|
|
43
|
-
except jwt.exceptions.DecodeError:
|
|
35
|
+
logged_email = api_client.info()['user_email']
|
|
36
|
+
if logged_email == email and \
|
|
37
|
+
not api_client.token_expired() and \
|
|
38
|
+
not force:
|
|
39
|
+
return True
|
|
40
|
+
else:
|
|
44
41
|
logger.debug('{}'.format('Cant decode token. Force login is used'))
|
|
45
42
|
|
|
46
43
|
logger.info('[Start] Login Secret')
|
|
@@ -71,13 +68,12 @@ def login_secret(api_client, email, password, client_id, client_secret=None, for
|
|
|
71
68
|
api_client.refresh_token = response_dict['refresh_token']
|
|
72
69
|
|
|
73
70
|
# set new client id for refresh
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if '
|
|
77
|
-
logger.info('[Done] Login Secret. User: {}'
|
|
71
|
+
logged_email = api_client.info()['user_email']
|
|
72
|
+
|
|
73
|
+
if not logged_email == 'null':
|
|
74
|
+
logger.info(f'[Done] Login Secret. User: {logged_email}')
|
|
78
75
|
else:
|
|
79
|
-
logger.info('[Done] Login Secret. User: {}'
|
|
80
|
-
logger.info(payload)
|
|
76
|
+
logger.info(f'[Done] Login Secret. User: {email}')
|
|
81
77
|
return True
|
|
82
78
|
|
|
83
79
|
|
|
@@ -206,8 +202,16 @@ def login(api_client, auth0_url=None, audience=None, client_id=None, login_domai
|
|
|
206
202
|
success, tokens = server.process_request()
|
|
207
203
|
|
|
208
204
|
if success:
|
|
209
|
-
|
|
210
|
-
|
|
205
|
+
# oxsec-disable jwt-signature-disabled - Client-side SDK: signature verification disabled intentionally to extract claims for display; server validates on API calls
|
|
206
|
+
decoded_jwt = jwt.decode(
|
|
207
|
+
tokens['id'],
|
|
208
|
+
options={
|
|
209
|
+
"verify_signature": False,
|
|
210
|
+
"verify_exp": False,
|
|
211
|
+
"verify_aud": False,
|
|
212
|
+
"verify_iss": False,
|
|
213
|
+
}
|
|
214
|
+
)
|
|
211
215
|
|
|
212
216
|
if 'email' in decoded_jwt:
|
|
213
217
|
logger.info('Logged in: {}'.format(decoded_jwt['email']))
|
dtlpy/utilities/videos/videos.py
CHANGED
|
@@ -7,6 +7,7 @@ import os
|
|
|
7
7
|
import logging
|
|
8
8
|
import dtlpy as dl
|
|
9
9
|
import shutil
|
|
10
|
+
from dtlpy import miscellaneous
|
|
10
11
|
|
|
11
12
|
logger = logging.getLogger(name='dtlpy')
|
|
12
13
|
|
|
@@ -344,6 +345,9 @@ class Videos:
|
|
|
344
345
|
raise
|
|
345
346
|
# https://www.ffmpeg.org/ffmpeg-formats.html#Examples-9
|
|
346
347
|
|
|
348
|
+
# Validate filepath to prevent path traversal
|
|
349
|
+
miscellaneous.PathUtils.validate_file_path(filepath)
|
|
350
|
+
|
|
347
351
|
if not os.path.isfile(filepath):
|
|
348
352
|
raise IOError('File doesnt exists: {}'.format(filepath))
|
|
349
353
|
logger.info('Extracting video information...')
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
dtlpy/__init__.py,sha256=p9qDLEQ01cTj0Xx8FkxJPHSmP7LMHMv8b5-ZWW1FPrU,20625
|
|
2
|
-
dtlpy/__version__.py,sha256=
|
|
2
|
+
dtlpy/__version__.py,sha256=rKb0jeJZoFfB0Nvl7WNtP0jvSvA4lP7Psb-TendlWlQ,21
|
|
3
3
|
dtlpy/exceptions.py,sha256=EQCKs3pwhwZhgMByQN3D3LpWpdxwcKPEEt-bIaDwURM,2871
|
|
4
4
|
dtlpy/new_instance.py,sha256=XegQav2hzPrPAUzuRFvUIGSPidoK-rbb02Q43NPsIpo,9982
|
|
5
5
|
dtlpy/assets/__init__.py,sha256=D_hAa6NM8Zoy32sF_9b7m0b7I-BQEyBFg8-9Tg2WOeo,976
|
|
@@ -38,7 +38,7 @@ dtlpy/caches/filesystem_cache.py,sha256=OrBqyEucSVp7g33c6R1BR3ICbkgQnwYWEDhQ7OxH
|
|
|
38
38
|
dtlpy/caches/redis_cache.py,sha256=bgJgxgAXFR_TxPDvlLS4TKumFds-ihNf668JbPYUfpc,2331
|
|
39
39
|
dtlpy/dlp/__init__.py,sha256=QG_BxSqeic0foFBmzIkpZEF4EvxOZamknj2f5Cb6T6Q,868
|
|
40
40
|
dtlpy/dlp/cli_utilities.py,sha256=gZA9XIN5GG-xWJ6S1i6T17CDDCypDoev6CY-WHlogYg,16055
|
|
41
|
-
dtlpy/dlp/command_executor.py,sha256=
|
|
41
|
+
dtlpy/dlp/command_executor.py,sha256=oQPOzE6eVUvm54IsdVYcWgVOByWjOqa2nQQY8EAd8Vc,34320
|
|
42
42
|
dtlpy/dlp/dlp,sha256=-F0vSCWuSOOtgERAtsPMPyMmzitjhB7Yeftg_PDlDjw,10
|
|
43
43
|
dtlpy/dlp/dlp.bat,sha256=QOvx8Dlx5dUbCTMpwbhOcAIXL1IWmgVRSboQqDhIn3A,37
|
|
44
44
|
dtlpy/dlp/dlp.py,sha256=Zv9yoXwNAx4gkED-JiayN-ZkX2dPn4FB0SDx9qc7muo,4404
|
|
@@ -141,11 +141,12 @@ dtlpy/examples/upload_items_and_custom_format_annotations.py,sha256=KgGeIs2Q6MoT
|
|
|
141
141
|
dtlpy/examples/upload_items_with_modalities.py,sha256=PJyzPIvRSWi_nh7JlOR9YZKFrMuvYffDx5mz9TiVIvU,1848
|
|
142
142
|
dtlpy/examples/upload_segmentation_annotations_from_mask_image.py,sha256=JQGc8wQ3zTRRlVcRLs223UwCYCAfChKlvU0QOPEqezI,1388
|
|
143
143
|
dtlpy/examples/upload_yolo_format_annotations.py,sha256=PDLhC5pBGrC68Pix-7I7SgdaCweYNZPgJxg0h4ssWyc,2610
|
|
144
|
-
dtlpy/miscellaneous/__init__.py,sha256=
|
|
144
|
+
dtlpy/miscellaneous/__init__.py,sha256=_-NhTwcGH2FxFsWMf-vfmYYT6Ouuw08rN1fALJg2Zvw,863
|
|
145
145
|
dtlpy/miscellaneous/dict_differ.py,sha256=POJbKR0YyWPf5gFADFpIaNFj9gt2aVBTNof7GJNxTCw,3489
|
|
146
146
|
dtlpy/miscellaneous/git_utils.py,sha256=CT_CCDsqDqu_bY3cLcOSU6k3Zr6w40t8GJULLUtAJ_U,7971
|
|
147
147
|
dtlpy/miscellaneous/json_utils.py,sha256=0P4YTlL6o_L7AUrvAeqkqA46MZZK_hDdTrdnmI59y6g,428
|
|
148
148
|
dtlpy/miscellaneous/list_print.py,sha256=fBGTMXFUwDG8DD4W6HyR8BTGtbTckLf4W09quNRJm5M,4828
|
|
149
|
+
dtlpy/miscellaneous/path_utils.py,sha256=TiYHuwfWUBbz6E99QP-MQivkBC70aMBdYRd3RTJ0QgQ,10426
|
|
149
150
|
dtlpy/miscellaneous/zipping.py,sha256=JplTc8UDFvO8WaD5vKuumVLN0lU_-GtHoE0doWKtmKg,5383
|
|
150
151
|
dtlpy/ml/__init__.py,sha256=vPkyXpc9kcWWZ_PxyPEOsjKBJdEbowLkZr8FZIb_OBM,799
|
|
151
152
|
dtlpy/ml/base_feature_extractor_adapter.py,sha256=iiEGYAx0Rdn4K46H_FlKrAv3ebTXHSxNVAmio0BxhaI,1178
|
|
@@ -156,7 +157,7 @@ dtlpy/ml/summary_writer.py,sha256=dehDi8zmGC1sAGyy_3cpSWGXoGQSiQd7bL_Thoo8yIs,27
|
|
|
156
157
|
dtlpy/ml/train_utils.py,sha256=t607DfyGBRrUQZ9jPmPe4V9Udzfk0hPWuw4OvKZKAeo,2440
|
|
157
158
|
dtlpy/repositories/__init__.py,sha256=D2YI3ZLlSx0OlgVr8y_E9rsj-IxCDOj0MB6QTlv2NSA,2061
|
|
158
159
|
dtlpy/repositories/analytics.py,sha256=dQPCYTPAIuyfVI_ppR49W7_GBj0033feIm9Gd7LW1V0,2966
|
|
159
|
-
dtlpy/repositories/annotations.py,sha256=
|
|
160
|
+
dtlpy/repositories/annotations.py,sha256=ygN6JRa9jAod9kXDwksaScsTqzWCzegYsmF_i2be5xA,43442
|
|
160
161
|
dtlpy/repositories/apps.py,sha256=miCYJNqte8TVFkBezE8yzueMsz593jNO9sSUfZRVV7M,15969
|
|
161
162
|
dtlpy/repositories/artifacts.py,sha256=Ke2ustTNw-1eQ0onLsWY7gL2aChjXPAX5p1uQ_EzMbo,19081
|
|
162
163
|
dtlpy/repositories/assignments.py,sha256=1VwJZ7ctQe1iaDDDpeYDgoj2G-TCgzolVLUEqUocd2w,25506
|
|
@@ -180,10 +181,10 @@ dtlpy/repositories/models.py,sha256=K8mqmYM_kj3z727VDcdcnQC_XDw18bf48ujEV-deStU,
|
|
|
180
181
|
dtlpy/repositories/nodes.py,sha256=xXJm_YA0vDUn0dVvaGeq6ORM0vI3YXvfjuylvGRtkxo,3061
|
|
181
182
|
dtlpy/repositories/ontologies.py,sha256=unnMhD2isR9DVE5S8Fg6fSDf1ZZ5Xemxxufx4LEUT3w,19577
|
|
182
183
|
dtlpy/repositories/organizations.py,sha256=6ijUDFbsogfRul1g_vUB5AZOb41MRmV5NhNU7WLHt3A,22825
|
|
183
|
-
dtlpy/repositories/packages.py,sha256=
|
|
184
|
+
dtlpy/repositories/packages.py,sha256=XA1cu7zW9sHfMlayjUD8OeAknsEpHzEcN-uebOIFmxc,87172
|
|
184
185
|
dtlpy/repositories/pipeline_executions.py,sha256=sEC03bu5DsHc554z3xDbMCP529rhfADcktXgWkswEwI,17281
|
|
185
186
|
dtlpy/repositories/pipelines.py,sha256=5qosyxLFgNbcmL7uoTr9klAj1VM-7mWvsOvngbUU1Qk,24320
|
|
186
|
-
dtlpy/repositories/projects.py,sha256=
|
|
187
|
+
dtlpy/repositories/projects.py,sha256=_e5z4byytBu-nEYqsRO0HF0IbLgyyUaLLkx0EBND6Ls,21501
|
|
187
188
|
dtlpy/repositories/recipes.py,sha256=q1FMk4kBPzBS-QIbkxeSsMcAJmYuS7gpYL8t3XIBWII,17117
|
|
188
189
|
dtlpy/repositories/resource_executions.py,sha256=PyzsbdJxz6jf17Gx13GZmqdu6tZo3TTVv-DypnJ_sY0,5374
|
|
189
190
|
dtlpy/repositories/schema.py,sha256=kTKDrbwm7BfQnBAK81LpAl9ChNFdyUweSLNazlJJhjk,3953
|
|
@@ -198,15 +199,15 @@ dtlpy/repositories/uploader.py,sha256=I9mP-Ikj0zUOdMTf-7FN_huHWXYeWc8gzVRpfUPAXj
|
|
|
198
199
|
dtlpy/repositories/webhooks.py,sha256=IIpxOJ-7KeQp1TY9aJZz-FuycSjAoYx0TDk8z86KAK8,9033
|
|
199
200
|
dtlpy/services/__init__.py,sha256=VfVJy2otIrDra6i7Sepjyez2ujiE6171ChQZp-YgxsM,904
|
|
200
201
|
dtlpy/services/aihttp_retry.py,sha256=tgntZsAY0dW9v08rkjX1T5BLNDdDd8svtgn7nH8DSGU,5022
|
|
201
|
-
dtlpy/services/api_client.py,sha256=
|
|
202
|
+
dtlpy/services/api_client.py,sha256=XS5tYyKvwW5RYlR1RD06zGk9-_MPf2h7yOoog2EDhxQ,72559
|
|
202
203
|
dtlpy/services/api_reference.py,sha256=cW-B3eoi9Xs3AwI87_Kr6GV_E6HPoC73aETFaGz3A-0,1515
|
|
203
204
|
dtlpy/services/async_utils.py,sha256=kaYHTPw0Lg8PeJJq8whPyzrBYkzD7offs5hsKRZXJm8,3960
|
|
204
205
|
dtlpy/services/calls_counter.py,sha256=gr0io5rIsO5-7Cgc8neA1vK8kUtYhgFPmDQ2jXtiZZs,1036
|
|
205
|
-
dtlpy/services/check_sdk.py,sha256=
|
|
206
|
+
dtlpy/services/check_sdk.py,sha256=CaOzAtI7ETtgX3x6hS5NWx77liqqRwIKLJ7B42EXIt4,2464
|
|
206
207
|
dtlpy/services/cookie.py,sha256=sSZR1QV4ienCcZ8lEK_Y4nZYBgAxO3kHrcBXFKGcmwQ,3694
|
|
207
208
|
dtlpy/services/create_logger.py,sha256=2dC39CCmc17H4LYRpY0bRIT4S50UTGNOBPYIqJnrsIU,6350
|
|
208
209
|
dtlpy/services/events.py,sha256=mpcu8RusLPrBcJEbWR61uFb4FiU_dQv3xoa7uM-rTcY,3686
|
|
209
|
-
dtlpy/services/logins.py,sha256=
|
|
210
|
+
dtlpy/services/logins.py,sha256=_fUJ5UlEDWLX09TtcsYy9G1Qca5p6X24xu4XvE-N9Sk,8745
|
|
210
211
|
dtlpy/services/reporter.py,sha256=qQ9Tgws-8cSpvKbLhJ8j8vgVSgxju2qAgnDL5sCk0Dg,9191
|
|
211
212
|
dtlpy/services/service_defaults.py,sha256=a7KoqkVmn2TXmM9gN9JRaVVtcG2b8JGIieVnaZeEaao,3860
|
|
212
213
|
dtlpy/utilities/__init__.py,sha256=ncQD1O5lZ7L9n9rNRBivyqNVFDZyQcmqn-X-wyQhhIs,898
|
|
@@ -225,13 +226,13 @@ dtlpy/utilities/reports/figures.py,sha256=o3TOrwo1ytY14H-NV-TyEgVYCFDSm6HYAXVXRd
|
|
|
225
226
|
dtlpy/utilities/reports/report.py,sha256=3nEsNnIWmdPEsd21nN8vMMgaZVcPKn9iawKTTeOQg2A,2639
|
|
226
227
|
dtlpy/utilities/videos/__init__.py,sha256=SV3w51vfPuGBxaMeNemx6qEMHw_C4lLpWNGXMvdsKSY,734
|
|
227
228
|
dtlpy/utilities/videos/video_player.py,sha256=LCxg0EZ_DeuwcT7U_r7MRC6Q19s0xdFb7x5Gk39PRms,24072
|
|
228
|
-
dtlpy/utilities/videos/videos.py,sha256=
|
|
229
|
-
dtlpy-1.118.
|
|
230
|
-
dtlpy-1.118.
|
|
231
|
-
dtlpy-1.118.
|
|
232
|
-
dtlpy-1.118.
|
|
233
|
-
dtlpy-1.118.
|
|
234
|
-
dtlpy-1.118.
|
|
235
|
-
dtlpy-1.118.
|
|
236
|
-
dtlpy-1.118.
|
|
237
|
-
dtlpy-1.118.
|
|
229
|
+
dtlpy/utilities/videos/videos.py,sha256=4xbbrHDeJhyR8VXW5ojLEp1GGGwgMoRIRyGrXEX5wiA,22031
|
|
230
|
+
dtlpy-1.118.13.data/scripts/dlp,sha256=-F0vSCWuSOOtgERAtsPMPyMmzitjhB7Yeftg_PDlDjw,10
|
|
231
|
+
dtlpy-1.118.13.data/scripts/dlp.bat,sha256=QOvx8Dlx5dUbCTMpwbhOcAIXL1IWmgVRSboQqDhIn3A,37
|
|
232
|
+
dtlpy-1.118.13.data/scripts/dlp.py,sha256=ZpfJvYE1_OTSorEYBphqTOutnHSb5TqOXh0y_mUCTJs,4393
|
|
233
|
+
dtlpy-1.118.13.dist-info/licenses/LICENSE,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
|
|
234
|
+
dtlpy-1.118.13.dist-info/METADATA,sha256=ozuEK7ANlXc6pEc4wDX84knLXQO7rv1BJ-AAhC_bdgw,5908
|
|
235
|
+
dtlpy-1.118.13.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
236
|
+
dtlpy-1.118.13.dist-info/entry_points.txt,sha256=C4PyKthCs_no88HU39eioO68oei64STYXC2ooGZTc4Y,43
|
|
237
|
+
dtlpy-1.118.13.dist-info/top_level.txt,sha256=MSr60TGZYlwXCKxlLoZCfILRZ6pU_3L-20d2SZvygyA,6
|
|
238
|
+
dtlpy-1.118.13.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|