dtlpy 1.118.12__tar.gz → 1.118.14__tar.gz
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-1.118.12 → dtlpy-1.118.14}/PKG-INFO +1 -1
- dtlpy-1.118.14/dtlpy/__version__.py +1 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/dlp/command_executor.py +55 -5
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/entities/annotation.py +8 -7
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/entities/dataset.py +8 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/miscellaneous/__init__.py +1 -0
- dtlpy-1.118.14/dtlpy/miscellaneous/path_utils.py +264 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/repositories/annotations.py +1 -4
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/repositories/packages.py +9 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/repositories/projects.py +1 -3
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/repositories/tasks.py +4 -4
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/services/api_client.py +20 -4
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/services/check_sdk.py +1 -4
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/services/logins.py +21 -17
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/utilities/videos/videos.py +4 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy.egg-info/SOURCES.txt +1 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/setup.py +1 -1
- dtlpy-1.118.12/dtlpy/__version__.py +0 -1
- {dtlpy-1.118.12 → dtlpy-1.118.14}/LICENSE +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/MANIFEST.in +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/README.md +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/docs/requirements.txt +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/__init__.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/assets/__init__.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/assets/code_server/config.yaml +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/assets/code_server/installation.sh +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/assets/code_server/launch.json +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/assets/code_server/settings.json +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/assets/lock_open.png +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/assets/main.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/assets/main_partial.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/assets/mock.json +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/assets/model_adapter.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/assets/package.json +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/assets/package_catalog.json +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/assets/package_gitignore +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/assets/project_dataset_recipe_ontology.png +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/assets/service_runners/__init__.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/assets/service_runners/converter.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/assets/service_runners/multi_method.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/assets/service_runners/multi_method_annotation.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/assets/service_runners/multi_method_dataset.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/assets/service_runners/multi_method_item.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/assets/service_runners/multi_method_json.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/assets/service_runners/single_method.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/assets/service_runners/single_method_annotation.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/assets/service_runners/single_method_dataset.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/assets/service_runners/single_method_item.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/assets/service_runners/single_method_json.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/assets/service_runners/single_method_multi_input.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/assets/voc_annotation_template.xml +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/caches/__init__.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/caches/base_cache.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/caches/cache.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/caches/dl_cache.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/caches/filesystem_cache.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/caches/redis_cache.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/dlp/__init__.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/dlp/cli_utilities.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/dlp/dlp +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/dlp/dlp.bat +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/dlp/dlp.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/dlp/parser.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/entities/__init__.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/entities/analytic.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/entities/annotation_collection.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/entities/annotation_definitions/__init__.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/entities/annotation_definitions/base_annotation_definition.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/entities/annotation_definitions/box.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/entities/annotation_definitions/classification.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/entities/annotation_definitions/comparison.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/entities/annotation_definitions/cube.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/entities/annotation_definitions/cube_3d.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/entities/annotation_definitions/description.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/entities/annotation_definitions/ellipse.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/entities/annotation_definitions/free_text.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/entities/annotation_definitions/gis.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/entities/annotation_definitions/note.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/entities/annotation_definitions/point.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/entities/annotation_definitions/polygon.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/entities/annotation_definitions/polyline.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/entities/annotation_definitions/pose.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/entities/annotation_definitions/ref_image.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/entities/annotation_definitions/segmentation.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/entities/annotation_definitions/subtitle.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/entities/annotation_definitions/text.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/entities/annotation_definitions/undefined_annotation.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/entities/app.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/entities/app_module.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/entities/artifact.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/entities/assignment.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/entities/base_entity.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/entities/bot.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/entities/codebase.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/entities/collection.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/entities/command.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/entities/compute.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/entities/directory_tree.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/entities/dpk.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/entities/driver.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/entities/execution.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/entities/feature.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/entities/feature_set.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/entities/filters.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/entities/gis_item.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/entities/integration.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/entities/item.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/entities/label.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/entities/links.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/entities/message.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/entities/model.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/entities/node.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/entities/ontology.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/entities/organization.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/entities/package.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/entities/package_defaults.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/entities/package_function.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/entities/package_module.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/entities/package_slot.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/entities/paged_entities.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/entities/pipeline.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/entities/pipeline_execution.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/entities/project.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/entities/prompt_item.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/entities/recipe.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/entities/reflect_dict.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/entities/resource_execution.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/entities/service.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/entities/service_driver.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/entities/setting.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/entities/task.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/entities/time_series.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/entities/trigger.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/entities/user.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/entities/webhook.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/examples/__init__.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/examples/add_labels.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/examples/add_metadata_to_item.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/examples/annotate_items_using_model.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/examples/annotate_video_using_model_and_tracker.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/examples/annotations_convert_to_voc.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/examples/annotations_convert_to_yolo.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/examples/convert_annotation_types.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/examples/converter.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/examples/copy_annotations.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/examples/copy_folder.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/examples/create_annotations.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/examples/create_video_annotations.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/examples/delete_annotations.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/examples/filters.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/examples/move_item.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/examples/play_video_annotation.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/examples/show_item_and_mask.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/examples/triggers.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/examples/upload_batch_of_items.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/examples/upload_items_and_custom_format_annotations.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/examples/upload_items_with_modalities.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/examples/upload_segmentation_annotations_from_mask_image.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/examples/upload_yolo_format_annotations.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/exceptions.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/miscellaneous/dict_differ.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/miscellaneous/git_utils.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/miscellaneous/json_utils.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/miscellaneous/list_print.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/miscellaneous/zipping.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/ml/__init__.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/ml/base_feature_extractor_adapter.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/ml/base_model_adapter.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/ml/metrics.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/ml/predictions_utils.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/ml/summary_writer.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/ml/train_utils.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/new_instance.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/repositories/__init__.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/repositories/analytics.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/repositories/apps.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/repositories/artifacts.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/repositories/assignments.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/repositories/bots.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/repositories/codebases.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/repositories/collections.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/repositories/commands.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/repositories/compositions.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/repositories/computes.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/repositories/datasets.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/repositories/downloader.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/repositories/dpks.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/repositories/drivers.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/repositories/executions.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/repositories/feature_sets.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/repositories/features.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/repositories/integrations.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/repositories/items.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/repositories/messages.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/repositories/models.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/repositories/nodes.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/repositories/ontologies.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/repositories/organizations.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/repositories/pipeline_executions.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/repositories/pipelines.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/repositories/recipes.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/repositories/resource_executions.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/repositories/schema.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/repositories/service_drivers.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/repositories/services.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/repositories/settings.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/repositories/times_series.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/repositories/triggers.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/repositories/upload_element.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/repositories/uploader.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/repositories/webhooks.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/services/__init__.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/services/aihttp_retry.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/services/api_reference.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/services/async_utils.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/services/calls_counter.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/services/cookie.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/services/create_logger.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/services/events.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/services/reporter.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/services/service_defaults.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/utilities/__init__.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/utilities/annotations/__init__.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/utilities/annotations/annotation_converters.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/utilities/base_package_runner.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/utilities/converter.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/utilities/dataset_generators/__init__.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/utilities/dataset_generators/dataset_generator.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/utilities/dataset_generators/dataset_generator_tensorflow.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/utilities/dataset_generators/dataset_generator_torch.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/utilities/local_development/__init__.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/utilities/local_development/local_session.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/utilities/reports/__init__.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/utilities/reports/figures.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/utilities/reports/report.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/utilities/videos/__init__.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/dtlpy/utilities/videos/video_player.py +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/requirements.txt +0 -0
- {dtlpy-1.118.12 → dtlpy-1.118.14}/setup.cfg +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
version = '1.118.14'
|
|
@@ -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
|
|
@@ -1121,13 +1121,14 @@ class Annotation(entities.BaseEntity):
|
|
|
1121
1121
|
annotation_definition=dl.Box(top=10,left=10,bottom=100, right=100,label='labelName'))
|
|
1122
1122
|
)
|
|
1123
1123
|
"""
|
|
1124
|
-
# handle fps
|
|
1125
|
-
if
|
|
1126
|
-
if self.
|
|
1127
|
-
if self._item
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1124
|
+
# handle fps - only needed if converting from time to frames
|
|
1125
|
+
if frame_num is None or (end_frame_num is None and end_time is not None):
|
|
1126
|
+
if self.fps is None:
|
|
1127
|
+
if self._item is not None:
|
|
1128
|
+
if self._item.fps is not None:
|
|
1129
|
+
self.fps = self._item.fps
|
|
1130
|
+
if self.fps is None:
|
|
1131
|
+
raise PlatformException('400', 'Annotation must have fps in order to perform this action')
|
|
1131
1132
|
|
|
1132
1133
|
if frame_num is None:
|
|
1133
1134
|
frame_num = int(np.round(start_time * self.fps))
|
|
@@ -290,6 +290,14 @@ class Dataset(entities.BaseEntity):
|
|
|
290
290
|
self._ontology_ids += recipe.ontology_ids
|
|
291
291
|
return self._ontology_ids
|
|
292
292
|
|
|
293
|
+
|
|
294
|
+
@property
|
|
295
|
+
def project_id(self):
|
|
296
|
+
_project_id = None
|
|
297
|
+
if self.projects is not None and len(self.projects) > 0:
|
|
298
|
+
_project_id = self.projects[0]
|
|
299
|
+
return _project_id
|
|
300
|
+
|
|
293
301
|
@_repositories.default
|
|
294
302
|
def set_repositories(self):
|
|
295
303
|
reps = namedtuple('repositories',
|
|
@@ -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)
|
|
@@ -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
|
|
|
@@ -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
|
|
|
@@ -549,7 +549,7 @@ class Tasks:
|
|
|
549
549
|
priority=entities.TaskPriority.MEDIUM,
|
|
550
550
|
consensus_percentage=None,
|
|
551
551
|
consensus_assignees=None,
|
|
552
|
-
scoring=
|
|
552
|
+
scoring=False,
|
|
553
553
|
limit=None,
|
|
554
554
|
wait=True,
|
|
555
555
|
enforce_video_conversion=True,
|
|
@@ -644,7 +644,7 @@ class Tasks:
|
|
|
644
644
|
consensus_task_type=entities.ConsensusTaskType.QUALIFICATION,
|
|
645
645
|
consensus_percentage=consensus_percentage,
|
|
646
646
|
consensus_assignees=consensus_assignees,
|
|
647
|
-
scoring=
|
|
647
|
+
scoring=False,
|
|
648
648
|
limit=limit,
|
|
649
649
|
wait=wait,
|
|
650
650
|
enforce_video_conversion=enforce_video_conversion,
|
|
@@ -666,7 +666,7 @@ class Tasks:
|
|
|
666
666
|
consensus_task_type: entities.ConsensusTaskType = entities.ConsensusTaskType.CONSENSUS,
|
|
667
667
|
consensus_percentage=None,
|
|
668
668
|
consensus_assignees=None,
|
|
669
|
-
scoring=
|
|
669
|
+
scoring=False,
|
|
670
670
|
limit=None,
|
|
671
671
|
wait=True,
|
|
672
672
|
enforce_video_conversion=True,
|
|
@@ -976,7 +976,7 @@ class Tasks:
|
|
|
976
976
|
consensus_task_type=None,
|
|
977
977
|
consensus_percentage=None,
|
|
978
978
|
consensus_assignees=None,
|
|
979
|
-
scoring=
|
|
979
|
+
scoring=False,
|
|
980
980
|
enforce_video_conversion=True,
|
|
981
981
|
) -> entities.Task:
|
|
982
982
|
"""
|
|
@@ -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()
|
|
@@ -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))
|
|
@@ -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']))
|