howler-api 4.1.0.dev1635__tar.gz → 4.1.0.dev1643__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.
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/PKG-INFO +1 -1
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/api/v1/utils/permission_helper.py +16 -6
- howler_api-4.1.0.dev1643/howler/odm/models/permission_request.py +12 -0
- howler_api-4.1.0.dev1643/howler/services/permission_service.py +152 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/pyproject.toml +1 -1
- howler_api-4.1.0.dev1635/howler/odm/models/permission_request.py +0 -17
- howler_api-4.1.0.dev1635/howler/services/permission_service.py +0 -150
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/README.md +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/__init__.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/actions/__init__.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/actions/add_label.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/actions/add_to_bundle.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/actions/add_to_case.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/actions/change_field.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/actions/demote.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/actions/example_plugin.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/actions/prioritization.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/actions/promote.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/actions/remove_from_bundle.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/actions/remove_label.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/actions/transition.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/api/__init__.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/api/base.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/api/socket.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/api/v1/__init__.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/api/v1/action.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/api/v1/analytic.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/api/v1/auth.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/api/v1/clue.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/api/v1/configs.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/api/v1/dossier.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/api/v1/help.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/api/v1/hit.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/api/v1/notebook.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/api/v1/overview.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/api/v1/search.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/api/v1/template.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/api/v1/tool.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/api/v1/user.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/api/v1/utils/__init__.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/api/v1/utils/etag.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/api/v1/utils/params.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/api/v1/view.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/api/v2/__init__.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/api/v2/case.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/api/v2/fuzzy.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/api/v2/ingest.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/api/v2/search.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/app.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/common/README.md +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/common/__init__.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/common/classification.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/common/classification.yml +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/common/exceptions.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/common/loader.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/common/logging/__init__.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/common/logging/audit.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/common/logging/format.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/common/net.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/common/net_static.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/common/random_user.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/common/swagger.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/config.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/cronjobs/__init__.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/cronjobs/action_queue_worker.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/cronjobs/correlation.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/cronjobs/retention.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/cronjobs/view_cleanup.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/datastore/README.md +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/datastore/__init__.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/datastore/bulk.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/datastore/collection.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/datastore/constants.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/datastore/exceptions.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/datastore/howler_store.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/datastore/migrations/__init__.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/datastore/migrations/action_owner.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/datastore/migrations/base.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/datastore/migrations/fix_process.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/datastore/migrations/runner.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/datastore/operations.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/datastore/schemas.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/datastore/store.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/datastore/support/__init__.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/datastore/support/build.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/datastore/support/schemas.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/datastore/types.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/datastore/utils.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/error.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/external/README.md +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/external/__init__.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/external/check_duplicates.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/external/generate_mitre.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/external/generate_sigma_rules.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/external/generate_tlds.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/external/reindex_data.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/external/run_migrations.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/external/wipe_databases.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/gunicorn_config.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/healthz.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/helper/__init__.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/helper/azure.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/helper/discover.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/helper/hit.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/helper/oauth.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/helper/search.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/helper/workflow.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/helper/ws.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/odm/README.md +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/odm/__init__.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/odm/base.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/odm/charter.txt +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/odm/constants.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/odm/helper.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/odm/howler_enum.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/odm/mixins.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/odm/models/__init__.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/odm/models/action.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/odm/models/analytic.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/odm/models/assemblyline.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/odm/models/aws.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/odm/models/azure.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/odm/models/case.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/odm/models/cbs.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/odm/models/clue.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/odm/models/config.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/odm/models/dossier.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/odm/models/ecs/__init__.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/odm/models/ecs/agent.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/odm/models/ecs/autonomous_system.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/odm/models/ecs/client.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/odm/models/ecs/cloud.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/odm/models/ecs/code_signature.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/odm/models/ecs/container.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/odm/models/ecs/dns.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/odm/models/ecs/egress.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/odm/models/ecs/elf.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/odm/models/ecs/email.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/odm/models/ecs/error.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/odm/models/ecs/event.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/odm/models/ecs/faas.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/odm/models/ecs/file.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/odm/models/ecs/geo.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/odm/models/ecs/group.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/odm/models/ecs/hash.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/odm/models/ecs/host.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/odm/models/ecs/http.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/odm/models/ecs/ingress.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/odm/models/ecs/interface.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/odm/models/ecs/network.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/odm/models/ecs/observer.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/odm/models/ecs/organization.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/odm/models/ecs/os.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/odm/models/ecs/pe.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/odm/models/ecs/process.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/odm/models/ecs/registry.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/odm/models/ecs/related.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/odm/models/ecs/rule.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/odm/models/ecs/server.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/odm/models/ecs/threat.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/odm/models/ecs/tls.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/odm/models/ecs/url.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/odm/models/ecs/user.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/odm/models/ecs/user_agent.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/odm/models/ecs/vulnerability.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/odm/models/event.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/odm/models/gcp.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/odm/models/hit.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/odm/models/howler_data.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/odm/models/lead.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/odm/models/localized_label.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/odm/models/log.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/odm/models/migration.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/odm/models/overview.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/odm/models/ownership.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/odm/models/pivot.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/odm/models/record.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/odm/models/template.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/odm/models/user.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/odm/models/view.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/odm/random_data.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/odm/randomizer.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/patched.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/plugins/__init__.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/plugins/config.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/remote/__init__.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/remote/datatypes/README.md +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/remote/datatypes/__init__.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/remote/datatypes/counters.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/remote/datatypes/events.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/remote/datatypes/hash.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/remote/datatypes/lock.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/remote/datatypes/queues/__init__.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/remote/datatypes/queues/comms.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/remote/datatypes/queues/multi.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/remote/datatypes/queues/named.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/remote/datatypes/queues/priority.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/remote/datatypes/set.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/remote/datatypes/user_quota_tracker.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/security/__init__.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/security/login.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/security/socket.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/security/utils.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/services/__init__.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/services/action_service.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/services/analytic_service.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/services/auth_service.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/services/bundle_compat_service.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/services/case_service.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/services/comms_service.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/services/config_service.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/services/correlation_service.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/services/docs_service.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/services/dossier_service.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/services/event_service.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/services/fuzzy_service.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/services/hit_service.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/services/jwt_service.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/services/lucene_service.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/services/notebook_service.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/services/overview_service.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/services/search_service.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/services/template_service.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/services/user_service.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/services/viewer_service.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/telemetry.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/utils/__init__.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/utils/annotations.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/utils/chunk.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/utils/compat.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/utils/constants.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/utils/dict_utils.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/utils/indexes.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/utils/isotime.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/utils/list_utils.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/utils/lucene.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/utils/net_utils.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/utils/path.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/utils/socket_utils.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/utils/str_utils.py +0 -0
- {howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/utils/uid.py +0 -0
{howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/api/v1/utils/permission_helper.py
RENAMED
|
@@ -31,10 +31,12 @@ def add_access_control_endpoints(api: Blueprint, odm: type[Ownership]):
|
|
|
31
31
|
refresh => ('true' | 'false' | 'wait_for') Whether to refresh the datastore before returning.
|
|
32
32
|
|
|
33
33
|
Data Block:
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
[
|
|
35
|
+
{
|
|
36
|
+
"privilege": "members", # The permission level: 'owner', 'admins', or 'members'
|
|
37
|
+
"user_id": "username" # The user receiving the permission
|
|
38
|
+
}
|
|
39
|
+
]
|
|
38
40
|
|
|
39
41
|
Result Example:
|
|
40
42
|
{
|
|
@@ -42,7 +44,7 @@ def add_access_control_endpoints(api: Blueprint, odm: type[Ownership]):
|
|
|
42
44
|
}
|
|
43
45
|
"""
|
|
44
46
|
try:
|
|
45
|
-
result = permission_service.
|
|
47
|
+
result = permission_service.give_privileges(id, user, odm, refresh=kwargs.get("refresh"))
|
|
46
48
|
except VersionConflictException:
|
|
47
49
|
return conflict(err="Someone has updated this item. Please try again")
|
|
48
50
|
except ForbiddenException as e:
|
|
@@ -67,13 +69,21 @@ def add_access_control_endpoints(api: Blueprint, odm: type[Ownership]):
|
|
|
67
69
|
Optional Arguments:
|
|
68
70
|
refresh => ('true' | 'false' | 'wait_for') Whether to refresh the datastore before returning.
|
|
69
71
|
|
|
72
|
+
Data Block:
|
|
73
|
+
[
|
|
74
|
+
{
|
|
75
|
+
"privilege": "members", # The permission level: 'admins' or 'members'
|
|
76
|
+
"user_id": "username" # The user losing the permission
|
|
77
|
+
}
|
|
78
|
+
]
|
|
79
|
+
|
|
70
80
|
Result Example:
|
|
71
81
|
{
|
|
72
82
|
...object # The updated action, dossier, or view
|
|
73
83
|
}
|
|
74
84
|
"""
|
|
75
85
|
try:
|
|
76
|
-
result = permission_service.
|
|
86
|
+
result = permission_service.remove_privileges(id, user, odm, refresh=kwargs.get("refresh"))
|
|
77
87
|
except VersionConflictException:
|
|
78
88
|
return conflict(err="Someone has updated this item. Please try again")
|
|
79
89
|
except ForbiddenException as e:
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from typing import Literal
|
|
2
|
+
|
|
3
|
+
from howler import odm
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@odm.model(index=False, store=False, description="A permission entry for ownership permission updates")
|
|
7
|
+
class PermissionRequest(odm.Model):
|
|
8
|
+
privilege: Literal["admins", "members", "owner"] = odm.Enum(
|
|
9
|
+
values=["admins", "members", "owner"],
|
|
10
|
+
description="The permission level to update",
|
|
11
|
+
)
|
|
12
|
+
user_id: str = odm.Keyword(description="The user ID to grant or revoke for the selected privilege.")
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
from collections.abc import Sequence
|
|
2
|
+
from typing import TYPE_CHECKING, Any, Literal, Protocol, cast
|
|
3
|
+
|
|
4
|
+
from flask import request
|
|
5
|
+
|
|
6
|
+
from howler.common.exceptions import ForbiddenException, HowlerTypeError, InvalidDataException
|
|
7
|
+
from howler.common.loader import datastore
|
|
8
|
+
from howler.odm.models.ownership import Ownership
|
|
9
|
+
from howler.odm.models.permission_request import PermissionRequest
|
|
10
|
+
from howler.odm.models.user import User
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from howler.datastore.collection import ESCollection
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class _VisibilityItem(Protocol):
|
|
17
|
+
@property
|
|
18
|
+
def owner(self) -> str: ...
|
|
19
|
+
|
|
20
|
+
@property
|
|
21
|
+
def type(self) -> str: ...
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def admins(self) -> Sequence[str]: ...
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def members(self) -> Sequence[str]: ...
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _is_allowed_to_change(level_requested: str, user: User, existing_item: Ownership) -> bool:
|
|
31
|
+
if "admin" in user.type:
|
|
32
|
+
return True
|
|
33
|
+
|
|
34
|
+
if user.uname not in existing_item.admins and user.uname != existing_item.owner:
|
|
35
|
+
return False
|
|
36
|
+
|
|
37
|
+
return level_requested != "owner" or user.uname == existing_item.owner
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _build_permissions_request() -> list[PermissionRequest]:
|
|
41
|
+
payload = cast(list[dict[str, Any]], request.json)
|
|
42
|
+
if not isinstance(payload, list):
|
|
43
|
+
raise InvalidDataException("Request body must be a JSON array.")
|
|
44
|
+
if not payload:
|
|
45
|
+
raise InvalidDataException("Request body must contain at least one permission.")
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
permission_requests = [PermissionRequest(entry) for entry in payload]
|
|
49
|
+
except (TypeError, ValueError, HowlerTypeError) as e:
|
|
50
|
+
raise InvalidDataException(message=str(e))
|
|
51
|
+
|
|
52
|
+
user_ids = [permission_request.user_id for permission_request in permission_requests]
|
|
53
|
+
if any(not user_id for user_id in user_ids):
|
|
54
|
+
raise InvalidDataException("user_id must contain a user.")
|
|
55
|
+
if len(user_ids) != len(set(user_ids)):
|
|
56
|
+
raise InvalidDataException("Each user can only be included once per request.")
|
|
57
|
+
|
|
58
|
+
return permission_requests
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def give_privileges(
|
|
62
|
+
id: str,
|
|
63
|
+
user: User,
|
|
64
|
+
object_type: type[Ownership],
|
|
65
|
+
refresh: Literal["true", "false", "wait_for"] | None = None,
|
|
66
|
+
) -> dict[str, Any]:
|
|
67
|
+
"""Grant privileges to users on an ownership object."""
|
|
68
|
+
permission_requests = _build_permissions_request()
|
|
69
|
+
|
|
70
|
+
storage = datastore()
|
|
71
|
+
index_name = object_type.__name__.lower()
|
|
72
|
+
collection: "ESCollection[Ownership]" = storage[index_name]
|
|
73
|
+
|
|
74
|
+
result, version = collection.get(id, as_obj=True, version=True)
|
|
75
|
+
if not result:
|
|
76
|
+
raise InvalidDataException(message=f"{index_name.capitalize()} {id} does not exist")
|
|
77
|
+
|
|
78
|
+
owner_requests = [
|
|
79
|
+
permission_request for permission_request in permission_requests if permission_request.privilege == "owner"
|
|
80
|
+
]
|
|
81
|
+
if len(owner_requests) > 1:
|
|
82
|
+
raise InvalidDataException("Only one owner can be set per request.")
|
|
83
|
+
|
|
84
|
+
for permission_request in permission_requests:
|
|
85
|
+
user_id = permission_request.user_id
|
|
86
|
+
if not _is_allowed_to_change(permission_request.privilege, user, result):
|
|
87
|
+
raise ForbiddenException("The user requesting the change is not allowed to make the change.")
|
|
88
|
+
|
|
89
|
+
if not storage.user.exists(user_id):
|
|
90
|
+
raise InvalidDataException(message=f"User {user_id} does not exist")
|
|
91
|
+
|
|
92
|
+
if permission_request.privilege != "owner" and user_id in result[permission_request.privilege]:
|
|
93
|
+
raise InvalidDataException(message=f"User {user_id} already has permission {permission_request.privilege}")
|
|
94
|
+
|
|
95
|
+
# Apply all changes only after the complete request has been validated.
|
|
96
|
+
for permission_request in permission_requests:
|
|
97
|
+
user_id = permission_request.user_id
|
|
98
|
+
if permission_request.privilege == "owner":
|
|
99
|
+
result.owner = user_id
|
|
100
|
+
|
|
101
|
+
for privilege in ("admins", "members"):
|
|
102
|
+
current_members = cast(list[str], result[privilege])
|
|
103
|
+
current_members[:] = [member_id for member_id in current_members if member_id != user_id]
|
|
104
|
+
else:
|
|
105
|
+
current_members = cast(list[str], result[permission_request.privilege])
|
|
106
|
+
other_privilege = "members" if permission_request.privilege == "admins" else "admins"
|
|
107
|
+
other_members = cast(list[str], result[other_privilege])
|
|
108
|
+
|
|
109
|
+
# Switching between non-owner privileges removes the previous privilege.
|
|
110
|
+
other_members[:] = [member_id for member_id in other_members if member_id != user_id]
|
|
111
|
+
current_members.append(user_id)
|
|
112
|
+
|
|
113
|
+
collection.save(id, result, version=version, refresh=refresh)
|
|
114
|
+
return result.as_primitives()
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def remove_privileges( # noqa: C901
|
|
118
|
+
id: str,
|
|
119
|
+
user: User,
|
|
120
|
+
object_type: type[Ownership],
|
|
121
|
+
refresh: Literal["true", "false", "wait_for"] | None = None,
|
|
122
|
+
) -> dict[str, Any]:
|
|
123
|
+
"""Revoke privileges from users on an ownership object."""
|
|
124
|
+
permission_requests = _build_permissions_request()
|
|
125
|
+
|
|
126
|
+
storage = datastore()
|
|
127
|
+
index_name = object_type.__name__.lower()
|
|
128
|
+
collection: "ESCollection[Ownership]" = storage[index_name]
|
|
129
|
+
|
|
130
|
+
result, version = collection.get(id, as_obj=True, version=True)
|
|
131
|
+
if not result:
|
|
132
|
+
raise InvalidDataException(message=f"{index_name.capitalize()} {id} does not exist")
|
|
133
|
+
|
|
134
|
+
for permission_request in permission_requests:
|
|
135
|
+
if not _is_allowed_to_change(permission_request.privilege, user, result):
|
|
136
|
+
raise ForbiddenException("The user requesting the change is not allowed to make the change.")
|
|
137
|
+
|
|
138
|
+
if permission_request.privilege == "owner":
|
|
139
|
+
raise InvalidDataException(message="You cannot remove the owner privilege. Only transfer is allowed.")
|
|
140
|
+
|
|
141
|
+
user_id = permission_request.user_id
|
|
142
|
+
current_members = result.admins if permission_request.privilege == "admins" else result.members
|
|
143
|
+
if user_id not in current_members:
|
|
144
|
+
raise InvalidDataException(
|
|
145
|
+
message=f"The user '{user_id}' does not have the '{permission_request.privilege}' privilege."
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
for permission_request in permission_requests:
|
|
149
|
+
cast(list[str], result[permission_request.privilege]).remove(permission_request.user_id)
|
|
150
|
+
|
|
151
|
+
collection.save(id, result, version=version, refresh=refresh)
|
|
152
|
+
return result.as_primitives()
|
|
@@ -244,7 +244,7 @@ suppress-none-returning = true
|
|
|
244
244
|
[tool.poetry]
|
|
245
245
|
package-mode = true
|
|
246
246
|
name = "howler-api"
|
|
247
|
-
version = "4.1.0.
|
|
247
|
+
version = "4.1.0.dev1643"
|
|
248
248
|
description = "Howler - API server"
|
|
249
249
|
authors = [
|
|
250
250
|
"Canadian Centre for Cyber Security <howler@cyber.gc.ca>",
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
from typing import Literal
|
|
2
|
-
|
|
3
|
-
from howler import odm
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
@odm.model(index=False, store=False, description="Request payload for ownership permission updates")
|
|
7
|
-
class PermissionRequest(odm.Model):
|
|
8
|
-
privilege: Literal["admins", "members", "owner"] = odm.Enum(
|
|
9
|
-
values=["admins", "members", "owner"],
|
|
10
|
-
description="The permission level to update",
|
|
11
|
-
)
|
|
12
|
-
user_ids: list[str] = odm.List(
|
|
13
|
-
odm.Keyword(),
|
|
14
|
-
description="The list of user IDs to grant or revoke for the selected privilege.",
|
|
15
|
-
default=[],
|
|
16
|
-
optional=False,
|
|
17
|
-
)
|
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
from collections.abc import Sequence
|
|
2
|
-
from typing import TYPE_CHECKING, Any, Literal, Protocol, cast
|
|
3
|
-
|
|
4
|
-
from flask import request
|
|
5
|
-
|
|
6
|
-
from howler.common.exceptions import ForbiddenException, HowlerTypeError, InvalidDataException
|
|
7
|
-
from howler.common.loader import datastore
|
|
8
|
-
from howler.odm.models.ownership import Ownership
|
|
9
|
-
from howler.odm.models.permission_request import PermissionRequest
|
|
10
|
-
from howler.odm.models.user import User
|
|
11
|
-
|
|
12
|
-
if TYPE_CHECKING:
|
|
13
|
-
from howler.datastore.collection import ESCollection
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class _VisibilityItem(Protocol):
|
|
17
|
-
@property
|
|
18
|
-
def owner(self) -> str: ...
|
|
19
|
-
|
|
20
|
-
@property
|
|
21
|
-
def type(self) -> str: ...
|
|
22
|
-
|
|
23
|
-
@property
|
|
24
|
-
def admins(self) -> Sequence[str]: ...
|
|
25
|
-
|
|
26
|
-
@property
|
|
27
|
-
def members(self) -> Sequence[str]: ...
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def _is_allowed_to_change(level_requested: str, user: User, existing_item: Ownership) -> bool:
|
|
31
|
-
if "admin" in user.type:
|
|
32
|
-
return True
|
|
33
|
-
|
|
34
|
-
if user.uname not in existing_item.admins and user.uname != existing_item.owner:
|
|
35
|
-
return False
|
|
36
|
-
|
|
37
|
-
return level_requested != "owner" or user.uname == existing_item.owner
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
def _build_permissions_request() -> PermissionRequest:
|
|
41
|
-
payload = cast(dict[str, Any], request.json)
|
|
42
|
-
if not isinstance(payload, dict):
|
|
43
|
-
raise InvalidDataException("Request body must be a JSON object.")
|
|
44
|
-
|
|
45
|
-
try:
|
|
46
|
-
permission_request = PermissionRequest(payload)
|
|
47
|
-
except (ValueError, HowlerTypeError) as e:
|
|
48
|
-
raise InvalidDataException(message=str(e))
|
|
49
|
-
|
|
50
|
-
if not permission_request.user_ids:
|
|
51
|
-
raise InvalidDataException("user_ids must contain at least one user.")
|
|
52
|
-
|
|
53
|
-
return permission_request
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
def give_privilege(
|
|
57
|
-
id: str,
|
|
58
|
-
user: User,
|
|
59
|
-
object_type: type[Ownership],
|
|
60
|
-
refresh: Literal["true", "false", "wait_for"] | None = None,
|
|
61
|
-
) -> dict[str, Any]:
|
|
62
|
-
"""Grant a privilege to one or more users on an ownership object."""
|
|
63
|
-
permission_request = _build_permissions_request()
|
|
64
|
-
user_ids = list(dict.fromkeys(permission_request.user_ids))
|
|
65
|
-
|
|
66
|
-
storage = datastore()
|
|
67
|
-
index_name = object_type.__name__.lower()
|
|
68
|
-
collection: "ESCollection[Ownership]" = storage[index_name]
|
|
69
|
-
|
|
70
|
-
result, version = collection.get(id, as_obj=True, version=True)
|
|
71
|
-
if not result:
|
|
72
|
-
raise InvalidDataException(message=f"{index_name.capitalize()} {id} does not exist")
|
|
73
|
-
|
|
74
|
-
if not _is_allowed_to_change(permission_request.privilege, user, result):
|
|
75
|
-
raise ForbiddenException("The user requesting the change is not allowed to make the change.")
|
|
76
|
-
|
|
77
|
-
if permission_request.privilege == "owner" and len(user_ids) != 1:
|
|
78
|
-
raise InvalidDataException("When setting the owner, user_ids must be a single entry long.")
|
|
79
|
-
|
|
80
|
-
errors: list[str] = []
|
|
81
|
-
for user_id in user_ids:
|
|
82
|
-
if not storage.user.exists(user_id):
|
|
83
|
-
errors.append(f"User {user_id} does not exist")
|
|
84
|
-
continue
|
|
85
|
-
|
|
86
|
-
if permission_request.privilege != "owner" and user_id in result[permission_request.privilege]:
|
|
87
|
-
errors.append(f"User {user_id} already has permission {permission_request.privilege}")
|
|
88
|
-
|
|
89
|
-
if errors:
|
|
90
|
-
raise InvalidDataException(message=f"Failed to grant privileges for some users: {'; '.join(errors)}")
|
|
91
|
-
|
|
92
|
-
if permission_request.privilege == "owner":
|
|
93
|
-
new_owner = user_ids[0]
|
|
94
|
-
result.owner = new_owner
|
|
95
|
-
|
|
96
|
-
# Ownership supersedes any non-owner privilege held by the new owner.
|
|
97
|
-
for privilege in ("admins", "members"):
|
|
98
|
-
current_members = cast(list[str], result[privilege])
|
|
99
|
-
current_members[:] = [user_id for user_id in current_members if user_id != new_owner]
|
|
100
|
-
else:
|
|
101
|
-
current_members = cast(list[str], result[permission_request.privilege])
|
|
102
|
-
other_privilege = "members" if permission_request.privilege == "admins" else "admins"
|
|
103
|
-
other_members = cast(list[str], result[other_privilege])
|
|
104
|
-
|
|
105
|
-
# Switching between non-owner privileges removes the previous privilege.
|
|
106
|
-
other_members[:] = [user_id for user_id in other_members if user_id not in user_ids]
|
|
107
|
-
current_members.extend(user_ids)
|
|
108
|
-
|
|
109
|
-
collection.save(id, result, version=version, refresh=refresh)
|
|
110
|
-
return result.as_primitives()
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
def remove_privilege( # noqa: C901
|
|
114
|
-
id: str,
|
|
115
|
-
user: User,
|
|
116
|
-
object_type: type[Ownership],
|
|
117
|
-
refresh: Literal["true", "false", "wait_for"] | None = None,
|
|
118
|
-
) -> dict[str, Any]:
|
|
119
|
-
"""Revoke a privilege from one or more users on an ownership object."""
|
|
120
|
-
permission_request = _build_permissions_request()
|
|
121
|
-
user_ids = list(dict.fromkeys(permission_request.user_ids))
|
|
122
|
-
|
|
123
|
-
storage = datastore()
|
|
124
|
-
index_name = object_type.__name__.lower()
|
|
125
|
-
collection: "ESCollection[Ownership]" = storage[index_name]
|
|
126
|
-
|
|
127
|
-
result, version = collection.get(id, as_obj=True, version=True)
|
|
128
|
-
if not result:
|
|
129
|
-
raise InvalidDataException(message=f"{index_name.capitalize()} {id} does not exist")
|
|
130
|
-
|
|
131
|
-
if not _is_allowed_to_change(permission_request.privilege, user, result):
|
|
132
|
-
raise ForbiddenException("The user requesting the change is not allowed to make the change.")
|
|
133
|
-
|
|
134
|
-
if permission_request.privilege == "owner":
|
|
135
|
-
raise InvalidDataException(message="You cannot remove the owner privilege. Only transfer is allowed.")
|
|
136
|
-
|
|
137
|
-
errors: list[str] = []
|
|
138
|
-
current_members = result.admins if permission_request.privilege == "admins" else result.members
|
|
139
|
-
for user_id in user_ids:
|
|
140
|
-
if user_id not in current_members:
|
|
141
|
-
errors.append(f"The user '{user_id}' does not have the '{permission_request.privilege}' privilege.")
|
|
142
|
-
|
|
143
|
-
if errors:
|
|
144
|
-
raise InvalidDataException(message=f"Failed to revoke privileges for some users: {'; '.join(errors)}")
|
|
145
|
-
|
|
146
|
-
for user_id in user_ids:
|
|
147
|
-
cast(list[str], result[permission_request.privilege]).remove(user_id)
|
|
148
|
-
|
|
149
|
-
collection.save(id, result, version=version, refresh=refresh)
|
|
150
|
-
return result.as_primitives()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/cronjobs/action_queue_worker.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/datastore/migrations/__init__.py
RENAMED
|
File without changes
|
{howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/datastore/migrations/action_owner.py
RENAMED
|
File without changes
|
|
File without changes
|
{howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/datastore/migrations/fix_process.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{howler_api-4.1.0.dev1635 → howler_api-4.1.0.dev1643}/howler/external/generate_sigma_rules.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|